mirror of
https://github.com/MirrorNetworking/Mirror.git
synced 2024-11-18 02:50:32 +00:00
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:
parent
720a06ea70
commit
8dbf46720e
@ -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)
|
||||
|
@ -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;
|
||||
static TypeReference[] GetGenericArguments(TypeReference tr, Type baseType, TypeReference[] genericArguments)
|
||||
{
|
||||
if (tr == null)
|
||||
return null;
|
||||
|
||||
public GenericArgumentResolver(int maxGenericArgument)
|
||||
{
|
||||
this.maxGenericArgument = maxGenericArgument;
|
||||
}
|
||||
TypeReference[] resolvedArguments = new TypeReference[0];
|
||||
|
||||
public bool GetGenericFromBaseClass(TypeDefinition td, int genericArgument, TypeReference baseType, out TypeReference itemType)
|
||||
if (tr is GenericInstanceType genericInstance)
|
||||
{
|
||||
itemType = null;
|
||||
if (GetGenericBaseType(td, baseType, out GenericInstanceType parent))
|
||||
// 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++)
|
||||
{
|
||||
TypeReference arg = parent.GenericArguments[genericArgument];
|
||||
if (arg.IsGenericParameter)
|
||||
TypeReference argument = resolvedArguments[i];
|
||||
if (argument is GenericParameter genericArgument)
|
||||
{
|
||||
itemType = FindParameterInStack(td, genericArgument);
|
||||
resolvedArguments[i] = genericArguments[genericArgument.Position];
|
||||
}
|
||||
else
|
||||
{
|
||||
itemType = Weaver.CurrentAssembly.MainModule.ImportReference(arg);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
/// <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)
|
||||
{
|
||||
// if less than `genericArgument` then we didnt find generic argument
|
||||
if (td == null)
|
||||
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)
|
||||
{
|
||||
stack.Clear();
|
||||
TypeReference parent = td.BaseType;
|
||||
found = 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[] { });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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<,>)))
|
||||
|
@ -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]
|
||||
|
@ -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();
|
||||
|
Loading…
Reference in New Issue
Block a user