[mcs] switch statement with recursive pattern matching
authorMarek Safar <marek.safar@gmail.com>
Mon, 8 Sep 2014 09:46:27 +0000 (11:46 +0200)
committerMarek Safar <marek.safar@gmail.com>
Mon, 8 Sep 2014 09:48:52 +0000 (11:48 +0200)
mcs/mcs/cs-parser.jay
mcs/mcs/expression.cs
mcs/mcs/module.cs
mcs/mcs/statement.cs
mcs/tests/test-pattern-06.cs [new file with mode: 0644]
mcs/tests/ver-il-net_4_5.xml

index 84d4e3f9f1b93c8579470abe7c1ef84cac526ebb..45f719a37380cea8014eaa8119e4218557fc71c3 100644 (file)
@@ -4352,13 +4352,16 @@ pattern_expr
                $$ = new Unary (Unary.Operator.UnaryNegation, (Expression) $2, GetLocation ($1));
          }
        | sizeof_expression
-       | invocation_expression
        | default_value_expression
        | STAR
          {
                $$ = new WildcardPattern (GetLocation ($1));
          }
-       | type_name_expression OPEN_PARENS opt_pattern_list CLOSE_PARENS
+       | pattern_expr_invocation
+       ;
+
+pattern_expr_invocation
+       : type_name_expression OPEN_PARENS opt_pattern_list CLOSE_PARENS
          {
                $$ = new RecursivePattern ((ATypeNameExpression) $1, (Arguments) $3, GetLocation ($2));
          }
@@ -4368,9 +4371,11 @@ pattern
        : pattern_expr
        | pattern_type_expr opt_identifier
          {
-               var lt = (LocatedToken) $2;
-               var variable = new LocalVariable (current_block, lt.Value, lt.Location);
-               current_block.AddLocalName (variable);    
+               if ($2 != null) {
+                       var lt = (LocatedToken) $2;
+                       var variable = new LocalVariable (current_block, lt.Value, lt.Location);
+                       current_block.AddLocalName (variable);
+               }
          }
        ;
 
@@ -5817,6 +5822,16 @@ switch_label
                Error_SyntaxError (yyToken);
                $$ = new SwitchLabel ((Expression) $2, GetLocation ($1));
          }
+       | CASE pattern_expr_invocation COLON
+         {
+               if (lang_version != LanguageVersion.Experimental)
+                       FeatureIsNotAvailable (GetLocation ($2), "pattern matching");
+
+               $$ = new SwitchLabel ((Expression) $2, GetLocation ($1)) {
+                       PatternMatching = true
+               };
+               lbag.AddLocation ($$, GetLocation ($3));
+         }
        | DEFAULT_COLON
          {
                $$ = new SwitchLabel (null, GetLocation ($1));
index a8573955cf3d80062353f83a8eab05b4cdba0be2..ed2860b7766685aaa149d24bdfe5ce0e34b171d0 100644 (file)
@@ -1807,6 +1807,10 @@ namespace Mono.CSharp
                        }
 
                        if (ProbeType is PatternExpression) {
+                               if (!(ProbeType is WildcardPattern) && !Convert.ImplicitConversionExists (rc, ProbeType, Expr.Type)) {
+                                       ProbeType.Error_ValueCannotBeConverted (rc, Expr.Type, false);
+                               }
+
                                return this;
                        }
 
index 68f1b8ece11b4e0daa90547ebc6d11630b2f9e94..138ec61638f00ed03c0955b9f90684c96516fde6 100644 (file)
@@ -181,6 +181,7 @@ namespace Mono.CSharp
 
 
                                var system_convert = new MemberAccess (new QualifiedAliasMember ("global", "System", loc), "Convert", loc);
+                               var expl_block = new ExplicitBlock (top_block, loc, loc);
 
                                //
                                // var converted = System.Convert.ChangeType (obj, System.Convert.GetTypeCode (value));
@@ -198,7 +199,7 @@ namespace Mono.CSharp
 
                                var changetype = new Invocation (new MemberAccess (system_convert, "ChangeType", loc), arguments_changetype);
 
