Browse Source

Enable nullability in RDG and fix warnings (#47144)

* Enable nullability in RDG and fix warnings

* Fix nullability for symbol resolution and semantic model

* Fix nullability warnings on build

* Only process handlers that take a Delegate

* Fix nullability warnings redux

* Add doc string on WarnOnNullable property
Safia Abdalla 3 years ago
parent
commit
9b3e79d7eb

+ 2 - 1
eng/targets/CSharp.Common.targets

@@ -122,7 +122,8 @@
         (('$(TargetFrameworkIdentifier)' == '.NETStandard' AND $([MSBuild]::VersionLessThanOrEquals('$(TargetFrameworkVersion)', '2.1'))) OR '$(TargetFrameworkIdentifier)' == '.NETFramework')">
       <PropertyGroup>
         <DefineConstants>$(DefineConstants),INTERNAL_NULLABLE_ATTRIBUTES</DefineConstants>
-        <NoWarn>$(NoWarn);nullable</NoWarn>
+        <!-- Repo-specific property to enable nullability warnings for ns2.0 -->
+        <NoWarn Condition=" '$(WarnOnNullable)' != 'true' ">$(NoWarn);nullable</NoWarn>
       </PropertyGroup>
       <ItemGroup>
         <Compile Include="$(SharedSourceRoot)Nullable\NullableAttributes.cs" />

+ 15 - 7
src/Http/Http.Extensions/gen/DiagnosticDescriptors.cs

@@ -27,11 +27,19 @@ internal static class DiagnosticDescriptors
 
     // This is temporary. The plan is to be able to resolve all parameters to a known EndpointParameterSource.
     // For now, we emit a warning for the unsupported set.
-    public static DiagnosticDescriptor UnableToResolveParameterDescriptor { get;  } = new(
-            "RDG003",
-            new LocalizableResourceString(nameof(Resources.UnableToResolveParameter_Title), Resources.ResourceManager, typeof(Resources)),
-            new LocalizableResourceString(nameof(Resources.UnableToResolveParameter_Message), Resources.ResourceManager, typeof(Resources)),
-            "Usage",
-            DiagnosticSeverity.Warning,
-            isEnabledByDefault: true);
+    public static DiagnosticDescriptor UnableToResolveParameterDescriptor { get; } = new(
+        "RDG003",
+        new LocalizableResourceString(nameof(Resources.UnableToResolveParameter_Title), Resources.ResourceManager, typeof(Resources)),
+        new LocalizableResourceString(nameof(Resources.UnableToResolveParameter_Message), Resources.ResourceManager, typeof(Resources)),
+        "Usage",
+        DiagnosticSeverity.Warning,
+        isEnabledByDefault: true);
+
+    public static DiagnosticDescriptor UnableToResolveAnonymousReturnType { get; } = new(
+        "RDG004",
+        new LocalizableResourceString(nameof(Resources.UnableToResolveAnonymousReturnType_Title), Resources.ResourceManager, typeof(Resources)),
+        new LocalizableResourceString(nameof(Resources.UnableToResolveAnonymousReturnType_Message), Resources.ResourceManager, typeof(Resources)),
+        "Usage",
+        DiagnosticSeverity.Warning,
+        isEnabledByDefault: true);
 }

+ 2 - 0
src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj

@@ -6,6 +6,8 @@
     <IsPackable>false</IsPackable>
     <IsAnalyzersProject>true</IsAnalyzersProject>
     <AddPublicApiAnalyzers>false</AddPublicApiAnalyzers>
+    <Nullable>enable</Nullable>
+    <WarnOnNullable>true</WarnOnNullable>
   </PropertyGroup>
 
   <ItemGroup>

+ 32 - 31
src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs

