Skip to content

Commit 2196cfd

Browse files
authored
Update RDG to use interceptors feature (#48817)
* Update RDG to use interceptors feature * Address feedback from review * Add separator to method name and update same interceptor test * Use group index to distinguish interceptor methods
1 parent 6dc4016 commit 2196cfd

File tree

56 files changed

+8196
-8123
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+8196
-8123
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@
245245
<Analyzer_MicrosoftCodeAnalysisCSharpWorkspacesVersion>3.3.1</Analyzer_MicrosoftCodeAnalysisCSharpWorkspacesVersion>
246246
<!-- Pin the version of the M.CA dependencies that we utilize with a cutom version property $(MicrosoftCodeAnalysisVersion_LatestVS) to avoid automatically
247247
consuming the newest version of the packages when using the $(MicrosoftCodeAnalysisCSharpVersion) properties in source-build. -->
248-
<MicrosoftCodeAnalysisVersion_LatestVS>4.5.0</MicrosoftCodeAnalysisVersion_LatestVS>
248+
<MicrosoftCodeAnalysisVersion_LatestVS>4.7.0-3.23314.3</MicrosoftCodeAnalysisVersion_LatestVS>
249249
<MicrosoftCodeAnalysisExternalAccessAspNetCoreVersion>4.7.0-3.23314.3</MicrosoftCodeAnalysisExternalAccessAspNetCoreVersion>
250250
<MicrosoftCodeAnalysisCommonVersion>4.7.0-3.23314.3</MicrosoftCodeAnalysisCommonVersion>
251251
<MicrosoftCodeAnalysisCSharpVersion>4.7.0-3.23314.3</MicrosoftCodeAnalysisCSharpVersion>

src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<Compile Include="$(SharedSourceRoot)RoslynUtils\SyntaxTreeExtensions.cs" LinkBase="Shared" />
3131
<Compile Include="$(SharedSourceRoot)RoslynUtils\ParsabilityHelper.cs" LinkBase="Shared" />
3232
<Compile Include="$(SharedSourceRoot)RoslynUtils\CodeWriter.cs" LinkBase="Shared" />
33+
<Compile Include="$(SharedSourceRoot)RoslynUtils\IncrementalValuesProviderExtensions.cs" LinkBase="Shared" />
3334
<Compile Include="$(SharedSourceRoot)Diagnostics\AnalyzerDebug.cs" LinkBase="Shared" />
3435
</ItemGroup>
3536

src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs

Lines changed: 103 additions & 128 deletions
Large diffs are not rendered by default.

src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs

Lines changed: 30 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System;
45
using System.Collections.Immutable;
56
using System.Linq;
67
using System.Text;
@@ -21,7 +22,8 @@ internal static class RequestDelegateGeneratorSources
2122
#nullable enable
2223
""";
2324

24-
public static string GeneratedCodeAttribute => $@"[System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(RequestDelegateGeneratorSources).Assembly.FullName}"", ""{typeof(RequestDelegateGeneratorSources).Assembly.GetName().Version}"")]";
25+
public static string GeneratedCodeConstructor => $@"System.CodeDom.Compiler.GeneratedCodeAttribute(""{typeof(RequestDelegateGeneratorSources).Assembly.FullName}"", ""{typeof(RequestDelegateGeneratorSources).Assembly.GetName().Version}"")";
26+
public static string GeneratedCodeAttribute => $"[{GeneratedCodeConstructor}]";
2527

2628
public static string ContentTypeConstantsType => $$"""
2729
{{GeneratedCodeAttribute}}
@@ -439,25 +441,19 @@ public override bool IsDefined(Type attributeType, bool inherit)
439441
}
440442
""";
441443

442-
public static string GetGeneratedRouteBuilderExtensionsSource(string genericThunks, string thunks, string endpoints, string helperMethods, string helperTypes, ImmutableHashSet<string> verbs) => $$"""
444+
public static string GetGeneratedRouteBuilderExtensionsSource(string endpoints, string helperMethods, string helperTypes, ImmutableHashSet<string> verbs) => $$"""
443445
{{SourceHeader}}
444446
445-
namespace Microsoft.AspNetCore.Builder
447+
namespace System.Runtime.CompilerServices
446448
{
447449
{{GeneratedCodeAttribute}}
448-
internal sealed class SourceKey
450+
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
451+
file sealed class InterceptsLocationAttribute : Attribute
449452
{
450-
public string Path { get; init; }
451-
public int Line { get; init; }
452-
453-
public SourceKey(string path, int line)
453+
public InterceptsLocationAttribute(string filePath, int line, int column)
454454
{
455-
Path = path;
456-
Line = line;
457455
}
458456
}
459-
460-
{{GetEndpoints(endpoints, verbs)}}
461457
}
462458
463459
namespace Microsoft.AspNetCore.Http.Generated
@@ -471,6 +467,7 @@ namespace Microsoft.AspNetCore.Http.Generated
471467
using System.Globalization;
472468
using System.Linq;
473469
using System.Reflection;
470+
using System.Runtime.CompilerServices;
474471
using System.Text.Json;
475472
using System.Text.Json.Serialization.Metadata;
476473
using System.Threading.Tasks;
@@ -493,8 +490,24 @@ namespace Microsoft.AspNetCore.Http.Generated
493490
{{GeneratedCodeAttribute}}
494491
file static class GeneratedRouteBuilderExtensionsCore
495492
{
496-
{{GetGenericThunks(genericThunks)}}
497-
{{GetThunks(thunks)}}
493+
{{GetVerbs(verbs)}}
494+
{{endpoints}}
495+
496+
internal static RouteHandlerBuilder MapCore(
497+
this IEndpointRouteBuilder routes,
498+
string pattern,
499+
Delegate handler,
500+
IEnumerable<string>? httpMethods,
501+
MetadataPopulator populateMetadata,
502+
RequestDelegateFactoryFunc createRequestDelegate)
503+
{
504+
return RouteHandlerServices.Map(routes, pattern, handler, httpMethods, populateMetadata, createRequestDelegate);
505+
}
506+
507+
private static T Cast<T>(Delegate d, T _) where T : Delegate
508+
{
509+
return (T)d;
510+
}
498511
499512
private static EndpointFilterDelegate BuildFilterDelegate(EndpointFilterDelegate filteredInvocation, EndpointBuilder builder, MethodInfo mi)
500513
{
@@ -557,84 +570,16 @@ private static bool ShouldUseWith(this JsonTypeInfo jsonTypeInfo, [NotNullWhen(f
557570
{{LogOrThrowExceptionHelperClass}}
558571
}
559572
""";
560-
private static string GetGenericThunks(string genericThunks) => genericThunks != string.Empty ? $$"""
561-
private static class GenericThunks<T>
562-
{
563-
public static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
564-
{
565-
{{genericThunks}}
566-
};
567-
}
568573

569-
internal static RouteHandlerBuilder MapCore<T>(
570-
this IEndpointRouteBuilder routes,
571-
string pattern,
572-
Delegate handler,
573-
IEnumerable<string> httpMethods,
574-
string filePath,
575-
int lineNumber)
576-
{
577-
var (populateMetadata, createRequestDelegate) = GenericThunks<T>.map[(filePath, lineNumber)];
578-
return RouteHandlerServices.Map(routes, pattern, handler, httpMethods, populateMetadata, createRequestDelegate);
579-
}
580-
""" : string.Empty;
581-
582-
private static string GetThunks(string thunks) => thunks != string.Empty ? $$"""
583-
private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
584-
{
585-
{{thunks}}
586-
};
587-
588-
internal static RouteHandlerBuilder MapCore(
589-
this IEndpointRouteBuilder routes,
590-
string pattern,
591-
Delegate handler,
592-
IEnumerable<string>? httpMethods,
593-
string filePath,
594-
int lineNumber)
595-
{
596-
var (populateMetadata, createRequestDelegate) = map[(filePath, lineNumber)];
597-
return RouteHandlerServices.Map(routes, pattern, handler, httpMethods, populateMetadata, createRequestDelegate);
598-
}
599-
""" : string.Empty;
600-
601-
private static string GetEndpoints(string endpoints, ImmutableHashSet<string> verbs)
574+
public static string GetVerbs(ImmutableHashSet<string> verbs)
602575
{
603-
if (endpoints == string.Empty)
604-
{
605-
return string.Empty;
606-
}
607-
608576
var builder = new StringBuilder();
609-
builder.Append($$"""
610-
// This class needs to be internal so that the compiled application
611-
// has access to the strongly-typed endpoint definitions that are
612-
// generated by the compiler so that they will be favored by
613-
// overload resolution and opt the runtime in to the code generated
614-
// implementation produced here.
615-
{{GeneratedCodeAttribute}}
616-
internal static class GenerateRouteBuilderEndpoints
617-
{
618-
619-
""");
620577

621-
foreach (string verb in verbs.OrderBy(p => p, System.StringComparer.Ordinal))
578+
foreach (string verb in verbs.OrderBy(p => p, StringComparer.Ordinal))
622579
{
623-
builder.AppendLine($$"""
624-
private static readonly string[] {{verb}}Verb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.{{verb}} };
625-
""");
580+
builder.AppendLine($$"""private static readonly string[] {{verb}}Verb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.{{verb}} };""");
626581
}
627582

628-
if (verbs.Count > 0)
629-
{
630-
builder.AppendLine();
631-
}
632-
633-
builder.Append($$"""
634-
{{endpoints}}
635-
}
636-
""");
637-
638583
return builder.ToString();
639584
}
640585
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Immutable;
5+
using System.Linq;
6+
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
7+
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
8+
using Microsoft.CodeAnalysis;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
using Microsoft.CodeAnalysis.Diagnostics;
11+
using Microsoft.CodeAnalysis.Operations;
12+
13+
/*
14+
* This class contains the logic for suppressing diagnostics that are
15+
* emitted by the linker analyzers when encountering the framework-provided
16+
* `Map` invocations. Pending the completion of https://github.com/dotnet/roslyn/issues/68669,
17+
* this workaround is necessary to apply these suppressions for `Map` invocations that the RDG
18+
* is able to generate code at compile time for that the analyzer is not able to resolve.
19+
*/
20+
21+
namespace Microsoft.AspNetCore.Http.RequestDelegateGenerator;
22+
23+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
24+
public sealed class RequestDelegateGeneratorSuppressor : DiagnosticSuppressor
25+
{
26+
private static readonly SuppressionDescriptor SuppressRUCDiagnostic = new(
27+
id: "RDGS001",
28+
suppressedDiagnosticId: "IL2026",
29+
justification: "The target method has been intercepted by a statically generated variant.");
30+
31+
private static readonly SuppressionDescriptor SuppressRDCDiagnostic = new(
32+
id: "RDGS002",
33+
suppressedDiagnosticId: "IL3050",
34+
justification: "The target method has been intercepted by a statically generated variant.");
35+
36+
public override void ReportSuppressions(SuppressionAnalysisContext context)
37+
{
38+
foreach (var diagnostic in context.ReportedDiagnostics)
39+
{
40+
if (diagnostic.Id != SuppressRDCDiagnostic.SuppressedDiagnosticId && diagnostic.Id != SuppressRUCDiagnostic.SuppressedDiagnosticId)
41+
{
42+
continue;
43+
}
44+
45+
var location = diagnostic.AdditionalLocations.Count > 0
46+
? diagnostic.AdditionalLocations[0]
47+
: diagnostic.Location;
48+
49+
if (location.SourceTree is not { } sourceTree
50+
|| sourceTree.GetRoot().FindNode(location.SourceSpan) is not InvocationExpressionSyntax node
51+
|| !node.TryGetMapMethodName(out var method)
52+
|| !InvocationOperationExtensions.KnownMethods.Contains(method))
53+
{
54+
continue;
55+
}
56+
57+
var semanticModel = context.GetSemanticModel(sourceTree);
58+
var operation = semanticModel.GetOperation(node, context.CancellationToken);
59+
var wellKnownTypes = WellKnownTypes.GetOrCreate(semanticModel.Compilation);
60+
if (operation.IsValidOperation(wellKnownTypes, out var invocationOperation))
61+
{
62+
var endpoint = new Endpoint(invocationOperation, wellKnownTypes, semanticModel);
63+
if (endpoint.Diagnostics.Count == 0)
64+
{
65+
var targetSuppression = diagnostic.Id == SuppressRUCDiagnostic.SuppressedDiagnosticId
66+
? SuppressRUCDiagnostic
67+
: SuppressRDCDiagnostic;
68+
context.ReportSuppression(Suppression.Create(targetSuppression, diagnostic));
69+
}
70+
}
71+
}
72+
}
73+
public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => ImmutableArray.Create(SuppressRUCDiagnostic, SuppressRDCDiagnostic);
74+
}

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes, S
9595
public EndpointParameter[] Parameters { get; } = Array.Empty<EndpointParameter>();
9696
public List<Diagnostic> Diagnostics { get; } = new List<Diagnostic>();
9797

