From 36498bca160d162cd06983183eccad46ff76ca63 Mon Sep 17 00:00:00 2001 From: Victor Nova Date: Tue, 4 Jan 2022 12:57:09 -0800 Subject: [PATCH] improved support for generic method overloading Prior to this change if method had multiple generic overloads, only 1 of them could be matched (whichever one reflection would return first) Now MethodBinder.MatchParameters returns all matching generic overloads, not just the first one. fixes https://github.com/pythonnet/pythonnet/issues/1522 --- src/runtime/methodbinder.cs | 38 ++++++++++++++++++++---------------- src/runtime/methodbinding.cs | 11 +++++++---- src/runtime/methodobject.cs | 5 ++++- src/testing/methodtest.cs | 3 +++ tests/test_generic.py | 12 ++++++++++++ 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/runtime/methodbinder.cs b/src/runtime/methodbinder.cs index 95c3aaa4a..42d3822fc 100644 --- a/src/runtime/methodbinder.cs +++ b/src/runtime/methodbinder.cs @@ -86,16 +86,17 @@ internal void AddMethod(MethodBase m) /// /// Given a sequence of MethodInfo and a sequence of type parameters, - /// return the MethodInfo that represents the matching closed generic. + /// return the MethodInfo(s) that represents the matching closed generic. /// If unsuccessful, returns null and may set a Python error. /// - internal static MethodInfo? MatchParameters(MethodBase[] mi, Type[]? tp) + internal static MethodInfo[] MatchParameters(MethodBase[] mi, Type[]? tp) { if (tp == null) { - return null; + return Array.Empty(); } int count = tp.Length; + var result = new List(); foreach (MethodInfo t in mi) { if (!t.IsGenericMethodDefinition) @@ -111,16 +112,14 @@ internal void AddMethod(MethodBase m) { // MakeGenericMethod can throw ArgumentException if the type parameters do not obey the constraints. MethodInfo method = t.MakeGenericMethod(tp); - Exceptions.Clear(); - return method; + result.Add(method); } - catch (ArgumentException e) + catch (ArgumentException) { - Exceptions.SetError(e); // The error will remain set until cleared by a successful match. } } - return null; + return result.ToArray(); } @@ -381,9 +380,6 @@ public MismatchedMethod(Exception exception, MethodBase mb) } } - var pynargs = (int)Runtime.PyTuple_Size(args); - var isGeneric = false; - MethodBase[] _methods; if (info != null) { @@ -395,11 +391,19 @@ public MismatchedMethod(Exception exception, MethodBase mb) _methods = GetMethods(); } - var argMatchedMethods = new List(_methods.Length); + return Bind(inst, args, kwargDict, _methods, matchGenerics: true); + } + + static Binding? Bind(BorrowedReference inst, BorrowedReference args, Dictionary kwargDict, MethodBase[] methods, bool matchGenerics) + { + var pynargs = (int)Runtime.PyTuple_Size(args); + var isGeneric = false; + + var argMatchedMethods = new List(methods.Length); var mismatchedMethods = new List(); // TODO: Clean up - foreach (MethodBase mi in _methods) + foreach (MethodBase mi in methods) { if (mi.IsGenericMethod) { @@ -535,17 +539,17 @@ public MismatchedMethod(Exception exception, MethodBase mb) return new Binding(mi, target, margs, outs); } - else if (isGeneric && info == null && methodinfo != null) + else if (matchGenerics && isGeneric) { // We weren't able to find a matching method but at least one // is a generic method and info is null. That happens when a generic // method was not called using the [] syntax. Let's introspect the // type of the arguments and use it to construct the correct method. Type[]? types = Runtime.PythonArgsToTypeArray(args, true); - MethodInfo? mi = MatchParameters(methodinfo, types); - if (mi != null) + MethodInfo[] overloads = MatchParameters(methods, types); + if (overloads.Length != 0) { - return Bind(inst, args, kw, mi, null); + return Bind(inst, args, kwargDict, overloads, matchGenerics: false); } } if (mismatchedMethods.Count > 0) diff --git a/src/runtime/methodbinding.cs b/src/runtime/methodbinding.cs index d9bf3aec6..6d21af01e 100644 --- a/src/runtime/methodbinding.cs +++ b/src/runtime/methodbinding.cs @@ -43,15 +43,18 @@ public static NewReference mp_subscript(BorrowedReference tp, BorrowedReference return Exceptions.RaiseTypeError("type(s) expected"); } - MethodBase? mi = self.m.IsInstanceConstructor - ? self.m.type.Value.GetConstructor(types) + MethodBase[] overloads = self.m.IsInstanceConstructor + ? self.m.type.Value.GetConstructor(types) is { } ctor + ? new[] { ctor } + : Array.Empty() : MethodBinder.MatchParameters(self.m.info, types); - if (mi == null) + if (overloads.Length == 0) { return Exceptions.RaiseTypeError("No match found for given type params"); } - var mb = new MethodBinding(self.m, self.target, self.targetType) { info = mi }; + MethodObject overloaded = self.m.WithOverloads(overloads); + var mb = new MethodBinding(overloaded, self.target, self.targetType); return mb.Alloc(); } diff --git a/src/runtime/methodobject.cs b/src/runtime/methodobject.cs index 397547616..b0fda49d3 100644 --- a/src/runtime/methodobject.cs +++ b/src/runtime/methodobject.cs @@ -27,7 +27,7 @@ internal class MethodObject : ExtensionType internal PyString? doc; internal MaybeType type; - public MethodObject(Type type, string name, MethodBase[] info, bool allow_threads = MethodBinder.DefaultAllowThreads) + public MethodObject(MaybeType type, string name, MethodBase[] info, bool allow_threads = MethodBinder.DefaultAllowThreads) { this.type = type; this.name = name; @@ -47,6 +47,9 @@ public MethodObject(Type type, string name, MethodBase[] info, bool allow_thread public bool IsInstanceConstructor => name == "__init__"; + public MethodObject WithOverloads(MethodBase[] overloads) + => new(type, name, overloads, allow_threads: binder.allow_threads); + internal MethodBase[] info { get diff --git a/src/testing/methodtest.cs b/src/testing/methodtest.cs index 9eae0e9f0..fe49de88d 100644 --- a/src/testing/methodtest.cs +++ b/src/testing/methodtest.cs @@ -646,6 +646,9 @@ public static int Overloaded(int i, string s) return i; } + public virtual void OverloadedConstrainedGeneric(T generic) where T : MethodTest { } + public virtual void OverloadedConstrainedGeneric(T generic, string str) where T: MethodTest { } + public static string CaseSensitive() { return "CaseSensitive"; diff --git a/tests/test_generic.py b/tests/test_generic.py index e03ac57af..6d514d638 100644 --- a/tests/test_generic.py +++ b/tests/test_generic.py @@ -762,6 +762,18 @@ def test_missing_generic_type(): with pytest.raises(TypeError): IList[bool] +# https://github.com/pythonnet/pythonnet/issues/1522 +def test_overload_generic_parameter(): + from Python.Test import MethodTest, MethodTestSub + + inst = MethodTest() + generic = MethodTestSub() + inst.OverloadedConstrainedGeneric(generic) + inst.OverloadedConstrainedGeneric[MethodTestSub](generic) + + inst.OverloadedConstrainedGeneric[MethodTestSub](generic, '42') + inst.OverloadedConstrainedGeneric[MethodTestSub](generic, System.String('42')) + def test_invalid_generic_type_parameter(): from Python.Test import GenericTypeWithConstraint with pytest.raises(TypeError): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy