Skip to content

Commit

Permalink
Apply performance improvements to the non-emit reflection provider.
Browse files Browse the repository at this point in the history
  • Loading branch information
eiriktsarpalis committed Nov 17, 2024
1 parent e52cab5 commit a63359e
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 46 deletions.
20 changes: 16 additions & 4 deletions src/PolyType/ReflectionProvider/Helpers/ReflectionHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,11 @@

namespace PolyType.ReflectionProvider;

#if !IS_TEST_PROJECT
[RequiresUnreferencedCode(ReflectionTypeShapeProvider.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(ReflectionTypeShapeProvider.RequiresDynamicCodeMessage)]
#endif
internal static class ReflectionHelpers
{
private const string RequiresUnreferencedCodeMessage = "The method requires unreferenced code.";
private const string RequiresDynamicCodeMessage = "The method requires dynamic code generation.";

public static bool IsNullableStruct(this Type type)
{
return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
Expand Down Expand Up @@ -42,6 +41,9 @@ public static void ResolveNullableAnnotation(this MemberInfo memberInfo, Nullabi
}
}

public static TDelegate CreateDelegate<TDelegate>(MethodInfo methodInfo) where TDelegate : Delegate
=> (TDelegate)Delegate.CreateDelegate(typeof(TDelegate), methodInfo);

public static bool IsNonNullableAnnotation(this ParameterInfo parameterInfo, NullabilityInfoContext? ctx)
{
if (GetNullabilityInfo(parameterInfo, ctx) is NullabilityInfo info)
Expand Down Expand Up @@ -166,6 +168,7 @@ public static Type GetEffectiveParameterType(this ParameterInfo type)
return parameterType.IsByRef ? parameterType.GetElementType()! : parameterType;
}

[UnconditionalSuppressMessage("Trimming", "IL2075:'this' argument does not satisfy 'DynamicallyAccessedMembersAttribute' in call to target method. The return value of the source method does not have matching annotations.", Justification = "Looking up the generic member definition of the input.")]
public static MemberInfo GetGenericMemberDefinition(this MemberInfo member)
{
if (member is Type type)
Expand Down Expand Up @@ -229,6 +232,7 @@ public static bool IsMemoryType(this Type type, [NotNullWhen(true)] out Type? el
return false;
}

[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static bool IsRecordType(this Type type)
{
return !type.IsValueType
Expand Down Expand Up @@ -258,6 +262,8 @@ public static void ResolveAccessibility(this MemberInfo memberInfo, out bool isG
}
}

[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(RequiresDynamicCodeMessage)]
public static bool TryGetCollectionBuilderAttribute(this Type type, Type elementType, [NotNullWhen(true)] out MethodInfo? builderMethod)
{
builderMethod = null;
Expand Down Expand Up @@ -330,6 +336,7 @@ public static Type MemberType(this MemberInfo memberInfo)
return memberInfo is FieldInfo f ? f.FieldType : ((PropertyInfo)memberInfo).PropertyType;
}

[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static IEnumerable<Type> GetAllInterfaces(this Type type)
{
if (type.IsInterface)
Expand All @@ -343,6 +350,7 @@ public static IEnumerable<Type> GetAllInterfaces(this Type type)
}
}

[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static bool ImplementsInterface(this Type type, Type interfaceType)
{
Debug.Assert(interfaceType.IsInterface && !interfaceType.IsConstructedGenericType);
Expand Down Expand Up @@ -373,6 +381,7 @@ public static bool IsExplicitInterfaceImplementation(this PropertyInfo propertyI
propertyInfo.SetMethod?.IsExplicitInterfaceImplementation() == true;
}

[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static PropertyInfo GetBaseDefinition(this PropertyInfo propertyInfo)
{
MethodInfo? getterOrSetter = propertyInfo.GetMethod ?? propertyInfo.SetMethod;
Expand Down Expand Up @@ -427,6 +436,7 @@ public static bool TryGetDefaultValueNormalized(this ParameterInfo parameterInfo
return true;
}

[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static ICollection<Type> GetSortedTypeHierarchy(this Type type)
{
if (!type.IsInterface)
Expand Down Expand Up @@ -480,6 +490,7 @@ public static bool IsNestedTupleRepresentation(this Type type)
genericArguments[7].IsTupleType();
}

[RequiresDynamicCode(RequiresDynamicCodeMessage)]
public static Type CreateValueTupleType(Type[] elementTypes)
{
return elementTypes.Length switch
Expand All @@ -496,6 +507,7 @@ public static Type CreateValueTupleType(Type[] elementTypes)
};
}

[RequiresUnreferencedCode(RequiresUnreferencedCodeMessage)]
public static IEnumerable<(string LogicalName, MemberInfo Member, MemberInfo[]? ParentMembers)> EnumerateTupleMemberPaths(Type tupleType)
{
// Walks the nested tuple representation, returning every element field and the parent "Rest" fields needed to access the value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
namespace PolyType.ReflectionProvider.MemberAccessors;

[RequiresUnreferencedCode(ReflectionTypeShapeProvider.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(ReflectionTypeShapeProvider.RequiresDynamicCodeMessage)]
internal sealed class ReflectionMemberAccessor : IReflectionMemberAccessor
{
public Getter<TDeclaringType, TPropertyType> CreateGetter<TDeclaringType, TPropertyType>(MemberInfo memberInfo, MemberInfo[]? parentMembers)
Expand All @@ -16,12 +15,21 @@ public Getter<TDeclaringType, TPropertyType> CreateGetter<TDeclaringType, TPrope

if (parentMembers is null or { Length: 0 })
{
return memberInfo switch
if (memberInfo is PropertyInfo p)
{
PropertyInfo p => (ref TDeclaringType obj) => (TPropertyType)p.GetValue(obj)!,
FieldInfo f => (ref TDeclaringType obj) => (TPropertyType)f.GetValue(obj)!,
_ => default!,
};
if (p.DeclaringType!.IsValueType)
{
// If a struct we can wrap the getter in the getter delegate directly.
return ReflectionHelpers.CreateDelegate<Getter<TDeclaringType, TPropertyType>>(p.GetMethod!);
}

// Reference types can't be wrapped directly, so we create an intermediate func delegate.
Func<TDeclaringType, TPropertyType> getterDelegate = ReflectionHelpers.CreateDelegate<Func<TDeclaringType, TPropertyType>>(p.GetMethod!);
return (ref TDeclaringType obj) => getterDelegate(obj);
}

var f = (FieldInfo)memberInfo;
return (ref TDeclaringType obj) => (TPropertyType)f.GetValueDirect(__makeref(obj))!;
}

Debug.Assert(typeof(TDeclaringType).IsNestedTupleRepresentation());
Expand All @@ -35,13 +43,14 @@ public Getter<TDeclaringType, TPropertyType> CreateGetter<TDeclaringType, TPrope
var parentFields = (FieldInfo[])parentMembers;
return (ref TDeclaringType obj) =>
{
object boxedObj = obj!;
TypedReference typedRef = __makeref(obj);
for (int i = 0; i < parentFields.Length; i++)
{
boxedObj = parentFields[i].GetValue(boxedObj)!;
object value = parentFields[i].GetValueDirect(typedRef)!;
typedRef = __makeref(value);
}

return (TPropertyType)fieldInfo.GetValue(boxedObj)!;
return (TPropertyType)fieldInfo.GetValueDirect(typedRef)!;
};
}
else
Expand Down Expand Up @@ -70,30 +79,22 @@ public Setter<TDeclaringType, TPropertyType> CreateSetter<TDeclaringType, TPrope

if (parentMembers is null or { Length: 0 })
{
return memberInfo switch
if (memberInfo is PropertyInfo p)
{
PropertyInfo p =>
!typeof(TDeclaringType).IsValueType
? (ref TDeclaringType obj, TPropertyType value) => p.SetValue(obj, value)
: (ref TDeclaringType obj, TPropertyType value) =>
{
object? boxedObj = obj;
p.SetValue(boxedObj, value);
obj = (TDeclaringType)boxedObj!;
},

FieldInfo f =>
!typeof(TDeclaringType).IsValueType
? (ref TDeclaringType obj, TPropertyType value) => f.SetValue(obj, value)
: (ref TDeclaringType obj, TPropertyType value) =>
{
object? boxedObj = obj;
f.SetValue(boxedObj, value);
obj = (TDeclaringType)boxedObj!;
},
MethodInfo setter = p.SetMethod!;
if (p.DeclaringType!.IsValueType)
{
// If a struct we can wrap the setter in the setter delegate directly.
return ReflectionHelpers.CreateDelegate<Setter<TDeclaringType, TPropertyType>>(setter);
}

_ => default!,
};
// Reference types can't be wrapped directly, so we create an intermediate action delegate.
Action<TDeclaringType, TPropertyType> setterDelegate = ReflectionHelpers.CreateDelegate<Action<TDeclaringType, TPropertyType>>(setter);
return (ref TDeclaringType obj, TPropertyType value) => setterDelegate(obj, value);
}

var f = (FieldInfo)memberInfo;
return (ref TDeclaringType obj, TPropertyType value) => f.SetValueDirect(__makeref(obj), value!);
}

Debug.Assert(typeof(TDeclaringType).IsNestedTupleRepresentation());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

namespace PolyType.ReflectionProvider;

[RequiresUnreferencedCode(ReflectionTypeShapeProvider.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(ReflectionTypeShapeProvider.RequiresDynamicCodeMessage)]
internal sealed class ReflectionConstructorParameterShape<TArgumentState, TParameter> : IConstructorParameterShape<TArgumentState, TParameter>
{
private readonly ReflectionTypeShapeProvider _provider;
Expand Down Expand Up @@ -58,8 +56,6 @@ internal interface IParameterShapeInfo
object? DefaultValue { get; }
}

[RequiresUnreferencedCode(ReflectionTypeShapeProvider.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(ReflectionTypeShapeProvider.RequiresDynamicCodeMessage)]
internal sealed class MethodParameterShapeInfo : IParameterShapeInfo
{
public MethodParameterShapeInfo(ParameterInfo parameterInfo, bool isNonNullable, MemberInfo? matchingMember = null, string? logicalName = null)
Expand Down Expand Up @@ -95,8 +91,6 @@ public MethodParameterShapeInfo(ParameterInfo parameterInfo, bool isNonNullable,
public object? DefaultValue { get; }
}

[RequiresUnreferencedCode(ReflectionTypeShapeProvider.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(ReflectionTypeShapeProvider.RequiresDynamicCodeMessage)]
internal sealed class MemberInitializerShapeInfo : IParameterShapeInfo
{
public MemberInitializerShapeInfo(MemberInfo memberInfo, string? logicalName, bool ctorSetsRequiredMembers, bool isSetterNonNullable)
Expand Down
2 changes: 0 additions & 2 deletions src/PolyType/ReflectionProvider/ReflectionConstructorShape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ internal interface IConstructorShapeInfo
IParameterShapeInfo[] Parameters { get; }
}

[RequiresUnreferencedCode(ReflectionTypeShapeProvider.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(ReflectionTypeShapeProvider.RequiresDynamicCodeMessage)]
internal sealed class MethodConstructorShapeInfo : IConstructorShapeInfo
{
public MethodConstructorShapeInfo(
Expand Down
2 changes: 0 additions & 2 deletions src/PolyType/ReflectionProvider/ReflectionPropertyShape.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@

namespace PolyType.ReflectionProvider;

[RequiresUnreferencedCode(ReflectionTypeShapeProvider.RequiresUnreferencedCodeMessage)]
[RequiresDynamicCode(ReflectionTypeShapeProvider.RequiresDynamicCodeMessage)]
internal sealed class ReflectionPropertyShape<TDeclaringType, TPropertyType> : IPropertyShape<TDeclaringType, TPropertyType>
{
private readonly ReflectionTypeShapeProvider _provider;
Expand Down
1 change: 0 additions & 1 deletion tests/PolyType.Tests/PolyType.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<DefineConstants>IS_TEST_PROJECT</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand Down

0 comments on commit a63359e

Please sign in to comment.