Skip to content

Annotate CodeGeneration/Services reflective surface for AOT (closes #255)#257

Merged
jeremydmiller merged 2 commits into
mainfrom
feature/aot-codegen-services-255
May 13, 2026
Merged

Annotate CodeGeneration/Services reflective surface for AOT (closes #255)#257
jeremydmiller merged 2 commits into
mainfrom
feature/aot-codegen-services-255

Conversation

@jeremydmiller
Copy link
Copy Markdown
Member

Third AOT-pillar slice (#213) — closes #255.

22 IL warnings per TFM across 4 files in CodeGeneration/Services0, plus one small ripple suppression at ServiceContainer.findFamily for the open-generic call into the now-annotated ServiceFamily.Close.

Annotations

Method Annotation
ConstructorPlan.FindPublicConstructorCandidates(Type implementationType) [DAM(PublicConstructors)] on implementationType — satisfies IL2070 at the GetConstructors() call. The call site in TryBuildPlan passes descriptor.ImplementationType / KeyedImplementationType, which already carry the matching DAM from MS DI.
ServiceFamily.Close(Type[] parameterTypes) [RequiresUnreferencedCode] + [RequiresDynamicCode] — open-generic close uses MakeGenericType on ServiceType + each ImplementationType. Honest characterization.

Suppressed with justification

Site Why
ServiceProviderFamily.BuildDefaultPlan / ServiceScopeFactoryFamily.BuildDefaultPlan (IL2111) The placeholder typeof(ServiceDescriptor) is never actually instantiated — ServiceProviderPlan.CreateVariable throws NotImplementedException; the real IServiceProvider / IServiceScopeFactory comes from the host's DI.
ArrayFamily(Type serviceType) (IL2067) The placeholder ServiceDescriptor for collection-shaped types — no constructor is ever invoked, CreateArrayFrame emits new T[]{…} source literal.
ServiceContainer.findFamily(Type) (IL2026 + IL3050) Inherited from the annotated Close() call at the open-generic close path. Open-generic registration is itself a dynamic-code feature; AOT-publishing apps avoid it.

Effect on the punch list

Before:  JasperFx total per TFM  188
After:   JasperFx total per TFM  166  (-22, this slice)

Remaining slices: #252 Core/Reflection (64), #253 Core/IoC (30), #254 ServiceContainer.cs (16, PR #256 in flight), plus smaller tails.

Overlap with #254

This PR adds an [UnconditionalSuppressMessage] for IL2026 + IL3050 to ServiceContainer.findFamily. PR #256 (closes #254) adds a separate [UnconditionalSuppressMessage] for IL2067 to the same method. Whichever PR merges second will need a one-attribute textual rebase — the attributes themselves are non-conflicting.

Verification

  • CoreTests — 407/407 pass on net9.0 + net10.0
  • SmokeTestAot — build clean, exits 0

Closes #255.

🤖 Generated with Claude Code

)

Third AOT-pillar slice (#213). 22 IL warnings per TFM across
4 files in CodeGeneration/Services → 0, plus one small ripple
suppression at ServiceContainer.findFamily's open-generic call into
ServiceFamily.Close.

Annotations

  ConstructorPlan.FindPublicConstructorCandidates(Type implementationType)
    [DynamicallyAccessedMembers(PublicConstructors)] on implementationType
    Satisfies IL2070 at the GetConstructors() call. Call site in
    TryBuildPlan passes descriptor.ImplementationType / KeyedImplementationType
    which already carry the matching DAM from MS DI.

  ServiceFamily.Close(Type[] parameterTypes)
    [RequiresUnreferencedCode] + [RequiresDynamicCode]
    Honest characterization: open-generic close uses MakeGenericType on
    ServiceType + each ImplementationType. AOT-publishing apps should
    register closed generics or use source-generated registration.

Suppressed with justification

  ServiceProviderFamily.BuildDefaultPlan / ServiceScopeFactoryFamily.BuildDefaultPlan
    IL2111 at `new ServiceDescriptor(typeof(IServiceProvider), typeof(ServiceDescriptor), ...)`
    The placeholder typeof(ServiceDescriptor) is never actually instantiated —
    ServiceProviderPlan.CreateVariable throws NotImplementedException, the
    real IServiceProvider/IServiceScopeFactory comes from the host's DI.

  ArrayFamily(Type serviceType)
    IL2067 at the placeholder ServiceDescriptor for collection-shaped types
    (T[] / IEnumerable<T> / IList<T> / IReadOnlyList<T>). No constructor
    is ever invoked — CreateArrayFrame emits `new T[]{...}` source literal.

  ServiceContainer.findFamily(Type) — IL2026 + IL3050
    Inherited from the now-annotated Close() call at line 241 (open-generic
    close path). Open-generic registration is itself a dynamic-code feature;
    AOT-publishing apps avoid it.

Effect on the punch list

  JasperFx total per TFM: 188 → 166  (-22, CodeGeneration/Services slice)

  Remaining slices:
    #252 Core/Reflection            64
    #253 Core/IoC                   30
    #254 ServiceContainer.cs        16   (PR #256 in flight)
    Core/TypeScanning               10
    CodeGeneration/Snapshots         8
    CodeGeneration/GeneratedType.cs  8
    JasperFxOptions.cs               6
    misc tail                       ~24

Overlap with #254

  This PR adds [UnconditionalSuppressMessage] for IL2026 + IL3050 to
  ServiceContainer.findFamily. PR #256 (closes #254) adds a separate
  [UnconditionalSuppressMessage] for IL2067 to the same method. Whichever
  PR merges second will need a one-attribute textual rebase — the
  attributes themselves are non-conflicting.

Verification

  CoreTests       407/407 pass on net9.0 + net10.0
  SmokeTestAot    build clean, exits 0
@jeremydmiller jeremydmiller merged commit df6741e into main May 13, 2026
1 check passed
thechucklingatom pushed a commit to thechucklingatom/jasperfx that referenced this pull request May 19, 2026
Second AOT-pillar slice (JasperFx#213). 30 IL warnings per TFM across
7 files in Core/IoC → 0, plus matching propagation to AssemblyScanner /
ScanningExploder / ServiceCollectionExtensions.Scan to keep IL2026 quiet
across the public convention-registration surface.

Convention-scanning is fundamentally trim-hostile: TypeSet enumerates
types from loaded assemblies, conventions register types by naming /
interface patterns, and ServiceDescriptors are constructed from runtime-
discovered Types. AOT-publishing apps should either avoid convention-based
registration entirely (use explicit services.AddSingleton<T, TImpl>())
or substitute a source-generated registration manifest.

Public surface annotated

  IRegistrationConvention.ScanTypes               — [RUC] + [RDC]
  ServiceCollectionExtensions.Scan(IServiceCollection, Action<IAssemblyScanner>)
                                                  — [RUC] + [RDC]
  ServiceCollectionExtensions.AddType(IServiceCollection, Type, Type, ServiceLifetime)
                                                  — [DAM(PublicConstructors)] on implementationType
  AssemblyScanner.ApplyRegistrations              — [RUC] + [RDC]
  FindAllTypesFilter.IRegistrationConvention.ScanTypes
                                                  — [RUC] + [RDC] (matches interface)

Implementations annotated (match interface)

  DefaultConventionScanner.ScanTypes              — [RUC] + [RDC]
  FirstInterfaceConvention.ScanTypes              — [RUC] + [RDC]
  ImplementationMap.ScanTypes                     — [RUC] + [RDC]
  GenericConnectionScanner.ScanTypes              — [RUC] + [RDC]

Internal helpers annotated

  TypeExtensions.CanBeCreated(this Type)          — [DAM(PublicConstructors)]
  TypeExtensions.FindFirstInterfaceThatCloses(this Type, Type)
                                                  — [DAM(Interfaces)]
  TypeExtensions.FindInterfacesThatClose(this Type, Type)
                                                  — [DAM(Interfaces)]

Internal propagation

  ScanningExploder.Explode + ExplodeSynchronously — [RUC] + [RDC]

Suppressed with justification

  DefaultConventionScanner.FindServiceType — IL2070 on GetInterfaces()
  GenericConnectionScanner.addConcretionsThatCouldBeClosed — IL2055/IL2067/IL3050 on MakeGenericType + ServiceDescriptor
  FindAllTypesFilter.Matches + determineLeastSpecificButValidType — IL2070/IL2067
  TypeExtensions.rawFindInterfacesThatCloses — IL2070 on recursive BaseType walk

Effect on the punch list

  JasperFx total per TFM: 188 → 158  (-30, Core/IoC slice)

  Remaining slices:
    JasperFx#252 Core/Reflection            64
    JasperFx#254 ServiceContainer.cs        16   (PR JasperFx#256 in flight)
    JasperFx#255 CodeGeneration/Services    22   (PR JasperFx#257 in flight)
    Core/TypeScanning               10
    CodeGeneration/Snapshots         8
    CodeGeneration/GeneratedType.cs  8
    JasperFxOptions.cs               6
    misc tail                       ~24

Verification

  CoreTests       407/407 pass on net9.0 + net10.0
  SmokeTestAot    build clean, exits 0

Closes JasperFx#253.
thechucklingatom pushed a commit to thechucklingatom/jasperfx that referenced this pull request May 19, 2026
)

Largest AOT-pillar slice (JasperFx#213). 64 IL warnings per TFM across
6 files in Core/Reflection → 0, plus propagated annotations to keep
EnumerableTypeExtensions clean.

GenericFactoryCache.cs

  All 8 BuildAs<T> overloads carry [RequiresDynamicCode] from PR JasperFx#191.
  Adds matching [UnconditionalSuppressMessage("Trimming", "IL2055")]
  to suppress the MakeGenericType-in-lambda warnings — the method-level
  [RequiresDynamicCode] already documents the contract; source-generated
  callers supply an AOT-safe factoryFactory and never reach MakeGenericType.

TypeExtensions.cs

  [DAM(Interfaces)]  on ImplementsInterfaceTemplate, FindInterfaceThatCloses,
                     Closes, FindParameterTypeTo, IsAnEnumerationOf
  [DAM(PublicConstructors)] on IsConcreteWithDefaultCtor
  [DAM(PublicParameterlessConstructor)] on Create<T>(), Create
  [UnconditionalSuppressMessage(IL3050)] on IsGenericEnumerable
                     (closes well-known IEnumerable<T> only)
  [UnconditionalSuppressMessage(IL2072)] on Closes (recursive interface walk)

ReflectionExtensions.cs

  [DAM(PublicConstructors | NonPublicConstructors)] on HasDefaultConstructor,
                     HasConstructorsWithArguments
  [DAM(PublicConstructors)] on TryFindConstructor
  [DAM(PublicMethods | NonPublicMethods)] on TryFindMethod, TryFindStaticMethod
  [UnconditionalSuppressMessage(IL2072)] on IsAsync
                     (checks well-known Task/ValueTask types)

EnumerableTypeExtensions.cs

  [DAM(Interfaces)] on IsEnumerable (propagated from TypeExtensions.Closes)

LambdaBuilder.cs

  [RequiresUnreferencedCode] on every public method:
    GetProperty, SetProperty, GetField, SetField, Getter, Setter
  All compile expression trees via FastExpressionCompiler (itself RUC-annotated).
  Honest characterization: this whole class is for runtime expression
  compilation; AOT consumers should source-generate accessor delegates.

ValueTypeInfo.cs

  [RequiresUnreferencedCode] on public ForType(Type), CreateWrapper<TOuter,TInner>,
                                    UnWrapper<TOuter,TInner>
  [DAM(PublicProperties | PublicConstructors | PublicMethods)] on ForType(Type)
  Strong-typed-id value-type discovery + Expression compilation; both
  fundamentally trim-hostile.

ReflectionHelper.cs

  [DAM(PublicParameterlessConstructor)] on MeetsSpecialGenericConstraints
  [UnconditionalSuppressMessage(IL2026/IL3050)] on VisitNew / VisitNewArray
                     (visits existing expression trees; trim invariant
                     owned by whoever built the input)

Effect on the punch list

  JasperFx total per TFM: 188 → 140  (-48)

  The slice's direct 64-warning count was higher than the net delta because
  some warnings propagated up cascade chains (ReflectionExtensions inherited
  4 from TypeExtensions DAM annotations, etc.) — each propagation got its
  own annotation, so the net result is the same 0 in the slice plus the
  surrounding cleanup.

  Remaining (all pre-existing, not introduced by this PR):
    JasperFx#254 ServiceContainer.cs            16  (PR JasperFx#256 in flight)
    JasperFx#255 CodeGeneration/Services        22  (PR JasperFx#257 in flight) — already
                                              addressed by ripple suppression
    Core/TypeScanning                   10
    CodeGeneration/Snapshots             8
    CodeGeneration/GeneratedType.cs      8
    JasperFxOptions.cs                   6
    misc tail                           ~24

Verification

  CoreTests       407/407 pass on net9.0 + net10.0
  SmokeTestAot    build clean, exits 0

Closes JasperFx#252.
thechucklingatom pushed a commit to thechucklingatom/jasperfx that referenced this pull request May 19, 2026
Final AOT-pillar cleanup pass for JasperFx (JasperFx#213). After PRs
JasperFx#256JasperFx#259 closed the four largest slices (ServiceContainer,
CodeGeneration/Services, Core/IoC, Core/Reflection), 74 warnings per
TFM remained across the long tail. This PR brings the JasperFx project
to **0 IL warnings** with `IsAotCompatible=true` enabled.

Annotated reflective entry points

  AssemblyFinder.FindAssemblies (3 overloads)   — [RUC]
  IJasperFxAssemblyLoadContext + impl            — [RUC]
  AssemblyTypes(Assembly)                        — [RUC]
  TypeRepository.ForAssembly / FindTypes (3x)    — [RUC]
  TypeQuery.Find(IEnumerable<Assembly>)          — [RUC]
  AssemblyScanner.Start + assembly-scan APIs     — [RUC] (matches
                                                   IAssemblyScanner)
  IAssemblyScanner.AssembliesFrom*               — [RUC] on interface
  CommandLineHostingExtensions.ApplyJasperFx*    — already RUC; cascades
  JasperFxOptions.HasReferenceToJasperFxTool     — [RUC]
  JasperFxOptions.DetermineCallingAssembly       — [RUC]
  JasperFxOptions.establishApplicationAssembly   — [RUC]
  JasperFxOptions.ReadHostEnvironment            — [RUC]
  JasperFxServiceCollectionExtensions.AddJasperFx + CritterStackDefaults
                                                  — [RUC]
  EnvironmentCheckExtensions (5 overloads)       — [RUC]
  SnapshotGate.Read / SnapshotGate.Write         — [RUC] + [RDC]
                                                   (STJ JsonSerializer)
  ISystemPart.WriteToConsole                     — [RUC] on interface
  DescribeCommand.Execute                        — suppress + #pragma
                                                   on async-state-machine
                                                   WriteToConsole call
  CodeGeneration/Frames/MethodCall(Type,string)  — [RUC] + [DAM(PublicMethods)]
  CodeGeneration/Frames/MethodCall.correctedReturnType — suppress IL2067
                                                   (well-known Task types)
  CodeGeneration/Frames/MethodCall.returnsValueTask — suppress IL2072
  CodeGeneration/GeneratedAssembly.AddType       — [DAM(PublicCtors|PublicMethods|NonPublicMethods)]
  CodeGeneration/GeneratedAssembly.AttachAssembly — [RUC]
  CodeGeneration/GeneratedType.CompiledType property — [DAM(PublicCtors)]
  CodeGeneration/GeneratedType.InheritsFrom<T>/<Type> — [DAM(PublicCtors|Methods)]
  CodeGeneration/GeneratedType.Implements<T>/(Type) — [DAM(PublicMethods)]
  CodeGeneration/GeneratedType.FindType + ApplySetterValues — [RUC]/suppress
  CodeGeneration/Model/Setter.SetInitialValue    — [RUC]
  CodeGeneration/Model/Variable.VariablesForProperties<T> — [DAM(PublicProperties)]
  CodeGeneration/Model/Variable.DefaultArgName   — suppress (cosmetic)
  CodeGeneration/Expressions/LambdaDefinition.Compile<TFunc> — [RUC]
  ServiceCollectionServerVariableSource.Matches  — suppress (IVariableSource
                                                   contract doesn't carry DAM)
  CodeGeneration/CodeGenerationExtensions.BuildExportedTypeIndex — suppress
  CodeGeneration/Services findFamily             — re-applied IL2067 suppression
                                                   (was lost in JasperFx#256 + JasperFx#257 merge)
  ServiceContainer.CouldResolve(Type)            — [DAM(PublicCtors)]
  ServiceContainer.findFamily                    — re-applied IL2067 suppression
                                                   (was lost in PR JasperFx#256 + JasperFx#257
                                                   merge resolution)
  Descriptors/OptionsDescription                 — [RUC] on ctor / For() / readProperties
  Descriptors/DatabaseDescriptor ctors           — [RUC] (inherits OptionsDescription)
  CommandLine/Descriptions/ConfigurationPreview.WriteToConsole — [RUC]
  CommandLine/Descriptions/DescribeCommand.WriteToConsole (×2 overrides) — [RUC]
  CommandLine/CommandFactory.IsJasperFxCommandType — [DAM(Interfaces)]
  CommandLine/CommandFactory.TryRegisterFromGeneratedManifest — IL2072 suppress added
  Resources/ResourcesCommand.ExecuteOnEach        — suppress IL3050
                                                   (Spectre WriteException
                                                   on error-display path)
  JasperFxAssemblyAttribute ctor                 — [DAM(PublicCtors|NonPublicCtors)]

Effect on the punch list

  Before:  JasperFx total per TFM  74 warnings
  After:   JasperFx total per TFM   0 warnings

  Cumulative since JasperFx#213 flag-flip (PR JasperFx#247):
    236 (initial fallout) → 0  (all addressed)

Note on JasperFx.Events

  The propagation of new annotations into the JasperFx.Events compilation
  surfaces ~246 warnings per TFM that aren't addressed here. That deserves
  its own focused PR + likely an issue under JasperFx#213.

Verification

  CoreTests       407/407 pass on net9.0 + net10.0
  CommandLineTests 280/280 pass on net9.0 + net10.0
  CodegenTests    366/366 pass on net9.0 + net10.0
  SmokeTestAot    build clean, exits 0

Closes (most) the AOT pillar JasperFx#213 for JasperFx itself. Events follow-up
deferred to a separate PR.
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.

AOT pillar (#213): annotate ServiceContainer.cs closed-generic registration AOT pillar (#213): annotate Core/Reflection reflective surface

1 participant