-                               top_block.AddStatement (new StatementExpression (new SimpleAssign (new LocalVariableReference (lv_converted, loc), changetype, loc)));
+                               expl_block.AddStatement (new StatementExpression (new SimpleAssign (new LocalVariableReference (lv_converted, loc), changetype, loc)));
 
 
                                //
@@ -207,7 +208,13 @@ namespace Mono.CSharp
                                var equals_arguments = new Arguments (1);
                                equals_arguments.Add (new Argument (top_block.GetParameterReference (1, loc)));
                                var equals_invocation = new Invocation (new MemberAccess (new LocalVariableReference (lv_converted, loc), "Equals"), equals_arguments);
-                               top_block.AddStatement (new Return (equals_invocation, loc));
+                               expl_block.AddStatement (new Return (equals_invocation, loc));
+
+                               var catch_block = new ExplicitBlock (top_block, loc, loc);
+                               catch_block.AddStatement (new Return (new BoolLiteral (Compiler.BuiltinTypes, false, loc), loc));
+                               top_block.AddStatement (new TryCatch (expl_block, new List<Catch> () {
+                                       new Catch (catch_block, loc)
+                               }, loc, false));
 
                                m.Define ();
                                m.PrepareEmit ();
index 441d65780c1fe7095e5ab81f6536b7abc8b220ca..e46dca447eb4696c322dae674237ffbb2bcc4e40 100644 (file)
@@ -4471,6 +4471,8 @@ namespace Mono.CSharp {
                        }
                }
 
+               public bool PatternMatching { get; set; }
+
                public bool SectionStart { get; set; }
 
                public Label GetILLabel (EmitContext ec)
