From a698af00dc1dc1d98fe0f8ce2450963862b72b1c Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Tue, 12 May 2026 20:30:41 -0500 Subject: [PATCH] Annotate ServiceContainer reflective surface for AOT (closes #254) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fourth AOT-pillar slice (jasperfx#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() — [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) — 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 --- src/JasperFx/IServiceContainer.cs | 11 ++++++++--- src/JasperFx/ServiceContainer.cs | 19 ++++++++++++++----- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/src/JasperFx/IServiceContainer.cs b/src/JasperFx/IServiceContainer.cs index fa21cba5..c554066b 100644 --- a/src/JasperFx/IServiceContainer.cs +++ b/src/JasperFx/IServiceContainer.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using JasperFx.CodeGeneration.Frames; using Microsoft.Extensions.DependencyInjection; @@ -25,7 +26,9 @@ public interface IServiceContainer /// /// /// - T QuickBuild(); + [RequiresUnreferencedCode("QuickBuild reflects over T's public constructors and resolves [FromKeyedServices] parameters by closing IFinder via CloseAndBuildAs.")] + [RequiresDynamicCode("CloseAndBuildAs uses MakeGenericType + Activator.CreateInstance on IFinder.")] + T QuickBuild<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(); /// /// Polyfill to make IServiceProvider work like Lamar's ability @@ -34,8 +37,10 @@ public interface IServiceContainer /// /// /// - object QuickBuild(Type concreteType); - + [RequiresUnreferencedCode("QuickBuild reflects over concreteType's public constructors and resolves [FromKeyedServices] parameters by closing IFinder via CloseAndBuildAs.")] + [RequiresDynamicCode("CloseAndBuildAs uses MakeGenericType + Activator.CreateInstance on IFinder.")] + object QuickBuild([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type concreteType); + IServiceProvider Services { get; } T GetInstance(); IReadOnlyList GetAllInstances(); diff --git a/src/JasperFx/ServiceContainer.cs b/src/JasperFx/ServiceContainer.cs index 666f1169..1c19c40a 100644 --- a/src/JasperFx/ServiceContainer.cs +++ b/src/JasperFx/ServiceContainer.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using ImTools; using JasperFx.CodeGeneration.Frames; using JasperFx.CodeGeneration.Services; @@ -74,6 +75,8 @@ public bool HasRegistrationFor() return DefaultFor(typeof(T)); } + [UnconditionalSuppressMessage("Trimming", "IL2067:DynamicallyAccessedMembers", + Justification = "Builds a transient self-binding ServiceDescriptor for each MethodCall.HandlerType so Wolverine can wire constructor dependencies into generated code. Handler types are reached from Wolverine's IHandlerDiscovery surface, which is itself an annotated reflective entry point — handler types preserve their public constructors transitively, and a missing public ctor surfaces as the explicit NotSupportedException below rather than as a silent runtime failure.")] public IEnumerable TryCreateConstructorFrames(IEnumerable calls) { if (calls.All(x => x.Method.IsStatic)) return new List(); @@ -206,6 +209,8 @@ public static bool IsEnumerable(Type type) return false; } + [UnconditionalSuppressMessage("Trimming", "IL2067:DynamicallyAccessedMembers", + Justification = "The IsPublic + IsConcrete branch below auto-registers a self-binding ServiceDescriptor for public concrete types that weren't explicitly registered. Public concrete types reached via the family lookup either come from typeof(T) (compiler-preserved) or from user-supplied registrations whose constructors the user must keep alive. A missing public ctor at activation time surfaces from ConstructorPlan.TryBuildPlan as an InvalidPlan, not a silent failure.")] private ServiceFamily findFamily(Type serviceType) { if (_families.TryFind(serviceType, out var family)) return family; @@ -287,7 +292,7 @@ public IReadOnlyList FindAll(Type serviceType, List PlanFor(descriptor, trail)).ToArray(); } - public object BuildFromType(Type concreteType) + public object BuildFromType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type concreteType) { var constructor = concreteType.GetConstructors().Single(); var dependencies = constructor.GetParameters().Select(x => _provider.GetService(x.ParameterType)).ToArray(); @@ -306,11 +311,13 @@ public bool HasMultiplesOf(Type variableType) /// /// /// - public T QuickBuild() + [RequiresUnreferencedCode("QuickBuild reflects over T's public constructors and resolves [FromKeyedServices] parameters by closing IFinder via CloseAndBuildAs. The trimmer may remove types reached only through that closure.")] + [RequiresDynamicCode("CloseAndBuildAs uses MakeGenericType + Activator.CreateInstance on IFinder.")] + public T QuickBuild<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>() { return (T)QuickBuild(typeof(T)); } - + /// /// Polyfill to make IServiceProvider work like Lamar's ability /// to create unknown concrete types @@ -318,7 +325,9 @@ public T QuickBuild() /// /// /// - public object QuickBuild(Type concreteType) + [RequiresUnreferencedCode("QuickBuild reflects over concreteType's public constructors and resolves [FromKeyedServices] parameters by closing IFinder via CloseAndBuildAs. The trimmer may remove types reached only through that closure.")] + [RequiresDynamicCode("CloseAndBuildAs uses MakeGenericType + Activator.CreateInstance on IFinder.")] + public object QuickBuild([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type concreteType) { var constructor = concreteType.GetConstructors().Single(); var args = constructor @@ -329,7 +338,7 @@ public object QuickBuild(Type concreteType) { return typeof(IFinder<>).CloseAndBuildAs(x.ParameterType).Find(_provider, att.Key.ToString()); } - + return _provider.GetService(x.ParameterType); }) .ToArray();