[mcs] null propagating operator on index expressions
authorMarek Safar <marek.safar@gmail.com>
Mon, 4 Aug 2014 15:32:15 +0000 (17:32 +0200)
committerMarek Safar <marek.safar@gmail.com>
Mon, 4 Aug 2014 15:33:43 +0000 (17:33 +0200)
12 files changed:
mcs/errors/cs0023-22.cs [new file with mode: 0644]
mcs/errors/cs0023-23.cs [new file with mode: 0644]
mcs/errors/cs1644-42.cs [new file with mode: 0644]
mcs/errors/cs8072-2.cs [new file with mode: 0644]
mcs/errors/cs8072.cs [new file with mode: 0644]
mcs/mcs/cs-parser.jay
mcs/mcs/cs-tokenizer.cs
mcs/mcs/ecore.cs
mcs/mcs/expression.cs
mcs/tests/test-null-operator-02.cs
mcs/tests/test-null-operator-05.cs [new file with mode: 0644]
mcs/tests/ver-il-net_4_5.xml

diff --git a/mcs/errors/cs0023-22.cs b/mcs/errors/cs0023-22.cs
new file mode 100644 (file)
index 0000000..ce5c0b2
--- /dev/null
@@ -0,0 +1,10 @@
+// CS0023: The `?' operator cannot be applied to operand of type `null'
+// Line: 8
+
+class C
+{
+       static void Main ()
+       {
+               var res = null?[0];
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs0023-23.cs b/mcs/errors/cs0023-23.cs
new file mode 100644 (file)
index 0000000..ae7db34
--- /dev/null
@@ -0,0 +1,12 @@
+// CS0023: The `?' operator cannot be applied to operand of type `void'
+// Line: 10
+
+using System;
+
+class C
+{
+       static void Main ()
+       {
+               var v = Console.WriteLine ()?[0];
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs1644-42.cs b/mcs/errors/cs1644-42.cs
new file mode 100644 (file)
index 0000000..aea12a8
--- /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 ()
+       {
+               string[] a = null;
+               var s = a?[0];
+       }
+}
\ No newline at end of file
diff --git a/mcs/errors/cs8072-2.cs b/mcs/errors/cs8072-2.cs
new file mode 100644 (file)
index 0000000..f9f2a1b
--- /dev/null
@@ -0,0 +1,16 @@
+// CS8072: An expression tree cannot contain a null propagating operator
+// Line: 14
+
+using System;
+using System.Linq.Expressions;
+
+public class C
+{
+       public void TestMethod () { }
+
+       static void Main ()
+       {
+               C c = null;
+               Expression<Action> e = () => c?.TestMethod ();
+       }
+}
diff --git a/mcs/errors/cs8072.cs b/mcs/errors/cs8072.cs
new file mode 100644 (file)
index 0000000..e53e906
--- /dev/null
@@ -0,0 +1,14 @@
+// CS8072: An expression tree cannot contain a null propagating operator
+// Line: 11
+
+using System;
+using System.Linq.Expressions;
+
+class C
+{
+       static int Main ()
+       {
+               Expression<Func<string, char?>> e = l => l?[1];
+               return 0;
+       }
+}
\ No newline at end of file
index 0d912c99818be15fe46a19b4add495c15bf027c3..693f95ce599197b0a21a67a67fcd0fea2c830cf8 100644 (file)
@@ -3566,9 +3566,11 @@ element_access
                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));
+               $$ = new ElementAccess ((Expression) $1, (Arguments) $4, GetLocation ($3)) {
+                       NullPropagating = true
+               };
+
+               lbag.AddLocation ($$, GetLocation ($2), GetLocation ($5));
          }
        | primary_expression OPEN_BRACKET_EXPR expression_list_arguments error
          {
index 908bdaeab8c466d9732aa1bfb87a66f4914e092e..7545043004879a4c47e97916c072cb90134512a8 100644 (file)
@@ -1264,15 +1264,6 @@ namespace Mono.CSharp
                                return Token.INTERR_OPERATOR;
                        }
 