@@ -4508,21 +4510,33 @@ namespace Mono.CSharp {
                // Resolves the expression, reduces it to a literal if possible
                // and then converts it to the requested type.
                //
-               bool ResolveAndReduce (BlockContext rc)
+               bool ResolveAndReduce (BlockContext bc)
                {
                        if (IsDefault)
                                return true;
 
-                       var c = label.ResolveLabelConstant (rc);
+                       var switch_statement = bc.Switch;
+
+                       if (PatternMatching) {
+                               label = new Is (switch_statement.ExpressionValue, label, loc).Resolve (bc);
+                               return label != null;
+                       }
+
+                       var c = label.ResolveLabelConstant (bc);
                        if (c == null)
                                return false;
 
-                       if (rc.Switch.IsNullable && c is NullLiteral) {
+                       if (switch_statement.IsNullable && c is NullLiteral) {
                                converted = c;
                                return true;
                        }
 
-                       converted = c.ImplicitConversionRequired (rc, rc.Switch.SwitchType);
+                       if (switch_statement.IsPatternMatching) {
+                               label = new Is (switch_statement.ExpressionValue, label, loc).Resolve (bc);
+                               return true;
+                       }
+
+                       converted = c.ImplicitConversionRequired (bc, switch_statement.SwitchType);
                        return converted != null;
                }
 
@@ -4728,12 +4742,24 @@ namespace Mono.CSharp {
                        }
                }
 
+               public bool IsPatternMatching {
+                       get {
+                               return new_expr == null && SwitchType != null;
+                       }
+               }
+
                public List<SwitchLabel> RegisteredLabels {
                        get {
                                return case_labels;
                        }
                }
 
+               public VariableReference ExpressionValue {
+                       get {
+                               return value;
+                       }
+               }
+
                //
                // Determines the governing type for a switch.  The returned
                // expression might be the expression from the switch, or an
@@ -4843,6 +4869,9 @@ namespace Mono.CSharp {
                                return;
                        }
 
+                       if (sl.Converted == null)
+                               return;
+
                        try {
                                if (string_labels != null) {
                                        string string_value = sl.Converted.GetValue () as string;
@@ -4851,7 +4880,7 @@ namespace Mono.CSharp {
                                        else
                                                string_labels.Add (string_value, sl);
                                } else {
-                                       if (sl.Converted is NullLiteral) {
+                                       if (sl.Converted.IsNull) {
                                                case_null = sl;
                                        } else {
                                                labels.Add (sl.Converted.GetValueAsLong (), sl);
@@ -5066,39 +5095,44 @@ namespace Mono.CSharp {
                                }
                        }
 
+                       Expression switch_expr;
                        if (new_expr == null) {
-                               if (Expr.Type != InternalType.ErrorType) {
-                                       ec.Report.Error (151, loc,
-                                               "A switch expression of type `{0}' cannot be converted to an integral type, bool, char, string, enum or nullable type",
-                                               Expr.Type.GetSignatureForError ());
-                               }
+                               if (ec.Module.Compiler.Settings.Version != LanguageVersion.Experimental) {
+                                       if (Expr.Type != InternalType.ErrorType) {
+                                               ec.Report.Error (151, loc,
+                                                       "A switch expression of type `{0}' cannot be converted to an integral type, bool, char, string, enum or nullable type",
+                                                       Expr.Type.GetSignatureForError ());
+                                       }
 
-                               return false;
-                       }
+                                       return false;
+                               }
 
-                       SwitchType = new_expr.Type;
-                       if (SwitchType.IsNullableType) {
-                               new_expr = unwrap = Nullable.Unwrap.Create (new_expr, true);
-                               SwitchType = Nullable.NullableInfo.GetUnderlyingType (SwitchType);
-                       }
+                               switch_expr = Expr;
+                               SwitchType = Expr.Type;
+                       } else {
+                               switch_expr = new_expr;
+                               SwitchType = new_expr.Type;
+                               if (SwitchType.IsNullableType) {
+                                       new_expr = unwrap = Nullable.Unwrap.Create (new_expr, true);
+                                       SwitchType = Nullable.NullableInfo.GetUnderlyingType (SwitchType);
+                               }
 
-                       if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.Bool && ec.Module.Compiler.Settings.Version == LanguageVersion.ISO_1) {
-                               ec.Report.FeatureIsNotAvailable (ec.Module.Compiler, loc, "switch expression of boolean type");
-                               return false;
-                       }
+                               if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.Bool && ec.Module.Compiler.Settings.Version == LanguageVersion.ISO_1) {
+                                       ec.Report.FeatureIsNotAvailable (ec.Module.Compiler, loc, "switch expression of boolean type");
+                                       return false;
+                               }
 
-                       if (block.Statements.Count == 0)
-                               return true;
+                               if (block.Statements.Count == 0)
+                                       return true;
 
-                       if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.String) {
-                               string_labels = new Dictionary<string, SwitchLabel> ();
-                       } else {
-                               labels = new Dictionary<long, SwitchLabel> ();
+                               if (SwitchType.BuiltinType == BuiltinTypeSpec.Type.String) {
+                                       string_labels = new Dictionary<string, SwitchLabel> ();
+                               } else {
+                                       labels = new Dictionary<long, SwitchLabel> ();
+                               }
                        }
 
-                       case_labels = new List<SwitchLabel> ();
-
-                       var constant = new_expr as Constant;
+                       var constant = switch_expr as Constant;
 
                        //
                        // Don't need extra variable for constant switch or switch with
@@ -5108,7 +5142,7 @@ namespace Mono.CSharp {
                                //
                                // Store switch expression for comparison purposes
                                //
-                               value = new_expr as VariableReference;
+                               value = switch_expr as VariableReference;
                                if (value == null && !HasOnlyDefaultSection ()) {
                                        var current_block = ec.CurrentBlock;
                                        ec.CurrentBlock = Block;
@@ -5119,6 +5153,8 @@ namespace Mono.CSharp {
                                }
                        }
 
+                       case_labels = new List<SwitchLabel> ();
+
                        Switch old_switch = ec.Switch;
                        ec.Switch = this;
                        var parent_los = ec.EnclosingLoopOrSwitch;
@@ -5411,6 +5447,11 @@ namespace Mono.CSharp {
 
                                var constant = label.Converted;
 
+                               if (constant == null) {
+                                       label.Label.EmitBranchable (ec, label.GetILLabel (ec), true);
+                                       continue;
+                               }
+
                                if (equal_method != null) {
                                        value.Emit (ec);
                                        constant.Emit (ec);
@@ -5436,6 +5477,11 @@ namespace Mono.CSharp {
 
                void EmitDispatch (EmitContext ec)
                {
+                       if (IsPatternMatching) {
+                               EmitShortSwitch (ec);
+                               return;
+                       }
+
                        if (value == null) {
                                //
                                // Constant switch, we've already done the work if there is only 1 label
@@ -5483,12 +5529,14 @@ namespace Mono.CSharp {
 
                        if (value != null) {
                                ec.Mark (loc);
+
+                               var switch_expr = new_expr ?? Expr;
                                if (IsNullable) {
                                        unwrap.EmitCheck (ec);
                                        ec.Emit (OpCodes.Brfalse, nullLabel);
-                                       value.EmitAssign (ec, new_expr, false, false);
-                               } else if (new_expr != value) {
-                                       value.EmitAssign (ec, new_expr, false, false);
+                                       value.EmitAssign (ec, switch_expr, false, false);
+                               } else if (switch_expr != value) {
+                                       value.EmitAssign (ec, switch_expr, false, false);
                                }
 
 
diff --git a/mcs/tests/test-pattern-06.cs b/mcs/tests/test-pattern-06.cs
new file mode 100644 (file)
index 0000000..3b54b84
--- /dev/null
@@ -0,0 +1,68 @@
+// Compiler options: -langversion:experimental
+
+using System;
+
+class RecursiveNamedPattern
+{
+       public static int Main ()
+       {
+               if (Switch_1 (null) != 4)
+                       return 1;
+
+               if (Switch_1 ("x") != 5)
+                       return 2;
+
+               if (Switch_1 (1) != 1)
+                       return 3;
+
+               if (Switch_1 (new C1 ()) != 3)
+                       return 4;
+
+               if (Switch_1 ((byte?) 1) != 1)
+                       return 5;
+
+               if (Switch_2 (new C1 ()) != 3)
+                       return 10;
+
+               if (Switch_2 (null) != 2)
+                       return 11;
+
+               Console.WriteLine ("ok");
+               return 0;
+       }
+
+       static int Switch_1 (object o)
+       {
+               switch (o) {
+                       case 1:
+                               return 1;
+                       case C1 (3):
+                               return 2;
+                       case C1 (2):
+                               return 3;
+                       case null:
+                               return 4;
+                       default:
+                               return 5;
+               }
+       }
+
+       static int Switch_2 (C1 o)
+       {
+               switch (o) {
+                       case null:
+                               return 2;
+               }
+
+               return 3;
+       }
+}
+
+public class C1
+{
+       public static bool operator is (C1 c1, out int i)
+       {
+               i = 2;
+               return true;
+       }
+}
index 3f1b70389a4e2705fc2108465e5abce4fa711ccd..12fab60c02747f28b228efaed3554d42812f04d4 100644 (file)
     </type>\r
     <type name="&lt;PatternMatchingHelper&gt;">\r
       <method name="Boolean NumberMatcher(System.Object, System.Object, Boolean)" attrs="150">\r
-        <size>54</size>\r
+        <size>69</size>\r
       </method>\r
     </type>\r
     <type name="ConstantPattern">\r
       </method>\r
     </type>\r
   </test>\r
+  <test name="test-pattern-06.cs">\r
+    <type name="RecursiveNamedPattern">\r
+      <method name="Int32 Main()" attrs="150">\r
+        <size>182</size>\r
+      </method>\r
+      <method name="Int32 Switch_1(System.Object)" attrs="145">\r
+        <size>149</size>\r
+      </method>\r
+      <method name="Int32 Switch_2(C1)" attrs="145">\r
+        <size>28</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+    <type name="C1">\r
+      <method name="Boolean op_Is(C1, Int32 ByRef)" attrs="2198">\r
+        <size>13</size>\r
+      </method>\r
+      <method name="Void .ctor()" attrs="6278">\r
+        <size>7</size>\r
+      </method>\r
+    </type>\r
+    <type name="&lt;PatternMatchingHelper&gt;">\r
+      <method name="Boolean NumberMatcher(System.Object, System.Object, Boolean)" attrs="150">\r
+        <size>69</size>\r
+      </method>\r
+    </type>\r
+  </test>\r
   <test name="test-primary-ctor-01.cs">\r
     <type name="Simple">\r
       <method name="Int32 get_Property()" attrs="2177">\r