Skip to content

Conversation

@dusrdev
Copy link
Contributor

@dusrdev dusrdev commented Nov 6, 2025

Before this PR:

When using delegate handlers such as:

var app = ConsoleApp.Create();
app.Add("", Commands.Root);

and Commands.Root included attributes like ValidationAttributes, the generated code would still call command.Method.GetParameters() even after source generation, This requires metadata that may be trimmed when compiling with Native AOT, causing a runtime exception.

What this PR does:

  1. Adds a sample console app "NativeAotTrimming" under tests that reproduces the error when compiled with Native AOT.
  2. Adds a unit test that publishes (compiles) said app with Native AOT, then runs it as a process to check the exit code (if the MethodInfo was trimmed away - exit code != 0 and test fails.
  3. Updates Emitter to fix the issue
  • Defines a SymbolDisplayFormat to fully qualify type names.
  • If command.CommandMethodInfo is null, meaning the runtime will rely on reflection to discover the delegate’s parameters, the emitter now uses the handler’s IMethodSymbol to create a DynamicDependency attribute that points to the handler’s containing type.
  • Injects this attribute before the RunCommand method signature.
  • If CommandMethodInfo is not null, no changes are made.

Rationale:

This change ensures that the necessary MethodInfo is preserved when it would otherwise be trimmed.

An alternative approach would be to cache the method and its parameters during build time, but that would require significant refactoring of Emitter. The current solution fixes the issue for affected users, and caching could be explored as a potential improvement in the future.

@neuecc
Copy link
Member

neuecc commented Nov 6, 2025

Thank you!
I definitely want to support this, but since the target is just a single method, instead of DynamicallyAccessedMemberTypes, how about specifying it like this:

            var docId = dynamicDependencyMethod.GetDocumentationCommentId();
            var parametersPart = docId!.Substring(docId.IndexOf('('));
            var memberSignature = dynamicDependencyMethod.Name + parametersPart;

            var containingType = dynamicDependencyMethod.ContainingType.ToDisplayString(DynamicDependencyContainingTypeFormat);
            dynamicDependencyAttribute = $"[global::System.Diagnostics.CodeAnalysis.DynamicDependency(\"{memberSignature}\", typeof({containingType}))]";

@dusrdev
Copy link
Contributor Author

dusrdev commented Nov 6, 2025

Thanks, using GetDocumentationCommentId is actually cleaner and allowed me to also remove the static field.

@neuecc
Copy link
Member

neuecc commented Nov 7, 2025

thank you!

@neuecc neuecc merged commit c821763 into Cysharp:master Nov 7, 2025
1 check passed
@dusrdev
Copy link
Contributor Author

dusrdev commented Nov 7, 2025

My pleasure.

@neuecc Considering the scope of its use, I think I could improve PooledStringWriter, clean up the code a bit and also gain some perf, would you want me to tackle this next?

@neuecc
Copy link
Member

neuecc commented Nov 7, 2025

PooledStringWriter is unused code so no need to care.(I've just removed unused code in master branch)

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