Skip to content

Commit 5ae74d7

Browse files
CopilotIeuanWalker
andauthored
Make source generator cache-friendly by replacing Location with CachableLocation wrapper (#43)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: IeuanWalker <6544051+IeuanWalker@users.noreply.github.com> Co-authored-by: Ieuan Walker <walker.ieuan@gmail.com>
1 parent a0b7585 commit 5ae74d7

File tree

26 files changed

+510
-384
lines changed

26 files changed

+510
-384
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using Microsoft.CodeAnalysis;
2+
using Microsoft.CodeAnalysis.Text;
3+
4+
namespace IeuanWalker.MinimalApi.Endpoints.Generator;
5+
6+
/// <summary>
7+
/// A cache-friendly wrapper around Location that extracts and stores only the essential data.
8+
/// This allows the incremental generator to cache diagnostics without holding references to syntax trees.
9+
/// Based on: https://github.com/dotnet/roslyn/issues/62269, implementation from: https://gist.github.com/dferretti/9d41651178a847ccf56dc2c5f9ab788f
10+
/// </summary>
11+
public readonly struct CachableLocation : IEquatable<CachableLocation>
12+
{
13+
readonly TextSpan _sourceSpan;
14+
readonly FileLinePositionSpan _fileLineSpan;
15+
16+
CachableLocation(TextSpan sourceSpan, FileLinePositionSpan fileLineSpan)
17+
{
18+
_sourceSpan = sourceSpan;
19+
_fileLineSpan = fileLineSpan;
20+
}
21+
22+
public static CachableLocation FromLocation(Location location)
23+
{
24+
if (location is null)
25+
{
26+
throw new ArgumentNullException(nameof(location));
27+
}
28+
29+
return new(location.SourceSpan, location.GetLineSpan());
30+
}
31+
32+
public Location ToLocation()
33+
=> Location.Create(_fileLineSpan.Path, _sourceSpan, _fileLineSpan.Span);
34+
35+
public bool Equals(CachableLocation other)
36+
=> _sourceSpan.Equals(other._sourceSpan)
37+
&& _fileLineSpan.Equals(other._fileLineSpan);
38+
39+
public override bool Equals(object obj) => obj is CachableLocation x && this.Equals(x);
40+
41+
public static bool operator ==(CachableLocation left, CachableLocation right)
42+
=> left.Equals(right);
43+
44+
public static bool operator !=(CachableLocation left, CachableLocation right)
45+
=> !left.Equals(right);
46+
47+
public override int GetHashCode() => (37 * _sourceSpan.GetHashCode()) + _fileLineSpan.GetHashCode();
48+
}

src/IeuanWalker.MinimalApi.Endpoints.Generator/DiagnosticInfo.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public DiagnosticInfo(
1414
string messageFormat,
1515
string category,
1616
DiagnosticSeverity severity,
17-
Location location,
17+
CachableLocation location,
1818
params object[] messageArgs)
1919
{
2020
Id = id;
@@ -31,6 +31,6 @@ public DiagnosticInfo(
3131
public string MessageFormat { get; }
3232
public string Category { get; }
3333
public DiagnosticSeverity Severity { get; }
34-
public Location Location { get; }
34+
public CachableLocation Location { get; }
3535
public object[] MessageArgs { get; }
3636
}

src/IeuanWalker.MinimalApi.Endpoints.Generator/EndpointGenerator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
9595
Compilation compilation = semanticModel.Compilation;
9696

9797
List<DiagnosticInfo> diagnostics = [];
98-
Location location = typeDeclaration.GetLocation();
98+
CachableLocation location = CachableLocation.FromLocation(typeDeclaration.GetLocation());
9999
string typeName = typeSymbol.ToDisplayString();
100100

101101
// Check if this is a validator
@@ -215,7 +215,7 @@ static void Execute(ImmutableArray<TypeInfo?> typeInfos, string assemblyName, So
215215
diagnosticInfo.Category,
216216
diagnosticInfo.Severity,
217217
isEnabledByDefault: true);
218-
context.ReportDiagnostic(Diagnostic.Create(descriptor, diagnosticInfo.Location, diagnosticInfo.MessageArgs));
218+
context.ReportDiagnostic(Diagnostic.Create(descriptor, diagnosticInfo.Location.ToLocation(), diagnosticInfo.MessageArgs));
219219
}
220220

221221
// Check for unused groups
@@ -228,7 +228,7 @@ static void Execute(ImmutableArray<TypeInfo?> typeInfos, string assemblyName, So
228228
{
229229
context.ReportDiagnostic(Diagnostic.Create(
230230
unusedGroupDescriptor,
231-
group.Location,
231+
group.Location.ToLocation(),
232232
group.TypeName));
233233
}
234234

@@ -242,7 +242,7 @@ static void Execute(ImmutableArray<TypeInfo?> typeInfos, string assemblyName, So
242242
{
243243
context.ReportDiagnostic(Diagnostic.Create(
244244
duplicateValidatorsDescriptor,
245-
validator.Location,
245+
validator.Location.ToLocation(),
246246
validator.ValidatedTypeName));
247247
}
248248
}
@@ -256,7 +256,7 @@ static void Execute(ImmutableArray<TypeInfo?> typeInfos, string assemblyName, So
256256
{
257257
context.ReportDiagnostic(Diagnostic.Create(
258258
hasValidatorButEndpointDisablesValidation,
259-
validator.Location,
259+
validator.Location.ToLocation(),
260260
endpoint.TypeName,
261261
validator.ValidatedTypeName));
262262
}
@@ -270,7 +270,7 @@ static void Execute(ImmutableArray<TypeInfo?> typeInfos, string assemblyName, So
270270
{
271271
context.ReportDiagnostic(Diagnostic.Create(
272272
usingAbstractValidatorOnRequestType,
273-
abstractValidator.Location,
273+
abstractValidator.Location.ToLocation(),
274274
abstractValidator.TypeName,
275275
abstractValidator.ValidatedTypeName));
276276
}

src/IeuanWalker.MinimalApi.Endpoints.Generator/EndpointInfo.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
using IeuanWalker.MinimalApi.Endpoints.Generator.Helpers;
2-
using Microsoft.CodeAnalysis;
32

43
namespace IeuanWalker.MinimalApi.Endpoints.Generator;
54

65
public class TypeInfo
76
{
8-
public TypeInfo(string typeName, Location location, List<DiagnosticInfo> diagnostics)
7+
public TypeInfo(string typeName, CachableLocation location, List<DiagnosticInfo> diagnostics)
98
{
109
TypeName = typeName;
1110
Location = location;
1211
Diagnostics = diagnostics;
1312
}
1413

1514
public string TypeName { get; }
16-
public Location Location { get; }
15+
public CachableLocation Location { get; }
1716
public List<DiagnosticInfo> Diagnostics { get; }
1817
}
1918

2019
public sealed class ValidatorInfo : TypeInfo
2120
{
22-
public ValidatorInfo(string typeName, string validatedTypeName, Location location, List<DiagnosticInfo> diagnostics)
21+
public ValidatorInfo(string typeName, string validatedTypeName, CachableLocation location, List<DiagnosticInfo> diagnostics)
2322
: base(typeName, location, diagnostics)
2423
{
2524
ValidatedTypeName = validatedTypeName;
@@ -30,7 +29,7 @@ public ValidatorInfo(string typeName, string validatedTypeName, Location locatio
3029

3130
public sealed class AbstractValidatorInfo : TypeInfo
3231
{
33-
public AbstractValidatorInfo(string typeName, string validatedTypeName, Location location, List<DiagnosticInfo> diagnostics)
32+
public AbstractValidatorInfo(string typeName, string validatedTypeName, CachableLocation location, List<DiagnosticInfo> diagnostics)
3433
: base(typeName, location, diagnostics)
3534
{
3635
ValidatedTypeName = validatedTypeName;
@@ -41,7 +40,7 @@ public AbstractValidatorInfo(string typeName, string validatedTypeName, Location
4140

4241
public sealed class EndpointGroupInfo : TypeInfo
4342
{
44-
public EndpointGroupInfo(string typeName, string pattern, string? withName, string? withTags, Location location, List<DiagnosticInfo> diagnostics)
43+
public EndpointGroupInfo(string typeName, string pattern, string? withName, string? withTags, CachableLocation location, List<DiagnosticInfo> diagnostics)
4544
: base(typeName, location, diagnostics)
4645
{
4746
Pattern = pattern;
@@ -67,7 +66,7 @@ public EndpointInfo(
6766
(RequestBindingTypeEnum requestType, string? name)? requestBindingType,
6867
bool disableValidation,
6968
string? responseType,
70-
Location location,
69+
CachableLocation location,
7170
List<DiagnosticInfo> diagnostics)
7271
: base(typeName, location, diagnostics)
7372
{

src/IeuanWalker.MinimalApi.Endpoints.Generator/Helpers/HttpVerbRouteHelpers.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static (HttpVerb verb, string pattern)? GetVerbAndPattern(this TypeDeclar
5050
noHttpVerbDescriptor.MessageFormat.ToString(),
5151
noHttpVerbDescriptor.Category,
5252
noHttpVerbDescriptor.DefaultSeverity,
53-
configureMethod.Identifier.GetLocation(),
53+
CachableLocation.FromLocation(configureMethod.Identifier.GetLocation()),
5454
typeName));
5555
return null;
5656
}
@@ -68,7 +68,7 @@ public static (HttpVerb verb, string pattern)? GetVerbAndPattern(this TypeDeclar
6868
multipleHttpVerbsDescriptor.MessageFormat.ToString(),
6969
multipleHttpVerbsDescriptor.Category,
7070
multipleHttpVerbsDescriptor.DefaultSeverity,
71-
httpVerbCall.GetLocation(),
71+
CachableLocation.FromLocation(httpVerbCall.GetLocation()),
7272
memberAccess.Name.Identifier.ValueText));
7373
}
7474
}

src/IeuanWalker.MinimalApi.Endpoints.Generator/Helpers/MapGroupHelpers.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ static class MapGroupHelpers
6464
multipleGroupCallsDescriptor.MessageFormat.ToString(),
6565
multipleGroupCallsDescriptor.Category,
6666
multipleGroupCallsDescriptor.DefaultSeverity,
67-
groupCall.GetLocation()));
67+
CachableLocation.FromLocation(groupCall.GetLocation())));
6868
}
6969
return null;
7070
}
@@ -135,7 +135,7 @@ memberAccessExpr.Name is GenericNameSyntax genericName &&
135135
noMapGroupDescriptor.MessageFormat.ToString(),
136136
noMapGroupDescriptor.Category,
137137
noMapGroupDescriptor.DefaultSeverity,
138-
configureMethod.Identifier.GetLocation(),
138+
CachableLocation.FromLocation(configureMethod.Identifier.GetLocation()),
139139
endpointGroupSymbol.Name));
140140
return null;
141141
}
@@ -151,7 +151,7 @@ memberAccessExpr.Name is GenericNameSyntax genericName &&
151151
multipleMapGroupsDescriptor.MessageFormat.ToString(),
152152
multipleMapGroupsDescriptor.Category,
153153
multipleMapGroupsDescriptor.DefaultSeverity,
154-
mapGroupCall.GetLocation()));
154+
CachableLocation.FromLocation(mapGroupCall.GetLocation())));
155155
}
156156
return null;
157157
}

src/IeuanWalker.MinimalApi.Endpoints.Generator/Helpers/RequestBindingTypeHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static (RequestBindingTypeEnum requestType, string? name)? GetRequestType
4444
multipleRequestTypeMethodsDescriptor.MessageFormat.ToString(),
4545
multipleRequestTypeMethodsDescriptor.Category,
4646
multipleRequestTypeMethodsDescriptor.DefaultSeverity,
47-
requestTypeCall.GetLocation(),
47+
CachableLocation.FromLocation(requestTypeCall.GetLocation()),
4848
memberAccess.Name.Identifier.ValueText));
4949
}
5050
}

0 commit comments

Comments
 (0)