fix: generic arguments resolution (#2300)

* fix: generic arguments lookup

The weaver was not able to figure out the synclist type in code like this:

```cs
    public class SomeList<G, T> : SyncList<T> { }
    public class SomeListInt : SomeList<string, int> { }
```

This code fixes that problem, and greatly reduces the complexity
of argument lookup.

* linting
This commit is contained in:
Paul Pacheco 2020-09-29 09:39:17 -05:00 committed by GitHub
parent 720a06ea70
commit 8dbf46720e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 53 additions and 118 deletions

View File

@ -249,12 +249,12 @@ public static MethodDefinition GetMethodInBaseType(this TypeDefinition td, strin
/// <param name="methodName"></param>
/// <param name="stopAt"></param>
/// <returns></returns>
public static bool HasMethodInBaseType(this TypeDefinition td, string methodName, TypeReference stopAt)
public static bool HasMethodInBaseType(this TypeDefinition td, string methodName, Type stopAt)
{
TypeDefinition typedef = td;
while (typedef != null)
{
if (typedef.FullName == stopAt.FullName)
if (typedef.Is(stopAt))
break;
foreach (MethodDefinition md in typedef.Methods)

View File

@ -1,107 +1,56 @@
using System.Collections.Generic;
using System;
using Mono.CecilX;
namespace Mirror.Weaver
{
public class GenericArgumentResolver
public static class GenericArgumentResolver
{
readonly Stack<TypeReference> stack = new Stack<TypeReference>();
readonly int maxGenericArgument;
public GenericArgumentResolver(int maxGenericArgument)
static TypeReference[] GetGenericArguments(TypeReference tr, Type baseType, TypeReference[] genericArguments)
{
this.maxGenericArgument = maxGenericArgument;
}
if (tr == null)
return null;
public bool GetGenericFromBaseClass(TypeDefinition td, int genericArgument, TypeReference baseType, out TypeReference itemType)
{
itemType = null;
if (GetGenericBaseType(td, baseType, out GenericInstanceType parent))
TypeReference[] resolvedArguments = new TypeReference[0];
if (tr is GenericInstanceType genericInstance)
{
TypeReference arg = parent.GenericArguments[genericArgument];
if (arg.IsGenericParameter)
// this type is a generic instance, for example List<int>
// however, the parameter may be generic itself, for example:
// List<T>. If T is a generic parameter, then look it up in
// from the arguments we got.
resolvedArguments = genericInstance.GenericArguments.ToArray();
for (int i = 0; i< resolvedArguments.Length; i++)
{
itemType = FindParameterInStack(td, genericArgument);
}
else
{
itemType = Weaver.CurrentAssembly.MainModule.ImportReference(arg);
TypeReference argument = resolvedArguments[i];
if (argument is GenericParameter genericArgument)
{
resolvedArguments[i] = genericArguments[genericArgument.Position];
}
}
}
return itemType != null;
}
if (tr.Is(baseType))
return resolvedArguments;
TypeReference FindParameterInStack(TypeDefinition td, int genericArgument)
{
while (stack.Count > 0)
{
TypeReference next = stack.Pop();
if (tr.CanBeResolved())
return GetGenericArguments(tr.Resolve().BaseType, baseType, resolvedArguments);
if (!(next is GenericInstanceType genericType))
{
// if type is not GenericInstanceType something has gone wrong
return null;
}
if (genericType.GenericArguments.Count < genericArgument)
{
// if less than `genericArgument` then we didnt find generic argument
return null;
}
if (genericType.GenericArguments.Count > maxGenericArgument)
{
// if greater than `genericArgument` it is hard to know which generic arg we want
// See SyncListGenericInheritanceWithMultipleGeneric test
Weaver.Error($"Type {td.Name} has too many generic arguments in base class {next}", td);
return null;
}
TypeReference genericArg = genericType.GenericArguments[genericArgument];
if (!genericArg.IsGenericParameter)
{
// if not generic, successfully found type
return Weaver.CurrentAssembly.MainModule.ImportReference(genericArg);
}
}
// nothing left in stack, something went wrong
return null;
}
bool GetGenericBaseType(TypeDefinition td, TypeReference baseType, out GenericInstanceType found)
/// <summary>
/// Find out what the arguments are to a generic base type
/// </summary>
/// <param name="td"></param>
/// <param name="baseType"></param>
/// <returns></returns>
public static TypeReference[] GetGenericArguments(TypeDefinition td, Type baseType)
{
stack.Clear();
TypeReference parent = td.BaseType;
found = null;
if (td == null)
return null;
while (parent != null)
{
string parentName = parent.FullName;
// strip generic <T> parameters from class name (if any)
parentName = Extensions.StripGenericParametersFromClassName(parentName);
if (parentName == baseType.FullName)
{
found = parent as GenericInstanceType;
break;
}
try
{
stack.Push(parent);
parent = parent.Resolve().BaseType;
}
catch (AssemblyResolutionException)
{
// this can happen for plugins.
break;
}
}
return found != null;
return GetGenericArguments(td.BaseType, baseType, new TypeReference[] { });
}
}
}

View File

@ -13,22 +13,11 @@ static class SyncDictionaryProcessor
/// <param name="td">The synclist class</param>
public static void Process(TypeDefinition td)
{
GenericArgumentResolver resolver = new GenericArgumentResolver(2);
TypeReference syncDictionaryType = WeaverTypes.Import(typeof(SyncDictionary<,>));
if (resolver.GetGenericFromBaseClass(td, 0, syncDictionaryType, out TypeReference keyType))
TypeReference []arguments = GenericArgumentResolver.GetGenericArguments(td, typeof(SyncDictionary<,>));
if (arguments != null)
{
SyncObjectProcessor.GenerateSerialization(td, keyType, syncDictionaryType, "SerializeKey", "DeserializeKey");
}
else
{
Weaver.Error($"Could not find generic arguments for SyncDictionary in {td.Name}", td);
return;
}
if (resolver.GetGenericFromBaseClass(td, 1, syncDictionaryType, out TypeReference itemType))
{
SyncObjectProcessor.GenerateSerialization(td, itemType, syncDictionaryType, "SerializeItem", "DeserializeItem");
SyncObjectProcessor.GenerateSerialization(td, arguments[0], typeof(SyncDictionary<,>), "SerializeKey", "DeserializeKey");
SyncObjectProcessor.GenerateSerialization(td, arguments[1], typeof(SyncDictionary<,>), "SerializeItem", "DeserializeItem");
}
else
{

View File

@ -1,3 +1,4 @@
using System;
using Mono.CecilX;
namespace Mirror.Weaver
@ -12,13 +13,12 @@ static class SyncListProcessor
/// </summary>
/// <param name="td">The synclist class</param>
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
public static void Process(TypeDefinition td, TypeReference mirrorBaseType)
public static void Process(TypeDefinition td, Type mirrorBaseType)
{
GenericArgumentResolver resolver = new GenericArgumentResolver(1);
if (resolver.GetGenericFromBaseClass(td, 0, mirrorBaseType, out TypeReference itemType))
TypeReference [] arguments = GenericArgumentResolver.GetGenericArguments(td, mirrorBaseType);
if (arguments != null)
{
SyncObjectProcessor.GenerateSerialization(td, itemType, mirrorBaseType, "SerializeItem", "DeserializeItem");
SyncObjectProcessor.GenerateSerialization(td, arguments[0], mirrorBaseType, "SerializeItem", "DeserializeItem");
}
else
{

View File

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using Mono.CecilX;
using Mono.CecilX.Cil;
@ -48,7 +49,7 @@ public static List<FieldDefinition> FindSyncObjectsFields(TypeDefinition td)
/// <param name="mirrorBaseType">the base SyncObject td inherits from</param>
/// <param name="serializeMethod">The name of the serialize method</param>
/// <param name="deserializeMethod">The name of the deserialize method</param>
public static void GenerateSerialization(TypeDefinition td, TypeReference itemType, TypeReference mirrorBaseType, string serializeMethod, string deserializeMethod)
public static void GenerateSerialization(TypeDefinition td, TypeReference itemType, Type mirrorBaseType, string serializeMethod, string deserializeMethod)
{
Weaver.DLog(td, "SyncObjectProcessor Start item:" + itemType.FullName);
@ -65,7 +66,7 @@ public static void GenerateSerialization(TypeDefinition td, TypeReference itemTy
}
// serialization of individual element
static bool GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType, TypeReference mirrorBaseType)
static bool GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType, Type mirrorBaseType)
{
Weaver.DLog(td, " GenerateSerialization");
bool existing = td.HasMethodInBaseType(methodName, mirrorBaseType);
@ -109,7 +110,7 @@ static bool GenerateSerialization(string methodName, TypeDefinition td, TypeRefe
return true;
}
static bool GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType, TypeReference mirrorBaseType)
static bool GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType, Type mirrorBaseType)
{
Weaver.DLog(td, " GenerateDeserialization");
bool existing = td.HasMethodInBaseType(methodName, mirrorBaseType);

View File

@ -210,12 +210,12 @@ static bool WeaveSyncObject(TypeDefinition td)
{
if (td.IsDerivedFrom(typeof(SyncList<>)))
{
SyncListProcessor.Process(td, WeaverTypes.Import(typeof(SyncList<>)));
SyncListProcessor.Process(td, typeof(SyncList<>));
modified = true;
}
else if (td.IsDerivedFrom(typeof(SyncSet<>)))
{
SyncListProcessor.Process(td, WeaverTypes.Import(typeof(SyncSet<>)));
SyncListProcessor.Process(td, typeof(SyncSet<>));
modified = true;
}
else if (td.IsDerivedFrom(typeof(SyncDictionary<,>)))

View File

@ -31,8 +31,7 @@ public void SyncListGenericInheritance()
[Test]
public void SyncListGenericInheritanceWithMultipleGeneric()
{
HasError("Could not find generic arguments for SyncList`1 in WeaverSyncListTests.SyncListGenericInheritanceWithMultipleGeneric.SyncListGenericInheritanceWithMultipleGeneric/SomeListInt",
"WeaverSyncListTests.SyncListGenericInheritanceWithMultipleGeneric.SyncListGenericInheritanceWithMultipleGeneric/SomeListInt");
IsSuccess();
}
[Test]

View File

@ -3,11 +3,8 @@
namespace WeaverSyncListTests.SyncListGenericInheritanceWithMultipleGeneric
{
/*
This test will fail
It is hard to know which generic argument we want from `SomeList<string, int>`
So instead give a useful error for this edge case
This test should pass
*/
class SyncListGenericInheritanceWithMultipleGeneric : NetworkBehaviour
{
readonly SomeListInt someList = new SomeListInt();