Skip to content

Commit

Permalink
Support generic fields in PersistedAssemblyBuilder (dotnet#110372)
Browse files Browse the repository at this point in the history
  • Loading branch information
steveharter authored Dec 19, 2024
1 parent 2d1175b commit 36fef72
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -734,13 +734,16 @@ private EntityHandle GetMemberReferenceHandle(MemberInfo memberInfo)
{
case FieldInfo field:
Type declaringType = field.DeclaringType!;
if (field.DeclaringType!.IsGenericTypeDefinition)
if (declaringType.IsGenericTypeDefinition)
{
//The type of the field has to be fully instantiated type.
declaringType = declaringType.MakeGenericType(declaringType.GetGenericArguments());
}

Type fieldType = ((FieldInfo)GetOriginalMemberIfConstructedType(field)).FieldType;
memberHandle = AddMemberReference(field.Name, GetTypeHandle(declaringType),
MetadataSignatureHelper.GetFieldSignature(field.FieldType, field.GetRequiredCustomModifiers(), field.GetOptionalCustomModifiers(), this));
MetadataSignatureHelper.GetFieldSignature(fieldType, field.GetRequiredCustomModifiers(), field.GetOptionalCustomModifiers(), this));

break;
case ConstructorInfo ctor:
ctor = (ConstructorInfo)GetOriginalMemberIfConstructedType(ctor);
Expand Down Expand Up @@ -809,17 +812,17 @@ internal static SignatureCallingConvention GetSignatureConvention(CallingConvent
return convention;
}

private MemberInfo GetOriginalMemberIfConstructedType(MethodBase methodBase)
private MemberInfo GetOriginalMemberIfConstructedType(MemberInfo memberInfo)
{
Type declaringType = methodBase.DeclaringType!;
Type declaringType = memberInfo.DeclaringType!;
if (declaringType.IsConstructedGenericType &&
declaringType.GetGenericTypeDefinition() is not TypeBuilderImpl &&
!ContainsTypeBuilder(declaringType.GetGenericArguments()))
{
return declaringType.GetGenericTypeDefinition().GetMemberWithSameMetadataDefinitionAs(methodBase);
return declaringType.GetGenericTypeDefinition().GetMemberWithSameMetadataDefinitionAs(memberInfo);
}

return methodBase;
return memberInfo;
}

private static Type[] ParameterTypes(ParameterInfo[] parameterInfos)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using Xunit;

namespace System.Reflection.Emit.Tests
Expand Down Expand Up @@ -124,11 +126,15 @@ public void CreateMembersThatUsesTypeLoadedFromCoreAssemblyTest()
}
}

private static TypeBuilder CreateAssemblyAndDefineType(out PersistedAssemblyBuilder assemblyBuilder)
private static ModuleBuilder CreateAssembly(out PersistedAssemblyBuilder assemblyBuilder)
{
assemblyBuilder = AssemblySaveTools.PopulateAssemblyBuilder(s_assemblyName);
return assemblyBuilder.DefineDynamicModule("MyModule")
.DefineType("TestInterface", TypeAttributes.Interface | TypeAttributes.Abstract);
return assemblyBuilder.DefineDynamicModule("MyModule");
}

private static TypeBuilder CreateAssemblyAndDefineType(out PersistedAssemblyBuilder assemblyBuilder)
{
return CreateAssembly(out assemblyBuilder).DefineType("TestInterface", TypeAttributes.Interface | TypeAttributes.Abstract);
}

[Fact]
Expand Down Expand Up @@ -205,6 +211,83 @@ public void SaveGenericTypeParametersForAType(string[] typeParamNames)
}
}

private class GenericClassWithGenericField<T>
{
#pragma warning disable CS0649
public T F;
#pragma warning restore CS0649
}

private class GenericClassWithNonGenericField<T>
{
#pragma warning disable CS0649
public int F;
#pragma warning restore CS0649
}

public static IEnumerable<object[]> GenericTypesWithField()
{
yield return new object[] { typeof(GenericClassWithGenericField<int>), true };
yield return new object[] { typeof(GenericClassWithNonGenericField<bool>), false };
}

[Theory]
[MemberData(nameof(GenericTypesWithField))]
public void SaveGenericField(Type declaringType, bool shouldFieldBeGeneric)
{
using (TempFile file = TempFile.Create())
{
ModuleBuilder mb = CreateAssembly(out PersistedAssemblyBuilder assemblyBuilder);
TypeBuilder tb = mb.DefineType("C", TypeAttributes.Class);
MethodBuilder method = tb.DefineMethod("TestMethod", MethodAttributes.Public, returnType: typeof(int), parameterTypes: null);
ILGenerator il = method.GetILGenerator();
il.Emit(OpCodes.Newobj, declaringType.GetConstructor([]));
il.Emit(OpCodes.Ldfld, declaringType.GetField("F"));
il.Emit(OpCodes.Ret);
Type createdType = tb.CreateType();
assemblyBuilder.Save(file.Path);

using (FileStream stream = File.OpenRead(file.Path))
{
using (PEReader peReader = new PEReader(stream))
{
bool found = false;
MetadataReader metadataReader = peReader.GetMetadataReader();
foreach (MemberReferenceHandle memberRefHandle in metadataReader.MemberReferences)
{
MemberReference memberRef = metadataReader.GetMemberReference(memberRefHandle);
if (memberRef.GetKind() == MemberReferenceKind.Field)
{
Assert.False(found);
found = true;

Assert.Equal("F", metadataReader.GetString(memberRef.Name));

// A reference to a generic field should point to the open generic field, and not the resolved generic type.
Assert.Equal(shouldFieldBeGeneric, IsGenericField(metadataReader.GetBlobReader(memberRef.Signature)));
}
}

Assert.True(found);
}
}
}

static bool IsGenericField(BlobReader signatureReader)
{
while (signatureReader.RemainingBytes > 0)
{
SignatureTypeCode typeCode = signatureReader.ReadSignatureTypeCode();
if (typeCode == SignatureTypeCode.GenericTypeParameter)
{
return true;
}
}

return false;
}
}

private static void SetVariousGenericParameterValues(GenericTypeParameterBuilder[] typeParams)
{
typeParams[0].SetInterfaceConstraints([typeof(IAccess), typeof(INoMethod)]);
Expand Down

0 comments on commit 36fef72

Please sign in to comment.