98-
public (string File, int LineNumber) Location { get; }
98+
public (string File, int LineNumber, int CharacterNumber) Location { get; }
9999
public IInvocationOperation Operation { get; }
100100

101101
public override bool Equals(object o) =>
@@ -138,13 +138,16 @@ public static int GetSignatureHashCode(Endpoint endpoint)
138138
return hashCode.ToHashCode();
139139
}
140140

141-
private static (string, int) GetLocation(IInvocationOperation operation)
141+
private static (string, int, int) GetLocation(IInvocationOperation operation)
142142
{
143143
var operationSpan = operation.Syntax.Span;
144-
var filePath = operation.Syntax.SyntaxTree.GetDisplayPath(operationSpan, operation.SemanticModel?.Compilation.Options.SourceReferenceResolver);
144+
var filePath = operation.Syntax.SyntaxTree.GetInterceptorFilePath(operation.SemanticModel?.Compilation.Options.SourceReferenceResolver);
145145
var span = operation.Syntax.SyntaxTree.GetLineSpan(operationSpan);
146146
var lineNumber = span.StartLinePosition.Line + 1;
147-
return (filePath, lineNumber);
147+
// Calculate the character offset to the end of the Map invocation detected
148+
var invocationLength = ((MemberAccessExpressionSyntax)((InvocationExpressionSyntax)operation.Syntax).Expression).Expression.Span.Length;
149+
var characterNumber = span.StartLinePosition.Character + invocationLength + 2;
150+
return (filePath, lineNumber, characterNumber);
148151
}
149152

