Skip to content

Commit 42e715d

Browse files
authored
Fixed RDG handling for nullable scenarios (#54335)
1 parent e72eca7 commit 42e715d

File tree

4 files changed

+30
-12
lines changed

4 files changed

+30
-12
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ public override int GetHashCode() =>
108108

109109
public static bool SignatureEquals(Endpoint a, Endpoint b)
110110
{
111-
if (!string.Equals(a.Response?.WrappedResponseType, b.Response?.WrappedResponseType, StringComparison.Ordinal) ||
111+
if (!string.Equals(a.Response?.WrappedResponseTypeDisplayName, b.Response?.WrappedResponseTypeDisplayName, StringComparison.Ordinal) ||
112112
!a.HttpMethod.Equals(b.HttpMethod, StringComparison.Ordinal) ||
113113
a.Parameters.Length != b.Parameters.Length)
114114
{
@@ -129,7 +129,7 @@ public static bool SignatureEquals(Endpoint a, Endpoint b)
129129
public static int GetSignatureHashCode(Endpoint endpoint)
130130
{
131131
var hashCode = new HashCode();
132-
hashCode.Add(endpoint.Response?.WrappedResponseType);
132+
hashCode.Add(endpoint.Response?.WrappedResponseTypeDisplayName);
133133
hashCode.Add(endpoint.HttpMethod);
134134

135135
foreach (var parameter in endpoint.Parameters)

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ namespace Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerM
1414
internal class EndpointResponse
1515
{
1616
public ITypeSymbol? ResponseType { get; set; }
17-
public string WrappedResponseType { get; set; }
17+
public ITypeSymbol WrappedResponseType { get; set; }
18+
public string WrappedResponseTypeDisplayName { get; set; }
1819
public string? ContentType { get; set; }
1920
public bool IsAwaitable { get; set; }
2021
public bool HasNoResponse { get; set; }
@@ -27,7 +28,8 @@ internal EndpointResponse(IMethodSymbol method, WellKnownTypes wellKnownTypes)
2728
{
2829
WellKnownTypes = wellKnownTypes;
2930
ResponseType = UnwrapResponseType(method, out bool isAwaitable, out bool awaitableIsVoid);
30-
WrappedResponseType = method.ReturnType.ToDisplayString(EmitterConstants.DisplayFormat);
31+
WrappedResponseType = method.ReturnType;
32+
WrappedResponseTypeDisplayName = method.ReturnType.ToDisplayString(EmitterConstants.DisplayFormat);
3133
IsAwaitable = isAwaitable;
3234
HasNoResponse = method.ReturnsVoid || awaitableIsVoid;
3335
IsIResult = GetIsIResult();
@@ -100,13 +102,14 @@ public override bool Equals(object obj)
100102
{
101103
return obj is EndpointResponse otherEndpointResponse &&
102104
SymbolEqualityComparer.Default.Equals(otherEndpointResponse.ResponseType, ResponseType) &&
103-
otherEndpointResponse.WrappedResponseType.Equals(WrappedResponseType, StringComparison.Ordinal) &&
105+
SymbolEqualityComparer.Default.Equals(otherEndpointResponse.WrappedResponseType, WrappedResponseType) &&
106+
otherEndpointResponse.WrappedResponseTypeDisplayName.Equals(WrappedResponseTypeDisplayName, StringComparison.Ordinal) &&
104107
otherEndpointResponse.IsAwaitable == IsAwaitable &&
105108
otherEndpointResponse.HasNoResponse == HasNoResponse &&
106109
otherEndpointResponse.IsIResult == IsIResult &&
107110
string.Equals(otherEndpointResponse.ContentType, ContentType, StringComparison.OrdinalIgnoreCase);
108111
}
109112

110113
public override int GetHashCode() =>
111-
HashCode.Combine(SymbolEqualityComparer.Default.GetHashCode(ResponseType), WrappedResponseType, IsAwaitable, HasNoResponse, IsIResult, ContentType);
114+
HashCode.Combine(SymbolEqualityComparer.Default.GetHashCode(ResponseType), SymbolEqualityComparer.Default.GetHashCode(WrappedResponseType), WrappedResponseTypeDisplayName, IsAwaitable, HasNoResponse, IsIResult, ContentType);
112115
}

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@ public static string EmitHandlerDelegateType(this Endpoint endpoint)
2222
// IResult (int arg0, Todo arg1) => throw null!
2323
if (endpoint.Parameters.Length == 0)
2424
{
25-
return endpoint.Response == null || (endpoint.Response.HasNoResponse && !endpoint.Response.IsAwaitable) ? "void ()" : $"{endpoint.Response.WrappedResponseType} ()";
25+
return endpoint.Response == null || (endpoint.Response.HasNoResponse && !endpoint.Response.IsAwaitable) ? "void ()" : $"{endpoint.Response.WrappedResponseTypeDisplayName} ()";
2626
}
2727
var parameterTypeList = string.Join(", ", endpoint.Parameters.Select((p, i) => $"{EmitUnwrappedParameterType(p)} arg{i}{(p.HasDefaultValue ? $"= {p.DefaultValue}" : string.Empty)}"));
2828

2929
if (endpoint.Response == null || (endpoint.Response.HasNoResponse && !endpoint.Response.IsAwaitable))
3030
{
3131
return $"void ({parameterTypeList})";
3232
}
33-
return $"{endpoint.Response.WrappedResponseType} ({parameterTypeList})";
33+
return $"{endpoint.Response.WrappedResponseTypeDisplayName} ({parameterTypeList})";
3434
}
3535

3636
private static string EmitUnwrappedParameterType(EndpointParameter p)
@@ -88,7 +88,7 @@ public static void EmitRequestHandler(this Endpoint endpoint, CodeWriter codeWri
8888
{
8989
codeWriter.WriteLine($"var task = handler({endpoint.EmitArgumentList()});");
9090
}
91-
if (endpoint.Response.IsAwaitable && endpoint.Response.ResponseType?.NullableAnnotation == NullableAnnotation.Annotated)
91+
if (endpoint.Response.IsAwaitable && endpoint.Response.WrappedResponseType.NullableAnnotation == NullableAnnotation.Annotated)
9292
{
9393
codeWriter.WriteLine("if (task == null)");
9494
codeWriter.StartBlock();
@@ -380,7 +380,7 @@ public static void EmitFilteredInvocation(this Endpoint endpoint, CodeWriter cod
380380
else if (endpoint.Response?.IsAwaitable == true)
381381
{
382382
codeWriter.WriteLine($"var task = handler({endpoint.EmitFilteredArgumentList()});");
383-
if (endpoint.Response?.ResponseType?.NullableAnnotation == NullableAnnotation.Annotated)
383+
if (endpoint.Response?.WrappedResponseType.NullableAnnotation == NullableAnnotation.Annotated)
384384
{
385385
codeWriter.WriteLine("if (task == null)");
386386
codeWriter.StartBlock();

src/Http/Http.Extensions/test/RequestDelegateGenerator/CompileTimeCreationTests.cs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ public async Task MapAction_NoJsonTypeInfoResolver_ThrowsException()
427427
Assert.Equal("JsonSerializerOptions instance must specify a TypeInfoResolver setting before being marked as read-only.", exception.Message);
428428
}
429429

430-
public static IEnumerable<object[]> NullResult
430+
public static IEnumerable<object[]> NullResultWithNullAnnotation
431431
{
432432
get
433433
{
@@ -443,7 +443,7 @@ public static IEnumerable<object[]> NullResult
443443
}
444444

445445
[Theory]
446-
[MemberData(nameof(NullResult))]
446+
[MemberData(nameof(NullResultWithNullAnnotation))]
447447
public async Task RequestDelegateThrowsInvalidOperationExceptionOnNullDelegate(string innerSource, string message)
448448
{
449449
var source = $"""
@@ -458,6 +458,21 @@ public async Task RequestDelegateThrowsInvalidOperationExceptionOnNullDelegate(s
458458
Assert.Equal(message, exception.Message);
459459
}
460460

461+
[Theory]
462+
[InlineData("Task<bool> () => null!")]
463+
[InlineData("Task<bool?> () => null!")]
464+
public async Task AwaitableRequestDelegateThrowsNullReferenceExceptionOnUnannotatedNullDelegate(string innerSource)
465+
{
466+
var source = $"""
467+
app.MapGet("/", {innerSource});
468+
""";
469+
var (_, compilation) = await RunGeneratorAsync(source);
470+
var endpoint = GetEndpointFromCompilation(compilation);
471+
472+
var httpContext = CreateHttpContext();
473+
_ = await Assert.ThrowsAsync<NullReferenceException>(async () => await endpoint.RequestDelegate(httpContext));
474+
}
475+
461476
[Theory]
462477
[InlineData("")]
463478
[InlineData("[FromRoute]")]

0 commit comments

Comments
 (0)