Implement proper generic parameter substitution

With this patch, generic parameters in BaseType and method param/return
types are substituted correctly and deeply. Next up will be to apply the
same substitution rules to fields, properties, events, ...
This commit is contained in:
Robert Xiao
2020-04-11 22:20:21 -07:00
committed by Katy
parent 202a802274
commit 1970879e52
3 changed files with 44 additions and 11 deletions

View File

@@ -110,14 +110,11 @@ namespace Il2CppInspector.Reflection
DefaultValueMetadataAddress = generic.DefaultValueMetadataAddress; DefaultValueMetadataAddress = generic.DefaultValueMetadataAddress;
} }
public ParameterInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments) { public ParameterInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) {
/* TODO: Deep substitution */ TypeInfo t = ParameterType.SubstituteGenericArguments(typeArguments, methodArguments);
if (ParameterType.IsGenericTypeParameter) if (t == ParameterType)
return new ParameterInfo(this, typeArguments[ParameterType.GenericParameterPosition]);
else if (ParameterType.IsGenericMethodParameter)
return new ParameterInfo(this, methodArguments[ParameterType.GenericParameterPosition]);
else
return this; return this;
return new ParameterInfo(this, t);
} }
// ref will be handled as part of the type name // ref will be handled as part of the type name

View File

