Skip to content

feat(AOT): Mark RequiresDynamicCode / RequiresUnreferencedCode#314

Merged
bartelink merged 3 commits into
fsprojects:masterfrom
dimension-zero:pr/26-trim-aot-readiness
Jun 8, 2026
Merged

feat(AOT): Mark RequiresDynamicCode / RequiresUnreferencedCode#314
bartelink merged 3 commits into
fsprojects:masterfrom
dimension-zero:pr/26-trim-aot-readiness

Conversation

@dimension-zero

Copy link
Copy Markdown
Contributor

feat(AOT): Annotate Create with RequiresDynamicCode / RequiresUnreferencedCode

  • New file TrimAnnotations.fs: internal polyfills in
    System.Diagnostics.CodeAnalysis for RequiresDynamicCodeAttribute and
    RequiresUnreferencedCodeAttribute. The trimmer and AOT compiler
    resolve attributes by full type name, so internal copies in this
    assembly are picked up. Guarded with #if !NET7_0_OR_GREATER so a
    future multi-target picks up the real BCL attributes.
  • ArgumentParser.fs:
    • Add module TrimMessages with constant explanatory strings.
    • Annotate ArgumentParser.Create<'Template> with both attributes so
      AOT-publish consumers see IL3050 and trim-publish consumers see
      IL2026, each with a clear message pointing at the underlying
      reflection use (MakeGenericType / Activator / FSharpType).
  • Argu.fsproj: set IsTrimmable=true and IsAotCompatible=false guarded
    with MSBuild IsTargetFrameworkCompatible('net8.0') so netstandard2.0
    builds still succeed without NETSDK1212 noise. A future multi-target
    pass to net8.0 will activate these flags automatically.

Pure metadata addition. No runtime impact. All 112 tests pass.


Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds trim/AOT diagnostic metadata to Argu’s parser creation path so consumers publishing with trimming or Native AOT get clearer warnings about reflection-heavy schema generation.

Changes:

  • Adds internal polyfills for RequiresDynamicCodeAttribute and RequiresUnreferencedCodeAttribute.
  • Adds trim/AOT warning messages and annotates ArgumentParser.Create<'Template>.
  • Adds conditional IsTrimmable / IsAotCompatible project properties for future net8.0-compatible targets.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
src/Argu/TrimAnnotations.fs Defines internal diagnostic attribute polyfills for pre-.NET 7 targets.
src/Argu/ArgumentParser.fs Adds trim/AOT messages and applies attributes to the static parser factory.
src/Argu/Argu.fsproj Includes the new source file and adds conditional trim/AOT metadata properties.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Argu/ArgumentParser.fs
@dimension-zero

Copy link
Copy Markdown
Contributor Author

@bartelink @Copilot — both review points addressed in e07cd21.

API surface coverage

You and Copilot were right: Create was not the only schema-building entry point. I walked the public surface and there are three classes of API:

Schema-building entry points (must be annotated):

  • ArgumentParser.Create<'T>(...) — already annotated
  • new ArgumentParser<'T>(?programName, ...) — public primary ctor; now annotated
  • ArgumentParser<'T>.CheckStructure() — forces argInfoWithCheck.Value; now annotated
  • toParseResults — calls Create; now annotated
  • tagOf — calls Create; now annotated

Internal pass-through (no fresh reflection — argInfo is already in hand):

  • ArgumentParser<'T>.GetSubCommandParser — uses the internal (argInfo, ...) ctor
  • ArgumentParser.GetSubCommandParsers — same
  • ParseResults<'T>.Parser extension getter — same

The latter group doesn't need annotating: they take an already-computed UnionArgInfo and forward it through the internal constructor. The lazy argInfoNoCheck / argInfoWithCheck on ArgumentParser<'T> are only forced from the five APIs in the first group, so those five are the exhaustive entry-point set.

Build-time validation ("how would we know if new code needs annotations")

This is the right question and the answer is: the trim/AOT analysers themselves, provided IsTrimmable=true / IsAotCompatible=false are active on the assembly's compile.

