diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
index 556ac846ac129..743f8a6464050 100644
--- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
+++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
@@ -103,11 +103,11 @@ static uint ComputeStringHash(string s)
}
/// Gets whether a given regular expression method is supported by the code generator.
- private static bool SupportsCodeGeneration(RegexMethod rm)
+ private static bool SupportsCodeGeneration(RegexMethod rm, out string? reason)
{
RegexNode root = rm.Tree.Root;
- if (!root.SupportsCompilation())
+ if (!root.SupportsCompilation(out reason))
{
return false;
}
@@ -119,6 +119,7 @@ private static bool SupportsCodeGeneration(RegexMethod rm)
// Place an artificial limit on max tree depth in order to mitigate such issues.
// The allowed depth can be tweaked as needed;its exceedingly rare to find
// expressions with such deep trees.
+ reason = "the regex will result in code that may exceed C# compiler limits";
return false;
}
@@ -163,8 +164,10 @@ private static ImmutableArray EmitRegexMethod(IndentedTextWriter wri
writer.Write(" public static global::System.Text.RegularExpressions.Regex Instance { get; } = ");
// If we can't support custom generation for this regex, spit out a Regex constructor call.
- if (!SupportsCodeGeneration(rm))
+ if (!SupportsCodeGeneration(rm, out string? reason))
{
+ writer.WriteLine();
+ writer.WriteLine($"// Cannot generate Regex-derived implementation because {reason}.");
writer.WriteLine($"new global::System.Text.RegularExpressions.Regex({patternExpression}, {optionsExpression}, {timeoutExpression});");
writer.WriteLine("}");
return ImmutableArray.Create(Diagnostic.Create(DiagnosticDescriptors.LimitedSourceGeneration, rm.MethodSyntax.GetLocation()));
diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs
index 60b1398b0268f..145d708d0c08a 100644
--- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs
+++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexLWCGCompiler.cs
@@ -32,7 +32,7 @@ internal sealed class RegexLWCGCompiler : RegexCompiler
/// The top-level driver. Initializes everything then calls the Generate* methods.
public RegexRunnerFactory? FactoryInstanceFromCode(string pattern, RegexTree regexTree, RegexOptions options, bool hasTimeout)
{
- if (!regexTree.Root.SupportsCompilation())
+ if (!regexTree.Root.SupportsCompilation(out _))
{
return null;
}
diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs
index 93df3aef238ad..e2bb2f4940cce 100644
--- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs
+++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/RegexNode.cs
@@ -1223,7 +1223,7 @@ static RegexNode ExtractCommonPrefixText(RegexNode alternation)
// Now compare the rest of the branches against it.
int endingIndex = startingIndex + 1;
- for ( ; endingIndex < children.Count; endingIndex++)
+ for (; endingIndex < children.Count; endingIndex++)
{
// Get the starting node of the next branch.
startingNode = children[endingIndex].FindBranchOneOrMultiStart();
@@ -2566,18 +2566,27 @@ public int ChildCount()
}
// Determines whether the node supports a compilation / code generation strategy based on walking the node tree.
- internal bool SupportsCompilation()
+ // Also returns a human-readable string to explain the reason (it will be emitted by the source generator, hence
+ // there's no need to localize).
+ internal bool SupportsCompilation([NotNullWhen(false)] out string? reason)
{
if (!StackHelper.TryEnsureSufficientExecutionStack())
{
- // If we can't recur further, code generation isn't supported as the tree is too deep.
+ reason = "run-time limits were exceeded";
return false;
}
- if ((Options & (RegexOptions.RightToLeft | RegexOptions.NonBacktracking)) != 0)
+ // NonBacktracking isn't supported, nor RightToLeft. The latter applies to both the top-level
+ // options as well as when used to specify positive and negative lookbehinds.
+ if ((Options & RegexOptions.NonBacktracking) != 0)
+ {
+ reason = "RegexOptions.NonBacktracking was specified";
+ return false;
+ }
+
+ if ((Options & RegexOptions.RightToLeft) != 0)
{
- // NonBacktracking isn't supported, nor RightToLeft. The latter applies to both the top-level
- // options as well as when used to specify positive and negative lookbehinds.
+ reason = "RegexOptions.RightToLeft or a positive/negative lookbehind was used";
return false;
}
@@ -2585,13 +2594,14 @@ internal bool SupportsCompilation()
for (int i = 0; i < childCount; i++)
{
// The node isn't supported if any of its children aren't supported.
- if (!Child(i).SupportsCompilation())
+ if (!Child(i).SupportsCompilation(out reason))
{
return false;
}
}
// Supported.
+ reason = null;
return true;
}