@@ -66,8 +66,7 @@ namespace Il2CppInspector.Reflection {
return Assembly.Model.TypesByReferenceIndex[Definition.parentIndex]; return Assembly.Model.TypesByReferenceIndex[Definition.parentIndex];
} }
if (genericTypeDefinition != null) { if (genericTypeDefinition != null) {
/* TODO substitute generic arguments */ return genericTypeDefinition.BaseType.SubstituteGenericArguments(genericArguments);
return genericTypeDefinition.BaseType;
} }
if (Namespace != "System" || BaseName != "Object") if (Namespace != "System" || BaseName != "Object")
return Assembly.Model.TypesByFullName["System.Object"]; return Assembly.Model.TypesByFullName["System.Object"];
@@ -90,8 +89,7 @@ namespace Il2CppInspector.Reflection {
return type; return type;
} }
if (genericTypeDefinition != null) { if (genericTypeDefinition != null) {
/* Generic type instance */ // Generic parameters are *not* substituted in the DeclaringType
/* TODO substitute generic arguments */
return genericTypeDefinition.DeclaringType; return genericTypeDefinition.DeclaringType;
} }
return base.DeclaringType; return base.DeclaringType;
@@ -264,6 +262,9 @@ namespace Il2CppInspector.Reflection {
n += "*"; n += "*";
return n; return n;
} else { } else {
/* XXX This is not exactly accurate to C# Type.Name:
* Type.Name should be the bare name (with & * [] suffixes)
* but without nested types or generic arguments */
var n = base.Name; var n = base.Name;
if (DeclaringType != null) if (DeclaringType != null)
n = DeclaringType.Name + "+" + n; n = DeclaringType.Name + "+" + n;
@@ -832,6 +833,10 @@ namespace Il2CppInspector.Reflection {
// and returns a TypeInfo object representing the resulting constructed type. // and returns a TypeInfo object representing the resulting constructed type.
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.makegenerictype?view=netframework-4.8 // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.makegenerictype?view=netframework-4.8
public TypeInfo MakeGenericType(params TypeInfo[] typeArguments) { public TypeInfo MakeGenericType(params TypeInfo[] typeArguments) {
if(typeArguments.Length != genericArguments.Length) {
throw new ArgumentException("The number of generic arguments provided does not match the generic type definition.");
}
TypeInfo result; TypeInfo result;
if (genericTypeInstances.TryGetValue(typeArguments, out result)) if (genericTypeInstances.TryGetValue(typeArguments, out result))
return result; return result;
@@ -840,6 +845,31 @@ namespace Il2CppInspector.Reflection {
return result; return result;
} }
public TypeInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) {
if (!ContainsGenericParameters)
return this;
if (IsGenericTypeParameter)
return typeArguments[GenericParameterPosition];
else if (IsGenericMethodParameter)
return methodArguments[GenericParameterPosition];
else if(IsGenericTypeDefinition)
return MakeGenericType(typeArguments);
else if(HasElementType) {
var elementType = ElementType.SubstituteGenericArguments(typeArguments, methodArguments);
if (IsArray)
return elementType.MakeArrayType(GetArrayRank());
else if (IsByRef)
return elementType.MakeByRefType();
else if (IsPointer)
return elementType.MakePointerType();
throw new InvalidOperationException("TypeInfo element type state is invalid!");
} else {
var newGenericArguments = genericArguments.Select(x => x.SubstituteGenericArguments(typeArguments, methodArguments));
return genericTypeDefinition.MakeGenericType(newGenericArguments.ToArray());
}
}
// Initialize a type that is a generic parameter of a generic type // Initialize a type that is a generic parameter of a generic type
// See: https://docs.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?view=netframework-4.8 // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?view=netframework-4.8
public TypeInfo(TypeInfo declaringType, Il2CppGenericParameter param) : base(declaringType) { public TypeInfo(TypeInfo declaringType, Il2CppGenericParameter param) : base(declaringType) {

View File

@@ -32,8 +32,11 @@ namespace Il2CppInspector
// Act // Act
TypeInfo tBase = asm.GetType("Il2CppTests.TestSources.Base`2"); TypeInfo tBase = asm.GetType("Il2CppTests.TestSources.Base`2");
TypeInfo tDerived = asm.GetType("Il2CppTests.TestSources.Derived`1"); TypeInfo tDerived = asm.GetType("Il2CppTests.TestSources.Derived`1");
TypeInfo tDerived_closed = model.GetType("Il2CppTests.TestSources.Derived`1[System.Int32]");
TypeInfo tDerivedBase = tDerived.BaseType; TypeInfo tDerivedBase = tDerived.BaseType;
TypeInfo tDerivedBase_closed = tDerived_closed.BaseType;
TypeInfo tDerivedArray = model.GetType("Il2CppTests.TestSources.Derived`1[System.Int32][]"); TypeInfo tDerivedArray = model.GetType("Il2CppTests.TestSources.Derived`1[System.Int32][]");
Assert.That(tDerivedArray, Is.EqualTo(tDerived_closed.MakeArrayType()));
TypeInfo tT = tBase.GenericTypeParameters[0]; TypeInfo tT = tBase.GenericTypeParameters[0];
TypeInfo tU = tBase.GenericTypeParameters[1]; TypeInfo tU = tBase.GenericTypeParameters[1];
@@ -74,6 +77,7 @@ namespace Il2CppInspector
(tBase, "Base`2[T,U]", true, true, true, false, -1), (tBase, "Base`2[T,U]", true, true, true, false, -1),
(tDerived, "Derived`1[V]", true, true, true, false, -1), (tDerived, "Derived`1[V]", true, true, true, false, -1),
(tDerivedBase, "Base`2[System.String,V]", true, false, true, false, -1), (tDerivedBase, "Base`2[System.String,V]", true, false, true, false, -1),
(tDerivedBase_closed, "Base`2[System.String,System.Int32]", true, false, false, false, -1),
(tDerivedArray, "Derived`1[System.Int32][]", false, false, false, false, -1), (tDerivedArray, "Derived`1[System.Int32][]", false, false, false, false, -1),
(tT, "T", false, false, true, true, 0), (tT, "T", false, false, true, true, 0),
(tU, "U", false, false, true, true, 1), (tU, "U", false, false, true, true, 1),
@@ -103,6 +107,8 @@ namespace Il2CppInspector
Assert.That(t.IsGenericTypeDefinition, Is.EqualTo(check.Item4)); Assert.That(t.IsGenericTypeDefinition, Is.EqualTo(check.Item4));
Assert.That(t.ContainsGenericParameters, Is.EqualTo(check.Item5)); Assert.That(t.ContainsGenericParameters, Is.EqualTo(check.Item5));
Assert.That(t.IsGenericParameter, Is.EqualTo(check.Item6)); Assert.That(t.IsGenericParameter, Is.EqualTo(check.Item6));
if (t.IsGenericParameter)
Assert.That(t.GenericParameterPosition, Is.EqualTo(check.Item7));
} }
foreach (var check in methodChecks) { foreach (var check in methodChecks) {