From c0dc3b66d3500a403b90b4a2377c48bf07ba6e26 Mon Sep 17 00:00:00 2001 From: Vipul Cariappa Date: Fri, 23 Oct 2026 19:19:49 +0530 Subject: [PATCH 1/2] Update usage of CppInterOp API According to compiler-research/CppInterOp#1147 With this change, the user will not be able to call templated static function from the object. For example: ```c++ struct A { static void foo(int) {}; template static void bar(T t) {}; }; ``` ```python gbl.A().foo(1) # good gbl.A.foo(1) # good gbl.A().bar(1) # bad gbl.A.bar(1) # good ``` --- src/cpyrt/TemplateProxy.cxx | 20 +++++++++++--- src/interop/interop_wrapper.cxx | 48 +++++++++++++++++++++------------ test/test_templates.py | 5 ++-- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/cpyrt/TemplateProxy.cxx b/src/cpyrt/TemplateProxy.cxx index d826c00f..db082b29 100644 --- a/src/cpyrt/TemplateProxy.cxx +++ b/src/cpyrt/TemplateProxy.cxx @@ -5,6 +5,7 @@ using namespace cppjit; #include "CPPClassMethod.h" #include "CPPConstructor.h" #include "CPPFunction.h" +#include "CPPInstance.h" #include "CPPMethod.h" #include "CPPOverload.h" #include "PyCallable.h" @@ -81,14 +82,17 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname, std::string proto = ""; // adjust arguments for self if this is a rebound global function + PyObject* self = nullptr; bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); + bool isStaticMethod = + interop::IsStaticTemplate(((CPPScope*)fTI->fPyClass)->fCppType, fname); if (!isNS && cpyrt_PyArgs_GET_SIZE(args, nargsf) && - (!fSelf || (fSelf == Py_None && - !interop::IsStaticTemplate( - ((CPPScope*)fTI->fPyClass)->fCppType, fname)))) { + (!fSelf || (fSelf == Py_None && !isStaticMethod))) { + self = args[0]; args += 1; nargsf -= 1; - } + } else if (fSelf && fSelf != Py_None && !isStaticMethod) + self = fSelf; Py_ssize_t argc = cpyrt_PyArgs_GET_SIZE(args, nargsf); if (argc != 0) { @@ -190,6 +194,14 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname, // the following causes instantiation as necessary interop::TCppScope_t scope = ((CPPClass*)fTI->fPyClass)->fCppType; + if (self && CPPInstance_Check(self) && interop::GetName(scope) != fname) { + std::string self_name = + interop::GetScopedFinalName(((CPPInstance*)self)->ObjectIsA(true)); + if (proto.empty()) + proto = self_name + '&'; + else + proto = self_name + "&, " + proto; + } interop::TCppMethod_t cppmeth = interop::GetMethodTemplate(scope, fname, proto); if (cppmeth) { // overload stops here diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index a4c6b537..18932e0d 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -1483,9 +1483,29 @@ bool interop::IsTemplatedMethod(TCppMethod_t method) { return Cpp::IsTemplatedFunction(method); } +static void ResolveMethodsFromName( + interop::TCppScope_t scope, const std::string& name, std::string& pureName, + std::string& explicitParams, + std::vector& unresolved_candidate_methods) { + if ((name.find("operator<") != 0) && (name.find('<') != std::string::npos)) { + pureName = name.substr(0, name.find('<')); + size_t start = name.find('<'); + size_t end = name.rfind('>'); + explicitParams = name.substr(start + 1, end - start - 1); + } else { + pureName = name; + } + + std::lock_guard Lock(InterOpMutex); + Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods); +} + bool interop::IsStaticTemplate(TCppScope_t scope, const std::string& name) { std::vector candidate_methods; - Cpp::GetClassTemplatedMethods(name, scope, candidate_methods); + std::string pureName; + std::string explicit_params; + ResolveMethodsFromName(scope, name, pureName, explicit_params, + candidate_methods); bool is_static = true; for (auto i : candidate_methods) { if (!Cpp::IsStaticMethod(i)) { @@ -1501,20 +1521,11 @@ interop::TCppMethod_t interop::GetMethodTemplate(TCppScope_t scope, const std::string& proto) { std::string pureName; std::string explicit_params; - - if ((name.find("operator<") != 0) && (name.find('<') != std::string::npos)) { - pureName = name.substr(0, name.find('<')); - size_t start = name.find('<'); - size_t end = name.rfind('>'); - explicit_params = name.substr(start + 1, end - start - 1); - } else { - pureName = name; - } + std::vector unresolved_candidate_methods; + ResolveMethodsFromName(scope, name, pureName, explicit_params, + unresolved_candidate_methods); std::lock_guard Lock(InterOpMutex); - - std::vector unresolved_candidate_methods; - Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods); if (unresolved_candidate_methods.empty() && name.find("operator") == 0) { // try operators interop::GetClassOperators(scope, pureName, unresolved_candidate_methods); @@ -1526,8 +1537,10 @@ interop::TCppMethod_t interop::GetMethodTemplate(TCppScope_t scope, interop::AppendTypesSlow(proto, arg_types, scope); interop::AppendTypesSlow(explicit_params, templ_params, scope); interop::TCppMethod_t cppmeth = nullptr; - cppmeth = Cpp::BestOverloadFunctionMatch(unresolved_candidate_methods, - templ_params, arg_types); + std::vector ambiguous_candidates; + cppmeth = + Cpp::BestOverloadFunctionMatch(unresolved_candidate_methods, templ_params, + arg_types, ambiguous_candidates); // If overload resolution failed but explicit template arguments were // supplied, fall back to direct template-argument substitution: ask Sema @@ -1612,8 +1625,9 @@ interop::TCppMethod_t interop::GetGlobalOperator(TCppScope_t scope, else return nullptr; } - interop::TCppMethod_t cppmeth = - Cpp::BestOverloadFunctionMatch(overloads, {}, arg_types); + std::vector ambiguous_candidates; + interop::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( + overloads, {}, arg_types, ambiguous_candidates); if (cppmeth) return cppmeth; return nullptr; diff --git a/test/test_templates.py b/test/test_templates.py index 93189de3..3e20e9a0 100644 --- a/test/test_templates.py +++ b/test/test_templates.py @@ -439,11 +439,12 @@ def get_tn(ns): return tn # templated class + A = ns.A["int", "double"] a = ns.A["int", "double"]() assert get_tn(ns) == "some_variadic::A" # static functions - a.sa(1, 1.0, "a") + A.sa(1, 1.0, "a") assert ( get_tn(ns).find("some_variadic::A::void(int&&,double&&,std::") == 0 @@ -480,8 +481,6 @@ def get_tn(ns): assert get_tn(ns) == "some_variadic::B" # static functions - b.sb(1, 1.0, "a") - assert get_tn(ns).find("some_variadic::B::void(int&&,double&&,std::") == 0 ns.B.sb(1, 1.0, "a") assert get_tn(ns).find("some_variadic::B::void(int&&,double&&,std::") == 0 ns.B.sb_T["int"](1, 1.0, "a") From b7e3017461c79578de6e41d884ea3fad3c2b4d0b Mon Sep 17 00:00:00 2001 From: Vipul Cariappa Date: Fri, 23 Oct 2026 20:35:59 +0530 Subject: [PATCH 2/2] [drop this commit] CI run --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac5fe0b7..f4a56c9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,8 +10,8 @@ include(GNUInstallDirs) # This option won't make a lot of sense since we only ship the shared library in site-packages # Perhaps this should permanently be OFF and users can build their own CppInterOp if they want to run the tests? option(CPPJIT_ENABLE_CPPINTEROP_TESTS "enable CppInterOp tests" OFF) -set(CPPINTEROP_GIT_REPOSITORY "https://github.com/compiler-research/CppInterOp.git" CACHE STRING "") -set(CPPINTEROP_GIT_TAG "f4adaa6b50542dd564089fe584d407bf15351c97" CACHE STRING "") +set(CPPINTEROP_GIT_REPOSITORY "https://github.com/Vipul-Cariappa/CppInterOp.git" CACHE STRING "") +set(CPPINTEROP_GIT_TAG "dev/best-overload-function-match" CACHE STRING "") set(CPPINTEROP_SOURCE_DIR "" CACHE PATH "Override default CppInterOp built by ExternalProject_Add, with a path to local CppInterOp source")