From 85490058c8335b60441ef02863f861aaabfa51dd Mon Sep 17 00:00:00 2001 From: Marek Safar Date: Thu, 28 Jan 2016 13:08:11 +0100 Subject: [PATCH] [mcs] Implements null operator for dynamic expressions. Fixes #37801, #37824 --- mcs/mcs/dynamic.cs | 147 ++++++++++++++++++++++++++-- mcs/mcs/expression.cs | 13 ++- mcs/tests/dtest-null-operator-01.cs | 57 +++++++++++ mcs/tests/ver-il-net_4_x.xml | 29 ++++++ 4 files changed, 235 insertions(+), 11 deletions(-) create mode 100644 mcs/tests/dtest-null-operator-01.cs diff --git a/mcs/mcs/dynamic.cs b/mcs/mcs/dynamic.cs index 3b2a5b4b18d..09d78302854 100644 --- a/mcs/mcs/dynamic.cs +++ b/mcs/mcs/dynamic.cs @@ -13,6 +13,11 @@ using System; using System.Linq; using SLE = System.Linq.Expressions; using System.Dynamic; +#if STATIC +using IKVM.Reflection.Emit; +#else +using System.Reflection.Emit; +#endif namespace Mono.CSharp { @@ -314,6 +319,50 @@ namespace Mono.CSharp EmitCall (ec, binder_expr, arguments, true); } + protected void EmitConditionalAccess (EmitContext ec) + { + var a_expr = arguments [0].Expr; + + var des = a_expr as DynamicExpressionStatement; + if (des != null) { + des.EmitConditionalAccess (ec); + } + + if (HasConditionalAccess ()) { + var NullOperatorLabel = ec.DefineLabel (); + + if (ExpressionAnalyzer.IsInexpensiveLoad (a_expr)) { + a_expr.Emit (ec); + } else { + var lt = new LocalTemporary (a_expr.Type); + lt.EmitAssign (ec, a_expr, true, false); + + Arguments [0].Expr = lt; + } + + ec.Emit (OpCodes.Brtrue_S, NullOperatorLabel); + + if (!ec.ConditionalAccess.Statement) { + if (ec.ConditionalAccess.Type.IsNullableType) + Nullable.LiftedNull.Create (ec.ConditionalAccess.Type, Location.Null).Emit (ec); + else + ec.EmitNull (); + } + + ec.Emit (OpCodes.Br, ec.ConditionalAccess.EndLabel); + ec.MarkLabel (NullOperatorLabel); + + return; + } + + if (a_expr.HasConditionalAccess ()) { + var lt = new LocalTemporary (a_expr.Type); + lt.EmitAssign (ec, a_expr, false, false); + + Arguments [0].Expr = lt; + } + } + protected void EmitCall (EmitContext ec, Expression binder, Arguments arguments, bool isStatement) { // @@ -502,6 +551,24 @@ namespace Mono.CSharp StatementExpression s = new StatementExpression (new SimpleAssign (site_field_expr, new Invocation (new MemberAccess (instanceAccessExprType, "Create"), args))); using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) { + + var conditionalAccessReceiver = IsConditionalAccessReceiver; + var ca = ec.ConditionalAccess; + + if (conditionalAccessReceiver) { + ec.ConditionalAccess = new ConditionalAccessContext (type, ec.DefineLabel ()) { + Statement = isStatement + }; + + // + // Emit conditional access expressions before dynamic call + // is initialized. It pushes site_field_expr on stack before + // the actual instance argument is emited which would cause + // jump from non-empty stack. + // + EmitConditionalAccess (ec); + } + if (s.Resolve (bc)) { Statement init = new If (new Binary (Binary.Operator.Equality, site_field_expr, new NullLiteral (loc)), s, loc); init.Emit (ec); @@ -526,9 +593,15 @@ namespace Mono.CSharp } } - Expression target = new DelegateInvocation (new MemberAccess (site_field_expr, "Target", loc).Resolve (bc), args, false, loc).Resolve (bc); - if (target != null) + var target = new DelegateInvocation (new MemberAccess (site_field_expr, "Target", loc).Resolve (bc), args, false, loc).Resolve (bc); + if (target != null) { target.Emit (ec); + } + + if (conditionalAccessReceiver) { + ec.CloseConditionalAccess (!isStatement && type.IsNullableType ? type : null); + ec.ConditionalAccess = ca; + } } } @@ -547,6 +620,12 @@ namespace Mono.CSharp { return new MemberAccess (new TypeExpression (binder_type, loc), name, loc); } + + protected virtual bool IsConditionalAccessReceiver { + get { + return false; + } + } } // @@ -671,14 +750,18 @@ namespace Mono.CSharp class DynamicIndexBinder : DynamicMemberAssignable { bool can_be_mutator; + readonly bool conditional_access_receiver; + readonly bool conditional_access; - public DynamicIndexBinder (Arguments args, Location loc) + public DynamicIndexBinder (Arguments args, bool conditionalAccessReceiver, bool conditionalAccess, Location loc) : base (args, loc) { + this.conditional_access_receiver = conditionalAccessReceiver; + this.conditional_access = conditionalAccess; } public DynamicIndexBinder (CSharpBinderFlags flags, Arguments args, Location loc) - : this (args, loc) + : this (args, false, false, loc) { base.flags = flags; } @@ -732,22 +815,35 @@ namespace Mono.CSharp setter_args.Add (new Argument (rhs)); return setter_args; } + + protected override bool IsConditionalAccessReceiver { + get { + return conditional_access_receiver; + } + } + + public override bool HasConditionalAccess () + { + return conditional_access; + } } class DynamicInvocation : DynamicExpressionStatement, IDynamicBinder { readonly ATypeNameExpression member; + readonly bool conditional_access_receiver; - public DynamicInvocation (ATypeNameExpression member, Arguments args, Location loc) + public DynamicInvocation (ATypeNameExpression member, Arguments args, bool conditionalAccessReceiver, Location loc) : base (null, args, loc) { base.binder = this; this.member = member; + this.conditional_access_receiver = conditionalAccessReceiver; } public static DynamicInvocation CreateSpecialNameInvoke (ATypeNameExpression member, Arguments args, Location loc) { - return new DynamicInvocation (member, args, loc) { + return new DynamicInvocation (member, args, false, loc) { flags = CSharpBinderFlags.InvokeSpecialName }; } @@ -805,11 +901,36 @@ namespace Mono.CSharp flags |= CSharpBinderFlags.ResultDiscarded; base.EmitStatement (ec); } + + protected override bool IsConditionalAccessReceiver { + get { + return conditional_access_receiver; + } + } + + public override bool HasConditionalAccess () + { + return member is ConditionalMemberAccess; + } + } + + class DynamicConditionalMemberBinder : DynamicMemberBinder + { + public DynamicConditionalMemberBinder (string name, Arguments args, Location loc) + : base (name, args, loc) + { + } + + public override bool HasConditionalAccess () + { + return true; + } } class DynamicMemberBinder : DynamicMemberAssignable { readonly string name; + bool conditionalAccessReceiver; public DynamicMemberBinder (string name, Arguments args, Location loc) : base (args, loc) @@ -835,6 +956,20 @@ namespace Mono.CSharp isSet |= (flags & CSharpBinderFlags.ValueFromCompoundAssignment) != 0; return new Invocation (GetBinder (isSet ? "SetMember" : "GetMember", loc), binder_args); } + + protected override Expression DoResolve (ResolveContext rc) + { + if (!rc.HasSet (ResolveContext.Options.DontSetConditionalAccessReceiver)) + conditionalAccessReceiver = HasConditionalAccess () || Arguments [0].Expr.HasConditionalAccess (); + + return base.DoResolve (rc); + } + + protected override bool IsConditionalAccessReceiver { + get { + return conditionalAccessReceiver; + } + } } // diff --git a/mcs/mcs/expression.cs b/mcs/mcs/expression.cs index e8938153c80..c5139d57f67 100644 --- a/mcs/mcs/expression.cs +++ b/mcs/mcs/expression.cs @@ -110,7 +110,6 @@ namespace Mono.CSharp protected override Expression DoResolve (ResolveContext rc) { Expression res = null; - using (rc.With (ResolveContext.Options.DontSetConditionalAccessReceiver, false)) { res = expr.Resolve (rc); } @@ -7182,7 +7181,7 @@ namespace Mono.CSharp } } - return new DynamicInvocation (expr as ATypeNameExpression, args, loc).Resolve (ec); + return new DynamicInvocation (expr as ATypeNameExpression, args, conditional_access_receiver, loc).Resolve (ec); } protected virtual MethodGroupExpr DoResolveOverload (ResolveContext ec) @@ -9720,6 +9719,8 @@ namespace Mono.CSharp return retval; } + var cma = this as ConditionalMemberAccess; + MemberExpr me; TypeSpec expr_type = expr.Type; if (expr_type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) { @@ -9729,10 +9730,13 @@ namespace Mono.CSharp Arguments args = new Arguments (1); args.Add (new Argument (expr)); + + if (cma != null) + return new DynamicConditionalMemberBinder (Name, args, loc); + return new DynamicMemberBinder (Name, args, loc); } - var cma = this as ConditionalMemberAccess; if (cma != null) { if (!IsNullPropagatingValid (expr.Type)) { expr.Error_OperatorCannotBeApplied (rc, loc, "?", expr.Type); @@ -10863,7 +10867,7 @@ namespace Mono.CSharp args.AddRange (arguments); best_candidate = null; - return new DynamicIndexBinder (args, loc); + return new DynamicIndexBinder (args, conditional_access_receiver, ConditionalAccess, loc); } // @@ -11671,7 +11675,6 @@ namespace Mono.CSharp args.Add (new Argument (rc.CurrentInitializerVariable)); target = new DynamicMemberBinder (Name, args, loc); } else { - var member = MemberLookup (rc, false, t, Name, 0, MemberLookupRestrictions.ExactArity, loc); if (member == null) { member = Expression.MemberLookup (rc, true, t, Name, 0, MemberLookupRestrictions.ExactArity, loc); diff --git a/mcs/tests/dtest-null-operator-01.cs b/mcs/tests/dtest-null-operator-01.cs new file mode 100644 index 00000000000..de6013ca044 --- /dev/null +++ b/mcs/tests/dtest-null-operator-01.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +class X +{ + public string Prop; + public A A = new A (); +} + +class A +{ + public string B; +} + +class MainClass +{ + static void NullCheckTest () + { + dynamic dyn = null; + dynamic res; + + res = dyn?.ToString (); + res = dyn?.GetHashCode (); + res = dyn?.DD.Length?.GetHashCode (); + + dyn?.ToString (); + + res = dyn?.Prop; + res = dyn?.Prop?.Prop2; + res = dyn?[0]; + } + + static void Test_1 () + { + dynamic dyn = new X (); + dynamic res; + + res = dyn.Prop?.Length; + res = dyn.A.B?.C.D?.E.F; + } + + static dynamic Test_2 (IEnumerable collection) + { + return collection?.FirstOrDefault ().Length; + } + + public static void Main () + { + NullCheckTest (); + + Test_1 (); + Test_2 (null); + } +} + + \ No newline at end of file diff --git a/mcs/tests/ver-il-net_4_x.xml b/mcs/tests/ver-il-net_4_x.xml index 56be2d26469..dcd5f610003 100644 --- a/mcs/tests/ver-il-net_4_x.xml +++ b/mcs/tests/ver-il-net_4_x.xml @@ -3468,6 +3468,35 @@ + + + + 18 + + + + + 7 + + + + + 890 + + + 672 + + + 103 + + + 19 + + + 7 + + + -- 2.25.1