19234507ba
Former-commit-id: 3494343bcc9ddb42b36b82dd9ae7b69e85e0229f
626 lines
16 KiB
C#
626 lines
16 KiB
C#
//
|
|
// tuples.cs: Tuples types
|
|
//
|
|
// Author:
|
|
// Marek Safar (marek.safar@gmail.com)
|
|
//
|
|
// Dual licensed under the terms of the MIT X11 or GNU GPL
|
|
//
|
|
// Copyright (C) Microsoft Corporation.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Globalization;
|
|
|
|
#if STATIC
|
|
using MetaType = IKVM.Reflection.Type;
|
|
using IKVM.Reflection;
|
|
using IKVM.Reflection.Emit;
|
|
#else
|
|
using MetaType = System.Type;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
#endif
|
|
|
|
namespace Mono.CSharp
|
|
{
|
|
class TupleTypeExpr : TypeExpr
|
|
{
|
|
TypeArguments elements;
|
|
List<string> names;
|
|
|
|
public TupleTypeExpr (TypeArguments elements, List<string> names, Location loc)
|
|
{
|
|
this.elements = elements;
|
|
this.names = names;
|
|
this.loc = loc;
|
|
}
|
|
|
|
public override TypeSpec ResolveAsType (IMemberContext mc, bool allowUnboundTypeArguments = false)
|
|
{
|
|
var length = elements.Count;
|
|
if (length > 7)
|
|
throw new NotImplementedException ("tuples > 7");
|
|
|
|
eclass = ExprClass.Type;
|
|
|
|
var otype = mc.Module.PredefinedTypes.Tuples [length - 1].Resolve ();
|
|
if (otype == null)
|
|
return null;
|
|
|
|
GenericTypeExpr ctype = new GenericTypeExpr (otype, elements, loc);
|
|
type = ctype.ResolveAsType (mc);
|
|
|
|
if (names != null && CheckElementNames (mc) && type != null) {
|
|
type = NamedTupleSpec.MakeType (mc.Module, (InflatedTypeSpec) type, names);
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
bool CheckElementNames (IMemberContext mc)
|
|
{
|
|
int first_name = -1;
|
|
for (int i = 0; i < names.Count; ++i) {
|
|
var name = names [i];
|
|
if (name == null)
|
|
continue;
|
|
|
|
if (IsReservedName (name)) {
|
|
mc.Module.Compiler.Report.Error (8126, loc, "The tuple element name `{0}' is reserved", name);
|
|
names [i] = null;
|
|
continue;
|
|
}
|
|
|
|
if (name.StartsWith ("Item", StringComparison.Ordinal)) {
|
|
var idx = name.Substring (4);
|
|
uint value;
|
|
if (uint.TryParse (idx, NumberStyles.Integer, CultureInfo.InvariantCulture, out value) && value != i + 1) {
|
|
mc.Module.Compiler.Report.Error (8125, loc, "The tuple element name `{0}' can only be used at position {1}", name, idx);
|
|
names [i] = null;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (first_name < 0) {
|
|
first_name = i;
|
|
continue;
|
|
}
|
|
|
|
for (int ii = first_name; ii < i; ++ii) {
|
|
if (name == names [ii]) {
|
|
mc.Module.Compiler.Report.Error (8127, loc, "The tuple element name `{0}' is a duplicate", name);
|
|
names [i] = null;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return first_name >= 0;
|
|
}
|
|
|
|
static bool IsReservedName (string name)
|
|
{
|
|
switch (name) {
|
|
case "CompareTo":
|
|
case "Deconstruct":
|
|
case "Equals":
|
|
case "GetHashCode":
|
|
case "Rest":
|
|
case "ToString":
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public override string GetSignatureForError ()
|
|
{
|
|
var sb = new StringBuilder ();
|
|
for (int i = 0; i < elements.Count; ++i) {
|
|
sb.Append (elements.Arguments [i].GetSignatureForError ());
|
|
|
|
if (names [i] != null) {
|
|
sb.Append (" ");
|
|
sb.Append (names [i]);
|
|
}
|
|
|
|
if (i != 0)
|
|
sb.Append (",");
|
|
}
|
|
|
|
return sb.ToString ();
|
|
}
|
|
}
|
|
|
|
public class NamedTupleSpec : TypeSpec
|
|
{
|
|
InflatedTypeSpec tuple;
|
|
IList<string> elements;
|
|
|
|
private NamedTupleSpec (InflatedTypeSpec tupleDefinition, IList<string> elements)
|
|
: base (tupleDefinition.Kind, tupleDefinition.DeclaringType, tupleDefinition.MemberDefinition, null, tupleDefinition.Modifiers)
|
|
{
|
|
tuple = tupleDefinition;
|
|
this.elements = elements;
|
|
|
|
state |= StateFlags.HasNamedTupleElement | StateFlags.Tuple;
|
|
}
|
|
|
|
public IList<string> Elements {
|
|
get {
|
|
return elements;
|
|
}
|
|
}
|
|
|
|
public override TypeSpec [] TypeArguments {
|
|
get {
|
|
return tuple.TypeArguments;
|
|
}
|
|
}
|
|
|
|
protected override void InitializeMemberCache (bool onlyTypes)
|
|
{
|
|
cache = tuple.MemberCache;
|
|
}
|
|
|
|
public override MetaType GetMetaInfo ()
|
|
{
|
|
return tuple.GetMetaInfo ();
|
|
}
|
|
|
|
public MemberSpec FindElement (IMemberContext mc, string name, Location loc)
|
|
{
|
|
// TODO: cache it
|
|
for (int i = 0; i < elements.Count; ++i) {
|
|
var ename = elements [i];
|
|
if (ename == null || ename != name)
|
|
continue;
|
|
|
|
var member_name = GetElementPropertyName (i);
|
|
var ms = MemberCache.FindMember (tuple, MemberFilter.Field (member_name, null), BindingRestriction.DeclaredOnly | BindingRestriction.InstanceOnly);
|
|
if (ms == null) {
|
|
mc.Module.Compiler.Report.Error (8128, loc, "Member `{0}' was not found on type '{1}'", member_name, tuple.GetSignatureForError ());
|
|
return null;
|
|
}
|
|
|
|
return ms;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public override string GetSignatureForError ()
|
|
{
|
|
//
|
|
// csc reports names as well but it seems to me redundant when
|
|
// they are not included in any type conversion
|
|
//
|
|
return tuple.GetSignatureForError ();
|
|
}
|
|
|
|
public string GetSignatureForErrorWithNames ()
|
|
{
|
|
// TODO: Include names
|
|
return tuple.GetSignatureForError ();
|
|
}
|
|
|
|
public static NamedTupleSpec MakeType (ModuleContainer module, InflatedTypeSpec tupleType, IList<string> names)
|
|
{
|
|
// TODO: cache it
|
|
return new NamedTupleSpec (tupleType, names);
|
|
}
|
|
|
|
public static string GetElementPropertyName (int index)
|
|
{
|
|
return "Item" + (index + 1).ToString (CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
public static bool CheckOverrideName (IParametersMember member, IParametersMember baseMember)
|
|
{
|
|
var btypes = baseMember.Parameters.Types;
|
|
var ttypes = member.Parameters.Types;
|
|
for (int ii = 0; ii < baseMember.Parameters.Count; ++ii) {
|
|
if (!CheckOverrideName (ttypes [ii], btypes [ii])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static bool CheckOverrideName (TypeSpec type, TypeSpec baseType)
|
|
{
|
|
var btype_ntuple = baseType as NamedTupleSpec;
|
|
var mtype_ntuple = type as NamedTupleSpec;
|
|
if (btype_ntuple == null && mtype_ntuple == null)
|
|
return true;
|
|
|
|
if (btype_ntuple == null || mtype_ntuple == null)
|
|
return false;
|
|
|
|
var b_elements = btype_ntuple.elements;
|
|
var m_elements = mtype_ntuple.elements;
|
|
for (int i = 0; i < b_elements.Count; ++i) {
|
|
if (b_elements [i] != m_elements [i])
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
class TupleLiteralElement
|
|
{
|
|
public TupleLiteralElement (string name, Expression expr, Location loc)
|
|
{
|
|
this.Name = name;
|
|
this.Expr = expr;
|
|
this.Location = loc;
|
|
}
|
|
|
|
public TupleLiteralElement (Expression expr)
|
|
{
|
|
this.Expr = expr;
|
|
this.Location = expr.Location;
|
|
}
|
|
|
|
public TupleLiteralElement Clone (CloneContext clonectx)
|
|
{
|
|
return new TupleLiteralElement (Name, Expr.Clone (clonectx), Location);
|
|
}
|
|
|
|
public string Name { get; private set; }
|
|
public Expression Expr { get; set; }
|
|
public Location Location { get; private set; }
|
|
}
|
|
|
|
sealed class TupleLiteral : Expression
|
|
{
|
|
List<TupleLiteralElement> elements;
|
|
|
|
public TupleLiteral (List<TupleLiteralElement> elements, Location loc)
|
|
{
|
|
this.elements = elements;
|
|
this.loc = loc;
|
|
}
|
|
|
|
public List<TupleLiteralElement> Elements {
|
|
get {
|
|
return elements;
|
|
}
|
|
}
|
|
|
|
protected override void CloneTo (CloneContext clonectx, Expression t)
|
|
{
|
|
var clone = new List<TupleLiteralElement> (elements.Count);
|
|
foreach (var te in elements)
|
|
clone.Add (te.Clone (clonectx));
|
|
|
|
TupleLiteral target = (TupleLiteral)t;
|
|
target.elements = clone;
|
|
}
|
|
|
|
public static bool ContainsNoTypeElement (TypeSpec type)
|
|
{
|
|
var ta = type.TypeArguments;
|
|
|
|
for (int i = 0; i < ta.Length; ++i) {
|
|
var et = ta [i];
|
|
if (InternalType.HasNoType (et))
|
|
return true;
|
|
|
|
if (et.IsTupleType && ContainsNoTypeElement (et))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public override Expression CreateExpressionTree (ResolveContext rc)
|
|
{
|
|
rc.Report.Error (8143, loc, "An expression tree cannot contain a tuple literal");
|
|
return ErrorExpression.Instance;
|
|
}
|
|
|
|
protected override Expression DoResolve (ResolveContext rc)
|
|
{
|
|
var ta = new TypeArguments ();
|
|
List<string> names = null;
|
|
|
|
for (int i = 0; i < elements.Count; ++i) {
|
|
var el = elements [i];
|
|
var expr = el.Expr.Resolve (rc);
|
|
if (expr == null) {
|
|
el.Expr = null;
|
|
ta = null;
|
|
continue;
|
|
}
|
|
|
|
if (expr.Type.Kind == MemberKind.Void) {
|
|
rc.Report.Error (8210, expr.Location, "A tuple literal cannot not contain a value of type `{0}'", expr.Type.GetSignatureForError ());
|
|
expr = null;
|
|
ta = null;
|
|
continue;
|
|
}
|
|
|
|
if (el.Name != null) {
|
|
if (names == null) {
|
|
names = new List<string> ();
|
|
for (int ii = 0; ii < i; ++ii) {
|
|
names.Add (null);
|
|
}
|
|
}
|
|
|
|
names.Add (el.Name);
|
|
}
|
|
|
|
el.Expr = expr;
|
|
|
|
if (ta != null)
|
|
ta.Add (new TypeExpression (expr.Type, expr.Location));
|
|
}
|
|
|
|
eclass = ExprClass.Value;
|
|
|
|
if (ta == null)
|
|
return null;
|
|
|
|
var t = new TupleTypeExpr (ta, names, loc);
|
|
type = t.ResolveAsType (rc) ?? InternalType.ErrorType;
|
|
|
|
return this;
|
|
}
|
|
|
|
public override void Emit (EmitContext ec)
|
|
{
|
|
foreach (var el in elements) {
|
|
el.Expr.Emit (ec);
|
|
}
|
|
|
|
// TODO: Needs arguments check
|
|
var ctor = MemberCache.FindMember (type, MemberFilter.Constructor (null), BindingRestriction.DeclaredOnly | BindingRestriction.InstanceOnly) as MethodSpec;
|
|
|
|
ec.Emit (OpCodes.Newobj, ctor);
|
|
}
|
|
|
|
public override void Error_ValueCannotBeConverted (ResolveContext rc, TypeSpec target, bool expl)
|
|
{
|
|
rc.Report.Error (8135, Location, "Tuple literal `{0}' cannot be converted to type `{1}'", type.GetSignatureForError (), target.GetSignatureForError ());
|
|
}
|
|
}
|
|
|
|
//
|
|
// Used when converting from a tuple literal or tuple instance to different tuple type
|
|
//
|
|
class TupleLiteralConversion : Expression
|
|
{
|
|
List<Expression> elements;
|
|
Expression source;
|
|
|
|
public TupleLiteralConversion (Expression source, TypeSpec type, List<Expression> elements, Location loc)
|
|
{
|
|
this.source = source;
|
|
this.type = type;
|
|
this.elements = elements;
|
|
this.loc = loc;
|
|
|
|
eclass = source.eclass;
|
|
}
|
|
|
|
public override Expression CreateExpressionTree (ResolveContext rc)
|
|
{
|
|
rc.Report.Error (8144, loc, "An expression tree cannot contain a tuple conversion");
|
|
return null;
|
|
}
|
|
|
|
protected override Expression DoResolve (ResolveContext rc)
|
|
{
|
|
// Should not be reached
|
|
throw new NotSupportedException ();
|
|
}
|
|
|
|
public override void Emit (EmitContext ec)
|
|
{
|
|
if (!(source is TupleLiteral)) {
|
|
var assign = source as CompilerAssign;
|
|
if (assign != null)
|
|
assign.EmitStatement (ec);
|
|
else
|
|
source.Emit (ec);
|
|
}
|
|
|
|
foreach (var el in elements) {
|
|
el.Emit (ec);
|
|
}
|
|
|
|
// TODO: Needs arguments check
|
|
var ctor = MemberCache.FindMember (type, MemberFilter.Constructor (null), BindingRestriction.DeclaredOnly | BindingRestriction.InstanceOnly) as MethodSpec;
|
|
|
|
ec.Emit (OpCodes.Newobj, ctor);
|
|
}
|
|
}
|
|
|
|
class TupleDeconstruct : ExpressionStatement, IAssignMethod
|
|
{
|
|
Expression source;
|
|
List<Expression> targetExprs;
|
|
List<BlockVariable> variables;
|
|
Expression instance;
|
|
|
|
public TupleDeconstruct (List<Expression> targetExprs, Expression source, Location loc)
|
|
{
|
|
this.source = source;
|
|
this.targetExprs = targetExprs;
|
|
this.loc = loc;
|
|
}
|
|
|
|
public TupleDeconstruct (List<BlockVariable> variables, Expression source, Location loc)
|
|
{
|
|
this.source = source;
|
|
this.variables = variables;
|
|
this.loc = loc;
|
|
}
|
|
|
|
public override Expression CreateExpressionTree (ResolveContext ec)
|
|
{
|
|
ec.Report.Error (832, loc, "An expression tree cannot contain an assignment operator");
|
|
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
protected override Expression DoResolve (ResolveContext rc)
|
|
{
|
|
var src = source.Resolve (rc);
|
|
if (src == null)
|
|
return null;
|
|
|
|
if (InternalType.HasNoType (src.Type)) {
|
|
rc.Report.Error (8131, source.Location, "Deconstruct assignment requires an expression with a type on the right-hand-side");
|
|
return null;
|
|
}
|
|
|
|
var src_type = src.Type;
|
|
|
|
if (src_type.IsTupleType) {
|
|
int target_count;
|
|
|
|
if (targetExprs == null) {
|
|
target_count = variables.Count;
|
|
targetExprs = new List<Expression> (target_count);
|
|
} else {
|
|
target_count = targetExprs.Count;
|
|
}
|
|
|
|
if (src_type.Arity != target_count) {
|
|
rc.Report.Error (8132, loc, "Cannot deconstruct a tuple of `{0}' elements into `{1}' variables",
|
|
src_type.Arity.ToString (CultureInfo.InvariantCulture), target_count.ToString (CultureInfo.InvariantCulture));
|
|
return null;
|
|
}
|
|
|
|
var tupleLiteral = src as TupleLiteral;
|
|
if (tupleLiteral == null && !ExpressionAnalyzer.IsInexpensiveLoad (src)) {
|
|
var expr_variable = LocalVariable.CreateCompilerGenerated (source.Type, rc.CurrentBlock, loc);
|
|
source = new CompilerAssign (expr_variable.CreateReferenceExpression (rc, loc), source, loc);
|
|
instance = expr_variable.CreateReferenceExpression (rc, loc);
|
|
}
|
|
|
|
for (int i = 0; i < target_count; ++i) {
|
|
var tle = src_type.TypeArguments [i];
|
|
|
|
if (variables != null) {
|
|
var variable = variables [i].Variable;
|
|
|
|
if (variable.Type == InternalType.Discard) {
|
|
variables [i] = null;
|
|
targetExprs.Add (EmptyExpressionStatement.Instance);
|
|
continue;
|
|
}
|
|
|
|
var variable_type = variables [i].TypeExpression;
|
|
|
|
targetExprs.Add (new LocalVariableReference (variable, variable.Location));
|
|
|
|
if (variable_type is VarExpr) {
|
|
if (InternalType.HasNoType (tle)) {
|
|
rc.Report.Error (8130, Location, "Cannot infer the type of implicitly-typed deconstruction variable `{0}'", variable.Name);
|
|
tle = InternalType.ErrorType;
|
|
}
|
|
|
|
variable.Type = tle;
|
|
} else {
|
|
variable.Type = variable_type.ResolveAsType (rc);
|
|
}
|
|
|
|
variable.PrepareAssignmentAnalysis ((BlockContext)rc);
|
|
}
|
|
|
|
var element_src = tupleLiteral == null ? new MemberAccess (instance, NamedTupleSpec.GetElementPropertyName (i)) : tupleLiteral.Elements [i].Expr;
|
|
targetExprs [i] = new SimpleAssign (targetExprs [i], element_src).Resolve (rc);
|
|
}
|
|
|
|
eclass = ExprClass.Value;
|
|
|
|
// TODO: The type is same only if there is no target element conversion
|
|
// var res = (/*byte*/ b, /*short*/ s) = (2, 4);
|
|
type = src.Type;
|
|
return this;
|
|
}
|
|
|
|
if (src_type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
|
|
rc.Report.Error (8133, loc, "Cannot deconstruct dynamic objects");
|
|
return null;
|
|
}
|
|
|
|
/*
|
|
var args = new Arguments (targetExprs.Count);
|
|
foreach (var t in targetExprs) {
|
|
args.Add (new Argument (t, Argument.AType.Out));
|
|
}
|
|
|
|
var invocation = new Invocation (new MemberAccess (src, "Deconstruct"), args);
|
|
var res = invocation.Resolve (rc);
|
|
*/
|
|
|
|
throw new NotImplementedException ("Custom deconstruct");
|
|
}
|
|
|
|
public override void Emit (EmitContext ec)
|
|
{
|
|
if (instance != null)
|
|
((ExpressionStatement)source).EmitStatement (ec);
|
|
|
|
foreach (ExpressionStatement expr in targetExprs)
|
|
expr.Emit (ec);
|
|
|
|
var ctor = MemberCache.FindMember (type, MemberFilter.Constructor (null), BindingRestriction.DeclaredOnly | BindingRestriction.InstanceOnly) as MethodSpec;
|
|
ec.Emit (OpCodes.Newobj, ctor);
|
|
}
|
|
|
|
public override void EmitStatement (EmitContext ec)
|
|
{
|
|
if (variables != null) {
|
|
foreach (var lv in variables) {
|
|
lv?.Variable.CreateBuilder (ec);
|
|
}
|
|
}
|
|
|
|
if (instance != null)
|
|
((ExpressionStatement) source).EmitStatement (ec);
|
|
|
|
foreach (ExpressionStatement expr in targetExprs)
|
|
expr.EmitStatement (ec);
|
|
}
|
|
|
|
public void Emit (EmitContext ec, bool leave_copy)
|
|
{
|
|
throw new NotImplementedException ();
|
|
}
|
|
|
|
public void EmitAssign (EmitContext ec, Expression source, bool leave_copy, bool isCompound)
|
|
{
|
|
if (leave_copy)
|
|
throw new NotImplementedException ();
|
|
|
|
EmitStatement (ec);
|
|
}
|
|
|
|
public override void FlowAnalysis (FlowAnalysisContext fc)
|
|
{
|
|
source.FlowAnalysis (fc);
|
|
foreach (var expr in targetExprs)
|
|
expr.FlowAnalysis (fc);
|
|
}
|
|
|
|
public void SetGeneratedFieldAssigned (FlowAnalysisContext fc)
|
|
{
|
|
if (variables == null)
|
|
return;
|
|
|
|
foreach (var lv in variables)
|
|
fc.SetVariableAssigned (lv.Variable.VariableInfo);
|
|
}
|
|
}
|
|
} |