summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShockah <me@shockah.pl>2022-02-08 21:54:53 +0100
committerShockah <me@shockah.pl>2022-02-08 21:54:53 +0100
commit3135925982b01d8f316adc171c7c1235ea41c1d3 (patch)
tree0052d86afe6e83eb7e09cb2cd40fb0384eb21451
parent0ff82c38e7a5b630256d2cd23a63ac1088d13e39 (diff)
downloadSMAPI-3135925982b01d8f316adc171c7c1235ea41c1d3.tar.gz
SMAPI-3135925982b01d8f316adc171c7c1235ea41c1d3.tar.bz2
SMAPI-3135925982b01d8f316adc171c7c1235ea41c1d3.zip
allow generic methods and any assignable types in API proxies
-rw-r--r--src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs53
1 files changed, 43 insertions, 10 deletions
diff --git a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
index 164cac0b..35faa852 100644
--- a/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
+++ b/src/SMAPI/Framework/Reflection/InterfaceProxyBuilder.cs
@@ -65,15 +65,30 @@ namespace StardewModdingAPI.Framework.Reflection
}
}
+ bool AreTypesMatching(Type targetType, Type proxyType, MethodTypeMatchingPart part)
+ {
+ var typeA = part == MethodTypeMatchingPart.Parameter ? targetType : proxyType;
+ var typeB = part == MethodTypeMatchingPart.Parameter ? proxyType : targetType;
+
+ if (typeA.IsGenericMethodParameter != typeB.IsGenericMethodParameter)
+ return false;
+ // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality)
+ return typeA.IsGenericMethodParameter ? typeA.GenericParameterPosition == typeB.GenericParameterPosition : typeA.IsAssignableFrom(typeB);
+ }
+
// proxy methods
foreach (MethodInfo proxyMethod in interfaceType.GetMethods())
{
var proxyMethodParameters = proxyMethod.GetParameters();
+ var proxyMethodGenericArguments = proxyMethod.GetGenericArguments();
var targetMethod = allTargetMethods.Where(m =>
{
if (m.Name != proxyMethod.Name)
return false;
- if (m.ReturnType != proxyMethod.ReturnType)
+
+ if (m.GetGenericArguments().Length != proxyMethodGenericArguments.Length)
+ return false;
+ if (!AreTypesMatching(m.ReturnType, proxyMethod.ReturnType, MethodTypeMatchingPart.ReturnType))
return false;
var mParameters = m.GetParameters();
@@ -81,9 +96,7 @@ namespace StardewModdingAPI.Framework.Reflection
return false;
for (int i = 0; i < mParameters.Length; i++)
{
- // TODO: decide if "assignable" checking is desired (instead of just 1:1 type equality)
- // TODO: test if this actually works
- if (!mParameters[i].ParameterType.IsAssignableFrom(proxyMethodParameters[i].ParameterType))
+ if (!AreTypesMatching(mParameters[i].ParameterType, proxyMethodParameters[i].ParameterType, MethodTypeMatchingPart.Parameter))
return false;
}
return true;
@@ -91,7 +104,7 @@ namespace StardewModdingAPI.Framework.Reflection
if (targetMethod == null)
throw new InvalidOperationException($"The {interfaceType.FullName} interface defines method {proxyMethod.Name} which doesn't exist in the API.");
- this.ProxyMethod(proxyBuilder, targetMethod, targetField);
+ this.ProxyMethod(proxyBuilder, proxyMethod, targetMethod, targetField);
}
// save info
@@ -115,16 +128,30 @@ namespace StardewModdingAPI.Framework.Reflection
*********/
/// <summary>Define a method which proxies access to a method on the target.</summary>
/// <param name="proxyBuilder">The proxy type being generated.</param>
+ /// <param name="proxy">The proxy method.</param>
/// <param name="target">The target method.</param>
/// <param name="instanceField">The proxy field containing the API instance.</param>
- private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo target, FieldBuilder instanceField)
+ private void ProxyMethod(TypeBuilder proxyBuilder, MethodInfo proxy, MethodInfo target, FieldBuilder instanceField)
{
- Type[] argTypes = target.GetParameters().Select(a => a.ParameterType).ToArray();
-
- // create method
MethodBuilder methodBuilder = proxyBuilder.DefineMethod(target.Name, MethodAttributes.Public | MethodAttributes.Final | MethodAttributes.Virtual);
+
+ // set up generic arguments
+ Type[] proxyGenericArguments = proxy.GetGenericArguments();
+ string[] genericArgNames = proxyGenericArguments.Select(a => a.Name).ToArray();
+ GenericTypeParameterBuilder[] genericTypeParameterBuilders = proxyGenericArguments.Length == 0 ? null : methodBuilder.DefineGenericParameters(genericArgNames);
+ for (int i = 0; i < proxyGenericArguments.Length; i++)
+ genericTypeParameterBuilders[i].SetGenericParameterAttributes(proxyGenericArguments[i].GenericParameterAttributes);
+
+ // set up return type
+ // TODO: keep if it's decided to use isAssignableFrom
+ methodBuilder.SetReturnType(proxy.ReturnType.IsGenericMethodParameter ? genericTypeParameterBuilders[proxy.ReturnType.GenericParameterPosition] : proxy.ReturnType);
+
+ // set up parameters
+ Type[] argTypes = proxy.GetParameters()
+ .Select(a => a.ParameterType)
+ .Select(t => t.IsGenericMethodParameter ? genericTypeParameterBuilders[t.GenericParameterPosition] : t)
+ .ToArray();
methodBuilder.SetParameters(argTypes);
- methodBuilder.SetReturnType(target.ReturnType);
// create method body
{
@@ -143,5 +170,11 @@ namespace StardewModdingAPI.Framework.Reflection
il.Emit(OpCodes.Ret);
}
}
+
+ /// <summary>The part of a method that is being matched.</summary>
+ private enum MethodTypeMatchingPart
+ {
+ ReturnType, Parameter
+ }
}
}