Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/JasperFx/Core/Reflection/EnumerableTypeExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace JasperFx.Core.Reflection;
using System.Diagnostics.CodeAnalysis;

namespace JasperFx.Core.Reflection;

public static class EnumerableTypeExtensions
{
Expand All @@ -17,7 +19,8 @@ public static class EnumerableTypeExtensions
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsEnumerable(this Type type)
public static bool IsEnumerable(
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type)
{
if (type.IsArray)
{
Expand Down
16 changes: 16 additions & 0 deletions src/JasperFx/Core/Reflection/GenericFactoryCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public static class GenericFactoryCache
/// expression. Called at most once per cache key.
/// </param>
[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<T>(
Type openType,
Type typeArgument,
Expand All @@ -70,6 +72,8 @@ public static T BuildAs<T>(
/// invoking a single-argument constructor with <paramref name="ctorArgument"/>.
/// </summary>
[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<T>(
Type openType,
Type typeArgument,
Expand All @@ -88,6 +92,8 @@ public static T BuildAs<T>(
/// Build an instance of <typeparamref name="T"/> with two constructor arguments.
/// </summary>
[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<T>(
Type openType,
Type typeArgument1,
Expand All @@ -108,6 +114,8 @@ public static T BuildAs<T>(
/// Build an instance of <typeparamref name="T"/> with three constructor arguments.
/// </summary>
[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<T>(
Type openType,
Type typeArgument1,
Expand Down Expand Up @@ -138,6 +146,8 @@ public static T BuildAs<T>(
/// where the original 4 overloads didn't fit.
/// </remarks>
[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<T>(
Type openType,
Type typeArgument,
Expand Down Expand Up @@ -165,6 +175,8 @@ public static T BuildAs<T>(
/// the original 4 overloads didn't fit.
/// </remarks>
[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<T>(
Type openType,
Type typeArgument,
Expand Down Expand Up @@ -193,6 +205,8 @@ public static T BuildAs<T>(
/// overload set didn't fit.
/// </remarks>
[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<T>(
Type openType,
Type typeArgument1,
Expand Down Expand Up @@ -225,6 +239,8 @@ public static T BuildAs<T>(
/// when they fit.
/// </remarks>
[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<T>(
Type openType,
Type typeArgument,
Expand Down
15 changes: 14 additions & 1 deletion src/JasperFx/Core/Reflection/LambdaBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Linq.Expressions;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using FastExpressionCompiler;

Expand All @@ -7,8 +8,15 @@ namespace JasperFx.Core.Reflection;
/// <summary>
/// Can be used to create compiled Func's to retrieve a value from the TTarget type
/// </summary>
/// <remarks>
/// Every method here builds an expression tree and compiles it via
/// <see cref="ExpressionCompiler.CompileFast{TDelegate}"/>, which the .NET
/// trimmer cannot statically analyse. AOT-publishing apps should source-generate
/// accessor delegates rather than route through LambdaBuilder.
/// </remarks>
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<TTarget, TProperty> GetProperty<TTarget, TProperty>(PropertyInfo property)
{
var target = Expression.Parameter(property.DeclaringType!, "target");
Expand All @@ -31,6 +39,7 @@ public static Func<TTarget, TProperty> GetProperty<TTarget, TProperty>(PropertyI
/// <typeparam name="TTarget"></typeparam>
/// <typeparam name="TProperty"></typeparam>
/// <returns></returns>
[RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")]
public static Action<TTarget, TProperty>? SetProperty<TTarget, TProperty>(PropertyInfo property)
{
var target = Expression.Parameter(typeof(TTarget), "target");
Expand All @@ -55,6 +64,7 @@ public static Func<TTarget, TProperty> GetProperty<TTarget, TProperty>(PropertyI
}


[RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")]
public static Func<TTarget, TField> GetField<TTarget, TField>(FieldInfo field)
{
var target = Expression.Parameter(typeof(TTarget), "target");
Expand All @@ -68,6 +78,7 @@ public static Func<TTarget, TField> GetField<TTarget, TField>(FieldInfo field)
return lambda.CompileFast();
}

[RequiresUnreferencedCode("Delegates to GetProperty / GetField which compile expression trees via FastExpressionCompiler.")]
public static Func<TTarget, TMember> Getter<TTarget, TMember>(MemberInfo member)
{
return member is PropertyInfo
Expand All @@ -76,6 +87,7 @@ public static Func<TTarget, TMember> Getter<TTarget, TMember>(MemberInfo member)
}


[RequiresUnreferencedCode("Compiles an expression tree via FastExpressionCompiler; the trimmer cannot reason about types reached only through the resulting delegate.")]
public static Action<TTarget, TField> SetField<TTarget, TField>(FieldInfo field)
{
var target = Expression.Parameter(typeof(TTarget), "target");
Expand All @@ -90,6 +102,7 @@ public static Action<TTarget, TField> SetField<TTarget, TField>(FieldInfo field)
}


[RequiresUnreferencedCode("Delegates to SetProperty / SetField which compile expression trees via FastExpressionCompiler.")]
public static Action<TTarget, TMember>? Setter<TTarget, TMember>(MemberInfo member)
{
return member is PropertyInfo
Expand Down
25 changes: 20 additions & 5 deletions src/JasperFx/Core/Reflection/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ public static void ForAttribute<T>(this ParameterInfo provider, Action<T> action
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
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;
Expand All @@ -185,7 +186,8 @@ public static bool HasDefaultConstructor(this Type t)
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
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());
Expand Down Expand Up @@ -236,6 +238,8 @@ public static string GetPrettyName(this Type t)
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
[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)
Expand Down Expand Up @@ -273,7 +277,10 @@ public static bool CanBeOverridden(this MethodInfo method)
/// <param name="ctor"></param>
/// <param name="arguments"></param>
/// <returns></returns>
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;
Expand All @@ -288,7 +295,11 @@ public static bool TryFindConstructor(this Type type, [NotNullWhen(true)]out Con
/// <param name="method"></param>
/// <param name="argumentType"></param>
/// <returns></returns>
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)
{
Expand All @@ -312,7 +323,11 @@ public static bool TryFindMethod(this Type? type, string methodName, [NotNullWhe
/// <param name="method"></param>
/// <param name="argumentType"></param>
/// <returns></returns>
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)
{
Expand Down
8 changes: 7 additions & 1 deletion src/JasperFx/Core/Reflection/ReflectionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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<Expression> args = VisitList(nex.Arguments);
Expand Down Expand Up @@ -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<Expression> exprs = VisitList(na.Expressions);
Expand Down
Loading
Loading