// // 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 names; public TupleTypeExpr (TypeArguments elements, List 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 elements; private NamedTupleSpec (InflatedTypeSpec tupleDefinition, IList elements) : base (tupleDefinition.Kind, tupleDefinition.DeclaringType, tupleDefinition.MemberDefinition, null, tupleDefinition.Modifiers) { tuple = tupleDefinition; this.elements = elements; state |= StateFlags.HasNamedTupleElement | StateFlags.Tuple; } public IList 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 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 string Name { get; private set; } public Expression Expr { get; set; } public Location Location { get; private set; } } sealed class TupleLiteral : Expression { List elements; public TupleLiteral (List elements, Location loc) { this.elements = elements; this.loc = loc; } public List Elements { get { return elements; } } 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 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 (); 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 elements; Expression source; public TupleLiteralConversion (Expression source, TypeSpec type, List 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 targetExprs; List variablesToInfer; Expression instance; public TupleDeconstruct (List targetExprs, Expression source, Location loc) { this.source = source; this.targetExprs = targetExprs; this.loc = loc; } public TupleDeconstruct (List targetExprs, List variables, Expression source, Location loc) : this (targetExprs, source, loc) { this.variablesToInfer = variables; } 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) { if (src_type.Arity != targetExprs.Count) { rc.Report.Error (8132, loc, "Cannot deconstruct a tuple of `{0}' elements into `{1}' variables", src_type.Arity.ToString (), targetExprs.Count.ToString ()); 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 < targetExprs.Count; ++i) { var tle = src_type.TypeArguments [i]; var lv = variablesToInfer? [i]; if (lv != null) { if (InternalType.HasNoType (tle)) { rc.Report.Error (8130, Location, "Cannot infer the type of implicitly-typed deconstruction variable `{0}'", lv.Name); lv.Type = InternalType.ErrorType; continue; } lv.Type = tle; lv.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; 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) { throw new NotImplementedException (); } public override void EmitStatement (EmitContext 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 (); foreach (var lv in variablesToInfer) lv.CreateBuilder (ec); EmitStatement (ec); } public override void FlowAnalysis (FlowAnalysisContext fc) { foreach (var expr in targetExprs) expr.FlowAnalysis (fc); } public void SetGeneratedFieldAssigned (FlowAnalysisContext fc) { if (variablesToInfer == null) return; foreach (var lv in variablesToInfer) fc.SetVariableAssigned (lv.VariableInfo); } } }