From 821ff00b970dbb6eab710947ae6d74b7210dcb29 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 18 Jun 2026 15:29:41 -0700 Subject: [PATCH 01/14] Phase 1: Enable AOT trim warnings to determine how to proceed. --- src/Autofac/Autofac.csproj | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index 82bfccc20..9f8e6cd37 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -46,6 +46,19 @@ $(NoWarn);8600;8601;8602;8603;8604 + + + true + true + true + From 0faf2a23653b385f6819178566914a2f64b4906a Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 18 Jun 2026 15:43:32 -0700 Subject: [PATCH 02/14] Phase 2a: Hot path items annotated. --- .../ConcreteReflectionActivatorData.cs | 3 +- .../Builder/ReflectionActivatorData.cs | 5 +- src/Autofac/Builder/RegistrationBuilder.cs | 4 +- .../Reflection/ReflectionActivator.cs | 20 ++++- .../Pipeline/ResolvePipelineBuilder.cs | 5 ++ .../Diagnostics/DiagnosticSourceExtensions.cs | 42 ++++++++--- .../RegistrationExtensions.Composite.cs | 5 +- src/Autofac/RegistrationExtensions.cs | 4 +- src/Autofac/Util/ActivatorMemberTypes.cs | 45 +++++++++++ src/Autofac/Util/LinkerAttributes.cs | 75 ++++++++++++++++++- 10 files changed, 187 insertions(+), 21 deletions(-) create mode 100644 src/Autofac/Util/ActivatorMemberTypes.cs diff --git a/src/Autofac/Builder/ConcreteReflectionActivatorData.cs b/src/Autofac/Builder/ConcreteReflectionActivatorData.cs index 2966ad4d3..fcaac1492 100644 --- a/src/Autofac/Builder/ConcreteReflectionActivatorData.cs +++ b/src/Autofac/Builder/ConcreteReflectionActivatorData.cs @@ -3,6 +3,7 @@ using Autofac.Core; using Autofac.Core.Activators.Reflection; +using Autofac.Util; namespace Autofac.Builder; @@ -15,7 +16,7 @@ public class ConcreteReflectionActivatorData : ReflectionActivatorData, IConcret /// Initializes a new instance of the class. /// /// Type that will be activated. - public ConcreteReflectionActivatorData(Type implementer) + public ConcreteReflectionActivatorData([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer) : base(implementer) { } diff --git a/src/Autofac/Builder/ReflectionActivatorData.cs b/src/Autofac/Builder/ReflectionActivatorData.cs index 01b30d127..1c41ffcb8 100644 --- a/src/Autofac/Builder/ReflectionActivatorData.cs +++ b/src/Autofac/Builder/ReflectionActivatorData.cs @@ -3,6 +3,7 @@ using Autofac.Core; using Autofac.Core.Activators.Reflection; +using Autofac.Util; namespace Autofac.Builder; @@ -14,6 +15,7 @@ public class ReflectionActivatorData private static readonly IConstructorFinder _defaultConstructorFinder = new DefaultConstructorFinder(); private static readonly IConstructorSelector _defaultConstructorSelector = new MostParametersConstructorSelector(); + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] private Type _implementer = default!; private IConstructorFinder _constructorFinder; private IConstructorSelector _constructorSelector; @@ -22,7 +24,7 @@ public class ReflectionActivatorData /// Initializes a new instance of the class. /// /// Type that will be activated. - public ReflectionActivatorData(Type implementer) + public ReflectionActivatorData([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer) { ImplementationType = implementer; @@ -33,6 +35,7 @@ public ReflectionActivatorData(Type implementer) /// /// Gets or sets the implementation type. /// + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] public Type ImplementationType { get diff --git a/src/Autofac/Builder/RegistrationBuilder.cs b/src/Autofac/Builder/RegistrationBuilder.cs index abfa91a35..f9fca03cc 100644 --- a/src/Autofac/Builder/RegistrationBuilder.cs +++ b/src/Autofac/Builder/RegistrationBuilder.cs @@ -59,7 +59,7 @@ public static IRegistrationBuilder /// Implementation type to register. /// A registration builder. - public static IRegistrationBuilder ForType() + public static IRegistrationBuilder ForType<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TImplementer>() where TImplementer : notnull { // Open generics can't be generic type parameters so we don't have to check for that here. @@ -79,7 +79,7 @@ public static IRegistrationBuilder /// Implementation type to register. /// A registration builder. - public static IRegistrationBuilder ForType(Type implementationType) + public static IRegistrationBuilder ForType([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType) { if (implementationType is null) { diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs index 6916839f0..6814694f1 100644 --- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs +++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs @@ -15,6 +15,7 @@ namespace Autofac.Core.Activators.Reflection; /// public class ReflectionActivator : InstanceActivator, IInstanceActivator { + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] private readonly Type _implementationType; private readonly Parameter[] _configuredProperties; private readonly Parameter[] _defaultParameters; @@ -33,7 +34,7 @@ public class ReflectionActivator : InstanceActivator, IInstanceActivator /// Parameters configured explicitly for this instance. /// Properties configured explicitly for this instance. public ReflectionActivator( - Type implementationType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType, IConstructorFinder constructorFinder, IConstructorSelector constructorSelector, IEnumerable configuredParameters, @@ -51,9 +52,14 @@ public ReflectionActivator( } _implementationType = implementationType; + + // The cache key is the (already annotated) implementation type; the factory + // ignores the dictionary's unannotated key parameter and reads the annotated + // local instead, so the [DynamicallyAccessedMembers] contract flows correctly + // into UsesServiceKeyAttribute. _requiresServiceKeyParameter = ReflectionCacheSet.Shared.Internal.ServiceKeyUsageByType.GetOrAdd( _implementationType, - static t => UsesServiceKeyAttribute(t)); + _ => UsesServiceKeyAttribute(implementationType)); ConstructorFinder = constructorFinder ?? throw new ArgumentNullException(nameof(constructorFinder)); ConstructorSelector = constructorSelector ?? throw new ArgumentNullException(nameof(constructorSelector)); _configuredProperties = configuredProperties.ToArray(); @@ -123,7 +129,11 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic /// /// The type to inspect. /// if the type uses the ; otherwise, . - private static bool UsesServiceKeyAttribute(Type implementationType) + [UnconditionalSuppressMessage( + "Trimming", + "IL2070:UnrecognizedReflectionPattern", + Justification = "This is a best-effort scan for [ServiceKey] across all constructors and properties, including non-public ones. Autofac's trim/AOT contract preserves only public constructors and properties (see ActivatorMemberTypes); if a non-public member is trimmed away, it simply is not found here, which matches the documented behavior that non-public activation is not trim/AOT-safe. No public member that drives activation is missed.")] + private static bool UsesServiceKeyAttribute([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType) { // Intentionally not picky about _which_ constructor or property has the // attribute. If a different constructor is picked via constructor @@ -347,6 +357,10 @@ private bool HasAnyRequiredMembers() }); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2077:UnrecognizedReflectionPattern", + Justification = "GetRuntimeProperties enumerates non-public properties as well, but Autofac's trim/AOT contract preserves only public properties (see ActivatorMemberTypes). The injectable set is filtered down to required members and explicitly-configured properties; non-public properties that the trimmer removes are not part of the supported (public) property-injection surface.")] private void InitializeInjectablePropertySet() { if (!_anyRequiredMembers && _configuredProperties.Length == 0) diff --git a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs index afe84fd5e..b58ba64b8 100644 --- a/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs +++ b/src/Autofac/Core/Resolving/Pipeline/ResolvePipelineBuilder.cs @@ -390,9 +390,14 @@ private void VerifyPhase(PipelinePhase middlewarePhase) { static string DescribeValidEnumRange(PipelinePhase start, PipelinePhase end) { +#if NET5_0_OR_GREATER + var enumValues = Enum.GetValues() + .Where(value => value >= start && value <= end); +#else var enumValues = Enum.GetValues(typeof(PipelinePhase)) .Cast() .Where(value => value >= start && value <= end); +#endif return string.Join(", ", enumValues); } diff --git a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs index fd9ac4c01..64567e5ae 100644 --- a/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs +++ b/src/Autofac/Diagnostics/DiagnosticSourceExtensions.cs @@ -22,7 +22,7 @@ public static void MiddlewareStart(this DiagnosticListener diagnosticSource, Res { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareStart)) { - diagnosticSource.Write(DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware)); + Write(diagnosticSource, DiagnosticEventKeys.MiddlewareStart, new MiddlewareDiagnosticData(requestContext, middleware)); } } @@ -36,7 +36,7 @@ public static void MiddlewareFailure(this DiagnosticListener diagnosticSource, R { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareFailure)) { - diagnosticSource.Write(DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware)); + Write(diagnosticSource, DiagnosticEventKeys.MiddlewareFailure, new MiddlewareDiagnosticData(requestContext, middleware)); } } @@ -50,7 +50,7 @@ public static void MiddlewareSuccess(this DiagnosticListener diagnosticSource, R { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.MiddlewareSuccess)) { - diagnosticSource.Write(DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware)); + Write(diagnosticSource, DiagnosticEventKeys.MiddlewareSuccess, new MiddlewareDiagnosticData(requestContext, middleware)); } } @@ -64,7 +64,7 @@ public static void OperationStart(this DiagnosticListener diagnosticSource, IRes { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationStart)) { - diagnosticSource.Write(DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest)); + Write(diagnosticSource, DiagnosticEventKeys.OperationStart, new OperationStartDiagnosticData(operation, initiatingRequest)); } } @@ -78,7 +78,7 @@ public static void OperationFailure(this DiagnosticListener diagnosticSource, IR { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationFailure)) { - diagnosticSource.Write(DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException)); + Write(diagnosticSource, DiagnosticEventKeys.OperationFailure, new OperationFailureDiagnosticData(operation, operationException)); } } @@ -92,7 +92,7 @@ public static void OperationSuccess(this DiagnosticListener diagnosticSource, IR { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.OperationSuccess)) { - diagnosticSource.Write(DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance)); + Write(diagnosticSource, DiagnosticEventKeys.OperationSuccess, new OperationSuccessDiagnosticData(operation, resolvedInstance)); } } @@ -106,7 +106,7 @@ public static void RequestStart(this DiagnosticListener diagnosticSource, IResol { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestStart)) { - diagnosticSource.Write(DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext)); + Write(diagnosticSource, DiagnosticEventKeys.RequestStart, new RequestDiagnosticData(operation, requestContext)); } } @@ -121,7 +121,7 @@ public static void RequestFailure(this DiagnosticListener diagnosticSource, IRes { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestFailure)) { - diagnosticSource.Write(DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException)); + Write(diagnosticSource, DiagnosticEventKeys.RequestFailure, new RequestFailureDiagnosticData(operation, requestContext, requestException)); } } @@ -135,7 +135,31 @@ public static void RequestSuccess(this DiagnosticListener diagnosticSource, IRes { if (diagnosticSource.IsEnabled(DiagnosticEventKeys.RequestSuccess)) { - diagnosticSource.Write(DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext)); + Write(diagnosticSource, DiagnosticEventKeys.RequestSuccess, new RequestDiagnosticData(operation, requestContext)); } } + + /// + /// Centralizes the call so the + /// trimming/AOT suppression for it lives in exactly one place. + /// + /// + /// is annotated + /// [RequiresUnreferencedCode] because a listener may reflect over the + /// anonymously-typed payload. Autofac's diagnostics are opt-in: this code path only + /// runs when a caller has explicitly attached a + /// subscriber, which is a development/observability scenario rather than part of the + /// core resolve behavior. The payload types are concrete, internal + /// *DiagnosticData classes that are always referenced from this assembly, so they + /// are not trimmed away. Suppressing here keeps the resolve pipeline (which calls these + /// methods) free of trim warnings. + /// + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:RequiresUnreferencedCode", + Justification = "Diagnostics are opt-in and only run when a DiagnosticListener subscriber is attached. Payload types are concrete internal types referenced from this assembly and are not trimmed.")] + private static void Write(DiagnosticListener diagnosticSource, string name, object value) + { + diagnosticSource.Write(name, value); + } } diff --git a/src/Autofac/RegistrationExtensions.Composite.cs b/src/Autofac/RegistrationExtensions.Composite.cs index 75a2cf316..d84a635d0 100644 --- a/src/Autofac/RegistrationExtensions.Composite.cs +++ b/src/Autofac/RegistrationExtensions.Composite.cs @@ -5,6 +5,7 @@ using Autofac.Builder; using Autofac.Core; using Autofac.Core.Registration; +using Autofac.Util; namespace Autofac; @@ -31,7 +32,7 @@ public static partial class RegistrationExtensions /// Service type to provide a composite for. /// Container builder. /// The composite registration for continued configuration. - public static IRegistrationBuilder RegisterComposite(this ContainerBuilder builder) + public static IRegistrationBuilder RegisterComposite<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TComposite, TService>(this ContainerBuilder builder) where TComposite : notnull, TService where TService : notnull { @@ -66,7 +67,7 @@ public static IRegistrationBuilderThe composite registration for continued configuration. public static IRegistrationBuilder RegisterComposite( this ContainerBuilder builder, - Type compositeType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type compositeType, Type serviceType) { if (builder == null) diff --git a/src/Autofac/RegistrationExtensions.cs b/src/Autofac/RegistrationExtensions.cs index 559edaedc..e2b302443 100644 --- a/src/Autofac/RegistrationExtensions.cs +++ b/src/Autofac/RegistrationExtensions.cs @@ -95,7 +95,7 @@ public static IRegistrationBuilderContainer builder. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - RegisterType<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementer>(this ContainerBuilder builder) + RegisterType<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TImplementer>(this ContainerBuilder builder) where TImplementer : notnull { if (builder == null) @@ -117,7 +117,7 @@ public static IRegistrationBuilderThe type of the component implementation. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - RegisterType(this ContainerBuilder builder, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementationType) + RegisterType(this ContainerBuilder builder, [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementationType) { if (builder == null) { diff --git a/src/Autofac/Util/ActivatorMemberTypes.cs b/src/Autofac/Util/ActivatorMemberTypes.cs new file mode 100644 index 000000000..f5b397a1a --- /dev/null +++ b/src/Autofac/Util/ActivatorMemberTypes.cs @@ -0,0 +1,45 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +namespace Autofac.Util; + +/// +/// Centralizes the contract for implementation +/// types that Autofac activates through reflection, so that every annotation across the +/// codebase advertises an identical, trim/AOT-consistent member set. +/// +/// +/// +/// The contract is plus +/// : +/// +/// +/// +/// Public constructors back the default activation path (the +/// ), so they must be +/// preserved. +/// +/// +/// Public properties back the default property-injection path +/// ( only injects public setters), a +/// first-class Autofac feature that must keep working under trimming and native AOT. +/// +/// +/// +/// Non-public constructors (reachable only via a custom +/// ) and non-public properties +/// (reachable only via a custom ) are +/// intentionally excluded: those paths are documented as not trim/AOT-safe, and the +/// custom-selector registration APIs already carry [RequiresUnreferencedCode]. +/// +/// +internal static class ActivatorMemberTypes +{ + /// + /// The member set preserved for reflection-activated implementation types: + /// | + /// . + /// + public const DynamicallyAccessedMemberTypes ActivatedType = + DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties; +} diff --git a/src/Autofac/Util/LinkerAttributes.cs b/src/Autofac/Util/LinkerAttributes.cs index b01d530e2..6dd580f0b 100644 --- a/src/Autofac/Util/LinkerAttributes.cs +++ b/src/Autofac/Util/LinkerAttributes.cs @@ -140,7 +140,7 @@ public string Message /// /// Gets or sets an optional URL that contains more information about the method, - /// why it requries unreferenced code, and what options a consumer has to deal with it. + /// why it requires unreferenced code, and what options a consumer has to deal with it. /// public string? Url { @@ -148,4 +148,77 @@ public string? Url } } +/// +/// Fake version for pre-net-5.0 targets. Suppresses reporting of a specified trimming/AOT +/// analysis rule for the decorated code element. +/// +[AttributeUsage( + AttributeTargets.All, + Inherited = false, + AllowMultiple = true)] +internal sealed class UnconditionalSuppressMessageAttribute : Attribute +{ + /// + /// Initializes a new instance of the + /// class with the specified category and rule identifier. + /// + /// The category for the attribute. + /// + /// The identifier of the analysis rule the attribute applies to. + /// + public UnconditionalSuppressMessageAttribute(string category, string checkId) + { + Category = category; + CheckId = checkId; + } + + /// + /// Gets the category identifying the classification of the attribute. + /// + public string Category + { + get; + } + + /// + /// Gets the identifier of the analysis rule the attribute applies to. + /// + public string CheckId + { + get; + } + + /// + /// Gets or sets the scope of the code that is relevant for the attribute. + /// + public string? Scope + { + get; set; + } + + /// + /// Gets or sets a fully qualified path that represents the target of the attribute. + /// + public string? Target + { + get; set; + } + + /// + /// Gets or sets an optional argument expanding on exclusion criteria. + /// + public string? MessageId + { + get; set; + } + + /// + /// Gets or sets the justification for suppressing the code analysis message. + /// + public string? Justification + { + get; set; + } +} + #endif From be20421d884a0a2d2389a4e4d2c2e00eff65f960 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Thu, 18 Jun 2026 16:22:03 -0700 Subject: [PATCH 03/14] Phase 2b: Propagating attributes through generics. --- src/Autofac/ContainerBuilder.cs | 4 ++ .../OpenGenericDecoratorMiddlewareSource.cs | 4 ++ .../OpenGenericDecoratorRegistrationSource.cs | 4 ++ .../OpenGenericDelegateRegistrationSource.cs | 4 ++ .../OpenGenericRegistrationExtensions.cs | 5 +- .../OpenGenericRegistrationSource.cs | 4 ++ .../OpenGenerics/OpenGenericServiceBinder.cs | 11 +++-- .../RegistrationExtensions.Generics.cs | 6 ++- src/Autofac/Util/LinkerAttributes.cs | 47 +++++++++++++++++++ 9 files changed, 83 insertions(+), 6 deletions(-) diff --git a/src/Autofac/ContainerBuilder.cs b/src/Autofac/ContainerBuilder.cs index cb4aa7007..4281a4cc4 100644 --- a/src/Autofac/ContainerBuilder.cs +++ b/src/Autofac/ContainerBuilder.cs @@ -244,6 +244,10 @@ private void Build(IComponentRegistryBuilder componentRegistry, bool excludeDefa } } + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "The built-in KeyedServiceIndex<,> adapter is registered for every container. The IIndex<,> relationship is only ever constructed when a consumer actually resolves an IIndex<,>, so this default registration does not by itself force dynamic code; suppressing here avoids tainting the always-run Build() path. Consumers that resolve IIndex<,> over value-type keys take on the same dynamic-code requirement as any other open generic.")] private void RegisterDefaultAdapters(IComponentRegistryBuilder componentRegistry) { this.RegisterGeneric(typeof(KeyedServiceIndex<,>)).As(typeof(IIndex<,>)).InstancePerLifetimeScope(); diff --git a/src/Autofac/Features/Decorators/OpenGenericDecoratorMiddlewareSource.cs b/src/Autofac/Features/Decorators/OpenGenericDecoratorMiddlewareSource.cs index fa3ea2708..63bd4b441 100644 --- a/src/Autofac/Features/Decorators/OpenGenericDecoratorMiddlewareSource.cs +++ b/src/Autofac/Features/Decorators/OpenGenericDecoratorMiddlewareSource.cs @@ -35,6 +35,10 @@ public OpenGenericDecoratorMiddlewareSource(DecoratorService decoratorService, R } /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "This middleware source is only ever added to the container by the [RequiresDynamicCode] RegisterGenericDecorator API, so the dynamic-code requirement has already been surfaced to the caller at registration time. The resolve-time binding here cannot be reached without that opt-in.")] public void ProvideMiddleware(Service service, IComponentRegistryServices availableServices, IResolvePipelineBuilder pipelineBuilder) { if (service is IServiceWithType swt) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs index d41a76d20..e537afd7f 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs @@ -55,6 +55,10 @@ public OpenGenericDecoratorRegistrationSource( public bool IsAdapterForIndividualComponents => true; /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "This registration source is only ever added to the container by the [RequiresDynamicCode] RegisterGenericDecorator API, so the dynamic-code requirement has already been surfaced to the caller at registration time. The resolve-time binding here cannot be reached without that opt-in.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (service == null) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs index f2997d462..6fcd4d11b 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs @@ -38,6 +38,10 @@ public OpenGenericDelegateRegistrationSource( public bool IsAdapterForIndividualComponents => false; /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "This registration source is only ever added to the container by the [RequiresDynamicCode] RegisterGeneric API, so the dynamic-code requirement has already been surfaced to the caller at registration time. The resolve-time binding here cannot be reached without that opt-in.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (service == null) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs index a672675a2..5fd33ad78 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using Autofac.Builder; using Autofac.Core; +using Autofac.Util; namespace Autofac.Features.OpenGenerics; @@ -20,7 +21,7 @@ internal static class OpenGenericRegistrationExtensions /// The open generic implementation type. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - RegisterGeneric(ContainerBuilder builder, Type implementer) + RegisterGeneric(ContainerBuilder builder, [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer) { if (builder == null) { @@ -65,7 +66,7 @@ public static IRegistrationBuilderThe open generic implementation type. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - CreateGenericBuilder(Type implementer) + CreateGenericBuilder([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer) { if (implementer == null) { diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs index dcb6a5671..d1070cdd3 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs @@ -50,6 +50,10 @@ public OpenGenericRegistrationSource( public bool IsAdapterForIndividualComponents => false; /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "This registration source is only ever added to the container by the [RequiresDynamicCode] RegisterGeneric API, so the dynamic-code requirement has already been surfaced to the caller at registration time. The resolve-time binding here cannot be reached without that opt-in.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (service == null) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs index b561f8430..47eaf094f 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs @@ -12,6 +12,8 @@ namespace Autofac.Features.OpenGenerics; /// internal static class OpenGenericServiceBinder { + private const string OpenGenericDynamicCodeWarning = "Binding open generic types constructs closed generic types at runtime via MakeGenericType, which may require dynamic code generation when closed over value types and is not compatible with native AOT."; + /// /// Given a closed generic service (that is being requested), creates a closed generic implementation type /// and associated services from the open generic implementation and services. @@ -22,12 +24,13 @@ internal static class OpenGenericServiceBinder /// The built closed generic implementation type. /// The built closed generic services. /// True if the closed generic service can be bound. False otherwise. + [RequiresDynamicCode(OpenGenericDynamicCodeWarning)] [SuppressMessage("CA1851", "CA1851", Justification = "The CPU cost in enumerating the list of services is low, while allocating a new list saves little in CPU but costs a lot in allocations.")] public static bool TryBindOpenGenericTypedService( IServiceWithType serviceWithType, IEnumerable configuredOpenGenericServices, - Type openGenericImplementationType, - [NotNullWhen(returnValue: true)] out Type? constructedImplementationType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type openGenericImplementationType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)][NotNullWhen(returnValue: true)] out Type? constructedImplementationType, [NotNullWhen(returnValue: true)] out Service[]? constructedServices) { if (serviceWithType.ServiceType.IsGenericType && !serviceWithType.ServiceType.IsGenericTypeDefinition) @@ -78,6 +81,7 @@ public static bool TryBindOpenGenericTypedService( /// The built closed generic implementation type. /// The built closed generic services. /// True if the closed generic service can be bound. False otherwise. + [RequiresDynamicCode(OpenGenericDynamicCodeWarning)] [SuppressMessage("CA1851", "CA1851", Justification = "The CPU cost in enumerating the list of services is low, while allocating a new list saves little in CPU but costs a lot in allocations.")] public static bool TryBindOpenGenericDelegateService( IServiceWithType serviceWithType, @@ -161,6 +165,7 @@ public static void EnforceBindable(Type implementationType, IEnumerable private static bool IsClosedGenericType(Type serviceType) => serviceType.IsGenericType && !serviceType.IsGenericTypeDefinition; + [RequiresDynamicCode(OpenGenericDynamicCodeWarning)] private static Service[] BuildImplementedServices(IEnumerable configuredOpenGenericServices, Type[] serviceGenericArguments) { var serviceGenericArgumentsLength = serviceGenericArguments.Length; @@ -282,7 +287,7 @@ private static Service[] BuildImplementedServices(IEnumerable configure return baseType; } - private static Type[] GetInterfaces(Type implementationType, Type serviceType) + private static Type[] GetInterfaces([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type implementationType, Type serviceType) => implementationType.GetInterfaces() .Where(i => i.Name == serviceType.Name && i.Namespace == serviceType.Namespace) .ToArray(); diff --git a/src/Autofac/RegistrationExtensions.Generics.cs b/src/Autofac/RegistrationExtensions.Generics.cs index 960472c9d..aeb35e847 100644 --- a/src/Autofac/RegistrationExtensions.Generics.cs +++ b/src/Autofac/RegistrationExtensions.Generics.cs @@ -4,6 +4,7 @@ using Autofac.Builder; using Autofac.Core; using Autofac.Features.OpenGenerics; +using Autofac.Util; namespace Autofac; @@ -13,6 +14,8 @@ namespace Autofac; [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "RegistrationBuilder is where all registration syntax lives.")] public static partial class RegistrationExtensions { + private const string OpenGenericDynamicCodeWarning = "Registering open generic types constructs closed generic types at runtime via MakeGenericType, which may require dynamic code generation when closed over value types and is not compatible with native AOT."; + /// /// Register an un-parameterized generic type, e.g. Repository<>. /// Concrete types will be made as they are requested, e.g. with Resolve<Repository<int>>(). @@ -20,8 +23,9 @@ public static partial class RegistrationExtensions /// Container builder. /// The open generic implementation type. /// Registration builder allowing the registration to be configured. + [RequiresDynamicCode(OpenGenericDynamicCodeWarning)] public static IRegistrationBuilder - RegisterGeneric(this ContainerBuilder builder, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type implementer) + RegisterGeneric(this ContainerBuilder builder, [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer) { return OpenGenericRegistrationExtensions.RegisterGeneric(builder, implementer); } diff --git a/src/Autofac/Util/LinkerAttributes.cs b/src/Autofac/Util/LinkerAttributes.cs index 6dd580f0b..75a48fdfa 100644 --- a/src/Autofac/Util/LinkerAttributes.cs +++ b/src/Autofac/Util/LinkerAttributes.cs @@ -76,6 +76,11 @@ internal enum DynamicallyAccessedMemberTypes /// Specifies all non-public events. /// NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, } /// @@ -148,6 +153,48 @@ public string? Url } } +/// +/// Fake version for pre-net-5.0 targets. Indicates that the specified method requires the +/// ability to generate new code at runtime, for example through +/// or APIs that emit IL. +/// +/// +/// This allows tools to understand which methods are unsafe to call when compiling ahead of +/// time (for example with native AOT). +/// +[AttributeUsage(AttributeTargets.Method | AttributeTargets.Constructor, Inherited = false)] +internal sealed class RequiresDynamicCodeAttribute : Attribute +{ + /// + /// Initializes a new instance of the class + /// with the specified message. + /// + /// + /// A message that contains information about the usage of dynamic code. + /// + public RequiresDynamicCodeAttribute(string message) + { + Message = message; + } + + /// + /// Gets a message that contains information about the usage of dynamic code. + /// + public string Message + { + get; + } + + /// + /// Gets or sets an optional URL that contains more information about the method, + /// why it requires dynamic code, and what options a consumer has to deal with it. + /// + public string? Url + { + get; set; + } +} + /// /// Fake version for pre-net-5.0 targets. Suppresses reporting of a specified trimming/AOT /// analysis rule for the decorated code element. From 0081dd25ed29527bb7e5e96ce7db7c5d88916742 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 09:58:27 -0700 Subject: [PATCH 04/14] Phase 2c: Continued propagation of attributes. --- src/Autofac/Builder/RegistrationExtensions.cs | 8 ++-- .../Reflection/DefaultConstructorFinder.cs | 7 +++- ...enGenericScanningRegistrationExtensions.cs | 4 ++ .../ScanningRegistrationExtensions.cs | 8 ++++ .../Features/Scanning/TypeExtensions.cs | 4 ++ ...nExtensions.OpenGenericAssemblyScanning.cs | 5 ++- src/Autofac/RegistrationExtensions.cs | 2 +- src/Autofac/TypeExtensions.cs | 12 +++--- src/Autofac/Util/Enforce.cs | 2 +- src/Autofac/Util/InternalTypeExtensions.cs | 37 +++++++++++++++---- src/Autofac/Util/ReflectionExtensions.cs | 4 ++ 11 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/Autofac/Builder/RegistrationExtensions.cs b/src/Autofac/Builder/RegistrationExtensions.cs index 37bacf9a6..7814076ae 100644 --- a/src/Autofac/Builder/RegistrationExtensions.cs +++ b/src/Autofac/Builder/RegistrationExtensions.cs @@ -27,7 +27,7 @@ public static class RegistrationExtensions /// and this method is generally not required. [Obsolete("Update your code to use the Func implicit relationship or delegate factories. See https://autofac.readthedocs.io/en/latest/resolve/relationships.html and https://autofac.readthedocs.io/en/latest/advanced/delegate-factories.html for more information.")] public static IRegistrationBuilder - RegisterGeneratedFactory(this ContainerBuilder builder, Type delegateType) + RegisterGeneratedFactory(this ContainerBuilder builder, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType) { if (delegateType == null) { @@ -51,7 +51,7 @@ public static IRegistrationBuilder [Obsolete("Update your code to use the Func implicit relationship or delegate factories. See https://autofac.readthedocs.io/en/latest/resolve/relationships.html and https://autofac.readthedocs.io/en/latest/advanced/delegate-factories.html for more information.")] public static IRegistrationBuilder - RegisterGeneratedFactory(this ContainerBuilder builder, Type delegateType, Service service) + RegisterGeneratedFactory(this ContainerBuilder builder, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType, Service service) { if (builder == null) { @@ -72,7 +72,7 @@ public static IRegistrationBuilder [Obsolete("Update your code to use the Func implicit relationship or delegate factories. See https://autofac.readthedocs.io/en/latest/resolve/relationships.html and https://autofac.readthedocs.io/en/latest/advanced/delegate-factories.html for more information.")] public static IRegistrationBuilder - RegisterGeneratedFactory(this ContainerBuilder builder, Service service) + RegisterGeneratedFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] TDelegate>(this ContainerBuilder builder, Service service) where TDelegate : class { if (builder == null) @@ -93,7 +93,7 @@ public static IRegistrationBuilder [Obsolete("Update your code to use the Func implicit relationship or delegate factories. See https://autofac.readthedocs.io/en/latest/resolve/relationships.html and https://autofac.readthedocs.io/en/latest/advanced/delegate-factories.html for more information.")] public static IRegistrationBuilder - RegisterGeneratedFactory(this ContainerBuilder builder) + RegisterGeneratedFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] TDelegate>(this ContainerBuilder builder) where TDelegate : class { if (builder == null) diff --git a/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs b/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs index 867cc4ed1..5a358a7e9 100644 --- a/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs +++ b/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs @@ -42,9 +42,12 @@ public ConstructorInfo[] FindConstructors(Type targetType) return _finder(targetType); } - private static ConstructorInfo[] GetDefaultPublicConstructors(Type type) + private static ConstructorInfo[] GetDefaultPublicConstructors([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) { + // The cache key is the (annotated) target type; the factory reads the annotated + // local rather than the dictionary's unannotated key parameter so the + // [DynamicallyAccessedMembers] contract flows into GetDeclaredPublicConstructors. return ReflectionCacheSet.Shared.Internal.DefaultPublicConstructors - .GetOrAdd(type, t => t.GetDeclaredPublicConstructors()); + .GetOrAdd(type, _ => type.GetDeclaredPublicConstructors()); } } diff --git a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs index f1c8ebd14..88a6201a5 100644 --- a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs +++ b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs @@ -162,6 +162,10 @@ public static IRegistrationBuilder (Service)new KeyedService(serviceKeyMapping(candidateType), candidateType)); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "The implementation types are discovered by assembly scanning, whose public entry points (RegisterAssemblyOpenGenericTypes etc.) already carry [RequiresUnreferencedCode]. Preserving the activatable members of scanned types is the responsibility of the consumer that opted into scanning.")] private static void ScanAssembliesForOpenGenerics(IEnumerable assemblies, IComponentRegistryBuilder cr, RegistrationBuilder rb) { rb.ActivatorData.Filters.Add(t => diff --git a/src/Autofac/Features/Scanning/ScanningRegistrationExtensions.cs b/src/Autofac/Features/Scanning/ScanningRegistrationExtensions.cs index 46067df7c..2e6b3351b 100644 --- a/src/Autofac/Features/Scanning/ScanningRegistrationExtensions.cs +++ b/src/Autofac/Features/Scanning/ScanningRegistrationExtensions.cs @@ -80,6 +80,10 @@ public static IRegistrationBuilderThe registration builder. /// The open generic to register closed types of. /// The registration builder for continued configuration. + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "The candidate types are discovered by assembly scanning, whose public entry points (RegisterAssemblyTypes etc.) already carry [RequiresUnreferencedCode]. Preserving the interfaces of scanned types is the responsibility of the consumer that opted into scanning.")] public static IRegistrationBuilder AsClosedTypesOf( IRegistrationBuilder registration, @@ -137,6 +141,10 @@ public static IRegistrationBuilderThe open generic to register closed types of. /// A function to determine the service key for a given type. /// The registration builder for continued configuration. + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "The candidate types are discovered by assembly scanning, whose public entry points (RegisterAssemblyTypes etc.) already carry [RequiresUnreferencedCode]. Preserving the interfaces of scanned types is the responsibility of the consumer that opted into scanning.")] public static IRegistrationBuilder AsClosedTypesOf( IRegistrationBuilder registration, diff --git a/src/Autofac/Features/Scanning/TypeExtensions.cs b/src/Autofac/Features/Scanning/TypeExtensions.cs index 8ade2cc61..b2b5aac3f 100644 --- a/src/Autofac/Features/Scanning/TypeExtensions.cs +++ b/src/Autofac/Features/Scanning/TypeExtensions.cs @@ -57,6 +57,10 @@ internal static IEnumerable AllowedByActivatorFilters + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "The concrete types are discovered by assembly scanning, whose public entry points (RegisterAssemblyTypes etc.) already carry [RequiresUnreferencedCode]. Preserving the activatable members of scanned types is the responsibility of the consumer that opted into scanning.")] internal static void FilterAndRegisterConcreteTypes(this IEnumerable types, IComponentRegistryBuilder cr, IRegistrationBuilder rb) { var closedTypes = types.WhichAreAllowedThroughScanning() diff --git a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs index ef42cedd0..51fda2b17 100644 --- a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs @@ -7,6 +7,7 @@ using Autofac.Core; using Autofac.Features.OpenGenerics; using Autofac.Features.Scanning; +using Autofac.Util; namespace Autofac; @@ -158,7 +159,7 @@ public static IRegistrationBuilder Except( this IRegistrationBuilder registration, - Type openGenericType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type openGenericType, Action> customizedRegistration) { var result = registration.Except(openGenericType); @@ -328,7 +329,7 @@ public static IRegistrationBuilderRegistration to set metadata on. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - WithMetadataFrom( + WithMetadataFrom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TAttribute>( this IRegistrationBuilder registration) { var attrType = typeof(TAttribute); diff --git a/src/Autofac/RegistrationExtensions.cs b/src/Autofac/RegistrationExtensions.cs index e2b302443..a37a4dbb1 100644 --- a/src/Autofac/RegistrationExtensions.cs +++ b/src/Autofac/RegistrationExtensions.cs @@ -265,7 +265,7 @@ public static IRegistrationBuilderRegistration to set metadata on. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - WithMetadataFrom( + WithMetadataFrom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TAttribute>( this IRegistrationBuilder registration) { var attrType = typeof(TAttribute); diff --git a/src/Autofac/TypeExtensions.cs b/src/Autofac/TypeExtensions.cs index fe6b54f23..af9df448a 100644 --- a/src/Autofac/TypeExtensions.cs +++ b/src/Autofac/TypeExtensions.cs @@ -67,7 +67,7 @@ public static bool IsInNamespaceOf(this Type @this) /// The type to test. /// The open generic against which the type should be tested. /// if the provided type is a closed version of the provided open generic; otherwise, . - public static bool IsClosedTypeOf(this Type @this, Type openGeneric) + public static bool IsClosedTypeOf([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type @this, Type openGeneric) { if (@this == null) { @@ -110,7 +110,7 @@ public static bool IsAssignableTo(this Type @this) /// The type. /// The name of the method. /// An object that represents the specified method, if found; otherwise, null. - public static MethodInfo GetDeclaredMethod(this Type @this, string methodName) + public static MethodInfo GetDeclaredMethod([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type @this, string methodName) { if (@this is null) { @@ -133,7 +133,7 @@ public static MethodInfo GetDeclaredMethod(this Type @this, string methodName) /// The type. /// The name of the property. /// An object that represents the specified property, if found; otherwise, null. - public static PropertyInfo GetDeclaredProperty(this Type @this, string propertyName) + public static PropertyInfo GetDeclaredProperty([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.NonPublicProperties)] this Type @this, string propertyName) { if (@this is null) { @@ -155,7 +155,7 @@ public static PropertyInfo GetDeclaredProperty(this Type @this, string propertyN /// /// The type. /// A collection of instance constructors. - public static ConstructorInfo[] GetDeclaredConstructors(this Type @this) + public static ConstructorInfo[] GetDeclaredConstructors([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] this Type @this) { if (@this is null) { @@ -171,7 +171,7 @@ public static ConstructorInfo[] GetDeclaredConstructors(this Type @this) /// /// The type. /// A collection of instance constructors. - public static ConstructorInfo[] GetDeclaredPublicConstructors(this Type @this) + public static ConstructorInfo[] GetDeclaredPublicConstructors([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type @this) { if (@this is null) { @@ -187,7 +187,7 @@ public static ConstructorInfo[] GetDeclaredPublicConstructors(this Type @this) /// The type being tested. /// The types of the contractor to find. /// The is a match is found; otherwise, null. - public static ConstructorInfo? GetMatchingConstructor(this Type type, Type[] constructorParameterTypes) + public static ConstructorInfo? GetMatchingConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] this Type type, Type[] constructorParameterTypes) { return type.GetDeclaredConstructors().FirstOrDefault( c => c.GetParameters().Select(p => p.ParameterType).SequenceEqual(constructorParameterTypes)); diff --git a/src/Autofac/Util/Enforce.cs b/src/Autofac/Util/Enforce.cs index b518db06c..8758cd2bb 100644 --- a/src/Autofac/Util/Enforce.cs +++ b/src/Autofac/Util/Enforce.cs @@ -85,7 +85,7 @@ public static string ArgumentNotNullOrEmpty([ValidatedNotNull] string value, str /// Enforce that the argument is a delegate type. /// /// The type to test. - public static void ArgumentTypeIsFunction(Type delegateType) + public static void ArgumentTypeIsFunction([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType) { if (delegateType == null) { diff --git a/src/Autofac/Util/InternalTypeExtensions.cs b/src/Autofac/Util/InternalTypeExtensions.cs index 098e15562..55ae63e5c 100644 --- a/src/Autofac/Util/InternalTypeExtensions.cs +++ b/src/Autofac/Util/InternalTypeExtensions.cs @@ -12,12 +12,14 @@ namespace Autofac.Util; /// internal static class InternalTypeExtensions { + private const string OpenGenericConstraintDynamicCodeWarning = "Validating open generic constraints constructs candidate closed generic types at runtime via MakeGenericType, which may require dynamic code generation when closed over value types and is not compatible with native AOT."; + /// /// For a delegate type, outputs the return type of the delegate. /// /// The delegate type. /// The delegate return type. - public static Type FunctionReturnType(this Type type) + public static Type FunctionReturnType([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type type) { var invoke = type.GetDeclaredMethod("Invoke"); Enforce.NotNull(invoke); @@ -39,7 +41,7 @@ public static bool IsOpenGeneric(this Type type) /// The type that is being checked for the interface. /// The open generic type to locate. /// The type of the interface. - public static IEnumerable GetTypesThatClose(this Type @this, Type openGeneric) + public static IEnumerable GetTypesThatClose([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type @this, Type openGeneric) { return FindAssignableTypesThatClose(@this, openGeneric); } @@ -50,7 +52,7 @@ public static IEnumerable GetTypesThatClose(this Type @this, Type openGene /// The type we are checking. /// The open generic type to validate against. /// True if is a closed type of . False otherwise. - public static bool IsClosedTypeOf(this Type @this, Type openGeneric) + public static bool IsClosedTypeOf([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type @this, Type openGeneric) { return TypesAssignableFrom(@this).Any(t => t.IsGenericType && !@this.ContainsGenericParameters && t.GetGenericTypeDefinition() == openGeneric); } @@ -61,6 +63,11 @@ public static bool IsClosedTypeOf(this Type @this, Type openGeneric) /// The generic type definition. /// The set of parameters to check against. /// True if the parameters match the generic parameter constraints. + [RequiresDynamicCode(OpenGenericConstraintDynamicCodeWarning)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2062:UnrecognizedReflectionPattern", + Justification = "The generic argument constraints and candidate parameter types are caller-supplied runtime types from an open generic registration. Their members cannot be statically annotated; preserving them is the responsibility of the consumer that opted into open generic registration (which carries [RequiresUnreferencedCode]/[RequiresDynamicCode]).")] public static bool IsCompatibleWithGenericParameterConstraints(this Type genericTypeDefinition, Type[] parameters) { var genericArgumentDefinitions = genericTypeDefinition.GetGenericArguments(); @@ -216,7 +223,7 @@ static bool Uncached(Type @this, Type type) /// The type we are checking. /// The type to validate against. /// True if is a open generic type of . False otherwise. - public static bool IsOpenGenericTypeOf(this Type @this, Type type) + public static bool IsOpenGenericTypeOf([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type @this, Type type) { if (@this == null || type == null) { @@ -276,7 +283,7 @@ private static bool CheckBaseTypeIsOpenGenericTypeOf(this Type @this, Type type) : type.IsAssignableFrom(@this.BaseType); } - private static bool CheckInterfacesAreOpenGenericTypeOf(this Type @this, Type type) + private static bool CheckInterfacesAreOpenGenericTypeOf([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type @this, Type type) { return @this.GetInterfaces() .Any(it => it.IsGenericType @@ -290,7 +297,11 @@ private static bool CheckInterfacesAreOpenGenericTypeOf(this Type @this, Type ty /// The type that is being checked for the interface. /// The open generic service type to locate. /// True if a closed implementation was found; otherwise false. - private static IEnumerable FindAssignableTypesThatClose(Type candidateType, Type openGenericServiceType) + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "The enumerated types are the base types and interfaces of candidateType (whose interfaces are preserved by this method's own annotation). These derived Type values cannot carry static annotations; the interface check on them is best-effort and the closed types involved are preserved by the consumer that opted into the (open generic / scanning) feature.")] + private static IEnumerable FindAssignableTypesThatClose([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type candidateType, Type openGenericServiceType) { return TypesAssignableFrom(candidateType) .Where(t => t.IsClosedTypeOf(openGenericServiceType)); @@ -306,7 +317,12 @@ private static Type SubstituteGenericParameterConstraint(Type[] parameters, Type return parameters[constraint.GenericParameterPosition]; } - private static bool ParameterCompatibleWithTypeConstraint(Type parameter, Type constraint) + [RequiresDynamicCode(OpenGenericConstraintDynamicCodeWarning)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2062:UnrecognizedReflectionPattern", + Justification = "The generic type arguments and interfaces inspected here come from caller-supplied runtime types in an open generic registration. They cannot carry static annotations; preserving them is the responsibility of the consumer that opted into open generic registration.")] + private static bool ParameterCompatibleWithTypeConstraint([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type parameter, Type constraint) { if (constraint.IsAssignableFrom(parameter)) { @@ -340,6 +356,11 @@ private static bool ParameterCompatibleWithTypeConstraint(Type parameter, Type c #if NET6_0_OR_GREATER [SuppressMessage("Microsoft.Design", "CA1031", Justification = "Implementing a real TryMakeGenericType is not worth the effort.")] #endif + [RequiresDynamicCode(OpenGenericConstraintDynamicCodeWarning)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2055:MakeGenericType", + Justification = "The constraint type definition and its generic arguments are caller-supplied runtime types from an open generic registration. Preserving the closed type is the responsibility of the consumer that opted into open generic registration.")] private static bool ParameterEqualsConstraint(Type parameter, Type constraint) { var genericArguments = parameter.GenericTypeArguments; @@ -374,7 +395,7 @@ private static bool ParameterEqualsConstraint(Type parameter, Type constraint) return false; } - private static IEnumerable TypesAssignableFrom(Type candidateType) + private static IEnumerable TypesAssignableFrom([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type candidateType) { return candidateType.GetInterfaces().Concat( Traverse.Across(candidateType, t => t.BaseType!)); diff --git a/src/Autofac/Util/ReflectionExtensions.cs b/src/Autofac/Util/ReflectionExtensions.cs index 51682e937..d557ece96 100644 --- a/src/Autofac/Util/ReflectionExtensions.cs +++ b/src/Autofac/Util/ReflectionExtensions.cs @@ -29,6 +29,10 @@ public static TDelegate CreateDelegate(this MethodInfo method, object /// Parameter to the property setter. /// The property info on which the setter is specified. /// True if the parameter is a property setter. + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "The declaring type is derived from a property setter's ParameterInfo that the caller already holds. The setter (and therefore its declaring property) is reachable, so resolving the property back from its own setter does not introduce a new trimming root.")] public static bool TryGetDeclaringProperty(this ParameterInfo pi, [NotNullWhen(returnValue: true)] out PropertyInfo? prop) { var mi = pi.Member as MethodInfo; From 67121902f5139fc2b63a6608d134ba6c9519aefc Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 10:23:11 -0700 Subject: [PATCH 05/14] Phase 2d: Continued propagation of attributes. --- .../Core/ImplicitRegistrationSource.cs | 8 ++++++ .../MetadataFilterAttribute.cs | 28 +++++++++++++++++++ .../CollectionRegistrationSource.cs | 12 ++++++++ .../GeneratedFactories/FactoryGenerator.cs | 14 +++++++--- .../GeneratedFactoryRegistrationSource.cs | 8 ++++++ .../LazyRegistrationSource.cs | 4 +++ .../LazyWithMetadataRegistrationSource.cs | 15 +++++++++- .../Features/Metadata/MetadataViewProvider.cs | 13 ++++++++- .../StronglyTypedMetaRegistrationSource.cs | 11 +++++++- .../OpenGenerics/OpenGenericServiceBinder.cs | 16 +++++++++++ .../ContravariantRegistrationSource.cs | 16 +++++++++-- 11 files changed, 136 insertions(+), 9 deletions(-) diff --git a/src/Autofac/Core/ImplicitRegistrationSource.cs b/src/Autofac/Core/ImplicitRegistrationSource.cs index 7830872ae..36efd872c 100644 --- a/src/Autofac/Core/ImplicitRegistrationSource.cs +++ b/src/Autofac/Core/ImplicitRegistrationSource.cs @@ -50,6 +50,14 @@ protected ImplicitRegistrationSource(Type type) public virtual string Description => GetType().Name; /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Implicit relationship sources (Lazy, Meta, etc.) are registered for every container but only ever construct the relationship registration via MakeGenericMethod when a consumer actually resolves that relationship type. The closed-type resolve path never reaches this. Consumers that resolve implicit relationships over value-type arguments take on the dynamic-code requirement.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2060:MakeGenericMethod", + Justification = "The generic argument is the relationship's value type supplied by the consumer at resolve time; preserving it is the responsibility of the consumer that resolves the implicit relationship.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (registrationAccessor == null) diff --git a/src/Autofac/Features/AttributeFilters/MetadataFilterAttribute.cs b/src/Autofac/Features/AttributeFilters/MetadataFilterAttribute.cs index 20c846698..3ef7a65e8 100644 --- a/src/Autofac/Features/AttributeFilters/MetadataFilterAttribute.cs +++ b/src/Autofac/Features/AttributeFilters/MetadataFilterAttribute.cs @@ -130,6 +130,14 @@ public object Value /// /// Thrown if or is . /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "MetadataFilter is an opt-in attribute the consumer applies to a constructor parameter. The MakeGenericMethod over the parameter's element type only runs when such a parameter is resolved; the dynamic-code/preservation responsibility lies with the consumer that applied [MetadataFilter]. The base ParameterFilterAttribute cannot be marked [RequiresDynamicCode] without forcing it on all filter attributes.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2060:MakeGenericMethod", + Justification = "The generic argument is the element type of the parameter being filtered, supplied by the consumer's metadata-filtered registration. Preserving it is the responsibility of the consumer that opted into [MetadataFilter].")] public override object? ResolveParameter(ParameterInfo parameter, IComponentContext context) { if (parameter == null) @@ -160,6 +168,14 @@ public object Value /// The specific parameter being resolved that is marked with this attribute. /// The component context under which the parameter is being resolved. /// true if parameter can be resolved; otherwise, false. + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "MetadataFilter is an opt-in attribute the consumer applies to a constructor parameter. The MakeGenericMethod over the parameter's element type only runs when such a parameter is resolved; the dynamic-code/preservation responsibility lies with the consumer that applied [MetadataFilter]. The base ParameterFilterAttribute cannot be marked [RequiresDynamicCode] without forcing it on all filter attributes.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2060:MakeGenericMethod", + Justification = "The generic argument is the element type of the parameter being filtered, supplied by the consumer's metadata-filtered registration. Preserving it is the responsibility of the consumer that opted into [MetadataFilter].")] public override bool CanResolveParameter(ParameterInfo parameter, IComponentContext context) { if (parameter == null) @@ -187,6 +203,10 @@ private static Type GetElementType(Type type) return type.IsGenericEnumerableInterfaceType() ? type.GenericTypeArguments[0] : type; } + [UnconditionalSuppressMessage( + "Trimming", + "IL2091:UnrecognizedReflectionPattern", + Justification = "The Lazy in Meta> is resolved through the container with a value factory; T's own constructor is never invoked by Lazy, so the nominal PublicParameterlessConstructor requirement does not apply.")] private static T? FilterOne(IComponentContext context, string metadataKey, object metadataValue) { // Using Lazy to ensure components that aren't actually used won't get activated. @@ -196,6 +216,10 @@ private static Type GetElementType(Type type) .FirstOrDefault(); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2091:UnrecognizedReflectionPattern", + Justification = "The Lazy in Meta> is resolved through the container with a value factory; T's own constructor is never invoked by Lazy, so the nominal PublicParameterlessConstructor requirement does not apply.")] private static T[] FilterAll(IComponentContext context, string metadataKey, object metadataValue) { // Using Lazy to ensure components that aren't actually used won't get activated. @@ -205,6 +229,10 @@ private static T[] FilterAll(IComponentContext context, string metadataKey, o .ToArray(); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2091:UnrecognizedReflectionPattern", + Justification = "The Lazy in Meta> is resolved through the container with a value factory; T's own constructor is never invoked by Lazy, so the nominal PublicParameterlessConstructor requirement does not apply.")] private static bool CanResolve(IComponentContext context, string metadataKey, object metadataValue) { // Using Lazy to ensure components that aren't actually used won't get activated. diff --git a/src/Autofac/Features/Collections/CollectionRegistrationSource.cs b/src/Autofac/Features/Collections/CollectionRegistrationSource.cs index 142f5145b..90b7624b7 100644 --- a/src/Autofac/Features/Collections/CollectionRegistrationSource.cs +++ b/src/Autofac/Features/Collections/CollectionRegistrationSource.cs @@ -58,6 +58,10 @@ internal class CollectionRegistrationSource : IRegistrationSource, IPerScopeRegi "Reliability", "CA2000:Dispose objects before losing scope", Justification = "Activator lifetime controlled by registry.")] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "The collection registration source is registered for every container but only constructs the List/array element type via MakeGenericType/MakeArrayType when a consumer actually resolves IEnumerable/IList/T[]. The closed-type resolve path never reaches this. Consumers that resolve collections of value types take on the dynamic-code requirement.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (service == null) @@ -174,6 +178,10 @@ private static (string? CollectionKind, string? CollectionDetail) GetCollectionM return (collectionKind, collectionDetail); } + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Only reached when a consumer resolves IList/ICollection; constructing the closed List is the consumer's dynamic-code requirement, already surfaced at the resolve of the collection relationship.")] private static Func GenerateListFactory(Type elementType) { var parameter = Expression.Parameter(typeof(int)); @@ -185,6 +193,10 @@ private static Func GenerateListFactory(Type elementType) return Expression.Lambda>(newList, parameter).Compile(); } + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Only reached when a consumer resolves T[]/IEnumerable; constructing the array type is the consumer's dynamic-code requirement, already surfaced at the resolve of the collection relationship.")] private static Func GenerateArrayFactory(Type elementType) { var parameter = Expression.Parameter(typeof(int)); diff --git a/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs b/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs index d6c61a3be..d09655f0a 100644 --- a/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs +++ b/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs @@ -15,6 +15,8 @@ namespace Autofac.Features.GeneratedFactories; /// public class FactoryGenerator { + private const string DynamicCodeWarning = "Generated factories compile a delegate from an expression tree at runtime, which requires dynamic code generation and is not compatible with native AOT."; + // The explicit '!' default is ok because the code is never executed, it's just used by // the expression tree. private static readonly ConstructorInfo _requestConstructor @@ -29,7 +31,8 @@ private static readonly ConstructorInfo _requestConstructor /// order to create the products of the factory. /// The delegate to provide as a factory. /// The parameter mapping mode to use. - public FactoryGenerator(Type delegateType, Service service, ParameterMapping parameterMapping) + [RequiresDynamicCode(DynamicCodeWarning)] + public FactoryGenerator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType, Service service, ParameterMapping parameterMapping) { if (service == null) { @@ -63,7 +66,8 @@ public FactoryGenerator(Type delegateType, Service service, ParameterMapping par /// order to create the products of the factory. /// The delegate to provide as a factory. /// The parameter mapping mode to use. - public FactoryGenerator(Type delegateType, Service service, ServiceRegistration productRegistration, ParameterMapping parameterMapping) + [RequiresDynamicCode(DynamicCodeWarning)] + public FactoryGenerator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType, Service service, ServiceRegistration productRegistration, ParameterMapping parameterMapping) { Enforce.ArgumentTypeIsFunction(delegateType); @@ -140,7 +144,8 @@ private static bool DelegateTypeIsFunc(Type delegateType) /// no instance-specific compile-time captures, making it safe to cache and share across all /// instances with the same (, ) pair. /// - private static Func, Delegate> CreateServiceOnlyGenerator(Type delegateType, ParameterMapping pm) + [RequiresDynamicCode(DynamicCodeWarning)] + private static Func, Delegate> CreateServiceOnlyGenerator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType, ParameterMapping pm) { // Outer parameters: (Service svc, IComponentContext c, IEnumerable p) var serviceParam = Expression.Parameter(typeof(Service), "svc"); @@ -208,7 +213,8 @@ private static Func, Delegate /// no instance-specific compile-time captures, making it safe to cache and share across all /// instances with the same (, ) pair. /// - private static Func, Delegate> CreateServiceRegistrationGenerator(Type delegateType, ParameterMapping pm) + [RequiresDynamicCode(DynamicCodeWarning)] + private static Func, Delegate> CreateServiceRegistrationGenerator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType, ParameterMapping pm) { // Outer parameters: (Service svc, ServiceRegistration sr, IComponentContext c, IEnumerable p) var serviceParam = Expression.Parameter(typeof(Service), "svc"); diff --git a/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationSource.cs b/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationSource.cs index c012d5b9d..718225097 100644 --- a/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationSource.cs +++ b/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationSource.cs @@ -22,6 +22,14 @@ internal class GeneratedFactoryRegistrationSource : IRegistrationSource /// The service that was requested. /// A function that will return existing registrations for a service. /// Registrations providing the service. + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "The generated-factory source is registered for every container but only compiles a factory delegate (via FactoryGenerator's expression tree) when a consumer actually resolves a delegate factory type. The closed-type resolve path never reaches this. Consumers that resolve generated factories take on the dynamic-code requirement.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "The delegate ServiceType is the factory type the consumer requested; reading its Invoke return type is intrinsic to a delegate and the consumer that resolves the factory is responsible for that type.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (service == null) diff --git a/src/Autofac/Features/LazyDependencies/LazyRegistrationSource.cs b/src/Autofac/Features/LazyDependencies/LazyRegistrationSource.cs index 14bf01f36..73e08524b 100644 --- a/src/Autofac/Features/LazyDependencies/LazyRegistrationSource.cs +++ b/src/Autofac/Features/LazyDependencies/LazyRegistrationSource.cs @@ -25,6 +25,10 @@ public LazyRegistrationSource() public override string Description => LazyRegistrationSourceResources.LazyRegistrationSourceDescription; /// + [UnconditionalSuppressMessage( + "Trimming", + "IL2091:UnrecognizedReflectionPattern", + Justification = "Lazy nominally wants a public parameterless constructor on T, but this Lazy is constructed with an explicit value factory that resolves T through the container - T's own constructor is never invoked by Lazy here.")] protected override object ResolveInstance(IComponentContext ctx, in ResolveRequest request) { var capturedContext = ctx.Resolve(); diff --git a/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs b/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs index be3ef9e5d..87f37f732 100644 --- a/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs +++ b/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs @@ -29,6 +29,14 @@ internal class LazyWithMetadataRegistrationSource : IRegistrationSource public bool IsAdapterForIndividualComponents => true; /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "This source is registered for every container but only builds the Lazy registration via MakeGenericMethod when a consumer actually resolves that relationship. The closed-type resolve path never reaches this. Consumers that resolve Lazy over value-type arguments take on the dynamic-code requirement.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2060:MakeGenericMethod", + Justification = "The generic arguments are the relationship's value and metadata types supplied by the consumer at resolve time; preserving them is the responsibility of the consumer that resolves the relationship.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (registrationAccessor == null) @@ -72,7 +80,12 @@ public override string ToString() return LazyWithMetadataRegistrationSourceResources.LazyWithMetadataRegistrationSourceDescription; } - private static IComponentRegistration CreateLazyRegistration(Service providedService, Service valueService, ServiceRegistration registrationResolveInfo) + [RequiresDynamicCode("Lazy builds a strongly-typed metadata view at runtime via expression compilation; only reached when a consumer resolves the relationship.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "lazyType is the closed Lazy the consumer requested. Activator.CreateInstance invokes its (T factory, metadata) constructor, which is intrinsic to Lazy<,> and always present; the consumer that resolves the relationship is responsible for that type.")] + private static IComponentRegistration CreateLazyRegistration(Service providedService, Service valueService, ServiceRegistration registrationResolveInfo) { var metadataProvider = MetadataViewProvider.GetMetadataViewProvider(); diff --git a/src/Autofac/Features/Metadata/MetadataViewProvider.cs b/src/Autofac/Features/Metadata/MetadataViewProvider.cs index bf599dcec..f73585cb9 100644 --- a/src/Autofac/Features/Metadata/MetadataViewProvider.cs +++ b/src/Autofac/Features/Metadata/MetadataViewProvider.cs @@ -14,6 +14,8 @@ namespace Autofac.Features.Metadata; /// internal static class MetadataViewProvider { + private const string DynamicCodeWarning = "Strongly-typed metadata views are populated by compiling an expression tree at runtime and using MakeGenericMethod over each property type, which requires dynamic code generation and is not compatible with native AOT."; + private static readonly MethodInfo _getMetadataValueMethod = typeof(MetadataViewProvider).GetDeclaredMethod(nameof(GetMetadataValue)); /// @@ -21,7 +23,16 @@ internal static class MetadataViewProvider /// /// The metadata type. /// A provider function. - public static Func, TMetadata> GetMetadataViewProvider() + [RequiresDynamicCode(DynamicCodeWarning)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2060:MakeGenericMethod", + Justification = "The generic argument is the metadata view property's own type. The properties of TMetadata are preserved by this method's [DynamicallyAccessedMembers(PublicProperties)] annotation, so the property types are reachable.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2087:UnrecognizedReflectionPattern", + Justification = "GetRuntimeProperties also enumerates non-public properties, but only public settable properties of the metadata view are populated (and those are preserved by the PublicProperties annotation). Non-public properties are not part of the supported metadata-view surface.")] + public static Func, TMetadata> GetMetadataViewProvider<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TMetadata>() { if (typeof(TMetadata) == typeof(IDictionary)) { diff --git a/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs b/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs index dc47e13f7..3e9547efe 100644 --- a/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs +++ b/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs @@ -26,6 +26,14 @@ internal class StronglyTypedMetaRegistrationSource : IRegistrationSource public bool IsAdapterForIndividualComponents => true; /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "This source is registered for every container but only builds the Meta registration via MakeGenericMethod when a consumer actually resolves that relationship. The closed-type resolve path never reaches this. Consumers that resolve Meta over value-type arguments take on the dynamic-code requirement.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2060:MakeGenericMethod", + Justification = "The generic arguments are the relationship's value and metadata types supplied by the consumer at resolve time; preserving them is the responsibility of the consumer that resolves the relationship.")] public IEnumerable RegistrationsFor(Service service, Func> registrationAccessor) { if (registrationAccessor == null) @@ -67,7 +75,8 @@ public override string ToString() return MetaRegistrationSourceResources.StronglyTypedMetaRegistrationSourceDescription; } - private static IComponentRegistration CreateMetaRegistration(Service providedService, Service valueService, ServiceRegistration implementation) + [RequiresDynamicCode("Meta builds a strongly-typed metadata view at runtime via expression compilation; only reached when a consumer resolves the relationship.")] + private static IComponentRegistration CreateMetaRegistration(Service providedService, Service valueService, ServiceRegistration implementation) { var metadataProvider = MetadataViewProvider.GetMetadataViewProvider(); diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs index 47eaf094f..0ea1056e6 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs @@ -25,6 +25,10 @@ internal static class OpenGenericServiceBinder /// The built closed generic services. /// True if the closed generic service can be bound. False otherwise. [RequiresDynamicCode(OpenGenericDynamicCodeWarning)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2055:MakeGenericType", + Justification = "Constructs the closed implementation and service types from the open generic registration's runtime type arguments. The implementation type's activatable members are preserved by its [DynamicallyAccessedMembers] annotation; the consumer that registered the open generic is responsible for the closed type.")] [SuppressMessage("CA1851", "CA1851", Justification = "The CPU cost in enumerating the list of services is low, while allocating a new list saves little in CPU but costs a lot in allocations.")] public static bool TryBindOpenGenericTypedService( IServiceWithType serviceWithType, @@ -118,6 +122,10 @@ public static bool TryBindOpenGenericDelegateService( /// /// The open generic implementation type. /// The set of open generic services. + [UnconditionalSuppressMessage( + "Trimming", + "IL2070:UnrecognizedReflectionPattern", + Justification = "Inspects the interfaces of the open generic implementation type to validate it can implement the configured services. The implementation type comes from an open generic registration (RegisterGeneric / open generic scanning), whose consumer is responsible for preserving its interfaces.")] public static void EnforceBindable(Type implementationType, IEnumerable services) { if (implementationType == null) @@ -166,6 +174,10 @@ private static bool IsClosedGenericType(Type serviceType) => serviceType.IsGenericType && !serviceType.IsGenericTypeDefinition; [RequiresDynamicCode(OpenGenericDynamicCodeWarning)] + [UnconditionalSuppressMessage( + "Trimming", + "IL2055:MakeGenericType", + Justification = "Constructs the closed service types from the open generic registration's runtime type arguments. The consumer that registered the open generic is responsible for preserving the closed types.")] private static Service[] BuildImplementedServices(IEnumerable configuredOpenGenericServices, Type[] serviceGenericArguments) { var serviceGenericArgumentsLength = serviceGenericArguments.Length; @@ -188,6 +200,10 @@ private static Service[] BuildImplementedServices(IEnumerable configure private static Type GetGenericTypeDefinition(Type type) => ReflectionCacheSet.Shared.Internal.GenericTypeDefinitionByType.GetOrAdd(type, static t => t.GetGenericTypeDefinition()); + [UnconditionalSuppressMessage( + "Trimming", + "IL2070:UnrecognizedReflectionPattern", + Justification = "Inspects the interfaces of the open generic implementation type to map its generic arguments. The implementation type comes from an open generic registration whose consumer is responsible for preserving its interfaces.")] private static Type?[] TryMapImplementationGenericArguments(Type implementationType, Type serviceType, Type serviceTypeDefinition, Type[] serviceGenericArguments) { if (serviceTypeDefinition == implementationType) diff --git a/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs b/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs index 56eacbf58..15aef0f5b 100644 --- a/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs +++ b/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs @@ -59,6 +59,14 @@ public class ContravariantRegistrationSource : IRegistrationSource /// additional services, along with the implementation of s. It is not an error to return components /// that do not implement . /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "The contravariant registration source is opt-in (registered via RegisterSource). It constructs candidate closed generic types via MakeGenericType only when a consumer resolves a contravariant interface. The consumer that opted into contravariance takes on the dynamic-code requirement.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2055:MakeGenericType", + Justification = "The constructed types are candidate closed generics over the contravariant type's base/interface types, used only to probe for existing registrations. Preserving them is the responsibility of the consumer that opted into contravariant resolution.")] public IEnumerable RegistrationsFor( Service service, Func> registrationAccessor) @@ -113,13 +121,17 @@ private static Type[] SubstituteArrayElementAt(Type[] array, Type newElement, in return copy; } - private static IEnumerable GetTypesAssignableFrom(Type type) + private static IEnumerable GetTypesAssignableFrom([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) { return GetBagOfTypesAssignableFrom(type) .Distinct(); } - private static IEnumerable GetBagOfTypesAssignableFrom(Type type) + [UnconditionalSuppressMessage( + "Trimming", + "IL2070:UnrecognizedReflectionPattern", + Justification = "Enumerates base types and interfaces of a contravariant type argument supplied by the consumer at resolve time. The walk recurses into base/interface types which cannot carry static annotations; contravariant resolution is an opt-in feature whose registered types the consumer is responsible for preserving.")] + private static IEnumerable GetBagOfTypesAssignableFrom([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) { if (type.BaseType is not null) { From 3f81bfa6e778e880f61507d4d3b0825204b63f97 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 10:56:50 -0700 Subject: [PATCH 06/14] Phase 2e: Annotations complete. --- .../Reflection/AutowiringPropertyInjector.cs | 12 ++++++++++++ .../Reflection/DefaultConstructorFinder.cs | 10 ++++++---- .../Reflection/DefaultValueParameter.cs | 4 ++++ .../GeneratedFactories/FactoryGenerator.cs | 8 ++++++-- .../GeneratedFactoryActivatorData.cs | 7 ++++++- .../GeneratedFactoryRegistrationExtensions.cs | 2 +- .../OpenGenericDecoratorActivatorData.cs | 3 ++- .../OpenGenericRegistrationExtensions.cs | 2 +- .../OpenGenerics/OpenGenericServiceBinder.cs | 4 ++-- ...nyConcreteTypeNotAlreadyRegisteredSource.cs | 4 ++++ .../Features/Scanning/AssemblyExtensions.cs | 4 ++++ ...penGenericScanningRegistrationExtensions.cs | 16 ++++++++++++++++ .../ContravariantRegistrationSource.cs | 8 ++++++++ src/Autofac/Module.cs | 8 ++++++++ src/Autofac/ModuleRegistrationExtensions.cs | 8 ++++++++ .../RegistrationExtensions.AssemblyScanning.cs | 18 +++++++++++++++--- .../RegistrationExtensions.Composite.cs | 3 ++- .../RegistrationExtensions.Decorators.cs | 13 +++++++++---- ...onExtensions.OpenGenericAssemblyScanning.cs | 8 ++++++++ src/Autofac/RegistrationExtensions.cs | 8 ++++++++ src/Autofac/Util/AssemblyExtensions.cs | 1 + src/Autofac/Util/InternalTypeExtensions.cs | 8 ++++++++ src/Autofac/Util/LinkerAttributes.cs | 12 ++++++++++++ 23 files changed, 151 insertions(+), 20 deletions(-) diff --git a/src/Autofac/Core/Activators/Reflection/AutowiringPropertyInjector.cs b/src/Autofac/Core/Activators/Reflection/AutowiringPropertyInjector.cs index 6ea6377c2..e9e113586 100644 --- a/src/Autofac/Core/Activators/Reflection/AutowiringPropertyInjector.cs +++ b/src/Autofac/Core/Activators/Reflection/AutowiringPropertyInjector.cs @@ -109,6 +109,10 @@ public static void InjectProperties(IComponentContext context, object instance, } } + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "instanceType is the runtime type of an already-constructed instance being property-injected. Its public properties are preserved by the activation contract (ActivatorMemberTypes) for reflection-activated types; for externally-provided instances the caller that opted into property injection is responsible for preserving them.")] private static IEnumerable GetInjectableProperties(Type instanceType) { foreach (var property in instanceType.GetRuntimeProperties()) @@ -169,6 +173,14 @@ private static bool IsUnsupportedPropertyType(Type propertyType) } [SuppressMessage("S125", "S125", Justification = "Commented code explains the code generation output.")] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Builds a strongly-typed property setter delegate via MakeGenericType/MakeGenericMethod over the property's declaring and value types. Reachable only when property injection is configured for a registration; the consumer that opted into property injection takes on the dynamic-code requirement for value-typed properties.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2060:MakeGenericMethod", + Justification = "The generic arguments are the property's declaring type and value type, both reachable from the property being injected. Preserving them is the responsibility of the consumer that opted into property injection.")] private static Action MakeFastPropertySetter(PropertyInfo propertyInfo) { // SetMethod will be non-null if we're trying to make a setter for it. diff --git a/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs b/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs index 5a358a7e9..e564a7a9e 100644 --- a/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs +++ b/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs @@ -42,11 +42,13 @@ public ConstructorInfo[] FindConstructors(Type targetType) return _finder(targetType); } - private static ConstructorInfo[] GetDefaultPublicConstructors([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type type) + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "This is the default constructor finder used by reflection activation. The target type's public constructors are preserved by the activation contract (ActivatorMemberTypes) flowed through the registration APIs. The finder is invoked via a Func delegate which cannot carry the annotation, so the requirement is asserted here.")] + [SuppressMessage("Major Code Smell", "S6612:The lambda parameter should be used instead of capturing arguments", Justification = "The factory deliberately reads the [DynamicallyAccessedMembers]-annotated 'type' local rather than the unannotated lambda parameter so the trimming contract flows into GetDeclaredPublicConstructors.")] + private static ConstructorInfo[] GetDefaultPublicConstructors(Type type) { - // The cache key is the (annotated) target type; the factory reads the annotated - // local rather than the dictionary's unannotated key parameter so the - // [DynamicallyAccessedMembers] contract flows into GetDeclaredPublicConstructors. return ReflectionCacheSet.Shared.Internal.DefaultPublicConstructors .GetOrAdd(type, _ => type.GetDeclaredPublicConstructors()); } diff --git a/src/Autofac/Core/Activators/Reflection/DefaultValueParameter.cs b/src/Autofac/Core/Activators/Reflection/DefaultValueParameter.cs index d35d15188..14c570acb 100644 --- a/src/Autofac/Core/Activators/Reflection/DefaultValueParameter.cs +++ b/src/Autofac/Core/Activators/Reflection/DefaultValueParameter.cs @@ -23,6 +23,10 @@ public class DefaultValueParameter : Parameter /// /// Thrown if is . /// + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "Activator.CreateInstance is only called for value types (pi.ParameterType.IsValueType), which always have an implicit parameterless constructor and require no metadata preservation or dynamic code to default-construct.")] public override bool CanSupplyValue(ParameterInfo pi, IComponentContext context, [NotNullWhen(returnValue: true)] out Func? valueProvider) { if (pi == null) diff --git a/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs b/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs index d09655f0a..f8b9c89e8 100644 --- a/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs +++ b/src/Autofac/Features/GeneratedFactories/FactoryGenerator.cs @@ -49,9 +49,11 @@ public FactoryGenerator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp // to share across all FactoryGenerator instances with the same structural signature. // Accessing ReflectionCacheSet.Shared at point-of-use (not stored in a field) ensures // correct weak-reference collection of the cache set. + // The factory reads the annotated delegateType local rather than the cache key's + // unannotated Item1 so the [DynamicallyAccessedMembers] contract flows into the generator. var compiledGenerator = ReflectionCacheSet.Shared.Internal.GeneratedFactoryServiceOnlyGenerators.GetOrAdd( (delegateType, pm), - static key => CreateServiceOnlyGenerator(key.Item1, key.Item2)); + key => CreateServiceOnlyGenerator(delegateType, key.Item2)); // Close over the specific service so _generator matches the expected signature. _generator = (context, parameters) => compiledGenerator(service, context, parameters); @@ -79,9 +81,11 @@ public FactoryGenerator([DynamicallyAccessedMembers(DynamicallyAccessedMemberTyp // captures and is safe to share across all FactoryGenerator instances for the same delegate type. // Accessing ReflectionCacheSet.Shared at point-of-use (not stored in a field) ensures // correct weak-reference collection of the cache set. + // The factory reads the annotated delegateType local rather than the cache key's + // unannotated Item1 so the [DynamicallyAccessedMembers] contract flows into the generator. var compiledGenerator = ReflectionCacheSet.Shared.Internal.GeneratedFactoryServiceRegistrationGenerators.GetOrAdd( (delegateType, pm), - static key => CreateServiceRegistrationGenerator(key.Item1, key.Item2)); + key => CreateServiceRegistrationGenerator(delegateType, key.Item2)); // Close over the specific service and product registration so _generator matches the // expected signature. These values are instance-specific but are not baked into the diff --git a/src/Autofac/Features/GeneratedFactories/GeneratedFactoryActivatorData.cs b/src/Autofac/Features/GeneratedFactories/GeneratedFactoryActivatorData.cs index 2fbd78b83..0cea67625 100644 --- a/src/Autofac/Features/GeneratedFactories/GeneratedFactoryActivatorData.cs +++ b/src/Autofac/Features/GeneratedFactories/GeneratedFactoryActivatorData.cs @@ -13,6 +13,7 @@ namespace Autofac.Features.GeneratedFactories; [Obsolete("Update your code to use the Func implicit relationship or delegate factories. See https://autofac.readthedocs.io/en/latest/resolve/relationships.html and https://autofac.readthedocs.io/en/latest/advanced/delegate-factories.html for more information.")] public class GeneratedFactoryActivatorData : IConcreteActivatorData { + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] private readonly Type _delegateType; private readonly Service _productService; @@ -21,7 +22,7 @@ public class GeneratedFactoryActivatorData : IConcreteActivatorData /// /// The type of the factory. /// The service used to provide the products of the factory. - public GeneratedFactoryActivatorData(Type delegateType, Service productService) + public GeneratedFactoryActivatorData([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType, Service productService) { _delegateType = delegateType ?? throw new ArgumentNullException(nameof(delegateType)); _productService = productService ?? throw new ArgumentNullException(nameof(productService)); @@ -38,6 +39,10 @@ public GeneratedFactoryActivatorData(Type delegateType, Service productService) /// /// Gets the activator data that can provide an IInstanceActivator instance. /// + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "This obsolete activator data implements IConcreteActivatorData (which cannot be marked [RequiresDynamicCode]). It compiles a factory delegate via FactoryGenerator's expression tree; the dynamic-code requirement is surfaced on the [Obsolete] RegisterGeneratedFactory APIs that create this data.")] public IInstanceActivator Activator { get diff --git a/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationExtensions.cs b/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationExtensions.cs index 68f586e0c..4e28f0cf2 100644 --- a/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationExtensions.cs +++ b/src/Autofac/Features/GeneratedFactories/GeneratedFactoryRegistrationExtensions.cs @@ -24,7 +24,7 @@ internal static class GeneratedFactoryRegistrationExtensions /// Factory delegates are provided automatically in Autofac 2, and /// this method is generally not required. internal static IRegistrationBuilder - RegisterGeneratedFactory(ContainerBuilder builder, Type delegateType, Service service) + RegisterGeneratedFactory(ContainerBuilder builder, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] Type delegateType, Service service) where TLimit : notnull { var activatorData = new GeneratedFactoryActivatorData(delegateType, service); diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorActivatorData.cs b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorActivatorData.cs index b3005f652..9fb044c1d 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorActivatorData.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorActivatorData.cs @@ -4,6 +4,7 @@ using System.Globalization; using Autofac.Builder; using Autofac.Core; +using Autofac.Util; namespace Autofac.Features.OpenGenerics; @@ -17,7 +18,7 @@ public class OpenGenericDecoratorActivatorData : ReflectionActivatorData /// /// The decorator type. /// The open generic service type to decorate. - public OpenGenericDecoratorActivatorData(Type implementer, IServiceWithType fromService) + public OpenGenericDecoratorActivatorData([DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type implementer, IServiceWithType fromService) : base(implementer) { if (fromService == null) diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs index 5fd33ad78..e7b5d388b 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationExtensions.cs @@ -123,7 +123,7 @@ public static IRegistrationBuilderService key or name given to the decorated components. /// The decorator registration for further configuration. public static IRegistrationBuilder - RegisterGenericDecorator(ContainerBuilder builder, Type decoratorType, Type decoratedServiceType, object fromKey, object? toKey) + RegisterGenericDecorator(ContainerBuilder builder, [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type decoratorType, Type decoratedServiceType, object fromKey, object? toKey) { if (builder == null) { diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs index 0ea1056e6..e51c7e948 100644 --- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs +++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs @@ -124,7 +124,7 @@ public static bool TryBindOpenGenericDelegateService( /// The set of open generic services. [UnconditionalSuppressMessage( "Trimming", - "IL2070:UnrecognizedReflectionPattern", + "IL2067:UnrecognizedReflectionPattern", Justification = "Inspects the interfaces of the open generic implementation type to validate it can implement the configured services. The implementation type comes from an open generic registration (RegisterGeneric / open generic scanning), whose consumer is responsible for preserving its interfaces.")] public static void EnforceBindable(Type implementationType, IEnumerable services) { @@ -202,7 +202,7 @@ private static Service[] BuildImplementedServices(IEnumerable configure [UnconditionalSuppressMessage( "Trimming", - "IL2070:UnrecognizedReflectionPattern", + "IL2067:UnrecognizedReflectionPattern", Justification = "Inspects the interfaces of the open generic implementation type to map its generic arguments. The implementation type comes from an open generic registration whose consumer is responsible for preserving its interfaces.")] private static Type?[] TryMapImplementationGenericArguments(Type implementationType, Type serviceType, Type serviceTypeDefinition, Type[] serviceGenericArguments) { diff --git a/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs b/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs index 982e5402c..70b04465e 100644 --- a/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs +++ b/src/Autofac/Features/ResolveAnything/AnyConcreteTypeNotAlreadyRegisteredSource.cs @@ -58,6 +58,10 @@ public ActionThe service that was requested. /// A function that will return existing registrations for a service. /// Registrations providing the service. + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "This source is opt-in (registered via RegisterSource / RegisterTypesThatAreNotAlreadyRegistered) and reflection-activates whatever concrete type is requested at resolve time. The requested types cannot be statically known; the consumer that opted into resolve-anything is responsible for preserving the constructible types they resolve.")] public IEnumerable RegistrationsFor( Service service, Func> registrationAccessor) diff --git a/src/Autofac/Features/Scanning/AssemblyExtensions.cs b/src/Autofac/Features/Scanning/AssemblyExtensions.cs index dedc35c95..91bc910be 100644 --- a/src/Autofac/Features/Scanning/AssemblyExtensions.cs +++ b/src/Autofac/Features/Scanning/AssemblyExtensions.cs @@ -19,6 +19,10 @@ internal static class AssemblyExtensions /// /// The assembly to load types from. /// The set of loadable types. + [UnconditionalSuppressMessage( + "Trimming", + "IL2026:RequiresUnreferencedCode", + Justification = "This is internal assembly-scanning infrastructure reached only from the public scanning APIs (RegisterAssemblyTypes etc.), which already carry [RequiresUnreferencedCode]. The consumer that opted into scanning is responsible for preserving the scanned types.")] internal static IEnumerable GetPermittedTypesForAssemblyScanning(this Assembly assembly) { static IReadOnlyList Uncached(Assembly assembly) diff --git a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs index 88a6201a5..a10a31c32 100644 --- a/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs +++ b/src/Autofac/Features/Scanning/OpenGenericScanningRegistrationExtensions.cs @@ -52,6 +52,14 @@ public static IRegistrationBuilderRegistration to set service mapping on. /// Function mapping types to services. /// Registration builder allowing the registration to be configured. + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "Operates on open generic implementation types discovered by assembly scanning, whose public entry points already carry [RequiresUnreferencedCode]. Preserving the interfaces of scanned types is the responsibility of the consumer that opted into scanning.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "Operates on the scanned implementation type's interfaces, discovered by assembly scanning whose public entry points already carry [RequiresUnreferencedCode]. Preserving them is the responsibility of the consumer that opted into scanning.")] public static IRegistrationBuilder As( IRegistrationBuilder registration, @@ -94,6 +102,10 @@ public static IRegistrationBuilderThe registration builder. /// The type or interface which all classes must be assignable from. /// The registration builder for continued configuration. + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "Operates on candidate types discovered by assembly scanning, whose public entry points already carry [RequiresUnreferencedCode]. Preserving the interfaces of scanned types is the responsibility of the consumer that opted into scanning.")] public static IRegistrationBuilder AssignableTo( IRegistrationBuilder registration, @@ -146,6 +158,10 @@ public static IRegistrationBuilderThe type or interface which all classes must be assignable from. /// A function to determine the service key for a given type. /// The registration builder for continued configuration. + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "Operates on candidate types discovered by assembly scanning, whose public entry points already carry [RequiresUnreferencedCode]. Preserving the interfaces of scanned types is the responsibility of the consumer that opted into scanning.")] public static IRegistrationBuilder AssignableTo( IRegistrationBuilder registration, diff --git a/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs b/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs index 15aef0f5b..700af315a 100644 --- a/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs +++ b/src/Autofac/Features/Variance/ContravariantRegistrationSource.cs @@ -67,6 +67,10 @@ public class ContravariantRegistrationSource : IRegistrationSource "Trimming", "IL2055:MakeGenericType", Justification = "The constructed types are candidate closed generics over the contravariant type's base/interface types, used only to probe for existing registrations. Preserving them is the responsibility of the consumer that opted into contravariant resolution.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2062:UnrecognizedReflectionPattern", + Justification = "The contravariant type argument is supplied by the consumer at resolve time and its base/interface walk operates on runtime types that cannot carry static annotations. Preserving them is the responsibility of the consumer that opted into contravariant resolution.")] public IEnumerable RegistrationsFor( Service service, Func> registrationAccessor) @@ -131,6 +135,10 @@ private static IEnumerable GetTypesAssignableFrom([DynamicallyAccessedMemb "Trimming", "IL2070:UnrecognizedReflectionPattern", Justification = "Enumerates base types and interfaces of a contravariant type argument supplied by the consumer at resolve time. The walk recurses into base/interface types which cannot carry static annotations; contravariant resolution is an opt-in feature whose registered types the consumer is responsible for preserving.")] + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "The recursive walk feeds base/interface types (runtime values that cannot carry static annotations) back into itself. Contravariant resolution is opt-in and the consumer is responsible for preserving its registered types.")] private static IEnumerable GetBagOfTypesAssignableFrom([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type) { if (type.BaseType is not null) diff --git a/src/Autofac/Module.cs b/src/Autofac/Module.cs index b88635cbe..693c2630c 100644 --- a/src/Autofac/Module.cs +++ b/src/Autofac/Module.cs @@ -125,6 +125,10 @@ protected virtual void AttachToRegistrationSource( { } + [UnconditionalSuppressMessage( + "Trimming", + "IL2070:UnrecognizedReflectionPattern", + Justification = "Reflects over the module's own runtime type to detect whether the well-known virtual method AttachToComponentRegistration has been overridden. The method is declared on Module (this assembly) and is always present; a derived module that overrides it inherently keeps its own method.")] private void AttachToRegistrations(IComponentRegistryBuilder componentRegistry) { if (componentRegistry == null) @@ -156,6 +160,10 @@ private void AttachToRegistrations(IComponentRegistryBuilder componentRegistry) } } + [UnconditionalSuppressMessage( + "Trimming", + "IL2070:UnrecognizedReflectionPattern", + Justification = "Reflects over the module's own runtime type to detect whether the well-known virtual method AttachToRegistrationSource has been overridden. The method is declared on Module (this assembly) and is always present; a derived module that overrides it inherently keeps its own method.")] private void AttachToSources(IComponentRegistryBuilder componentRegistry) { if (componentRegistry == null) diff --git a/src/Autofac/ModuleRegistrationExtensions.cs b/src/Autofac/ModuleRegistrationExtensions.cs index 54774f447..238338013 100644 --- a/src/Autofac/ModuleRegistrationExtensions.cs +++ b/src/Autofac/ModuleRegistrationExtensions.cs @@ -12,6 +12,8 @@ namespace Autofac; /// public static class ModuleRegistrationExtensions { + private const string AssemblyScanningWarning = "Registering modules by scanning assemblies is not compatible with member-level trimming; the linker cannot determine which module types to preserve."; + /// /// Registers modules found in an assembly. /// @@ -24,6 +26,7 @@ public static class ModuleRegistrationExtensions /// The to allow /// additional chained module registrations. /// + [RequiresUnreferencedCode(AssemblyScanningWarning)] public static IModuleRegistrar RegisterAssemblyModules(this ContainerBuilder builder, params Assembly[] assemblies) { if (builder == null) @@ -47,6 +50,7 @@ public static IModuleRegistrar RegisterAssemblyModules(this ContainerBuilder bui /// The to allow /// additional chained module registrations. /// + [RequiresUnreferencedCode(AssemblyScanningWarning)] public static IModuleRegistrar RegisterAssemblyModules(this IModuleRegistrar registrar, params Assembly[] assemblies) { if (registrar == null) @@ -70,6 +74,7 @@ public static IModuleRegistrar RegisterAssemblyModules(this IModuleRegistrar reg /// The to allow /// additional chained module registrations. /// + [RequiresUnreferencedCode(AssemblyScanningWarning)] public static IModuleRegistrar RegisterAssemblyModules(this ContainerBuilder builder, params Assembly[] assemblies) where TModule : IModule { @@ -95,6 +100,7 @@ public static IModuleRegistrar RegisterAssemblyModules(this ContainerBu /// The to allow /// additional chained module registrations. /// + [RequiresUnreferencedCode(AssemblyScanningWarning)] public static IModuleRegistrar RegisterAssemblyModules(this IModuleRegistrar registrar, params Assembly[] assemblies) where TModule : IModule { @@ -119,6 +125,7 @@ public static IModuleRegistrar RegisterAssemblyModules(this IModuleRegi /// The to allow /// additional chained module registrations. /// + [RequiresUnreferencedCode(AssemblyScanningWarning)] public static IModuleRegistrar RegisterAssemblyModules(this ContainerBuilder builder, Type moduleType, params Assembly[] assemblies) { if (builder == null) @@ -148,6 +155,7 @@ public static IModuleRegistrar RegisterAssemblyModules(this ContainerBuilder bui /// The to allow /// additional chained module registrations. /// + [RequiresUnreferencedCode(AssemblyScanningWarning)] public static IModuleRegistrar RegisterAssemblyModules(this IModuleRegistrar registrar, Type moduleType, params Assembly[] assemblies) { if (registrar == null) diff --git a/src/Autofac/RegistrationExtensions.AssemblyScanning.cs b/src/Autofac/RegistrationExtensions.AssemblyScanning.cs index 892748a71..4590010fc 100644 --- a/src/Autofac/RegistrationExtensions.AssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.AssemblyScanning.cs @@ -187,6 +187,10 @@ public static IRegistrationBuilderRegistration limit type. /// Registration to set service mapping on. /// Registration builder allowing the registration to be configured. + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "Maps scanned types to their implemented interfaces. The types are discovered by assembly scanning, whose public entry points already carry [RequiresUnreferencedCode]; the consumer that opted into scanning is responsible for preserving the interfaces of scanned types.")] public static IRegistrationBuilder AsImplementedInterfaces(this IRegistrationBuilder registration) { @@ -205,6 +209,10 @@ public static IRegistrationBuilderActivator data type. /// Registration to set service mapping on. /// Registration builder allowing the registration to be configured. + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "Maps the registered component's limit type to its implemented interfaces. The interfaces of a type the consumer explicitly registered are reachable; the LimitType getter cannot carry the annotation, so the requirement is asserted here.")] public static IRegistrationBuilder AsImplementedInterfaces(this IRegistrationBuilder registration) where TConcreteActivatorData : IConcreteActivatorData @@ -223,6 +231,10 @@ public static IRegistrationBuilderRegistration limit type. /// Registration to set service mapping on. /// Registration builder allowing the registration to be configured. + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "Maps the registered component's implementation type to its implemented interfaces. The interfaces of a type the consumer explicitly registered are reachable; the ImplementationType getter cannot carry the Interfaces annotation, so the requirement is asserted here.")] public static IRegistrationBuilder AsImplementedInterfaces(this IRegistrationBuilder registration) { @@ -371,7 +383,7 @@ public static IRegistrationBuilderThe concrete type to exclude. /// Registration builder allowing the registration to be configured. public static IRegistrationBuilder - Except( + Except<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] T>( this IRegistrationBuilder registration, Action> customizedRegistration) where T : notnull @@ -460,13 +472,13 @@ public static IRegistrationBuilder i != typeof(IDisposable)); return type.IsInterface ? interfaces.AppendItem(type).ToArray() : interfaces.ToArray(); } - private static Type[] GetOpenGenericImplementedInterfaces(this Type @this) + private static Type[] GetOpenGenericImplementedInterfaces([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type @this) { return @this.GetInterfaces() .Where(it => it.IsGenericType) diff --git a/src/Autofac/RegistrationExtensions.Composite.cs b/src/Autofac/RegistrationExtensions.Composite.cs index d84a635d0..dffaac363 100644 --- a/src/Autofac/RegistrationExtensions.Composite.cs +++ b/src/Autofac/RegistrationExtensions.Composite.cs @@ -160,9 +160,10 @@ public static IRegistrationBuilder /// Service type to provide a composite for. /// The composite registration for continued configuration. + [RequiresDynamicCode("Registering an open generic composite constructs closed generic types at runtime via MakeGenericType, which may require dynamic code generation when closed over value types and is not compatible with native AOT.")] public static IRegistrationBuilder RegisterGenericComposite( this ContainerBuilder builder, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type compositeType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type compositeType, Type serviceType) { if (builder == null) diff --git a/src/Autofac/RegistrationExtensions.Decorators.cs b/src/Autofac/RegistrationExtensions.Decorators.cs index 7fc68233f..0c0029a43 100644 --- a/src/Autofac/RegistrationExtensions.Decorators.cs +++ b/src/Autofac/RegistrationExtensions.Decorators.cs @@ -8,6 +8,7 @@ using Autofac.Features.Decorators; using Autofac.Features.LightweightAdapters; using Autofac.Features.OpenGenerics; +using Autofac.Util; namespace Autofac; @@ -17,6 +18,8 @@ namespace Autofac; [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "RegistrationBuilder is where all registration syntax lives.")] public static partial class RegistrationExtensions { + private const string GenericDecoratorDynamicCodeWarning = "Registering an open generic decorator constructs closed generic types at runtime via MakeGenericType, which may require dynamic code generation when closed over value types and is not compatible with native AOT."; + /// /// Decorate all components implementing service /// using the provided function. @@ -125,7 +128,7 @@ public static IRegistrationBuilderService type being decorated. /// Container builder. /// A function that when provided with an instance determines if the decorator should be applied. - public static void RegisterDecorator<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TDecorator, TService>(this ContainerBuilder builder, Func? condition = null) + public static void RegisterDecorator<[DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] TDecorator, TService>(this ContainerBuilder builder, Func? condition = null) where TDecorator : notnull, TService { if (builder == null) @@ -157,7 +160,7 @@ public static IRegistrationBuilderA function that when provided with an instance determines if the decorator should be applied. public static void RegisterDecorator( this ContainerBuilder builder, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type decoratorType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type decoratorType, Type serviceType, Func? condition = null) { @@ -249,10 +252,11 @@ public static void RegisterDecorator( /// Service key or name associated with the components being decorated. /// Service key or name given to the decorated components. /// The decorator registration for continued configuration. + [RequiresDynamicCode(GenericDecoratorDynamicCodeWarning)] public static IRegistrationBuilder RegisterGenericDecorator( this ContainerBuilder builder, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type decoratorType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type decoratorType, Type decoratedServiceType, object fromKey, object? toKey = null) @@ -283,9 +287,10 @@ public static IRegistrationBuilder, which will be set to the instance being decorated. /// Service type being decorated. Must be an open generic type. /// A function that when provided with an instance determines if the decorator should be applied. + [RequiresDynamicCode(GenericDecoratorDynamicCodeWarning)] public static void RegisterGenericDecorator( this ContainerBuilder builder, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type decoratorType, + [DynamicallyAccessedMembers(ActivatorMemberTypes.ActivatedType)] Type decoratorType, Type serviceType, Func? condition = null) { diff --git a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs index 51fda2b17..6b248e02b 100644 --- a/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs +++ b/src/Autofac/RegistrationExtensions.OpenGenericAssemblyScanning.cs @@ -291,6 +291,10 @@ public static IRegistrationBuilderRegistration limit type. /// Registration to set service mapping on. /// Registration builder allowing the registration to be configured. + [UnconditionalSuppressMessage( + "Trimming", + "IL2067:UnrecognizedReflectionPattern", + Justification = "Maps scanned open generic types to their implemented interfaces. The types are discovered by assembly scanning, whose public entry points already carry [RequiresUnreferencedCode]; the consumer that opted into scanning is responsible for preserving the interfaces of scanned types.")] public static IRegistrationBuilder AsImplementedInterfaces(this IRegistrationBuilder registration) { @@ -328,6 +332,10 @@ public static IRegistrationBuilderThe attribute applied to the scanned type. /// Registration to set metadata on. /// Registration builder allowing the registration to be configured. + [UnconditionalSuppressMessage( + "Trimming", + "IL2087:UnrecognizedReflectionPattern", + Justification = "GetRuntimeProperties also enumerates non-public properties, but only readable public properties contribute metadata (preserved by the PublicProperties annotation on TAttribute). Non-public properties are not part of the supported metadata surface.")] public static IRegistrationBuilder WithMetadataFrom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TAttribute>( this IRegistrationBuilder registration) diff --git a/src/Autofac/RegistrationExtensions.cs b/src/Autofac/RegistrationExtensions.cs index a37a4dbb1..a3dc7e524 100644 --- a/src/Autofac/RegistrationExtensions.cs +++ b/src/Autofac/RegistrationExtensions.cs @@ -264,6 +264,10 @@ public static IRegistrationBuilderThe attribute applied to the scanned type. /// Registration to set metadata on. /// Registration builder allowing the registration to be configured. + [UnconditionalSuppressMessage( + "Trimming", + "IL2087:UnrecognizedReflectionPattern", + Justification = "GetRuntimeProperties also enumerates non-public properties, but only readable public properties contribute metadata (preserved by the PublicProperties annotation on TAttribute). Non-public properties are not part of the supported metadata surface.")] public static IRegistrationBuilder WithMetadataFrom<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TAttribute>( this IRegistrationBuilder registration) @@ -395,6 +399,10 @@ public static IRegistrationBuilder PropertiesAut /// Registration to set policy on. /// Constructor signature to match. /// A registration builder allowing further configuration of the component. + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "Validates that a constructor matching the caller-specified signature exists on the implementation type. The activation contract (ActivatorMemberTypes) preserves public constructors; this is a best-effort validation and the activator itself enforces constructor availability at resolve time.")] public static IRegistrationBuilder UsingConstructor( this IRegistrationBuilder registration, diff --git a/src/Autofac/Util/AssemblyExtensions.cs b/src/Autofac/Util/AssemblyExtensions.cs index ede8346f7..bd78f5a65 100644 --- a/src/Autofac/Util/AssemblyExtensions.cs +++ b/src/Autofac/Util/AssemblyExtensions.cs @@ -21,6 +21,7 @@ public static class AssemblyExtensions /// /// Thrown if is . /// + [RequiresUnreferencedCode("Enumerating all types defined in an assembly is not compatible with member-level trimming; the linker cannot determine which types to preserve.")] public static IEnumerable GetLoadableTypes(this Assembly assembly) { // Algorithm from StackOverflow answer here: diff --git a/src/Autofac/Util/InternalTypeExtensions.cs b/src/Autofac/Util/InternalTypeExtensions.cs index 55ae63e5c..27e56471c 100644 --- a/src/Autofac/Util/InternalTypeExtensions.cs +++ b/src/Autofac/Util/InternalTypeExtensions.cs @@ -271,6 +271,10 @@ public static bool IsOpenGenericTypeOf([DynamicallyAccessedMembers(DynamicallyAc // Run IsCompilerGenerated check last due to perf. See AssemblyScanningPerformanceTests.MeasurePerformance. internal static bool MayAllowReflectionActivation(this Type? type, bool allowCompilerGenerated = false) => type is not null && type.IsClass && !type.IsAbstract && !type.IsDelegate() && (allowCompilerGenerated || !type.IsCompilerGenerated()); + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "The generic type definition is derived from the base type's own generic instantiation; only its identity is compared (no members are reflected on it beyond the recursive open-generic check). Reachable through open generic / scanning paths whose consumers preserve their types.")] private static bool CheckBaseTypeIsOpenGenericTypeOf(this Type @this, Type type) { if (@this.BaseType == null) @@ -283,6 +287,10 @@ private static bool CheckBaseTypeIsOpenGenericTypeOf(this Type @this, Type type) : type.IsAssignableFrom(@this.BaseType); } + [UnconditionalSuppressMessage( + "Trimming", + "IL2072:UnrecognizedReflectionPattern", + Justification = "Each interface's generic type definition is derived from the interface's own instantiation; only its identity is compared via the recursive open-generic check. Reachable through open generic / scanning paths whose consumers preserve their types.")] private static bool CheckInterfacesAreOpenGenericTypeOf([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type @this, Type type) { return @this.GetInterfaces() diff --git a/src/Autofac/Util/LinkerAttributes.cs b/src/Autofac/Util/LinkerAttributes.cs index 75a48fdfa..d1dbc6a94 100644 --- a/src/Autofac/Util/LinkerAttributes.cs +++ b/src/Autofac/Util/LinkerAttributes.cs @@ -10,8 +10,20 @@ namespace System.Diagnostics.CodeAnalysis; /// /// Fake version for pre-net-5.0 targets. /// +/// +/// This faithfully mirrors the .NET DynamicallyAccessedMemberTypes enum, which is a +/// enum whose members are deliberately individual bit values +/// (not all of which are pure powers of two, e.g. PublicConstructors). +/// +[Flags] +[SuppressMessage("Major Code Smell", "S4070:Non-flags enums should not be marked with \"FlagsAttribute\"", Justification = "Mirrors the framework DynamicallyAccessedMemberTypes [Flags] enum exactly; the values are combined with bitwise operations throughout.")] internal enum DynamicallyAccessedMemberTypes { + /// + /// Specifies no members. + /// + None = 0x0000, + /// /// Specifies the default, parameterless public constructor. /// From 5e8b45f7c29a79fba26f431efe926f97be596393 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 10:57:39 -0700 Subject: [PATCH 07/14] Add AOT trim enablement flag. --- src/Autofac/Autofac.csproj | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Autofac/Autofac.csproj b/src/Autofac/Autofac.csproj index 9f8e6cd37..7e4689baf 100644 --- a/src/Autofac/Autofac.csproj +++ b/src/Autofac/Autofac.csproj @@ -47,17 +47,18 @@ $(NoWarn);8600;8601;8602;8603;8604 - true - true - true + true From 36bd9e8ce6e47a2cf98c63edd0e68296ac85f4f3 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 14:02:21 -0700 Subject: [PATCH 08/14] Added AOT test suite. --- .github/workflows/aot.yml | 45 +++++ default.proj | 22 +++ .../LazyWithMetadataRegistrationSource.cs | 5 +- .../Features/Metadata/MetadataViewProvider.cs | 7 +- .../StronglyTypedMetaRegistrationSource.cs | 5 +- test/Autofac.Test.Aot/Autofac.Test.Aot.csproj | 40 ++++ test/Autofac.Test.Aot/Program.cs | 173 ++++++++++++++++++ 7 files changed, 292 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/aot.yml create mode 100644 test/Autofac.Test.Aot/Autofac.Test.Aot.csproj create mode 100644 test/Autofac.Test.Aot/Program.cs diff --git a/.github/workflows/aot.yml b/.github/workflows/aot.yml new file mode 100644 index 000000000..672c294f2 --- /dev/null +++ b/.github/workflows/aot.yml @@ -0,0 +1,45 @@ +name: Native AOT Verification + +# Repo-local, additive workflow (the shared org CI in autofac/.github builds and +# tests the solution). This job proves Autofac stays Native-AOT/trim clean: it +# publishes the AOT smoke-test app with PublishAot=true - which runs the ILC +# trim/AOT analyzer and fails on any IL2104/IL3053 because the project treats +# warnings as errors - and then executes the produced native binary, which exits +# non-zero on any runtime failure of the AOT-safe resolve path. + +on: + pull_request: + branches: + - develop + - main + push: + branches: + - develop + - main + - feature/* + tags: + - v[0-9]+.[0-9]+.[0-9]+ + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + verify-aot: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + global-json-file: global.json + + # Native AOT on Linux compiles and links native code, which requires a C + # toolchain and zlib development headers. + - name: Install Native AOT prerequisites + run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev + + - name: Verify Native AOT compatibility + run: dotnet msbuild ./default.proj -t:VerifyAot -p:Configuration=Release diff --git a/default.proj b/default.proj index ec152aed1..9d8b158a6 100644 --- a/default.proj +++ b/default.proj @@ -70,4 +70,26 @@ + + + $([System.IO.Path]::Combine($(MSBuildProjectDirectory),'test/Autofac.Test.Aot')) + $([System.IO.Path]::Combine($(ArtifactDirectory),'aot')) + Autofac.Test.Aot + Autofac.Test.Aot.exe + + + + + + + + diff --git a/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs b/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs index 87f37f732..ead2d206c 100644 --- a/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs +++ b/src/Autofac/Features/LazyDependencies/LazyWithMetadataRegistrationSource.cs @@ -80,7 +80,10 @@ public override string ToString() return LazyWithMetadataRegistrationSourceResources.LazyWithMetadataRegistrationSourceDescription; } - [RequiresDynamicCode("Lazy builds a strongly-typed metadata view at runtime via expression compilation; only reached when a consumer resolves the relationship.")] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Builds a strongly-typed Lazy metadata view via expression compilation. Reached only when a consumer resolves that relationship through this always-registered implicit source (dispatched by relationship type, which cannot surface the requirement at the consumer's call site). Consumers that resolve Lazy take on the dynamic-code requirement.")] [UnconditionalSuppressMessage( "Trimming", "IL2072:UnrecognizedReflectionPattern", diff --git a/src/Autofac/Features/Metadata/MetadataViewProvider.cs b/src/Autofac/Features/Metadata/MetadataViewProvider.cs index f73585cb9..2b274268f 100644 --- a/src/Autofac/Features/Metadata/MetadataViewProvider.cs +++ b/src/Autofac/Features/Metadata/MetadataViewProvider.cs @@ -14,8 +14,6 @@ namespace Autofac.Features.Metadata; /// internal static class MetadataViewProvider { - private const string DynamicCodeWarning = "Strongly-typed metadata views are populated by compiling an expression tree at runtime and using MakeGenericMethod over each property type, which requires dynamic code generation and is not compatible with native AOT."; - private static readonly MethodInfo _getMetadataValueMethod = typeof(MetadataViewProvider).GetDeclaredMethod(nameof(GetMetadataValue)); /// @@ -23,7 +21,10 @@ internal static class MetadataViewProvider /// /// The metadata type. /// A provider function. - [RequiresDynamicCode(DynamicCodeWarning)] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Strongly-typed metadata views compile an expression tree and use MakeGenericMethod over each property type. This is only reached when a consumer resolves a Meta / Lazy relationship (an always-registered implicit source dispatched by relationship type, which cannot surface the requirement at the consumer's call site). Consumers that resolve strongly-typed metadata views take on the dynamic-code requirement.")] [UnconditionalSuppressMessage( "Trimming", "IL2060:MakeGenericMethod", diff --git a/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs b/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs index 3e9547efe..35d0af7b9 100644 --- a/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs +++ b/src/Autofac/Features/Metadata/StronglyTypedMetaRegistrationSource.cs @@ -75,7 +75,10 @@ public override string ToString() return MetaRegistrationSourceResources.StronglyTypedMetaRegistrationSourceDescription; } - [RequiresDynamicCode("Meta builds a strongly-typed metadata view at runtime via expression compilation; only reached when a consumer resolves the relationship.")] + [UnconditionalSuppressMessage( + "AOT", + "IL3050:RequiresDynamicCode", + Justification = "Builds a strongly-typed Meta metadata view via expression compilation. Reached only when a consumer resolves that relationship through this always-registered implicit source (dispatched by relationship type, which cannot surface the requirement at the consumer's call site). Consumers that resolve Meta take on the dynamic-code requirement.")] private static IComponentRegistration CreateMetaRegistration(Service providedService, Service valueService, ServiceRegistration implementation) { var metadataProvider = MetadataViewProvider.GetMetadataViewProvider(); diff --git a/test/Autofac.Test.Aot/Autofac.Test.Aot.csproj b/test/Autofac.Test.Aot/Autofac.Test.Aot.csproj new file mode 100644 index 000000000..147918231 --- /dev/null +++ b/test/Autofac.Test.Aot/Autofac.Test.Aot.csproj @@ -0,0 +1,40 @@ + + + + Exe + net10.0 + latest + enable + enable + false + + false + + true + false + + true + true + + + + + diff --git a/test/Autofac.Test.Aot/Program.cs b/test/Autofac.Test.Aot/Program.cs new file mode 100644 index 000000000..aa4f26f41 --- /dev/null +++ b/test/Autofac.Test.Aot/Program.cs @@ -0,0 +1,173 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac; + +// Native AOT smoke test. +// +// Exercises the surface of Autofac that is documented as AOT/trim-safe: registering +// concrete types, resolving by generic type and by System.Type, constructor injection, +// property injection, parameterized resolution with TypedParameter, and child scopes. +// +// Every check throws on failure; the process returns a non-zero exit code so a CI step +// can assert success simply by checking the exit code of the published native binary. +// +// This deliberately does NOT touch the dynamic-code surface (open generics, collections, +// Lazy/Meta, generated factories, assembly scanning). Those are annotated +// [RequiresDynamicCode]/[RequiresUnreferencedCode] and are expected to warn at the call +// site under AOT; exercising them here would defeat the warning-free goal of this test. +var failures = new List(); + +void Check(string name, Func assertion) +{ + try + { + if (!assertion()) + { + failures.Add(name); + Console.Error.WriteLine($"FAIL: {name}"); + } + else + { + Console.WriteLine($"PASS: {name}"); + } + } + catch (Exception ex) + { + failures.Add($"{name} ({ex.GetType().Name}: {ex.Message})"); + Console.Error.WriteLine($"FAIL: {name} -> {ex}"); + } +} + +var builder = new ContainerBuilder(); +builder.RegisterType().As(); +builder.RegisterType(); +builder.RegisterType().PropertiesAutowired(); +builder.RegisterType(); +builder.RegisterType().Keyed("special"); + +using var container = builder.Build(); + +// Resolve() of an interface backed by a concrete implementation. +Check("Resolve", () => container.Resolve() is Dependency); + +// Resolve(Type) - the closed-Type path the source generator depends on. +Check("Resolve(typeof(IDependency))", () => container.Resolve(typeof(IDependency)) is Dependency); + +// Constructor injection of a dependency. +Check("Constructor injection", () => +{ + var consumer = container.Resolve(); + return consumer.Dependency is Dependency; +}); + +// Property injection (a key Autofac feature beyond MEDI). +Check("Property injection", () => +{ + var consumer = container.Resolve(); + return consumer.Dependency is Dependency; +}); + +// Parameterized resolution with TypedParameter (used by the generated aggregate code). +Check("Resolve with TypedParameter", () => +{ + var resolved = (ParameterizedConsumer)container.Resolve( + typeof(ParameterizedConsumer), + new[] { new TypedParameter(typeof(string), "configured") }); + return resolved.Value == "configured"; +}); + +// Keyed resolution by Type. +Check("ResolveKeyed(Type)", () => container.ResolveKeyed("special", typeof(IDependency)) is KeyedDependency); + +// Child lifetime scope resolution. +Check("Child scope resolution", () => +{ + using var scope = container.BeginLifetimeScope(); + return scope.Resolve() is Dependency; +}); + +// Child scope with additional registrations. +Check("Child scope with registrations", () => +{ + using var scope = container.BeginLifetimeScope(b => b.RegisterType().AsSelf()); + return scope.Resolve() is not null; +}); + +if (failures.Count > 0) +{ + Console.Error.WriteLine($"\n{failures.Count} AOT smoke check(s) failed:"); + foreach (var failure in failures) + { + Console.Error.WriteLine($" - {failure}"); + } + + return 1; +} + +Console.WriteLine("\nAll AOT smoke checks passed."); +return 0; + +#pragma warning disable SA1402 // File may only contain a single type - smoke test fixtures. + +/// +/// A simple service contract used to verify interface-to-implementation resolution. +/// +internal interface IDependency +{ +} + +/// +/// Default implementation of . +/// +internal sealed class Dependency : IDependency +{ +} + +/// +/// Keyed implementation of used to verify keyed resolution. +/// +internal sealed class KeyedDependency : IDependency +{ +} + +/// +/// Verifies constructor injection through reflection activation. +/// +internal sealed class ConstructorConsumer +{ + public ConstructorConsumer(IDependency dependency) + { + Dependency = dependency; + } + + public IDependency Dependency { get; } +} + +/// +/// Verifies public-property injection (preserved by the AOT contract). +/// +internal sealed class PropertyConsumer +{ + public IDependency? Dependency { get; set; } +} + +/// +/// Verifies resolution with an explicit . +/// +internal sealed class ParameterizedConsumer +{ + public ParameterizedConsumer(string value) + { + Value = value; + } + + public string Value { get; } +} + +/// +/// Registered only inside a child lifetime scope. +/// +internal sealed class ScopedService +{ +} From 6a750cf43051b3ab0d8af8e64adc152e064df8f6 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 15:03:17 -0700 Subject: [PATCH 09/14] Added AOT test project. --- test/Autofac.Test.Aot/Autofac.Test.Aot.csproj | 36 +++++++---- test/Autofac.Test.Aot/ConstructorConsumer.cs | 18 ++++++ test/Autofac.Test.Aot/Dependency.cs | 9 +++ test/Autofac.Test.Aot/IDependency.cs | 9 +++ test/Autofac.Test.Aot/KeyedDependency.cs | 9 +++ .../Autofac.Test.Aot/ParameterizedConsumer.cs | 20 ++++++ test/Autofac.Test.Aot/Program.cs | 64 ------------------- test/Autofac.Test.Aot/PropertyConsumer.cs | 13 ++++ test/Autofac.Test.Aot/ScopedService.cs | 9 +++ 9 files changed, 111 insertions(+), 76 deletions(-) create mode 100644 test/Autofac.Test.Aot/ConstructorConsumer.cs create mode 100644 test/Autofac.Test.Aot/Dependency.cs create mode 100644 test/Autofac.Test.Aot/IDependency.cs create mode 100644 test/Autofac.Test.Aot/KeyedDependency.cs create mode 100644 test/Autofac.Test.Aot/ParameterizedConsumer.cs create mode 100644 test/Autofac.Test.Aot/PropertyConsumer.cs create mode 100644 test/Autofac.Test.Aot/ScopedService.cs diff --git a/test/Autofac.Test.Aot/Autofac.Test.Aot.csproj b/test/Autofac.Test.Aot/Autofac.Test.Aot.csproj index 147918231..b1bbb2538 100644 --- a/test/Autofac.Test.Aot/Autofac.Test.Aot.csproj +++ b/test/Autofac.Test.Aot/Autofac.Test.Aot.csproj @@ -2,31 +2,40 @@ Exe net10.0 + $(NoWarn);CS1591 + true + ../../Autofac.snk + true + true + ../../build/Test.ruleset + AllEnabledByDefault + true + false latest enable enable - false false true false @@ -37,4 +46,7 @@ + + + diff --git a/test/Autofac.Test.Aot/ConstructorConsumer.cs b/test/Autofac.Test.Aot/ConstructorConsumer.cs new file mode 100644 index 000000000..11ff0eb90 --- /dev/null +++ b/test/Autofac.Test.Aot/ConstructorConsumer.cs @@ -0,0 +1,18 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Verifies constructor injection through reflection activation. +/// +internal sealed class ConstructorConsumer +{ + public ConstructorConsumer(IDependency dependency) + { + Dependency = dependency; + } + + public IDependency Dependency + { + get; + } +} diff --git a/test/Autofac.Test.Aot/Dependency.cs b/test/Autofac.Test.Aot/Dependency.cs new file mode 100644 index 000000000..1731cb7b9 --- /dev/null +++ b/test/Autofac.Test.Aot/Dependency.cs @@ -0,0 +1,9 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Default implementation of . +/// +internal sealed class Dependency : IDependency +{ +} diff --git a/test/Autofac.Test.Aot/IDependency.cs b/test/Autofac.Test.Aot/IDependency.cs new file mode 100644 index 000000000..c4124139c --- /dev/null +++ b/test/Autofac.Test.Aot/IDependency.cs @@ -0,0 +1,9 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// A simple service contract used to verify interface-to-implementation resolution. +/// +internal interface IDependency +{ +} diff --git a/test/Autofac.Test.Aot/KeyedDependency.cs b/test/Autofac.Test.Aot/KeyedDependency.cs new file mode 100644 index 000000000..cf297910b --- /dev/null +++ b/test/Autofac.Test.Aot/KeyedDependency.cs @@ -0,0 +1,9 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Keyed implementation of used to verify keyed resolution. +/// +internal sealed class KeyedDependency : IDependency +{ +} diff --git a/test/Autofac.Test.Aot/ParameterizedConsumer.cs b/test/Autofac.Test.Aot/ParameterizedConsumer.cs new file mode 100644 index 000000000..7d1c6befa --- /dev/null +++ b/test/Autofac.Test.Aot/ParameterizedConsumer.cs @@ -0,0 +1,20 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac; + +/// +/// Verifies resolution with an explicit . +/// +internal sealed class ParameterizedConsumer +{ + public ParameterizedConsumer(string value) + { + Value = value; + } + + public string Value + { + get; + } +} diff --git a/test/Autofac.Test.Aot/Program.cs b/test/Autofac.Test.Aot/Program.cs index aa4f26f41..f937abe8e 100644 --- a/test/Autofac.Test.Aot/Program.cs +++ b/test/Autofac.Test.Aot/Program.cs @@ -107,67 +107,3 @@ void Check(string name, Func assertion) Console.WriteLine("\nAll AOT smoke checks passed."); return 0; - -#pragma warning disable SA1402 // File may only contain a single type - smoke test fixtures. - -/// -/// A simple service contract used to verify interface-to-implementation resolution. -/// -internal interface IDependency -{ -} - -/// -/// Default implementation of . -/// -internal sealed class Dependency : IDependency -{ -} - -/// -/// Keyed implementation of used to verify keyed resolution. -/// -internal sealed class KeyedDependency : IDependency -{ -} - -/// -/// Verifies constructor injection through reflection activation. -/// -internal sealed class ConstructorConsumer -{ - public ConstructorConsumer(IDependency dependency) - { - Dependency = dependency; - } - - public IDependency Dependency { get; } -} - -/// -/// Verifies public-property injection (preserved by the AOT contract). -/// -internal sealed class PropertyConsumer -{ - public IDependency? Dependency { get; set; } -} - -/// -/// Verifies resolution with an explicit . -/// -internal sealed class ParameterizedConsumer -{ - public ParameterizedConsumer(string value) - { - Value = value; - } - - public string Value { get; } -} - -/// -/// Registered only inside a child lifetime scope. -/// -internal sealed class ScopedService -{ -} diff --git a/test/Autofac.Test.Aot/PropertyConsumer.cs b/test/Autofac.Test.Aot/PropertyConsumer.cs new file mode 100644 index 000000000..29d3de2b6 --- /dev/null +++ b/test/Autofac.Test.Aot/PropertyConsumer.cs @@ -0,0 +1,13 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Verifies public-property injection (preserved by the AOT contract). +/// +internal sealed class PropertyConsumer +{ + public IDependency? Dependency + { + get; set; + } +} diff --git a/test/Autofac.Test.Aot/ScopedService.cs b/test/Autofac.Test.Aot/ScopedService.cs new file mode 100644 index 000000000..2c2874e19 --- /dev/null +++ b/test/Autofac.Test.Aot/ScopedService.cs @@ -0,0 +1,9 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Registered only inside a child lifetime scope. +/// +internal sealed class ScopedService +{ +} From 5174a565bbfd94b3d69e385971b56f7d48d2d99c Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 17:57:13 -0700 Subject: [PATCH 10/14] Improve AOT testing. --- .github/workflows/aot.yml | 15 +- default.proj | 37 ++- test/Autofac.Test.Aot/DisposableService.cs | 19 ++ test/Autofac.Test.Aot/GenericHolder.cs | 10 + test/Autofac.Test.Aot/IGenericHolder.cs | 10 + test/Autofac.Test.Aot/LambdaService.cs | 18 ++ test/Autofac.Test.Aot/ModuleService.cs | 9 + test/Autofac.Test.Aot/Program.cs | 269 ++++++++++++------ test/Autofac.Test.Aot/ProvidedService.cs | 9 + test/Autofac.Test.Aot/SmokeTestModule.cs | 15 + .../Autofac.Test.AotWarnings.csproj | 44 +++ test/Autofac.Test.AotWarnings/Program.cs | 39 +++ 12 files changed, 401 insertions(+), 93 deletions(-) create mode 100644 test/Autofac.Test.Aot/DisposableService.cs create mode 100644 test/Autofac.Test.Aot/GenericHolder.cs create mode 100644 test/Autofac.Test.Aot/IGenericHolder.cs create mode 100644 test/Autofac.Test.Aot/LambdaService.cs create mode 100644 test/Autofac.Test.Aot/ModuleService.cs create mode 100644 test/Autofac.Test.Aot/ProvidedService.cs create mode 100644 test/Autofac.Test.Aot/SmokeTestModule.cs create mode 100644 test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj create mode 100644 test/Autofac.Test.AotWarnings/Program.cs diff --git a/.github/workflows/aot.yml b/.github/workflows/aot.yml index 672c294f2..29dddcc6e 100644 --- a/.github/workflows/aot.yml +++ b/.github/workflows/aot.yml @@ -1,11 +1,16 @@ name: Native AOT Verification # Repo-local, additive workflow (the shared org CI in autofac/.github builds and -# tests the solution). This job proves Autofac stays Native-AOT/trim clean: it -# publishes the AOT smoke-test app with PublishAot=true - which runs the ILC -# trim/AOT analyzer and fails on any IL2104/IL3053 because the project treats -# warnings as errors - and then executes the produced native binary, which exits -# non-zero on any runtime failure of the AOT-safe resolve path. +# tests the solution). This job runs the VerifyAot target, which proves Autofac +# stays Native-AOT/trim clean in two complementary ways: +# 1. VerifyAotWarnings - builds a fixture that uses the [RequiresDynamicCode] / +# [RequiresUnreferencedCode] APIs and asserts those diagnostics still fire, +# guarding against silently losing an annotation. +# 2. The native publish - publishes the smoke-test app with PublishAot=true (the +# ILC trim/AOT analyzer fails on any IL2104/IL3053 since the project treats +# warnings as errors) and executes the native binary, which exits non-zero on +# any runtime failure of the AOT-safe resolve path (or if a dynamic-code +# scenario unexpectedly stops throwing). on: pull_request: diff --git a/default.proj b/default.proj index 9d8b158a6..2387e7e9e 100644 --- a/default.proj +++ b/default.proj @@ -85,11 +85,46 @@ Autofac.Test.Aot Autofac.Test.Aot.exe - + + + + $([System.IO.Path]::Combine($(MSBuildProjectDirectory),'test/Autofac.Test.AotWarnings')) + + + + + + + + + + + + + + + @(AotWarningsBuildOutput, '%0a') + + + + diff --git a/test/Autofac.Test.Aot/DisposableService.cs b/test/Autofac.Test.Aot/DisposableService.cs new file mode 100644 index 000000000..dfb7cab5a --- /dev/null +++ b/test/Autofac.Test.Aot/DisposableService.cs @@ -0,0 +1,19 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Verifies that Autofac disposes owned instances when +/// their lifetime scope is disposed. +/// +internal sealed class DisposableService : IDisposable +{ + public bool IsDisposed + { + get; private set; + } + + public void Dispose() + { + IsDisposed = true; + } +} diff --git a/test/Autofac.Test.Aot/GenericHolder.cs b/test/Autofac.Test.Aot/GenericHolder.cs new file mode 100644 index 000000000..4a439ef71 --- /dev/null +++ b/test/Autofac.Test.Aot/GenericHolder.cs @@ -0,0 +1,10 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Open generic implementation used to verify open generic resolution under AOT. +/// +/// The held type. +internal sealed class GenericHolder : IGenericHolder +{ +} diff --git a/test/Autofac.Test.Aot/IGenericHolder.cs b/test/Autofac.Test.Aot/IGenericHolder.cs new file mode 100644 index 000000000..44b573a73 --- /dev/null +++ b/test/Autofac.Test.Aot/IGenericHolder.cs @@ -0,0 +1,10 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Open generic service contract used to verify open generic resolution under AOT. +/// +/// The held type. +internal interface IGenericHolder +{ +} diff --git a/test/Autofac.Test.Aot/LambdaService.cs b/test/Autofac.Test.Aot/LambdaService.cs new file mode 100644 index 000000000..7623e8f0b --- /dev/null +++ b/test/Autofac.Test.Aot/LambdaService.cs @@ -0,0 +1,18 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Verifies lambda registration - the recommended AOT-friendly registration pattern. +/// +internal sealed class LambdaService +{ + public LambdaService(IDependency dependency) + { + Dependency = dependency; + } + + public IDependency Dependency + { + get; + } +} diff --git a/test/Autofac.Test.Aot/ModuleService.cs b/test/Autofac.Test.Aot/ModuleService.cs new file mode 100644 index 000000000..d39303840 --- /dev/null +++ b/test/Autofac.Test.Aot/ModuleService.cs @@ -0,0 +1,9 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Registered by to verify module-based registration. +/// +internal sealed class ModuleService +{ +} diff --git a/test/Autofac.Test.Aot/Program.cs b/test/Autofac.Test.Aot/Program.cs index f937abe8e..b550773db 100644 --- a/test/Autofac.Test.Aot/Program.cs +++ b/test/Autofac.Test.Aot/Program.cs @@ -1,109 +1,204 @@ // Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using Autofac; - -// Native AOT smoke test. -// -// Exercises the surface of Autofac that is documented as AOT/trim-safe: registering -// concrete types, resolving by generic type and by System.Type, constructor injection, -// property injection, parameterized resolution with TypedParameter, and child scopes. -// -// Every check throws on failure; the process returns a non-zero exit code so a CI step -// can assert success simply by checking the exit code of the published native binary. -// -// This deliberately does NOT touch the dynamic-code surface (open generics, collections, -// Lazy/Meta, generated factories, assembly scanning). Those are annotated -// [RequiresDynamicCode]/[RequiresUnreferencedCode] and are expected to warn at the call -// site under AOT; exercising them here would defeat the warning-free goal of this test. -var failures = new List(); - -void Check(string name, Func assertion) +using Autofac.Features.Metadata; +using Autofac.Features.OwnedInstances; + +/// +/// Native AOT smoke test. +/// +/// +/// +/// Validates two things that the static IsAotCompatible build gate cannot: +/// +/// +/// +/// The AOT-safe surface actually WORKS at runtime under Native AOT - i.e. we did +/// not over-suppress and break a path that should function. These are the +/// Check assertions. +/// +/// +/// The features that genuinely require dynamic code FAIL at runtime in the expected +/// way (a DependencyResolutionException), proving the boundary is real and +/// that we did not accidentally make an unsupported scenario "work" by luck. These +/// are the CheckThrows assertions. +/// +/// +/// +/// Empirically (net10.0 / osx-arm64) the dividing line is runtime code generation, +/// NOT value types in general: closing an OPEN GENERIC over a value type needs +/// MakeGenericType and fails, but resolving IEnumerable<valueType> +/// succeeds because no new type is constructed. Reference-typed relationships all work. +/// +/// +internal static class Program { - try + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "A smoke test that exercises the breadth of the resolve surface is necessarily coupled to many types.")] + public static int Main() { - if (!assertion()) - { - failures.Add(name); - Console.Error.WriteLine($"FAIL: {name}"); - } - else + var failures = new List(); + + var builder = new ContainerBuilder(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType(); + builder.RegisterType().PropertiesAutowired(); + builder.RegisterType(); + builder.RegisterType().Keyed("special"); + builder.RegisterInstance(new ProvidedService()).AsSelf(); + builder.Register(c => new LambdaService(c.Resolve())).AsSelf(); + builder.RegisterType().AsSelf().InstancePerDependency(); + builder.RegisterModule(); + RegisterOpenGeneric(builder); + + using var container = builder.Build(); + + // --- Scenarios that MUST work under Native AOT --- + + // Resolve() of an interface backed by a concrete implementation. + Check(failures, "Resolve", () => container.Resolve() is Dependency); + + // Resolve(Type) - the closed-Type path the source generator depends on. + Check(failures, "Resolve(typeof(IDependency))", () => container.Resolve(typeof(IDependency)) is Dependency); + + // Constructor injection of a dependency. + Check(failures, "Constructor injection", () => container.Resolve().Dependency is Dependency); + + // Public property injection (a key Autofac feature beyond MEDI). + Check(failures, "Property injection", () => container.Resolve().Dependency is Dependency); + + // Parameterized resolution with TypedParameter (used by the generated aggregate code). + Check(failures, "Resolve with TypedParameter", () => { - Console.WriteLine($"PASS: {name}"); - } - } - catch (Exception ex) - { - failures.Add($"{name} ({ex.GetType().Name}: {ex.Message})"); - Console.Error.WriteLine($"FAIL: {name} -> {ex}"); - } -} + var resolved = (ParameterizedConsumer)container.Resolve( + typeof(ParameterizedConsumer), + new[] { new TypedParameter(typeof(string), "configured") }); + return resolved.Value == "configured"; + }); -var builder = new ContainerBuilder(); -builder.RegisterType().As(); -builder.RegisterType(); -builder.RegisterType().PropertiesAutowired(); -builder.RegisterType(); -builder.RegisterType().Keyed("special"); + // Keyed resolution by Type. + Check(failures, "ResolveKeyed(Type)", () => container.ResolveKeyed("special", typeof(IDependency)) is KeyedDependency); -using var container = builder.Build(); + // Pre-built instance registration (no reflection activation at all). + Check(failures, "RegisterInstance", () => container.Resolve() is not null); -// Resolve() of an interface backed by a concrete implementation. -Check("Resolve", () => container.Resolve() is Dependency); + // Lambda registration (the recommended AOT-friendly registration pattern). + Check(failures, "Register(lambda)", () => container.Resolve().Dependency is Dependency); -// Resolve(Type) - the closed-Type path the source generator depends on. -Check("Resolve(typeof(IDependency))", () => container.Resolve(typeof(IDependency)) is Dependency); + // Module registration. + Check(failures, "RegisterModule", () => container.Resolve() is not null); -// Constructor injection of a dependency. -Check("Constructor injection", () => -{ - var consumer = container.Resolve(); - return consumer.Dependency is Dependency; -}); + // SingleInstance sharing returns the same instance. + Check(failures, "SingleInstance sharing", () => ReferenceEquals(container.Resolve(), container.Resolve())); -// Property injection (a key Autofac feature beyond MEDI). -Check("Property injection", () => -{ - var consumer = container.Resolve(); - return consumer.Dependency is Dependency; -}); + // Child lifetime scope resolution. + Check(failures, "Child scope resolution", () => + { + using var scope = container.BeginLifetimeScope(); + return scope.Resolve() is Dependency; + }); -// Parameterized resolution with TypedParameter (used by the generated aggregate code). -Check("Resolve with TypedParameter", () => -{ - var resolved = (ParameterizedConsumer)container.Resolve( - typeof(ParameterizedConsumer), - new[] { new TypedParameter(typeof(string), "configured") }); - return resolved.Value == "configured"; -}); + // Child scope with additional registrations. + Check(failures, "Child scope with registrations", () => + { + using var scope = container.BeginLifetimeScope(b => b.RegisterType().AsSelf()); + return scope.Resolve() is not null; + }); -// Keyed resolution by Type. -Check("ResolveKeyed(Type)", () => container.ResolveKeyed("special", typeof(IDependency)) is KeyedDependency); + // IDisposable instances are disposed when their owning scope is disposed. + Check(failures, "Disposal on scope dispose", () => + { + DisposableService captured; + using (var scope = container.BeginLifetimeScope()) + { + captured = scope.Resolve(); + } + + return captured.IsDisposed; + }); + + // Reference-typed relationships resolve under AOT (no runtime type construction). + Check(failures, "Resolve IEnumerable", () => container.Resolve>().Any()); + Check(failures, "Resolve Lazy", () => container.Resolve>().Value is Dependency); + Check(failures, "Resolve Func", () => container.Resolve>()() is Dependency); + Check(failures, "Resolve Meta", () => container.Resolve>().Value is Dependency); + Check(failures, "Resolve Owned", () => + { + using var owned = container.Resolve>(); + return owned.Value is Dependency; + }); -// Child lifetime scope resolution. -Check("Child scope resolution", () => -{ - using var scope = container.BeginLifetimeScope(); - return scope.Resolve() is Dependency; -}); + // An open generic closed over a REFERENCE type works (no value-type code generation). + Check(failures, "Resolve IGenericHolder (ref close)", () => container.Resolve>() is GenericHolder); -// Child scope with additional registrations. -Check("Child scope with registrations", () => -{ - using var scope = container.BeginLifetimeScope(b => b.RegisterType().AsSelf()); - return scope.Resolve() is not null; -}); + // --- Scenarios that MUST throw under Native AOT (dynamic-code boundary) --- -if (failures.Count > 0) -{ - Console.Error.WriteLine($"\n{failures.Count} AOT smoke check(s) failed:"); - foreach (var failure in failures) + // Closing an open generic over a VALUE type requires MakeGenericType over a + // value type, which Native AOT cannot generate. + CheckThrows(failures, "Resolve IGenericHolder (value-type close)", () => container.Resolve>()); + + if (failures.Count > 0) + { + Console.Error.WriteLine($"\n{failures.Count} AOT smoke check(s) failed:"); + foreach (var failure in failures) + { + Console.Error.WriteLine($" - {failure}"); + } + + return 1; + } + + Console.WriteLine("\nAll AOT smoke checks passed."); + return 0; + } + + // RegisterGeneric is an opt-in dynamic-code API and intentionally raises IL3050 - + // that warning is itself part of what this test validates (see the value-type-close + // CheckThrows case). Suppress it here so the rest of the project keeps treating + // trim/AOT warnings as build-breaking errors. + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "Deliberately exercises the open-generic dynamic-code boundary; the matching value-type-close resolution is asserted to throw at runtime.")] + private static void RegisterOpenGeneric(ContainerBuilder builder) { - Console.Error.WriteLine($" - {failure}"); + builder.RegisterGeneric(typeof(GenericHolder<>)).As(typeof(IGenericHolder<>)); } - return 1; -} + private static void Check(List failures, string name, Func assertion) + { + try + { + if (!assertion()) + { + failures.Add(name); + Console.Error.WriteLine($"FAIL: {name}"); + } + else + { + Console.WriteLine($"PASS: {name}"); + } + } + catch (Exception ex) + { + failures.Add($"{name} ({ex.GetType().Name}: {ex.Message})"); + Console.Error.WriteLine($"FAIL: {name} -> {ex}"); + } + } -Console.WriteLine("\nAll AOT smoke checks passed."); -return 0; + // Asserts that an operation throws (the expected outcome for a dynamic-code-only + // feature under AOT). A scenario that unexpectedly succeeds is itself a failure - + // it means the AOT boundary moved and the docs/annotations may be stale. + private static void CheckThrows(List failures, string name, Action action) + { + try + { + action(); + failures.Add($"{name} (expected an exception under AOT but none was thrown)"); + Console.Error.WriteLine($"FAIL: {name} -> expected throw, but succeeded"); + } + catch (Exception ex) + { + Console.WriteLine($"PASS: {name} (threw {ex.GetType().Name} as expected)"); + } + } +} diff --git a/test/Autofac.Test.Aot/ProvidedService.cs b/test/Autofac.Test.Aot/ProvidedService.cs new file mode 100644 index 000000000..6410aa167 --- /dev/null +++ b/test/Autofac.Test.Aot/ProvidedService.cs @@ -0,0 +1,9 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +/// +/// Verifies registration of a pre-built instance (no reflection activation). +/// +internal sealed class ProvidedService +{ +} diff --git a/test/Autofac.Test.Aot/SmokeTestModule.cs b/test/Autofac.Test.Aot/SmokeTestModule.cs new file mode 100644 index 000000000..048cf9890 --- /dev/null +++ b/test/Autofac.Test.Aot/SmokeTestModule.cs @@ -0,0 +1,15 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using Autofac; + +/// +/// Verifies module registration (RegisterModule) under Native AOT. +/// +internal sealed class SmokeTestModule : Module +{ + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().AsSelf(); + } +} diff --git a/test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj b/test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj new file mode 100644 index 000000000..4f1c410c4 --- /dev/null +++ b/test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj @@ -0,0 +1,44 @@ + + + + Exe + net10.0 + enable + enable + false + false + + true + false + + false + true + + + + + diff --git a/test/Autofac.Test.AotWarnings/Program.cs b/test/Autofac.Test.AotWarnings/Program.cs new file mode 100644 index 000000000..dcb56127a --- /dev/null +++ b/test/Autofac.Test.AotWarnings/Program.cs @@ -0,0 +1,39 @@ +// Copyright (c) Autofac Project. All rights reserved. +// Licensed under the MIT License. See LICENSE in the project root for license information. + +using System.Reflection; +using Autofac; + +// AOT/trim WARNING fixture - see the .csproj header for the full rationale. +// +// Each statement below calls an Autofac API that is annotated [RequiresDynamicCode] +// or [RequiresUnreferencedCode]. With IsAotCompatible=true the trim/AOT analyzers +// run and emit the IL codes noted in the comments. The 'VerifyAotWarnings' target in +// default.proj builds this project and asserts those codes are present in the output; +// if an annotation is ever lost, the corresponding warning disappears and the target +// fails. The expected codes are duplicated in that target - keep them in sync. +var builder = new ContainerBuilder(); + +// IL3050 (RequiresDynamicCode): open generic registration constructs closed types +// at runtime via MakeGenericType. +builder.RegisterGeneric(typeof(GenericFixture<>)); + +// IL3050 (RequiresDynamicCode): open generic decorator. +builder.RegisterGenericDecorator(typeof(GenericFixture<>), typeof(GenericFixture<>)); + +// IL2026 (RequiresUnreferencedCode): assembly type scanning. +builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()); + +// IL2026 (RequiresUnreferencedCode): assembly module scanning. +builder.RegisterAssemblyModules(Assembly.GetExecutingAssembly()); + +using var container = builder.Build(); +return 0; + +/// +/// Open generic type used purely to trigger the open-generic registration warnings. +/// +/// Unused type parameter. +internal sealed class GenericFixture +{ +} From e2bc90354e64f5a6d501e2aa58ea05a7473b46e0 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 17:58:40 -0700 Subject: [PATCH 11/14] dotnet format --- src/Autofac/Util/ActivatorMemberTypes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Autofac/Util/ActivatorMemberTypes.cs b/src/Autofac/Util/ActivatorMemberTypes.cs index f5b397a1a..a10c658dc 100644 --- a/src/Autofac/Util/ActivatorMemberTypes.cs +++ b/src/Autofac/Util/ActivatorMemberTypes.cs @@ -1,4 +1,4 @@ -// Copyright (c) Autofac Project. All rights reserved. +// Copyright (c) Autofac Project. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. namespace Autofac.Util; From aff579d329852a1d877f963678cd4af00cfb85b6 Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 19:06:15 -0700 Subject: [PATCH 12/14] Bump version to 9.3.0 for AOT/trim annotation release. --- default.proj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.proj b/default.proj index 2387e7e9e..1a113ce76 100644 --- a/default.proj +++ b/default.proj @@ -2,7 +2,7 @@ - 9.2.0 + 9.3.0 Autofac Release $([System.IO.Path]::Combine($(MSBuildProjectDirectory),"artifacts")) From 1d59c92a6090fa209cabc68f436c5a435c038ddc Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 22:18:58 -0700 Subject: [PATCH 13/14] Address PR review: fix AOT warning fixture comments and harden per-API check. --- default.proj | 25 +++++++++++---- test/Autofac.Test.Aot/Program.cs | 6 +++- .../Autofac.Test.AotWarnings.csproj | 32 +++++++++++-------- test/Autofac.Test.AotWarnings/Program.cs | 10 ++++-- 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/default.proj b/default.proj index 1a113ce76..bdc83cfb8 100644 --- a/default.proj +++ b/default.proj @@ -100,15 +100,25 @@ fails. Unlike VerifyAot proper, this needs no native toolchain - a plain build surfaces the analyzer diagnostics - so it is cheap to run anywhere. - Keep ExpectedAotWarningCodes in sync with the diagnostics produced by - test/Autofac.Test.AotWarnings/Program.cs. + The check is PER CALL SITE, not just per diagnostic code: each ExpectedAotWarning + item names both the code (IL2026/IL3050) and a distinctive substring of the + annotated member's signature as it appears in the warning text. Multiple call + sites share a code (e.g. RegisterGeneric and RegisterGenericDecorator are both + IL3050), so asserting only "IL3050 appears somewhere" would not catch losing the + annotation on just one of them. Matching the member signature catches each one. + + Keep the ExpectedAotWarning items in sync with the calls in + test/Autofac.Test.AotWarnings/Program.cs - one item per annotated call. --> $([System.IO.Path]::Combine($(MSBuildProjectDirectory),'test/Autofac.Test.AotWarnings')) - - + + + + + @@ -123,8 +133,9 @@ @(AotWarningsBuildOutput, '%0a') - - + + + diff --git a/test/Autofac.Test.Aot/Program.cs b/test/Autofac.Test.Aot/Program.cs index b550773db..2d30703a4 100644 --- a/test/Autofac.Test.Aot/Program.cs +++ b/test/Autofac.Test.Aot/Program.cs @@ -136,7 +136,11 @@ public static int Main() // --- Scenarios that MUST throw under Native AOT (dynamic-code boundary) --- // Closing an open generic over a VALUE type requires MakeGenericType over a - // value type, which Native AOT cannot generate. + // value type, which Native AOT cannot generate. This assertion is deliberately + // runtime/environment sensitive: if a future runtime adds universal shared + // generics for value types, this could start SUCCEEDING and fail the check. + // That is by design - a failure here likely means "the AOT boundary moved" + // (update this test and the AOT docs), not "Autofac is broken". CheckThrows(failures, "Resolve IGenericHolder (value-type close)", () => container.Resolve>()); if (failures.Count > 0) diff --git a/test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj b/test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj index 4f1c410c4..243bcab60 100644 --- a/test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj +++ b/test/Autofac.Test.AotWarnings/Autofac.Test.AotWarnings.csproj @@ -2,22 +2,23 @@ Exe @@ -26,14 +27,17 @@ enable false false + + ../../Autofac.snk + true true false false true diff --git a/test/Autofac.Test.AotWarnings/Program.cs b/test/Autofac.Test.AotWarnings/Program.cs index dcb56127a..ad6c1e267 100644 --- a/test/Autofac.Test.AotWarnings/Program.cs +++ b/test/Autofac.Test.AotWarnings/Program.cs @@ -9,9 +9,13 @@ // Each statement below calls an Autofac API that is annotated [RequiresDynamicCode] // or [RequiresUnreferencedCode]. With IsAotCompatible=true the trim/AOT analyzers // run and emit the IL codes noted in the comments. The 'VerifyAotWarnings' target in -// default.proj builds this project and asserts those codes are present in the output; -// if an annotation is ever lost, the corresponding warning disappears and the target -// fails. The expected codes are duplicated in that target - keep them in sync. +// default.proj builds this project and asserts, PER CALL SITE, that the expected +// warning appears (matching both the IL code and the member signature - several call +// sites share a code, so a code-only check would miss losing one of them). If an +// annotation is ever lost, the matching warning disappears and the target fails. +// +// Keep these calls in sync with the ExpectedAotWarning items in default.proj - one +// item there per annotated call here. var builder = new ContainerBuilder(); // IL3050 (RequiresDynamicCode): open generic registration constructs closed types From e2ce0a90e8ba576032d9f8596835c0670704781d Mon Sep 17 00:00:00 2001 From: Travis Illig Date: Fri, 19 Jun 2026 22:55:22 -0700 Subject: [PATCH 14/14] Redirect AOT warning fixture build to a log to avoid PR annotations. --- default.proj | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/default.proj b/default.proj index bdc83cfb8..f729e920b 100644 --- a/default.proj +++ b/default.proj @@ -124,12 +124,24 @@ - - - - + + + $([System.IO.Path]::Combine($(LogDirectory),'aot-warnings-build.log')) + + + + + + @(AotWarningsBuildOutput, '%0a')