From d13a3c49068a7b29ffc88f881cee3f290bc9b6a5 Mon Sep 17 00:00:00 2001 From: "Jeremy D. Miller" Date: Tue, 12 May 2026 21:00:20 -0500 Subject: [PATCH] Annotate Core/Reflection reflective surface for AOT (closes #252) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Largest AOT-pillar slice (jasperfx#213). 64 IL warnings per TFM across 6 files in Core/Reflection → 0, plus propagated annotations to keep EnumerableTypeExtensions clean. GenericFactoryCache.cs All 8 BuildAs overloads carry [RequiresDynamicCode] from PR #191. Adds matching [UnconditionalSuppressMessage("Trimming", "IL2055")] to suppress the MakeGenericType-in-lambda warnings — the method-level [RequiresDynamicCode] already documents the contract; source-generated callers supply an AOT-safe factoryFactory and never reach MakeGenericType. TypeExtensions.cs [DAM(Interfaces)] on ImplementsInterfaceTemplate, FindInterfaceThatCloses, Closes, FindParameterTypeTo, IsAnEnumerationOf [DAM(PublicConstructors)] on IsConcreteWithDefaultCtor [DAM(PublicParameterlessConstructor)] on Create(), Create [UnconditionalSuppressMessage(IL3050)] on IsGenericEnumerable (closes well-known IEnumerable only) [UnconditionalSuppressMessage(IL2072)] on Closes (recursive interface walk) ReflectionExtensions.cs [DAM(PublicConstructors | NonPublicConstructors)] on HasDefaultConstructor, HasConstructorsWithArguments [DAM(PublicConstructors)] on TryFindConstructor [DAM(PublicMethods | NonPublicMethods)] on TryFindMethod, TryFindStaticMethod [UnconditionalSuppressMessage(IL2072)] on IsAsync (checks well-known Task/ValueTask types) EnumerableTypeExtensions.cs [DAM(Interfaces)] on IsEnumerable (propagated from TypeExtensions.Closes) LambdaBuilder.cs [RequiresUnreferencedCode] on every public method: GetProperty, SetProperty, GetField, SetField, Getter, Setter All compile expression trees via FastExpressionCompiler (itself RUC-annotated). Honest characterization: this whole class is for runtime expression compilation; AOT consumers should source-generate accessor delegates. ValueTypeInfo.cs [RequiresUnreferencedCode] on public ForType(Type), CreateWrapper, UnWrapper [DAM(PublicProperties | PublicConstructors | PublicMethods)] on ForType(Type) Strong-typed-id value-type discovery + Expression compilation; both fundamentally trim-hostile. ReflectionHelper.cs [DAM(PublicParameterlessConstructor)] on MeetsSpecialGenericConstraints [UnconditionalSuppressMessage(IL2026/IL3050)] on VisitNew / VisitNewArray (visits existing expression trees; trim invariant owned by whoever built the input) Effect on the punch list JasperFx total per TFM: 188 → 140 (-48) The slice's direct 64-warning count was higher than the net delta because some warnings propagated up cascade chains (ReflectionExtensions inherited 4 from TypeExtensions DAM annotations, etc.) — each propagation got its own annotation, so the net result is the same 0 in the slice plus the surrounding cleanup. Remaining (all pre-existing, not introduced by this PR): #254 ServiceContainer.cs 16 (PR #256 in flight) #255 CodeGeneration/Services 22 (PR #257 in flight) — already addressed by ripple suppression Core/TypeScanning 10 CodeGeneration/Snapshots 8 CodeGeneration/GeneratedType.cs 8 JasperFxOptions.cs 6 misc tail ~24 Verification CoreTests 407/407 pass on net9.0 + net10.0 SmokeTestAot build clean, exits 0 Closes #252. --- .../Reflection/EnumerableTypeExtensions.cs | 7 ++-- .../Core/Reflection/GenericFactoryCache.cs | 16 ++++++++++ src/JasperFx/Core/Reflection/LambdaBuilder.cs | 15 ++++++++- .../Core/Reflection/ReflectionExtensions.cs | 25 ++++++++++++--- .../Core/Reflection/ReflectionHelper.cs | 8 ++++- .../Core/Reflection/TypeExtensions.cs | 32 ++++++++++++++----- src/JasperFx/Core/Reflection/ValueTypeInfo.cs | 9 ++++-- 7 files changed, 93 insertions(+), 19 deletions(-) diff --git a/src/JasperFx/Core/Reflection/EnumerableTypeExtensions.cs b/src/JasperFx/Core/Reflection/EnumerableTypeExtensions.cs index 86ca8268..6fc4e304 100644 --- a/src/JasperFx/Core/Reflection/EnumerableTypeExtensions.cs +++ b/src/JasperFx/Core/Reflection/EnumerableTypeExtensions.cs @@ -1,4 +1,6 @@ -namespace JasperFx.Core.Reflection; +using System.Diagnostics.CodeAnalysis; + +namespace JasperFx.Core.Reflection; public static class EnumerableTypeExtensions { @@ -17,7 +19,8 @@ public static class EnumerableTypeExtensions /// /// /// - public static bool IsEnumerable(this Type type) + public static bool IsEnumerable( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type) { if (type.IsArray) { diff --git a/src/JasperFx/Core/Reflection/GenericFactoryCache.cs b/src/JasperFx/Core/Reflection/GenericFactoryCache.cs index 1a9fda07..95f3b7c4 100644 --- a/src/JasperFx/Core/Reflection/GenericFactoryCache.cs +++ b/src/JasperFx/Core/Reflection/GenericFactoryCache.cs @@ -51,6 +51,8 @@ public static class GenericFactoryCache /// expression. Called at most once per cache key. /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument, @@ -70,6 +72,8 @@ public static T BuildAs( /// invoking a single-argument constructor with . /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument, @@ -88,6 +92,8 @@ public static T BuildAs( /// Build an instance of with two constructor arguments. /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument1, @@ -108,6 +114,8 @@ public static T BuildAs( /// Build an instance of with three constructor arguments. /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument1, @@ -138,6 +146,8 @@ public static T BuildAs( /// where the original 4 overloads didn't fit. /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument, @@ -165,6 +175,8 @@ public static T BuildAs( /// the original 4 overloads didn't fit. /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument, @@ -193,6 +205,8 @@ public static T BuildAs( /// overload set didn't fit. /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument1, @@ -225,6 +239,8 @@ public static T BuildAs( /// when they fit. /// [RequiresDynamicCode("The default factoryFactory implementation may use MakeGenericType / Activator.CreateInstance; supply an AOT-safe delegate factory to avoid this.")] + [UnconditionalSuppressMessage("Trimming", "IL2055:DynamicallyAccessedMembers", + Justification = "MakeGenericType is invoked inside the cache-miss factoryFactory lambda. The method-level [RequiresDynamicCode] already documents the contract: callers supplying the default reflective factoryFactory accept that the closed generic's members must survive trimming. Source-generated callers supply an AOT/trim-safe delegate factory and never reach the MakeGenericType path.")] public static T BuildAs( Type openType, Type typeArgument, diff --git a/src/JasperFx/Core/Reflection/LambdaBuilder.cs b/src/JasperFx/Core/Reflection/LambdaBuilder.cs index f29e3b22..c9d879b8 100644 --- a/src/JasperFx/Core/Reflection/LambdaBuilder.cs +++ b/src/JasperFx/Core/Reflection/LambdaBuilder.cs @@ -1,4 +1,5 @@ -using System.Linq.Expressions; +using System.Diagnostics.CodeAnalysis; +using System.Linq.Expressions; using System.Reflection; using FastExpressionCompiler; @@ -7,8 +8,15 @@ namespace JasperFx.Core.Reflection; /// /// Can be used to create compiled Func's to retrieve a value from the TTarget type /// +/// +/// Every method here builds an expression tree and compiles it via +/// , which the .NET +/// trimmer cannot statically analyse. AOT-publishing apps should source-generate +/// accessor delegates rather than route through LambdaBuilder. +/// public static class LambdaBuilder { + [RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")] public static Func GetProperty(PropertyInfo property) { var target = Expression.Parameter(property.DeclaringType!, "target"); @@ -31,6 +39,7 @@ public static Func GetProperty(PropertyI /// /// /// + [RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")] public static Action? SetProperty(PropertyInfo property) { var target = Expression.Parameter(typeof(TTarget), "target"); @@ -55,6 +64,7 @@ public static Func GetProperty(PropertyI } + [RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")] public static Func GetField(FieldInfo field) { var target = Expression.Parameter(typeof(TTarget), "target"); @@ -68,6 +78,7 @@ public static Func GetField(FieldInfo field) return lambda.CompileFast(); } + [RequiresUnreferencedCode("Delegates to GetProperty / GetField which compile expression trees via FastExpressionCompiler.")] public static Func Getter(MemberInfo member) { return member is PropertyInfo @@ -76,6 +87,7 @@ public static Func Getter(MemberInfo member) } + [RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")] public static Action SetField(FieldInfo field) { var target = Expression.Parameter(typeof(TTarget), "target"); @@ -90,6 +102,7 @@ public static Action SetField(FieldInfo field) } + [RequiresUnreferencedCode("Delegates to SetProperty / SetField which compile expression trees via FastExpressionCompiler.")] public static Action? Setter(MemberInfo member) { return member is PropertyInfo diff --git a/src/JasperFx/Core/Reflection/ReflectionExtensions.cs b/src/JasperFx/Core/Reflection/ReflectionExtensions.cs index d792b7a0..d39d320f 100644 --- a/src/JasperFx/Core/Reflection/ReflectionExtensions.cs +++ b/src/JasperFx/Core/Reflection/ReflectionExtensions.cs @@ -174,7 +174,8 @@ public static void ForAttribute(this ParameterInfo provider, Action action /// /// /// - public static bool HasDefaultConstructor(this Type t) + public static bool HasDefaultConstructor( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] this Type t) { return t.IsValueType || t.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null) != null; @@ -185,7 +186,8 @@ public static bool HasDefaultConstructor(this Type t) /// /// /// - public static bool HasConstructorsWithArguments(this Type t) + public static bool HasConstructorsWithArguments( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] this Type t) { return t.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) .Any(x => x.GetParameters().Any()); @@ -236,6 +238,8 @@ public static string GetPrettyName(this Type t) /// /// /// + [UnconditionalSuppressMessage("Trimming", "IL2072:DynamicallyAccessedMembers", + Justification = "method.ReturnType is checked against the well-known framework types Task / Task<> / ValueTask / ValueTask<>. Closes(...) walks Interfaces which are intrinsic to the framework type system; no user-supplied interfaces need to survive trimming for this check to behave correctly.")] public static bool IsAsync(this MethodInfo method) { if (method.ReturnType == null) @@ -273,7 +277,10 @@ public static bool CanBeOverridden(this MethodInfo method) /// /// /// - public static bool TryFindConstructor(this Type type, [NotNullWhen(true)]out ConstructorInfo? ctor, params Type[] arguments) + public static bool TryFindConstructor( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type, + [NotNullWhen(true)]out ConstructorInfo? ctor, + params Type[] arguments) { ctor = type.GetConstructor(arguments); return ctor != null; @@ -288,7 +295,11 @@ public static bool TryFindConstructor(this Type type, [NotNullWhen(true)]out Con /// /// /// - public static bool TryFindMethod(this Type? type, string methodName, [NotNullWhen(true)]out MethodInfo? method, Type argumentType) + public static bool TryFindMethod( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type? type, + string methodName, + [NotNullWhen(true)]out MethodInfo? method, + Type argumentType) { if (type == null) { @@ -312,7 +323,11 @@ public static bool TryFindMethod(this Type? type, string methodName, [NotNullWhe /// /// /// - public static bool TryFindStaticMethod(this Type? type, string methodName, [NotNullWhen(true)]out MethodInfo? method, Type argumentType) + public static bool TryFindStaticMethod( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type? type, + string methodName, + [NotNullWhen(true)]out MethodInfo? method, + Type argumentType) { if (type == null) { diff --git a/src/JasperFx/Core/Reflection/ReflectionHelper.cs b/src/JasperFx/Core/Reflection/ReflectionHelper.cs index 0e9ef626..28917bfe 100644 --- a/src/JasperFx/Core/Reflection/ReflectionHelper.cs +++ b/src/JasperFx/Core/Reflection/ReflectionHelper.cs @@ -7,7 +7,9 @@ namespace JasperFx.Core.Reflection; public static class ReflectionHelper { - public static bool MeetsSpecialGenericConstraints(Type genericArgType, Type proposedSpecificType) + public static bool MeetsSpecialGenericConstraints( + Type genericArgType, + [System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] Type proposedSpecificType) { var genericArgTypeInfo = genericArgType.GetTypeInfo(); var proposedSpecificTypeInfo = proposedSpecificType.GetTypeInfo(); @@ -548,6 +550,8 @@ protected virtual Expression VisitLambda(LambdaExpression lambda) return lambda; } + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", + Justification = "Visits an existing NewExpression and reconstructs it with mutated args. The ConstructorInfo + MemberInfo[] come from the input tree; they're not newly resolved here, so the trim invariant is owned by whoever built the input expression.")] protected virtual NewExpression VisitNew(NewExpression nex) { IEnumerable args = VisitList(nex.Arguments); @@ -590,6 +594,8 @@ protected virtual Expression VisitListInit(ListInitExpression init) return init; } + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "Visits an existing NewArrayExpression; the element type comes from the input tree and an array of that element type was already constructible at the time the input tree was built.")] protected virtual Expression VisitNewArray(NewArrayExpression na) { IEnumerable exprs = VisitList(na.Expressions); diff --git a/src/JasperFx/Core/Reflection/TypeExtensions.cs b/src/JasperFx/Core/Reflection/TypeExtensions.cs index 7b4d22bf..247885b9 100644 --- a/src/JasperFx/Core/Reflection/TypeExtensions.cs +++ b/src/JasperFx/Core/Reflection/TypeExtensions.cs @@ -132,6 +132,8 @@ public static bool IsOpenGeneric(this Type? type) return typeInfo.IsGenericTypeDefinition || typeInfo.ContainsGenericParameters; } + [UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", + Justification = "Closes the well-known framework type IEnumerable with a single generic argument. The closed shape is always IEnumerable for concrete T from the input type's generic args; native code for IEnumerable is preserved by the framework for any T reachable through the input type.")] public static bool IsGenericEnumerable(this Type? type) { if (type == null) @@ -153,7 +155,9 @@ public static bool IsConcreteTypeOf(this Type? pluggedType) return pluggedType.IsConcrete() && typeof(T).IsAssignableFrom(pluggedType); } - public static bool ImplementsInterfaceTemplate(this Type pluggedType, Type templateType) + public static bool ImplementsInterfaceTemplate( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type pluggedType, + Type templateType) { if (!pluggedType.IsConcrete()) { @@ -172,12 +176,15 @@ public static bool ImplementsInterfaceTemplate(this Type pluggedType, Type templ return false; } - public static bool IsConcreteWithDefaultCtor(this Type type) + public static bool IsConcreteWithDefaultCtor( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type) { return type.IsConcrete() && type.GetConstructor(Type.EmptyTypes) != null; } - public static Type? FindInterfaceThatCloses(this Type type, Type openType) + public static Type? FindInterfaceThatCloses( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type, + Type openType) { if (type == typeof(object)) { @@ -212,7 +219,9 @@ public static bool IsConcreteWithDefaultCtor(this Type type) : typeInfo.BaseType?.FindInterfaceThatCloses(openType); } - public static Type? FindParameterTypeTo(this Type type, Type openType) + public static Type? FindParameterTypeTo( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type, + Type openType) { var interfaceType = type.FindInterfaceThatCloses(openType); return interfaceType?.GetGenericArguments().FirstOrDefault(); @@ -224,7 +233,11 @@ public static bool IsNullable(this Type type) return typeInfo.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>); } - public static bool Closes(this Type? type, Type openType) + [UnconditionalSuppressMessage("Trimming", "IL2072:DynamicallyAccessedMembers", + Justification = "Walks the interface chain via @interface.Closes(openType). Interfaces returned from GetInterfaces() don't statically carry DAM; the recursive walk is bounded by the interface hierarchy of `type`, which itself has [DAM(Interfaces)].")] + public static bool Closes( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type? type, + Type openType) { if (type == null) { @@ -450,7 +463,8 @@ public static bool PropertyMatches(this PropertyInfo prop1, PropertyInfo prop2) /// /// /// - public static T Create(this Type type) + public static T Create( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] this Type type) { return (T)type.Create(); } @@ -460,13 +474,15 @@ public static T Create(this Type type) /// /// /// - public static object Create(this Type type) + public static object Create( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] this Type type) { return Activator.CreateInstance(type)!; } - public static Type IsAnEnumerationOf(this Type type) + public static Type IsAnEnumerationOf( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type) { if (!type.Closes(typeof(IEnumerable<>))) { diff --git a/src/JasperFx/Core/Reflection/ValueTypeInfo.cs b/src/JasperFx/Core/Reflection/ValueTypeInfo.cs index 2f564ad4..5b9b3a7d 100644 --- a/src/JasperFx/Core/Reflection/ValueTypeInfo.cs +++ b/src/JasperFx/Core/Reflection/ValueTypeInfo.cs @@ -1,3 +1,4 @@ +using System.Diagnostics.CodeAnalysis; using System.Linq.Expressions; using System.Reflection; using FastExpressionCompiler; @@ -12,8 +13,10 @@ namespace JasperFx.Core.Reflection; public class ValueTypeInfo { private static ImHashMap _valueTypes = ImHashMap.Empty; - - public static ValueTypeInfo ForType(Type type) + + [RequiresUnreferencedCode("Reflects over type's public properties + constructors + static factory methods to discover the strong-typed-id shape. Discovered members must survive trimming.")] + public static ValueTypeInfo ForType( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods)] Type type) { if (_valueTypes.TryFind(type, out var valueType)) return valueType; @@ -84,6 +87,7 @@ public ValueTypeInfo(Type outerType, Type simpleType, PropertyInfo valueProperty public MethodInfo? Builder { get; } public ConstructorInfo? Ctor { get; } + [RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")] public Func CreateWrapper() { if (_converter != null) @@ -119,6 +123,7 @@ public Func CreateWrapper() /// /// /// + [RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")] public Func UnWrapper() { var outer = Expression.Parameter(typeof(TOuter), "outer");