@@ -30,7 +30,7 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
     public void Initialize(IncrementalGeneratorInitializationContext context)
     {
         var endpointsWithDiagnostics = context.SyntaxProvider.CreateSyntaxProvider(
-            predicate: (node, _) => node is InvocationExpressionSyntax
+            predicate: static (node, _) => node is InvocationExpressionSyntax
             {
                 Expression: MemberAccessExpressionSyntax
                 {
@@ -41,25 +41,31 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
                 },
                 ArgumentList: { Arguments: { Count: 2 } args }
             } && _knownMethods.Contains(method),
-            transform: (context, token) =>
+            transform: static (context, token) =>
             {
-                var operation = context.SemanticModel.GetOperation(context.Node, token) as IInvocationOperation;
+                var operation = context.SemanticModel.GetOperation(context.Node, token);
                 var wellKnownTypes = WellKnownTypes.GetOrCreate(context.SemanticModel.Compilation);
-                return new Endpoint(operation, wellKnownTypes);
+                if (operation is IInvocationOperation { Arguments: { Length: 3 } parameters } invocationOperation &&
+                    invocationOperation.GetRouteHandlerArgument() is { Parameter.Type: {} delegateType } &&
+                    SymbolEqualityComparer.Default.Equals(delegateType, wellKnownTypes.Get(WellKnownTypeData.WellKnownType.System_Delegate)))
+                {
+                    return new Endpoint(invocationOperation, wellKnownTypes, context.SemanticModel);
+                }
+                return null;
             })
+            .Where(static endpoint => endpoint != null)
             .WithTrackingName(GeneratorSteps.EndpointModelStep);
 
         context.RegisterSourceOutput(endpointsWithDiagnostics, (context, endpoint) =>
         {
-            var (filePath, _) = endpoint.Location;
-            foreach (var diagnostic in endpoint.Diagnostics)
+            foreach (var diagnostic in endpoint!.Diagnostics)
             {
                 context.ReportDiagnostic(diagnostic);
             }
         });
 
         var endpoints = endpointsWithDiagnostics
-            .Where(endpoint => endpoint.Diagnostics.Count == 0)
+            .Where(endpoint => endpoint!.Diagnostics.Count == 0)
             .WithTrackingName(GeneratorSteps.EndpointsWithoutDiagnosicsStep);
 
         var thunks = endpoints.Select((endpoint, _) =>
@@ -67,21 +73,21 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
             using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
             using var codeWriter = new CodeWriter(stringWriter, baseIndent: 3);
             codeWriter.InitializeIndent();
-            codeWriter.WriteLine($"[{endpoint.EmitSourceKey()}] = (");
+            codeWriter.WriteLine($"[{endpoint!.EmitSourceKey()}] = (");
             codeWriter.Indent++;
             codeWriter.WriteLine("(methodInfo, options) =>");
             codeWriter.StartBlock();
             codeWriter.WriteLine(@"Debug.Assert(options?.EndpointBuilder != null, ""EndpointBuilder not found."");");
-            codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new SourceKey{endpoint.EmitSourceKey()});");
+            codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new SourceKey{endpoint!.EmitSourceKey()});");
             codeWriter.WriteLine("return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };");
             codeWriter.EndBlockWithComma();
             codeWriter.WriteLine("(del, options, inferredMetadataResult) =>");
             codeWriter.StartBlock();
-            codeWriter.WriteLine($"var handler = ({endpoint.EmitHandlerDelegateCast()})del;");
+            codeWriter.WriteLine($"var handler = ({endpoint!.EmitHandlerDelegateCast()})del;");
             codeWriter.WriteLine("EndpointFilterDelegate? filteredInvocation = null;");