That's what Argu.fsproj already does, guarded behind IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'). Today the project still targets netstandard2.0 so the guards are never live — but the day someone adds a net8.0 (or later) target, the SDK runs the trim and AOT analysers and emits IL2026 / IL3050 warnings on every unannotated reflection-using API. New code that adds a reflection path without an opt-out attribute will fail the build (warnings as errors is one PR away).

So the path to a hard build-time gate is:

  1. Add net8.0 (or current LTS) to TargetFrameworks in Argu.fsproj
  2. Set TreatWarningsAsErrors=true or specifically WarningsAsErrors=IL2026;IL3050

I deliberately didn't bundle that into this PR — it changes the publish surface and warrants its own discussion. Happy to open a follow-up issue for it (a "validate AOT/trim coverage in CI" tracker), or fold it into the V7-shaped breaking-change window you mentioned on #317. Just say which you'd prefer.

The polyfill RequiresDynamicCodeAttribute / RequiresUnreferencedCodeAttribute types in TrimAnnotations.fs are guarded with #if !NET7_0_OR_GREATER, so they drop out automatically once a net7.0+ target is added — no double-definition risk.

CI green; all 162 tests pass.

@bartelink bartelink left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems reasonable to merge at this point - I guess my main ask/concern/risk I'm flagging is that we merge it and then don't get to the full delivery of the overall feature. So, let me know if/when you'd like it merged and I'll do so.

@dimension-zero

Copy link
Copy Markdown
Contributor Author

Very gracious of you to ask for my appro - but it's your repo, so please do as you will!

dimension-zero and others added 3 commits June 8, 2026 10:04
…encedCode

* New file TrimAnnotations.fs: internal polyfills in
  System.Diagnostics.CodeAnalysis for RequiresDynamicCodeAttribute and
  RequiresUnreferencedCodeAttribute. The trimmer and AOT compiler
  resolve attributes by full type name, so internal copies in this
  assembly are picked up. Guarded with #if !NET7_0_OR_GREATER so a
  future multi-target picks up the real BCL attributes.
* ArgumentParser.fs:
  - Add module TrimMessages with constant explanatory strings.
  - Annotate ArgumentParser.Create<'Template> with both attributes so
    AOT-publish consumers see IL3050 and trim-publish consumers see
    IL2026, each with a clear message pointing at the underlying
    reflection use (MakeGenericType / Activator / FSharpType).
* Argu.fsproj: set IsTrimmable=true and IsAotCompatible=false guarded
  with MSBuild IsTargetFrameworkCompatible('net8.0') so netstandard2.0
  builds still succeed without NETSDK1212 noise. A future multi-target
  pass to net8.0 will activate these flags automatically.

Pure metadata addition. No runtime impact. All 112 tests pass.

# Conflicts:
#	src/Argu/Argu.fsproj
#	src/Argu/ArgumentParser.fs
…y points

Addresses review feedback that the warnings only covered ArgumentParser.Create
while several other public APIs also enter the reflection schema-building path
(preComputeUnionArgInfo / checkUnionArgInfo via the static lazies on
ArgumentParser<'T>).

Newly annotated public entry points:
- new ArgumentParser<'T>(?programName, ?helpTextMessage, ?usageStringCharacterWidth,
  ?errorHandler, ?checkStructure) — the primary public constructor
- ArgumentParser<'T>.CheckStructure() — forces argInfoWithCheck.Value
- toParseResults — top-level helper that goes through Create
- tagOf — top-level helper that goes through Create

Schema work that goes through the internal (argInfo, ...) constructor
(GetSubCommandParser, GetSubCommandParsers, ParseResults.Parser) does not
trigger fresh reflection and so does not need annotating: it reuses an
argInfo already produced behind one of the annotated entry points.

No behaviour change. All 162 tests pass.
@bartelink bartelink force-pushed the pr/26-trim-aot-readiness branch from e07cd21 to cbf001f Compare June 8, 2026 09:07
@bartelink bartelink changed the title feat(AOT): Annotate Create with RequiresDynamicCode / RequiresUnreferencedCode feat(AOT): Mark RequiresDynamicCode / RequiresUnreferencedCode Jun 8, 2026
@bartelink bartelink merged commit 0ae01cf into fsprojects:master Jun 8, 2026
4 checks passed
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.

3 participants