-                       switch (current_token) {
-                       case Token.CLOSE_PARENS:
-                       case Token.TRUE:
-                       case Token.FALSE:
-                       case Token.NULL:
-                       case Token.LITERAL:
-                               return Token.INTERR;
-                       }
-
                        if (d != ' ') {
                                if (d == ',' || d == ';' || d == '>')
                                        return Token.INTERR_NULLABLE;
index ee5479c94fada401d94abd81377c1dca4c6fdb72..97fe44d239d5dc4213cce585784c0fbeab194858 100644 (file)
@@ -429,6 +429,18 @@ namespace Mono.CSharp {
                {
                        return type.GetDefinition ().GetSignatureForError ();
                }
+
+               protected static bool IsNullPropagatingValid (TypeSpec type)
+               {
+                       return (TypeSpec.IsReferenceType (type) && type != InternalType.NullLiteral) || type.IsNullableType;
+               }
+
+               protected static TypeSpec LiftMemberType (ResolveContext rc, TypeSpec type)
+               {
+                       return TypeSpec.IsValueType (type) && !type.IsNullableType ?
+                               Nullable.NullableInfo.MakeType (rc.Module, type) :
+                               type;
+               }
               
                /// <summary>
                ///   Resolves an expression and performs semantic analysis on it.
@@ -936,6 +948,11 @@ namespace Mono.CSharp {
                        ec.Report.Error (1944, loc, "An expression tree cannot contain an unsafe pointer operation");
                }
 
+               protected void Error_NullShortCircuitInsideExpressionTree (ResolveContext rc)
+               {
+                       rc.Report.Error (8072, loc, "An expression tree cannot contain a null propagating operator");
+               }
+
                public virtual void FlowAnalysis (FlowAnalysisContext fc)
                {
                }
@@ -3350,13 +3367,6 @@ 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))
@@ -3818,7 +3828,10 @@ namespace Mono.CSharp {
                        if (IsConditionallyExcluded)
                                ec.Report.Error (765, loc,
                                        "Partial methods with only a defining declaration or removed conditional methods cannot be used in an expression tree");
-                       
+
+                       if (NullShortCircuit)
+                               Error_NullShortCircuitInsideExpressionTree (ec);
+
                        return new TypeOfMethod (best_candidate, loc);
                }
                
