Skip to content

Commit 05920e1

Browse files
Optimize the Regex source generator's handling of Compilation objects. (#65431)
* Optimize the Regex source generator's handling of Compilation objects. * Use the common downlevel IsExternalInit file in the regex source generator. And remove a now-unused file. * Address PR feedback; revert to the old way of matching symbols. * Fix an outdated comment.
1 parent a1a653c commit 05920e1

5 files changed

Lines changed: 36 additions & 52 deletions

File tree

src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Emitter.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public partial class RegexGenerator
3737
};
3838

3939
/// <summary>Generates the code for one regular expression class.</summary>
40-
private static (string, ImmutableArray<Diagnostic>) EmitRegexType(RegexType regexClass, Compilation compilation)
40+
private static (string, ImmutableArray<Diagnostic>) EmitRegexType(RegexType regexClass, bool allowUnsafe)
4141
{
4242
var sb = new StringBuilder(1024);
4343
var writer = new IndentedTextWriter(new StringWriter(sb));
@@ -78,7 +78,7 @@ private static (string, ImmutableArray<Diagnostic>) EmitRegexType(RegexType rege
7878
generatedName += ComputeStringHash(generatedName).ToString("X");
7979

8080
// Generate the regex type
81-
ImmutableArray<Diagnostic> diagnostics = EmitRegexMethod(writer, regexClass.Method, generatedName, compilation);
81+
ImmutableArray<Diagnostic> diagnostics = EmitRegexMethod(writer, regexClass.Method, generatedName, allowUnsafe);
8282

8383
while (writer.Indent != 0)
8484
{
@@ -145,7 +145,7 @@ static bool ExceedsMaxDepthForSimpleCodeGeneration(RegexNode node, int allowedDe
145145
}
146146

147147
/// <summary>Generates the code for a regular expression method.</summary>
148-
private static ImmutableArray<Diagnostic> EmitRegexMethod(IndentedTextWriter writer, RegexMethod rm, string id, Compilation compilation)
148+
private static ImmutableArray<Diagnostic> EmitRegexMethod(IndentedTextWriter writer, RegexMethod rm, string id, bool allowUnsafe)
149149
{
150150
string patternExpression = Literal(rm.Pattern);
151151
string optionsExpression = Literal(rm.Options);
@@ -170,8 +170,6 @@ private static ImmutableArray<Diagnostic> EmitRegexMethod(IndentedTextWriter wri
170170
return ImmutableArray.Create(Diagnostic.Create(DiagnosticDescriptors.LimitedSourceGeneration, rm.MethodSyntax.GetLocation()));
171171
}
172172

173-
bool allowUnsafe = compilation.Options is CSharpCompilationOptions { AllowUnsafe: true };
174-
175173
writer.WriteLine($"new {id}();");
176174
writer.WriteLine();
177175
writer.WriteLine($" private {id}()");

src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.Parser.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,34 +21,42 @@ public partial class RegexGenerator
2121
private const string RegexName = "System.Text.RegularExpressions.Regex";
2222
private const string RegexGeneratorAttributeName = "System.Text.RegularExpressions.RegexGeneratorAttribute";
2323

24-
private static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
24+
private static bool IsSyntaxTargetForGeneration(SyntaxNode node, CancellationToken cancellationToken) =>
2525
// We don't have a semantic model here, so the best we can do is say whether there are any attributes.
2626
node is MethodDeclarationSyntax { AttributeLists: { Count: > 0 } };
2727

28-
private static MethodDeclarationSyntax? GetSemanticTargetForGeneration(GeneratorSyntaxContext context)
28+
private static bool IsSemanticTargetForGeneration(SemanticModel semanticModel, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken)
2929
{
30-
var methodDeclarationSyntax = (MethodDeclarationSyntax)context.Node;
31-
3230
foreach (AttributeListSyntax attributeListSyntax in methodDeclarationSyntax.AttributeLists)
3331
{
3432
foreach (AttributeSyntax attributeSyntax in attributeListSyntax.Attributes)
3533
{
36-
if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is IMethodSymbol attributeSymbol &&
34+
if (semanticModel.GetSymbolInfo(attributeSyntax, cancellationToken).Symbol is IMethodSymbol attributeSymbol &&
3735
attributeSymbol.ContainingType.ToDisplayString() == RegexGeneratorAttributeName)
3836
{
39-
return methodDeclarationSyntax;
37+
return true;
4038
}
4139
}
4240
}
4341

44-
return null;
42+
return false;
4543
}
4644

4745
// Returns null if nothing to do, Diagnostic if there's an error to report, or RegexType if the type was analyzed successfully.
48-
private static object? GetRegexTypeToEmit(Compilation compilation, MethodDeclarationSyntax methodSyntax, CancellationToken cancellationToken)
46+
private static object? GetSemanticTargetForGeneration(GeneratorSyntaxContext context, CancellationToken cancellationToken)
4947
{
48+
var methodSyntax = (MethodDeclarationSyntax)context.Node;
49+
SemanticModel sm = context.SemanticModel;
50+
51+
if (!IsSemanticTargetForGeneration(sm, methodSyntax, cancellationToken))
52+
{
53+
return null;
54+
}
55+
56+
Compilation compilation = sm.Compilation;
5057
INamedTypeSymbol? regexSymbol = compilation.GetBestTypeByMetadataName(RegexName);
5158
INamedTypeSymbol? regexGeneratorAttributeSymbol = compilation.GetBestTypeByMetadataName(RegexGeneratorAttributeName);
59+
5260
if (regexSymbol is null || regexGeneratorAttributeSymbol is null)
5361
{
5462
// Required types aren't available
@@ -61,8 +69,6 @@ private static bool IsSyntaxTargetForGeneration(SyntaxNode node) =>
6169
return null;
6270
}
6371

64-
SemanticModel sm = compilation.GetSemanticModel(methodSyntax.SyntaxTree);
65-
6672
IMethodSymbol? regexMethodSymbol = sm.GetDeclaredSymbol(methodSyntax, cancellationToken) as IMethodSymbol;
6773
if (regexMethodSymbol is null)
6874
{

src/libraries/System.Text.RegularExpressions/gen/RegexGenerator.cs

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,31 @@ public partial class RegexGenerator : IIncrementalGenerator
2525
{
2626
public void Initialize(IncrementalGeneratorInitializationContext context)
2727
{
28+
// To avoid invalidating the generator's output when anything from the compilation
29+
// changes, we will extract from it the only thing we care about: whether unsafe
30+
// code is allowed.
31+
IncrementalValueProvider<bool> allowUnsafeProvider =
32+
context.CompilationProvider
33+
.Select((x, _) => x.Options is CSharpCompilationOptions { AllowUnsafe: true });
34+
2835
// Contains one entry per regex method, either the generated code for that regex method,
2936
// a diagnostic to fail with, or null if no action should be taken for that regex.
3037
IncrementalValueProvider<ImmutableArray<object?>> codeOrDiagnostics =
3138
context.SyntaxProvider
3239

33-
// Find all MethodDeclarationSyntax nodes attributed with RegexGenerator
34-
.CreateSyntaxProvider(static (s, _) => IsSyntaxTargetForGeneration(s), static (ctx, _) => GetSemanticTargetForGeneration(ctx))
40+
// Find all MethodDeclarationSyntax nodes attributed with RegexGenerator and gather the required information
41+
.CreateSyntaxProvider(IsSyntaxTargetForGeneration, GetSemanticTargetForGeneration)
3542
.Where(static m => m is not null)
3643

37-
// Pair each with the compilation
38-
.Combine(context.CompilationProvider)
39-
40-
// Use a custom comparer that ignores the compilation. We want to avoid regenerating for regex methods
41-
// that haven't been changed, but any change to a regex method will change the Compilation, so we ignore
42-
// the Compilation for purposes of caching.
43-
.WithComparer(new LambdaComparer<(MethodDeclarationSyntax?, Compilation)>(
44-
static (left, right) => EqualityComparer<MethodDeclarationSyntax>.Default.Equals(left.Item1, right.Item1),
45-
static o => o.Item1?.GetHashCode() ?? 0))
44+
// Pair each with whether unsafe code is allowed
45+
.Combine(allowUnsafeProvider)
4646

47-
// Get the resulting code string or error Diagnostic for each MethodDeclarationSyntax/Compilation pair
48-
.Select((state, cancellationToken) =>
47+
// Get the resulting code string or error Diagnostic for
48+
// each MethodDeclarationSyntax/allow-unsafe-blocks pair
49+
.Select((state, _) =>
4950
{
50-
Debug.Assert(state.Item1 is not null);
51-
object? result = GetRegexTypeToEmit(state.Item2, state.Item1, cancellationToken);
52-
return result is RegexType regexType ? EmitRegexType(regexType, state.Item2) : result;
51+
Debug.Assert(state.Left is not null);
52+
return state.Left is RegexType regexType ? EmitRegexType(regexType, state.Right) : state.Left;
5353
})
5454
.Collect();
5555

@@ -83,21 +83,5 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
8383
context.AddSource("RegexGenerator.g.cs", string.Join(Environment.NewLine, code));
8484
});
8585
}
86-
87-
private sealed class LambdaComparer<T> : IEqualityComparer<T>
88-
{
89-
private readonly Func<T?, T?, bool> _equal;
90-
private readonly Func<T?, int> _getHashCode;
91-
92-
public LambdaComparer(Func<T?, T?, bool> equal, Func<T?, int> getHashCode)
93-
{
94-
_equal = equal;
95-
_getHashCode = getHashCode;
96-
}
97-
98-
public bool Equals(T? x, T? y) => _equal(x, y);
99-
100-
public int GetHashCode(T obj) => _getHashCode(obj);
101-
}
10286
}
10387
}

src/libraries/System.Text.RegularExpressions/gen/Stubs.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,3 @@ public RegexReplacement(string rep, RegexNode concat, Hashtable caps) { }
8484
public const int WholeString = -4;
8585
}
8686
}
87-
88-
namespace System.Runtime.CompilerServices
89-
{
90-
internal static class IsExternalInit { }
91-
}

src/libraries/System.Text.RegularExpressions/gen/System.Text.RegularExpressions.Generator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<ItemGroup>
2424
<!-- Common generator support -->
2525
<Compile Include="$(CommonPath)Roslyn\GetBestTypeByMetadataName.cs" Link="Common\Roslyn\GetBestTypeByMetadataName.cs" />
26+
<Compile Include="$(CommonPath)System\Runtime\CompilerServices\IsExternalInit.cs" Link="Common\System\Runtime\CompilerServices\IsExternalInit.cs" />
2627

2728
<!-- Code included from System.Text.RegularExpressions -->
2829
<Compile Include="$(CommonPath)System\HexConverter.cs" Link="Production\HexConverter.cs" />

0 commit comments

Comments
 (0)