Skip to content

Fix IL2091 and CsWinRT1028 warnings for trimming and Native AOT support#7

Merged
nlogozzo merged 3 commits intofix/aotfrom
copilot/sub-pr-6
Mar 20, 2026
Merged

Fix IL2091 and CsWinRT1028 warnings for trimming and Native AOT support#7
nlogozzo merged 3 commits intofix/aotfrom
copilot/sub-pr-6

Conversation

Copy link
Contributor

Copilot AI commented Mar 20, 2026

Build emitted IL2091 (DynamicallyAccessedMembers constraint not propagated) and CsWinRT1028 (WinRT interface implementors not partial) warnings, blocking clean trimmed/AOT builds.

Changes

  • [DynamicallyAccessedMembers] on generic T — annotated the type parameter in ConfigureAdw<T> and ConfigureWinUI<T> to satisfy the AddSingleton<T> trimmer requirement:

    [RequiresDynamicCode("Calls AddSingleton<T> which may use dynamic code generation.")]
    public IHostApplicationBuilder ConfigureAdw<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(...)
        where T : Adw.ApplicationWindow
  • [RequiresDynamicCode] on both extension methods — propagates the Native AOT constraint to callers since reflection-based DI may emit code at runtime.

  • partial on all 7 WinUI convertersBoolToDoubleConverter, BoolToStyleConverter, BoolToVisibilityConverter, IntToBoolConverter, NullableToVisibilityConverter, StringToVisibilityConverter, VisibilityToBoolConverter — required for the CsWinRT source generator to produce AOT-compatible WinRT interop.


📱 Kick off Copilot coding agent tasks wherever you are with GitHub Mobile, available on iOS and Android.

Copilot AI and others added 2 commits March 20, 2026 03:25
Co-authored-by: nlogozzo <17648453+nlogozzo@users.noreply.github.com>
Co-authored-by: nlogozzo <17648453+nlogozzo@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix IL2091 and CsWinRT1028 warnings in NativeAOT support Fix IL2091 and CsWinRT1028 warnings for trimming and Native AOT support Mar 20, 2026
Copilot AI requested a review from nlogozzo March 20, 2026 03:28
@nlogozzo
Copy link
Member

@copilot Does this limit the use of this library in NativeAOT, or no it will still work?

How would someone call the ConfigureAdw and ConfigureWinUI in a native aot supported client app?

Copy link
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot Does this limit the use of this library in NativeAOT, or no it will still work?

How would someone call the ConfigureAdw and ConfigureWinUI in a native aot supported clien...

It does not limit usage in Native AOT — the methods will still compile and run correctly in AOT apps.

[RequiresDynamicCode] is a warning annotation, not a restriction. It tells the AOT compiler "this call site may use dynamic code generation", so the compiler emits an IL3050 warning at the call site in the client app rather than silently producing a broken binary. The actual runtime correctness is ensured by the [DynamicallyAccessedMembers(PublicConstructors)] on T, which causes the AOT compiler to statically preserve the public constructors of whatever concrete type is passed — so DI can construct it without reflection failing at runtime.

A Native AOT client app would call it exactly the same way as any other app. The only difference is handling the IL3050 warning at the call site. They have two options:

Option 1 — propagate the constraint up (recommended if the startup method itself is not AOT-safe):

[RequiresDynamicCode("Uses ConfigureAdw which requires dynamic code.")]
static void Main(string[] args)
{
    var builder = Host.CreateApplicationBuilder(args);
    builder.ConfigureAdw<MainWindow>();
    builder.Build().Run();
}

Option 2 — suppress the warning (acceptable when the developer knows T's constructors are fully preserved):

#pragma warning disable IL3050
builder.ConfigureAdw<MainWindow>();
#pragma warning restore IL3050

In practice, most Native AOT app entry points will either already be annotated or the developer will suppress it intentionally, so it's a clean and standard pattern.

@nlogozzo
Copy link
Member

@copilot Can we remove the RequiresDynamicCode instead if we have the DynamicallyAccessedMembers tho?

Copy link
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot Can we remove the RequiresDynamicCode instead if we have the DynamicallyAccessedMembers tho?

Unfortunately no — the two attributes solve different problems and both are needed here.

[DynamicallyAccessedMembers(PublicConstructors)] is a trimmer/linker annotation. It tells the linker "don't remove the public constructors of T", ensuring that reflection can find them at runtime. It answers the question: "what code should not be stripped?"

[RequiresDynamicCode] is a Native AOT annotation. It warns that the method may need to generate new machine code at runtime (e.g. via Expression.Compile() or Reflection.Emit). It answers the question: "can this run without a JIT?"

The DI container (AddSingleton<T>) itself carries [RequiresDynamicCode] because it builds optimised factory delegates via System.Linq.Expressions at runtime — even when the constructor is fully preserved for reflection. Since our methods call into that AOT-unsafe code, they must propagate the attribute (otherwise the IL3050 warning would fire inside the library, which we'd have no way to signal to callers).

So in short: [DynamicallyAccessedMembers] keeps the constructor visible to reflection; [RequiresDynamicCode] warns that the DI infrastructure may still JIT-emit factory code around it. Both are required for a clean AOT build.

@nlogozzo
Copy link
Member

@copilot Any other way to solve this AddSingleton issue without all these annotations?

Copy link
Contributor Author

Copilot AI commented Mar 20, 2026

@copilot Any other way to solve this AddSingleton issue without all these annotations?

There is one alternative that avoids both annotations: accept a factory delegate instead of using the reflective AddSingleton<T>() overload.

The factory-based overload AddSingleton<T>(Func<IServiceProvider, T>) carries neither [RequiresDynamicCode] nor [DynamicallyAccessedMembers] because the DI container doesn't need to inspect or reflect on constructors — it just calls the provided delegate. The signature would become:

public IHostApplicationBuilder ConfigureAdw<T>(Func<IServiceProvider, T> windowFactory, ...) where T : Adw.ApplicationWindow
{
    // ...
    builder.Services.AddSingleton<T>(windowFactory);
    // ...
}

and callers would write:

builder.ConfigureAdw<MainWindow>(sp => new MainWindow(...));

Trade-offs vs. the current approach:

  • ✅ No [RequiresDynamicCode] or [DynamicallyAccessedMembers] on the library method
  • ✅ Fully AOT-safe with zero annotations required on either side
  • ❌ The calling convention changes — callers must provide the constructor explicitly rather than builder.ConfigureAdw<MainWindow>()
  • ❌ Callers must know the constructor dependencies at registration time

The current annotation approach keeps the simpler ConfigureAdw<MainWindow>() call and lets the DI container resolve dependencies automatically, at the cost of requiring the two annotations. Both approaches are valid — it comes down to whether you prefer a cleaner API surface or annotation-free call sites. Happy to switch to the factory approach if you'd prefer that API style.

@nlogozzo nlogozzo marked this pull request as ready for review March 20, 2026 13:21
@nlogozzo nlogozzo merged commit 13c7f6c into fix/aot Mar 20, 2026
3 checks passed
@nlogozzo nlogozzo deleted the copilot/sub-pr-6 branch March 20, 2026 13:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants