[mcs] Member access null propagating operator
authorMarek Safar <marek.safar@gmail.com>
Thu, 31 Jul 2014 09:04:50 +0000 (11:04 +0200)
committerMarek Safar <marek.safar@gmail.com>
Thu, 31 Jul 2014 09:06:52 +0000 (11:06 +0200)
18 files changed:
mcs/errors/cs0023-20.cs [new file with mode: 0644]
mcs/errors/cs1061-15.cs [new file with mode: 0644]
mcs/errors/cs1644-41.cs [new file with mode: 0644]
mcs/mcs/async.cs
mcs/mcs/codegen.cs
mcs/mcs/cs-parser.jay
mcs/mcs/cs-tokenizer.cs
mcs/mcs/delegate.cs
mcs/mcs/ecore.cs
mcs/mcs/expression.cs
mcs/mcs/method.cs
mcs/mcs/nullable.cs
mcs/mcs/statement.cs
mcs/tests/test-null-operator-01.cs [new file with mode: 0644]
mcs/tests/test-null-operator-02.cs [new file with mode: 0644]
mcs/tests/test-null-operator-03.cs [new file with mode: 0644]
mcs/tests/test-null-operator-04.cs [new file with mode: 0644]
mcs/tests/ver-il-net_4_5.xml

diff --git a/mcs/errors/cs0023-20.cs b/mcs/errors/cs0023-20.cs
new file mode 100644 (file)
index 0000000..ffde8ad
--- /dev/null
@@ -0,0 +1,13 @@
+// CS0023: The `?' operator cannot be applied to operand of type `int'
+// Line: 11
+
+using System;
+
+class C
+{
+       static void Main()
+       {
+               int k = 0;
+               var r = k?.ToString ();
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs1061-15.cs b/mcs/errors/cs1061-15.cs
new file mode 100644 (file)
index 0000000..15b3864
--- /dev/null
@@ -0,0 +1,11 @@
+// CS1061: Type `int' does not contain a definition for `GetValueOrDefault' and no extension method `GetValueOrDefault' of type `int' could be found. Are you missing an assembly reference?
+// Line: 9
+
+class C
+{
+       static void Main ()
+       {
+               int? i = 4;
+               var m = i?.GetValueOrDefault ();
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs1644-41.cs b/mcs/errors/cs1644-41.cs
new file mode 100644 (file)
index 0000000..0891786
--- /dev/null
@@ -0,0 +1,12 @@
+// CS1644: Feature `null propagating operator' cannot be used because it is not part of the C# 5.0 language specification
+// Line: 10
+// Compiler options: -langversion:5
+
+class C
+{
+       static void Main ()
+       {
+               object o = null;
+               string s = o?.ToString ();
+       }
+}
\ No newline at end of file
index 0029cf894067c2e7c6bb3fa2537aa52c132fa9a5..6ffa92aa06a3f9cda8df1e97a0cbf67eaa719206 100644 (file)
@@ -819,7 +819,7 @@ namespace Mono.CSharp
                        args.Add (new Argument (awaiter, Argument.AType.Ref));
                        args.Add (new Argument (new CompilerGeneratedThis (CurrentType, Location), Argument.AType.Ref));
                        using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
-                               mg.EmitCall (ec, args);
+                               mg.EmitCall (ec, args, true);
                        }
                }
 
@@ -897,7 +897,7 @@ namespace Mono.CSharp
                        args.Add (new Argument (exceptionVariable));
 
                        using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
-                               mg.EmitCall (ec, args);
+                               mg.EmitCall (ec, args, true);
                        }
                }
 
@@ -921,7 +921,7 @@ namespace Mono.CSharp
                        }
 
                        using (ec.With (BuilderContext.Options.OmitDebugInfo, true)) {
-                               mg.EmitCall (ec, args);
+                               mg.EmitCall (ec, args, true);
                        }
                }
 
index 0b3eb43807af4be467fb07ede3f2ab3fd5d369d4..8cf96b140c08467b7f3368e8297e1f633cf82b50 100644 (file)
@@ -983,7 +983,7 @@ namespace Mono.CSharp
                public Expression InstanceExpression;
 
                //
-               // When set leaves an extra copy of all arguments on the stack
+               // When call has to leave an extra copy of all arguments on the stack
                //
                public bool DuplicateArguments;
 
@@ -998,18 +998,27 @@ namespace Mono.CSharp
                //
                public bool HasAwaitArguments;
 
+               public bool NullShortCircuit;
+
                //
                // When dealing with await arguments the original arguments are converted
                // into a new set with hoisted stack results
                //
                public Arguments EmittedArguments;
 
+               public Label NullOperatorLabel;
+
                public void Emit (EmitContext ec, MethodSpec method, Arguments Arguments, Location loc)
                {
-                       EmitPredefined (ec, method, Arguments, loc);
+                       EmitPredefined (ec, method, Arguments, false, loc);
+               }
+
+               public void EmitStatement (EmitContext ec, MethodSpec method, Arguments Arguments, Location loc)
+               {
+                       EmitPredefined (ec, method, Arguments, true, loc);
                }
 
-               public void EmitPredefined (EmitContext ec, MethodSpec method, Arguments Arguments, Location? loc = null)
+               public void EmitPredefined (EmitContext ec, MethodSpec method, Arguments Arguments, bool statement = false, Location? loc = null)
                {
                        Expression instance_copy = null;
 
@@ -1022,6 +1031,7 @@ namespace Mono.CSharp
 
                        OpCode call_op;
                        LocalTemporary lt = null;
+                       InstanceEmitter ie = new InstanceEmitter ();
 
                        if (method.IsStatic) {
                                call_op = OpCodes.Call;
@@ -1034,15 +1044,24 @@ namespace Mono.CSharp
 
                                if (HasAwaitArguments) {
                                        instance_copy = InstanceExpression.EmitToField (ec);
-                                       if (Arguments == null)
-                                               EmitCallInstance (ec, instance_copy, method.DeclaringType, call_op);
+                                       ie = new InstanceEmitter (instance_copy, IsAddressCall (instance_copy, call_op, method.DeclaringType));
+
+                                       if (Arguments == null) {
+                                               ie.EmitLoad (ec);
+                                       }
                                } else if (!InstanceExpressionOnStack) {
-                                       var instance_on_stack_type = EmitCallInstance (ec, InstanceExpression, method.DeclaringType, call_op);
+                                       ie = new InstanceEmitter (InstanceExpression, IsAddressCall (InstanceExpression, call_op, method.DeclaringType));
+                                       ie.NullShortCircuit = NullShortCircuit;
+                                       ie.Emit (ec);
+
+                                       if (NullShortCircuit) {
+                                               NullOperatorLabel = ie.NullOperatorLabel;
+                                       }
 
                                        if (DuplicateArguments) {
                                                ec.Emit (OpCodes.Dup);
                                                if (Arguments != null && Arguments.Count != 0) {
-                                                       lt = new LocalTemporary (instance_on_stack_type);
+                                                       lt = new LocalTemporary (ie.GetStackType (ec));
                                                        lt.Store (ec);
                                                        instance_copy = lt;
                                                }
@@ -1054,7 +1073,8 @@ namespace Mono.CSharp
                                EmittedArguments = Arguments.Emit (ec, DuplicateArguments, HasAwaitArguments);
                                if (EmittedArguments != null) {
                                        if (instance_copy != null) {
-                                               EmitCallInstance (ec, instance_copy, method.DeclaringType, call_op);
+                                               ie = new InstanceEmitter (instance_copy, IsAddressCall (instance_copy, call_op, method.DeclaringType));
+                                               ie.Emit (ec);
 
                                                if (lt != null)
                                                        lt.Release (ec);
@@ -1085,55 +1105,25 @@ namespace Mono.CSharp
                        if (method.Parameters.HasArglist) {
                                var varargs_types = GetVarargsTypes (method, Arguments);
                                ec.Emit (call_op, method, varargs_types);
-                               return;
-                       }
-
-                       //
-                       // If you have:
-                       // this.DoFoo ();
-                       // and DoFoo is not virtual, you can omit the callvirt,
-                       // because you don't need the null checking behavior.
-                       //
-                       ec.Emit (call_op, method);
-               }
-
-               static TypeSpec EmitCallInstance (EmitContext ec, Expression instance, TypeSpec declaringType, OpCode callOpcode)
-               {
-                       var instance_type = instance.Type;
-
-                       //
-                       // Push the instance expression
-                       //
-                       if ((instance_type.IsStructOrEnum && (callOpcode == OpCodes.Callvirt || (callOpcode == OpCodes.Call && declaringType.IsStruct))) ||
-                               instance_type.IsGenericParameter || declaringType.IsNullableType) {
+                       } else {
                                //
-                               // If the expression implements IMemoryLocation, then
-                               // we can optimize and use AddressOf on the
-                               // return.
+                               // If you have:
+                               // this.DoFoo ();
+                               // and DoFoo is not virtual, you can omit the callvirt,
+                               // because you don't need the null checking behavior.
                                //
-                               // If not we have to use some temporary storage for
-                               // it.
-                               var iml = instance as IMemoryLocation;
-                               if (iml != null) {
-                                       iml.AddressOf (ec, AddressOp.Load);
-                               } else {
-                                       LocalTemporary temp = new LocalTemporary (instance_type);
-                                       instance.Emit (ec);
-                                       temp.Store (ec);
-                                       temp.AddressOf (ec, AddressOp.Load);
-                               }
-
-                               return ReferenceContainer.MakeType (ec.Module, instance_type);
+                               ec.Emit (call_op, method);
                        }
 
-                       if (instance_type.IsStructOrEnum) {
-                               instance.Emit (ec);
-                               ec.Emit (OpCodes.Box, instance_type);
-                               return ec.BuiltinTypes.Object;
-                       }
+                       // 
+                       // Pop the return value if there is one and stack should be empty
+                       //
+                       if (statement && method.ReturnType.Kind != MemberKind.Void)
+                               ec.Emit (OpCodes.Pop);
 
-                       instance.Emit (ec);
-                       return instance_type;
+                       if (NullShortCircuit && !DuplicateArguments) {
+                               ie.EmitResultLift (ec, method.ReturnType, statement);
+                       }
                }
 
                static MetaType[] GetVarargsTypes (MethodSpec method, Arguments arguments)
@@ -1175,5 +1165,152 @@ namespace Mono.CSharp
 
                        return true;
                }
+
+               static bool IsAddressCall (Expression instance, OpCode callOpcode, TypeSpec declaringType)
+               {
+                       var instance_type = instance.Type;
+                       return (instance_type.IsStructOrEnum && (callOpcode == OpCodes.Callvirt || (callOpcode == OpCodes.Call && declaringType.IsStruct))) ||
+                               instance_type.IsGenericParameter || declaringType.IsNullableType;
+               }
+       }
+
+       public struct InstanceEmitter
+       {
+               readonly Expression instance;
+               readonly bool addressRequired;
+               bool value_on_stack;
+
+               public bool NullShortCircuit;
+               public Label NullOperatorLabel;
+
+               public InstanceEmitter (Expression instance, bool addressLoad)
+               {
+                       this.instance = instance;
+                       this.addressRequired = addressLoad;
+                       NullShortCircuit = false;
+                       NullOperatorLabel = new Label ();
+               }
+
+               public void Emit (EmitContext ec)
+               {
+                       Nullable.Unwrap unwrap;
+
+                       if (NullShortCircuit) {
+                               NullOperatorLabel = ec.DefineLabel ();
+                               unwrap = instance as Nullable.Unwrap;
+                       } else {
+                               unwrap = null;
+                       }
+
+                       if (unwrap != null) {
+                               unwrap.Store (ec);
+                               unwrap.EmitCheck (ec);
+                               ec.Emit (OpCodes.Brfalse, NullOperatorLabel);
+                               unwrap.Emit (ec);
+                               var tmp = ec.GetTemporaryLocal (unwrap.Type);
+                               ec.Emit (OpCodes.Stloc, tmp);
+                               ec.Emit (OpCodes.Ldloca, tmp);
+                               ec.FreeTemporaryLocal (tmp, unwrap.Type);
+                               return;
+                       }
+
+                       EmitLoad (ec);
+
+                       if (NullShortCircuit) {
+                               ec.Emit (OpCodes.Dup);
+                               ec.Emit (OpCodes.Brfalse, NullOperatorLabel);
+                       }
+
+                       value_on_stack = true;
+               }
+
+               public void EmitLoad (EmitContext ec)
+               {
+                       var instance_type = instance.Type;
+
+                       //
+                       // Push the instance expression
+                       //
+                       if (addressRequired) {
+                               //
+                               // If the expression implements IMemoryLocation, then
+                               // we can optimize and use AddressOf on the
+                               // return.
+                               //
+                               // If not we have to use some temporary storage for
+                               // it.
+                               var iml = instance as IMemoryLocation;
+                               if (iml != null) {
+                                       iml.AddressOf (ec, AddressOp.Load);
+                               } else {
+                                       LocalTemporary temp = new LocalTemporary (instance_type);
+                                       instance.Emit (ec);
+                                       temp.Store (ec);
+                                       temp.AddressOf (ec, AddressOp.Load);
+                               }
+
+                               return;
+                       }
+
+                       instance.Emit (ec);
+
+                       // Only to make verifier happy
+                       if (instance_type.IsGenericParameter && !(instance is This) && TypeSpec.IsReferenceType (instance_type)) {
+                               ec.Emit (OpCodes.Box, instance_type);
+                       } else if (instance_type.IsStructOrEnum) {
+                               ec.Emit (OpCodes.Box, instance_type);
+                       }
+               }
+
+               public void EmitResultLift (EmitContext ec, TypeSpec type, bool statement)
+               {
+                       if (!NullShortCircuit)
+                               throw new InternalErrorException ();
+
+                       bool value_rt = TypeSpec.IsValueType (type);
+                       TypeSpec lifted;
+                       if (value_rt) {
+                               if (type.IsNullableType)
+                                       lifted = type;
+                               else {
+                                       lifted = Nullable.NullableInfo.MakeType (ec.Module, type);
+                                       ec.Emit (OpCodes.Newobj, Nullable.NullableInfo.GetConstructor (lifted));
+                               }
+                       } else {
+                               lifted = null;
+                       }
+
+                       var end = ec.DefineLabel ();
+                       if (value_on_stack || !statement) {
+                               ec.Emit (OpCodes.Br_S, end);
+                       }
+
+                       ec.MarkLabel (NullOperatorLabel);
+
+                       if (value_on_stack)
+                               ec.Emit (OpCodes.Pop);
+
+                       if (!statement) {
+                               if (value_rt)
+                                       Nullable.LiftedNull.Create (lifted, Location.Null).Emit (ec);
+                               else
+                                       ec.EmitNull ();
+                       }
+
+                       ec.MarkLabel (end);
+               }
+
+               public TypeSpec GetStackType (EmitContext ec)
+               {
+                       var instance_type = instance.Type;
+
+                       if (addressRequired)
+                               return ReferenceContainer.MakeType (ec.Module, instance_type);
+
+                       if (instance_type.IsStructOrEnum)
+                               return ec.Module.Compiler.BuiltinTypes.Object;
+
+                       return instance_type;
+               }
        }
 }
index 5ce85c2b9db4d3f2eb1ab5048d0a4b5c73854bf6..0d912c99818be15fe46a19b4add495c15bf027c3 100644 (file)
@@ -257,6 +257,7 @@ namespace Mono.CSharp
 %token MAKEREF
 %token ASYNC
 %token AWAIT
+%token INTERR_OPERATOR
 
 /* C# keywords which are not really keywords */
 %token GET
@@ -3288,7 +3289,7 @@ parenthesized_expression
                $$ = new ParenthesizedExpression ((Expression) $2, GetLocation ($1));
          }
        ;
-       
+
 member_access
        : primary_expression DOT identifier_inside_body opt_type_argument_list
          {
@@ -3296,6 +3297,15 @@ member_access
                $$ = new MemberAccess ((Expression) $1, lt.Value, (TypeArguments) $4, lt.Location);
                lbag.AddLocation ($$, GetLocation ($2));
          }
+       | primary_expression INTERR_OPERATOR DOT identifier_inside_body opt_type_argument_list
+         {
+               if (lang_version < LanguageVersion.V_6)
+                       FeatureIsNotAvailable (GetLocation ($2), "null propagating operator");
+
+               var lt = (LocatedToken) $4;
+               $$ = new NullMemberAccess ((Expression) $1, lt.Value, (TypeArguments) $5, lt.Location);
+               lbag.AddLocation ($$, GetLocation ($2), GetLocation ($3));
+         }
        | builtin_types DOT identifier_inside_body opt_type_argument_list
          {
                var lt = (LocatedToken) $3;
@@ -3551,6 +3561,15 @@ element_access
                $$ = new ElementAccess ((Expression) $1, (Arguments) $3, GetLocation ($2));
                lbag.AddLocation ($$, GetLocation ($4));
          }
+       | primary_expression INTERR_OPERATOR OPEN_BRACKET_EXPR expression_list_arguments CLOSE_BRACKET  
+         {
+               if (lang_version < LanguageVersion.V_6)
+                       FeatureIsNotAvailable (GetLocation ($2), "null propagating operator");
+
+               throw new NotImplementedException ();
+//             $$ = new ElementAccess ((Expression) $1, (Arguments) $4, GetLocation ($3));
+//             lbag.AddLocation ($$, GetLocation ($4));
+         }
        | primary_expression OPEN_BRACKET_EXPR expression_list_arguments error
          {
                Error_SyntaxError (yyToken);
index 23adf2073a2e3b40afb8c7d3001f942ff167645c..908bdaeab8c466d9732aa1bfb87a66f4914e092e 100644 (file)
@@ -1260,6 +1260,10 @@ namespace Mono.CSharp
                                return Token.OP_COALESCING;
                        }
 
+                       if (d == '.') {
+                               return Token.INTERR_OPERATOR;
+                       }
+
                        switch (current_token) {
                        case Token.CLOSE_PARENS:
                        case Token.TRUE:
@@ -1282,6 +1286,10 @@ namespace Mono.CSharp
                        int parens = 0;
 
                        switch (xtoken ()) {
+                       case Token.DOT:
+                       case Token.OPEN_BRACKET_EXPR:
+                               next_token = Token.INTERR_OPERATOR;
+                               break;
                        case Token.LITERAL:
                        case Token.TRUE:
                        case Token.FALSE:
index 7632a6128fd43ae74c53de938fef8abedc8e1a9f..b083cfd46d23d36cc51f84433941f566fe6ac941 100644 (file)
@@ -564,10 +564,15 @@ namespace Mono.CSharp {
                
                public override void Emit (EmitContext ec)
                {
-                       if (method_group.InstanceExpression == null)
+                       InstanceEmitter ie;
+                       if (method_group.InstanceExpression == null) {
+                               ie = new InstanceEmitter ();
                                ec.EmitNull ();
-                       else
-                               method_group.InstanceExpression.Emit (ec);
+                       } else {
+                               ie = new InstanceEmitter (method_group.InstanceExpression, false);
+                               ie.NullShortCircuit = method_group.NullShortCircuit;
+                               ie.Emit (ec);
+                       }
 
                        var delegate_method = method_group.BestCandidate;
 
@@ -580,6 +585,10 @@ namespace Mono.CSharp {
                        }
 
                        ec.Emit (OpCodes.Newobj, constructor_method);
+
+                       if (method_group.NullShortCircuit) {
+                               ie.EmitResultLift (ec, type, false);
+                       }
                }
 
                public override void FlowAnalysis (FlowAnalysisContext fc) {
@@ -889,17 +898,14 @@ namespace Mono.CSharp {
                        //
                        var call = new CallEmitter ();
                        call.InstanceExpression = InstanceExpr;
-                       call.EmitPredefined (ec, method, arguments, loc);
+                       call.Emit (ec, method, arguments, loc);
                }
 
                public override void EmitStatement (EmitContext ec)
                {
-                       Emit (ec);
-                       // 
-                       // Pop the return value if there is one
-                       //
-                       if (type.Kind != MemberKind.Void)
-                               ec.Emit (OpCodes.Pop);
+                       var call = new CallEmitter ();
+                       call.InstanceExpression = InstanceExpr;
+                       call.EmitStatement (ec, method, arguments, loc);
                }
 
                public override System.Linq.Expressions.Expression MakeExpression (BuilderContext ctx)
index f0a9de98b6103af652e3d63bcae44183a33246f7..ee5479c94fada401d94abd81377c1dca4c6fdb72 100644 (file)
@@ -3151,6 +3151,8 @@ namespace Mono.CSharp {
                        get;
                }
 
+               public bool NullShortCircuit { get; set; }
+
                protected abstract TypeSpec DeclaringType {
                        get;
                }
@@ -3348,6 +3350,13 @@ namespace Mono.CSharp {
                                InstanceExpression.FlowAnalysis (fc);
                }
 
+               protected static TypeSpec LiftMemberType (ResolveContext rc, TypeSpec type)
+               {
+                       return TypeSpec.IsValueType (type) && !type.IsNullableType ?
+                               Nullable.NullableInfo.MakeType (rc.Module, type) :
+                               type;
+               }
+
                public bool ResolveInstanceExpression (ResolveContext rc, Expression rhs)
                {
                        if (!ResolveInstanceExpressionCore (rc, rhs))
@@ -3494,7 +3503,7 @@ namespace Mono.CSharp {
 
                public virtual MemberExpr ResolveMemberAccess (ResolveContext ec, Expression left, SimpleName original)
                {
-                       if (left != null && left.IsNull && TypeSpec.IsReferenceType (left.Type)) {
+                       if (left != null && !NullShortCircuit && left.IsNull && TypeSpec.IsReferenceType (left.Type)) {
                                ec.Report.Warning (1720, 1, left.Location,
                                        "Expression will always cause a `{0}'", "System.NullReferenceException");
                        }
@@ -3503,30 +3512,16 @@ namespace Mono.CSharp {
                        return this;
                }
 
-               protected void EmitInstance (EmitContext ec, bool prepare_for_load)
+               protected InstanceEmitter EmitInstance (EmitContext ec, bool prepare_for_load)
                {
-                       TypeSpec instance_type = InstanceExpression.Type;
-                       if (TypeSpec.IsValueType (instance_type)) {
-                               if (InstanceExpression is IMemoryLocation) {
-                                       ((IMemoryLocation) InstanceExpression).AddressOf (ec, AddressOp.Load);
-                               } else {
-                                       // Cannot release the temporary variable when its address
-                                       // is required to be on stack for any parent
-                                       LocalTemporary t = new LocalTemporary (instance_type);
-                                       InstanceExpression.Emit (ec);
-                                       t.Store (ec);
-                                       t.AddressOf (ec, AddressOp.Store);
-                               }
-                       } else {
-                               InstanceExpression.Emit (ec);
-
-                               // Only to make verifier happy
-                               if (instance_type.IsGenericParameter && !(InstanceExpression is This) && TypeSpec.IsReferenceType (instance_type))
-                                       ec.Emit (OpCodes.Box, instance_type);
-                       }
+                       var inst = new InstanceEmitter (InstanceExpression, TypeSpec.IsValueType (InstanceExpression.Type));
+                       inst.NullShortCircuit = NullShortCircuit;
+                       inst.Emit (ec);
 
                        if (prepare_for_load)
                                ec.Emit (OpCodes.Dup);
+
+                       return inst;
                }
 
                public abstract void SetTypeArguments (ResolveContext ec, TypeArguments ta);
@@ -3845,11 +3840,15 @@ namespace Mono.CSharp {
                        throw new NotSupportedException ();
                }
                
-               public void EmitCall (EmitContext ec, Arguments arguments)
+               public void EmitCall (EmitContext ec, Arguments arguments, bool statement)
                {
                        var call = new CallEmitter ();
                        call.InstanceExpression = InstanceExpression;
-                       call.Emit (ec, best_candidate, arguments, loc);                 
+                       call.NullShortCircuit = NullShortCircuit;
+                       if (statement)
+                               call.EmitStatement (ec, best_candidate, arguments, loc);
+                       else
+                               call.Emit (ec, best_candidate, arguments, loc);
                }
 
                public override void Error_ValueCannotBeConverted (ResolveContext ec, TypeSpec target, bool expl)
@@ -3956,6 +3955,9 @@ namespace Mono.CSharp {
                        if (best_candidate_return.Kind == MemberKind.Void && best_candidate.IsConditionallyExcluded (ec))
                                Methods = Excluded;
 
+                       if (NullShortCircuit)
+                               best_candidate_return = LiftMemberType (ec, best_candidate_return);
+
                        return this;
                }
 
@@ -5986,6 +5988,13 @@ namespace Mono.CSharp {
                                variable_info = var.VariableInfo.GetStructFieldInfo (Name);
                        }
 
+                       if (NullShortCircuit) {
+                               type = LiftMemberType (ec, type);
+
+                               if (InstanceExpression.IsNull)
+                                       return Constant.CreateConstantFromValue (type, null, loc);
+                       }
+
                        eclass = ExprClass.Variable;
                        return this;
                }
@@ -6192,8 +6201,11 @@ namespace Mono.CSharp {
 
                                ec.Emit (OpCodes.Ldsfld, spec);
                        } else {
+                               InstanceEmitter ie;
                                if (!prepared)
-                                       EmitInstance (ec, false);
+                                       ie = EmitInstance (ec, false);
+                               else
+                                       ie = new InstanceEmitter ();
 
                                // Optimization for build-in types
                                if (type.IsStruct && type == ec.CurrentType && InstanceExpression.Type == type) {
@@ -6208,6 +6220,10 @@ namespace Mono.CSharp {
                                                        ec.Emit (OpCodes.Volatile);
 
                                                ec.Emit (OpCodes.Ldfld, spec);
+
+                                               if (NullShortCircuit) {
+                                                       ie.EmitResultLift (ec, spec.MemberType, false);
+                                               }
                                        }
                                }
                        }
@@ -6229,6 +6245,9 @@ namespace Mono.CSharp {
                        }
 
                        if (IsInstance) {
+                               if (NullShortCircuit)
+                                       throw new NotImplementedException ("null operator assignment");
+
                                if (has_await_source)
                                        source = source.EmitToField (ec);
 
@@ -6518,9 +6537,14 @@ namespace Mono.CSharp {
                        // Special case: length of single dimension array property is turned into ldlen
                        //
                        if (IsSingleDimensionalArrayLength ()) {
-                               EmitInstance (ec, false);
+                               var inst = EmitInstance (ec, false);
+
                                ec.Emit (OpCodes.Ldlen);
                                ec.Emit (OpCodes.Conv_I4);
+
+                               if (NullShortCircuit)
+                                       inst.EmitResultLift (ec, ec.BuiltinTypes.Int, false);
+
                                return;
                        }
 
@@ -6576,8 +6600,15 @@ namespace Mono.CSharp {
                        call.InstanceExpression = InstanceExpression;
                        if (args == null)
                                call.InstanceExpressionOnStack = true;
+                       if (NullShortCircuit) {
+                               call.NullShortCircuit = true;
+                               call.NullOperatorLabel = null_operator_label;
+                       }
 
-                       call.Emit (ec, Setter, args, loc);
+                       if (leave_copy)
+                               call.Emit (ec, Setter, args, loc);
+                       else
+                               call.EmitStatement (ec, Setter, args, loc);
 
                        if (temp != null) {
                                temp.Emit (ec);
@@ -6642,6 +6673,7 @@ namespace Mono.CSharp {
                protected LocalTemporary temp;
                protected bool emitting_compound_assignment;
                protected bool has_await_arguments;
+               protected Label null_operator_label;
 
                protected PropertyOrIndexerExpr (Location l)
                {
@@ -6679,6 +6711,10 @@ namespace Mono.CSharp {
                                if (expr == null)
                                        return null;
 
+                               if (NullShortCircuit && !ec.HasSet (ResolveContext.Options.CompoundAssignmentScope)) {
+                                       type = LiftMemberType (ec, type);
+                               }
+
                                if (expr != this)
                                        return expr.Resolve (ec);
                        }
@@ -6717,6 +6753,14 @@ namespace Mono.CSharp {
                        if (!ResolveSetter (ec))
                                return null;
 
+                       if (NullShortCircuit && ec.HasSet (ResolveContext.Options.CompoundAssignmentScope)) {
+                               var lifted_type = LiftMemberType (ec, type);
+                               if (type != lifted_type) {
+                                       // TODO: Workaround to disable codegen for now
+                                       return Nullable.Wrap.Create (this, lifted_type);
+                               }
+                       }
+
                        return this;
                }
 
@@ -6726,6 +6770,7 @@ namespace Mono.CSharp {
                public virtual void Emit (EmitContext ec, bool leave_copy)
                {
                        var call = new CallEmitter ();
+                       call.NullShortCircuit = NullShortCircuit;
                        call.InstanceExpression = InstanceExpression;
                        if (has_await_arguments)
                                call.HasAwaitArguments = true;
@@ -6740,6 +6785,9 @@ namespace Mono.CSharp {
                                has_await_arguments = true;
                        }
 
+                       if (NullShortCircuit && emitting_compound_assignment)
+                               null_operator_label = call.NullOperatorLabel;
+
                        if (leave_copy) {
                                ec.Emit (OpCodes.Dup);
                                temp = new LocalTemporary (Type);
@@ -6963,7 +7011,8 @@ namespace Mono.CSharp {
 
                        var call = new CallEmitter ();
                        call.InstanceExpression = InstanceExpression;
-                       call.Emit (ec, op, args, loc);
+                       call.NullShortCircuit = NullShortCircuit;
+                       call.EmitStatement (ec, op, args, loc);
                }
 
                #endregion
index 0ab48acb382e4c47a8494814726d8a2c6ce99dff..8368959c671533671ce0371cf1bf0b8ea73e9cfd 100644 (file)
@@ -80,7 +80,7 @@ namespace Mono.CSharp
                public override void Emit (EmitContext ec)
                {
                        var call = new CallEmitter ();
-                       call.EmitPredefined (ec, oper, arguments, loc);
+                       call.Emit (ec, oper, arguments, loc);
                }
 
                public override void FlowAnalysis (FlowAnalysisContext fc)
@@ -5042,7 +5042,7 @@ namespace Mono.CSharp
                        var method = res.ResolveMember<MethodSpec> (new ResolveContext (ec.MemberContext), ref arguments);
                        if (method != null) {
                                var call = new CallEmitter ();
-                               call.EmitPredefined (ec, method, arguments);
+                               call.EmitPredefined (ec, method, arguments, false);
                        }
                }
 
@@ -6089,7 +6089,7 @@ namespace Mono.CSharp
                        fc.SetVariableAssigned (variable_info);
                }
        }
-       
+
        /// <summary>
        ///   Invocation of methods or delegates.
        /// </summary>
@@ -6426,18 +6426,15 @@ namespace Mono.CSharp
                        if (mg.IsConditionallyExcluded)
                                return;
 
-                       mg.EmitCall (ec, arguments);
+                       mg.EmitCall (ec, arguments, false);
                }
                
                public override void EmitStatement (EmitContext ec)
                {
-                       Emit (ec);
+                       if (mg.IsConditionallyExcluded)
+                               return;
 
-                       // 
-                       // Pop the return value if there is one
-                       //
-                       if (type.Kind != MemberKind.Void)
-                               ec.Emit (OpCodes.Pop);
+                       mg.EmitCall (ec, arguments, true);
                }
 
                public override SLE.Expression MakeExpression (BuilderContext ctx)
@@ -8852,6 +8849,11 @@ namespace Mono.CSharp
                        return (type.Kind & dot_kinds) != 0 || type.BuiltinType == BuiltinTypeSpec.Type.Dynamic;
                }
 
+               static bool IsNullPropagatingValid (TypeSpec type)
+               {
+                       return TypeSpec.IsReferenceType (type) || type.IsNullableType;
+               }
+
                public override Expression LookupNameExpression (ResolveContext rc, MemberLookupRestrictions restrictions)
                {
                        var sn = expr as SimpleName;
@@ -8910,6 +8912,16 @@ namespace Mono.CSharp
                                return null;
                        }
 
+                       if (this is NullMemberAccess) {
+                               if (!IsNullPropagatingValid (expr.Type))
+                                       expr.Error_OperatorCannotBeApplied (rc, loc, "?", expr.Type);
+
+                               if (expr_type.IsNullableType) {
+                                       expr = Nullable.Unwrap.Create (expr, true).Resolve (rc);
+                                       expr_type = expr.Type;
+                               }
+                       }
+
                        var lookup_arity = Arity;
                        bool errorMode = false;
                        Expression member_lookup;
@@ -8993,6 +9005,10 @@ namespace Mono.CSharp
                                sn = null;
                        }
 
+                       if (this is NullMemberAccess) {
+                               me.NullShortCircuit = true;
+                       }
+
                        me = me.ResolveMemberAccess (rc, expr, sn);
 
                        if (Arity > 0) {
@@ -9164,6 +9180,14 @@ namespace Mono.CSharp
                }
        }
 
+       public class NullMemberAccess : MemberAccess
+       {
+               public NullMemberAccess (Expression expr, string identifier, TypeArguments args, Location loc)
+                       : base (expr, identifier, args, loc)
+               {
+               }
+       }
+
        /// <summary>
        ///   Implements checked expressions
        /// </summary>
index d368fe933b6eb4d04227bed03b9359fc92eeaa2f..ca4b68854439763550826489061fc82239816771 100644 (file)
@@ -1535,7 +1535,7 @@ namespace Mono.CSharp {
                        
                        var call = new CallEmitter ();
                        call.InstanceExpression = new CompilerGeneratedThis (type, loc); 
-                       call.EmitPredefined (ec, base_ctor, argument_list);
+                       call.EmitPredefined (ec, base_ctor, argument_list, false);
                }
 
                public override void EmitStatement (EmitContext ec)
index dd36b88aef1aaeeb79f41ed26d8b86efb025e515..8621360a52f97b11b09f74ead6311b452982a322 100644 (file)
@@ -84,9 +84,15 @@ namespace Mono.CSharp.Nullable
                }
 
                public static TypeSpec GetEnumUnderlyingType (ModuleContainer module, TypeSpec nullableEnum)
+               {
+                       return MakeType (module, EnumSpec.GetUnderlyingType (GetUnderlyingType (nullableEnum)));
+               }
+
+               public static TypeSpec MakeType (ModuleContainer module, TypeSpec underlyingType)
                {
                        return module.PredefinedTypes.Nullable.TypeSpec.MakeGenericType (module,
-                               new[] { EnumSpec.GetUnderlyingType (GetUnderlyingType (nullableEnum)) });
+                               new[] { underlyingType });
+
                }
        }
 
@@ -701,7 +707,7 @@ namespace Mono.CSharp.Nullable
                        }
 
                        if (!type.IsNullableType)
-                               type = rc.Module.PredefinedTypes.Nullable.TypeSpec.MakeGenericType (rc.Module, new[] { type });
+                               type = NullableInfo.MakeType (rc.Module, type);
 
                        return Wrap.Create (expr, type);
                }
index 085a9a0fb5adf09da74a561c68300679586bca25..444aafebe23934346b7aa8e4daeb69fb37232c85 100644 (file)
@@ -5635,7 +5635,7 @@ namespace Mono.CSharp {
 
                                var ce = new CallEmitter ();
                                ce.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
-                               ce.EmitPredefined (ec, finally_host.Spec, new Arguments (0));
+                               ce.EmitPredefined (ec, finally_host.Spec, new Arguments (0), true);
                        } else {
                                EmitFinallyBody (ec);
                        }
@@ -5700,7 +5700,7 @@ namespace Mono.CSharp {
                        if (finally_host != null) {
                                var ce = new CallEmitter ();
                                ce.InstanceExpression = new CompilerGeneratedThis (ec.CurrentType, loc);
-                               ce.EmitPredefined (ec, finally_host.Spec, new Arguments (0));
+                               ce.EmitPredefined (ec, finally_host.Spec, new Arguments (0), true);
                        } else {
                                EmitFinallyBody (ec);
                        }
diff --git a/mcs/tests/test-null-operator-01.cs b/mcs/tests/test-null-operator-01.cs
new file mode 100644 (file)
index 0000000..debd85f
--- /dev/null
@@ -0,0 +1,155 @@
+using System;
+
+struct S
+{
+    public int Prop { get; set; }
+}
+
+interface I
+{
+    int Method ();
+}
+
+class CI : I
+{
+    public int Method ()
+    {
+        return 33;
+    }
+
+    public int Prop { get; set; }
+}
+
+class C
+{
+    static int prop_calls;
+    static string Prop {
+        get {
+            ++prop_calls;
+            return null;
+        }
+    }
+
+    static int TestArray ()
+    {
+        int[] k = null;
+        var t1 = k?.ToString ();
+        if (t1 != null)
+            return 1;
+
+        var t2 = k?.GetLength (0);
+        if (t2 != null)
+            return 2;
+
+        var t3 = k?.Length;
+        if (t3 != null)
+            return 3;
+
+        k = new int[] { 3 };
+        var t11 = k?.ToString ();
+        if (t11.GetType () != typeof (string))
+            return 10;
+
+        var t12 = k?.GetLength (0);
+        if (t12.GetType () != typeof (int))
+            return 11;
+
+        var t13 = k?.Length;
+        if (t13.GetType () != typeof (int))
+            return 12;
+
+        return 0;
+    }
+
+    static int TestReferenceType ()
+    {
+        string s = null;
+        var t1 = s?.Split ();
+        if (t1 != null)
+            return 1;
+
+        var t2 = s?.Length;
+        if (t2 != null)
+            return 2;
+
+        var t3 = Prop?.Length;
+        if (t3 != null)
+            return 3;
+        if (prop_calls != 1)
+            return 4;
+
+        var t4 = Prop?.Split ();
+        if (t4 != null)
+            return 5;
+        if (prop_calls != 2)
+            return 6;
+
+        return 0;
+    }
+
+    static int TestGeneric<T> (T t) where T : class, I
+    {
+        // FIXME:
+        //var t1 = t?.Method ();
+        //if (t1 != null)
+        //  return 1;
+
+        T[] at = null;
+        var t2 = at?.Length;
+        if (t2 != null)
+            return 2;
+
+        return 0;
+    }
+
+    static int TestNullable ()
+    {
+        int? i = 4;
+        var m = i?.CompareTo (3);
+        if (m.GetType () != typeof (int))
+            return 1;
+
+        if (m != 1)
+            return 2;
+
+        DateTime? dt = null;
+        dt?.ToString ();
+        if (dt?.ToString () != null)
+            return 3;
+
+        byte? b = 0;
+        if (b?.ToString () != "0")
+            return 4;
+
+        S? s = null;
+        var p1 = s?.Prop;
+        if (p1 != null)
+            return 5;
+
+        return 0;
+    }
+
+    static int Main ()
+    {
+        int res;
+        res = TestNullable ();
+        if (res != 0)
+            return 100 + res;
+
+        res = TestArray ();
+        if (res != 0)
+            return 200 + res;
+
+        res = TestReferenceType ();
+        if (res != 0)
+            return 300 + res;
+
+        CI ci = null;
+        res = TestGeneric<CI> (ci);
+        if (res != 0)
+            return 400 + res;
+
+        Console.WriteLine ("ok");
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/mcs/tests/test-null-operator-02.cs b/mcs/tests/test-null-operator-02.cs
new file mode 100644 (file)
index 0000000..de4158a
--- /dev/null
@@ -0,0 +1,122 @@
+using System;
+
+class CI
+{
+    public long Field;
+    public sbyte? FieldNullable;
+    public object FieldReference;
+
+       public int Prop { get; set; }
+    public byte? PropNullable { get; set; }
+    public string PropReference { get; set; }
+
+    public event Action ev1;
+
+    public int this [bool arg] { get { return 4; } }
+    public int? this [int? arg] { get { return arg; } }
+    public string this [string arg] { get { return arg; } }
+
+/*
+    void Foo ()
+    {
+        var l = this?.Field;    
+    }
+*/
+}
+
+class C
+{
+   /* 
+    static int TestProperty ()
+    {
+        CI ci = null;
+        var m1 = ci?.Prop;
+        var m2 = ci?.PropNullable;
+        var m3 = ci?.PropReference;
+
+        ci?.Prop = 6;
+
+        ci = new CI ();
+        m1 = ci?.Prop;
+        m2 = ci?.PropNullable;
+        m3 = ci?.PropReference;
+
+        ci?.Prop = 5;
+        if (ci.Prop != 5)
+            return 1;
+
+// TODO: It's not allowed for now
+//      ci?.Prop += 4;
+//      var pp1 = ci?.Prop = 4;
+//      var pp2 = ci?.Prop += 4;
+
+        return 0;
+    }
+
+    static int TestField ()
+    {
+        CI ci = null;
+        var m1 = ci?.Field;
+        var m2 = ci?.FieldNullable;
+        var m3 = ci?.FieldReference;
+
+//        ci?.Field = 6;
+
+        ci = new CI ();
+        m1 = ci?.Field;
+        m2 = ci?.FieldNullable;
+        m3 = ci?.FieldReference;
+
+//        ci?.Field = 5;
+//        if (ci.Field != 5)
+//            return 1;
+
+// TODO: It's not allowed for now
+//      ci?.Field += 4;
+//      var pp1 = ci?.Field = 4;
+//      var pp2 = ci?.Field += 4;
+
+        return 0;
+    }
+
+    static int TestEvent ()
+    {
+        CI ci = null;
+        ci?.ev1 += null;
+
+        ci = new CI ();
+        ci?.ev1 += null;
+
+        return 0;
+    }
+*/
+/*
+    static int TestIndexer ()
+    {
+        CI ci = null;
+        var m1 = ci?[0];
+        var m2 = ci?[(int?) 1];
+        var m3 = ci?[""];
+    }
+*/
+    static int Main ()
+    {
+        int res;
+/*
+        res = TestProperty ();
+        if (res != 0)
+            return 10 + res;
+
+        res = TestField ();
+        if (res != 0)
+            return 20 + res;
+
+        res = TestEvent ();
+        if (res != 0)
+            return 30 + res;            
+*/
+
+       Console.WriteLine ("ok");
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/mcs/tests/test-null-operator-03.cs b/mcs/tests/test-null-operator-03.cs
new file mode 100644 (file)
index 0000000..c58d689
--- /dev/null
@@ -0,0 +1,31 @@
+using System;
+
+class C
+{
+    int field;
+
+    int Test1 ()
+    {
+        var x = this?.field;
+        if (x == null)
+            return 1;
+
+        // TODO: Should it really be of int? type
+
+        return 0;
+    }
+
+    static int Main ()
+    {
+        var c = new C ();
+        c.Test1 ();
+
+        const C c2 = null;
+        var res = c2?.field;
+        if (res != null)
+            return 1;
+
+       Console.WriteLine ("ok");
+        return 0;
+    }
+}
\ No newline at end of file
diff --git a/mcs/tests/test-null-operator-04.cs b/mcs/tests/test-null-operator-04.cs
new file mode 100644 (file)
index 0000000..6796920
--- /dev/null
@@ -0,0 +1,14 @@
+using System;
+
+public class D
+{
+       void Foo ()
+       {
+       }
+
+       public static void Main()
+       {
+               D d = null;
+               Action a = d?.Foo;
+       }
+}
index 58243cd6ff0c716183d991ffe361aac8670fa51c..050d30b880218a81dea068456332b230eae90bad 100644 (file)
       </method>\r
     </type>\r
   </test>\r
+  <test name="test-null-operator-01.cs">\r
+    <type name="S">\r
+      <method name="Int32 get_Prop()" attrs="2182">\r
+        <size>14</size>\r
+      </method>\r
+      <method name="Void set_Prop(Int32)" attrs="2182">\r
+        <size>8</size>\r
+      </method>\r
+    </type>\r
+    <type name="CI">\r
+      <method name="Int32 Method()" attrs="486">\r
+        <size>11</size>\r
+      </method>\r
+      <method name="Int32 get_Prop()" attrs="2182">\r
+        <size>14</size>\r
+      </method>\r
+      <method name="Void set_Prop(Int32)" attrs="2182">\r
+        <size>8</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+    <type name="C">\r
+      <method name="System.String get_Prop()" attrs="2193">\r
+        <size>22</size>\r
+      </method>\r
+      <method name="Int32 TestArray()" attrs="145">\r
+        <size>349</size>\r
+      </method>\r
+      <method name="Int32 TestReferenceType()" attrs="145">\r
+        <size>231</size>\r
+      </method>\r
+      <method name="Int32 TestGeneric[T](T)" attrs="145">\r
+        <size>58</size>\r
+      </method>\r
+      <method name="Int32 TestNullable()" attrs="145">\r
+        <size>384</size>\r
+      </method>\r
+      <method name="Int32 Main()" attrs="145">\r
+        <size>120</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+  </test>\r
+  <test name="test-null-operator-02.cs">\r
+    <type name="CI">\r
+      <method name="Int32 get_Prop()" attrs="2182">\r
+        <size>14</size>\r
+      </method>\r
+      <method name="Void set_Prop(Int32)" attrs="2182">\r
+        <size>8</size>\r
+      </method>\r
+      <method name="System.Nullable`1[System.Byte] get_PropNullable()" attrs="2182">\r
+        <size>14</size>\r
+      </method>\r
+      <method name="Void set_PropNullable(System.Nullable`1[System.Byte])" attrs="2182">\r
+        <size>8</size>\r
+      </method>\r
+      <method name="System.String get_PropReference()" attrs="2182">\r
+        <size>14</size>\r
+      </method>\r
+      <method name="Void set_PropReference(System.String)" attrs="2182">\r
+        <size>8</size>\r
+      </method>\r
+      <method name="Void add_ev1(System.Action)" attrs="2182">\r
+        <size>42</size>\r
+      </method>\r
+      <method name="Void remove_ev1(System.Action)" attrs="2182">\r
+        <size>42</size>\r
+      </method>\r
+      <method name="Int32 get_Item(Boolean)" attrs="2182">\r
+        <size>10</size>\r
+      </method>\r
+      <method name="System.Nullable`1[System.Int32] get_Item(System.Nullable`1[System.Int32])" attrs="2182">\r
+        <size>10</size>\r
+      </method>\r
+      <method name="System.String get_Item(System.String)" attrs="2182">\r
+        <size>10</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+    <type name="C">\r
+      <method name="Int32 Main()" attrs="145">\r
+        <size>20</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+  </test>\r
+  <test name="test-null-operator-03.cs">\r
+    <type name="C">\r
+      <method name="Int32 Test1()" attrs="129">\r
+        <size>62</size>\r
+      </method>\r
+      <method name="Int32 Main()" attrs="145">\r
+        <size>62</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+  </test>\r
+  <test name="test-null-operator-04.cs">\r
+    <type name="D">\r
+      <method name="Void Foo()" attrs="129">\r
+        <size>2</size>\r
+      </method>\r
+      <method name="Void Main()" attrs="150">\r
+        <size>27</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+  </test>\r
   <test name="test-partial-01.cs">\r
     <type name="Foo.Hello">\r
       <method name="Void .ctor()" attrs="6278">\r