Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Routing source generation for Native AOT not working well with generic constraints and nullable reference types #51866

Closed
1 task done
joaofbantunes opened this issue Nov 4, 2023 · 4 comments · Fixed by #53049
Assignees
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-rdg NativeAOT
Milestone

Comments

@joaofbantunes
Copy link

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

Building an API using .NET 8's Native AOT, I've encountered an issue when using generic constraints and nullable reference types, along with the services being injected. Here's a practical example.

Given the following interfaces:

public interface IInputConstraint<TOutput>;

public interface IGenericService<TInput, TOutput> where TInput : IInputConstraint<TOutput>
{
    TOutput Get(TInput input);
}

With some concrete implementations:

public record SomeInput(int Value) : IInputConstraint<string?>;

public class ConcreteService : IGenericService<SomeInput, string?>
{
    public string? Get(SomeInput input) => input.Value % 2 == 0 ? input.Value.ToString() : null;
}

Then registering and using this:

builder.Services.AddSingleton<IGenericService<SomeInput, string?>, ConcreteService>();

// ...

app.MapGet("/", (IGenericService<SomeInput, string?> service) 
    => "Maybe? " + service.Get(new SomeInput(Random.Shared.Next())));

When building the API, an error occurs, indicating that in the GeneratedRouteBuilderExtensions.g.cs file, SomeInput cannot be used as a type parameter because nullability doesn't match the generic constraint.

Looking at the GeneratedRouteBuilderExtensions.g.cs file, we can see that its missing the question mark to denote the string nullability:

var jsonBodyOrServiceTypeTuples = new (bool, Type)[] {
    (false, typeof(global::IGenericService<global::SomeInput, global::System.String>)),
};

Expected Behavior

The generated source code matches the original code, and the application works normally.

Steps To Reproduce

I created a simple example here: https://github.com/joaofbantunes/NativeAotVsNullableReferenceTypes

Exceptions (if any)

Not really exception, but compiler error:

GeneratedRouteBuilderExtensions.g.cs(75,60): Error CS8631 : The type 'SomeInput' cannot be used as type parameter 'TInput' in the generic type or method 'IGenericService<TInput, TOutput>'. Nullability of type argument 'SomeInput' doesn't match constraint type 'IInputConstraint<string>'.

.NET Version

8.0.100-rc.2.23502.2

Anything else?

Runtime Environment:
OS Name: Mac OS X
OS Version: 14.0
OS Platform: Darwin
RID: osx-arm64
Base Path: /usr/local/share/dotnet/sdk/8.0.100-rc.2.23502.2/

.NET workloads installed:
There are no installed workloads to display.

Host:
Version: 8.0.0-rc.2.23479.6
Architecture: arm64
Commit: 0b25e38ad3

.NET SDKs installed:
6.0.408 [/usr/local/share/dotnet/sdk]
7.0.302 [/usr/local/share/dotnet/sdk]
8.0.100-rc.2.23502.2 [/usr/local/share/dotnet/sdk]

.NET runtimes installed:
Microsoft.AspNetCore.App 6.0.16 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.5 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.0-rc.2.23480.2 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
Microsoft.NETCore.App 6.0.16 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.5 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.0-rc.2.23479.6 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

Other architectures found:
None

Environment variables:
Not set

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download

@ghost ghost added the NativeAOT label Nov 4, 2023
@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Nov 4, 2023
@gfoidl gfoidl removed the needs-area-label Used by the dotnet-issue-labeler to label those issues which couldn't be triaged automatically label Nov 4, 2023
@captainsafia captainsafia added feature-rdg area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc labels Nov 6, 2023
@captainsafia
Copy link
Member

@joaofbantunes Thanks for reporting this issue!

So, currently, we omit the nullability annotations on types that are provided as arguments to the typeof operator since it doesn't support NRTs. Our symbol name rendering logic is currently sophisticated enough to determine if the type that is provided is a generic type where the arguments themselves are nullable. I'll have to see if there is some configuration of SymbolDisplayFormat that would allow us to support this.

@captainsafia captainsafia self-assigned this Nov 7, 2023
@captainsafia
Copy link
Member

Just an FYI - dotnet/runtime's source generators actually disable nullable warnings to avoid problems like this. See dotnet/runtime#94267

Yeah, the main reason that I avoided this in RDG is because we care about nullability for certain application behaviors. For example, we do nullability checks on the parameter types to determine if we should generate code that throws an exception on a per-request basis if a parameter isn't provided.

Perhaps the middle ground is to disable nullability using pragmas around certain lines of code instead of the entire file...

@eerhardt
Copy link
Member

I'm not sure why disabling nullable warnings on the whole generated file would prevent us from doing nullability checks on the user's code and generating the necessary exceptions.

@captainsafia captainsafia added this to the 9.0-preview1 milestone Nov 22, 2023
@ghost ghost locked as resolved and limited conversation to collaborators Feb 7, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-minimal Includes minimal APIs, endpoint filters, parameter binding, request delegate generator etc feature-rdg NativeAOT
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants