diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
index d4e697bc177fd..d7f11af56b3a2 100644
--- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
+++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs
@@ -25,23 +25,23 @@ namespace System.Text.RegularExpressions.Generator
public partial class RegexGenerator
{
/// Emits the definition of the partial method. This method just delegates to the property cache on the generated Regex-derived type.
- private static void EmitRegexPartialMethod(RegexType regexClass, IndentedTextWriter writer, string generatedClassName, int id)
+ private static void EmitRegexPartialMethod(RegexMethod regexMethod, IndentedTextWriter writer, string generatedClassName)
{
- // Emit the namespace
- if (!string.IsNullOrWhiteSpace(regexClass.Namespace))
+ // Emit the namespace.
+ RegexType? parent = regexMethod.DeclaringType;
+ if (!string.IsNullOrWhiteSpace(parent.Namespace))
{
- writer.WriteLine($"namespace {regexClass.Namespace}");
+ writer.WriteLine($"namespace {parent.Namespace}");
writer.WriteLine("{");
writer.Indent++;
}
- // Emit containing types
- RegexType? parent = regexClass.ParentClass;
+ // Emit containing types.
var parentClasses = new Stack();
while (parent is not null)
{
parentClasses.Push($"partial {parent.Keyword} {parent.Name}");
- parent = parent.ParentClass;
+ parent = parent.Parent;
}
while (parentClasses.Count != 0)
{
@@ -50,12 +50,11 @@ private static void EmitRegexPartialMethod(RegexType regexClass, IndentedTextWri
writer.Indent++;
}
- // Emit the direct parent type, including the partial method definition
- writer.WriteLine($"partial {regexClass.Keyword} {regexClass.Name}");
- writer.WriteLine("{");
- writer.Indent++;
+ // Emit the partial method definition.
writer.WriteLine($"[global::System.CodeDom.Compiler.{s_generatedCodeAttribute}]");
- writer.WriteLine($"{regexClass.Method.Modifiers} global::System.Text.RegularExpressions.Regex {regexClass.Method.MethodName}() => global::{GeneratedNamespace}.{generatedClassName}.{regexClass.Method.MethodName}_{id}.Instance;");
+ writer.WriteLine($"{regexMethod.Modifiers} global::System.Text.RegularExpressions.Regex {regexMethod.MethodName}() => global::{GeneratedNamespace}.{generatedClassName}.{regexMethod.GeneratedName}.Instance;");
+
+ // Unwind all scopes
while (writer.Indent != 0)
{
writer.Indent--;
@@ -69,7 +68,7 @@ private static void EmitRegexPartialMethod(RegexType regexClass, IndentedTextWri
{
writer.WriteLine($"/// Caches a instance for the {rm.MethodName} method.");
writer.WriteLine($"/// A custom Regex-derived type could not be generated because {reason}.");
- writer.WriteLine($"internal sealed class {rm.MethodName}_{id} : Regex");
+ writer.WriteLine($"internal sealed class {rm.GeneratedName} : Regex");
writer.WriteLine($"{{");
writer.WriteLine($" /// Cached, thread-safe singleton instance.");
writer.WriteLine($" internal static Regex Instance {{ get; }} = new({Literal(rm.Pattern)}, {Literal(rm.Options)}, {GetTimeoutExpression(rm.MatchTimeout)});");
@@ -81,13 +80,13 @@ private static void EmitRegexPartialMethod(RegexType regexClass, IndentedTextWri
IndentedTextWriter writer, RegexMethod rm, int id, string runnerFactoryImplementation)
{
writer.WriteLine($"/// Custom -derived type for the {rm.MethodName} method.");
- writer.WriteLine($"internal sealed class {rm.MethodName}_{id} : Regex");
+ writer.WriteLine($"internal sealed class {rm.GeneratedName} : Regex");
writer.WriteLine($"{{");
writer.WriteLine($" /// Cached, thread-safe singleton instance.");
- writer.WriteLine($" internal static {rm.MethodName}_{id} Instance {{ get; }} = new();");
+ writer.WriteLine($" internal static {rm.GeneratedName} Instance {{ get; }} = new();");
writer.WriteLine($"");
writer.WriteLine($" /// Initializes the instance.");
- writer.WriteLine($" private {rm.MethodName}_{id}()");
+ writer.WriteLine($" private {rm.GeneratedName}()");
writer.WriteLine($" {{");
writer.WriteLine($" base.pattern = {Literal(rm.Pattern)};");
writer.WriteLine($" base.roptions = {Literal(rm.Options)};");
diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs
index 3f6ae21618522..d6e1a015aee81 100644
--- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs
+++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs
@@ -192,7 +192,13 @@ private static bool IsSemanticTargetForGeneration(SemanticModel semanticModel, M
string? ns = regexMethodSymbol.ContainingType?.ContainingNamespace?.ToDisplayString(
SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted));
+ var regexType = new RegexType(
+ typeDec is RecordDeclarationSyntax rds ? $"{typeDec.Keyword.ValueText} {rds.ClassOrStructKeyword}" : typeDec.Keyword.ValueText,
+ ns ?? string.Empty,
+ $"{typeDec.Identifier}{typeDec.TypeParameterList}");
+
var regexMethod = new RegexMethod(
+ regexType,
methodSyntax,
regexMethodSymbol.Name,
methodSyntax.Modifiers.ToString(),
@@ -201,28 +207,21 @@ private static bool IsSemanticTargetForGeneration(SemanticModel semanticModel, M
matchTimeout ?? Timeout.Infinite,
regexTree);
- var regexType = new RegexType(
- regexMethod,
- typeDec is RecordDeclarationSyntax rds ? $"{typeDec.Keyword.ValueText} {rds.ClassOrStructKeyword}" : typeDec.Keyword.ValueText,
- ns ?? string.Empty,
- $"{typeDec.Identifier}{typeDec.TypeParameterList}");
-
RegexType current = regexType;
var parent = typeDec.Parent as TypeDeclarationSyntax;
while (parent is not null && IsAllowedKind(parent.Kind()))
{
- current.ParentClass = new RegexType(
- null,
+ current.Parent = new RegexType(
parent is RecordDeclarationSyntax rds2 ? $"{parent.Keyword.ValueText} {rds2.ClassOrStructKeyword}" : parent.Keyword.ValueText,
ns ?? string.Empty,
$"{parent.Identifier}{parent.TypeParameterList}");
- current = current.ParentClass;
+ current = current.Parent;
parent = parent.Parent as TypeDeclarationSyntax;
}
- return regexType;
+ return regexMethod;
static bool IsAllowedKind(SyntaxKind kind) =>
kind == SyntaxKind.ClassDeclaration ||
@@ -233,13 +232,16 @@ private static bool IsSemanticTargetForGeneration(SemanticModel semanticModel, M
}
/// A regex method.
- internal sealed record RegexMethod(MethodDeclarationSyntax MethodSyntax, string MethodName, string Modifiers, string Pattern, RegexOptions Options, int MatchTimeout, RegexTree Tree);
+ internal sealed record RegexMethod(RegexType DeclaringType, MethodDeclarationSyntax MethodSyntax, string MethodName, string Modifiers, string Pattern, RegexOptions Options, int MatchTimeout, RegexTree Tree)
+ {
+ public int GeneratedId { get; set; }
+ public string GeneratedName => $"{MethodName}_{GeneratedId}";
+ }
/// A type holding a regex method.
- internal sealed record RegexType(RegexMethod? Method, string Keyword, string Namespace, string Name)
+ internal sealed record RegexType(string Keyword, string Namespace, string Name)
{
- public RegexType? ParentClass { get; set; }
- public int GeneratedId { get; set; }
+ public RegexType? Parent { get; set; }
}
}
}
diff --git a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs
index eca96833f2427..69b8f55b6e828 100644
--- a/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs
+++ b/src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs
@@ -45,8 +45,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Produces one entry per generated regex. This may be:
// - Diagnostic in the case of a failure that should end the compilation
- // - (RegexType regexType, string runnerFactoryImplementation, Dictionary requiredHelpers) in the case of valid regex
- // - (RegexType regexType, string reason, Diagnostic diagnostic) in the case of a limited-support regex
+ // - (RegexMethod regexMethod, string runnerFactoryImplementation, Dictionary requiredHelpers) in the case of valid regex
+ // - (RegexMethod regexMethod, string reason, Diagnostic diagnostic) in the case of a limited-support regex
IncrementalValueProvider> codeOrDiagnostics =
context.SyntaxProvider
@@ -57,7 +57,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// Generate the RunnerFactory for each regex, if possible. This is where the bulk of the implementation occurs.
.Select((state, _) =>
{
- if (state is not RegexType regexType)
+ if (state is not RegexMethod regexMethod)
{
Debug.Assert(state is Diagnostic);
return state;
@@ -65,9 +65,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// If we're unable to generate a full implementation for this regex, report a diagnostic.
// We'll still output a limited implementation that just caches a new Regex(...).
- if (!regexType.Method.Tree.Root.SupportsCompilation(out string? reason))
+ if (!regexMethod.Tree.Root.SupportsCompilation(out string? reason))
{
- return (regexType, reason, Diagnostic.Create(DiagnosticDescriptors.LimitedSourceGeneration, regexType.Method.MethodSyntax.GetLocation()));
+ return (regexMethod, reason, Diagnostic.Create(DiagnosticDescriptors.LimitedSourceGeneration, regexMethod.MethodSyntax.GetLocation()));
}
// Generate the core logic for the regex.
@@ -76,9 +76,9 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
var writer = new IndentedTextWriter(sw);
writer.Indent += 3;
writer.WriteLine();
- EmitRegexDerivedTypeRunnerFactory(writer, regexType.Method, requiredHelpers);
+ EmitRegexDerivedTypeRunnerFactory(writer, regexMethod, requiredHelpers);
writer.Indent -= 3;
- return (regexType, sw.ToString(), requiredHelpers);
+ return (regexMethod, sw.ToString(), requiredHelpers);
})
.Collect();
@@ -128,37 +128,28 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// For every generated type, we give it an incrementally increasing ID, in order to create
// unique type names even in situations where method names were the same, while also keeping
// the type names short. Note that this is why we only generate the RunnerFactory implementations
- // earlier in the pipeline... we wait to avoid generating code that relies on the class names
+ // earlier in the pipeline... we want to avoid generating code that relies on the class names
// until we're able to iterate through them linearly keeping track of a deterministic ID
// used to name them. The boilerplate code generation that happens here is minimal when compared to
// the work required to generate the actual matching code for the regex.
int id = 0;
string generatedClassName = $"__{ComputeStringHash(compilationDataAndResults.Right.AssemblyName ?? ""):x}";
- // If we have any (RegexType regexType, string generatedName, string reason, Diagnostic diagnostic), these are regexes for which we have
- // limited support and need to simply output boilerplate. For now assign an ID, emit the diagnostic, and emit the partial method.
+ // If we have any (RegexMethod regexMethod, string generatedName, string reason, Diagnostic diagnostic), these are regexes for which we have
+ // limited support and need to simply output boilerplate. We need to emit their diagnostics.
+ // If we have any (RegexMethod regexMethod, string generatedName, string runnerFactoryImplementation, Dictionary requiredHelpers),
+ // those are generated implementations to be emitted. We need to gather up their required helpers.
+ Dictionary requiredHelpers = new();
foreach (object? result in results)
{
- if (result is ValueTuple limitedSupportResult)
+ RegexMethod? regexMethod = null;
+ if (result is ValueTuple limitedSupportResult)
{
context.ReportDiagnostic(limitedSupportResult.Item3);
- limitedSupportResult.Item1.GeneratedId = id++;
-
- EmitRegexPartialMethod(limitedSupportResult.Item1, writer, generatedClassName, limitedSupportResult.Item1.GeneratedId);
- writer.WriteLine();
+ regexMethod = limitedSupportResult.Item1;
}
- }
-
- // If we have any (RegexType regexType, string generatedName, string runnerFactoryImplementation, Dictionary requiredHelpers),
- // those are generated implementations to be emitted. For now, assign an ID, and emit the partial method. We also gather up all of the helpers
- // these methods will need emitted.
- Dictionary requiredHelpers = new();
- foreach (object? result in results)
- {
- if (result is ValueTuple> regexImpl)
+ else if (result is ValueTuple> regexImpl)
{
- regexImpl.Item1.GeneratedId = id++;
-
foreach (KeyValuePair helper in regexImpl.Item3)
{
if (!requiredHelpers.ContainsKey(helper.Key))
@@ -167,7 +158,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
}
}
- EmitRegexPartialMethod(regexImpl.Item1, writer, generatedClassName, regexImpl.Item1.GeneratedId);
+ regexMethod = regexImpl.Item1;
+ }
+
+ if (regexMethod is not null)
+ {
+ regexMethod.GeneratedId = id++;
+ EmitRegexPartialMethod(regexMethod, writer, generatedClassName);
writer.WriteLine();
}
}
@@ -207,14 +204,14 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
writer.Indent += 2;
foreach (object? result in results)
{
- if (result is ValueTuple limitedSupportResult)
+ if (result is ValueTuple limitedSupportResult)
{
- EmitRegexLimitedBoilerplate(writer, limitedSupportResult.Item1.Method, limitedSupportResult.Item1.GeneratedId, limitedSupportResult.Item2);
+ EmitRegexLimitedBoilerplate(writer, limitedSupportResult.Item1, limitedSupportResult.Item1.GeneratedId, limitedSupportResult.Item2);
writer.WriteLine();
}
- else if (result is ValueTuple> regexImpl)
+ else if (result is ValueTuple> regexImpl)
{
- EmitRegexDerivedImplementation(writer, regexImpl.Item1.Method, regexImpl.Item1.GeneratedId, regexImpl.Item2);
+ EmitRegexDerivedImplementation(writer, regexImpl.Item1, regexImpl.Item1.GeneratedId, regexImpl.Item2);
writer.WriteLine();
}
}