150153
private static string GetHttpMethod(IInvocationOperation operation)

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ public EndpointParameter(Endpoint endpoint, IParameterSymbol parameter, WellKnow
2323
{
2424
Ordinal = parameter.Ordinal;
2525
IsOptional = parameter.IsOptional();
26+
HasDefaultValue = parameter.HasExplicitDefaultValue;
2627
DefaultValue = parameter.GetDefaultValueString();
2728
ProcessEndpointParameterSource(endpoint, parameter, parameter.GetAttributes(), wellKnownTypes);
2829
}
@@ -32,6 +33,7 @@ private EndpointParameter(Endpoint endpoint, IPropertySymbol property, IParamete
3233
Ordinal = parameter?.Ordinal ?? 0;
3334
IsProperty = true;
3435
IsOptional = property.IsOptional() || parameter?.IsOptional() == true;
36+
HasDefaultValue = parameter?.HasExplicitDefaultValue ?? false;
3537
DefaultValue = parameter?.GetDefaultValueString() ?? "null";
3638
// Coalesce attributes on the property and attributes on the matching parameter
3739
var attributeBuilder = ImmutableArray.CreateBuilder<AttributeData>();
@@ -251,6 +253,7 @@ private static bool ImplementsIEndpointParameterMetadataProvider(ITypeSymbol typ
251253
public bool IsOptional { get; set; }
252254
public bool IsArray { get; set; }
253255
public string DefaultValue { get; set; } = "null";
256+
public bool HasDefaultValue { get; set; }
254257
[MemberNotNullWhen(true, nameof(PropertyAsParameterInfoConstruction))]
255258
public bool IsProperty { get; set; }
256259
public EndpointParameterSource Source { get; set; }
@@ -610,17 +613,17 @@ obj is EndpointParameter other &&
610613
other.SymbolName == SymbolName &&
611614
other.Ordinal == Ordinal &&
612615
other.IsOptional == IsOptional &&
613-
SymbolEqualityComparer.Default.Equals(other.Type, Type);
616+
SymbolEqualityComparer.IncludeNullability.Equals(other.Type, Type);
614617

615618
public bool SignatureEquals(object obj) =>
616619
obj is EndpointParameter other &&
617-
SymbolEqualityComparer.Default.Equals(other.Type, Type);
620+
SymbolEqualityComparer.IncludeNullability.Equals(other.Type, Type);
618621

619622
public override int GetHashCode()
620623
{
621624
var hashCode = new HashCode();
622625
hashCode.Add(SymbolName);
623-
hashCode.Add(Type, SymbolEqualityComparer.Default);
626+
hashCode.Add(Type, SymbolEqualityComparer.IncludeNullability);
624627
return hashCode.ToHashCode();
625628
}
626629
}

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/InvocationOperationExtensions.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Diagnostics.CodeAnalysis;
5+
using Microsoft.AspNetCore.App.Analyzers.Infrastructure;
56
using Microsoft.CodeAnalysis;
67
using Microsoft.CodeAnalysis.CSharp.Syntax;
78
using Microsoft.CodeAnalysis.Operations;
@@ -22,6 +23,20 @@ internal static class InvocationOperationExtensions
2223
"MapFallback"
2324
};
2425

26+
public static bool IsValidOperation(this IOperation? operation, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out IInvocationOperation? invocationOperation)
27+
{
28+
invocationOperation = null;
29+
if (operation is IInvocationOperation targetOperation &&
30+
targetOperation.TryGetRouteHandlerArgument(out var routeHandlerParameter) &&
31+
routeHandlerParameter is { Parameter.Type: {} delegateType } &&
32+
SymbolEqualityComparer.Default.Equals(delegateType, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Delegate)))
33+
{
34+
invocationOperation = targetOperation;
35+
return true;
36+
}
37+
return false;
38+
}
39+
2540
public static bool TryGetRouteHandlerMethod(this IInvocationOperation invocation, SemanticModel semanticModel, [NotNullWhen(true)] out IMethodSymbol? method)
2641
{
2742
method = null;

0 commit comments

Comments
 (0)