@@ -5895,6 +5908,10 @@ namespace Mono.CSharp {
 
                public override Expression CreateExpressionTree (ResolveContext ec)
                {
+                       if (NullShortCircuit) {
+                               Error_NullShortCircuitInsideExpressionTree (ec);
+                       }
+
                        return CreateExpressionTree (ec, true);
                }
 
@@ -6089,8 +6106,11 @@ namespace Mono.CSharp {
                        return null;
                }
 
-               override public Expression DoResolveLValue (ResolveContext ec, Expression right_side)
+               public override Expression DoResolveLValue (ResolveContext ec, Expression right_side)
                {
+                       if (NullShortCircuit)
+                               throw new NotSupportedException ("null propagating operator assignment");
+
                        if (spec is FixedFieldSpec) {
                                // It could be much better error message but we want to be error compatible
                                Error_ValueAssignment (ec, right_side);
@@ -6470,6 +6490,10 @@ namespace Mono.CSharp {
 
                public override Expression CreateExpressionTree (ResolveContext ec)
                {
+                       if (NullShortCircuit) {
+                               Error_NullShortCircuitInsideExpressionTree (ec);
+                       }
+
                        Arguments args;
                        if (IsSingleDimensionalArrayLength ()) {
                                args = new Arguments (1);
@@ -6727,6 +6751,9 @@ namespace Mono.CSharp {
 
                public override Expression DoResolveLValue (ResolveContext ec, Expression right_side)
                {
+                       if (NullShortCircuit)
+                               throw new NotSupportedException ("null propagating operator assignment");
+
                        if (right_side == EmptyExpression.OutAccess) {
                                // TODO: best_candidate can be null at this point
                                INamedBlockVariable variable = null;
@@ -6752,7 +6779,7 @@ 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) {
@@ -6760,7 +6787,7 @@ namespace Mono.CSharp {
                                        return Nullable.Wrap.Create (this, lifted_type);
                                }
                        }
-
+*/
                        return this;
                }
 
index d2fced11c689b6a1219fc56cf59750b98b842932..69ab3e79db66103f96e06ae693285551848fd164 100644 (file)
@@ -8849,11 +8849,6 @@ 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;
@@ -9355,6 +9350,8 @@ namespace Mono.CSharp
                        this.Arguments = args;
                }
 
+               public bool NullPropagating { get; set; }
+
                public override Location StartLocation {
                        get {
                                return Expr.StartLocation;
@@ -9372,8 +9369,15 @@ namespace Mono.CSharp
                //
                Expression CreateAccessExpression (ResolveContext ec)
                {
+                       if (NullPropagating && !IsNullPropagatingValid (type)) {
+                               Error_OperatorCannotBeApplied (ec, loc, "?", type);
+                               return null;
+                       }
+
                        if (type.IsArray)
-                               return (new ArrayAccess (this, loc));
+                               return new ArrayAccess (this, loc) {
+                                       NullShortCircuit = NullPropagating
+                               };
 
                        if (type.IsPointer)
                                return MakePointerAccess (ec, type);
@@ -9388,7 +9392,9 @@ namespace Mono.CSharp
 
                        var indexers = MemberCache.FindMembers (type, MemberCache.IndexerNameAlias, false);
                        if (indexers != null || type.BuiltinType == BuiltinTypeSpec.Type.Dynamic) {
-                               return new IndexerExpr (indexers, type, this);
+                               return new IndexerExpr (indexers, type, this) {
+                                       NullShortCircuit = NullPropagating
+                               };
                        }
 
                        if (type != InternalType.ErrorType) {
@@ -9514,6 +9520,8 @@ namespace Mono.CSharp
                        loc = l;
                }
 
+               public bool NullShortCircuit { get; set; }
+
                public void AddressOf (EmitContext ec, AddressOp mode)
                {
                        var ac = (ArrayContainer) ea.Expr.Type;
@@ -9532,6 +9540,9 @@ namespace Mono.CSharp
 
                public override Expression CreateExpressionTree (ResolveContext ec)
                {
+                       if (NullShortCircuit)
+                               Error_NullShortCircuitInsideExpressionTree (ec);
+
                        return ea.CreateExpressionTree (ec);
                }
 
@@ -9542,6 +9553,9 @@ namespace Mono.CSharp
 
                public override Expression DoResolveLValue (ResolveContext ec, Expression right_side)
                {
+                       if (NullShortCircuit)
+                               throw new NotSupportedException ("null propagating operator assignment");
+
                        return DoResolve (ec);
                }
 
@@ -9564,9 +9578,13 @@ namespace Mono.CSharp
                                UnsafeError (ec, ea.Location);
                        }
 
+                       if (NullShortCircuit)
+                               type = LiftMemberType (ec, type);
+
                        foreach (Argument a in ea.Arguments) {
-                               if (a is NamedArgument)
-                                       ElementAccess.Error_NamedArgument ((NamedArgument) a, ec.Report);
+                               var na = a as NamedArgument;
+                               if (na != null)
+                                       ElementAccess.Error_NamedArgument (na, ec.Report);
 
                                a.Expr = ConvertExpressionToArrayIndex (ec, a.Expr);
                        }
@@ -9589,30 +9607,35 @@ namespace Mono.CSharp
                //
                // Load the array arguments into the stack.
                //
-               void LoadInstanceAndArguments (EmitContext ec, bool duplicateArguments, bool prepareAwait)
+               InstanceEmitter LoadInstanceAndArguments (EmitContext ec, bool duplicateArguments, bool prepareAwait)
                {
+                       InstanceEmitter ie;
                        if (prepareAwait) {
+                               ie = new InstanceEmitter ();
                                ea.Expr = ea.Expr.EmitToField (ec);
-                       } else if (duplicateArguments) {
-                               ea.Expr.Emit (ec);
-                               ec.Emit (OpCodes.Dup);
-
-                               var copy = new LocalTemporary (ea.Expr.Type);
-                               copy.Store (ec);
-                               ea.Expr = copy;
                        } else {
-                               ea.Expr.Emit (ec);
+                               ie = new InstanceEmitter (ea.Expr, false);
+                               ie.NullShortCircuit = NullShortCircuit;
+                               ie.Emit (ec);
+
+                               if (duplicateArguments) {
+                                       ec.Emit (OpCodes.Dup);
+
+                                       var copy = new LocalTemporary (ea.Expr.Type);
+                                       copy.Store (ec);
+                                       ea.Expr = copy;
+                               }
                        }
 
                        var dup_args = ea.Arguments.Emit (ec, duplicateArguments, prepareAwait);
                        if (dup_args != null)
                                ea.Arguments = dup_args;
+
+                       return ie;
                }
 
                public void Emit (EmitContext ec, bool leave_copy)
                {
-                       var ac = ea.Expr.Type as ArrayContainer;
-
                        if (prepared) {
                                ec.EmitLoadFromPtr (type);
                        } else {
@@ -9620,8 +9643,12 @@ namespace Mono.CSharp
                                        LoadInstanceAndArguments (ec, false, true);
                                }
 
-                               LoadInstanceAndArguments (ec, false, false);
+                               var ac = (ArrayContainer) ea.Expr.Type;
+                               var inst = LoadInstanceAndArguments (ec, false, false);
                                ec.EmitArrayLoad (ac);
+
+                               if (NullShortCircuit)
+                                       inst.EmitResultLift (ec, ((ArrayContainer) ea.Expr.Type).Element, false);
                        }       
 
                        if (leave_copy) {
@@ -9810,6 +9837,10 @@ namespace Mono.CSharp
 
                public override Expression CreateExpressionTree (ResolveContext ec)
                {
+                       if (NullShortCircuit) {
+                               Error_NullShortCircuitInsideExpressionTree (ec);
+                       }
+
                        Arguments args = Arguments.CreateForExpressionTree (ec, arguments,
                                InstanceExpression.CreateExpressionTree (ec),
                                new TypeOfMethod (Getter, loc));
index de4158a3d2b4faa9a0f782961dba9f65a8069d76..83cec417b91dbc733fd2e1fe2ba4eb8bf712aa46 100644 (file)
@@ -11,22 +11,10 @@ class CI
     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;
@@ -34,16 +22,16 @@ class C
         var m2 = ci?.PropNullable;
         var m3 = ci?.PropReference;
 
-        ci?.Prop = 6;
+//        ci?.Prop = 6;
 
         ci = new CI ();
         m1 = ci?.Prop;
         m2 = ci?.PropNullable;
         m3 = ci?.PropReference;
 
-        ci?.Prop = 5;
-        if (ci.Prop != 5)
-            return 1;
+//        ci?.Prop = 5;
+//        if (ci.Prop != 5)
+//            return 1;
 
 // TODO: It's not allowed for now
 //      ci?.Prop += 4;
@@ -89,20 +77,11 @@ class C
 
         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;
@@ -114,7 +93,6 @@ class C
         res = TestEvent ();
         if (res != 0)
             return 30 + res;            
-*/
 
        Console.WriteLine ("ok");
         return 0;
diff --git a/mcs/tests/test-null-operator-05.cs b/mcs/tests/test-null-operator-05.cs
new file mode 100644 (file)
index 0000000..5fa5c6f
--- /dev/null
@@ -0,0 +1,58 @@
+using System;
+
+class CI
+{
+       public string this [string i] { set { } get { return ""; } }
+       public int? this [int i] { set { } get { return 1; } }
+}
+
+class C
+{
+       static int TestArrayAccess ()
+       {
+               byte[] arr = null;
+               var v = arr? [0];
+               if (v != null)
+                       return 1;
+
+               long?[] ar2 = null;
+               var v2 = ar2? [-1];
+               if (v2 != null)
+                       return 2;
+
+// TODO: Disabled for now?
+//        arr? [0] += 2;
+               return 0;
+       }
+
+       static int TestIndexerAccess ()
+       {
+               CI ci = null;
+               var v = ci? ["x"];
+               if (v != null)
+                       return 1;
+
+               var v2 = ci? [0];
+               if (v2 != null)
+                       return 2;
+
+// TODO: Disabled for now?
+//       ci? [0] += 3;
+               return 0;
+       }
+
+       static int Main ()
+       {
+               int res;
+               res = TestArrayAccess ();
+               if (res != 0)
+                       return 10 + res;
+
+               res = TestIndexerAccess ();
+               if (res != 0)
+                       return 20 + res;
+
+               Console.WriteLine ("ok");
+               return 0;
+       }
+}
\ No newline at end of file
index 098d947283595e5a8adaac692ae6f0ebaf26b52d..5f6c441e5d76052337fa535919ae25153b9ff726 100644 (file)
       <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
+        <size>86</size>\r
       </method>\r
       <method name="Void .ctor()" attrs="6278">\r
         <size>7</size>\r
       </method>\r
+      <method name="Int32 TestProperty()" attrs="145">\r
+        <size>168</size>\r
+      </method>\r
+      <method name="Int32 TestField()" attrs="145">\r
+        <size>168</size>\r
+      </method>\r
+      <method name="Int32 TestEvent()" attrs="145">\r
+        <size>50</size>\r
+      </method>\r
     </type>\r
   </test>\r
   <test name="test-null-operator-03.cs">\r
       </method>\r
     </type>\r
   </test>\r
+  <test name="test-null-operator-05.cs">\r
+    <type name="CI">\r
+      <method name="Void set_Item(System.String, System.String)" attrs="2182">\r
+        <size>2</size>\r
+      </method>\r
+      <method name="System.String get_Item(System.String)" attrs="2182">\r
+        <size>14</size>\r
+      </method>\r
+      <method name="Void set_Item(Int32, System.Nullable`1[System.Int32])" attrs="2182">\r
+        <size>2</size>\r
+      </method>\r
+      <method name="System.Nullable`1[System.Int32] get_Item(Int32)" attrs="2182">\r
+        <size>15</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 TestArrayAccess()" attrs="145">\r
+        <size>114</size>\r
+      </method>\r
+      <method name="Int32 TestIndexerAccess()" attrs="145">\r
+        <size>93</size>\r
+      </method>\r
+      <method name="Int32 Main()" attrs="145">\r
+        <size>64</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