Skip to content

Annotate ServiceContainer reflective surface for AOT (closes #254)#256

Merged
jeremydmiller merged 1 commit into
mainfrom
feature/aot-service-container-254
May 13, 2026
Merged

Annotate ServiceContainer reflective surface for AOT (closes #254)#256
jeremydmiller merged 1 commit into
mainfrom
feature/aot-service-container-254

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Fourth AOT-pillar slice (#213) — closes #254.

16 IL warnings per TFM in `ServiceContainer.cs` → 0, with one matching annotation pair on `IServiceContainer` to keep IL2046 quiet across the impl boundary.

Annotations

Member Annotation
`IServiceContainer.QuickBuild()` + impl `[RequiresUnreferencedCode]` + `[RequiresDynamicCode]` + `[DAM(PublicConstructors)] T`
`IServiceContainer.QuickBuild(Type concreteType)` + impl same shape, `[DAM(PublicConstructors)]` on `concreteType`
`ServiceContainer.BuildFromType(Type concreteType)` `[DAM(PublicConstructors)]` on `concreteType` (not on interface, so interface stays untouched)

Suppressed with justification

Method Why
`TryCreateConstructorFrames(IEnumerable)` IL2067 at the `ServiceDescriptor` self-binding for each `MethodCall.HandlerType`. Handler types reach this method from Wolverine's annotated `IHandlerDiscovery` surface; a missing ctor surfaces as the explicit `NotSupportedException` below, not a silent failure.
`findFamily(Type)` IL2067 at the auto-self-binding branch for public concrete types not explicitly registered. Reached either from `typeof(T)` (compiler-preserved) or from user registrations whose ctors the user must keep alive. `ConstructorPlan.TryBuildPlan` returns `InvalidPlan` on missing ctors.

Effect on the punch list

Before (after #247 + #250):  JasperFx total per TFM 188
After this PR:               JasperFx total per TFM 172  (-16)

Remaining slices:

Issue Area Warnings
#252 Core/Reflection 64
#253 Core/IoC 30
#255 CodeGeneration/Services 22
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, program exits 0

Closes #254.

🤖 Generated with Claude Code

Fourth AOT-pillar slice (#213). 16 IL warnings per TFM in
ServiceContainer.cs → 0, with one matching annotation pair on the
IServiceContainer interface to keep IL2046 quiet across the impl
boundary.

Annotations

  IServiceContainer + ServiceContainer
    QuickBuild<T>()           — [RequiresUnreferencedCode] +
                                [RequiresDynamicCode] on the methods +
                                [DAM(PublicConstructors)] on T
    QuickBuild(Type)          — same shape, [DAM(PublicConstructors)]
                                on concreteType
  ServiceContainer only
    BuildFromType(Type)       — [DAM(PublicConstructors)] on concreteType
                                (this method isn't on IServiceContainer,
                                so the interface stays untouched for it)

Suppressed with justification

  TryCreateConstructorFrames(IEnumerable<MethodCall>) — IL2067 at the
    ServiceDescriptor self-binding for each MethodCall.HandlerType.
    Handler types reach this method from Wolverine's annotated
    IHandlerDiscovery surface; a missing ctor surfaces as the explicit
    NotSupportedException below, not a silent failure.

  findFamily(Type) — IL2067 at the auto-self-binding branch for public
    concrete types not explicitly registered. Reached either from
    typeof(T) (compiler-preserved) or from user registrations whose
    ctors the user must keep alive. ConstructorPlan.TryBuildPlan
    returns InvalidPlan on missing ctors, so the failure mode is
    explicit, not silent.

Effect on the punch list

  JasperFx total per TFM: 188 → 172  (-16, ServiceContainer slice)

  Remaining: Core/Reflection 64, Core/IoC 30, CodeGeneration/Services 22,
  Core/TypeScanning 10, Snapshots 8, GeneratedType 8, JasperFxOptions 6,
  Model 4, Frames 4, et al.

Verification

  CoreTests       407/407 pass on net9.0 + net10.0
  SmokeTestAot    build clean, exits 0
@jeremydmiller jeremydmiller merged commit 5d72c5a into main May 13, 2026
1 check passed
thechucklingatom pushed a commit to thechucklingatom/jasperfx that referenced this pull request May 19, 2026
…asperFx#255)

Third AOT-pillar slice (JasperFx#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:
    JasperFx#252 Core/Reflection            64
    JasperFx#253 Core/IoC                   30
    JasperFx#254 ServiceContainer.cs        16   (PR JasperFx#256 in flight)
    Core/TypeScanning               10
    CodeGeneration/Snapshots         8
    CodeGeneration/GeneratedType.cs  8
    JasperFxOptions.cs               6
    misc tail                       ~24

Overlap with JasperFx#254

  This PR adds [UnconditionalSuppressMessage] for IL2026 + IL3050 to
  ServiceContainer.findFamily. PR JasperFx#256 (closes JasperFx#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
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 Core/Reflection reflective surface

1 participant