-            endpoint.EmitRouteOrQueryResolver(codeWriter);
-            endpoint.EmitJsonBodyOrServicePreparation(codeWriter);
-            endpoint.EmitJsonPreparation(codeWriter);
+            endpoint!.EmitRouteOrQueryResolver(codeWriter);
+            endpoint!.EmitJsonBodyOrServicePreparation(codeWriter);
+            endpoint!.Response?.EmitJsonPreparation(codeWriter);
             if (endpoint.NeedsParameterArray)
             {
                 codeWriter.WriteLine("var parameters = del.Method.GetParameters();");
@@ -89,14 +95,9 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
             codeWriter.WriteLineNoTabs(string.Empty);
             codeWriter.WriteLine("if (options?.EndpointBuilder?.FilterFactories.Count > 0)");
             codeWriter.StartBlock();
-            if (endpoint.Response.IsAwaitable)
-            {
-                codeWriter.WriteLine("filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(async ic =>");
-            }
-            else
-            {
-                codeWriter.WriteLine("filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>");
-            }
+            codeWriter.WriteLine(endpoint!.Response?.IsAwaitable == true
+                ? "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(async ic =>"
+                : "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>");
             codeWriter.StartBlock();
             codeWriter.WriteLine("if (ic.HttpContext.Response.StatusCode == 400)");
             codeWriter.StartBlock();
@@ -124,16 +125,16 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
             .Collect()
             .Select((endpoints, _) =>
             {
-                var dedupedByDelegate = endpoints.Distinct(EndpointDelegateComparer.Instance);
+                var dedupedByDelegate = endpoints.Distinct<Endpoint>(EndpointDelegateComparer.Instance);
                 using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
                 using var codeWriter = new CodeWriter(stringWriter, baseIndent: 2);
                 foreach (var endpoint in dedupedByDelegate)
                 {
-                    codeWriter.WriteLine($"internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder {endpoint.HttpMethod}(");
+                    codeWriter.WriteLine($"internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder {endpoint!.HttpMethod}(");
                     codeWriter.Indent++;
                     codeWriter.WriteLine("this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,");
                     codeWriter.WriteLine(@"[global::System.Diagnostics.CodeAnalysis.StringSyntax(""Route"")] string pattern,");
-                    codeWriter.WriteLine($"global::{endpoint.EmitHandlerDelegateType()} handler,");
+                    codeWriter.WriteLine($"global::{endpoint!.EmitHandlerDelegateType()} handler,");
                     codeWriter.WriteLine(@"[global::System.Runtime.CompilerServices.CallerFilePath] string filePath = """",");
                     codeWriter.WriteLine("[global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)");
                     codeWriter.Indent--;
@@ -143,7 +144,7 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
                     codeWriter.WriteLine("endpoints,");
                     codeWriter.WriteLine("pattern,");
                     codeWriter.WriteLine("handler,");
-                    codeWriter.WriteLine($"{endpoint.EmitVerb()},");
+                    codeWriter.WriteLine($"{endpoint!.EmitVerb()},");
                     codeWriter.WriteLine("filePath,");
                     codeWriter.WriteLine("lineNumber);");
                     codeWriter.Indent--;
@@ -157,12 +158,12 @@ public sealed class RequestDelegateGenerator : IIncrementalGenerator
             .Collect()
             .Select((endpoints, _) =>
             {
-                var hasJsonBodyOrService = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBodyOrService);
-                var hasJsonBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBody);
-                var hasRouteOrQuery = endpoints.Any(endpoint => endpoint.EmitterContext.HasRouteOrQuery);
-                var hasBindAsync = endpoints.Any(endpoint => endpoint.EmitterContext.HasBindAsync);
-                var hasParsable = endpoints.Any(endpoint => endpoint.EmitterContext.HasParsable);
-                var hasJsonResponse = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonResponse);
+                var hasJsonBodyOrService = endpoints.Any(endpoint => endpoint!.EmitterContext.HasJsonBodyOrService);
+                var hasJsonBody = endpoints.Any(endpoint => endpoint!.EmitterContext.HasJsonBody);
+                var hasRouteOrQuery = endpoints.Any(endpoint => endpoint!.EmitterContext.HasRouteOrQuery);
+                var hasBindAsync = endpoints.Any(endpoint => endpoint!.EmitterContext.HasBindAsync);
+                var hasParsable = endpoints.Any(endpoint => endpoint!.EmitterContext.HasParsable);
+                var hasJsonResponse = endpoints.Any(endpoint => endpoint!.EmitterContext.HasJsonResponse);
 
                 using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
                 using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);

+ 6 - 0
src/Http/Http.Extensions/gen/Resources.resx

@@ -135,4 +135,10 @@
   <data name="UnableToResolveParameter_Title" xml:space="preserve">
     <value>Unable to resolve parameter</value>
   </data>
+  <data name="UnableToResolveAnonymousReturnType_Message" xml:space="preserve">
+    <value>Unable to resolve anonymous return type. Compile-time endpoint generation will skip this endpoint and the endpoint will be generated at runtime.</value>
+  </data>
+  <data name="UnableToResolveAnonymousReturnType_Title" xml:space="preserve">
+    <value>Unable to resolve anonymous type</value>
+  </data>
 </root>

+ 7 - 9
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointJsonResponseEmitter.cs

@@ -5,11 +5,11 @@ namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
 
 internal static class EndpointJsonResponseEmitter
 {
-    internal static void EmitJsonPreparation(this Endpoint endpoint, CodeWriter codeWriter)
+    internal static void EmitJsonPreparation(this EndpointResponse endpointResponse, CodeWriter codeWriter)
     {
-        if (endpoint.Response.IsSerializable)
+        if (endpointResponse is { IsSerializable: true, ResponseType: {} responseType })
         {
-            var typeName = endpoint.Response.ResponseType.ToDisplayString(EmitterConstants.DisplayFormat);
+            var typeName = responseType.ToDisplayString(EmitterConstants.DisplayFormat);
 
             codeWriter.WriteLine("var serviceProvider = options?.ServiceProvider ?? options?.EndpointBuilder?.ApplicationServices;");
             codeWriter.WriteLine("var serializerOptions = serviceProvider?.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions ?? new JsonOptions().SerializerOptions;");
@@ -17,15 +17,13 @@ internal static class EndpointJsonResponseEmitter
         }
     }
 
-    internal static string EmitJsonResponse(this Endpoint endpoint)
+    internal static string EmitJsonResponse(this EndpointResponse endpointResponse)
     {
-        if (endpoint.Response.ResponseType.IsSealed || endpoint.Response.ResponseType.IsValueType)
+        if (endpointResponse.ResponseType != null &&
+            (endpointResponse.ResponseType.IsSealed || endpointResponse.ResponseType.IsValueType))
         {
             return $"httpContext.Response.WriteAsJsonAsync(result, jsonTypeInfo);";
         }
-        else
-        {
-            return $"GeneratedRouteBuilderExtensionsCore.WriteToResponseAsync(result, httpContext, jsonTypeInfo, serializerOptions);";
-        }
+        return $"GeneratedRouteBuilderExtensionsCore.WriteToResponseAsync(result, httpContext, jsonTypeInfo, serializerOptions);";
     }
 }

+ 12 - 6
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs

@@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel;
 
 internal class Endpoint
 {
-    public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes)
+    public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes, SemanticModel semanticModel)
     {
         Operation = operation;
         Location = GetLocation(operation);
@@ -30,15 +30,21 @@ internal class Endpoint
 
         RoutePattern = routeToken.ValueText;
 
-        if (!operation.TryGetRouteHandlerMethod(out var method))
+        if (!operation.TryGetRouteHandlerMethod(semanticModel, out var method) || method == null)
         {
             Diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.UnableToResolveMethod, Operation.Syntax.GetLocation()));
             return;
         }
 
         Response = new EndpointResponse(method, wellKnownTypes);
-        EmitterContext.HasJsonResponse = !(Response.ResponseType.IsSealed || Response.ResponseType.IsValueType);
-        IsAwaitable = Response.IsAwaitable;
+        if (Response.IsAnonymousType)
+        {
+            Diagnostics.Add(Diagnostic.Create(DiagnosticDescriptors.UnableToResolveAnonymousReturnType, Operation.Syntax.GetLocation()));
+            return;
+        }
+
+        EmitterContext.HasJsonResponse = Response is not { ResponseType: { IsSealed: true } or { IsValueType: true } };
+        IsAwaitable = Response?.IsAwaitable == true;
 
         if (method.Parameters.Length == 0)
         {
@@ -107,7 +113,7 @@ internal class Endpoint
 
     public static bool SignatureEquals(Endpoint a, Endpoint b)
     {
-        if (!a.Response.WrappedResponseType.Equals(b.Response.WrappedResponseType, StringComparison.Ordinal) ||
+        if (!string.Equals(a.Response?.WrappedResponseType, b.Response?.WrappedResponseType, StringComparison.Ordinal) ||
             !a.HttpMethod.Equals(b.HttpMethod, StringComparison.Ordinal) ||
             a.Parameters.Length != b.Parameters.Length)
         {
@@ -128,7 +134,7 @@ internal class Endpoint
     public static int GetSignatureHashCode(Endpoint endpoint)
     {
         var hashCode = new HashCode();
-        hashCode.Add(endpoint.Response.WrappedResponseType);
+        hashCode.Add(endpoint.Response?.WrappedResponseType);
         hashCode.Add(endpoint.HttpMethod);
 
         foreach (var parameter in endpoint.Parameters)

+ 13 - 4
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointResponse.cs

@@ -15,11 +15,12 @@ internal class EndpointResponse
 {
     public ITypeSymbol? ResponseType { get; set; }
     public string WrappedResponseType { get; set; }
-    public string ContentType { get; set; }
+    public string? ContentType { get; set; }
     public bool IsAwaitable { get; set; }
     public bool IsVoid { get; set; }
     public bool IsIResult { get; set; }
     public bool IsSerializable { get; set; }
+    public bool IsAnonymousType { get; set; }
 
     private WellKnownTypes WellKnownTypes { get; init; }
 
@@ -31,11 +32,12 @@ internal class EndpointResponse
         IsAwaitable = GetIsAwaitable(method);
         IsVoid = method.ReturnsVoid;
         IsIResult = GetIsIResult();
-        IsSerializable = !IsIResult && !IsVoid && ResponseType.SpecialType != SpecialType.System_String && ResponseType.SpecialType != SpecialType.System_Object;
+        IsSerializable = GetIsSerializable();
         ContentType = GetContentType(method);
+        IsAnonymousType = method.ReturnType.IsAnonymousType;
     }
 
-    private ITypeSymbol UnwrapResponseType(IMethodSymbol method)
+    private ITypeSymbol? UnwrapResponseType(IMethodSymbol method)
     {
         var returnType = method.ReturnType;
         var task = WellKnownTypes.Get(WellKnownType.System_Threading_Tasks_Task);
@@ -57,6 +59,13 @@ internal class EndpointResponse
         return returnType;
     }
 
+    private bool GetIsSerializable() =>
+        !IsIResult &&
+        !IsVoid &&
+        ResponseType != null &&
+        ResponseType.SpecialType != SpecialType.System_String &&
+        ResponseType.SpecialType != SpecialType.System_Object;
+
     private bool GetIsIResult()
     {
         var resultType = WellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_IResult);
@@ -120,7 +129,7 @@ internal class EndpointResponse
             otherEndpointResponse.IsAwaitable == IsAwaitable &&
             otherEndpointResponse.IsVoid == IsVoid &&
             otherEndpointResponse.IsIResult == IsIResult &&
-            otherEndpointResponse.ContentType.Equals(ContentType, StringComparison.OrdinalIgnoreCase);
+            string.Equals(otherEndpointResponse.ContentType, ContentType, StringComparison.OrdinalIgnoreCase);
     }
 
     public override int GetHashCode() =>

+ 25 - 11
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/InvocationOperationExtensions.cs

@@ -1,6 +1,7 @@
 // Licensed to the .NET Foundation under one or more agreements.
 // The .NET Foundation licenses this file to you under the MIT license.
 
+using System.Diagnostics.CodeAnalysis;
 using Microsoft.CodeAnalysis;
 using Microsoft.CodeAnalysis.CSharp.Syntax;
 using Microsoft.CodeAnalysis.Operations;
@@ -12,13 +13,13 @@ internal static class InvocationOperationExtensions
     private const int RoutePatternArgumentOrdinal = 1;
     private const int RouteHandlerArgumentOrdinal = 2;
 
-    public static bool TryGetRouteHandlerMethod(this IInvocationOperation invocation, out IMethodSymbol method)
+    public static bool TryGetRouteHandlerMethod(this IInvocationOperation invocation, SemanticModel semanticModel, out IMethodSymbol? method)
     {
         foreach (var argument in invocation.Arguments)
         {
             if (argument.Parameter?.Ordinal == RouteHandlerArgumentOrdinal)
             {
-                method = ResolveMethodFromOperation(argument);
+                method = ResolveMethodFromOperation(argument, semanticModel);
                 return true;
             }
         }
@@ -26,6 +27,18 @@ internal static class InvocationOperationExtensions
         return false;
     }
 
+    public static IArgumentOperation? GetRouteHandlerArgument(this IInvocationOperation invocation)
+    {
+        foreach (var argument in invocation.Arguments)
+        {
+            if (argument.Parameter?.Ordinal == RouteHandlerArgumentOrdinal)
+            {
+                return argument;
+            }
+        }
+        return null;
+    }
+
     public static bool TryGetRouteHandlerPattern(this IInvocationOperation invocation, out SyntaxToken token)
     {
         IArgumentOperation? argumentOperation = null;
@@ -46,21 +59,21 @@ internal static class InvocationOperationExtensions
         return true;
     }
 
-    private static IMethodSymbol ResolveMethodFromOperation(IOperation operation) => operation switch
+    private static IMethodSymbol? ResolveMethodFromOperation(IOperation operation, SemanticModel semanticModel) => operation switch
     {
-        IArgumentOperation argument => ResolveMethodFromOperation(argument.Value),
-        IConversionOperation conv => ResolveMethodFromOperation(conv.Operand),
-        IDelegateCreationOperation del => ResolveMethodFromOperation(del.Target),
-        IFieldReferenceOperation { Field.IsReadOnly: true } f when ResolveDeclarationOperation(f.Field, operation.SemanticModel) is IOperation op =>
-            ResolveMethodFromOperation(op),
+        IArgumentOperation argument => ResolveMethodFromOperation(argument.Value, semanticModel),
+        IConversionOperation conv => ResolveMethodFromOperation(conv.Operand, semanticModel),
+        IDelegateCreationOperation del => ResolveMethodFromOperation(del.Target, semanticModel),
+        IFieldReferenceOperation { Field.IsReadOnly: true } f when ResolveDeclarationOperation(f.Field, semanticModel) is IOperation op =>
+            ResolveMethodFromOperation(op, semanticModel),
         IAnonymousFunctionOperation anon => anon.Symbol,
         ILocalFunctionOperation local => local.Symbol,
         IMethodReferenceOperation method => method.Method,
-        IParenthesizedOperation parenthesized => ResolveMethodFromOperation(parenthesized.Operand),
+        IParenthesizedOperation parenthesized => ResolveMethodFromOperation(parenthesized.Operand, semanticModel),
         _ => null
     };
 
-    private static IOperation ResolveDeclarationOperation(ISymbol symbol, SemanticModel semanticModel)
+    private static IOperation? ResolveDeclarationOperation(ISymbol symbol, SemanticModel? semanticModel)
     {
         foreach (var syntaxReference in symbol.DeclaringSyntaxReferences)
         {
@@ -75,7 +88,8 @@ internal static class InvocationOperationExtensions
                 })
             {
                 // Use the correct semantic model based on the syntax tree
-                var operation = semanticModel.GetOperation(expr);
+                var targetSemanticModel = semanticModel?.Compilation.GetSemanticModel(expr.SyntaxTree);
+                var operation = targetSemanticModel?.GetOperation(expr);
 
                 if (operation is not null)
                 {

+ 31 - 38
src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs

@@ -15,43 +15,32 @@ internal static class StaticRouteHandlerModelEmitter
     {
         if (endpoint.Parameters.Length == 0)
         {
-            return endpoint.Response.IsVoid ? "System.Action" : $"System.Func<{endpoint.Response.WrappedResponseType}>";
+            return endpoint.Response == null || endpoint.Response.IsVoid ? "System.Action" : $"System.Func<{endpoint.Response.WrappedResponseType}>";
         }
-        else
-        {
-            var parameterTypeList = string.Join(", ", endpoint.Parameters.Select(p => p.Type.ToDisplayString(EmitterConstants.DisplayFormat)));
+        var parameterTypeList = string.Join(", ", endpoint.Parameters.Select(p => p.Type.ToDisplayString(EmitterConstants.DisplayFormat)));
 
-            if (endpoint.Response.IsVoid)
-            {
-                return $"System.Action<{parameterTypeList}>";
-            }
-            else
-            {
-                return $"System.Func<{parameterTypeList}, {endpoint.Response.WrappedResponseType}>";
-            }
+        if (endpoint.Response == null || endpoint.Response.IsVoid)
+        {
+            return $"System.Action<{parameterTypeList}>";
         }
+        return $"System.Func<{parameterTypeList}, {endpoint.Response.WrappedResponseType}>";
     }
 
     public static string EmitHandlerDelegateCast(this Endpoint endpoint)
     {
         if (endpoint.Parameters.Length == 0)
         {
-            return endpoint.Response.IsVoid ? "Action" : $"Func<{endpoint.Response.WrappedResponseType}>";
+            return endpoint.Response == null || endpoint.Response.IsVoid ? "Action" : $"Func<{endpoint.Response.WrappedResponseType}>";
         }
-        else
-        {
-            var parameterTypeList = string.Join(", ", endpoint.Parameters.Select(
-                p => p.Type.ToDisplayString(p.IsOptional ? NullableFlowState.MaybeNull : NullableFlowState.NotNull, EmitterConstants.DisplayFormat)));
 
-            if (endpoint.Response.IsVoid)
-            {
-                return $"Action<{parameterTypeList}>";
-            }
-            else
-            {
-                return $"Func<{parameterTypeList}, {endpoint.Response.WrappedResponseType}>";
-            }
+        var parameterTypeList = string.Join(", ", endpoint.Parameters.Select(
+            p => p.Type.ToDisplayString(p.IsOptional ? NullableFlowState.MaybeNull : NullableFlowState.NotNull, EmitterConstants.DisplayFormat)));
+
+        if (endpoint.Response == null || endpoint.Response.IsVoid)
+        {
+            return $"Action<{parameterTypeList}>";
         }
+        return $"Func<{parameterTypeList}, {endpoint.Response.WrappedResponseType}>";
     }
 
     public static string EmitSourceKey(this Endpoint endpoint)
@@ -94,9 +83,13 @@ internal static class StaticRouteHandlerModelEmitter
         codeWriter.WriteLine("httpContext.Response.StatusCode = 400;");
         codeWriter.WriteLine(endpoint.IsAwaitable ? "return;" : "return Task.CompletedTask;");
         codeWriter.EndBlock(); // End if-statement block
-        if (!endpoint.Response.IsVoid)
+        if (endpoint.Response == null)
+        {
+            return;
+        }
+        if (!endpoint.Response.IsVoid && endpoint.Response is { ContentType: {} contentType})
         {
-            codeWriter.WriteLine($@"httpContext.Response.ContentType ??= ""{endpoint.Response.ContentType}"";");
+            codeWriter.WriteLine($@"httpContext.Response.ContentType ??= ""{contentType}"";");
         }
         if (!endpoint.Response.IsVoid)
         {
@@ -109,7 +102,7 @@ internal static class StaticRouteHandlerModelEmitter
         codeWriter.WriteLine($"handler({endpoint.EmitArgumentList()});");
         if (!endpoint.Response.IsVoid)
         {
-            codeWriter.WriteLine(endpoint.EmitResponseWritingCall());
+            codeWriter.WriteLine(endpoint.Response.EmitResponseWritingCall(endpoint.IsAwaitable));
         }
         else if (!endpoint.IsAwaitable)
         {
@@ -118,27 +111,27 @@ internal static class StaticRouteHandlerModelEmitter
         codeWriter.EndBlock(); // End handler method block
     }
 
-    private static string EmitResponseWritingCall(this Endpoint endpoint)
+    private static string EmitResponseWritingCall(this EndpointResponse endpointResponse, bool isAwaitable)
     {
-        var returnOrAwait = endpoint.IsAwaitable ? "await" : "return";
+        var returnOrAwait = isAwaitable ? "await" : "return";
 
-        if (endpoint.Response.IsIResult)
+        if (endpointResponse.IsIResult)
         {
             return $"{returnOrAwait} result.ExecuteAsync(httpContext);";
         }
-        else if (endpoint.Response.ResponseType.SpecialType == SpecialType.System_String)
+        else if (endpointResponse.ResponseType?.SpecialType == SpecialType.System_String)
         {
             return $"{returnOrAwait} httpContext.Response.WriteAsync(result);";
         }
-        else if (endpoint.Response.ResponseType.SpecialType == SpecialType.System_Object)
+        else if (endpointResponse.ResponseType?.SpecialType == SpecialType.System_Object)
         {
             return $"{returnOrAwait} GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);";
         }
-        else if (!endpoint.Response.IsVoid)
+        else if (!endpointResponse.IsVoid)
         {
-            return $"{returnOrAwait} {endpoint.EmitJsonResponse()}";
+            return $"{returnOrAwait} {endpointResponse.EmitJsonResponse()}";
         }
-        else if (!endpoint.Response.IsAwaitable && endpoint.Response.IsVoid)
+        else if (!endpointResponse.IsAwaitable && endpointResponse.IsVoid)
         {
             return $"{returnOrAwait} Task.CompletedTask;";
         }
@@ -185,12 +178,12 @@ internal static class StaticRouteHandlerModelEmitter
 
     public static void EmitFilteredInvocation(this Endpoint endpoint, CodeWriter codeWriter)
     {
-        if (endpoint.Response.IsVoid)
+        if (endpoint.Response?.IsVoid == true)
         {
             codeWriter.WriteLine($"handler({endpoint.EmitFilteredArgumentList()});");
             codeWriter.WriteLine("return ValueTask.FromResult<object?>(Results.Empty);");
         }
-        else if (endpoint.Response.IsAwaitable)
+        else if (endpoint.Response?.IsAwaitable == true)
         {
             codeWriter.WriteLine($"var result = await handler({endpoint.EmitFilteredArgumentList()});");
             codeWriter.WriteLine("return (object?)result;");

+ 0 - 2
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt

@@ -132,7 +132,6 @@ namespace Microsoft.AspNetCore.Http.Generated
                             httpContext.Response.StatusCode = 400;
                             return;
                         }
-                        httpContext.Response.ContentType ??= "";
                         var result = handler(todo_local!);
                         await result.ExecuteAsync(httpContext);
                     }
@@ -202,7 +201,6 @@ namespace Microsoft.AspNetCore.Http.Generated
                             httpContext.Response.StatusCode = 400;
                             return;
                         }
-                        httpContext.Response.ContentType ??= "";
                         var result = handler(todo_local);
                         await result.ExecuteAsync(httpContext);
                     }

+ 0 - 298
src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt

@@ -1,298 +0,0 @@
-//------------------------------------------------------------------------------
-// <auto-generated>
-//     This code was generated by a tool.
-//
-//     Changes to this file may cause incorrect behavior and will be lost if
-//     the code is regenerated.
-// </auto-generated>
-//------------------------------------------------------------------------------
-#nullable enable
-
-namespace Microsoft.AspNetCore.Builder
-{
-    %GENERATEDCODEATTRIBUTE%
-    internal class SourceKey
-    {
-        public string Path { get; init; }
-        public int Line { get; init; }
-
-        public SourceKey(string path, int line)
-        {
-            Path = path;
-            Line = line;
-        }
-    }
-
-    // This class needs to be internal so that the compiled application
-    // has access to the strongly-typed endpoint definitions that are
-    // generated by the compiler so that they will be favored by
-    // overload resolution and opt the runtime in to the code generated
-    // implementation produced here.
-    %GENERATEDCODEATTRIBUTE%
-    internal static class GenerateRouteBuilderEndpoints
-    {
-        private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get };
-        private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post };
-        private static readonly string[] PutVerb = new[]  { global::Microsoft.AspNetCore.Http.HttpMethods.Put };
-        private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete };
-        private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch };
-
-        internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet(
-            this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints,
-            [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern,
-            global::System.Func<global::System.TimeOnly, global::System.String> handler,
-            [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "",
-            [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0)
-        {
-            return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore(
-                endpoints,
-                pattern,
-                handler,
-                GetVerb,
-                filePath,
-                lineNumber);
-        }
-
-    }
-}
-
-namespace Microsoft.AspNetCore.Http.Generated
-{
-    using System;
-    using System.Collections;
-    using System.Collections.Generic;
-    using System.Collections.ObjectModel;
-    using System.Diagnostics;
-    using System.Diagnostics.CodeAnalysis;
-    using System.Globalization;
-    using System.Linq;
-    using System.Reflection;
-    using System.Text.Json;
-    using System.Text.Json.Serialization.Metadata;
-    using System.Threading.Tasks;
-    using System.IO;
-    using Microsoft.AspNetCore.Routing;
-    using Microsoft.AspNetCore.Routing.Patterns;
-    using Microsoft.AspNetCore.Builder;
-    using Microsoft.AspNetCore.Http;
-    using Microsoft.AspNetCore.Http.Json;
-    using Microsoft.AspNetCore.Http.Metadata;
-    using Microsoft.Extensions.DependencyInjection;
-    using Microsoft.Extensions.FileProviders;
-    using Microsoft.Extensions.Primitives;
-    using Microsoft.Extensions.Options;
-
-    using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
-    using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
-
-    file static class GeneratedRouteBuilderExtensionsCore
-    {
-
-        private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new()
-        {
-            [(@"TestMapActions.cs", 24)] = (
-                (methodInfo, options) =>
-                {
-                    Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found.");
-                    options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24));
-                    return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };
-                },
-                (del, options, inferredMetadataResult) =>
-                {
-                    var handler = (Func<global::System.TimeOnly, global::System.String>)del;
-                    EndpointFilterDelegate? filteredInvocation = null;
-
-                    if (options?.EndpointBuilder?.FilterFactories.Count > 0)
-                    {
-                        filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>
-                        {
-                            if (ic.HttpContext.Response.StatusCode == 400)
-                            {
-                                return ValueTask.FromResult<object?>(Results.Empty);
-                            }
-                            return ValueTask.FromResult<object?>(handler(ic.GetArgument<global::System.TimeOnly>(0)));
-                        },
-                        options.EndpointBuilder,
-                        handler.Method);
-                    }
-
-                    Task RequestHandler(HttpContext httpContext)
-                    {
-                        var wasParamCheckFailure = false;
-                        // Endpoint Parameter: p (Type = System.TimeOnly, IsOptional = False, IsParsable = True, Source = Query)
-                        var p_raw = httpContext.Request.Query["p"];
-                        if (StringValues.IsNullOrEmpty(p_raw))
-                        {
-                            wasParamCheckFailure = true;
-                        }
-                        var p_temp = (string?)p_raw;
-                        if (!ParsableHelper<global::System.TimeOnly>.TryParse(p_temp!, CultureInfo.InvariantCulture, out var p_parsed_temp))
-                        {
-                            wasParamCheckFailure = true;
-                        }
-                        global::System.TimeOnly p_local = p_parsed_temp!;
-
-                        if (wasParamCheckFailure)
-                        {
-                            httpContext.Response.StatusCode = 400;
-                            return Task.CompletedTask;
-                        }
-                        httpContext.Response.ContentType ??= "text/plain";
-                        var result = handler(p_local);
-                        return httpContext.Response.WriteAsync(result);
-                    }
-
-                    async Task RequestHandlerFiltered(HttpContext httpContext)
-                    {
-                        var wasParamCheckFailure = false;
-                        // Endpoint Parameter: p (Type = System.TimeOnly, IsOptional = False, IsParsable = True, Source = Query)
-                        var p_raw = httpContext.Request.Query["p"];
-                        if (StringValues.IsNullOrEmpty(p_raw))
-                        {
-                            wasParamCheckFailure = true;
-                        }
-                        var p_temp = (string?)p_raw;
-                        if (!ParsableHelper<global::System.TimeOnly>.TryParse(p_temp!, CultureInfo.InvariantCulture, out var p_parsed_temp))
-                        {
-                            wasParamCheckFailure = true;
-                        }
-                        global::System.TimeOnly p_local = p_parsed_temp!;
-
-                        if (wasParamCheckFailure)
-                        {
-                            httpContext.Response.StatusCode = 400;
-                        }
-                        var result = await filteredInvocation(EndpointFilterInvocationContext.Create<global::System.TimeOnly>(httpContext, p_local));
-                        await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext);
-                    }
-
-                    RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;
-                    var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;
-                    return new RequestDelegateResult(targetDelegate, metadata);
-                }),
-
-        };
-
-        internal static RouteHandlerBuilder MapCore(
-            this IEndpointRouteBuilder routes,
-            string pattern,
-            Delegate handler,
-            IEnumerable<string> httpMethods,
-            string filePath,
-            int lineNumber)
-        {
-            var (populateMetadata, createRequestDelegate) = map[(filePath, lineNumber)];
-            return RouteHandlerServices.Map(routes, pattern, handler, httpMethods, populateMetadata, createRequestDelegate);
-        }
-
-        private static EndpointFilterDelegate BuildFilterDelegate(EndpointFilterDelegate filteredInvocation, EndpointBuilder builder, MethodInfo mi)
-        {
-            var routeHandlerFilters =  builder.FilterFactories;
-            var context0 = new EndpointFilterFactoryContext
-            {
-                MethodInfo = mi,
-                ApplicationServices = builder.ApplicationServices,
-            };
-            var initialFilteredInvocation = filteredInvocation;
-            for (var i = routeHandlerFilters.Count - 1; i >= 0; i--)
-            {
-                var filterFactory = routeHandlerFilters[i];
-                filteredInvocation = filterFactory(context0, filteredInvocation);
-            }
-            return filteredInvocation;
-        }
-
-        private static Task ExecuteObjectResult(object? obj, HttpContext httpContext)
-        {
-            if (obj is IResult r)
-            {
-                return r.ExecuteAsync(httpContext);
-            }
-            else if (obj is string s)
-            {
-                return httpContext.Response.WriteAsync(s);
-            }
-            else
-            {
-                return httpContext.Response.WriteAsJsonAsync(obj);
-            }
-        }
-
-        private static Func<HttpContext, StringValues> ResolveFromRouteOrQuery(string parameterName, IEnumerable<string>? routeParameterNames)
-        {
-            return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true
-                ? (httpContext) => new StringValues((string?)httpContext.Request.RouteValues[parameterName])
-                : (httpContext) => httpContext.Request.Query[parameterName];
-        }
-
-        private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
-        {
-            var runtimeType = value?.GetType();
-            if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
-            {
-                return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
-            }
-
-            return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
-        }
-
-        private static async ValueTask<(bool, T?)> TryResolveBodyAsync<T>(HttpContext httpContext, bool allowEmpty)
-        {
-            var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();
-
-            if (feature?.CanHaveBody == true)
-            {
-                if (!httpContext.Request.HasJsonContentType())
-                {
-                    httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType;
-                    return (false, default);
-                }
-                try
-                {
-                    var bodyValue = await httpContext.Request.ReadFromJsonAsync<T>();
-                    if (!allowEmpty && bodyValue == null)
-                    {
-                        httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
-                        return (false, bodyValue);
-                    }
-                    return (true, bodyValue);
-                }
-                catch (IOException)
-                {
-                    return (false, default);
-                }
-                catch (System.Text.Json.JsonException)
-                {
-                    httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
-                    return (false, default);
-                }
-            }
-            return (false, default);
-        }
-
-        private static ValueTask<T?> BindAsync<T>(HttpContext context, ParameterInfo parameter)
-            where T : class, IBindableFromHttpContext<T>
-        {
-            return T.BindAsync(context, parameter);
-        }
-
-        private static ValueTask<(bool, T?)> TryResolveJsonBodyOrServiceAsync<T>(HttpContext httpContext, bool isOptional, IServiceProviderIsService? serviceProviderIsService = null)
-        {
-            if (serviceProviderIsService is not null)
-            {
-                if (serviceProviderIsService.IsService(typeof(T)))
-                {
-                    return new ValueTask<(bool, T?)>((true, httpContext.RequestServices.GetService<T>()));
-                }
-            }
-            return TryResolveBodyAsync<T>(httpContext, isOptional);
-        }
-    }
-
-    %GENERATEDCODEATTRIBUTE%
-    file static class ParsableHelper<T> where T : IParsable<T>
-    {
-        public static T Parse(string s, IFormatProvider? provider) => T.Parse(s, provider);
-        public static bool TryParse(string? s, IFormatProvider? provider, [MaybeNullWhen(returnValue: false)] out T result) => T.TryParse(s, provider, out result);
-    }
-}

+ 1 - 1
src/Shared/RoslynUtils/SymbolExtensions.cs

@@ -27,7 +27,7 @@ internal static class SymbolExtensions
         }
 
         // If it is nullable, unwrap it.
-        if (unwrappedTypeSymbol!.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
+        if (unwrappedTypeSymbol?.ConstructedFrom.SpecialType == SpecialType.System_Nullable_T)
         {
             unwrappedTypeSymbol = unwrappedTypeSymbol.TypeArguments[0] as INamedTypeSymbol;
         }