Skip to content

fix(stjm): surface actionable trimming and NativeAOT warnings at public entry points #256

Closed

Description

@egil

Supersedes #210.

Users should learn that their configuration is unsupported from the build, with enough information in the diagnostic to fix it. They should not have to find and read the AOT recipe before discovering that migration can fail after publishing.

Today, AddJsonMigrationSupport() enables reflection-based discovery and runtime generic construction. A source-generated JsonSerializerContext supplies JSON metadata, but does not remove those migration requirements. Even RegisterMigrator<TSource, TTarget, TMigrator>() currently enters a reflection-based invoker factory.

Scope

Make the current compatibility boundary explicit through standard .NET annotations and actionable consumer diagnostics. Keep documentation as supporting detail. A genuinely AOT-safe registration API is follow-up work, not a prerequisite for this fix.

Public diagnostics

  • Annotate both public AddJsonMigrationSupport() overloads with RequiresUnreferencedCode and RequiresDynamicCode.
  • Audit the public builder registration methods and other entry points, and propagate the appropriate requirements from their implementations. Annotating only internal helpers is insufficient.
  • Make warning messages explain the unsupported behavior and the supported action without requiring a link. Include a URL for optional context.
  • Explain that a source-generated JsonSerializerContext does not make migration discovery AOT-safe.

Suggested messages, to refine against the implementation:

[RequiresUnreferencedCode(
    "Migration discovery requires members that trimming may remove. " +
    "Publish this application with PublishTrimmed=false and PublishAot=false. " +
    "A source-generated JsonSerializerContext does not remove this requirement.")]
[RequiresDynamicCode(
    "Migration creates generic converters and invokers at runtime. " +
    "Publish this application with PublishAot=false.")]

RequiresUnreferencedCode produces IL2026 under trim analysis. RequiresDynamicCode produces IL3050 under AOT analysis. PublishTrimmed alone must not be described as enabling both diagnostics. Ordinary builds without the relevant analyzers are not guaranteed to report these warnings.

Internal analysis

  • Enable EnableTrimAnalyzer and EnableAotAnalyzer for the library's supported target frameworks.
  • Audit the current implementation rather than copying the old issue's symbol list. At the time of review, generic converter construction lives in JsonMigrationTypeInfoResolver.CreateMigrationTypeInfo, and invoker construction lives in MigratorInvokerFactory.
  • Include assembly scanning, external migrator activation, static contract discovery, reflection fallback, and .NET 11 union paths in the audit.
  • Use DynamicallyAccessedMembers where a statically identifiable type can carry precise member-preservation requirements. Use DynamicDependency only for specific dependencies that analysis cannot infer. Neither attribute makes arbitrary runtime generic construction AOT-safe.
  • Respect interface and override annotation contracts. Any suppression must be narrow, justified, and covered by evidence that the unsupported path has an appropriate public warning. Do not silence warnings globally.
  • Do not introduce IsTrimmable or IsAotCompatible as a substitute for enabling analysis or proving compatibility.

Acceptance criteria

  • A consumer configured for trimming receives IL2026 at each AddJsonMigrationSupport() overload, with the actionable library message.
  • A consumer configured for NativeAOT receives IL3050 at each overload, with the actionable library message, alongside applicable trim diagnostics.
  • The same diagnostics appear when the consumer supplies source-generated JSON metadata and when it uses the existing generic migrator registration API.
  • Checks assert diagnostic ID, consumer source location, and useful message content, rather than merely checking attribute presence.
  • Validate through a packed-package consumer and publish analysis, so the test covers the annotations consumers actually receive. Cover both target frameworks, including the .NET 11-specific public surface.
  • Existing supported untrimmed migration behavior remains unchanged and its tests pass.
  • Library analysis has no unexplained warnings or broad suppressions.
  • The AOT recipe describes the diagnostics and current supported configuration, links this issue, and does not imply that JSON source generation establishes NativeAOT support.

Follow-up: a supported NativeAOT path

Preserve the longer-term goal from #210 as separately scoped work:

  • Provide explicit registration that directly constructs closed generic converters and invokes migration contracts, without entering runtime discovery or generic factories on that path.
  • Define how explicit and discovered registrations interact; do not accidentally fall back to reflection in an AOT-safe configuration.
  • Preserve the existing service-provider activation behavior when designing external migrator registration; do not assume every DI-created migrator can have a new() constraint.
  • Keep warnings on APIs that remain unsafe, and update their messages to name the safe alternative once it exists.
  • Publish and run a NativeAOT sample in CI, exercising old-payload migration, static and external migrators, reference and value types, and the relevant union behavior before claiming support.

No new registration API or source generator is required to complete this issue.

References

Activity

  1. added
    stjmEgil.SystemTextJson.Migration scope
    on Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    stjmEgil.SystemTextJson.Migration scope

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions