Add missing case in PruneLowerPriorityThanNullability (#72871)
authorOlli Saarikivi <olsaarik@microsoft.com>
Tue, 26 Jul 2022 23:31:20 +0000 (16:31 -0700)
committerGitHub <noreply@github.com>
Tue, 26 Jul 2022 23:31:20 +0000 (16:31 -0700)
* Add missing case in pruning

* Enable previously failing Rust test

src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Symbolic/SymbolicRegexNode.cs
src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/RegexRustTests.cs

index ad55cc5..8460923 100644 (file)
@@ -1072,9 +1072,8 @@ namespace System.Text.RegularExpressions.Symbolic
             }
             else
             {
-                // If this node is nullable for the given context then prune any branches that are less preferred than
-                // just the empty match. This is done in order to maintain backtracking semantics.
-                SymbolicRegexNode<TSet> node = IsNullableFor(context) ? PruneLowerPriorityThanNullability(builder, context) : this;
+                // To maintain backtracking semantics, prune any branches that are less preferred than just the empty match
+                SymbolicRegexNode<TSet> node = PruneLowerPriorityThanNullability(builder, context);
                 return node.CreateDerivative(builder, elem, context);
             }
         }
@@ -1082,7 +1081,10 @@ namespace System.Text.RegularExpressions.Symbolic
         /// <summary>Prune this node wrt the given context in order to maintain backtracking semantics. Mimics how backtracking chooses a path.</summary>
         private SymbolicRegexNode<TSet> PruneLowerPriorityThanNullability(SymbolicRegexBuilder<TSet> builder, uint context)
         {
-            //caching pruning to avoid otherwise potential quadratic worst case behavior
+            if (!IsNullableFor(context))
+                return this;
+
+            // Cache result to avoid otherwise potential quadratic worst case behavior
             SymbolicRegexNode<TSet>? prunedNode;
             (SymbolicRegexNode<TSet>, uint) key = (this, context);
             if (builder._pruneLowerPriorityThanNullabilityCache.TryGetValue(key, out prunedNode))
@@ -1125,19 +1127,21 @@ namespace System.Text.RegularExpressions.Symbolic
                         CreateConcat(builder, _left.PruneLowerPriorityThanNullability(builder, context), _right.PruneLowerPriorityThanNullability(builder, context));
                     break;
 
-                case SymbolicRegexNodeKind.Loop when _info.IsLazyLoop && _lower == 0:
-                    //lazy nullable loop reduces to (), i.e., the loop body is just forgotten
-                    prunedNode = builder.Epsilon;
+                case SymbolicRegexNodeKind.Loop:
+                    Debug.Assert(_left is not null);
+                    // Lazy nullable loop reduces to (), i.e., the loop body is just forgotten
+                    prunedNode = _info.IsLazyLoop && _lower == 0 ? builder.Epsilon :
+                        CreateLoop(builder, _left.PruneLowerPriorityThanNullability(builder, context), _lower, _upper, _info.IsLazyLoop);
                     break;
 
                 case SymbolicRegexNodeKind.Effect:
-                    //Effects are maintained and the pruning is propagated to the body of the effect
+                    // Effects are maintained and the pruning is propagated to the body of the effect
                     Debug.Assert(_left is not null && _right is not null);
                     prunedNode = CreateEffect(builder, _left.PruneLowerPriorityThanNullability(builder, context), _right);
                     break;
 
                 default:
-                    //In all other remaining cases no pruning takes place
+                    // In all other remaining cases no pruning takes place
                     prunedNode = this;
                     break;
             }
index 45b7f54..102e1e9 100644 (file)
@@ -61,11 +61,8 @@ namespace System.Text.RegularExpressions.Tests
                 yield return new object[] { engine, @"(?m)$[a-z]", "abc\ndef\nxyz", new ValueTuple<int, int>[] { } };
                 yield return new object[] { engine, @"(?m)^$", "", new[] { (0, 0) } };
                 yield return new object[] { engine, @"(?m)(?:^$)*", "a\nb\nc", new[] { (0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5) } };
-                if (!RegexHelpers.IsNonBacktracking(engine)) // https://github.com/dotnet/runtime/issues/72470
-                {
-                    yield return new object[] { engine, @"(?m)(?:^|a)+", "a\naaa\n", new[] { (0, 0), (2, 2), (3, 5), (6, 6) } };
-                    yield return new object[] { engine, @"(?m)(?:^|a)*", "a\naaa\n", new[] { (0, 0), (1, 1), (2, 2), (3, 5), (5, 5), (6, 6) } };
-                }
+                yield return new object[] { engine, @"(?m)(?:^|a)+", "a\naaa\n", new[] { (0, 0), (2, 2), (3, 5), (6, 6) } };
+                yield return new object[] { engine, @"(?m)(?:^|a)*", "a\naaa\n", new[] { (0, 0), (1, 1), (2, 2), (3, 5), (5, 5), (6, 6) } };
                 yield return new object[] { engine, @"(?m)(?:^[a-z])+", "abc\ndef\nxyz", new[] { (0, 1), (4, 5), (8, 9) } };
                 yield return new object[] { engine, @"(?m)(?:^[a-z]{3}\n?)+", "abc\ndef\nxyz", new[] { (0, 11) } };
                 yield return new object[] { engine, @"(?m)(?:^[a-z]{3}\n?)*", "abc\ndef\nxyz", new[] { (0, 11), (11, 11) } };