From 91b4d21488d222d24127c397817c00690bc2bf3b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 10 Feb 2026 17:00:12 +0100 Subject: [PATCH 01/29] use CppInterOp and Dispatch in cppyy-backend --- .../pyroot/cppyy/cppyy-backend/CMakeLists.txt | 9 +- .../clingwrapper/src/callcontext.h | 2 +- .../clingwrapper/src/clingwrapper.cxx | 4132 +++++++++-------- .../clingwrapper/src/cpp_cppyy.h | 284 +- .../clingwrapper/src/cppinterop_dispatch.cxx | 5 + 5 files changed, 2366 insertions(+), 2066 deletions(-) create mode 100644 bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cppinterop_dispatch.cxx diff --git a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt index 8b142b4d30a60..ccdfd3eb62ec3 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt +++ b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt @@ -4,7 +4,14 @@ # For the licensing terms see $ROOTSYS/LICENSE. # For the list of contributors see $ROOTSYS/README/CREDITS. -add_library(cppyy_backend STATIC clingwrapper/src/clingwrapper.cxx) +add_library(cppyy_backend STATIC + clingwrapper/src/clingwrapper.cxx + clingwrapper/src/cppinterop_dispatch.cxx) + +target_include_directories(cppyy_backend PRIVATE + ${CMAKE_SOURCE_DIR}/interpreter/CppInterOp/include +) + target_link_libraries(cppyy_backend Core) if(NOT MSVC) target_compile_options(cppyy_backend PRIVATE -fPIC) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h index edd7ca522c864..5f729410e4c2c 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h @@ -8,7 +8,7 @@ #include "cpp_cppyy.h" //ROOT -#include "Rtypes.h" +// #include "Rtypes.h" namespace CPyCppyy { diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index 896fe55805126..9872cf75fecff 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -6,7 +6,7 @@ #endif // Bindings -#include "precommondefs.h" +#include "capi.h" #include "cpp_cppyy.h" #include "callcontext.h" @@ -38,189 +38,74 @@ #include "TSystem.h" #include "TThread.h" +#ifndef WIN32 +#include +#endif + // Standard -#include +#include #include // for std::count, std::remove -#include #include #include #include +#include #include #include -#include -#include // for getenv -#include +#include +#include // for getenv +#include #include +#include +#include -#if defined(__arm64__) -#include -#include -#define CLING_CATCH_UNCAUGHT_ \ -ARMUncaughtException guard; \ -if (setjmp(gExcJumBuf) == 0) { -#define _CLING_CATCH_UNCAUGHT \ -} else { \ - if (!std::getenv("CPPYY_UNCAUGHT_QUIET")) \ - std::cerr << "Warning: uncaught exception in JIT is rethrown; resources may leak" \ - << " (suppress with \"CPPYY_UNCAUGHT_QUIET=1\")" << std::endl;\ - std::rethrow_exception(std::current_exception()); \ -} -#else -#define CLING_CATCH_UNCAUGHT_ -#define _CLING_CATCH_UNCAUGHT -#endif - -#if 0 -// force std::string and allocator instantation, otherwise Clang 13+ fails to JIT -// symbols that rely on some private helpers (e.g. _M_use_local_data) when used in -// in conjunction with the PCH; hat tip: -// https://github.com/sxs-collaboration/spectre/pull/5222/files#diff-093aadf224e5fee0d33ae1810f2f1c23304fb5ca398ba6b96c4e7918e0811729 -#if defined(__GLIBCXX__) && __GLIBCXX__ >= 20220506 -template class std::allocator; -template class std::basic_string; -template class std::basic_string; -#endif - -using namespace CppyyLegacy; -#endif // temp #include -typedef CPyCppyy::Parameter Parameter; // --temp -#if 0 -#if defined(__arm64__) -namespace { - -// Trap uncaught exceptions and longjump back to the point of JIT wrapper entry -jmp_buf gExcJumBuf; - -void arm_uncaught_exception() { - longjmp(gExcJumBuf, 1); -} - -class ARMUncaughtException { - std::terminate_handler m_Handler; -public: - ARMUncaughtException() { m_Handler = std::set_terminate(arm_uncaught_exception); } - ~ARMUncaughtException() { std::set_terminate(m_Handler); } -}; - -} // unnamed namespace -#endif // __arm64__ -#endif - -// small number that allows use of stack for argument passing -const int SMALL_ARGS_N = 8; - -// convention to pass flag for direct calls (similar to Python's vector calls) -#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) -static inline size_t CALL_NARGS(size_t nargs) { - return nargs & ~DIRECT_CALL; -} - // data for life time management --------------------------------------------- -typedef std::vector ClassRefs_t; -static ClassRefs_t g_classrefs(1); -static const ClassRefs_t::size_type GLOBAL_HANDLE = 1; -static const ClassRefs_t::size_type STD_HANDLE = GLOBAL_HANDLE + 1; - -typedef std::map Name2ClassRefIndex_t; -static Name2ClassRefIndex_t g_name2classrefidx; - -static std::map resolved_enum_types; - -namespace { - -static inline -Cppyy::TCppType_t find_memoized_scope(const std::string& name) -{ - auto icr = g_name2classrefidx.find(name); - if (icr != g_name2classrefidx.end()) - return (Cppyy::TCppType_t)icr->second; - return (Cppyy::TCppType_t)0; -} - -static inline -std::string find_memoized_resolved_name(const std::string& name) -{ -// resolved class types - Cppyy::TCppType_t klass = find_memoized_scope(name); - if (klass) return Cppyy::GetScopedFinalName(klass); - -// resolved enum types - auto res = resolved_enum_types.find(name); - if (res != resolved_enum_types.end()) - return res->second; - -// unknown ... - return ""; -} - -class CallWrapper { -public: - typedef const void* DeclId_t; - -public: - CallWrapper(TFunction* f) : fDecl(f->GetDeclId()), fName(f->GetName()), fTF(new TFunction(*f)) {} - CallWrapper(DeclId_t fid, const std::string& n) : fDecl(fid), fName(n), fTF(nullptr) {} - ~CallWrapper() { - delete fTF; - } - -public: - TInterpreter::CallFuncIFacePtr_t fFaceptr; - DeclId_t fDecl; - std::string fName; - TFunction* fTF; -}; - -} - -static std::vector gWrapperHolder; - -static inline -CallWrapper* new_CallWrapper(TFunction* f) -{ - CallWrapper* wrap = new CallWrapper(f); - gWrapperHolder.push_back(wrap); - return wrap; -} - -static inline -CallWrapper* new_CallWrapper(CallWrapper::DeclId_t fid, const std::string& n) -{ - CallWrapper* wrap = new CallWrapper(fid, n); - gWrapperHolder.push_back(wrap); - return wrap; -} - -typedef std::vector GlobalVars_t; -typedef std::map GlobalVarsIndices_t; - -static GlobalVars_t g_globalvars; -static GlobalVarsIndices_t g_globalidx; - -static std::set gSTLNames; - - -// data ---------------------------------------------------------------------- -Cppyy::TCppScope_t Cppyy::gGlobalScope = GLOBAL_HANDLE; - -// builtin types (including a few common STL templates as long as they live in -// the global namespace b/c of choices upstream) +// typedef std::vector ClassRefs_t; +// static ClassRefs_t g_classrefs(1); +// static const ClassRefs_t::size_type GLOBAL_HANDLE = 1; +// static const ClassRefs_t::size_type STD_HANDLE = GLOBAL_HANDLE + 1; + +// typedef std::map Name2ClassRefIndex_t; +// static Name2ClassRefIndex_t g_name2classrefidx; + +// namespace { + +// static inline +// Cppyy::TCppType_t find_memoized(const std::string& name) +// { +// auto icr = g_name2classrefidx.find(name); +// if (icr != g_name2classrefidx.end()) +// return (Cppyy::TCppType_t)icr->second; +// return (Cppyy::TCppType_t)0; +// } +// +// } // namespace +// +// static inline +// CallWrapper* new_CallWrapper(CppyyLegacy::TFunction* f) +// { +// CallWrapper* wrap = new CallWrapper(f); +// gWrapperHolder.push_back(wrap); +// return wrap; +// } +// + +// typedef std::vector GlobalVars_t; +// typedef std::map GlobalVarsIndices_t; + +// static GlobalVars_t g_globalvars; +// static GlobalVarsIndices_t g_globalidx; + + +// builtin types static std::set g_builtins = {"bool", "char", "signed char", "unsigned char", "wchar_t", "short", "unsigned short", "int", "unsigned int", "long", "unsigned long", "long long", "unsigned long long", - "float", "double", "long double", "void", - "allocator", "array", "basic_string", "complex", "initializer_list", "less", "list", - "map", "pair", "set", "vector"}; - -// smart pointer types -static std::set gSmartPtrTypes = - {"auto_ptr", "std::auto_ptr", "shared_ptr", "std::shared_ptr", - "unique_ptr", "std::unique_ptr", "weak_ptr", "std::weak_ptr"}; + "float", "double", "long double", "void"}; // to filter out ROOT names static std::set gInitialNames; @@ -233,6 +118,8 @@ static bool gEnableFastPath = true; // global initialization ----------------------------------------------------- namespace { +const int kMAXSIGNALS = 16; + // names copied from TUnixSystem #ifdef WIN32 const int SIGBUS = 0; // simple placeholders for ones that don't exist @@ -269,54 +156,106 @@ static struct Signalmap_t { { SIGUSR2, "user-defined signal 2" } }; -static void inline do_trace(int sig) { - std::cerr << " *** Break *** " << (sig < kMAXSIGNALS ? gSignalMap[sig].fSigName : "") << std::endl; - gSystem->StackTrace(); -} - -class TExceptionHandlerImp : public TExceptionHandler { -public: - void HandleException(Int_t sig) override { - if (TROOT::Initialized()) { - if (gException) { - gInterpreter->RewindDictionary(); - gInterpreter->ClearFileBusy(); - } - - if (!std::getenv("CPPYY_CRASH_QUIET")) - do_trace(sig); +// static void inline do_trace(int sig) { +// std::cerr << " *** Break *** " << (sig < kMAXSIGNALS ? gSignalMap[sig].fSigName : "") << std::endl; +// gSystem->StackTrace(); +// } + +// class TExceptionHandlerImp : public TExceptionHandler { +// public: +// virtual void HandleException(Int_t sig) { +// if (TROOT::Initialized()) { +// if (gException) { +// gInterpreter->RewindDictionary(); +// gInterpreter->ClearFileBusy(); +// } +// +// if (!getenv("CPPYY_CRASH_QUIET")) +// do_trace(sig); +// +// // jump back, if catch point set +// Throw(sig); +// } +// +// do_trace(sig); +// gSystem->Exit(128 + sig); +// } +// }; - // jump back, if catch point set - Throw(sig); - } +static inline +void push_tokens_from_string(char *s, std::vector &tokens) { + char *token = strtok(s, " "); - do_trace(sig); - gSystem->Exit(128 + sig); + while (token) { + tokens.push_back(token); + token = strtok(NULL, " "); } -}; +} + +static inline +bool is_integral(std::string& s) +{ + if (s == "false") { s = "0"; return true; } + else if (s == "true") { s = "1"; return true; } + return !s.empty() && std::find_if(s.begin(), + s.end(), [](unsigned char c) { return !std::isdigit(c); }) == s.end(); +} class ApplicationStarter { + Cpp::TInterp_t Interp; public: ApplicationStarter() { - // initialize ROOT early to guarantee proper order of shutdown later on (gROOT is a - // macro that resolves to the ::CppyyLegacy::GetROOT() function call) + + (void)gROOT; + char *libcling = gSystem->DynamicPathName("libCling"); + + if (!libcling) { + std::cerr << "[cppyy-backend] Failed to find libCling" << std::endl; + return; + } + if (!Cpp::LoadDispatchAPI(libcling)) { + std::cerr << "[cppyy-backend] Failed to load CppInterOp" << std::endl; + return; + } - // setup dummy holders for global and std namespaces - assert(g_classrefs.size() == GLOBAL_HANDLE); - g_name2classrefidx[""] = GLOBAL_HANDLE; - g_classrefs.push_back(TClassRef("")); + // Check if somebody already loaded CppInterOp and created an + // interpreter for us. + if (auto * existingInterp = Cpp::GetInterpreter()) { + Interp = existingInterp; + } + else { +#ifdef __arm64__ +#ifdef __APPLE__ + // If on apple silicon don't use -march=native + std::vector InterpArgs({"-std=c++17"}); +#else + std::vector InterpArgs( + {"-std=c++17", "-march=native"}); +#endif +#else + std::vector InterpArgs({"-std=c++17", "-march=native"}); +#endif + char *InterpArgString = getenv("CPPINTEROP_EXTRA_INTERPRETER_ARGS"); - // aliases for std (setup already in pythonify) - g_name2classrefidx["std"] = STD_HANDLE; - g_name2classrefidx["::std"] = g_name2classrefidx["std"]; - g_classrefs.push_back(TClassRef("std")); + if (InterpArgString) + push_tokens_from_string(InterpArgString, InterpArgs); - // add a dummy global to refer to as null at index 0 - g_globalvars.push_back(nullptr); - g_globalidx[nullptr] = 0; +#ifdef __arm64__ +#ifdef __APPLE__ + // If on apple silicon don't use -march=native + Interp = Cpp::CreateInterpreter({"-std=c++17"}, /*GpuArgs=*/{}); +#else + Interp = Cpp::CreateInterpreter({"-std=c++17", "-march=native"}, + /*GpuArgs=*/{}); +#endif +#else + Interp = Cpp::CreateInterpreter({"-std=c++17", "-march=native"}, + /*GpuArgs=*/{}); +#endif + } - // fill out the builtins + // fill out the builtins std::set bi{g_builtins}; for (const auto& name : bi) { for (const char* a : {"*", "&", "*&", "[]", "*[]"}) @@ -324,132 +263,128 @@ class ApplicationStarter { } // disable fast path if requested - if (std::getenv("CPPYY_DISABLE_FASTPATH")) gEnableFastPath = false; - - // fill the set of STL names - const char* stl_names[] = {"allocator", "auto_ptr", "bad_alloc", "bad_cast", - "bad_exception", "bad_typeid", "basic_filebuf", "basic_fstream", "basic_ifstream", - "basic_ios", "basic_iostream", "basic_istream", "basic_istringstream", - "basic_ofstream", "basic_ostream", "basic_ostringstream", "basic_streambuf", - "basic_string", "basic_stringbuf", "basic_stringstream", "binary_function", - "binary_negate", "bitset", "byte", "char_traits", "codecvt_byname", "codecvt", "collate", - "collate_byname", "compare", "complex", "ctype_byname", "ctype", "default_delete", - "deque", "divides", "domain_error", "equal_to", "exception", "forward_list", "fpos", - "function", "greater_equal", "greater", "gslice_array", "gslice", "hash", "indirect_array", - "integer_sequence", "invalid_argument", "ios_base", "istream_iterator", "istreambuf_iterator", - "istrstream", "iterator_traits", "iterator", "length_error", "less_equal", "less", - "list", "locale", "localedef utility", "locale utility", "logic_error", "logical_and", - "logical_not", "logical_or", "map", "mask_array", "mem_fun", "mem_fun_ref", "messages", - "messages_byname", "minus", "modulus", "money_get", "money_put", "moneypunct", - "moneypunct_byname", "multimap", "multiplies", "multiset", "negate", "not_equal_to", - "num_get", "num_put", "numeric_limits", "numpunct", "numpunct_byname", - "ostream_iterator", "ostreambuf_iterator", "ostrstream", "out_of_range", - "overflow_error", "pair", "plus", "pointer_to_binary_function", - "pointer_to_unary_function", "priority_queue", "queue", "range_error", - "raw_storage_iterator", "reverse_iterator", "runtime_error", "set", "shared_ptr", - "slice_array", "slice", "stack", "string", "strstream", "strstreambuf", - "time_get_byname", "time_get", "time_put_byname", "time_put", "unary_function", - "unary_negate", "unique_ptr", "underflow_error", "unordered_map", "unordered_multimap", - "unordered_multiset", "unordered_set", "valarray", "vector", "weak_ptr", "wstring", - "__hash_not_enabled"}; - for (auto& name : stl_names) - gSTLNames.insert(name); + if (getenv("CPPYY_DISABLE_FASTPATH")) gEnableFastPath = false; // set opt level (default to 2 if not given; Cling itself defaults to 0) int optLevel = 2; - if (std::getenv("CPPYY_OPT_LEVEL")) optLevel = atoi(std::getenv("CPPYY_OPT_LEVEL")); + + if (getenv("CPPYY_OPT_LEVEL")) optLevel = atoi(getenv("CPPYY_OPT_LEVEL")); + if (optLevel != 0) { std::ostringstream s; s << "#pragma cling optimize " << optLevel; - gInterpreter->ProcessLine(s.str().c_str()); + Cpp::Process(s.str().c_str()); } - // load frequently used headers + // This would give us something like: + // /home/vvassilev/workspace/builds/scratch/cling-build/builddir/lib/clang/13.0.0 + const char * ResourceDir = Cpp::GetResourceDir(); + std::string ClingSrc = std::string(ResourceDir) + "/../../../../cling-src"; + std::string ClingBuildDir = std::string(ResourceDir) + "/../../../"; + Cpp::AddIncludePath((ClingSrc + "/tools/cling/include").c_str()); + Cpp::AddIncludePath((ClingSrc + "/include").c_str()); + Cpp::AddIncludePath((ClingBuildDir + "/include").c_str()); + Cpp::AddIncludePath((std::string(CPPINTEROP_DIR) + "/include").c_str()); + Cpp::LoadLibrary("libstdc++", /* lookup= */ true); + + // load frequently used headers const char* code = - "#include \n" - "#include \n" - "#include \n" // defines R__EXTERN - "#include \n" - "#include "; - gInterpreter->ProcessLine(code); + "#include \n" + "#include \n" + "#include \n" + "#include \n" + "#include \n" // for strcpy + "#include \n" + // "#include \n" // defines R__EXTERN + "#include \n" + "#include \n" + "#include \n" + "#include \n" // for the dispatcher code to use + // std::function + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#include \n" // FIXME: Replace with modules + "#if __has_include()\n" + "#include \n" + "#endif\n" + "#include \n"; + Cpp::Process(code); // create helpers for comparing thingies - gInterpreter->Declare( - "namespace __cppyy_internal { template" - " bool is_equal(const C1& c1, const C2& c2) { return (bool)(c1 == c2); } }"); - gInterpreter->Declare( - "namespace __cppyy_internal { template" - " bool is_not_equal(const C1& c1, const C2& c2) { return (bool)(c1 != c2); } }"); + Cpp::Declare("namespace __cppyy_internal { template" + " bool is_equal(const C1& c1, const C2& c2) { return " + "(bool)(c1 == c2); } }", + /*silent=*/false); + Cpp::Declare("namespace __cppyy_internal { template" + " bool is_not_equal(const C1& c1, const C2& c2) { return " + "(bool)(c1 != c2); } }", + /*silent=*/false); + + // Define gCling when we run with clang-repl. + // FIXME: We should get rid of all the uses of gCling as this seems to + // break encapsulation. + std::stringstream InterpPtrSS; + InterpPtrSS << "#ifndef __CLING__\n" + << "namespace cling { namespace runtime {\n" + << "void* gCling=(void*)" << static_cast(Interp) + << ";\n }}\n" + << "#endif \n"; + Cpp::Process(InterpPtrSS.str().c_str()); // helper for multiple inheritance - gInterpreter->Declare("namespace __cppyy_internal { struct Sep; }"); - - // retrieve all initial (ROOT) C++ names in the global scope to allow filtering later - gROOT->GetListOfGlobals(true); // force initialize - gROOT->GetListOfGlobalFunctions(true); // id. - std::set initial; - Cppyy::GetAllCppNames(GLOBAL_HANDLE, initial); - gInitialNames = initial; + Cpp::Declare("namespace __cppyy_internal { struct Sep; }", + /*silent=*/false); -#ifndef WIN32 - gRootSOs.insert("libCore.so "); - gRootSOs.insert("libRIO.so "); - gRootSOs.insert("libThread.so "); - gRootSOs.insert("libMathCore.so "); -#else - gRootSOs.insert("libCore.dll "); - gRootSOs.insert("libRIO.dll "); - gRootSOs.insert("libThread.dll "); - gRootSOs.insert("libMathCore.dll "); -#endif + // std::string libInterOp = I->getDynamicLibraryManager()->lookupLibrary("libcling"); + // void *interopDL = dlopen(libInterOp.c_str(), RTLD_LAZY); + // if (!interopDL) { + // std::cerr << "libInterop could not be opened!\n"; + // exit(1); + // } // start off with a reasonable size placeholder for wrappers - gWrapperHolder.reserve(1024); + // gWrapperHolder.reserve(1024); // create an exception handler to process signals - gExceptionHandler = new TExceptionHandlerImp{}; + // gExceptionHandler = new TExceptionHandlerImp{}; } ~ApplicationStarter() { - for (auto wrap : gWrapperHolder) - delete wrap; - delete gExceptionHandler; gExceptionHandler = nullptr; + //Cpp::DeleteInterpreter(Interp); + // for (auto wrap : gWrapperHolder) + // delete wrap; + // delete gExceptionHandler; gExceptionHandler = nullptr; } } _applicationStarter; } // unnamed namespace -// local helpers ------------------------------------------------------------- -static inline -TClassRef& type_from_handle(Cppyy::TCppScope_t scope) -{ - assert((ClassRefs_t::size_type)scope < g_classrefs.size()); - return g_classrefs[(ClassRefs_t::size_type)scope]; -} - -static inline -TFunction* m2f(Cppyy::TCppMethod_t method) { - CallWrapper* wrap = ((CallWrapper*)method); - if (!wrap->fTF) { - MethodInfo_t* mi = gInterpreter->MethodInfo_Factory(wrap->fDecl); - wrap->fTF = new TFunction(mi); - } - return wrap->fTF; -} - -/* -static inline -CallWrapper::DeclId_t m2d(Cppyy::TCppMethod_t method) { - CallWrapper* wrap = ((CallWrapper*)method); - if (!wrap->fTF || wrap->fTF->GetDeclId() != wrap->fDecl) { - MethodInfo_t* mi = gInterpreter->MethodInfo_Factory(wrap->fDecl); - wrap->fTF = new TFunction(mi); - } - return wrap->fDecl; -} -*/ - +// // local helpers ------------------------------------------------------------- +// static inline +// TClassRef& type_from_handle(Cppyy::TCppScope_t scope) +// { +// assert((ClassRefs_t::size_type)scope < g_classrefs.size()); +// return g_classrefs[(ClassRefs_t::size_type)scope]; +// } +// +// static inline +// TFunction* m2f(Cppyy::TCppMethod_t method) { +// CallWrapper *wrap = (CallWrapper *)method; +// +// if (!wrap->fTF) { +// MethodInfo_t* mi = gInterpreter->MethodInfo_Factory(wrap->fDecl); +// wrap->fTF = new TFunction(mi); +// } +// return (TFunction *) wrap->fTF; +// } +// static inline char* cppstring_to_cstring(const std::string& cppstr) { @@ -457,505 +392,598 @@ char* cppstring_to_cstring(const std::string& cppstr) memcpy(cstr, cppstr.c_str(), cppstr.size()+1); return cstr; } +// +// static inline +// bool match_name(const std::string& tname, const std::string fname) +// { +// // either match exactly, or match the name as template +// if (fname.rfind(tname, 0) == 0) { +// if ((tname.size() == fname.size()) || +// (tname.size() < fname.size() && fname[tname.size()] == '<')) +// return true; +// } +// return false; +// } +// +// +// // direct interpreter access ------------------------------------------------- +// Returns false on failure and true on success +bool Cppyy::Compile(const std::string& code, bool silent) +{ + // Declare returns an enum which equals 0 on success + return !Cpp::Declare(code.c_str(), silent); +} -static inline -bool match_name(const std::string& tname, const std::string fname) +std::string Cppyy::ToString(TCppType_t klass, TCppObject_t obj) { -// either match exactly, or match the name as template - if (fname.rfind(tname, 0) == 0) { - if ((tname.size() == fname.size()) || - (tname.size() < fname.size() && fname[tname.size()] == '<')) - return true; - } - return false; + if (klass && obj && !Cpp::IsNamespace((TCppScope_t)klass)) + return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(), + (void*)obj); + return ""; } -static inline -bool is_missclassified_stl(const std::string& name) -{ - std::string::size_type pos = name.find('<'); - if (pos != std::string::npos) - return gSTLNames.find(name.substr(0, pos)) != gSTLNames.end(); - return gSTLNames.find(name) != gSTLNames.end(); +// // name to opaque C++ scope representation ----------------------------------- +std::string Cppyy::ResolveName(const std::string& name) { + if (!name.empty()) { + if (Cppyy::TCppType_t type = + Cppyy::GetType(name, /*enable_slow_lookup=*/true)) + return Cppyy::GetTypeAsString(Cppyy::ResolveType(type)); + return name; + } + return ""; +} +// // Fully resolve the given name to the final type name. +// +// // try memoized type cache, in case seen before +// TCppType_t klass = find_memoized(cppitem_name); +// if (klass) return GetScopedFinalName(klass); +// +// // remove global scope '::' if present +// std::string tclean = cppitem_name.compare(0, 2, "::") == 0 ? +// cppitem_name.substr(2, std::string::npos) : cppitem_name; +// +// // classes (most common) +// tclean = TClassEdit::CleanType(tclean.c_str()); +// if (tclean.empty() [> unknown, eg. an operator <]) return cppitem_name; +// +// // reduce [N] to [] +// if (tclean[tclean.size()-1] == ']') +// tclean = tclean.substr(0, tclean.rfind('[')) + "[]"; +// +// // remove __restrict and __restrict__ +// auto pos = tclean.rfind("__restrict"); +// if (pos != std::string::npos) +// tclean = tclean.substr(0, pos); +// +// if (tclean.compare(0, 9, "std::byte") == 0) +// return tclean; +// +// // check data types list (accept only builtins as typedefs will +// // otherwise not be resolved) +// if (IsBuiltin(tclean)) return tclean; +// +// // special case for enums +// if (IsEnum(cppitem_name)) +// return ResolveEnum(cppitem_name); +// +// // special case for clang's builtin __type_pack_element (which does not resolve) +// pos = cppitem_name.size() > 20 ? \ +// cppitem_name.rfind("__type_pack_element", 5) : std::string::npos; +// if (pos != std::string::npos) { +// // shape is "[std::]__type_pack_elementcpd": extract +// // first the index, and from there the indexed type; finally, restore the +// // qualifiers +// const char* str = cppitem_name.c_str(); +// char* endptr = nullptr; +// unsigned long index = strtoul(str+20+pos, &endptr, 0); +// +// std::string tmplvars{endptr}; +// auto start = tmplvars.find(',') + 1; +// auto end = tmplvars.find(',', start); +// while (index != 0) { +// start = end+1; +// end = tmplvars.find(',', start); +// if (end == std::string::npos) end = tmplvars.rfind('>'); +// --index; +// } +// +// std::string resolved = tmplvars.substr(start, end-start); +// auto cpd = tmplvars.rfind('>'); +// if (cpd != std::string::npos && cpd+1 != tmplvars.size()) +// return resolved + tmplvars.substr(cpd+1, std::string::npos); +// return resolved; +// } +// +// // typedefs etc. (and a couple of hacks around TClassEdit-isms, fixing of which +// // in ResolveTypedef itself is a TODO ...) +// tclean = TClassEdit::ResolveTypedef(tclean.c_str(), true); +// pos = 0; +// while ((pos = tclean.find("::::", pos)) != std::string::npos) { +// tclean.replace(pos, 4, "::"); +// pos += 2; +// } +// +// if (tclean.compare(0, 6, "const ") != 0) +// return TClassEdit::ShortType(tclean.c_str(), 2); +// return "const " + TClassEdit::ShortType(tclean.c_str(), 2); +// } + +Cppyy::TCppType_t Cppyy::ResolveEnumReferenceType(TCppType_t type) { + if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue) + return type; + + TCppType_t nonReferenceType = Cpp::GetNonReferenceType(type); + if (Cpp::IsEnumType(nonReferenceType)) { + TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(nonReferenceType); + return Cpp::GetReferencedType(underlying_type, /*rvalue=*/false); + } + return type; +} + +Cppyy::TCppType_t Cppyy::ResolveEnumPointerType(TCppType_t type) { + if (!Cpp::IsPointerType(type)) + return type; + + TCppType_t PointeeType = Cpp::GetPointeeType(type); + if (Cpp::IsEnumType(PointeeType)) { + TCppType_t underlying_type = Cpp::GetIntegerTypeFromEnumType(PointeeType); + return Cpp::GetPointerType(underlying_type); + } + return type; +} + +Cppyy::TCppType_t int_like_type(Cppyy::TCppType_t type) { + Cppyy::TCppType_t check_int_typedefs = type; + if (Cpp::IsPointerType(check_int_typedefs)) + check_int_typedefs = Cpp::GetPointeeType(check_int_typedefs); + if (Cpp::IsReferenceType(check_int_typedefs)) + check_int_typedefs = + Cpp::GetReferencedType(check_int_typedefs, /*rvalue=*/false); + + if (Cpp::GetTypeAsString(check_int_typedefs) == "int8_t" || Cpp::GetTypeAsString(check_int_typedefs) == "uint8_t") + return check_int_typedefs; + return nullptr; } +Cppyy::TCppType_t Cppyy::ResolveType(TCppType_t type) { + if (!type) return type; -// direct interpreter access ------------------------------------------------- -bool Cppyy::Compile(const std::string& code, bool /*silent*/) -{ - return gInterpreter->Declare(code.c_str()); -} + TCppType_t check_int_typedefs = int_like_type(type); + if (check_int_typedefs) + return type; -std::string Cppyy::ToString(TCppType_t klass, TCppObject_t obj) -{ - if (klass && obj && !IsNamespace((TCppScope_t)klass)) - return gInterpreter->ToString(GetScopedFinalName(klass).c_str(), (void*)obj); - return ""; -} + Cppyy::TCppType_t canonType = Cpp::GetCanonicalType(type); + + if (Cpp::IsEnumType(canonType)) { + if (Cppyy::GetTypeAsString(type) != "std::byte") + return Cpp::GetIntegerTypeFromEnumType(canonType); + } + if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) { + return Cpp::RemoveTypeQualifier(canonType, Cpp::QualKind::Restrict); + } + return canonType; +} -// name to opaque C++ scope representation ----------------------------------- -std::string Cppyy::ResolveName(const std::string& cppitem_name) -{ -// Fully resolve the given name to the final type name. +Cppyy::TCppType_t Cppyy::GetRealType(TCppType_t type) { + TCppType_t check_int_typedefs = int_like_type(type); + if (check_int_typedefs) + return check_int_typedefs; + return Cpp::GetUnderlyingType(type); +} -// try memoized type cache, in case seen before - std::string memoized = find_memoized_resolved_name(cppitem_name); - if (!memoized.empty()) return memoized; +Cppyy::TCppType_t Cppyy::GetPointerType(TCppType_t type) { + return Cpp::GetPointerType(type); +} -// remove global scope '::' if present - std::string tclean = cppitem_name.compare(0, 2, "::") == 0 ? - cppitem_name.substr(2, std::string::npos) : cppitem_name; +Cppyy::TCppType_t Cppyy::GetReferencedType(TCppType_t type, bool rvalue) { + return Cpp::GetReferencedType(type, rvalue); +} -// classes (most common) - tclean = TClassEdit::CleanType(tclean.c_str()); - if (tclean.empty() /* unknown, eg. an operator */) return cppitem_name; +bool Cppyy::IsRValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::RValue; +} -// reduce [N] to [] - if (tclean[tclean.size()-1] == ']') - tclean = tclean.substr(0, tclean.rfind('[')) + "[]"; +bool Cppyy::IsLValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::LValue; +} - if (tclean.rfind("byte", 0) == 0 || tclean.rfind("std::byte", 0) == 0) - return tclean; +bool Cppyy::IsClassType(TCppType_t type) { + return Cpp::IsRecordType(type); +} -// remove __restrict and __restrict__ - auto pos = tclean.rfind("__restrict"); - if (pos != std::string::npos) - tclean = tclean.substr(0, pos); +bool Cppyy::IsPointerType(TCppType_t type) { + return Cpp::IsPointerType(type); +} - if (tclean.compare(0, 9, "std::byte") == 0) - return tclean; +bool Cppyy::IsFunctionPointerType(TCppType_t type) { + return Cpp::IsFunctionPointerType(type); +} -// check data types list (accept only builtins as typedefs will -// otherwise not be resolved) - if (IsBuiltin(tclean)) return tclean; +std::string trim(const std::string& line) +{ + if (line.empty()) return ""; + const char* WhiteSpace = " \t\v\r\n"; + std::size_t start = line.find_first_not_of(WhiteSpace); + std::size_t end = line.find_last_not_of(WhiteSpace); + return line.substr(start, end - start + 1); +} -// special case for enums - if (IsEnum(cppitem_name)) - return ResolveEnum(cppitem_name); +// returns false of angular brackets dont match, else true +bool split_comma_saparated_types(const std::string& name, + std::vector& types) { + std::string trimed_name = trim(name); + size_t start_pos = 0; + size_t end_pos = 0; + size_t appended_count = 0; + int matching_angular_brackets = 0; + while (end_pos < trimed_name.size()) { + switch (trimed_name[end_pos]) { + case ',': { + if (!matching_angular_brackets) { + types.push_back( + trim(trimed_name.substr(start_pos, end_pos - start_pos))); + start_pos = end_pos + 1; + } + break; + } + case '<': { + matching_angular_brackets++; + break; + } + case '>': { + if (matching_angular_brackets > 0) { + types.push_back( + trim(trimed_name.substr(start_pos, end_pos - start_pos + 1))); + start_pos = end_pos + 1; + } else if (matching_angular_brackets < 1) { + types.clear(); + return false; + } + start_pos++; + end_pos++; + matching_angular_brackets--; + break; + } + } + end_pos++; + } + if (start_pos < trimed_name.size()) + types.push_back(trim(trimed_name.substr(start_pos, end_pos - start_pos))); + return true; +} + +Cpp::TCppScope_t GetEnumFromCompleteName(const std::string &name) { + std::string delim = "::"; + size_t start = 0; + size_t end = name.find(delim); + Cpp::TCppScope_t curr_scope = 0; + while (end != std::string::npos) { + curr_scope = Cpp::GetNamed(name.substr(start, end - start), curr_scope); + start = end + delim.length(); + end = name.find(delim, start); + } + return Cpp::GetNamed(name.substr(start, end), curr_scope); +} + +// returns true if no new type was added. +bool Cppyy::AppendTypesSlow(const std::string& name, + std::vector& types, Cppyy::TCppScope_t parent) { + + // Add no new type if string is empty + if (name.empty()) + return true; -// special case for clang's builtin __type_pack_element (which does not resolve) - pos = cppitem_name.size() > 20 ? \ - cppitem_name.rfind("__type_pack_element", 5) : std::string::npos; - if (pos != std::string::npos) { - // shape is "[std::]__type_pack_elementcpd": extract - // first the index, and from there the indexed type; finally, restore the - // qualifiers - const char* str = cppitem_name.c_str(); - char* endptr = nullptr; - unsigned long index = strtoul(str+20+pos, &endptr, 0); + auto replace_all = [](std::string& str, const std::string& from, const std::string& to) { + if(from.empty()) + return; + size_t start_pos = 0; + while((start_pos = str.find(from, start_pos)) != std::string::npos) { + str.replace(start_pos, from.length(), to); + start_pos += to.length(); + } + }; + + std::string resolved_name = name; + replace_all(resolved_name, "std::initializer_list<", "std::vector<"); // replace initializer_list with vector + + // We might have an entire expression such as int, double. + static unsigned long long struct_count = 0; + std::string code = "template struct __Cppyy_AppendTypesSlow {};\n"; + if (!struct_count) + Cpp::Declare(code.c_str(), /*silent=*/false); // initialize the trampoline + + std::string var = "__Cppyy_s" + std::to_string(struct_count++); + // FIXME: We cannot use silent because it erases our error code from Declare! + if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + resolved_name + "> " + var +";\n").c_str(), /*silent=*/false)) { + TCppType_t varN = + Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr)); + TCppScope_t instance_class = Cpp::GetScopeFromType(varN); + size_t oldSize = types.size(); + Cpp::GetClassTemplateInstantiationArgs(instance_class, types); + return oldSize == types.size(); + } + + // We split each individual types based on , and resolve it + // FIXME: see discussion on should we support template instantiation with string: + // https://github.com/compiler-research/cppyy-backend/pull/137#discussion_r2079357491 + // We should consider eliminating the `split_comma_saparated_types` and `is_integral` + // string parsing. + std::vector individual_types; + if (!split_comma_saparated_types(resolved_name, individual_types)) + return true; - std::string tmplvars{endptr}; - auto start = tmplvars.find(',') + 1; - auto end = tmplvars.find(',', start); - while (index != 0) { - start = end+1; - end = tmplvars.find(',', start); - if (end == std::string::npos) end = tmplvars.rfind('>'); - --index; - } + for (std::string& i : individual_types) { + // Try going via Cppyy::GetType first. + const char* integral_value = nullptr; + Cppyy::TCppType_t type = nullptr; - std::string resolved = tmplvars.substr(start, end-start); - auto cpd = tmplvars.rfind('>'); - if (cpd != std::string::npos && cpd+1 != tmplvars.size()) - return resolved + tmplvars.substr(cpd+1, std::string::npos); - return resolved; + type = GetType(i, /*enable_slow_lookup=*/true); + if (!type && parent && (Cpp::IsNamespace(parent) || Cpp::IsClass(parent))) { + type = Cppyy::GetTypeFromScope(Cppyy::GetNamed(resolved_name, parent)); } -// typedefs etc. (and a couple of hacks around TClassEdit-isms, fixing of which -// in ResolveTypedef itself is a TODO ...) - tclean = TClassEdit::ResolveTypedef(tclean.c_str(), true); - pos = 0; - while ((pos = tclean.find("::::", pos)) != std::string::npos) { - tclean.replace(pos, 4, "::"); - pos += 2; + if (!type) { + types.clear(); + return true; } - if (tclean.compare(0, 6, "const ") != 0) - return TClassEdit::ShortType(tclean.c_str(), 2); - return "const " + TClassEdit::ShortType(tclean.c_str(), 2); + if (is_integral(i)) + integral_value = strdup(i.c_str()); + if (Cpp::TCppScope_t scope = GetEnumFromCompleteName(i)) + if (Cpp::IsEnumConstant(scope)) + integral_value = + strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str()); + types.emplace_back(type, integral_value); + } + return false; } -#if 0 -//---------------------------------------------------------------------------- -static std::string extract_namespace(const std::string& name) -{ -// Find the namespace the named class lives in, take care of templates -// Note: this code also lives in CPyCppyy (TODO: refactor?) - if (name.empty()) - return name; - - int tpl_open = 0; - for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { - std::string::value_type c = name[pos]; +Cppyy::TCppType_t Cppyy::GetType(const std::string &name, bool enable_slow_lookup /* = false */) { + static unsigned long long var_count = 0; - // count '<' and '>' to be able to skip template contents - if (c == '>') - ++tpl_open; - else if (c == '<') - --tpl_open; + if (auto type = Cpp::GetType(name)) + return type; - // collect name up to "::" - else if (tpl_open == 0 && c == ':' && name[pos-1] == ':') { - // found the extend of the scope ... done - return name.substr(0, pos-1); - } + if (!enable_slow_lookup) { + if (name.find("::") != std::string::npos) + throw std::runtime_error("Calling Cppyy::GetType with qualified name '" + + name + "'\n"); + return nullptr; } -// no namespace; assume outer scope - return ""; -} -#endif + // Here we might need to deal with integral types such as 3.14. -std::string Cppyy::ResolveEnum(const std::string& enum_type) -{ -// The underlying type of a an enum may be any kind of integer. -// Resolve that type via a workaround (note: this function assumes -// that the enum_type name is a valid enum type name) - auto res = resolved_enum_types.find(enum_type); - if (res != resolved_enum_types.end()) - return res->second; - -// desugar the type before resolving - std::string et_short = TClassEdit::ShortType(enum_type.c_str(), 1); - if (et_short.find("(unnamed") == std::string::npos) { - std::ostringstream decl; - // TODO: now presumed fixed with https://sft.its.cern.ch/jira/browse/ROOT-6988 - for (auto& itype : {"unsigned int"}) { - decl << "std::is_same<" - << itype - << ", std::underlying_type<" - << et_short - << ">::type>::value;"; - if (gInterpreter->ProcessLine(decl.str().c_str())) { - // TODO: "re-sugaring" like this is brittle, but the top - // should be re-translated into AST-based code anyway - std::string resugared; - if (et_short.size() != enum_type.size()) { - auto pos = enum_type.find(et_short); - if (pos != std::string::npos) { - resugared = enum_type.substr(0, pos) + itype; - if (pos+et_short.size() < enum_type.size()) - resugared += enum_type.substr(pos+et_short.size(), std::string::npos); - } - } - if (resugared.empty()) resugared = itype; - resolved_enum_types[enum_type] = resugared; - return resugared; - } - } - } + std::string id = "__Cppyy_GetType_" + std::to_string(var_count++); + std::string using_clause = "using " + id + " = __typeof__(" + name + ");\n"; -// failed or anonymous ... signal upstream to special case this - int ipos = (int)enum_type.size()-1; - for (; 0 <= ipos; --ipos) { - char c = enum_type[ipos]; - if (isspace(c)) continue; - if (isalnum(c) || c == '_' || c == '>' || c == ')') break; + if (!Cpp::Declare(using_clause.c_str(), /*silent=*/false)) { + TCppScope_t lookup = Cpp::GetNamed(id, 0); + TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup); + return Cpp::GetCanonicalType(lookup_ty); } - bool isConst = enum_type.find("const ", 6) != std::string::npos; - std::string restype = isConst ? "const " : ""; - restype += "internal_enum_type_t"+enum_type.substr((std::string::size_type)ipos+1, std::string::npos); - resolved_enum_types[enum_type] = restype; - return restype; // should default to some int variant -} - -#if 0 -static Cppyy::TCppIndex_t ArgSimilarityScore(void *argqtp, void *reqqtp) -{ -// This scoring is not based on any particular rules - if (gInterpreter->IsSameType(argqtp, reqqtp)) - return 0; // Best match - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsSignedIntegerType(reqqtp)) || - (gInterpreter->IsUnsignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsFloatingType(reqqtp))) - return 1; - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp))) - return 2; - else if ((gInterpreter->IsIntegerType(argqtp) && gInterpreter->IsIntegerType(reqqtp))) - return 3; - else if ((gInterpreter->IsIntegralType(argqtp) && gInterpreter->IsIntegralType(reqqtp))) - return 4; - else if ((gInterpreter->IsVoidPointerType(argqtp) && gInterpreter->IsPointerType(reqqtp))) - return 5; - else - return 10; // Penalize heavily for no possible match + return nullptr; } -#endif -Cppyy::TCppScope_t Cppyy::GetScope(const std::string& sname) -{ -// First, try cache - TCppType_t result = find_memoized_scope(sname); - if (result) return result; - -// Second, skip builtins before going through the more expensive steps of resolving -// typedefs and looking up TClass - if (g_builtins.find(sname) != g_builtins.end()) - return (TCppScope_t)0; - -// TODO: scope_name should always be final already? -// Resolve name fully before lookup to make sure all aliases point to the same scope - std::string scope_name = ResolveName(sname); - bool bHasAlias1 = sname != scope_name; - if (bHasAlias1) { - result = find_memoized_scope(scope_name); - if (result) { - g_name2classrefidx[sname] = result; - return result; - } - } -// both failed, but may be STL name that's missing 'std::' now, but didn't before - bool b_scope_name_missclassified = is_missclassified_stl(scope_name); - if (b_scope_name_missclassified) { - result = find_memoized_scope("std::"+scope_name); - if (result) g_name2classrefidx["std::"+scope_name] = (ClassRefs_t::size_type)result; - } - bool b_sname_missclassified = bHasAlias1 ? is_missclassified_stl(sname) : false; - if (b_sname_missclassified) { - if (!result) result = find_memoized_scope("std::"+sname); - if (result) g_name2classrefidx["std::"+sname] = (ClassRefs_t::size_type)result; +Cppyy::TCppType_t Cppyy::GetComplexType(const std::string &name) { + return Cpp::GetComplexType(Cpp::GetType(name)); +} + + +// //---------------------------------------------------------------------------- +// static std::string extract_namespace(const std::string& name) +// { +// // Find the namespace the named class lives in, take care of templates +// // Note: this code also lives in CPyCppyy (TODO: refactor?) +// if (name.empty()) +// return name; +// +// int tpl_open = 0; +// for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { +// std::string::value_type c = name[pos]; +// +// // count '<' and '>' to be able to skip template contents +// if (c == '>') +// ++tpl_open; +// else if (c == '<') +// --tpl_open; +// +// // collect name up to "::" +// else if (tpl_open == 0 && c == ':' && name[pos-1] == ':') { +// // found the extend of the scope ... done +// return name.substr(0, pos-1); +// } +// } +// +// // no namespace; assume outer scope +// return ""; +// } +// + +std::string Cppyy::ResolveEnum(TCppScope_t handle) +{ + std::string type = Cpp::GetTypeAsString( + Cpp::GetIntegerTypeFromEnumScope(handle)); + if (type == "signed char") + return "char"; + return type; +} + +Cppyy::TCppScope_t Cppyy::GetUnderlyingScope(TCppScope_t scope) +{ + return Cpp::GetUnderlyingScope(scope); +} + +Cppyy::TCppScope_t Cppyy::GetScope(const std::string& name, + TCppScope_t parent_scope) +{ + if (Cppyy::TCppScope_t scope = Cpp::GetScope(name, parent_scope)) + return scope; + if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) + if (Cppyy::TCppScope_t scope = Cpp::GetScopeFromCompleteName(name)) + return scope; + + // FIXME: avoid string parsing here + if (name.find('<') != std::string::npos) { + // Templated Type; May need instantiation + size_t start = name.find('<'); + size_t end = name.rfind('>'); + std::string params = name.substr(start + 1, end - start - 1); + + std::string pure_name = name.substr(0, start); + Cppyy::TCppScope_t scope = Cpp::GetScope(pure_name, parent_scope); + if (!scope && (!parent_scope || parent_scope == Cpp::GetGlobalScope())) + scope = Cpp::GetScopeFromCompleteName(pure_name); + + if (Cppyy::IsTemplate(scope)) { + std::vector templ_params; + if (!Cppyy::AppendTypesSlow(params, templ_params)) + return Cpp::InstantiateTemplate(scope, templ_params.data(), + templ_params.size(), + /*instantiate_body=*/false); + } } + return nullptr; +} - if (result) return result; - -// use TClass directly, to enable auto-loading; class may be stubbed (eg. for -// function returns) or forward declared, leading to a non-null TClass that is -// otherwise invalid/unusable - TClassRef cr(TClass::GetClass(scope_name.c_str(), true /* load */, true /* silent */)); - if (!cr.GetClass()) - return (TCppScope_t)0; - -// memoize found/created TClass - bool bHasAlias2 = cr->GetName() != scope_name; - if (bHasAlias2) { - result = find_memoized_scope(cr->GetName()); - if (result) { - g_name2classrefidx[scope_name] = result; - if (bHasAlias1) g_name2classrefidx[sname] = result; - return result; - } - } +Cppyy::TCppScope_t Cppyy::GetFullScope(const std::string& name) +{ + return Cppyy::GetScope(name); +} - ClassRefs_t::size_type sz = g_classrefs.size(); - g_name2classrefidx[scope_name] = sz; - if (bHasAlias1) g_name2classrefidx[sname] = sz; - if (bHasAlias2) g_name2classrefidx[cr->GetName()] = sz; -// TODO: make ROOT/meta NOT remove std :/ - if (b_scope_name_missclassified) - g_name2classrefidx["std::"+scope_name] = sz; - if (b_sname_missclassified) - g_name2classrefidx["std::"+sname] = sz; +Cppyy::TCppScope_t Cppyy::GetTypeScope(TCppScope_t var) +{ + return Cpp::GetScopeFromType( + Cpp::GetVariableType(var)); +} - g_classrefs.push_back(TClassRef(scope_name.c_str())); +Cppyy::TCppScope_t Cppyy::GetNamed(const std::string& name, + TCppScope_t parent_scope) +{ + return Cpp::GetNamed(name, parent_scope); +} - return (TCppScope_t)sz; +Cppyy::TCppScope_t Cppyy::GetParentScope(TCppScope_t scope) +{ + return Cpp::GetParentScope(scope); } -bool Cppyy::IsTemplate(const std::string& template_name) +Cppyy::TCppScope_t Cppyy::GetScopeFromType(TCppType_t type) { - return (bool)gInterpreter->CheckClassTemplate(template_name.c_str()); + return Cpp::GetScopeFromType(type); } -namespace { - class AutoCastRTTI { - public: - virtual ~AutoCastRTTI() {} - }; +Cppyy::TCppType_t Cppyy::GetTypeFromScope(TCppScope_t klass) +{ + return Cpp::GetTypeFromScope(klass); } -Cppyy::TCppType_t Cppyy::GetActualClass(TCppType_t klass, TCppObject_t obj) +Cppyy::TCppScope_t Cppyy::GetGlobalScope() { - TClassRef& cr = type_from_handle(klass); - if (!cr.GetClass() || !obj) return klass; + return Cpp::GetGlobalScope(); +} - if (!(cr->ClassProperty() & kClassHasVirtual)) - return klass; // not polymorphic: no RTTI info available +bool Cppyy::IsTemplate(TCppScope_t handle) +{ + return Cpp::IsTemplate(handle); +} -// TODO: ios class casting (ostream, streambuf, etc.) fails with a crash in GetActualClass() -// below on Mac ARM (it's likely that the found actual class was replaced, maybe because -// there are duplicates from pcm/pch?); filter them out for now as it's usually unnecessary -// anyway to autocast these - std::string clName = cr->GetName(); - if (clName.find("std::", 0, 5) == 0 && clName.find("stream") != std::string::npos) - return klass; +bool Cppyy::IsTemplateInstantiation(TCppScope_t handle) +{ + return Cpp::IsTemplateSpecialization(handle); +} -#ifdef _WIN64 -// Cling does not provide a consistent ImageBase address for calculating relative addresses -// as used in Windows 64b RTTI. So, check for our own RTTI extension instead. If that fails, -// see whether the unmangled raw_name is available (e.g. if this is an MSVC compiled rather -// than JITed class) and pass on if it is. - volatile const char* raw = nullptr; // to prevent too aggressive reordering - try { - // this will filter those objects that do not have RTTI to begin with (throws) - AutoCastRTTI* pcst = (AutoCastRTTI*)obj; - raw = typeid(*pcst).raw_name(); - - // check the signature id (0 == absolute, 1 == relative, 2 == ours) - void* vfptr = *(void**)((intptr_t)obj); - void* meta = (void*)((intptr_t)*((void**)((intptr_t)vfptr-sizeof(void*)))); - if (*(intptr_t*)meta == 2) { - // access the extra data item which is an absolute pointer to the RTTI - void* ptdescr = (void*)((intptr_t)meta + 4*sizeof(unsigned long)+sizeof(void*)); - if (ptdescr && *(void**)ptdescr) { - auto rtti = *(std::type_info**)ptdescr; - raw = rtti->raw_name(); - if (raw && raw[0] != '\0') // likely unnecessary - return (TCppType_t)GetScope(rtti->name()); - } +bool Cppyy::IsTypedefed(TCppScope_t handle) +{ + return Cpp::IsTypedefed(handle); +} - return klass; // do not fall through if no RTTI info available - } +namespace { +class AutoCastRTTI { +public: + virtual ~AutoCastRTTI() {} +}; +} // namespace - // if the raw name is the empty string (no guarantees that this is so as truly, the - // address is corrupt, but it is common to be empty), then there is no accessible RTTI - // and getting the unmangled name will crash ... - if (!raw) - return klass; - } catch (std::bad_typeid) { - return klass; // can't risk passing to ROOT/meta as it may do RTTI - } -#endif +Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { + if (!Cpp::IsClassPolymorphic(klass)) + return klass; - TClass* clActual = cr->GetActualClass((void*)obj); - // The additional check using TClass::GetClassInfo is to prevent returning classes of which the Interpreter has no info (see https://github.com/root-project/root/pull/16177) - if (clActual && clActual != cr.GetClass() && clActual->GetClassInfo()) { - auto itt = g_name2classrefidx.find(clActual->GetName()); - if (itt != g_name2classrefidx.end()) - return (TCppType_t)itt->second; - return (TCppType_t)GetScope(clActual->GetName()); - } + const std::type_info *typ = &typeid(*(AutoCastRTTI *)obj); + + std::string mangled_name = typ->name(); + std::string demangled_name = Cpp::Demangle(mangled_name); + + if (TCppScope_t scope = Cppyy::GetScope(demangled_name)) + return scope; return klass; } -size_t Cppyy::SizeOf(TCppType_t klass) +size_t Cppyy::SizeOf(TCppScope_t klass) { - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass() && cr->GetClassInfo()) - return (size_t)gInterpreter->ClassInfo_Size(cr->GetClassInfo()); - return (size_t)0; + return Cpp::SizeOf(klass); } -size_t Cppyy::SizeOf(const std::string& type_name) +size_t Cppyy::SizeOfType(TCppType_t klass) { - TDataType* dt = gROOT->GetType(type_name.c_str()); - if (dt) return dt->Size(); - return SizeOf(GetScope(type_name)); + return Cpp::GetSizeOfType(klass); } +// size_t Cppyy::SizeOf(const std::string& type_name) +// { +// TDataType* dt = gROOT->GetType(type_name.c_str()); +// if (dt) return dt->Size(); +// return SizeOf(GetScope(type_name)); +// } + bool Cppyy::IsBuiltin(const std::string& type_name) { - if (g_builtins.find(type_name) != g_builtins.end()) - return true; - - const std::string& tclean = TClassEdit::CleanType(type_name.c_str(), 1); - if (g_builtins.find(tclean) != g_builtins.end()) - return true; + static std::set s_builtins = + {"bool", "char", "signed char", "unsigned char", "wchar_t", "short", + "unsigned short", "int", "unsigned int", "long", "unsigned long", + "long long", "unsigned long long", "float", "double", "long double", + "void"}; + if (s_builtins.find(trim(type_name)) != s_builtins.end()) + return true; - if (strstr(tclean.c_str(), "std::complex")) + if (strstr(type_name.c_str(), "std::complex")) return true; return false; } -bool Cppyy::IsComplete(const std::string& type_name) +bool Cppyy::IsBuiltin(TCppType_t type) { -// verify whether the dictionary of this class is fully available - bool b = false; - - int oldEIL = gErrorIgnoreLevel; - gErrorIgnoreLevel = 3000; - TClass* klass = TClass::GetClass(type_name.c_str()); - if (klass && klass->GetClassInfo()) // works for normal case w/ dict - b = gInterpreter->ClassInfo_IsLoaded(klass->GetClassInfo()); - else { // special case for forward declared classes - ClassInfo_t* ci = gInterpreter->ClassInfo_Factory(type_name.c_str()); - if (ci) { - b = gInterpreter->ClassInfo_IsLoaded(ci); - gInterpreter->ClassInfo_Delete(ci); // we own the fresh class info - } - } - gErrorIgnoreLevel = oldEIL; - return b; + return Cpp::IsBuiltin(type); + } -// memory management --------------------------------------------------------- -Cppyy::TCppObject_t Cppyy::Allocate(TCppType_t type) +bool Cppyy::IsComplete(TCppScope_t scope) { - TClassRef& cr = type_from_handle(type); - return (TCppObject_t)::operator new(gInterpreter->ClassInfo_Size(cr->GetClassInfo())); + return Cpp::IsComplete(scope); } -void Cppyy::Deallocate(TCppType_t /* type */, TCppObject_t instance) +// // memory management --------------------------------------------------------- +Cppyy::TCppObject_t Cppyy::Allocate(TCppScope_t scope) { - ::operator delete(instance); + return Cpp::Allocate(scope, /*count=*/1); } -Cppyy::TCppObject_t Cppyy::Construct(TCppType_t type, void* arena) +void Cppyy::Deallocate(TCppScope_t scope, TCppObject_t instance) { - TClassRef& cr = type_from_handle(type); - if (arena) - return (TCppObject_t)cr->New(arena, TClass::kRealNew); - return (TCppObject_t)cr->New(TClass::kRealNew); + Cpp::Deallocate(scope, instance, /*count=*/1); } -static std::map sHasOperatorDelete; -void Cppyy::Destruct(TCppType_t type, TCppObject_t instance) +Cppyy::TCppObject_t Cppyy::Construct(TCppScope_t scope, void* arena/*=nullptr*/) { - TClassRef& cr = type_from_handle(type); - if (cr->ClassProperty() & (kClassHasExplicitDtor | kClassHasImplicitDtor)) - cr->Destructor((void*)instance); - else { - ROOT::DelFunc_t fdel = cr->GetDelete(); - if (fdel) fdel((void*)instance); - else { - auto ib = sHasOperatorDelete.find(type); - if (ib == sHasOperatorDelete.end()) { - TFunction *f = (TFunction *)cr->GetMethodAllAny("operator delete"); - sHasOperatorDelete[type] = (bool)(f && (f->Property() & kIsPublic)); - ib = sHasOperatorDelete.find(type); - } - ib->second ? cr->Destructor((void*)instance) : ::operator delete((void*)instance); - } - } + return Cpp::Construct(scope, arena, /*count=*/1); } - -// method/function dispatching ----------------------------------------------- -static TInterpreter::CallFuncIFacePtr_t GetCallFunc(Cppyy::TCppMethod_t method) +void Cppyy::Destruct(TCppScope_t scope, TCppObject_t instance) { -// TODO: method should be a callfunc, so that no mapping would be needed. - CallWrapper* wrap = (CallWrapper*)method; - - CallFunc_t* callf = gInterpreter->CallFunc_Factory(); - MethodInfo_t* meth = gInterpreter->MethodInfo_Factory(wrap->fDecl); - gInterpreter->CallFunc_SetFunc(callf, meth); - gInterpreter->MethodInfo_Delete(meth); - - if (!(callf && gInterpreter->CallFunc_IsValid(callf))) { - // TODO: propagate this error to caller w/o use of Python C-API - /* - PyErr_Format(PyExc_RuntimeError, "could not resolve %s::%s(%s)", - const_cast(klass).GetClassName(), - wrap.fName, callString.c_str()); */ - std::cerr << "TODO: report unresolved function error to Python\n"; - if (callf) gInterpreter->CallFunc_Delete(callf); - return TInterpreter::CallFuncIFacePtr_t{}; - } - -// generate the wrapper and JIT it; ignore wrapper generation errors (will simply -// result in a nullptr that is reported upstream if necessary; often, however, -// there is a different overload available that will do) - auto oldErrLvl = gErrorIgnoreLevel; - gErrorIgnoreLevel = kFatal; - wrap->fFaceptr = gInterpreter->CallFunc_IFacePtr(callf); - gErrorIgnoreLevel = oldErrLvl; - - gInterpreter->CallFunc_Delete(callf); // does not touch IFacePtr - return wrap->fFaceptr; + Cpp::Destruct(instance, scope, true, /*count=*/0); } static inline @@ -981,60 +1009,48 @@ bool copy_args(Parameter* args, size_t nargs, void** vargs) } static inline -void release_args(Parameter* args, size_t nargs) -{ +void release_args(Parameter* args, size_t nargs) { for (size_t i = 0; i < nargs; ++i) { if (args[i].fTypeCode == 'X') free(args[i].fValue.fVoidp); } } -static inline bool WrapperCall(Cppyy::TCppMethod_t method, size_t nargs, void* args_, void* self, void* result) +// static inline +// bool is_ready(CallWrapper* wrap, bool is_direct) { +// return (!is_direct && wrap->fFaceptr.fGeneric) || (is_direct && wrap->fFaceptr.fDirect); +// } + +static inline +bool WrapperCall(Cppyy::TCppMethod_t method, size_t nargs, void* args_, void* self, void* result) { Parameter* args = (Parameter*)args_; - //bool is_direct = nargs & DIRECT_CALL; + bool is_direct = nargs & DIRECT_CALL; nargs = CALL_NARGS(nargs); - CallWrapper* wrap = (CallWrapper*)method; - const TInterpreter::CallFuncIFacePtr_t& faceptr = wrap->fFaceptr.fGeneric ? wrap->fFaceptr : GetCallFunc(method); - if (!faceptr.fGeneric) - return false; // happens with compilation error + // if (!is_ready(wrap, is_direct)) + // return false; // happens with compilation error - if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kGeneric) { + if (Cpp::JitCall JC = Cpp::MakeFunctionCallable(method)) { bool runRelease = false; + //const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr; if (nargs <= SMALL_ARGS_N) { void* smallbuf[SMALL_ARGS_N]; if (nargs) runRelease = copy_args(args, nargs, smallbuf); - faceptr.fGeneric(self, (int)nargs, smallbuf, result); + // CLING_CATCH_UNCAUGHT_ + JC.Invoke(result, {smallbuf, nargs}, self); + // _CLING_CATCH_UNCAUGHT } else { std::vector buf(nargs); runRelease = copy_args(args, nargs, buf.data()); - faceptr.fGeneric(self, (int)nargs, buf.data(), result); + // CLING_CATCH_UNCAUGHT_ + JC.Invoke(result, {buf.data(), nargs}, self); + // _CLING_CATCH_UNCAUGHT } if (runRelease) release_args(args, nargs); return true; } - if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kCtor) { - bool runRelease = false; - if (nargs <= SMALL_ARGS_N) { - void* smallbuf[SMALL_ARGS_N]; - if (nargs) runRelease = copy_args(args, nargs, (void**)smallbuf); - faceptr.fCtor((void**)smallbuf, result, (unsigned long)nargs); - } else { - std::vector buf(nargs); - runRelease = copy_args(args, nargs, buf.data()); - faceptr.fCtor(buf.data(), result, (unsigned long)nargs); - } - if (runRelease) release_args(args, nargs); - return true; - } - - if (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kDtor) { - std::cerr << " DESTRUCTOR NOT IMPLEMENTED YET! " << std::endl; - return false; - } - return false; } @@ -1045,12 +1061,21 @@ T CallT(Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, size_t nargs, void T t{}; if (WrapperCall(method, nargs, args, (void*)self, &t)) return t; + throw std::runtime_error("failed to resolve function"); return (T)-1; } +#ifdef PRINT_DEBUG + #define _IMP_CALL_PRINT_STMT(type) \ + printf("IMP CALL with type: %s\n", #type); +#else + #define _IMP_CALL_PRINT_STMT(type) +#endif + #define CPPYY_IMP_CALL(typecode, rtype) \ rtype Cppyy::Call##typecode(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args)\ { \ + _IMP_CALL_PRINT_STMT(rtype) \ return CallT(method, self, nargs, args); \ } @@ -1065,10 +1090,10 @@ CPPYY_IMP_CALL(C, char ) CPPYY_IMP_CALL(H, short ) CPPYY_IMP_CALL(I, int ) CPPYY_IMP_CALL(L, long ) -CPPYY_IMP_CALL(LL, Long64_t ) +CPPYY_IMP_CALL(LL, long long ) CPPYY_IMP_CALL(F, float ) CPPYY_IMP_CALL(D, double ) -CPPYY_IMP_CALL(LD, LongDouble_t ) +CPPYY_IMP_CALL(LD, long double ) void* Cppyy::CallR(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args) { @@ -1082,7 +1107,7 @@ char* Cppyy::CallS( TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length) { char* cstr = nullptr; - TClassRef cr("std::string"); + // TClassRef cr("std::string"); // TODO: Why is this required? std::string* cppresult = (std::string*)malloc(sizeof(std::string)); if (WrapperCall(method, nargs, args, self, (void*)cppresult)) { cstr = cppstring_to_cstring(*cppresult); @@ -1095,35 +1120,22 @@ char* Cppyy::CallS( } Cppyy::TCppObject_t Cppyy::CallConstructor( - TCppMethod_t method, TCppType_t /* klass */, size_t nargs, void* args) + TCppMethod_t method, TCppScope_t klass, size_t nargs, void* args) { void* obj = nullptr; - if (WrapperCall(method, nargs, args, nullptr, &obj)) - return (TCppObject_t)obj; - return (TCppObject_t)0; + WrapperCall(method, nargs, args, nullptr, &obj); + return (TCppObject_t)obj; } -void Cppyy::CallDestructor(TCppType_t type, TCppObject_t self) +void Cppyy::CallDestructor(TCppScope_t scope, TCppObject_t self) { - TClassRef& cr = type_from_handle(type); - cr->Destructor((void*)self, true); + Cpp::Destruct(self, scope, /*withFree=*/false, /*count=*/0); } Cppyy::TCppObject_t Cppyy::CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, TCppType_t result_type) { - TClassRef& cr = type_from_handle(result_type); - auto *classInfo = cr->GetClassInfo(); - // If the class info is missing, we better return null and let cppyy - // handle the error, before we step into undefined behavior - if(!classInfo) - return (TCppObject_t)0; - auto classSize = gInterpreter->ClassInfo_Size(classInfo); - // ClassInfo_Size returns -1 in case of invalid info, and 0 for - // forward-declared classes, which we can't use. - if (classSize <= 0) - return (TCppObject_t)0; - void* obj = ::operator new(classSize); + void* obj = ::operator new(Cpp::GetSizeOfType(result_type)); if (WrapperCall(method, nargs, args, self, obj)) return (TCppObject_t)obj; ::operator delete(obj); @@ -1132,54 +1144,7 @@ Cppyy::TCppObject_t Cppyy::CallO(TCppMethod_t method, Cppyy::TCppFuncAddr_t Cppyy::GetFunctionAddress(TCppMethod_t method, bool check_enabled) { - if (check_enabled && !gEnableFastPath) return (TCppFuncAddr_t)nullptr; - TFunction* f = m2f(method); - - TCppFuncAddr_t pf = (TCppFuncAddr_t)gInterpreter->FindSym(f->GetMangledName()); - if (pf) return pf; - - int ierr = 0; - const char* fn = TClassEdit::DemangleName(f->GetMangledName(), ierr); - if (ierr || !fn) - return pf; - - // TODO: the following attempts are all brittle and leak transactions, but - // each properly exposes the symbol so subsequent lookups will succeed - if (strstr(f->GetName(), "<")) { - // force explicit instantiation and try again - std::ostringstream sig; - sig << "template " << fn << ";"; - gInterpreter->ProcessLine(sig.str().c_str()); - } else { - std::ostringstream sig; - - std::string sfn = fn; - std::string::size_type pos = sfn.find('('); - if (pos != std::string::npos) sfn = sfn.substr(0, pos); - - // start cast - sig << '(' << f->GetReturnTypeName() << " ("; - - // add scope for methods - pos = sfn.rfind(':'); - if (pos != std::string::npos) { - std::string scope_name = sfn.substr(0, pos-1); - TCppScope_t scope = GetScope(scope_name); - if (scope && !IsNamespace(scope)) - sig << scope_name << "::"; - } - - // finalize cast - sig << "*)" << GetMethodSignature(method, false) - << ((f->Property() & kIsConstMethod) ? " const" : "") - << ')'; - - // load address - sig << '&' << sfn; - gInterpreter->Calc(sig.str().c_str()); - } - - return (TCppFuncAddr_t)gInterpreter->FindSym(f->GetMangledName()); + return (TCppFuncAddr_t) Cpp::GetFunctionAddress(method); } @@ -1208,339 +1173,196 @@ size_t Cppyy::GetFunctionArgTypeoffset() // scope reflection information ---------------------------------------------- bool Cppyy::IsNamespace(TCppScope_t scope) { -// Test if this scope represents a namespace. - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return cr->Property() & kIsNamespace; - return false; + if (!scope) + return false; + + // Test if this scope represents a namespace. + return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope; } -bool Cppyy::IsAbstract(TCppType_t klass) +bool Cppyy::IsClass(TCppScope_t scope) { -// Test if this type may not be instantiated. - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass()) - return cr->Property() & kIsAbstract; - return false; + // Test if this scope represents a namespace. + return Cpp::IsClass(scope); } - -bool Cppyy::IsEnum(const std::string& type_name) +// +bool Cppyy::IsAbstract(TCppScope_t scope) { - if (type_name.empty()) return false; - - if (type_name.rfind("enum ", 0) == 0) - return true; // by definition (C-style) - - std::string tn_short = TClassEdit::ShortType(type_name.c_str(), 1); - if (tn_short.empty()) return false; - return gInterpreter->ClassInfo_IsEnum(tn_short.c_str()); + // Test if this type may not be instantiated. + return Cpp::IsAbstract(scope); } -bool Cppyy::IsAggregate(TCppType_t type) +bool Cppyy::IsEnumScope(TCppScope_t scope) { -// Test if this type is a "plain old data" type - TClassRef& cr = type_from_handle(type); - if (cr.GetClass()) - return cr->ClassProperty() & kClassIsAggregate; - return false; + return Cpp::IsEnumScope(scope); } -bool Cppyy::IsDefaultConstructable(TCppType_t type) +bool Cppyy::IsEnumConstant(TCppScope_t scope) { -// Test if this type has a default constructor or is a "plain old data" type - TClassRef& cr = type_from_handle(type); - if (cr.GetClass()) - return cr->HasDefaultConstructor() || (cr->ClassProperty() & kClassIsAggregate); - return true; + return Cpp::IsEnumConstant(Cpp::GetUnderlyingScope(scope)); } -// helpers for stripping scope names -static -std::string outer_with_template(const std::string& name) +bool Cppyy::IsEnumType(TCppType_t type) { -// Cut down to the outer-most scope from , taking proper care of templates. - int tpl_open = 0; - for (std::string::size_type pos = 0; pos < name.size(); ++pos) { - std::string::value_type c = name[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '<') - ++tpl_open; - else if (c == '>') - --tpl_open; - - // collect name up to "::" - else if (tpl_open == 0 && \ - c == ':' && pos+1 < name.size() && name[pos+1] == ':') { - // found the extend of the scope ... done - return name.substr(0, pos-1); - } - } - -// whole name is apparently a single scope - return name; + return Cpp::IsEnumType(type); } -static -std::string outer_no_template(const std::string& name) +bool Cppyy::IsAggregate(TCppType_t type) { -// Cut down to the outer-most scope from , drop templates - std::string::size_type first_scope = name.find(':'); - if (first_scope == std::string::npos) - return name.substr(0, name.find('<')); - std::string::size_type first_templ = name.find('<'); - if (first_templ == std::string::npos) - return name.substr(0, first_scope); - return name.substr(0, std::min(first_templ, first_scope)); + // Test if this type is a "plain old data" type + return Cpp::IsAggregate(type); } -#define FILL_COLL(type, filter) { \ - TIter itr{coll}; \ - type* obj = nullptr; \ - while ((obj = (type*)itr.Next())) { \ - const char* nm = obj->GetName(); \ - if (nm && nm[0] != '_' && !(obj->Property() & (filter))) { \ - if (gInitialNames.find(nm) == gInitialNames.end()) \ - cppnames.insert(nm); \ - }}} - -static inline -void cond_add(Cppyy::TCppScope_t scope, const std::string& ns_scope, - std::set& cppnames, const char* name, bool nofilter = false) +bool Cppyy::IsDefaultConstructable(TCppScope_t scope) { - if (!name || strstr(name, ".h") != 0) - return; - - if (scope == GLOBAL_HANDLE) { - std::string to_add = outer_no_template(name); - if ((nofilter || gInitialNames.find(to_add) == gInitialNames.end()) && !is_missclassified_stl(name)) - cppnames.insert(outer_no_template(name)); - } else if (scope == STD_HANDLE) { - if (strncmp(name, "std::", 5) == 0) { - name += 5; -#ifdef __APPLE__ - if (strncmp(name, "__1::", 5) == 0) name += 5; -#endif - } else if (!is_missclassified_stl(name)) - return; - cppnames.insert(outer_no_template(name)); - } else { - if (strncmp(name, ns_scope.c_str(), ns_scope.size()) == 0) - cppnames.insert(outer_with_template(name + ns_scope.size())); - } -} +// Test if this type has a default constructor or is a "plain old data" type + return Cpp::HasDefaultConstructor(scope); +} + +bool Cppyy::IsVariable(TCppScope_t scope) +{ + return Cpp::IsVariable(scope); +} + +// // helpers for stripping scope names +// static +// std::string outer_with_template(const std::string& name) +// { +// // Cut down to the outer-most scope from , taking proper care of templates. +// int tpl_open = 0; +// for (std::string::size_type pos = 0; pos < name.size(); ++pos) { +// std::string::value_type c = name[pos]; +// +// // count '<' and '>' to be able to skip template contents +// if (c == '<') +// ++tpl_open; +// else if (c == '>') +// --tpl_open; +// +// // collect name up to "::" +// else if (tpl_open == 0 && \ +// c == ':' && pos+1 < name.size() && name[pos+1] == ':') { +// // found the extend of the scope ... done +// return name.substr(0, pos-1); +// } +// } +// +// // whole name is apparently a single scope +// return name; +// } +// +// static +// std::string outer_no_template(const std::string& name) +// { +// // Cut down to the outer-most scope from , drop templates +// std::string::size_type first_scope = name.find(':'); +// if (first_scope == std::string::npos) +// return name.substr(0, name.find('<')); +// std::string::size_type first_templ = name.find('<'); +// if (first_templ == std::string::npos) +// return name.substr(0, first_scope); +// return name.substr(0, std::min(first_templ, first_scope)); +// } +// +// #define FILL_COLL(type, filter) { \ +// TIter itr{coll}; \ +// type* obj = nullptr; \ +// while ((obj = (type*)itr.Next())) { \ +// const char* nm = obj->GetName(); \ +// if (nm && nm[0] != '_' && !(obj->Property() & (filter))) { \ +// if (gInitialNames.find(nm) == gInitialNames.end()) \ +// cppnames.insert(nm); \ +// }}} +// +// static inline +// void cond_add(Cppyy::TCppScope_t scope, const std::string& ns_scope, +// std::set& cppnames, const char* name, bool nofilter = false) +// { +// if (!name || name[0] == '_' || strstr(name, ".h") != 0 || strncmp(name, "operator", 8) == 0) +// return; +// +// if (scope == GLOBAL_HANDLE) { +// std::string to_add = outer_no_template(name); +// if (nofilter || gInitialNames.find(to_add) == gInitialNames.end()) +// cppnames.insert(outer_no_template(name)); +// } else if (scope == STD_HANDLE) { +// if (strncmp(name, "std::", 5) == 0) { +// name += 5; +// #ifdef __APPLE__ +// if (strncmp(name, "__1::", 5) == 0) name += 5; +// #endif +// } +// cppnames.insert(outer_no_template(name)); +// } else { +// if (strncmp(name, ns_scope.c_str(), ns_scope.size()) == 0) +// cppnames.insert(outer_with_template(name + ns_scope.size())); +// } +// } void Cppyy::GetAllCppNames(TCppScope_t scope, std::set& cppnames) { // Collect all known names of C++ entities under scope. This is useful for IDEs // employing tab-completion, for example. Note that functions names need not be // unique as they can be overloaded. - TClassRef& cr = type_from_handle(scope); - if (scope != GLOBAL_HANDLE && !(cr.GetClass() && cr->Property())) - return; - - std::string ns_scope = GetFinalName(scope); - if (scope != GLOBAL_HANDLE) ns_scope += "::"; - -// add existing values from read rootmap files if within this scope - TCollection* coll = gInterpreter->GetMapfile()->GetTable(); - { - TIter itr{coll}; - TEnvRec* ev = nullptr; - while ((ev = (TEnvRec*)itr.Next())) { - // TEnv contains rootmap entries and user-side rootmap files may be already - // loaded on startup. Thus, filter on file name rather than load time. - if (gRootSOs.find(ev->GetValue()) == gRootSOs.end()) - cond_add(scope, ns_scope, cppnames, ev->GetName(), true); - } - } - -// do we care about the class table or are the rootmap and list of types enough? -/* - gClassTable->Init(); - const int N = gClassTable->Classes(); - for (int i = 0; i < N; ++i) - cond_add(scope, ns_scope, cppnames, gClassTable->Next()); -*/ - -#if 0 -// add interpreted classes (no load) - { - ClassInfo_t* ci = gInterpreter->ClassInfo_FactoryWithScope( - false /* all */, scope == GLOBAL_HANDLE ? nullptr : cr->GetName()); - while (gInterpreter->ClassInfo_Next(ci)) { - const char* className = gInterpreter->ClassInfo_FullName(ci); - if (strstr(className, "(anonymous)") || strstr(className, "(unnamed)")) - continue; - cond_add(scope, ns_scope, cppnames, className); - } - gInterpreter->ClassInfo_Delete(ci); - } -#endif - -// any other types (e.g. that may have come from parsing headers) - coll = gROOT->GetListOfTypes(); - { - TIter itr{coll}; - TDataType* dt = nullptr; - while ((dt = (TDataType*)itr.Next())) { - if (!(dt->Property() & kIsFundamental)) { - cond_add(scope, ns_scope, cppnames, dt->GetName()); - } - } - } - -// add functions - coll = (scope == GLOBAL_HANDLE) ? - gROOT->GetListOfGlobalFunctions(true) : cr->GetListOfMethods(true); - { - TIter itr{coll}; - TFunction* obj = nullptr; - while ((obj = (TFunction*)itr.Next())) { - const char* nm = obj->GetName(); - // skip templated functions, adding only the un-instantiated ones - if (nm && gInitialNames.find(nm) == gInitialNames.end()) - cppnames.insert(nm); - } - } - -// add uninstantiated templates - coll = (scope == GLOBAL_HANDLE) ? - gROOT->GetListOfFunctionTemplates() : cr->GetListOfFunctionTemplates(true); - FILL_COLL(TFunctionTemplate, kIsPrivate | kIsProtected) - -// add (global) data members - if (scope == GLOBAL_HANDLE) { - coll = gROOT->GetListOfGlobals(); - FILL_COLL(TGlobal, kIsEnum | kIsPrivate | kIsProtected) - } else { - coll = cr->GetListOfDataMembers(); - FILL_COLL(TDataMember, kIsEnum | kIsPrivate | kIsProtected) - coll = cr->GetListOfUsingDataMembers(); - FILL_COLL(TDataMember, kIsEnum | kIsPrivate | kIsProtected) - } - -// add enums values only for user classes/namespaces - if (scope != GLOBAL_HANDLE && scope != STD_HANDLE) { - coll = cr->GetListOfEnums(); - FILL_COLL(TEnum, kIsPrivate | kIsProtected) - } - -#ifdef __APPLE__ -// special case for Apple, add version namespace '__1' entries to std - if (scope == STD_HANDLE) - GetAllCppNames(GetScope("std::__1"), cppnames); -#endif + Cpp::GetAllCppNames(scope, cppnames); } - -// class reflection information ---------------------------------------------- +// +// // class reflection information ---------------------------------------------- std::vector Cppyy::GetUsingNamespaces(TCppScope_t scope) { - std::vector res; - if (!IsNamespace(scope)) - return res; - -#ifdef __APPLE__ - if (scope == STD_HANDLE) { - res.push_back(GetScope("__1")); - return res; - } -#endif - - TClassRef& cr = type_from_handle(scope); - if (!cr.GetClass() || !cr->GetClassInfo()) - return res; - - const std::vector& v = gInterpreter->GetUsingNamespaces(cr->GetClassInfo()); - res.reserve(v.size()); - for (const auto& uid : v) { - Cppyy::TCppScope_t uscope = GetScope(uid); - if (uscope) res.push_back(uscope); - } - - return res; + return Cpp::GetUsingNamespaces(scope); } - -// class reflection information ---------------------------------------------- +// // class reflection information ---------------------------------------------- std::string Cppyy::GetFinalName(TCppType_t klass) { - if (klass == GLOBAL_HANDLE) - return ""; - TClassRef& cr = type_from_handle(klass); - std::string clName = cr->GetName(); -// TODO: why is this template splitting needed? - std::string::size_type pos = clName.substr(0, clName.find('<')).rfind("::"); - if (pos != std::string::npos) - return clName.substr(pos+2, std::string::npos); - return clName; + return Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass)); } std::string Cppyy::GetScopedFinalName(TCppType_t klass) { - if (klass == GLOBAL_HANDLE) - return ""; - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass()) { - std::string name = cr->GetName(); - if (is_missclassified_stl(name)) - return std::string("std::")+cr->GetName(); - return cr->GetName(); - } - return ""; -} - -bool Cppyy::HasVirtualDestructor(TCppType_t klass) -{ - TClassRef& cr = type_from_handle(klass); - if (!cr.GetClass()) - return false; - - TFunction* f = cr->GetMethod(("~"+GetFinalName(klass)).c_str(), ""); - if (f && (f->Property() & kIsVirtual)) - return true; - - return false; -} - -bool Cppyy::HasComplexHierarchy(TCppType_t klass) -{ - int is_complex = 1; - size_t nbases = 0; - - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass() && cr->GetListOfBases() != 0) - nbases = GetNumBases(klass); - - if (1 < nbases) - is_complex = 1; - else if (nbases == 0) - is_complex = 0; - else { // one base class only - TBaseClass* base = (TBaseClass*)cr->GetListOfBases()->At(0); - if (base->Property() & kIsVirtualBase) - is_complex = 1; // TODO: verify; can be complex, need not be. - else - is_complex = HasComplexHierarchy(GetScope(base->GetName())); - } - - return is_complex; -} - -Cppyy::TCppIndex_t Cppyy::GetNumBases(TCppType_t klass) + return Cpp::GetQualifiedCompleteName(klass); +} + +bool Cppyy::HasVirtualDestructor(TCppScope_t scope) +{ + TCppMethod_t func = Cpp::GetDestructor(scope); + return Cpp::IsVirtualMethod(func); +} + +// bool Cppyy::HasComplexHierarchy(TCppType_t klass) +// { +// int is_complex = 1; +// size_t nbases = 0; +// +// TClassRef& cr = type_from_handle(klass); +// if (cr.GetClass() && cr->GetListOfBases() != 0) +// nbases = GetNumBases(klass); +// +// if (1 < nbases) +// is_complex = 1; +// else if (nbases == 0) +// is_complex = 0; +// else { // one base class only +// TBaseClass* base = (TBaseClass*)cr->GetListOfBases()->At(0); +// if (base->Property() & kIsVirtualBase) +// is_complex = 1; // TODO: verify; can be complex, need not be. +// else +// is_complex = HasComplexHierarchy(GetScope(base->GetName())); +// } +// +// return is_complex; +// } + +Cppyy::TCppIndex_t Cppyy::GetNumBases(TCppScope_t klass) { // Get the total number of base classes that this class has. - TClassRef& cr = type_from_handle(klass); - if (cr.GetClass() && cr->GetListOfBases() != 0) - return (TCppIndex_t)cr->GetListOfBases()->GetSize(); - return (TCppIndex_t)0; + return Cpp::GetNumBases(klass); } //////////////////////////////////////////////////////////////////////////////// -/// \fn Cppyy::TCppIndex_t GetLongestInheritancePath(TClass *klass) +/// \fn Cppyy::TCppIndex_t Cppyy::GetNumBasesLongestBranch(TCppScope_t klass) /// \brief Retrieve number of base classes in the longest branch of the /// inheritance tree of the input class. /// \param[in] klass The class to start the retrieval process from. @@ -1560,159 +1382,83 @@ Cppyy::TCppIndex_t Cppyy::GetNumBases(TCppType_t klass) /// /// calling this function on an instance of `C` will return 3, the steps /// required to go from C to X. -Cppyy::TCppIndex_t GetLongestInheritancePath(TClass *klass) -{ - - auto directbases = klass->GetListOfBases(); - if (!directbases) { - // This is a leaf with no bases - return 0; - } - auto ndirectbases = directbases->GetSize(); - if (ndirectbases == 0) { - // This is a leaf with no bases - return 0; - } else { - // If there is at least one direct base - std::vector nbases_branches; - nbases_branches.reserve(ndirectbases); - - // Traverse all direct bases of the current class and call the function - // recursively - for (auto baseclass : TRangeDynCast(directbases)) { - if (!baseclass) - continue; - if (auto baseclass_tclass = baseclass->GetClassPointer()) { - nbases_branches.emplace_back(GetLongestInheritancePath(baseclass_tclass)); - } - } - - // Get longest path among the direct bases of the current class - auto longestbranch = std::max_element(std::begin(nbases_branches), std::end(nbases_branches)); - - // Add 1 to include the current class in the count - return 1 + *longestbranch; - } +Cppyy::TCppIndex_t Cppyy::GetNumBasesLongestBranch(TCppScope_t klass) { + std::vector num; + for (TCppIndex_t ibase = 0; ibase < GetNumBases(klass); ++ibase) + num.push_back(GetNumBasesLongestBranch(Cppyy::GetBaseScope(klass, ibase))); + if (num.empty()) + return 0; + return *std::max_element(num.begin(), num.end()) + 1; } -//////////////////////////////////////////////////////////////////////////////// -/// \fn Cppyy::TCppIndex_t Cppyy::GetNumBasesLongest(TCppType_t klass) -/// \brief Retrieve number of base classes in the longest branch of the -/// inheritance tree. -/// \param[in] klass The class to start the retrieval process from. -/// -/// The function converts the input class to a `TClass *` and calls -/// GetLongestInheritancePath. -Cppyy::TCppIndex_t Cppyy::GetNumBasesLongestBranch(TCppType_t klass) +std::string Cppyy::GetBaseName(TCppType_t klass, TCppIndex_t ibase) { - - const auto &cr = type_from_handle(klass); - - if (auto klass_tclass = cr.GetClass()) { - return GetLongestInheritancePath(klass_tclass); - } - - // In any other case, return zero - return 0; + return Cpp::GetName(Cpp::GetBaseClass(klass, ibase)); } -std::string Cppyy::GetBaseName(TCppType_t klass, TCppIndex_t ibase) +Cppyy::TCppScope_t Cppyy::GetBaseScope(TCppScope_t klass, TCppIndex_t ibase) { - TClassRef& cr = type_from_handle(klass); - return ((TBaseClass*)cr->GetListOfBases()->At((int)ibase))->GetName(); + return Cpp::GetBaseClass(klass, ibase); } -bool Cppyy::IsSubtype(TCppType_t derived, TCppType_t base) +bool Cppyy::IsSubclass(TCppScope_t derived, TCppScope_t base) { - if (derived == base) - return true; - TClassRef& derived_type = type_from_handle(derived); - TClassRef& base_type = type_from_handle(base); - if (derived_type.GetClass() && base_type.GetClass()) - return derived_type->GetBaseClass(base_type) != 0; - return false; + return Cpp::IsSubclass(derived, base); } -bool Cppyy::IsSmartPtr(TCppType_t klass) +static std::set gSmartPtrTypes = + {"std::auto_ptr", "std::shared_ptr", "std::unique_ptr", "std::weak_ptr"}; + +bool Cppyy::IsSmartPtr(TCppScope_t klass) { - TClassRef& cr = type_from_handle(klass); - const std::string& tn = cr->GetName(); - if (gSmartPtrTypes.find(tn.substr(0, tn.find("<"))) != gSmartPtrTypes.end()) + const std::string& rn = Cppyy::GetScopedFinalName(klass); + if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end()) return true; return false; } bool Cppyy::GetSmartPtrInfo( - const std::string& tname, TCppType_t* raw, TCppMethod_t* deref) + const std::string& tname, TCppScope_t* raw, TCppMethod_t* deref) { + // TODO: We can directly accept scope instead of name const std::string& rn = ResolveName(tname); - if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) != gSmartPtrTypes.end()) { - if (!raw && !deref) return true; - - TClassRef& cr = type_from_handle(GetScope(tname)); - if (cr.GetClass()) { - TFunction* func = cr->GetMethod("operator->", ""); - if (!func) - func = cr->GetMethod("operator->", ""); - if (func) { - if (deref) *deref = (TCppMethod_t)new_CallWrapper(func); - if (raw) *raw = GetScope(TClassEdit::ShortType( - func->GetReturnTypeNormalizedName().c_str(), 1)); - return (!deref || *deref) && (!raw || *raw); - } - } - } + if (gSmartPtrTypes.find(rn.substr(0, rn.find("<"))) == gSmartPtrTypes.end()) + return false; - return false; -} + if (!raw && !deref) return true; -void Cppyy::AddSmartPtrType(const std::string& type_name) -{ - gSmartPtrTypes.insert(ResolveName(type_name)); -} + TCppScope_t scope = Cppyy::GetScope(rn); + if (!scope) + return false; -void Cppyy::AddTypeReducer(const std::string& /*reducable*/, const std::string& /*reduced*/) -{ - // This function is deliberately left empty, because it is not used in - // PyROOT, and synchronizing it with cppyy-backend upstream would require - // patches to ROOT meta. + std::vector ops; + Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops, + /*kind=*/Cpp::OperatorArity::kBoth); + if (ops.size() != 1) + return false; + + if (deref) *deref = ops[0]; + if (raw) *raw = Cppyy::GetScopeFromType(Cpp::GetFunctionReturnType(ops[0])); + return (!deref || *deref) && (!raw || *raw); } +// void Cppyy::AddSmartPtrType(const std::string& type_name) +// { +// gSmartPtrTypes.insert(ResolveName(type_name)); +// } +// +// void Cppyy::AddTypeReducer(const std::string& reducable, const std::string& reduced) +// { +// gInterpreter->AddTypeReducer(reducable, reduced); +// } + // type offsets -------------------------------------------------------------- -ptrdiff_t Cppyy::GetBaseOffset(TCppType_t derived, TCppType_t base, +ptrdiff_t Cppyy::GetBaseOffset(TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror) { -// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 - if (derived == base || !(base && derived)) - return (ptrdiff_t)0; - - TClassRef& cd = type_from_handle(derived); - TClassRef& cb = type_from_handle(base); - - if (!cd.GetClass() || !cb.GetClass()) - return (ptrdiff_t)0; - - ptrdiff_t offset = -1; - if (!(cd->GetClassInfo() && cb->GetClassInfo())) { // gInterpreter requirement - // would like to warn, but can't quite determine error from intentional - // hiding by developers, so only cover the case where we really should have - // had a class info, but apparently don't: - if (cd->IsLoaded()) { - // warn to allow diagnostics - std::ostringstream msg; - msg << "failed offset calculation between " << cb->GetName() << " and " << cd->GetName(); - // TODO: propagate this warning to caller w/o use of Python C-API - // PyErr_WarnEx(PyExc_RuntimeWarning, const_cast(msg.str().c_str()), 1); - std::cerr << "Warning: " << msg.str() << '\n'; - } - - // return -1 to signal caller NOT to apply offset - return rerror ? (ptrdiff_t)offset : 0; - } + intptr_t offset = Cpp::GetBaseClassOffset(derived, base); - offset = gInterpreter->ClassInfo_GetBaseOffset( - cd->GetClassInfo(), cb->GetClassInfo(), (void*)address, direction > 0); if (offset == -1) // Cling error, treat silently return rerror ? (ptrdiff_t)offset : 0; @@ -1720,931 +1466,1353 @@ ptrdiff_t Cppyy::GetBaseOffset(TCppType_t derived, TCppType_t base, } -// method/function reflection information ------------------------------------ -Cppyy::TCppIndex_t Cppyy::GetNumMethods(TCppScope_t scope, bool accept_namespace) -{ - if (!accept_namespace && IsNamespace(scope)) - return (TCppIndex_t)0; // enforce lazy - - if (scope == GLOBAL_HANDLE) - return gROOT->GetListOfGlobalFunctions(true)->GetSize(); - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass() && cr->GetListOfMethods(true)) { - Cppyy::TCppIndex_t nMethods = (TCppIndex_t)cr->GetListOfMethods(false)->GetSize(); - if (nMethods == (TCppIndex_t)0) { - std::string clName = GetScopedFinalName(scope); - if (clName.find('<') != std::string::npos) { - // chicken-and-egg problem: TClass does not know about methods until - // instantiation, so force it - std::ostringstream stmt; - stmt << "template class " << clName << ";"; - gInterpreter->Declare(stmt.str().c_str()/*, silent = true*/); - - // now reload the methods - return (TCppIndex_t)cr->GetListOfMethods(true)->GetSize(); - } - } - return nMethods; - } - - return (TCppIndex_t)0; // unknown class? -} - -std::vector Cppyy::GetMethodIndicesFromName( +// // method/function reflection information ------------------------------------ +// Cppyy::TCppIndex_t Cppyy::GetNumMethods(TCppScope_t scope, bool accept_namespace) +// { +// if (!accept_namespace && IsNamespace(scope)) +// return (TCppIndex_t)0; // enforce lazy +// +// if (scope == GLOBAL_HANDLE) +// return gROOT->GetListOfGlobalFunctions(true)->GetSize(); +// +// TClassRef& cr = type_from_handle(scope); +// if (cr.GetClass() && cr->GetListOfMethods(true)) { +// Cppyy::TCppIndex_t nMethods = (TCppIndex_t)cr->GetListOfMethods(false)->GetSize(); +// if (nMethods == (TCppIndex_t)0) { +// std::string clName = GetScopedFinalName(scope); +// if (clName.find('<') != std::string::npos) { +// // chicken-and-egg problem: TClass does not know about methods until +// // instantiation, so force it +// std::ostringstream stmt; +// stmt << "template class " << clName << ";"; +// gInterpreter->Declare(stmt.str().c_str(), true [> silent <]); +// +// // now reload the methods +// return (TCppIndex_t)cr->GetListOfMethods(true)->GetSize(); +// } +// } +// return nMethods; +// } +// +// return (TCppIndex_t)0; // unknown class? +// } + +void Cppyy::GetClassMethods(TCppScope_t scope, std::vector &methods) +{ + Cpp::GetClassMethods(scope, methods); +} + +std::vector Cppyy::GetMethodsFromName( TCppScope_t scope, const std::string& name) { - std::vector indices; - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - gInterpreter->UpdateListOfMethods(cr.GetClass()); - int imeth = 0; - TFunction* func = nullptr; - TIter next(cr->GetListOfMethods()); - while ((func = (TFunction*)next())) { - if (match_name(name, func->GetName())) { - // C++ functions should be public to allow access; C functions have no access - // specifier and should always be accepted - auto prop = func->Property(); - if ((prop & kIsPublic) || !(prop & (kIsPrivate | kIsProtected | kIsPublic))) - indices.push_back((TCppIndex_t)imeth); - } - ++imeth; - } - } else if (scope == GLOBAL_HANDLE) { - TCollection* funcs = gROOT->GetListOfGlobalFunctions(true); - - // tickle deserialization - if (!funcs->FindObject(name.c_str())) - return indices; - - TFunction* func = nullptr; - TIter ifunc(funcs); - while ((func = (TFunction*)ifunc.Next())) { - if (match_name(name, func->GetName())) - indices.push_back((TCppIndex_t)new_CallWrapper(func)); - } - } - - return indices; -} - -Cppyy::TCppMethod_t Cppyy::GetMethod(TCppScope_t scope, TCppIndex_t idx) -{ - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunction* f = (TFunction*)cr->GetListOfMethods(false)->At((int)idx); - if (f) return (Cppyy::TCppMethod_t)new_CallWrapper(f); - return (Cppyy::TCppMethod_t)nullptr; - } - - assert(klass == (Cppyy::TCppType_t)GLOBAL_HANDLE); - return (Cppyy::TCppMethod_t)idx; -} - + return Cpp::GetFunctionsUsingName(scope, name); +} + +// Cppyy::TCppMethod_t Cppyy::GetMethod(TCppScope_t scope, TCppIndex_t idx) +// { +// TClassRef& cr = type_from_handle(scope); +// if (cr.GetClass()) { +// TFunction* f = (TFunction*)cr->GetListOfMethods(false)->At((int)idx); +// if (f) return (Cppyy::TCppMethod_t)new_CallWrapper(f); +// return (Cppyy::TCppMethod_t)nullptr; +// } +// +// assert(klass == (Cppyy::TCppType_t)GLOBAL_HANDLE); +// return (Cppyy::TCppMethod_t)idx; +// } +// std::string Cppyy::GetMethodName(TCppMethod_t method) { - if (method) { - const std::string& name = ((CallWrapper*)method)->fName; - - if (name.compare(0, 8, "operator") != 0) - // strip template instantiation part, if any - return name.substr(0, name.find('<')); - return name; - } - return ""; + return Cpp::GetName(method); } std::string Cppyy::GetMethodFullName(TCppMethod_t method) { - if (method) { - std::string name = ((CallWrapper*)method)->fName; - name.erase(std::remove(name.begin(), name.end(), ' '), name.end()); - return name; - } - return ""; + return Cpp::GetCompleteName(method); } -std::string Cppyy::GetMethodMangledName(TCppMethod_t method) +// std::string Cppyy::GetMethodMangledName(TCppMethod_t method) +// { +// if (method) +// return m2f(method)->GetMangledName(); +// return ""; +// } + +Cppyy::TCppType_t Cppyy::GetMethodReturnType(TCppMethod_t method) { - if (method) - return m2f(method)->GetMangledName(); - return ""; + return Cpp::GetFunctionReturnType(method); } -std::string Cppyy::GetMethodResultType(TCppMethod_t method) +std::string Cppyy::GetMethodReturnTypeAsString(TCppMethod_t method) { - if (method) { - TFunction* f = m2f(method); - if (f->ExtraProperty() & kIsConstructor) - return "constructor"; - std::string restype = f->GetReturnTypeName(); - // TODO: this is ugly; GetReturnTypeName() keeps typedefs, but may miss scopes - // for some reason; GetReturnTypeNormalizedName() has been modified to return - // the canonical type to guarantee correct namespaces. Sometimes typedefs look - // better, sometimes not, sometimes it's debatable (e.g. vector::size_type). - // So, for correctness sake, GetReturnTypeNormalizedName() is used, except for a - // special case of uint8_t/int8_t that must propagate as their typedefs. - if (restype.find("int8_t") != std::string::npos) - return restype; - restype = f->GetReturnTypeNormalizedName(); - if (restype == "(lambda)") { - std::ostringstream s; - // TODO: what if there are parameters to the lambda? - s << "__cling_internal::FT::F"; - TClass* cl = TClass::GetClass(s.str().c_str()); - if (cl) return cl->GetName(); - // TODO: signal some type of error (or should that be upstream? - } - return restype; - } - return ""; + return + Cpp::GetTypeAsString( + Cpp::GetCanonicalType( + Cpp::GetFunctionReturnType(method))); } Cppyy::TCppIndex_t Cppyy::GetMethodNumArgs(TCppMethod_t method) { - if (method) - return m2f(method)->GetNargs(); - return 0; + return Cpp::GetFunctionNumArgs(method); } Cppyy::TCppIndex_t Cppyy::GetMethodReqArgs(TCppMethod_t method) { - if (method) { - TFunction* f = m2f(method); - return (TCppIndex_t)(f->GetNargs() - f->GetNargsOpt()); - } - return (TCppIndex_t)0; + return Cpp::GetFunctionRequiredArgs(method); } std::string Cppyy::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) { - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At((int)iarg); - return arg->GetName(); - } - return ""; + if (!method) + return ""; + + return Cpp::GetFunctionArgName(method, iarg); } -std::string Cppyy::GetMethodArgType(TCppMethod_t method, TCppIndex_t iarg) +Cppyy::TCppType_t Cppyy::GetMethodArgType(TCppMethod_t method, TCppIndex_t iarg) { - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At((int)iarg); - std::string ft = arg->GetFullTypeName(); - if (ft.rfind("enum ", 0) != std::string::npos) { // special case to preserve 'enum' tag - std::string arg_type = arg->GetTypeNormalizedName(); - return arg_type.insert(arg_type.rfind("const ", 0) == std::string::npos ? 0 : 6, "enum "); - } else if (g_builtins.find(ft) != g_builtins.end() || ft.find("int8_t") != std::string::npos) - return ft; // do not resolve int8_t and uint8_t typedefs + return Cpp::GetFunctionArgType(method, iarg); +} - return arg->GetTypeNormalizedName(); - } - return ""; +std::string Cppyy::GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg) +{ + return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier( + Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const)); } -Cppyy::TCppIndex_t Cppyy::CompareMethodArgType(TCppMethod_t method, TCppIndex_t iarg, const std::string &req_type) +std::string Cppyy::GetMethodArgCanonTypeAsString(TCppMethod_t method, TCppIndex_t iarg) { - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg *)f->GetListOfMethodArgs()->At((int)iarg); - void *argqtp = gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo()); - - TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str()); - void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti); - - // This scoring is not based on any particular rules - if (gInterpreter->IsSameType(argqtp, reqqtp)) - return 0; // Best match - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsSignedIntegerType(reqqtp)) || - (gInterpreter->IsUnsignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsFloatingType(reqqtp))) - return 1; - else if ((gInterpreter->IsSignedIntegerType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp)) || - (gInterpreter->IsFloatingType(argqtp) && gInterpreter->IsUnsignedIntegerType(reqqtp))) - return 2; - else if ((gInterpreter->IsIntegerType(argqtp) && gInterpreter->IsIntegerType(reqqtp))) - return 3; - else if ((gInterpreter->IsVoidPointerType(argqtp) && gInterpreter->IsPointerType(reqqtp))) - return 4; - else - return 10; // Penalize heavily for no possible match - } - return INT_MAX; // Method is not valid + return + Cpp::GetTypeAsString( + Cpp::GetCanonicalType( + Cpp::GetFunctionArgType(method, iarg))); } std::string Cppyy::GetMethodArgDefault(TCppMethod_t method, TCppIndex_t iarg) { - if (method) { - TFunction* f = m2f(method); - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At((int)iarg); - const char* def = arg->GetDefault(); - if (def) - return def; - } - - return ""; + if (!method) + return ""; + return Cpp::GetFunctionArgDefault(method, iarg); } -std::string Cppyy::GetMethodSignature(TCppMethod_t method, bool show_formalargs, TCppIndex_t maxargs) -{ - TFunction* f = m2f(method); - if (f) { - std::ostringstream sig; - sig << "("; - int nArgs = f->GetNargs(); - if (maxargs != (TCppIndex_t)-1) nArgs = std::min(nArgs, (int)maxargs); - for (int iarg = 0; iarg < nArgs; ++iarg) { - TMethodArg* arg = (TMethodArg*)f->GetListOfMethodArgs()->At(iarg); - sig << arg->GetFullTypeName(); - if (show_formalargs) { - const char* argname = arg->GetName(); - if (argname && argname[0] != '\0') sig << " " << argname; - const char* defvalue = arg->GetDefault(); - if (defvalue && defvalue[0] != '\0') sig << " = " << defvalue; - } - if (iarg != nArgs-1) sig << (show_formalargs ? ", " : ","); +Cppyy::TCppIndex_t Cppyy::CompareMethodArgType(TCppMethod_t method, TCppIndex_t iarg, const std::string &req_type) +{ + // if (method) { + // TFunction* f = m2f(method); + // TMethodArg* arg = (TMethodArg *)f->GetListOfMethodArgs()->At((int)iarg); + // void *argqtp = gInterpreter->TypeInfo_QualTypePtr(arg->GetTypeInfo()); + + // TypeInfo_t *reqti = gInterpreter->TypeInfo_Factory(req_type.c_str()); + // void *reqqtp = gInterpreter->TypeInfo_QualTypePtr(reqti); + + // if (ArgSimilarityScore(argqtp, reqqtp) < 10) { + // return ArgSimilarityScore(argqtp, reqqtp); + // } + // else { // Match using underlying types + // if(gInterpreter->IsPointerType(argqtp)) + // argqtp = gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetPointerType(argqtp)); + + // // Handles reference types and strips qualifiers + // TypeInfo_t *arg_ul = gInterpreter->GetNonReferenceType(argqtp); + // TypeInfo_t *req_ul = gInterpreter->GetNonReferenceType(reqqtp); + // argqtp = gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(arg_ul))); + // reqqtp = gInterpreter->TypeInfo_QualTypePtr(gInterpreter->GetUnqualifiedType(gInterpreter->TypeInfo_QualTypePtr(req_ul))); + + // return ArgSimilarityScore(argqtp, reqqtp); + // } + // } + return 0; // Method is not valid +} + +std::string Cppyy::GetMethodSignature(TCppMethod_t method, bool show_formal_args, TCppIndex_t max_args) +{ + std::ostringstream sig; + sig << "("; + int nArgs = GetMethodNumArgs(method); + if (max_args != (TCppIndex_t)-1) nArgs = std::min(nArgs, (int)max_args); + for (int iarg = 0; iarg < nArgs; ++iarg) { + sig << Cppyy::GetMethodArgTypeAsString(method, iarg); + if (show_formal_args) { + std::string argname = Cppyy::GetMethodArgName(method, iarg); + if (!argname.empty()) sig << " " << argname; + std::string defvalue = Cppyy::GetMethodArgDefault(method, iarg); + if (!defvalue.empty()) sig << " = " << defvalue; } - sig << ")"; - return sig.str(); + if (iarg != nArgs-1) sig << ", "; } - return ""; + sig << ")"; + return sig.str(); } -std::string Cppyy::GetMethodPrototype(TCppScope_t scope, TCppMethod_t method, bool show_formalargs) +std::string Cppyy::GetMethodPrototype(TCppMethod_t method, bool show_formal_args) { - std::string scName = GetScopedFinalName(scope); - TFunction* f = m2f(method); - if (f) { - std::ostringstream sig; - sig << f->GetReturnTypeName() << " " - << scName << "::" << f->GetName(); - sig << GetMethodSignature(method, show_formalargs); - return sig.str(); - } - return ""; + assert(0 && "Unused"); + return ""; // return Cpp::GetFunctionPrototype(method, show_formal_args); } -bool Cppyy::IsConstMethod(TCppMethod_t method) +std::string Cppyy::GetDoxygenComment(TCppScope_t scope, bool strip_markers) { - if (method) { - TFunction* f = m2f(method); - return f->Property() & kIsConstMethod; - } - return false; + return Cpp::GetDoxygenComment(scope, strip_markers); } -Cppyy::TCppIndex_t Cppyy::GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace) +bool Cppyy::IsConstMethod(TCppMethod_t method) { - if (!accept_namespace && IsNamespace(scope)) - return (TCppIndex_t)0; // enforce lazy - - if (scope == GLOBAL_HANDLE) { - TCollection* coll = gROOT->GetListOfFunctionTemplates(); - if (coll) return (TCppIndex_t)coll->GetSize(); - } else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TCollection* coll = cr->GetListOfFunctionTemplates(true); - if (coll) return (TCppIndex_t)coll->GetSize(); - } - } - -// failure ... - return (TCppIndex_t)0; + if (!method) + return false; + return Cpp::IsConstMethod(method); } -std::string Cppyy::GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth) +void Cppyy::GetTemplatedMethods(TCppScope_t scope, std::vector &methods) { - if (scope == (TCppScope_t)GLOBAL_HANDLE) - return ((THashList*)gROOT->GetListOfFunctionTemplates())->At((int)imeth)->GetName(); - else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return cr->GetListOfFunctionTemplates(false)->At((int)imeth)->GetName(); - } + Cpp::GetFunctionTemplatedDecls(scope, methods); +} -// failure ... - assert(!"should not be called unless GetNumTemplatedMethods() succeeded"); - return ""; +Cppyy::TCppIndex_t Cppyy::GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace) +{ + std::vector mc; + Cpp::GetFunctionTemplatedDecls(scope, mc); + return mc.size(); } -bool Cppyy::IsTemplatedConstructor(TCppScope_t scope, TCppIndex_t imeth) +std::string Cppyy::GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth) { - if (scope == (TCppScope_t)GLOBAL_HANDLE) - return false; + std::vector mc; + Cpp::GetFunctionTemplatedDecls(scope, mc); - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunctionTemplate* f = (TFunctionTemplate*)cr->GetListOfFunctionTemplates(false)->At((int)imeth); - return f->ExtraProperty() & kIsConstructor; - } + if (imeth < mc.size()) return GetMethodName(mc[imeth]); - return false; + return ""; } bool Cppyy::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) { - if (scope == (TCppScope_t)GLOBAL_HANDLE) - return (bool)gROOT->GetFunctionTemplate(name.c_str()); - else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return (bool)cr->GetFunctionTemplate(name.c_str()); - } - -// failure ... - return false; + return Cpp::ExistsFunctionTemplate(name, scope); } -bool Cppyy::IsStaticTemplate(TCppScope_t scope, const std::string& name) +bool Cppyy::IsTemplatedMethod(TCppMethod_t method) { - TFunctionTemplate* tf = nullptr; - if (scope == (TCppScope_t)GLOBAL_HANDLE) - tf = gROOT->GetFunctionTemplate(name.c_str()); - else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - tf = cr->GetFunctionTemplate(name.c_str()); - } - - if (!tf) return false; - - return (bool)(tf->Property() & kIsStatic); + return Cpp::IsTemplatedFunction(method); } -bool Cppyy::IsMethodTemplate(TCppScope_t scope, TCppIndex_t idx) +bool Cppyy::IsStaticTemplate(TCppScope_t scope, const std::string& name) { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunction* f = (TFunction*)cr->GetListOfMethods(false)->At((int)idx); - if (f && strstr(f->GetName(), "<")) return true; - return false; - } - - assert(scope == (Cppyy::TCppType_t)GLOBAL_HANDLE); - if (((CallWrapper*)idx)->fName.find('<') != std::string::npos) return true; + if (Cpp::TCppFunction_t tf = GetMethodTemplate(scope, name, "")) + return Cpp::IsStaticMethod(tf); return false; } -// helpers for Cppyy::GetMethodTemplate() -static std::map gMethodTemplates; - -static inline -void remove_space(std::string& n) { - std::string::iterator pos = std::remove_if(n.begin(), n.end(), isspace); - n.erase(pos, n.end()); -} - -static inline -bool template_compare(std::string n1, std::string n2) { - if (n1.back() == '>') n1 = n1.substr(0, n1.size()-1); - remove_space(n1); - remove_space(n2); - return n2.compare(0, n1.size(), n1) == 0; -} - Cppyy::TCppMethod_t Cppyy::GetMethodTemplate( TCppScope_t scope, const std::string& name, const std::string& proto) { -// There is currently no clean way of extracting a templated method out of ROOT/meta -// for a variety of reasons, none of them fundamental. The game played below is to -// first get any pre-existing functions already managed by ROOT/meta, but if that fails, -// to do an explicit lookup that ignores the prototype (i.e. the full name should be -// enough), and finally to ignore the template arguments part of the name as this fails -// in cling if there are default parameters. - TFunction* func = nullptr; ClassInfo_t* cl = nullptr; - if (scope == (TCppScope_t)GLOBAL_HANDLE) { - func = gROOT->GetGlobalFunctionWithPrototype(name.c_str(), proto.c_str()); - if (func && name.back() == '>') { - // make sure that all template parameters match (more are okay, e.g. defaults or - // ones derived from the arguments or variadic templates) - if (!template_compare(name, func->GetName())) - func = nullptr; // happens if implicit conversion matches the overload - } + 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 { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - func = cr->GetMethodWithPrototype(name.c_str(), proto.c_str()); - if (!func) { - cl = cr->GetClassInfo(); - // try base classes to cover a common 'using' case (TODO: this is stupid and misses - // out on base classes; fix that with improved access to Cling) - TCppIndex_t nbases = GetNumBases(scope); - for (TCppIndex_t i = 0; i < nbases; ++i) { - TClassRef& base = type_from_handle(GetScope(GetBaseName(scope, i))); - if (base.GetClass()) { - func = base->GetMethodWithPrototype(name.c_str(), proto.c_str()); - if (func) break; - } - } - } - } + pureName = name; } - if (!func && name.back() == '>' && (cl || scope == (TCppScope_t)GLOBAL_HANDLE)) { - // try again, ignoring proto in case full name is complete template - auto declid = gInterpreter->GetFunction(cl, name.c_str()); - if (declid) { - auto existing = gMethodTemplates.find(declid); - if (existing == gMethodTemplates.end()) { - auto cw = new_CallWrapper(declid, name); - existing = gMethodTemplates.insert(std::make_pair(declid, cw)).first; - } - return (TCppMethod_t)existing->second; - } + std::vector unresolved_candidate_methods; + Cpp::GetClassTemplatedMethods(pureName, scope, + unresolved_candidate_methods); + if (unresolved_candidate_methods.empty() && name.find("operator") == 0) { + // try operators + Cppyy::GetClassOperators(scope, pureName, unresolved_candidate_methods); } - if (func) { - // make sure we didn't match a non-templated overload - if (func->ExtraProperty() & kIsTemplateSpec) - return (TCppMethod_t)new_CallWrapper(func); + // CPyCppyy assumes that we attempt instantiation here + std::vector arg_types; + std::vector templ_params; + Cppyy::AppendTypesSlow(proto, arg_types, scope); + Cppyy::AppendTypesSlow(explicit_params, templ_params, scope); - // disregard this non-templated method as it will be considered when appropriate - return (TCppMethod_t)nullptr; - } + Cppyy::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( + unresolved_candidate_methods, templ_params, arg_types); -// try again with template arguments removed from name, if applicable - if (name.back() == '>') { - auto pos = name.find('<'); - if (pos != std::string::npos) { - TCppMethod_t cppmeth = GetMethodTemplate(scope, name.substr(0, pos), proto); - if (cppmeth) { - // allow if requested template names match up to the result - const std::string& alt = GetMethodFullName(cppmeth); - if (name.size() < alt.size() && alt.find('<') == pos) { - if (template_compare(name, alt)) - return cppmeth; - } - } - } - } + if (!cppmeth && unresolved_candidate_methods.size() == 1 && + !templ_params.empty()) + cppmeth = Cpp::InstantiateTemplate( + unresolved_candidate_methods[0], templ_params.data(), + templ_params.size(), /*instantiate_body=*/false); + + return cppmeth; + + // if it fails, use Sema to propogate info about why it failed (DeductionInfo) -// failure ... - return (TCppMethod_t)nullptr; } -static inline -std::string type_remap(const std::string& n1, const std::string& n2) -{ -// Operator lookups of (C++ string, Python str) should succeed for the combos of -// string/str, wstring/str, string/unicode and wstring/unicode; since C++ does not have a -// operator+(std::string, std::wstring), we'll have to look up the same type and rely on -// the converters in CPyCppyy/_cppyy. - if (n1 == "str" || n1 == "unicode") { - if (n2 == "std::basic_string,std::allocator >") - return n2; // match like for like - return "std::string"; // probably best bet +static inline std::string type_remap(const std::string& n1, + const std::string& n2) { + // Operator lookups of (C++ string, Python str) should succeed for the + // combos of string/str, wstring/str, string/unicode and wstring/unicode; + // since C++ does not have a operator+(std::string, std::wstring), we'll + // have to look up the same type and rely on the converters in + // CPyCppyy/_cppyy. + if (n1 == "str" || n1 == "unicode" || n1 == "std::basic_string") { + if (n2 == "std::basic_string") + return "std::basic_string&"; // match like for like + return "std::basic_string&"; // probably best bet + } else if (n1 == "std::basic_string") { + return "std::basic_string&"; } else if (n1 == "float") { - return "double"; // debatable, but probably intended + return "double"; // debatable, but probably intended } else if (n1 == "complex") { return "std::complex"; } return n1; } -Cppyy::TCppIndex_t Cppyy::GetGlobalOperator( +void Cppyy::GetClassOperators(Cppyy::TCppScope_t klass, + const std::string& opname, + std::vector& operators) { + std::string op = opname.substr(8); + Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators, + /*kind=*/Cpp::OperatorArity::kBoth); +} + +Cppyy::TCppMethod_t Cppyy::GetGlobalOperator( TCppType_t scope, const std::string& lc, const std::string& rc, const std::string& opname) { -// Find a global operator function with a matching signature; prefer by-ref, but -// fall back on by-value if that fails. - std::string lcname1 = TClassEdit::CleanType(lc.c_str()); - const std::string& rcname = rc.empty() ? rc : type_remap(TClassEdit::CleanType(rc.c_str()), lcname1); - const std::string& lcname = type_remap(lcname1, rcname); - - std::string proto = lcname + "&" + (rc.empty() ? rc : (", " + rcname + "&")); - if (scope == (TCppScope_t)GLOBAL_HANDLE) { - TFunction* func = gROOT->GetGlobalFunctionWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)new_CallWrapper(func); - proto = lcname + (rc.empty() ? rc : (", " + rcname)); - func = gROOT->GetGlobalFunctionWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)new_CallWrapper(func); - } else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TFunction* func = cr->GetMethodWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)cr->GetListOfMethods()->IndexOf(func); - proto = lcname + (rc.empty() ? rc : (", " + rcname)); - func = cr->GetMethodWithPrototype(opname.c_str(), proto.c_str()); - if (func) return (TCppIndex_t)cr->GetListOfMethods()->IndexOf(func); - } + std::string rc_type = type_remap(rc, lc); + std::string lc_type = type_remap(lc, rc); + bool is_templated = false; + if ((lc_type.find('<') != std::string::npos) || + (rc_type.find('<') != std::string::npos)) { + is_templated = true; } -// failure ... - return (TCppIndex_t)-1; -} + std::vector overloads; + Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads, + /*kind=*/Cpp::OperatorArity::kBoth); -// method properties --------------------------------------------------------- + std::vector unresolved_candidate_methods; + for (auto overload: overloads) { + if (Cpp::IsTemplatedFunction(overload)) { + unresolved_candidate_methods.push_back(overload); + continue; + } else { + TCppType_t lhs_type = Cpp::GetFunctionArgType(overload, 0); + if (lc_type != + Cpp::GetTypeAsString(Cpp::GetUnderlyingType(lhs_type))) + continue; -static inline bool testMethodProperty(Cppyy::TCppMethod_t method, EProperty prop) -{ - if (!method) - return false; - TFunction *f = m2f(method); - return f->Property() & prop; + if (!rc_type.empty()) { + if (Cpp::GetFunctionNumArgs(overload) != 2) + continue; + TCppType_t rhs_type = Cpp::GetFunctionArgType(overload, 1); + if (rc_type != + Cpp::GetTypeAsString(Cpp::GetUnderlyingType(rhs_type))) + continue; + } + return overload; + } + } + if (is_templated) { + std::string lc_template = lc_type.substr( + lc_type.find("<") + 1, lc_type.rfind(">") - lc_type.find("<") - 1); + std::string rc_template = rc_type.substr( + rc_type.find("<") + 1, rc_type.rfind(">") - rc_type.find("<") - 1); + + std::vector arg_types; + if (auto l = Cppyy::GetType(lc_type, true)) + arg_types.emplace_back(l); + else + return nullptr; + + if (!rc_type.empty()) { + if (auto r = Cppyy::GetType(rc_type, true)) + arg_types.emplace_back(r); + else + return nullptr; + } + Cppyy::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( + unresolved_candidate_methods, {}, arg_types); + if (cppmeth) + return cppmeth; + } + { + // we are trying to do a madeup IntegralToFloating implicit cast emulating clang + bool flag = false; + if (rc_type == "int") { + rc_type = "double"; + flag = true; + } + if (lc_type == "int") { + lc_type = "double"; + flag = true; + } + if (flag) + return GetGlobalOperator(scope, lc_type, rc_type, opname); + } + return nullptr; } -static inline bool testMethodExtraProperty(Cppyy::TCppMethod_t method, EFunctionProperty prop) +// // method properties --------------------------------------------------------- +bool Cppyy::IsDeletedMethod(TCppMethod_t method) { - if (!method) - return false; - TFunction *f = m2f(method); - return f->ExtraProperty() & prop; + return Cpp::IsFunctionDeleted(method); } bool Cppyy::IsPublicMethod(TCppMethod_t method) { - return testMethodProperty(method, kIsPublic); + return Cpp::IsPublicMethod(method); } bool Cppyy::IsProtectedMethod(TCppMethod_t method) { - return testMethodProperty(method, kIsProtected); + return Cpp::IsProtectedMethod(method); +} + +bool Cppyy::IsPrivateMethod(TCppMethod_t method) +{ + return Cpp::IsPrivateMethod(method); } bool Cppyy::IsConstructor(TCppMethod_t method) { - return testMethodExtraProperty(method, kIsConstructor); + return Cpp::IsConstructor(method); } bool Cppyy::IsDestructor(TCppMethod_t method) { - return testMethodExtraProperty(method, kIsDestructor); + return Cpp::IsDestructor(method); } bool Cppyy::IsStaticMethod(TCppMethod_t method) { - return testMethodProperty(method, kIsStatic); + return Cpp::IsStaticMethod(method); } bool Cppyy::IsExplicit(TCppMethod_t method) { - return testMethodProperty(method, kIsExplicit); -} + return Cpp::IsExplicit(method); +} + +// +// // data member reflection information ---------------------------------------- +// Cppyy::TCppIndex_t Cppyy::GetNumDatamembers(TCppScope_t scope, bool accept_namespace) +// { +// if (!accept_namespace && IsNamespace(scope)) +// return (TCppIndex_t)0; // enforce lazy +// +// if (scope == GLOBAL_HANDLE) +// return gROOT->GetListOfGlobals(true)->GetSize(); +// +// TClassRef& cr = type_from_handle(scope); +// if (cr.GetClass() && cr->GetListOfDataMembers()) +// return cr->GetListOfDataMembers()->GetSize(); +// +// return (TCppIndex_t)0; // unknown class? +// } -// data member reflection information ---------------------------------------- -Cppyy::TCppIndex_t Cppyy::GetNumDatamembers(TCppScope_t scope, bool accept_namespace) +void Cppyy::GetDatamembers(TCppScope_t scope, std::vector& datamembers) { - if (!accept_namespace && IsNamespace(scope)) - return (TCppIndex_t)0; // enforce lazy - - if (scope == GLOBAL_HANDLE) - return gROOT->GetListOfGlobals(true)->GetSize(); - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass() && cr->GetListOfDataMembers()) - return cr->GetListOfDataMembers()->GetSize(); - - return (TCppIndex_t)0; // unknown class? + Cpp::GetDatamembers(scope, datamembers); + Cpp::GetStaticDatamembers(scope, datamembers); + Cpp::GetEnumConstantDatamembers(scope, datamembers, false); } -std::string Cppyy::GetDatamemberName(TCppScope_t scope, TCppIndex_t idata) -{ - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->GetName(); - } - assert(scope == GLOBAL_HANDLE); - TGlobal* gbl = g_globalvars[idata]; - return gbl->GetName(); +bool Cppyy::CheckDatamember(TCppScope_t scope, const std::string& name) { + return (bool) Cpp::LookupDatamember(name, scope); } -static inline -int count_scopes(const std::string& tpname) -{ - int count = 0; - std::string::size_type pos = tpname.find("::", 0); - while (pos != std::string::npos) { - count++; - pos = tpname.find("::", pos+1); - } - return count; -} - -std::string Cppyy::GetDatamemberType(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - std::string fullType = gbl->GetFullTypeName(); - - if ((int)gbl->GetArrayDim()) { - std::ostringstream s; - for (int i = 0; i < (int)gbl->GetArrayDim(); ++i) - s << '[' << gbl->GetMaxIndex(i) << ']'; - fullType.append(s.str()); - } - return fullType; - } - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - // TODO: fix this upstream ... Usually, we want m->GetFullTypeName(), because it - // does not resolve typedefs, but it looses scopes for inner classes/structs, it - // doesn't resolve constexpr (leaving unresolved names), leaves spurious "struct" - // or "union" in the name, and can not handle anonymous unions. In that case - // m->GetTrueTypeName() should be used. W/o clear criteria to determine all these - // cases, the general rules are to prefer the true name if the full type does not - // exist as a type for classes, and the most scoped name otherwise. - const char* ft = m->GetFullTypeName(); std::string fullType = ft ? ft : ""; - const char* tn = m->GetTrueTypeName(); std::string trueName = tn ? tn : ""; - if (!trueName.empty() && fullType != trueName && !IsBuiltin(trueName)) { - if ( (!TClass::GetClass(fullType.c_str()) && TClass::GetClass(trueName.c_str())) || \ - (count_scopes(trueName) > count_scopes(fullType)) ) { - bool is_enum_tag = fullType.rfind("enum ", 0) != std::string::npos; - fullType = trueName; - if (is_enum_tag) - fullType.insert(fullType.rfind("const ", 0) == std::string::npos ? 0 : 6, "enum "); - } - } - - if ((int)m->GetArrayDim()) { - std::ostringstream s; - for (int i = 0; i < (int)m->GetArrayDim(); ++i) - s << '[' << m->GetMaxIndex(i) << ']'; - fullType.append(s.str()); - } - -#if 0 - // this is the only place where anonymous structs are uniquely identified, so setup - // a class if needed, such that subsequent GetScope() and GetScopedFinalName() calls - // return the uniquely named class - auto declid = m->GetTagDeclId(); //GetDeclId(); - if (declid && (m->Property() & (kIsClass | kIsStruct | kIsUnion)) &&\ - (fullType.find("(anonymous)") != std::string::npos || fullType.find("(unnamed)") != std::string::npos)) { - - // use the (fixed) decl id address to guarantee a unique name, even when there - // are multiple anonymous structs in the parent scope - std::ostringstream fulls; - fulls << fullType << "@" << (void*)declid; - fullType = fulls.str(); - - if (g_name2classrefidx.find(fullType) == g_name2classrefidx.end()) { - ClassInfo_t* ci = gInterpreter->ClassInfo_Factory(declid); - TClass* cl = gInterpreter->GenerateTClass(ci, kTRUE /* silent */); - gInterpreter->ClassInfo_Delete(ci); - if (cl) cl->SetName(fullType.c_str()); - g_name2classrefidx[fullType] = g_classrefs.size(); - g_classrefs.emplace_back(cl); - } - } -#endif - return fullType; - } - - return ""; -} - -intptr_t Cppyy::GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata) +bool Cppyy::IsLambdaClass(TCppType_t type) { + return Cpp::IsLambdaClass(type); +} + +Cppyy::TCppScope_t Cppyy::WrapLambdaFromVariable(TCppScope_t var) { + std::ostringstream code; + std::string name = Cppyy::GetFinalName(var); + code << "namespace __cppyy_internal_wrap_g {\n" + << " " << "std::function " << name << " = ::" << Cpp::GetQualifiedName(var) << ";\n" + << "}\n"; + + if (Cppyy::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppyy_internal_wrap_g", /*parent=*/nullptr)); + if (res) return res; + } + return var; +} + +Cppyy::TCppScope_t Cppyy::AdaptFunctionForLambdaReturn(TCppScope_t fn) { + std::string fn_name = Cpp::GetQualifiedCompleteName(fn); + std::string signature = Cppyy::GetMethodSignature(fn, true); + + std::ostringstream call; + call << "("; + for (size_t i = 0, n = Cppyy::GetMethodNumArgs(fn); i < n; i++) { + call << Cppyy::GetMethodArgName(fn, i); + if (i != n - 1) + call << ", "; + } + call << ")"; + + std::ostringstream code; + static int i = 0; + std::string name = "lambda_return_convert_" + std::to_string(++i); + code << "namespace __cppyy_internal_wrap_g {\n" + << "auto " << name << signature << "{" << "return std::function(" << fn_name << call.str() << "); }\n" + << "}\n"; + if (Cppyy::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppyy_internal_wrap_g", /*parent=*/nullptr)); + if (res) return res; + } + return fn; +} + +// std::string Cppyy::GetDatamemberName(TCppScope_t scope, TCppIndex_t idata) +// { +// TClassRef& cr = type_from_handle(scope); +// if (cr.GetClass()) { +// TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); +// return m->GetName(); +// } +// assert(scope == GLOBAL_HANDLE); +// TGlobal* gbl = g_globalvars[idata]; +// return gbl->GetName(); +// } +// +// static inline +// int count_scopes(const std::string& tpname) +// { +// int count = 0; +// std::string::size_type pos = tpname.find("::", 0); +// while (pos != std::string::npos) { +// count++; +// pos = tpname.find("::", pos+1); +// } +// return count; +// } + +Cppyy::TCppType_t Cppyy::GetDatamemberType(TCppScope_t var) +{ + return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var)); +} + +std::string Cppyy::GetDatamemberTypeAsString(TCppScope_t scope) +{ + return Cpp::GetTypeAsString( + Cpp::GetVariableType(Cpp::GetUnderlyingScope(scope))); +} + +std::string Cppyy::GetTypeAsString(TCppType_t type) +{ + return Cpp::GetTypeAsString(type); +} + +intptr_t Cppyy::GetDatamemberOffset(TCppScope_t var, TCppScope_t klass) +{ + return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass); +} + +// static inline +// Cppyy::TCppIndex_t gb2idx(TGlobal* gb) +// { +// if (!gb) return (Cppyy::TCppIndex_t)-1; +// +// auto pidx = g_globalidx.find(gb); +// if (pidx == g_globalidx.end()) { +// auto idx = g_globalvars.size(); +// g_globalvars.push_back(gb); +// g_globalidx[gb] = idx; +// return (Cppyy::TCppIndex_t)idx; +// } +// return (Cppyy::TCppIndex_t)pidx->second; +// } +// +// Cppyy::TCppIndex_t Cppyy::GetDatamemberIndex(TCppScope_t scope, const std::string& name) +// { +// if (scope == GLOBAL_HANDLE) { +// TGlobal* gb = (TGlobal*)gROOT->GetListOfGlobals(false [> load <])->FindObject(name.c_str()); +// if (!gb) gb = (TGlobal*)gROOT->GetListOfGlobals(true [> load <])->FindObject(name.c_str()); +// if (!gb) { +// // some enums are not loaded as they are not considered part of +// // the global scope, but of the enum scope; get them w/o checking +// TDictionary::DeclId_t did = gInterpreter->GetDataMember(nullptr, name.c_str()); +// if (did) { +// DataMemberInfo_t* t = gInterpreter->DataMemberInfo_Factory(did, nullptr); +// ((TListOfDataMembers*)gROOT->GetListOfGlobals())->Get(t, true); +// gb = (TGlobal*)gROOT->GetListOfGlobals(false [> load <])->FindObject(name.c_str()); +// } +// } +// +// if (gb && strcmp(gb->GetFullTypeName(), "(lambda)") == 0) { +// // lambdas use a compiler internal closure type, so we wrap +// // them, then return the wrapper's type +// // TODO: this current leaks the std::function; also, if possible, +// // should instantiate through TClass rather then ProcessLine +// std::ostringstream s; +// s << "auto __cppyy_internal_wrap_" << name << " = " +// "new __cling_internal::FT::F" +// "{" << name << "};"; +// gInterpreter->ProcessLine(s.str().c_str()); +// TGlobal* wrap = (TGlobal*)gROOT->GetListOfGlobals(true)->FindObject( +// ("__cppyy_internal_wrap_"+name).c_str()); +// if (wrap && wrap->GetAddress()) gb = wrap; +// } +// +// return gb2idx(gb); +// +// } else { +// TClassRef& cr = type_from_handle(scope); +// if (cr.GetClass()) { +// TDataMember* dm = +// (TDataMember*)cr->GetListOfDataMembers()->FindObject(name.c_str()); +// // TODO: turning this into an index is silly ... +// if (dm) return (TCppIndex_t)cr->GetListOfDataMembers()->IndexOf(dm); +// } +// } +// +// return (TCppIndex_t)-1; +// } +// +// Cppyy::TCppIndex_t Cppyy::GetDatamemberIndexEnumerated(TCppScope_t scope, TCppIndex_t idata) +// { +// if (scope == GLOBAL_HANDLE) { +// TGlobal* gb = (TGlobal*)((THashList*)gROOT->GetListOfGlobals(false [> load <]))->At((int)idata); +// return gb2idx(gb); +// } +// +// return idata; +// } + +// data member properties ---------------------------------------------------- +bool Cppyy::IsPublicData(TCppScope_t datamem) { - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - if (!gbl->GetAddress() || gbl->GetAddress() == (void*)-1) { - // CLING WORKAROUND: make sure variable is loaded - intptr_t addr = (intptr_t)gInterpreter->ProcessLine((std::string("&")+gbl->GetName()+";").c_str()); - if (gbl->GetAddress() && gbl->GetAddress() != (void*)-1) - return (intptr_t)gbl->GetAddress(); // now loaded! - return addr; // last resort ... - } - return (intptr_t)gbl->GetAddress(); - } - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - // CLING WORKAROUND: the following causes templates to be instantiated first within the proper - // scope, making the lookup succeed and preventing spurious duplicate instantiations later. Also, - // if the variable is not yet loaded, pull it in through gInterpreter. - intptr_t offset = (intptr_t)-1; - if (m->Property() & kIsStatic) { - if (strchr(cr->GetName(), '<')) - gInterpreter->ProcessLine(((std::string)cr->GetName()+"::"+m->GetName()+";").c_str()); - offset = (intptr_t)m->GetOffsetCint(); // yes, Cling (GetOffset() is both wrong - // and caches that wrong result! - if (offset == (intptr_t)-1) - return (intptr_t)gInterpreter->ProcessLine((std::string("&")+cr->GetName()+"::"+m->GetName()+";").c_str()); - } else - offset = (intptr_t)m->GetOffsetCint(); // yes, Cling, see above - return offset; - } - - return (intptr_t)-1; + return Cpp::IsPublicVariable(datamem); } -static inline -Cppyy::TCppIndex_t gb2idx(TGlobal* gb) +bool Cppyy::IsProtectedData(TCppScope_t datamem) { - if (!gb) return (Cppyy::TCppIndex_t)-1; - - auto pidx = g_globalidx.find(gb); - if (pidx == g_globalidx.end()) { - auto idx = g_globalvars.size(); - g_globalvars.push_back(gb); - g_globalidx[gb] = idx; - return (Cppyy::TCppIndex_t)idx; - } - return (Cppyy::TCppIndex_t)pidx->second; -} - -Cppyy::TCppIndex_t Cppyy::GetDatamemberIndex(TCppScope_t scope, const std::string& name) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gb = (TGlobal*)gROOT->GetListOfGlobals(false /* load */)->FindObject(name.c_str()); - if (!gb) gb = (TGlobal*)gROOT->GetListOfGlobals(true /* load */)->FindObject(name.c_str()); - if (!gb) { - // some enums are not loaded as they are not considered part of - // the global scope, but of the enum scope; get them w/o checking - TDictionary::DeclId_t did = gInterpreter->GetDataMember(nullptr, name.c_str()); - if (did) { - DataMemberInfo_t* t = gInterpreter->DataMemberInfo_Factory(did, nullptr); - ((TListOfDataMembers*)gROOT->GetListOfGlobals())->Get(t, true); - gb = (TGlobal*)gROOT->GetListOfGlobals(false /* load */)->FindObject(name.c_str()); - } - } - - if (gb && strcmp(gb->GetFullTypeName(), "(lambda)") == 0) { - // lambdas use a compiler internal closure type, so we wrap - // them, then return the wrapper's type - // TODO: this current leaks the std::function; also, if possible, - // should instantiate through TClass rather then ProcessLine - std::ostringstream s; - s << "auto __cppyy_internal_wrap_" << name << " = " - "new __cling_internal::FT::F" - "{" << name << "};"; - gInterpreter->ProcessLine(s.str().c_str()); - TGlobal* wrap = (TGlobal*)gROOT->GetListOfGlobals(true)->FindObject( - ("__cppyy_internal_wrap_"+name).c_str()); - if (wrap && wrap->GetAddress()) gb = wrap; - } - - return gb2idx(gb); - - } else { - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* dm = - (TDataMember*)cr->GetListOfDataMembers()->FindObject(name.c_str()); - // TODO: turning this into an index is silly ... - if (dm) return (TCppIndex_t)cr->GetListOfDataMembers()->IndexOf(dm); - } - } - - return (TCppIndex_t)-1; + return Cpp::IsProtectedVariable(datamem); } -Cppyy::TCppIndex_t Cppyy::GetDatamemberIndexEnumerated(TCppScope_t scope, TCppIndex_t idata) +bool Cppyy::IsPrivateData(TCppScope_t datamem) { - if (scope == GLOBAL_HANDLE) { - TGlobal* gb = (TGlobal*)((THashList*)gROOT->GetListOfGlobals(false /* load */))->At((int)idata); - return gb2idx(gb); - } - - return idata; + return Cpp::IsPrivateVariable(datamem); } - -// data member properties ---------------------------------------------------- -bool Cppyy::IsPublicData(TCppScope_t scope, TCppIndex_t idata) +bool Cppyy::IsStaticDatamember(TCppScope_t var) { - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr->Property() & kIsNamespace) - return true; - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->Property() & kIsPublic; + return Cpp::IsStaticVariable(Cpp::GetUnderlyingScope(var)); } -bool Cppyy::IsProtectedData(TCppScope_t scope, TCppIndex_t idata) +bool Cppyy::IsConstVar(TCppScope_t var) { - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr->Property() & kIsNamespace) - return true; - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->Property() & kIsProtected; + return Cpp::IsConstVariable(var); } -bool Cppyy::IsStaticData(TCppScope_t scope, TCppIndex_t idata) -{ - if (scope == GLOBAL_HANDLE) - return true; - TClassRef& cr = type_from_handle(scope); - if (cr->Property() & kIsNamespace) - return true; - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->Property() & kIsStatic; -} +Cppyy::TCppScope_t Cppyy::ReduceReturnType(TCppScope_t fn, TCppType_t reduce) { + std::string fn_name = Cpp::GetQualifiedCompleteName(fn); + std::string signature = Cppyy::GetMethodSignature(fn, true); + std::string result_type = Cppyy::GetTypeAsString(reduce); -bool Cppyy::IsConstData(TCppScope_t scope, TCppIndex_t idata) -{ - Long_t property = 0; - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - property = gbl->Property(); + std::ostringstream call; + call << "("; + for (size_t i = 0, n = Cppyy::GetMethodNumArgs(fn); i < n; i++) { + call << Cppyy::GetMethodArgName(fn, i); + if (i != n - 1) + call << ", "; } - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - property = m->Property(); + call << ")"; + + std::ostringstream code; + static int i = 0; + std::string name = "reduced_function_" + std::to_string(++i); + code << "namespace __cppyy_internal_wrap_g {\n" + << result_type << " " << name << signature << "{" << "return (" << result_type << ")::" << fn_name << call.str() << "; }\n" + << "}\n"; + if (Cppyy::Compile(code.str().c_str())) { + TCppScope_t res = Cpp::GetNamed( + name, Cpp::GetScope("__cppyy_internal_wrap_g", /*parent=*/nullptr)); + if (res) return res; } - -// if the data type is const, but the data member is a pointer/array, the data member -// itself is not const; alternatively it is a pointer that is constant - return ((property & kIsConstant) && !(property & (kIsPointer | kIsArray))) || (property & kIsConstPointer); + return fn; } -bool Cppyy::IsEnumData(TCppScope_t scope, TCppIndex_t idata) -{ -// TODO: currently, ROOT/meta does not properly distinguish between variables of enum -// type, and values of enums. The latter are supposed to be const. This code relies on -// odd features (bugs?) to figure out the difference, but this should really be fixed -// upstream and/or deserves a new API. +// bool Cppyy::IsEnumData(TCppScope_t scope, TCppIndex_t idata) +// { +// // TODO: currently, ROOT/meta does not properly distinguish between variables of enum +// // type, and values of enums. The latter are supposed to be const. This code relies on +// // odd features (bugs?) to figure out the difference, but this should really be fixed +// // upstream and/or deserves a new API. - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; +// if (scope == GLOBAL_HANDLE) { +// TGlobal* gbl = g_globalvars[idata]; - // make use of an oddity: enum global variables do not have their kIsStatic bit - // set, whereas enum global values do - return (gbl->Property() & kIsEnum) && (gbl->Property() & kIsStatic); - } +// // make use of an oddity: enum global variables do not have their kIsStatic bit +// // set, whereas enum global values do +// return (gbl->Property() & kIsEnum) && (gbl->Property() & kIsStatic); +// } - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - std::string ti = m->GetTypeName(); - - // can't check anonymous enums by type name, so just accept them as enums - if (ti.rfind("(anonymous)") != std::string::npos || ti.rfind("(unnamed)") != std::string::npos) - return m->Property() & kIsEnum; - - // since there seems to be no distinction between data of enum type and enum values, - // check the list of constants for the type to see if there's a match - if (ti.rfind(cr->GetName(), 0) != std::string::npos) { - std::string::size_type s = strlen(cr->GetName())+2; - if (s < ti.size()) { - TEnum* ee = ((TListOfEnums*)cr->GetListOfEnums())->GetObject(ti.substr(s, std::string::npos).c_str()); - if (ee) return ee->GetConstant(m->GetName()); - } - } - } +// TClassRef& cr = type_from_handle(scope); +// if (cr.GetClass()) { +// TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); +// std::string ti = m->GetTypeName(); -// this default return only means that the data will be writable, not that it will -// be unreadable or otherwise misrepresented - return false; -} +// // can't check anonymous enums by type name, so just accept them as enums +// if (ti.rfind("(anonymous)") != std::string::npos) +// return m->Property() & kIsEnum; -int Cppyy::GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension) -{ - if (scope == GLOBAL_HANDLE) { - TGlobal* gbl = g_globalvars[idata]; - return gbl->GetMaxIndex(dimension); - } - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - return m->GetMaxIndex(dimension); - } - return -1; -} +// // since there seems to be no distinction between data of enum type and enum values, +// // check the list of constants for the type to see if there's a match +// if (ti.rfind(cr->GetName(), 0) != std::string::npos) { +// std::string::size_type s = strlen(cr->GetName())+2; +// if (s < ti.size()) { +// TEnum* ee = ((TListOfEnums*)cr->GetListOfEnums())->GetObject(ti.substr(s, std::string::npos).c_str()); +// if (ee) return ee->GetConstant(m->GetName()); +// } +// } +// } +// // this default return only means that the data will be writable, not that it will +// // be unreadable or otherwise misrepresented +// return false; +// } -// enum properties ----------------------------------------------------------- -Cppyy::TCppEnum_t Cppyy::GetEnum(TCppScope_t scope, const std::string& enum_name) +std::vector Cppyy::GetDimensions(TCppType_t type) { - if (scope == GLOBAL_HANDLE) - return (TCppEnum_t)gROOT->GetListOfEnums(kTRUE)->FindObject(enum_name.c_str()); - - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) - return (TCppEnum_t)cr->GetListOfEnums(kTRUE)->FindObject(enum_name.c_str()); - - return (TCppEnum_t)0; + return Cpp::GetDimensions(type); } -Cppyy::TCppIndex_t Cppyy::GetNumEnumData(TCppEnum_t etype) +// enum properties ----------------------------------------------------------- +std::vector Cppyy::GetEnumConstants(TCppScope_t scope) { - return (TCppIndex_t)((TEnum*)etype)->GetConstants()->GetSize(); + return Cpp::GetEnumConstants(scope); } -std::string Cppyy::GetEnumDataName(TCppEnum_t etype, TCppIndex_t idata) +Cppyy::TCppType_t Cppyy::GetEnumConstantType(TCppScope_t scope) { - return ((TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata))->GetName(); + return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope)); } -long long Cppyy::GetEnumDataValue(TCppEnum_t etype, TCppIndex_t idata) +Cppyy::TCppIndex_t Cppyy::GetEnumDataValue(TCppScope_t scope) { - TEnumConstant* ecst = (TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata); - return (long long)ecst->GetValue(); + return Cpp::GetEnumConstantValue(scope); } - +// std::string Cppyy::GetEnumDataName(TCppEnum_t etype, TCppIndex_t idata) +// { +// return ((TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata))->GetName(); +// } +// +// long long Cppyy::GetEnumDataValue(TCppEnum_t etype, TCppIndex_t idata) +// { +// TEnumConstant* ecst = (TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata); +// return (long long)ecst->GetValue(); +// } + +Cppyy::TCppScope_t Cppyy::InstantiateTemplate( + TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size) +{ + return Cpp::InstantiateTemplate(tmpl, args, args_size, + /*instantiate_body=*/false); +} + +void Cppyy::DumpScope(TCppScope_t scope) +{ + Cpp::DumpScope(scope); +} + +//- C-linkage wrappers ------------------------------------------------------- + +extern "C" { +// direct interpreter access ---------------------------------------------- +int cppyy_compile(const char* code) { + return Cppyy::Compile(code); +} + +int cppyy_compile_silent(const char* code) { + return Cppyy::Compile(code, true /* silent */); +} + +char* cppyy_to_string(cppyy_type_t klass, cppyy_object_t obj) { + return cppstring_to_cstring(Cppyy::ToString((Cppyy::TCppType_t) klass, obj)); +} + + +// name to opaque C++ scope representation -------------------------------- +// char* cppyy_resolve_name(const char* cppitem_name) { +// return cppstring_to_cstring(Cppyy::ResolveName(cppitem_name)); +// } + +// char* cppyy_resolve_enum(const char* enum_type) { +// return cppstring_to_cstring(Cppyy::ResolveEnum(enum_type)); +// } + +cppyy_scope_t cppyy_get_scope(const char* scope_name) { + return cppyy_scope_t(Cppyy::GetScope(scope_name)); +} +// +// cppyy_type_t cppyy_actual_class(cppyy_type_t klass, cppyy_object_t obj) { +// return cppyy_type_t(Cppyy::GetActualClass(klass, (void*)obj)); +// } + +size_t cppyy_size_of_klass(cppyy_type_t klass) { + return Cppyy::SizeOf((Cppyy::TCppType_t) klass); +} + +// size_t cppyy_size_of_type(const char* type_name) { +// return Cppyy::SizeOf(type_name); +// } +// +// int cppyy_is_builtin(const char* type_name) { +// return (int)Cppyy::IsBuiltin(type_name); +// } +// +// int cppyy_is_complete(const char* type_name) { +// return (int)Cppyy::IsComplete(type_name); +// } +// +// +// [> memory management ------------------------------------------------------ <] +// cppyy_object_t cppyy_allocate(cppyy_scope_t type) { +// return cppyy_object_t(Cppyy::Allocate(type)); +// } +// +// void cppyy_deallocate(cppyy_scope_t type, cppyy_object_t self) { +// Cppyy::Deallocate(type, (void*)self); +// } +// +// cppyy_object_t cppyy_construct(cppyy_type_t type) { +// return (cppyy_object_t)Cppyy::Construct(type); +// } +// +// void cppyy_destruct(cppyy_type_t type, cppyy_object_t self) { +// Cppyy::Destruct(type, (void*)self); +// } +// +// +// [> method/function dispatching -------------------------------------------- <] +// [> Exception types: +// 1: default (unknown exception) +// 2: standard exception +// */ +// #define CPPYY_HANDLE_EXCEPTION \ +// catch (std::exception& e) { \ +// cppyy_exctype_t* etype = (cppyy_exctype_t*)((Parameter*)args+nargs); \ +// *etype = (cppyy_exctype_t)2; \ +// *((char**)(etype+1)) = cppstring_to_cstring(e.what()); \ +// } \ +// catch (...) { \ +// cppyy_exctype_t* etype = (cppyy_exctype_t*)((Parameter*)args+nargs); \ +// *etype = (cppyy_exctype_t)1; \ +// *((char**)(etype+1)) = \ +// cppstring_to_cstring("unhandled, unknown C++ exception"); \ +// } +// +// void cppyy_call_v(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// Cppyy::CallV(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// } +// +// unsigned char cppyy_call_b(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (unsigned char)Cppyy::CallB(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (unsigned char)-1; +// } +// +// char cppyy_call_c(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (char)Cppyy::CallC(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (char)-1; +// } +// +// short cppyy_call_h(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (short)Cppyy::CallH(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (short)-1; +// } +// +// int cppyy_call_i(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (int)Cppyy::CallI(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (int)-1; +// } +// +// long cppyy_call_l(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (long)Cppyy::CallL(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (long)-1; +// } +// +// long long cppyy_call_ll(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (long long)Cppyy::CallLL(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (long long)-1; +// } +// +// float cppyy_call_f(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (float)Cppyy::CallF(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (float)-1; +// } +// +// double cppyy_call_d(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (double)Cppyy::CallD(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (double)-1; +// } +// +// long double cppyy_call_ld(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (long double)Cppyy::CallLD(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (long double)-1; +// } +// +// double cppyy_call_nld(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// return (double)cppyy_call_ld(method, self, nargs, args); +// } +// +// void* cppyy_call_r(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { +// try { +// return (void*)Cppyy::CallR(method, (void*)self, nargs, args); +// } CPPYY_HANDLE_EXCEPTION +// return (void*)nullptr; +// } +// +// char* cppyy_call_s( +// cppyy_method_t method, cppyy_object_t self, int nargs, void* args, size_t* lsz) { +// try { +// return Cppyy::CallS(method, (void*)self, nargs, args, lsz); +// } CPPYY_HANDLE_EXCEPTION +// return (char*)nullptr; +// } +// +// cppyy_object_t cppyy_constructor( +// cppyy_method_t method, cppyy_type_t klass, int nargs, void* args) { +// try { +// return cppyy_object_t(Cppyy::CallConstructor(method, klass, nargs, args)); +// } CPPYY_HANDLE_EXCEPTION +// return (cppyy_object_t)0; +// } +// +// void cppyy_destructor(cppyy_type_t klass, cppyy_object_t self) { +// Cppyy::CallDestructor(klass, self); +// } +// +// cppyy_object_t cppyy_call_o(cppyy_method_t method, cppyy_object_t self, +// int nargs, void* args, cppyy_type_t result_type) { +// try { +// return cppyy_object_t(Cppyy::CallO(method, (void*)self, nargs, args, result_type)); +// } CPPYY_HANDLE_EXCEPTION +// return (cppyy_object_t)0; +// } +// +// cppyy_funcaddr_t cppyy_function_address(cppyy_method_t method) { +// return cppyy_funcaddr_t(Cppyy::GetFunctionAddress(method, true)); +// } +// +// +// [> handling of function argument buffer ----------------------------------- <] +// void* cppyy_allocate_function_args(int nargs) { +// // for calls through C interface, require extra space for reporting exceptions +// return malloc(nargs*sizeof(Parameter)+sizeof(cppyy_exctype_t)+sizeof(char**)); +// } +// +// void cppyy_deallocate_function_args(void* args) { +// free(args); +// } +// +// size_t cppyy_function_arg_sizeof() { +// return (size_t)Cppyy::GetFunctionArgSizeof(); +// } +// +// size_t cppyy_function_arg_typeoffset() { +// return (size_t)Cppyy::GetFunctionArgTypeoffset(); +// } + + +// scope reflection information ------------------------------------------- +int cppyy_is_namespace(cppyy_scope_t scope) { + return (int)Cppyy::IsNamespace((Cppyy::TCppScope_t) scope); +} + +// int cppyy_is_template(const char* template_name) { +// return (int)Cppyy::IsTemplate(template_name); +// } +// +// int cppyy_is_abstract(cppyy_type_t type) { +// return (int)Cppyy::IsAbstract(type); +// } +// +// int cppyy_is_enum(const char* type_name) { +// return (int)Cppyy::IsEnum(type_name); +// } +// +// int cppyy_is_aggregate(cppyy_type_t type) { +// return (int)Cppyy::IsAggregate(type); +// } +// +// int cppyy_is_default_constructable(cppyy_type_t type) { +// return (int)Cppyy::IsDefaultConstructable(type); +// } +// +// const char** cppyy_get_all_cpp_names(cppyy_scope_t scope, size_t* count) { +// std::set cppnames; +// Cppyy::GetAllCppNames(scope, cppnames); +// const char** c_cppnames = (const char**)malloc(cppnames.size()*sizeof(const char*)); +// int i = 0; +// for (const auto& name : cppnames) { +// c_cppnames[i] = cppstring_to_cstring(name); +// ++i; +// } +// *count = cppnames.size(); +// return c_cppnames; +// } +// +// +// [> namespace reflection information --------------------------------------- <] +// cppyy_scope_t* cppyy_get_using_namespaces(cppyy_scope_t scope) { +// const std::vector& uv = Cppyy::GetUsingNamespaces((Cppyy::TCppScope_t)scope); +// +// if (uv.empty()) +// return (cppyy_index_t*)nullptr; +// +// cppyy_scope_t* llresult = (cppyy_scope_t*)malloc(sizeof(cppyy_scope_t)*(uv.size()+1)); +// for (int i = 0; i < (int)uv.size(); ++i) llresult[i] = uv[i]; +// llresult[uv.size()] = (cppyy_scope_t)0; +// return llresult; +// } +// +// +// [> class reflection information ------------------------------------------- <] +// char* cppyy_final_name(cppyy_type_t type) { +// return cppstring_to_cstring(Cppyy::GetFinalName(type)); +// } +// +// char* cppyy_scoped_final_name(cppyy_type_t type) { +// return cppstring_to_cstring(Cppyy::GetScopedFinalName(type)); +// } +// +// int cppyy_has_virtual_destructor(cppyy_type_t type) { +// return (int)Cppyy::HasVirtualDestructor(type); +// } +// +// int cppyy_has_complex_hierarchy(cppyy_type_t type) { +// return (int)Cppyy::HasComplexHierarchy(type); +// } +// +// int cppyy_num_bases(cppyy_type_t type) { +// return (int)Cppyy::GetNumBases(type); +// } +// +// char* cppyy_base_name(cppyy_type_t type, int base_index) { +// return cppstring_to_cstring(Cppyy::GetBaseName (type, base_index)); +// } +// +// int cppyy_is_subtype(cppyy_type_t derived, cppyy_type_t base) { +// return (int)Cppyy::IsSubclass(derived, base); +// } + + int cppyy_is_smartptr(cppyy_type_t type) { + return (int)Cppyy::IsSmartPtr((Cppyy::TCppType_t)type); + } + +// int cppyy_smartptr_info(const char* name, cppyy_type_t* raw, cppyy_method_t* deref) { +// return (int)Cppyy::GetSmartPtrInfo(name, raw, deref); +// } +// +// void cppyy_add_smartptr_type(const char* type_name) { +// Cppyy::AddSmartPtrType(type_name); +// } +// +// void cppyy_add_type_reducer(const char* reducable, const char* reduced) { +// Cppyy::AddTypeReducer(reducable, reduced); +// } +// +// +// [> calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 <] +// ptrdiff_t cppyy_base_offset(cppyy_type_t derived, cppyy_type_t base, cppyy_object_t address, int direction) { +// return (ptrdiff_t)Cppyy::GetBaseOffset(derived, base, (void*)address, direction, 0); +// } +// +// +// [> method/function reflection information --------------------------------- <] +// int cppyy_num_methods(cppyy_scope_t scope) { +// return (int)Cppyy::GetNumMethods(scope); +// } +// +// int cppyy_num_methods_ns(cppyy_scope_t scope) { +// return (int)Cppyy::GetNumMethods(scope, true); +// } +// +// cppyy_index_t* cppyy_method_indices_from_name(cppyy_scope_t scope, const char* name) +// { +// std::vector result = Cppyy::GetMethodIndicesFromName(scope, name); +// +// if (result.empty()) +// return (cppyy_index_t*)nullptr; +// +// cppyy_index_t* llresult = (cppyy_index_t*)malloc(sizeof(cppyy_index_t)*(result.size()+1)); +// for (int i = 0; i < (int)result.size(); ++i) llresult[i] = result[i]; +// llresult[result.size()] = -1; +// return llresult; +// } +// +// cppyy_method_t cppyy_get_method(cppyy_scope_t scope, cppyy_index_t idx) { +// return cppyy_method_t(Cppyy::GetMethod(scope, idx)); +// } +// +// char* cppyy_method_name(cppyy_method_t method) { +// return cppstring_to_cstring(Cppyy::GetMethodName((Cppyy::TCppMethod_t)method)); +// } +// +// char* cppyy_method_full_name(cppyy_method_t method) { +// return cppstring_to_cstring(Cppyy::GetMethodFullName((Cppyy::TCppMethod_t)method)); +// } +// +// char* cppyy_method_mangled_name(cppyy_method_t method) { +// return cppstring_to_cstring(Cppyy::GetMethodMangledName((Cppyy::TCppMethod_t)method)); +// } +// +// char* cppyy_method_result_type(cppyy_method_t method) { +// return cppstring_to_cstring(Cppyy::GetMethodReturnTypeAsString((Cppyy::TCppMethod_t)method)); +// } +// +// int cppyy_method_num_args(cppyy_method_t method) { +// return (int)Cppyy::GetMethodNumArgs((Cppyy::TCppMethod_t)method); +// } +// +// int cppyy_method_req_args(cppyy_method_t method) { +// return (int)Cppyy::GetMethodReqArgs((Cppyy::TCppMethod_t)method); +// } +// +// char* cppyy_method_arg_name(cppyy_method_t method, int arg_index) { +// return cppstring_to_cstring(Cppyy::GetMethodArgName((Cppyy::TCppMethod_t)method, (Cppyy::TCppIndex_t)arg_index)); +// } +// +// char* cppyy_method_arg_type(cppyy_method_t method, int arg_index) { +// return cppstring_to_cstring(Cppyy::GetMethodArgType((Cppyy::TCppMethod_t)method, (Cppyy::TCppIndex_t)arg_index)); +// } +// +// char* cppyy_method_arg_default(cppyy_method_t method, int arg_index) { +// return cppstring_to_cstring(Cppyy::GetMethodArgDefault((Cppyy::TCppMethod_t)method, (Cppyy::TCppIndex_t)arg_index)); +// } +// +// char* cppyy_method_signature(cppyy_method_t method, int show_formalargs) { +// return cppstring_to_cstring(Cppyy::GetMethodSignature((Cppyy::TCppMethod_t)method, (bool)show_formalargs)); +// } +// +// char* cppyy_method_signature_max(cppyy_method_t method, int show_formalargs, int maxargs) { +// return cppstring_to_cstring(Cppyy::GetMethodSignature((Cppyy::TCppMethod_t)method, (bool)show_formalargs, (Cppyy::TCppIndex_t)maxargs)); +// } +// +// char* cppyy_method_prototype(cppyy_scope_t scope, cppyy_method_t method, int show_formalargs) { +// return cppstring_to_cstring(Cppyy::GetMethodPrototype( +// (Cppyy::TCppScope_t)scope, (Cppyy::TCppMethod_t)method, (bool)show_formalargs)); +// } +// +// int cppyy_is_const_method(cppyy_method_t method) { +// return (int)Cppyy::IsConstMethod((Cppyy::TCppMethod_t)method); +// } +// +// int cppyy_get_num_templated_methods(cppyy_scope_t scope) { +// return (int)Cppyy::GetNumTemplatedMethods((Cppyy::TCppScope_t)scope); +// } +// +// int cppyy_get_num_templated_methods_ns(cppyy_scope_t scope) { +// return (int)Cppyy::GetNumTemplatedMethods((Cppyy::TCppScope_t)scope, true); +// } +// +// char* cppyy_get_templated_method_name(cppyy_scope_t scope, cppyy_index_t imeth) { +// return cppstring_to_cstring(Cppyy::GetTemplatedMethodName((Cppyy::TCppScope_t)scope, (Cppyy::TCppIndex_t)imeth)); +// } +// +// int cppyy_is_templated_constructor(cppyy_scope_t scope, cppyy_index_t imeth) { +// return Cppyy::IsTemplatedConstructor((Cppyy::TCppScope_t)scope, (Cppyy::TCppIndex_t)imeth); +// } +// +// int cppyy_exists_method_template(cppyy_scope_t scope, const char* name) { +// return (int)Cppyy::ExistsMethodTemplate((Cppyy::TCppScope_t)scope, name); +// } +// +// int cppyy_method_is_template(cppyy_scope_t scope, cppyy_index_t idx) { +// return (int)Cppyy::IsMethodTemplate((Cppyy::TCppScope_t)scope, idx); +// } +// +// cppyy_method_t cppyy_get_method_template(cppyy_scope_t scope, const char* name, const char* proto) { +// return cppyy_method_t(Cppyy::GetMethodTemplate((Cppyy::TCppScope_t)scope, name, proto)); +// } +// +// cppyy_index_t cppyy_get_global_operator(cppyy_scope_t scope, cppyy_scope_t lc, cppyy_scope_t rc, const char* op) { +// return cppyy_index_t(Cppyy::GetGlobalOperator(scope, Cppyy::GetScopedFinalName(lc), Cppyy::GetScopedFinalName(rc), op)); +// } +// +// +// [> method properties ------------------------------------------------------ <] +// int cppyy_is_publicmethod(cppyy_method_t method) { +// return (int)Cppyy::IsPublicMethod((Cppyy::TCppMethod_t)method); +// } +// +// int cppyy_is_protectedmethod(cppyy_method_t method) { +// return (int)Cppyy::IsProtectedMethod((Cppyy::TCppMethod_t)method); +// } +// +// int cppyy_is_constructor(cppyy_method_t method) { +// return (int)Cppyy::IsConstructor((Cppyy::TCppMethod_t)method); +// } +// +// int cppyy_is_destructor(cppyy_method_t method) { +// return (int)Cppyy::IsDestructor((Cppyy::TCppMethod_t)method); +// } +// +// int cppyy_is_staticmethod(cppyy_method_t method) { +// return (int)Cppyy::IsStaticMethod((Cppyy::TCppMethod_t)method); +// } +// +// +// [> data member reflection information ------------------------------------- <] +// int cppyy_num_datamembers(cppyy_scope_t scope) { +// return (int)Cppyy::GetNumDatamembers(scope); +// } +// +// int cppyy_num_datamembers_ns(cppyy_scope_t scope) { +// return (int)Cppyy::GetNumDatamembers(scope, true); +// } +// +// char* cppyy_datamember_name(cppyy_scope_t scope, int datamember_index) { +// return cppstring_to_cstring(Cppyy::GetDatamemberName(scope, datamember_index)); +// } +// +// char* cppyy_datamember_type(cppyy_scope_t scope, int datamember_index) { +// return cppstring_to_cstring(Cppyy::GetDatamemberType(scope, datamember_index)); +// } +// +// intptr_t cppyy_datamember_offset(cppyy_scope_t scope, int datamember_index) { +// return intptr_t(Cppyy::GetDatamemberOffset(scope, datamember_index)); +// } +// +// int cppyy_datamember_index(cppyy_scope_t scope, const char* name) { +// return (int)Cppyy::GetDatamemberIndex(scope, name); +// } +// +// int cppyy_datamember_index_enumerated(cppyy_scope_t scope, int datamember_index) { +// return (int)Cppyy::GetDatamemberIndexEnumerated(scope, datamember_index); +// } +// +// +// [> data member properties ------------------------------------------------- <] +// int cppyy_is_publicdata(cppyy_type_t type, cppyy_index_t datamember_index) { +// return (int)Cppyy::IsPublicData(type, datamember_index); +// } +// +// int cppyy_is_protecteddata(cppyy_type_t type, cppyy_index_t datamember_index) { +// return (int)Cppyy::IsProtectedData(type, datamember_index); +// } +// +// int cppyy_is_staticdata(cppyy_type_t type, cppyy_index_t datamember_index) { +// return (int)Cppyy::IsStaticData(type, datamember_index); +// } +// +// int cppyy_is_const_data(cppyy_scope_t scope, cppyy_index_t idata) { +// return (int)Cppyy::IsConstData(scope, idata); +// } +// +// int cppyy_is_enum_data(cppyy_scope_t scope, cppyy_index_t idata) { +// return (int)Cppyy::IsEnumData(scope, idata); +// } +// +// int cppyy_get_dimension_size(cppyy_scope_t scope, cppyy_index_t idata, int dimension) { +// return Cppyy::GetDimensionSize(scope, idata, dimension); +// } +// +// +// [> enum properties -------------------------------------------------------- <] +// cppyy_enum_t cppyy_get_enum(cppyy_scope_t scope, const char* enum_name) { +// return Cppyy::GetEnum(scope, enum_name); +// } +// +// cppyy_index_t cppyy_get_num_enum_data(cppyy_enum_t e) { +// return Cppyy::GetNumEnumData(e); +// } +// +// const char* cppyy_get_enum_data_name(cppyy_enum_t e, cppyy_index_t idata) { +// return cppstring_to_cstring(Cppyy::GetEnumDataName(e, idata)); +// } +// +// long long cppyy_get_enum_data_value(cppyy_enum_t e, cppyy_index_t idata) { +// return Cppyy::GetEnumDataValue(e, idata); +// } +// +// +// [> misc helpers ----------------------------------------------------------- <] +// RPY_EXTERN +// void* cppyy_load_dictionary(const char* lib_name) { +// int result = gSystem->Load(lib_name); +// return (void*)(result == 0 [> success */ || result == 1 /* already loaded <]); +// } +// +// #if defined(_MSC_VER) +// long long cppyy_strtoll(const char* str) { +// return _strtoi64(str, NULL, 0); +// } +// +// extern "C" { +// unsigned long long cppyy_strtoull(const char* str) { +// return _strtoui64(str, NULL, 0); +// } +// } +// #else +// long long cppyy_strtoll(const char* str) { +// return strtoll(str, NULL, 0); +// } +// +// extern "C" { +// unsigned long long cppyy_strtoull(const char* str) { +// return strtoull(str, NULL, 0); +// } +// } +// #endif +// +// void cppyy_free(void* ptr) { +// free(ptr); +// } +// +// cppyy_object_t cppyy_charp2stdstring(const char* str, size_t sz) { +// return (cppyy_object_t)new std::string(str, sz); +// } +// +// const char* cppyy_stdstring2charp(cppyy_object_t ptr, size_t* lsz) { +// *lsz = ((std::string*)ptr)->size(); +// return ((std::string*)ptr)->data(); +// } +// +// cppyy_object_t cppyy_stdstring2stdstring(cppyy_object_t ptr) { +// return (cppyy_object_t)new std::string(*(std::string*)ptr); +// } +// +// double cppyy_longdouble2double(void* p) { +// return (double)*(long double*)p; +// } +// +// void cppyy_double2longdouble(double d, void* p) { +// *(long double*)p = d; +// } +// +// int cppyy_vectorbool_getitem(cppyy_object_t ptr, int idx) { +// return (int)(*(std::vector*)ptr)[idx]; +// } +// +// void cppyy_vectorbool_setitem(cppyy_object_t ptr, int idx, int value) { +// (*(std::vector*)ptr)[idx] = (bool)value; +// } + +} // end C-linkage wrappers diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h index 44056f8b71d95..8d5696d3dc297 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -1,14 +1,21 @@ #ifndef CPYCPPYY_CPPYY_H #define CPYCPPYY_CPPYY_H +#include + // Standard +#include #include #include #include #include #include +#include +#include "callcontext.h" // some more types; assumes Cppyy.h follows Python.h + +// using CppFinal #ifndef PY_LONG_LONG #ifdef _WIN32 typedef __int64 PY_LONG_LONG; @@ -29,54 +36,114 @@ typedef unsigned long long PY_ULONG_LONG; typedef long double PY_LONG_DOUBLE; #endif +typedef CPyCppyy::Parameter Parameter; -namespace Cppyy { - typedef size_t TCppScope_t; - typedef TCppScope_t TCppType_t; - typedef void* TCppEnum_t; - typedef void* TCppObject_t; - typedef intptr_t TCppMethod_t; +// small number that allows use of stack for argument passing +const int SMALL_ARGS_N = 8; - typedef size_t TCppIndex_t; - typedef void* TCppFuncAddr_t; +// convention to pass flag for direct calls (similar to Python's vector calls) +#define DIRECT_CALL ((size_t)1 << (8 * sizeof(size_t) - 1)) +static inline size_t CALL_NARGS(size_t nargs) { + return nargs & ~DIRECT_CALL; +} -// direct interpreter access ------------------------------------------------- +namespace Cppyy { + typedef Cpp::TCppScope_t TCppScope_t; + typedef Cpp::TCppType_t TCppType_t; + typedef Cpp::TCppScope_t TCppEnum_t; + typedef Cpp::TCppScope_t TCppObject_t; + typedef Cpp::TCppFunction_t TCppMethod_t; + typedef Cpp::TCppIndex_t TCppIndex_t; + typedef intptr_t TCppFuncAddr_t; + +// // direct interpreter access ------------------------------------------------- + // RPY_EXPORTED + // void AddSearchPath(const char* dir, bool isUser = true, bool prepend = false); RPY_EXPORTED bool Compile(const std::string& code, bool silent = false); RPY_EXPORTED std::string ToString(TCppType_t klass, TCppObject_t obj); - -// name to opaque C++ scope representation ----------------------------------- +// +// // name to opaque C++ scope representation ----------------------------------- RPY_EXPORTED std::string ResolveName(const std::string& cppitem_name); RPY_EXPORTED - std::string ResolveEnum(const std::string& enum_type); + TCppType_t ResolveType(TCppType_t cppitem_name); + RPY_EXPORTED + TCppType_t ResolveEnumReferenceType(TCppType_t type); + RPY_EXPORTED + TCppType_t ResolveEnumPointerType(TCppType_t type); + RPY_EXPORTED + TCppType_t GetRealType(TCppType_t type); + RPY_EXPORTED + TCppType_t GetPointerType(TCppType_t type); + RPY_EXPORTED + TCppType_t GetReferencedType(TCppType_t type, bool rvalue = false); + RPY_EXPORTED + std::string ResolveEnum(TCppScope_t enum_scope); + RPY_EXPORTED + bool IsClassType(TCppType_t type); + RPY_EXPORTED + bool IsPointerType(TCppType_t type); + RPY_EXPORTED + bool IsFunctionPointerType(TCppType_t type); RPY_EXPORTED - TCppScope_t GetScope(const std::string& scope_name); + TCppType_t GetType(const std::string &name, bool enable_slow_lookup = false); RPY_EXPORTED - TCppType_t GetActualClass(TCppType_t klass, TCppObject_t obj); + bool AppendTypesSlow(const std::string &name, + std::vector& types, Cppyy::TCppScope_t parent = nullptr); RPY_EXPORTED - size_t SizeOf(TCppType_t klass); + TCppType_t GetComplexType(const std::string &element_type); RPY_EXPORTED - size_t SizeOf(const std::string& type_name); + TCppScope_t GetScope(const std::string& scope_name, + TCppScope_t parent_scope = 0); + RPY_EXPORTED + TCppScope_t GetUnderlyingScope(TCppScope_t scope); + RPY_EXPORTED + TCppScope_t GetFullScope(const std::string& scope_name); + RPY_EXPORTED + TCppScope_t GetTypeScope(TCppScope_t klass); + RPY_EXPORTED + TCppScope_t GetNamed(const std::string& scope_name, + TCppScope_t parent_scope = 0); + RPY_EXPORTED + TCppScope_t GetParentScope(TCppScope_t scope); + RPY_EXPORTED + TCppScope_t GetScopeFromType(TCppType_t type); + RPY_EXPORTED + TCppType_t GetTypeFromScope(TCppScope_t klass); + RPY_EXPORTED + TCppScope_t GetGlobalScope(); + RPY_EXPORTED + TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); + RPY_EXPORTED + size_t SizeOf(TCppScope_t klass); + RPY_EXPORTED + size_t SizeOfType(TCppType_t type); + RPY_EXPORTED + size_t SizeOf(const std::string &type) { assert(0 && "SizeOf"); return 0; } RPY_EXPORTED bool IsBuiltin(const std::string& type_name); + RPY_EXPORTED - bool IsComplete(const std::string& type_name); + bool IsBuiltin(TCppType_t type); RPY_EXPORTED - TCppScope_t gGlobalScope; // for fast access + bool IsComplete(TCppScope_t type); -// memory management --------------------------------------------------------- +// RPY_EXPORTED +// inline TCppScope_t gGlobalScope = 0; // for fast access +// +// // memory management --------------------------------------------------------- RPY_EXPORTED - TCppObject_t Allocate(TCppType_t type); + TCppObject_t Allocate(TCppScope_t scope); RPY_EXPORTED - void Deallocate(TCppType_t type, TCppObject_t instance); + void Deallocate(TCppScope_t scope, TCppObject_t instance); RPY_EXPORTED - TCppObject_t Construct(TCppType_t type, void* arena = nullptr); + TCppObject_t Construct(TCppScope_t scope, void* arena = nullptr); RPY_EXPORTED - void Destruct(TCppType_t type, TCppObject_t instance); + void Destruct(TCppScope_t scope, TCppObject_t instance); // method/function dispatching ----------------------------------------------- RPY_EXPORTED @@ -105,16 +172,16 @@ namespace Cppyy { RPY_EXPORTED char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length); RPY_EXPORTED - TCppObject_t CallConstructor(TCppMethod_t method, TCppType_t type, size_t nargs, void* args); + TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, size_t nargs, void* args); RPY_EXPORTED - void CallDestructor(TCppType_t type, TCppObject_t self); + void CallDestructor(TCppScope_t type, TCppObject_t self); RPY_EXPORTED TCppObject_t CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, TCppType_t result_type); RPY_EXPORTED TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, bool check_enabled=true); -// handling of function argument buffer -------------------------------------- +// // handling of function argument buffer -------------------------------------- RPY_EXPORTED void* AllocateFunctionArgs(size_t nargs); RPY_EXPORTED @@ -124,28 +191,40 @@ namespace Cppyy { RPY_EXPORTED size_t GetFunctionArgTypeoffset(); -// scope reflection information ---------------------------------------------- +// // scope reflection information ---------------------------------------------- RPY_EXPORTED bool IsNamespace(TCppScope_t scope); RPY_EXPORTED - bool IsTemplate(const std::string& template_name); + bool IsClass(TCppScope_t scope); RPY_EXPORTED - bool IsAbstract(TCppType_t type); + bool IsTemplate(TCppScope_t scope); RPY_EXPORTED - bool IsEnum(const std::string& type_name); + bool IsTemplateInstantiation(TCppScope_t scope); + RPY_EXPORTED + bool IsTypedefed(TCppScope_t scope); + RPY_EXPORTED + bool IsAbstract(TCppScope_t scope); + RPY_EXPORTED + bool IsEnumScope(TCppScope_t scope); + RPY_EXPORTED + bool IsEnumConstant(TCppScope_t scope); + RPY_EXPORTED + bool IsEnumType(TCppType_t type); RPY_EXPORTED bool IsAggregate(TCppType_t type); RPY_EXPORTED - bool IsDefaultConstructable(TCppType_t type); + bool IsDefaultConstructable(TCppScope_t scope); + RPY_EXPORTED + bool IsVariable(TCppScope_t scope); RPY_EXPORTED void GetAllCppNames(TCppScope_t scope, std::set& cppnames); -// namespace reflection information ------------------------------------------ +// // namespace reflection information ------------------------------------------ RPY_EXPORTED - std::vector GetUsingNamespaces(TCppScope_t); - -// class reflection information ---------------------------------------------- + std::vector GetUsingNamespaces(TCppScope_t); +// +// // class reflection information ---------------------------------------------- RPY_EXPORTED std::string GetFinalName(TCppType_t type); RPY_EXPORTED @@ -153,47 +232,53 @@ namespace Cppyy { RPY_EXPORTED bool HasVirtualDestructor(TCppType_t type); RPY_EXPORTED - bool HasComplexHierarchy(TCppType_t type); + bool HasComplexHierarchy(TCppType_t type) { assert(0 && "HasComplexHierarchy"); return false; } RPY_EXPORTED - TCppIndex_t GetNumBases(TCppType_t type); + TCppIndex_t GetNumBases(TCppScope_t klass); RPY_EXPORTED - TCppIndex_t GetNumBasesLongestBranch(TCppType_t type); + TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); RPY_EXPORTED - std::string GetBaseName(TCppType_t type, TCppIndex_t ibase); + std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); RPY_EXPORTED - bool IsSubtype(TCppType_t derived, TCppType_t base); + TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); RPY_EXPORTED - bool IsSmartPtr(TCppType_t type); + bool IsSubclass(TCppType_t derived, TCppType_t base); + RPY_EXPORTED + bool IsSmartPtr(TCppScope_t klass); RPY_EXPORTED bool GetSmartPtrInfo(const std::string&, TCppType_t* raw, TCppMethod_t* deref); RPY_EXPORTED - void AddSmartPtrType(const std::string&); + void AddSmartPtrType(const std::string&) { assert(0 && "AddSmartPtrType"); return; } RPY_EXPORTED - void AddTypeReducer(const std::string& reducable, const std::string& reduced); + void AddTypeReducer(const std::string& reducable, const std::string& reduced) { assert(0 && "AddTypeReducer"); return; } -// calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 +// // calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 RPY_EXPORTED ptrdiff_t GetBaseOffset( TCppType_t derived, TCppType_t base, TCppObject_t address, int direction, bool rerror = false); -// method/function reflection information ------------------------------------ +// // method/function reflection information ------------------------------------ RPY_EXPORTED - TCppIndex_t GetNumMethods(TCppScope_t scope, bool accept_namespace = false); + void GetClassMethods(TCppScope_t scope, std::vector &methods); RPY_EXPORTED - std::vector GetMethodIndicesFromName(TCppScope_t scope, const std::string& name); + std::vector GetMethodsFromName(TCppScope_t scope, + const std::string& name); RPY_EXPORTED - TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth); + TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth) { return 0; } RPY_EXPORTED std::string GetMethodName(TCppMethod_t); RPY_EXPORTED std::string GetMethodFullName(TCppMethod_t); + // GetMethodMangledName is unused. + RPY_EXPORTED + std::string GetMethodMangledName(TCppMethod_t) { assert(0 && "GetMethodMangledName"); return ""; } RPY_EXPORTED - std::string GetMethodMangledName(TCppMethod_t); + TCppType_t GetMethodReturnType(TCppMethod_t); RPY_EXPORTED - std::string GetMethodResultType(TCppMethod_t); + std::string GetMethodReturnTypeAsString(TCppMethod_t); RPY_EXPORTED TCppIndex_t GetMethodNumArgs(TCppMethod_t); RPY_EXPORTED @@ -201,90 +286,125 @@ namespace Cppyy { RPY_EXPORTED std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); RPY_EXPORTED - std::string GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); + TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); RPY_EXPORTED TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, const std::string &req_type); RPY_EXPORTED + std::string GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg); + RPY_EXPORTED + std::string GetMethodArgCanonTypeAsString(TCppMethod_t method, TCppIndex_t iarg); + RPY_EXPORTED std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); RPY_EXPORTED - std::string GetMethodSignature(TCppMethod_t, bool show_formalargs, TCppIndex_t maxargs = (TCppIndex_t)-1); + std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, TCppIndex_t max_args = (TCppIndex_t)-1); + // GetMethodPrototype is unused. RPY_EXPORTED - std::string GetMethodPrototype(TCppScope_t scope, TCppMethod_t, bool show_formalargs); + std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); RPY_EXPORTED bool IsConstMethod(TCppMethod_t); - +// // Templated method/function reflection information ------------------------------------ + RPY_EXPORTED + void GetTemplatedMethods(TCppScope_t scope, std::vector &methods); RPY_EXPORTED TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace = false); RPY_EXPORTED std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); RPY_EXPORTED - bool IsTemplatedConstructor(TCppScope_t scope, TCppIndex_t imeth); - RPY_EXPORTED bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); RPY_EXPORTED - bool IsStaticTemplate(TCppScope_t scope, const std::string& name); + bool IsTemplatedMethod(TCppMethod_t method); RPY_EXPORTED - bool IsMethodTemplate(TCppScope_t scope, TCppIndex_t imeth); + bool IsStaticTemplate(TCppScope_t scope, const std::string& name); RPY_EXPORTED TCppMethod_t GetMethodTemplate( TCppScope_t scope, const std::string& name, const std::string& proto); - RPY_EXPORTED - TCppIndex_t GetGlobalOperator( + void GetClassOperators(Cppyy::TCppScope_t klass, const std::string& opname, + std::vector& operators); + RPY_EXPORTED + TCppMethod_t GetGlobalOperator( TCppType_t scope, const std::string& lc, const std::string& rc, const std::string& op); // method properties --------------------------------------------------------- + RPY_EXPORTED + bool IsDeletedMethod(TCppMethod_t method); RPY_EXPORTED bool IsPublicMethod(TCppMethod_t method); RPY_EXPORTED bool IsProtectedMethod(TCppMethod_t method); RPY_EXPORTED + bool IsPrivateMethod(TCppMethod_t method); + RPY_EXPORTED bool IsConstructor(TCppMethod_t method); RPY_EXPORTED bool IsDestructor(TCppMethod_t method); RPY_EXPORTED bool IsStaticMethod(TCppMethod_t method); - RPY_EXPORTED - bool IsExplicit(TCppMethod_t method); -// data member reflection information ---------------------------------------- +// // data member reflection information ---------------------------------------- + // GetNumDatamembers is unused. + // RPY_EXPORTED + // TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace = false) { return 0; } RPY_EXPORTED - TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace = false); + void GetDatamembers(TCppScope_t scope, std::vector& datamembers); + // GetDatamemberName is unused. + // RPY_EXPORTED + // std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata) { return ""; } RPY_EXPORTED - std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata); + bool IsLambdaClass(TCppType_t type); RPY_EXPORTED - std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata); + TCppScope_t WrapLambdaFromVariable(TCppScope_t var); RPY_EXPORTED - intptr_t GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata); + TCppScope_t AdaptFunctionForLambdaReturn(TCppScope_t fn); RPY_EXPORTED - TCppIndex_t GetDatamemberIndex(TCppScope_t scope, const std::string& name); + TCppType_t GetDatamemberType(TCppScope_t data); RPY_EXPORTED - TCppIndex_t GetDatamemberIndexEnumerated(TCppScope_t scope, TCppIndex_t idata); + std::string GetDatamemberTypeAsString(TCppScope_t var); + RPY_EXPORTED + std::string GetTypeAsString(TCppType_t type); + RPY_EXPORTED + intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); + RPY_EXPORTED + bool CheckDatamember(TCppScope_t scope, const std::string& name); -// data member properties ---------------------------------------------------- +// // data member properties ---------------------------------------------------- RPY_EXPORTED - bool IsPublicData(TCppScope_t scope, TCppIndex_t idata); + bool IsPublicData(TCppScope_t var); RPY_EXPORTED - bool IsProtectedData(TCppScope_t scope, TCppIndex_t idata); + bool IsProtectedData(TCppScope_t var); RPY_EXPORTED - bool IsStaticData(TCppScope_t scope, TCppIndex_t idata); + bool IsPrivateData(TCppScope_t var); RPY_EXPORTED - bool IsConstData(TCppScope_t scope, TCppIndex_t idata); + bool IsStaticDatamember(TCppScope_t var); RPY_EXPORTED - bool IsEnumData(TCppScope_t scope, TCppIndex_t idata); + bool IsConstVar(TCppScope_t var); RPY_EXPORTED - int GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension); + TCppScope_t ReduceReturnType(TCppScope_t fn, TCppType_t reduce); +// RPY_EXPORTED +// bool IsEnumData(TCppScope_t scope, TCppIndex_t idata); + RPY_EXPORTED + std::vector GetDimensions(TCppType_t type); -// enum properties ----------------------------------------------------------- +// // enum properties ----------------------------------------------------------- + // GetEnum is unused. + // RPY_EXPORTED + // TCppEnum_t GetEnum(TCppScope_t scope, const std::string& enum_name) { return 0; } RPY_EXPORTED - TCppEnum_t GetEnum(TCppScope_t scope, const std::string& enum_name); + std::vector GetEnumConstants(TCppScope_t scope); + // GetEnumDataName is unused. + // RPY_EXPORTED + // std::string GetEnumDataName(TCppEnum_t, TCppIndex_t idata) { return ""; } RPY_EXPORTED - TCppIndex_t GetNumEnumData(TCppEnum_t); + TCppType_t GetEnumConstantType(TCppScope_t scope); RPY_EXPORTED - std::string GetEnumDataName(TCppEnum_t, TCppIndex_t idata); + TCppIndex_t GetEnumDataValue(TCppScope_t scope); + RPY_EXPORTED - long long GetEnumDataValue(TCppEnum_t, TCppIndex_t idata); + TCppScope_t InstantiateTemplate( + TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size); + RPY_EXPORTED + void DumpScope(TCppScope_t scope); } // namespace Cppyy -#endif // !CPYCPPYY_CPPYY_H +#endif // !CPYCPPYY_CPPYY_H \ No newline at end of file diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cppinterop_dispatch.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cppinterop_dispatch.cxx new file mode 100644 index 0000000000000..4d6c6c025c186 --- /dev/null +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cppinterop_dispatch.cxx @@ -0,0 +1,5 @@ +#include + +#define DISPATCH_API(name, type) CppAPIType::name Cpp::name = nullptr; +CPPINTEROP_API_TABLE +#undef DISPATCH_API From 120c170e9945a154f06d1db4c5aa0c0966029b4a Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 10 Feb 2026 17:01:12 +0100 Subject: [PATCH 02/29] [cppyy] Update CPyCppyy API to use InterOp based forks --- .../cppyy/CPyCppyy/include/CPyCppyy/API.h | 11 +- .../CPyCppyy/include/CPyCppyy/CommonDefs.h | 8 +- bindings/pyroot/cppyy/CPyCppyy/src/API.cxx | 19 +- .../cppyy/CPyCppyy/src/CPPConstructor.cxx | 17 +- .../cppyy/CPyCppyy/src/CPPDataMember.cxx | 96 +-- .../pyroot/cppyy/CPyCppyy/src/CPPDataMember.h | 7 +- .../pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx | 43 +- bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h | 2 + .../pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx | 22 +- .../pyroot/cppyy/CPyCppyy/src/CPPInstance.h | 15 +- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 179 ++--- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.h | 2 - .../pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx | 14 +- .../pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx | 121 ++-- .../pyroot/cppyy/CPyCppyy/src/CPPOverload.h | 6 +- .../pyroot/cppyy/CPyCppyy/src/CPPScope.cxx | 75 ++- bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h | 8 +- .../cppyy/CPyCppyy/src/CPyCppyyModule.cxx | 113 ++-- .../pyroot/cppyy/CPyCppyy/src/CallContext.cxx | 29 + .../pyroot/cppyy/CPyCppyy/src/CallContext.h | 18 + .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 610 +++++++++--------- .../pyroot/cppyy/CPyCppyy/src/Converters.h | 11 +- bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 178 +++-- .../cppyy/CPyCppyy/src/DeclareConverters.h | 85 +-- .../cppyy/CPyCppyy/src/DeclareExecutors.h | 16 +- .../pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx | 27 +- .../pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx | 89 ++- .../pyroot/cppyy/CPyCppyy/src/Executors.cxx | 190 +++++- .../pyroot/cppyy/CPyCppyy/src/Executors.h | 1 + .../cppyy/CPyCppyy/src/LowLevelViews.cxx | 40 -- .../pyroot/cppyy/CPyCppyy/src/LowLevelViews.h | 9 - .../cppyy/CPyCppyy/src/MemoryRegulator.cxx | 20 +- .../cppyy/CPyCppyy/src/ProxyWrappers.cxx | 381 ++++++----- .../pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h | 8 +- .../pyroot/cppyy/CPyCppyy/src/PyCallable.h | 2 - .../pyroot/cppyy/CPyCppyy/src/PyException.cxx | 16 +- .../pyroot/cppyy/CPyCppyy/src/PyStrings.cxx | 3 + .../pyroot/cppyy/CPyCppyy/src/PyStrings.h | 1 + .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 162 ++--- .../pyroot/cppyy/CPyCppyy/src/Pythonize.h | 2 +- .../cppyy/CPyCppyy/src/SignalTryCatch.h | 11 +- .../cppyy/CPyCppyy/src/TemplateProxy.cxx | 52 +- .../cppyy/CPyCppyy/src/TupleOfInstances.cxx | 2 +- .../cppyy/CPyCppyy/src/TupleOfInstances.h | 2 +- .../pyroot/cppyy/CPyCppyy/src/TypeManip.cxx | 12 +- .../pyroot/cppyy/CPyCppyy/src/Utility.cxx | 359 ++++++++--- bindings/pyroot/cppyy/CPyCppyy/src/Utility.h | 29 +- 47 files changed, 1730 insertions(+), 1393 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h index 05bd9f13937a8..332731957c232 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h +++ b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h @@ -25,15 +25,15 @@ #endif #include "Python.h" -#define CPYCPPYY_VERSION_HEX 0x010c10 +#define CPYCPPYY_VERSION_HEX 0x011200 // Cppyy types namespace Cppyy { - typedef size_t TCppScope_t; + typedef void* TCppScope_t; typedef TCppScope_t TCppType_t; typedef void* TCppEnum_t; typedef void* TCppObject_t; - typedef intptr_t TCppMethod_t; + typedef void* TCppMethod_t; typedef size_t TCppIndex_t; typedef void* TCppFuncAddr_t; @@ -124,6 +124,7 @@ class CPYCPPYY_CLASS_EXTERN Converter { // create a converter based on its full type name and dimensions CPYCPPYY_EXTERN Converter* CreateConverter(const std::string& name, cdims_t = 0); +CPYCPPYY_EXTERN Converter* CreateConverter(Cppyy::TCppType_t type, cdims_t = 0); // delete a previously created converter CPYCPPYY_EXTERN void DestroyConverter(Converter* p); @@ -154,6 +155,7 @@ class CPYCPPYY_CLASS_EXTERN Executor { // create an executor based on its full type name CPYCPPYY_EXTERN Executor* CreateExecutor(const std::string& name, cdims_t = 0); +CPYCPPYY_EXTERN Executor* CreateExecutor(Cppyy::TCppType_t type, cdims_t = 0); // delete a previously created executor CPYCPPYY_EXTERN void DestroyConverter(Converter* p); @@ -184,7 +186,8 @@ CPYCPPYY_EXTERN void* Instance_AsVoidPtr(PyObject* pyobject); // void* to C++ Instance (python object proxy) conversion, returns a new reference CPYCPPYY_EXTERN PyObject* Instance_FromVoidPtr( void* addr, const std::string& classname, bool python_owns = false); - +CPYCPPYY_EXTERN PyObject* Instance_FromVoidPtr( + void* addr, Cppyy::TCppScope_t klass_scope, bool python_owns = false); // type verifiers for C++ Scope CPYCPPYY_EXTERN bool Scope_Check(PyObject* pyobject); CPYCPPYY_EXTERN bool Scope_CheckExact(PyObject* pyobject); diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h index e309c6a0f9b3b..af2fa60b71bf4 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h +++ b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/CommonDefs.h @@ -6,6 +6,7 @@ #ifdef _MSC_VER // Windows requires symbols to be explicitly exported #define CPYCPPYY_EXPORT extern __declspec(dllexport) +#define CPYCPPYY_IMPORT extern __declspec(dllimport) #define CPYCPPYY_CLASS_EXPORT __declspec(dllexport) // CPYCPPYY_EXTERN is dual use in the public API @@ -13,8 +14,8 @@ #define CPYCPPYY_EXTERN extern __declspec(dllexport) #define CPYCPPYY_CLASS_EXTERN __declspec(dllexport) #else -#define CPYCPPYY_EXTERN extern -#define CPYCPPYY_CLASS_EXTERN +#define CPYCPPYY_EXTERN extern __declspec(dllimport) +#define CPYCPPYY_CLASS_EXTERN __declspec(dllimport) #endif #define CPYCPPYY_STATIC @@ -22,6 +23,7 @@ #else // Linux, Mac, etc. #define CPYCPPYY_EXPORT extern +#define CPYCPPYY_IMPORT extern #define CPYCPPYY_CLASS_EXPORT #define CPYCPPYY_EXTERN extern #define CPYCPPYY_CLASS_EXTERN @@ -29,6 +31,4 @@ #endif -#define CPYCPPYY_IMPORT extern - #endif // !CPYCPPYY_COMMONDEFS_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx index 0438e69f69c0e..120f465a64777 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx @@ -147,6 +147,23 @@ PyObject* CPyCppyy::Instance_FromVoidPtr( return pyobject; } +//----------------------------------------------------------------------------- +PyObject* CPyCppyy::Instance_FromVoidPtr( + void* addr, Cppyy::TCppScope_t klass_scope, bool python_owns) +{ +// Bind the addr to a python object of class defined by classname. + if (!Initialize()) + return nullptr; + +// perform cast (the call will check TClass and addr, and set python errors) + PyObject* pyobject = BindCppObjectNoCast(addr, klass_scope, false); + +// give ownership, for ref-counting, to the python side, if so requested + if (python_owns && CPPInstance_Check(pyobject)) + ((CPPInstance*)pyobject)->PythonOwns(); + + return pyobject; +} namespace CPyCppyy { // version with C type arguments only for use with Numba PyObject* Instance_FromVoidPtr(void* addr, const char* classname, int python_owns) { @@ -238,7 +255,7 @@ bool CPyCppyy::Instance_IsLively(PyObject* pyobject) // the instance fails the lively test if it owns the C++ object while having a // reference count of 1 (meaning: it could delete the C++ instance any moment) - if (Py_REFCNT(pyobject) <= 1 && (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsOwner)) + if (pyobject->ob_refcnt <= 1 && (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsOwner)) return false; return true; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx index f935d3d5371cb..d121364d839ba 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx @@ -15,6 +15,8 @@ //- data _____________________________________________________________________ namespace CPyCppyy { extern PyObject* gNullPtrObject; + void* Instance_AsVoidPtr(PyObject* pyobject); + PyObject* Instance_FromVoidPtr(void* addr, Cppyy::TCppScope_t klass_scope, bool python_owns); } @@ -44,7 +46,7 @@ PyObject* CPyCppyy::CPPConstructor::Reflex( if (request == Cppyy::Reflex::RETURN_TYPE) { std::string fn = Cppyy::GetScopedFinalName(this->GetScope()); if (format == Cppyy::Reflex::OPTIMAL || format == Cppyy::Reflex::AS_TYPE) - return CreateScopeProxy(fn); + return CreateScopeProxy(this->GetScope()); else if (format == Cppyy::Reflex::AS_STRING) return CPyCppyy_PyText_FromString(fn.c_str()); } @@ -56,7 +58,6 @@ PyObject* CPyCppyy::CPPConstructor::Reflex( PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt) { - // setup as necessary if (fArgsRequired == -1 && !this->Initialize(ctxt)) return nullptr; // important: 0, not Py_None @@ -78,15 +79,6 @@ PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, return nullptr; } - const auto cppScopeFlags = ((CPPScope*)Py_TYPE(self))->fFlags; - -// Do nothing if the constructor is explicit and we are in an implicit -// conversion context. We recognize this by checking the CPPScope::kNoImplicit -// flag, as further implicit conversions are disabled to prevent infinite -// recursion. See also the ConvertImplicit() helper in Converters.cxx. - if((cppScopeFlags & CPPScope::kNoImplicit) && Cppyy::IsExplicit(GetMethod())) - return nullptr; - // self provides the python context for lifelines if (!ctxt->fPyContext) ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership @@ -135,7 +127,7 @@ PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, } else { // translate the arguments - if (cppScopeFlags & CPPScope::kNoImplicit) + if (((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kNoImplicit) ctxt->fFlags |= CallContext::kNoImplicit; if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) return nullptr; @@ -182,6 +174,7 @@ PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, return nullptr; } + //---------------------------------------------------------------------------- CPyCppyy::CPPMultiConstructor::CPPMultiConstructor(Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method) : CPPConstructor(scope, method) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx index 13b144b046045..109aa2be54f55 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx @@ -4,6 +4,7 @@ #include "PyStrings.h" #include "CPPDataMember.h" #include "CPPInstance.h" +#include "CPPEnum.h" #include "Dimensions.h" #include "LowLevelViews.h" #include "ProxyWrappers.h" @@ -47,10 +48,6 @@ static PyObject* dm_get(CPPDataMember* dm, CPPInstance* pyobj, PyObject* /* kls } } -// non-initialized or public data accesses through class (e.g. by help()) - void* address = dm->GetAddress(pyobj); - if (!address || (intptr_t)address == -1 /* Cling error */) - return nullptr; if (dm->fFlags & (kIsEnumPrep | kIsEnumType)) { if (dm->fFlags & kIsEnumPrep) { @@ -58,19 +55,16 @@ static PyObject* dm_get(CPPDataMember* dm, CPPInstance* pyobj, PyObject* /* kls dm->fFlags &= ~kIsEnumPrep; // fDescription contains the full name of the actual enum value object - const std::string& lookup = CPyCppyy_PyText_AsString(dm->fDescription); - const std::string& enum_type = TypeManip::extract_namespace(lookup); - const std::string& enum_scope = TypeManip::extract_namespace(enum_type); + const Cppyy::TCppScope_t enum_type = Cppyy::GetParentScope(dm->fScope); + const Cppyy::TCppScope_t enum_scope = Cppyy::GetParentScope(enum_type); - PyObject* pyscope = nullptr; - if (enum_scope.empty()) pyscope = GetScopeProxy(Cppyy::gGlobalScope); - else pyscope = CreateScopeProxy(enum_scope); + PyObject* pyscope = CreateScopeProxy(enum_scope); if (pyscope) { - PyObject* pyEnumType = PyObject_GetAttrString(pyscope, - enum_type.substr(enum_scope.size() ? enum_scope.size()+2 : 0, std::string::npos).c_str()); + PyObject* pyEnumType = + PyObject_GetAttrString(pyscope, Cppyy::GetFinalName(enum_type).c_str()); if (pyEnumType) { - PyObject* pyval = PyObject_GetAttrString(pyEnumType, - lookup.substr(enum_type.size()+2, std::string::npos).c_str()); + PyObject* pyval = + PyObject_GetAttrString(pyEnumType, Cppyy::GetFinalName(dm->fScope).c_str()); Py_DECREF(pyEnumType); if (pyval) { Py_DECREF(dm->fDescription); @@ -88,7 +82,16 @@ static PyObject* dm_get(CPPDataMember* dm, CPPInstance* pyobj, PyObject* /* kls Py_INCREF(dm->fDescription); return dm->fDescription; } + + if (Cppyy::IsEnumConstant(dm->fScope)) { + // anonymous enum + return pyval_from_enum(Cppyy::ResolveEnum(dm->fScope), nullptr, nullptr, dm->fScope); + } } +// non-initialized or public data accesses through class (e.g. by help()) + void* address = dm->GetAddress(pyobj); + if (!address || (intptr_t)address == -1 /* Cling error */) + return nullptr; if (dm->fConverter != 0) { PyObject* result = dm->fConverter->FromMemory((dm->fFlags & kIsArrayType) ? &address : address); @@ -319,53 +322,54 @@ PyTypeObject CPPDataMember_Type = { //- public members ----------------------------------------------------------- -void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata) +void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, Cppyy::TCppScope_t data) { - fEnclosingScope = scope; - fOffset = Cppyy::GetDatamemberOffset(scope, idata); // TODO: make lazy - fFlags = Cppyy::IsStaticData(scope, idata) ? kIsStaticData : 0; - - std::vector dims; - int ndim = 0; Py_ssize_t size = 0; - while (0 < (size = Cppyy::GetDimensionSize(scope, idata, ndim))) { - ndim += 1; - if (size == INT_MAX) // meaning: incomplete array type - size = UNKNOWN_SIZE; - if (ndim == 1) dims.reserve(4); - dims.push_back((dim_t)size); + if (Cppyy::IsLambdaClass(Cppyy::GetDatamemberType(data))) { + fScope = Cppyy::WrapLambdaFromVariable(data); + } else { + fScope = data; } - if (!dims.empty()) - fFlags |= kIsArrayType; - const std::string name = Cppyy::GetDatamemberName(scope, idata); - fFullType = Cppyy::GetDatamemberType(scope, idata); - if (Cppyy::IsEnumData(scope, idata)) { + fEnclosingScope = scope; + fOffset = Cppyy::GetDatamemberOffset(fScope, fScope == data ? scope : Cppyy::GetScope("__cppyy_internal_wrap_g")); // XXX: Check back here // TODO: make lazy + fFlags = Cppyy::IsStaticDatamember(fScope) ? kIsStaticData : 0; + + const std::string name = Cppyy::GetFinalName(fScope); + Cppyy::TCppType_t type; + + + if (Cppyy::IsEnumConstant(fScope)) { + type = Cppyy::GetEnumConstantType(fScope); + fFullType = Cppyy::GetTypeAsString(type); if (fFullType.find("(anonymous)") == std::string::npos && fFullType.find("(unnamed)") == std::string::npos) { // repurpose fDescription for lazy lookup of the enum later fDescription = CPyCppyy_PyText_FromString((fFullType + "::" + name).c_str()); fFlags |= kIsEnumPrep; } - fFullType = Cppyy::ResolveEnum(fFullType); - fFlags |= kIsConstData; - } else if (Cppyy::IsConstData(scope, idata)) { + type = Cppyy::ResolveType(type); fFlags |= kIsConstData; + } else { + type = Cppyy::GetDatamemberType(fScope); + fFullType = Cppyy::GetTypeAsString(type); + + // Get the integer type if it's an enum + if (Cppyy::IsEnumType(type)) + type = Cppyy::ResolveType(type); + + if (Cppyy::IsConstVar(fScope)) + fFlags |= kIsConstData; } -// if this data member is an array, the conversion needs to be pointer to object for instances, -// to prevent the need for copying in the conversion; furthermore, fixed arrays' full type for -// builtins are not declared as such if more than 1-dim (TODO: fix in clingwrapper) - if (!dims.empty() && fFullType.back() != '*') { - if (Cppyy::GetScope(fFullType)) fFullType += '*'; - else if (fFullType.back() != ']') { - for (auto d: dims) fFullType += d == UNKNOWN_SIZE ? "*" : "[]"; - } - } + std::vector dims = Cppyy::GetDimensions(type); + + if (!dims.empty()) + fFlags |= kIsArrayType; if (dims.empty()) - fConverter = CreateConverter(fFullType); + fConverter = CreateConverter(type, 0); else - fConverter = CreateConverter(fFullType, {(dim_t)dims.size(), dims.data()}); + fConverter = CreateConverter(type, {(dim_t)dims.size(), dims.data()}); if (!(fFlags & kIsEnumPrep)) fDescription = CPyCppyy_PyText_FromString(name.c_str()); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h index 9fe32cfd93e72..9241b4dd267ef 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.h @@ -14,7 +14,7 @@ class CPPInstance; class CPPDataMember { public: - void Set(Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata); + void Set(Cppyy::TCppScope_t scope, Cppyy::TCppScope_t var); void Set(Cppyy::TCppScope_t scope, const std::string& name, void* address); std::string GetName(); @@ -25,6 +25,7 @@ class CPPDataMember { intptr_t fOffset; long fFlags; Converter* fConverter; + Cppyy::TCppScope_t fScope; Cppyy::TCppScope_t fEnclosingScope; PyObject* fDescription; PyObject* fDoc; @@ -55,12 +56,12 @@ inline bool CPPDataMember_CheckExact(T* object) //- creation ----------------------------------------------------------------- inline CPPDataMember* CPPDataMember_New( - Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata) + Cppyy::TCppScope_t scope, Cppyy::TCppScope_t var) { // Create an initialize a new property descriptor, given the C++ datum. CPPDataMember* pyprop = (CPPDataMember*)CPPDataMember_Type.tp_new(&CPPDataMember_Type, nullptr, nullptr); - pyprop->Set(scope, idata); + pyprop->Set(scope, var); return pyprop; } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx index dbb2f0fe4da29..6027db9776e35 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx @@ -19,9 +19,9 @@ static PyObject* pytype_from_enum_type(const std::string& enum_type) } //---------------------------------------------------------------------------- -static PyObject* pyval_from_enum(const std::string& enum_type, PyObject* pytype, - PyObject* btype, Cppyy::TCppEnum_t etype, Cppyy::TCppIndex_t idata) { - long long llval = Cppyy::GetEnumDataValue(etype, idata); +PyObject* CPyCppyy::pyval_from_enum(const std::string& enum_type, PyObject* pytype, + PyObject* btype, Cppyy::TCppScope_t enum_constant) { + long long llval = Cppyy::GetEnumDataValue(enum_constant); if (enum_type == "bool") { PyObject* result = (bool)llval ? Py_True : Py_False; @@ -45,14 +45,15 @@ static PyObject* pyval_from_enum(const std::string& enum_type, PyObject* pytype, if (!bval) return nullptr; // e.g. when out of range for small integers - PyObject* args = PyTuple_New(1); - PyTuple_SET_ITEM(args, 0, bval); - PyObject* result = ((PyTypeObject*)btype)->tp_new((PyTypeObject*)pytype, args, nullptr); - Py_DECREF(args); - return result; + if (pytype && btype) { + PyObject* args = PyTuple_New(1); + PyTuple_SET_ITEM(args, 0, bval); + bval = ((PyTypeObject*)btype)->tp_new((PyTypeObject*)pytype, args, nullptr); + Py_DECREF(args); + } + return bval; } - //- enum methods ------------------------------------------------------------- static int enum_setattro(PyObject* /* pyclass */, PyObject* /* pyname */, PyObject* /* pyval */) { @@ -66,6 +67,8 @@ static PyObject* enum_repr(PyObject* self) { using namespace CPyCppyy; + PyObject* kls_scope = PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gThisModule); + if (!kls_scope) PyErr_Clear(); PyObject* kls_cppname = PyObject_GetAttr((PyObject*)Py_TYPE(self), PyStrings::gCppName); if (!kls_cppname) PyErr_Clear(); PyObject* obj_cppname = PyObject_GetAttr(self, PyStrings::gCppName); @@ -74,7 +77,7 @@ static PyObject* enum_repr(PyObject* self) PyObject* repr = nullptr; if (kls_cppname && obj_cppname && obj_str) { - const std::string resolved = Cppyy::ResolveEnum(CPyCppyy_PyText_AsString(kls_cppname)); + const std::string resolved = Cppyy::ResolveEnum(PyLong_AsVoidPtr(kls_scope)); repr = CPyCppyy_PyText_FromFormat("(%s::%s) : (%s) %s", CPyCppyy_PyText_AsString(kls_cppname), CPyCppyy_PyText_AsString(obj_cppname), resolved.c_str(), CPyCppyy_PyText_AsString(obj_str)); @@ -144,12 +147,12 @@ CPyCppyy::CPPEnum* CPyCppyy::CPPEnum_New(const std::string& name, Cppyy::TCppSco CPPEnum* pyenum = nullptr; - const std::string& ename = scope == Cppyy::gGlobalScope ? name : Cppyy::GetScopedFinalName(scope)+"::"+name; - Cppyy::TCppEnum_t etype = Cppyy::GetEnum(scope, name); + Cppyy::TCppScope_t etype = scope; + const std::string& ename = Cppyy::GetScopedFinalName(scope); if (etype) { // create new enum type with labeled values in place, with a meta-class // to make sure the enum values are read-only - const std::string& resolved = Cppyy::ResolveEnum(ename); + const std::string& resolved = Cppyy::ResolveEnum(etype); PyObject* pyside_type = pytype_from_enum_type(resolved); PyObject* pymetabases = PyTuple_New(1); PyObject* btype = (PyObject*)Py_TYPE(pyside_type); @@ -169,12 +172,14 @@ CPyCppyy::CPPEnum* CPyCppyy::CPPEnum_New(const std::string& name, Cppyy::TCppSco // create the __cpp_name__ for templates PyObject* dct = PyDict_New(); PyObject* pycppname = CPyCppyy_PyText_FromString(ename.c_str()); + PyObject* pycppscope = PyLong_FromVoidPtr(etype); PyDict_SetItem(dct, PyStrings::gCppName, pycppname); + PyDict_SetItem(dct, PyStrings::gThisModule, pycppscope); Py_DECREF(pycppname); PyObject* pyresolved = CPyCppyy_PyText_FromString(resolved.c_str()); PyDict_SetItem(dct, PyStrings::gUnderlying, pyresolved); Py_DECREF(pyresolved); - + // add the __module__ to allow pickling std::string modname = TypeManip::extract_namespace(ename); TypeManip::cppscope_to_pyscope(modname); // :: -> . @@ -196,15 +201,15 @@ CPyCppyy::CPPEnum* CPyCppyy::CPPEnum_New(const std::string& name, Cppyy::TCppSco ((PyTypeObject*)pyenum)->tp_str = ((PyTypeObject*)pyside_type)->tp_repr; // collect the enum values - Cppyy::TCppIndex_t ndata = Cppyy::GetNumEnumData(etype); + std::vector econstants = Cppyy::GetEnumConstants(etype); bool values_ok = true; - for (Cppyy::TCppIndex_t idata = 0; idata < ndata; ++idata) { - PyObject* val = pyval_from_enum(resolved, pyenum, pyside_type, etype, idata); + for (auto *econstant : econstants) { + PyObject* val = pyval_from_enum(resolved, pyenum, pyside_type, econstant); if (!val) { values_ok = false; break; } - const std::string& dname = Cppyy::GetEnumDataName(etype, idata); + const std::string& dname = Cppyy::GetFinalName(econstant); PyObject* pydname = CPyCppyy_PyText_FromString(dname.c_str()); PyObject_SetAttr(pyenum, pydname, val); Py_DECREF(pydname); @@ -235,4 +240,4 @@ CPyCppyy::CPPEnum* CPyCppyy::CPPEnum_New(const std::string& name, Cppyy::TCppSco } return pyenum; -} +} \ No newline at end of file diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h index 730baf4ba75d9..d1d0e9eb670bf 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h @@ -11,6 +11,8 @@ typedef PyObject CPPEnum; //- creation ----------------------------------------------------------------- CPPEnum* CPPEnum_New(const std::string& name, Cppyy::TCppScope_t scope); +PyObject* pyval_from_enum(const std::string& enum_type, PyObject* pytype, + PyObject* btype, Cppyy::TCppScope_t enum_constant); } // namespace CPyCppyy #endif // !CPYCPPYY_CPPENUM_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx index 57095383c3e7b..e435ddcbd981a 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx @@ -274,7 +274,7 @@ static PyObject* op_destruct(CPPInstance* self) } //= CPyCppyy object dispatch support ========================================= -static PyObject* op_dispatch(PyObject* self, PyObject* args, PyObject* /* kwds */) +static PyObject* op_dispatch(PyObject* self, PyObject* args, PyObject* /* kdws */) { // User-side __dispatch__ method to allow selection of a specific overloaded // method. The actual selection is in the __overload__() method of CPPOverload. @@ -431,7 +431,6 @@ static PyMethodDef op_methods[] = { {(char*)nullptr, nullptr, 0, nullptr} }; - //= CPyCppyy object proxy construction/destruction =========================== static CPPInstance* op_new(PyTypeObject* subtype, PyObject*, PyObject*) { @@ -488,6 +487,10 @@ static inline PyObject* eqneq_binop(CPPClass* klass, PyObject* self, PyObject* o bool flipit = false; PyObject* binop = op == Py_EQ ? klass->fOperators->fEq : klass->fOperators->fNe; + if (!binop) { + binop = op == Py_EQ ? klass->fOperators->fNe : klass->fOperators->fEq; + if (binop) flipit = true; + } if (!binop) { const char* cppop = op == Py_EQ ? "==" : "!="; PyCallable* pyfunc = FindBinaryOperator(self, obj, cppop); @@ -501,11 +504,6 @@ static inline PyObject* eqneq_binop(CPPClass* klass, PyObject* self, PyObject* o else klass->fOperators->fNe = binop; } - if (binop == Py_None) { // can try !== or !!= as alternatives - binop = op == Py_EQ ? klass->fOperators->fNe : klass->fOperators->fEq; - if (binop && binop != Py_None) flipit = true; - } - if (!binop || binop == Py_None) return nullptr; PyObject* args = PyTuple_New(1); @@ -537,8 +535,8 @@ static inline void* cast_actual(void* obj) { if (((CPPInstance*)obj)->fFlags & CPPInstance::kIsActual) return address; - Cppyy::TCppType_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; - Cppyy::TCppType_t clActual = Cppyy::GetActualClass(klass, address); + Cppyy::TCppScope_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; + Cppyy::TCppScope_t clActual = klass /* XXX: Cppyy::GetActualClass(klass, address) */; if (clActual && clActual != klass) { intptr_t offset = Cppyy::GetBaseOffset( clActual, klass, address, -1 /* down-cast */, true /* report errors */); @@ -703,7 +701,7 @@ static Py_hash_t op_hash(CPPInstance* self) return h; } - Cppyy::TCppScope_t stdhash = Cppyy::GetScope("std::hash<"+Cppyy::GetScopedFinalName(self->ObjectIsA())+">"); + Cppyy::TCppScope_t stdhash = Cppyy::GetFullScope("std::hash<"+Cppyy::GetScopedFinalName(self->ObjectIsA())+">"); if (stdhash) { PyObject* hashcls = CreateScopeProxy(stdhash); PyObject* dct = PyObject_GetAttr(hashcls, PyStrings::gDict); @@ -734,7 +732,7 @@ static Py_hash_t op_hash(CPPInstance* self) //---------------------------------------------------------------------------- static PyObject* op_str_internal(PyObject* pyobj, PyObject* lshift, bool isBound) { - static Cppyy::TCppScope_t sOStringStreamID = Cppyy::GetScope("std::ostringstream"); + static Cppyy::TCppScope_t sOStringStreamID = Cppyy::GetFullScope("std::ostringstream"); std::ostringstream s; PyObject* pys = BindCppObjectNoCast(&s, sOStringStreamID); Py_INCREF(pys); @@ -795,7 +793,7 @@ static PyObject* op_str(CPPInstance* self) // normal lookup failed; attempt lazy install of global operator<<(ostream&, type&) std::string rcname = Utility::ClassName((PyObject*)self); Cppyy::TCppScope_t rnsID = Cppyy::GetScope(TypeManip::extract_namespace(rcname)); - PyCallable* pyfunc = Utility::FindBinaryOperator("std::ostream", rcname, "<<", rnsID); + PyCallable* pyfunc = Utility::FindBinaryOperator("std::ostream&", rcname, "<<", rnsID); if (!pyfunc) continue; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h index 3635543d178fc..e17c324dc444e 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h @@ -16,6 +16,7 @@ // Standard #include +#include #include @@ -62,7 +63,7 @@ class CPPInstance { // access to C++ pointer and type void* GetObject(); void*& GetObjectRaw() { return IsExtended() ? *(void**) fObject : fObject; } - Cppyy::TCppType_t ObjectIsA(bool check_smart = true) const; + Cppyy::TCppScope_t ObjectIsA(bool check_smart = true) const; // memory management: ownership of the underlying C++ object void PythonOwns(); @@ -86,7 +87,8 @@ class CPPInstance { // implementation of the __reduce__ method: doesn't wrap any function by // default but can be re-assigned by libraries that add C++ object // serialization support, like ROOT - static PyCFunction &ReduceMethod(); +static std::function &ReduceMethod(); + private: void CreateExtension(); @@ -116,7 +118,7 @@ inline void* CPPInstance::GetObject() } //---------------------------------------------------------------------------- -inline Cppyy::TCppType_t CPPInstance::ObjectIsA(bool check_smart) const +inline Cppyy::TCppScope_t CPPInstance::ObjectIsA(bool check_smart) const { // Retrieve the C++ type identifier (or raw type if smart). if (check_smart || !IsSmart()) return ((CPPClass*)Py_TYPE(this))->fCppType; @@ -125,12 +127,7 @@ inline Cppyy::TCppType_t CPPInstance::ObjectIsA(bool check_smart) const //- object proxy type and type verification ---------------------------------- -// Needs to be extern because the libROOTPythonizations is secretly using it -#ifdef _MSC_VER -extern __declspec(dllimport) PyTypeObject CPPInstance_Type; -#else -extern PyTypeObject CPPInstance_Type; -#endif +CPYCPPYY_IMPORT PyTypeObject CPPInstance_Type; template inline bool CPPInstance_Check(T* object) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index df1dbef0567a4..55f094cce07ef 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx @@ -126,7 +126,7 @@ inline PyObject* CPyCppyy::CPPMethod::ExecuteFast( result = nullptr; // error already set } catch (std::exception& e) { // attempt to set the exception to the actual type, to allow catching with the Python C++ type - static Cppyy::TCppType_t exc_type = (Cppyy::TCppType_t)Cppyy::GetScope("std::exception"); + static Cppyy::TCppType_t exc_type = (Cppyy::TCppType_t)Cppyy::GetFullScope("std::exception"); ctxt->fFlags |= CallContext::kCppException; @@ -237,10 +237,11 @@ bool CPyCppyy::CPPMethod::InitConverters_() // setup the dispatch cache for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string& fullType = Cppyy::GetMethodArgType(fMethod, iarg); + Cppyy::TCppType_t fullType = Cppyy::GetMethodArgType(fMethod, iarg); Converter* conv = CreateConverter(fullType); if (!conv) { - PyErr_Format(PyExc_TypeError, "argument type %s not handled", fullType.c_str()); + PyErr_Format(PyExc_TypeError, "argument type %s not handled", + Cppyy::GetTypeAsString(fullType).c_str()); return false; } @@ -254,9 +255,9 @@ bool CPyCppyy::CPPMethod::InitConverters_() bool CPyCppyy::CPPMethod::InitExecutor_(Executor*& executor, CallContext* /* ctxt */) { // install executor conform to the return type - executor = CreateExecutor( - (bool)fMethod == true ? Cppyy::GetMethodResultType(fMethod) \ - : Cppyy::GetScopedFinalName(fScope)); + executor = + (bool)fMethod == true ? CreateExecutor(Cppyy::GetMethodReturnType(fMethod)) \ + : CreateExecutor(Cppyy::GetScopedFinalName(fScope)); if (!executor) return false; @@ -293,28 +294,27 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) PyObject *evalue = nullptr; PyObject *etrace = nullptr; + PyObject *etype = nullptr, *evalue = nullptr; if (PyErr_Occurred()) { + PyObject* etrace = nullptr; + PyErr_Fetch(&etype, &evalue, &etrace); - } -#endif - const bool isCppExc = evalue && PyType_IsSubtype((PyTypeObject*)etype, &CPPExcInstance_Type); - // If the error is not a CPPExcInstance, the error from Python itself is - // already complete and messing with it would only make it less informative. - // Just restore and return. - if (evalue && !isCppExc) { -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(evalue); -#else - PyErr_Restore(etype, evalue, etrace); -#endif - return; - } + if (evalue) { + PyObject* descr = PyObject_Str(evalue); + if (descr) { + details = CPyCppyy_PyText_AsString(descr); + Py_DECREF(descr); + } + } + + Py_XDECREF(etrace); + } PyObject* doc = GetDocString(); - const char* cdoc = CPyCppyy_PyText_AsString(doc); - const char* cmsg = msg ? CPyCppyy_PyText_AsString(msg) : nullptr; - PyObject* errtype = etype ? etype : PyExc_TypeError; + PyObject* errtype = etype; + if (!errtype) + errtype = PyExc_TypeError; PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName); const char* cname = pyname ? CPyCppyy_PyText_AsString(pyname) : "Exception"; @@ -329,28 +329,41 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) if (msg) { topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: %s | ", cdoc, cname, cmsg); } else { - topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: ", cdoc, cname); + PyErr_Format(errtype, "%s =>\n %s: %s", + CPyCppyy_PyText_AsString(doc), cname, details.c_str()); } - // restore the updated error -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(evalue); -#else - PyErr_Restore(etype, evalue, etrace); -#endif + } else { + Py_XDECREF(((CPPExcInstance*)evalue)->fTopMessage); + if (msg) { + ((CPPExcInstance*)evalue)->fTopMessage = CPyCppyy_PyText_FromFormat(\ + "%s =>\n %s: %s | ", CPyCppyy_PyText_AsString(doc), cname, CPyCppyy_PyText_AsString(msg)); + } else { + ((CPPExcInstance*)evalue)->fTopMessage = CPyCppyy_PyText_FromFormat(\ + "%s =>\n %s: ", CPyCppyy_PyText_AsString(doc), cname); + } + PyErr_SetObject(errtype, evalue); } Py_XDECREF(pyname); + Py_XDECREF(evalue); + Py_XDECREF(etype); Py_DECREF(doc); Py_XDECREF(msg); } +extern std::map TypeReductionMap; + //- constructors and destructor ---------------------------------------------- CPyCppyy::CPPMethod::CPPMethod( Cppyy::TCppScope_t scope, Cppyy::TCppMethod_t method) : fMethod(method), fScope(scope), fExecutor(nullptr), fArgIndices(nullptr), fArgsRequired(-1) { - // empty + Cppyy::TCppType_t result = Cppyy::ResolveType(Cppyy::GetMethodReturnType(fMethod)); + if (TypeReductionMap.contains(result)) + fMethod = Cppyy::ReduceReturnType(fMethod, TypeReductionMap[result]); + if (result && Cppyy::IsLambdaClass(result)) + fMethod = Cppyy::AdaptFunctionForLambdaReturn(fMethod); } //---------------------------------------------------------------------------- @@ -396,7 +409,7 @@ CPyCppyy::CPPMethod::~CPPMethod() * namespace c { * int foo(int x); * }}} - * + * * This function returns: * * 'int foo(int x)' @@ -410,12 +423,10 @@ PyObject* CPyCppyy::CPPMethod::GetPrototype(bool fa) // gives // a::b std::string finalscope = Cppyy::GetScopedFinalName(fScope); - return CPyCppyy_PyText_FromFormat("%s%s %s%s%s%s", + return CPyCppyy_PyText_FromFormat("%s%s %s%s", (Cppyy::IsStaticMethod(fMethod) ? "static " : ""), - Cppyy::GetMethodResultType(fMethod).c_str(), - finalscope.c_str(), - (finalscope.empty() ? "" : "::"), // Add final set of '::' if the method is scoped in namespace(s) - Cppyy::GetMethodName(fMethod).c_str(), + Cppyy::GetMethodReturnTypeAsString(fMethod).c_str(), + Cppyy::GetScopedFinalName(fMethod).c_str(), GetSignatureString(fa).c_str()); } @@ -479,7 +490,10 @@ int CPyCppyy::CPPMethod::GetPriority() const size_t nArgs = Cppyy::GetMethodNumArgs(fMethod); for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string aname = Cppyy::GetMethodArgType(fMethod, iarg); + const std::string aname = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); + // FIXME: convert the string comparisons with comparison to the underlying + // type: + // Cppyy::TCppType_t type = Cppyy::GetMethodArgType(fMethod, iarg); if (Cppyy::IsBuiltin(aname)) { // complex type (note: double penalty: for complex and the template type) @@ -528,7 +542,7 @@ int CPyCppyy::CPPMethod::GetPriority() if (scope) priority += static_cast(Cppyy::GetNumBasesLongestBranch(scope)); - if (Cppyy::IsEnum(clean_name)) + if (Cppyy::IsEnumScope(scope)) priority -= 100; // a couple of special cases as explained above @@ -536,7 +550,7 @@ int CPyCppyy::CPPMethod::GetPriority() priority += 150; // needed for proper implicit conversion rules } else if (aname.rfind("&&", aname.size()-2) != std::string::npos) { priority += 100; // prefer moves over other ref/ptr - } else if (scope && !Cppyy::IsComplete(clean_name)) { + } else if (scope && !Cppyy::IsComplete(scope)) { // class is known, but no dictionary available, 2 more cases: * and & if (aname[aname.size() - 1] == '&') priority += -5000; @@ -554,6 +568,10 @@ int CPyCppyy::CPPMethod::GetPriority() if (Cppyy::IsConstMethod(fMethod) && Cppyy::GetMethodName(fMethod) == "operator[]") priority += -10; + // constructors are prefered + if (Cppyy::IsConstructor(fMethod)) + priority += 100; + return priority; } @@ -568,8 +586,8 @@ bool CPyCppyy::CPPMethod::IsGreedy() if (!nArgs) return false; for (int iarg = 0; iarg < (int)nArgs; ++iarg) { - const std::string aname = Cppyy::GetMethodArgType(fMethod, iarg); - if (aname.find("void*") != 0) + const std::string aname = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); + if (aname.find("void *") != 0) return false; } return true; @@ -593,7 +611,7 @@ PyObject* CPyCppyy::CPPMethod::GetCoVarNames() PyObject* co_varnames = PyTuple_New(co_argcount+1 /* self */); PyTuple_SET_ITEM(co_varnames, 0, CPyCppyy_PyText_FromString("self")); for (int iarg = 0; iarg < co_argcount; ++iarg) { - std::string argrep = Cppyy::GetMethodArgType(fMethod, iarg); + std::string argrep = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); const std::string& parname = Cppyy::GetMethodArgName(fMethod, iarg); if (!parname.empty()) { argrep += " "; @@ -910,10 +928,10 @@ bool CPyCppyy::CPPMethod::ProcessArgs(PyCallArgs& cargs) // demand CPyCppyy object, and an argument that may match down the road if (CPPInstance_Check(pyobj)) { Cppyy::TCppType_t oisa = pyobj->ObjectIsA(); - if (fScope == Cppyy::gGlobalScope || // free global + if (fScope == Cppyy::GetGlobalScope() || // free global oisa == 0 || // null pointer or ctor call oisa == fScope || // matching types - Cppyy::IsSubtype(oisa, fScope)) { // id. + Cppyy::IsSubclass(oisa, fScope)) { // id. // reset self Py_INCREF(pyobj); // corresponding Py_DECREF is in CPPOverload @@ -965,7 +983,7 @@ bool CPyCppyy::CPPMethod::ConvertAndSetArgs(CPyCppyy_PyArgs_t args, size_t nargs Parameter* cppArgs = ctxt->GetArgs(argc); for (int i = 0; i < (int)argc; ++i) { if (!fConverters[i]->SetArg(CPyCppyy_PyArgs_GET_ITEM(args, i), cppArgs[i], ctxt)) { - SetPyError_(CPyCppyy_PyText_FromFormat("could not convert argument %d", i+1)); + SetPyError_(CPyCppyy_PyText_FromFormat("could not convert argument %d: %s", i+1, fConverters[i]->GetFailureMsg().c_str())); isOK = false; break; } @@ -980,7 +998,8 @@ PyObject* CPyCppyy::CPPMethod::Execute(void* self, ptrdiff_t offset, CallContext // call the interface method PyObject* result = 0; - if (!(CallContext::GlobalPolicyFlags() & CallContext::kProtected) && !(ctxt->fFlags & CallContext::kProtected)) { + if (CallContext::sSignalPolicy != CallContext::kProtected && \ + !(ctxt->fFlags & CallContext::kProtected)) { // bypasses try block (i.e. segfaults will abort) result = ExecuteFast(self, offset, ctxt); } else { @@ -1054,72 +1073,8 @@ PyObject* CPyCppyy::CPPMethod::GetSignature(bool fa) return CPyCppyy_PyText_FromString(GetSignatureString(fa).c_str()); } -/** - * @brief Returns a tuple with the names of the input parameters of this method. - * - * For example given a function with prototype: - * - * double foo(int a, float b, double c) - * - * this function returns: - * - * ('a', 'b', 'c') - */ -PyObject *CPyCppyy::CPPMethod::GetSignatureNames() -{ - // Build a tuple of the argument names for this signature. - int argcount = GetMaxArgs(); - PyObject *signature_names = PyTuple_New(argcount); - - for (int iarg = 0; iarg < argcount; ++iarg) { - const std::string &argname_cpp = Cppyy::GetMethodArgName(fMethod, iarg); - PyObject *argname_py = CPyCppyy_PyText_FromString(argname_cpp.c_str()); - PyTuple_SET_ITEM(signature_names, iarg, argname_py); - } - - return signature_names; -} - -/** - * @brief Returns a dictionary with the types of the signature of this method. - * - * This dictionary will store both the return type and the input parameter - * types of this method, respectively with keys "return_type" and - * "input_types", for example given a function with prototype: - * - * double foo(int a, float b, double c) - * - * this function returns: - * - * {'input_types': ('int', 'float', 'double'), 'return_type': 'double'} - */ -PyObject *CPyCppyy::CPPMethod::GetSignatureTypes() -{ - - PyObject *signature_types_dict = PyDict_New(); - - // Insert the return type first - std::string return_type = GetReturnTypeName(); - PyObject *return_type_py = CPyCppyy_PyText_FromString(return_type.c_str()); - PyDict_SetItem(signature_types_dict, CPyCppyy_PyText_FromString("return_type"), return_type_py); - - // Build a tuple of the argument types for this signature. - int argcount = GetMaxArgs(); - PyObject *parameter_types = PyTuple_New(argcount); - - for (int iarg = 0; iarg < argcount; ++iarg) { - const std::string &argtype_cpp = Cppyy::GetMethodArgType(fMethod, iarg); - PyObject *argtype_py = CPyCppyy_PyText_FromString(argtype_cpp.c_str()); - PyTuple_SET_ITEM(parameter_types, iarg, argtype_py); - } - - PyDict_SetItem(signature_types_dict, CPyCppyy_PyText_FromString("input_types"), parameter_types); - - return signature_types_dict; -} - //---------------------------------------------------------------------------- std::string CPyCppyy::CPPMethod::GetReturnTypeName() { - return Cppyy::GetMethodResultType(fMethod); + return Cppyy::GetMethodReturnTypeAsString(fMethod); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h index e9558f5c6869b..88e49621ef629 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h @@ -51,8 +51,6 @@ class CPPMethod : public PyCallable { public: PyObject* GetSignature(bool show_formalargs = true) override; - PyObject* GetSignatureNames() override; - PyObject* GetSignatureTypes() override; PyObject* GetPrototype(bool show_formalargs = true) override; PyObject* GetTypeName() override; PyObject* Reflex(Cppyy::Reflex::RequestId_t request, diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx index 4832136d74cd2..9aacc3dd50528 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx @@ -2,7 +2,6 @@ #include "CPyCppyy.h" #include "CPPOperator.h" #include "CPPInstance.h" -#include "Utility.h" //- constructor -------------------------------------------------------------- @@ -50,14 +49,17 @@ PyObject* CPyCppyy::CPPOperator::Call(CPPInstance*& self, return result; } -// fetch the current error, resetting the error buffer - auto error = CPyCppyy::Utility::FetchPyError(); + PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); result = fStub((PyObject*)self, CPyCppyy_PyArgs_GET_ITEM(args, idx_other)); -// if there was still a problem, restore the Python error buffer - if (!result) { - CPyCppyy::Utility::RestorePyError(error); + if (!result) + PyErr_Restore(pytype, pyvalue, pytrace); + else { + Py_XDECREF(pytype); + Py_XDECREF(pyvalue); + Py_XDECREF(pytrace); } return result; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx index f8eabb993c406..92e87a3bc3d98 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx @@ -62,12 +62,6 @@ class TPythonCallback : public PyCallable { PyObject* GetSignature(bool /*show_formalargs*/ = true) override { return CPyCppyy_PyText_FromString("*args, **kwargs"); } - PyObject* GetSignatureNames() override { - return PyTuple_New(0); - } - PyObject* GetSignatureTypes() override { - return PyTuple_New(0); - } PyObject* GetPrototype(bool /*show_formalargs*/ = true) override { return CPyCppyy_PyText_FromString(""); } @@ -291,54 +285,6 @@ static int mp_doc_set(CPPOverload* pymeth, PyObject *val, void *) return 0; } -/** - * @brief Returns a dictionary with the input parameter names for all overloads. - * - * This dictionary may look like: - * - * {'double ::foo(int a, float b, double c)': ('a', 'b', 'c'), - * 'float ::foo(float b)': ('b',), - * 'int ::foo(int a)': ('a',), - * 'int ::foo(int a, float b)': ('a', 'b')} - */ -static PyObject *mp_func_overloads_names(CPPOverload *pymeth) -{ - - const CPPOverload::Methods_t &methods = pymeth->fMethodInfo->fMethods; - - PyObject *overloads_names_dict = PyDict_New(); - - for (PyCallable *method : methods) { - PyDict_SetItem(overloads_names_dict, method->GetPrototype(), method->GetSignatureNames()); - } - - return overloads_names_dict; -} - -/** - * @brief Returns a dictionary with the types of all overloads. - * - * This dictionary may look like: - * - * {'double ::foo(int a, float b, double c)': {'input_types': ('int', 'float', 'double'), 'return_type': 'double'}, - * 'float ::foo(float b)': {'input_types': ('float',), 'return_type': 'float'}, - * 'int ::foo(int a)': {'input_types': ('int',), 'return_type': 'int'}, - * 'int ::foo(int a, float b)': {'input_types': ('int', 'float'), 'return_type': 'int'}} - */ -static PyObject *mp_func_overloads_types(CPPOverload *pymeth) -{ - - const CPPOverload::Methods_t &methods = pymeth->fMethodInfo->fMethods; - - PyObject *overloads_types_dict = PyDict_New(); - - for (PyCallable *method : methods) { - PyDict_SetItem(overloads_types_dict, method->GetPrototype(), method->GetSignatureTypes()); - } - - return overloads_types_dict; -} - //---------------------------------------------------------------------------- static PyObject* mp_meth_func(CPPOverload* pymeth, void*) { @@ -549,25 +495,40 @@ static int mp_setcreates(CPPOverload* pymeth, PyObject* value, void*) return set_flag(pymeth, value, CallContext::kIsCreator, "__creates__"); } -constexpr const char *mempolicy_error_message = - "The __mempolicy__ attribute can't be used, because in the past it was reserved to manage the local memory policy. " - "If you want to do that now, please implement a pythonization for your class that uses SetOwnership() to manage the " - "ownership of arguments according to your needs."; - //---------------------------------------------------------------------------- -static PyObject* mp_getmempolicy(CPPOverload*, void*) +static PyObject* mp_getmempolicy(CPPOverload* pymeth, void*) { - PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message); - return nullptr; +// Get '_mempolicy' enum, which determines ownership of call arguments. + if (pymeth->fMethodInfo->fFlags & CallContext::kUseHeuristics) + return PyInt_FromLong(CallContext::kUseHeuristics); + + if (pymeth->fMethodInfo->fFlags & CallContext::kUseStrict) + return PyInt_FromLong(CallContext::kUseStrict); + + return PyInt_FromLong(-1); } //---------------------------------------------------------------------------- -static int mp_setmempolicy(CPPOverload*, PyObject*, void*) +static int mp_setmempolicy(CPPOverload* pymeth, PyObject* value, void*) { - PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message); - return -1; +// Set '_mempolicy' enum, which determines ownership of call arguments. + long mempolicy = PyLong_AsLong(value); + if (mempolicy == CallContext::kUseHeuristics) { + pymeth->fMethodInfo->fFlags |= CallContext::kUseHeuristics; + pymeth->fMethodInfo->fFlags &= ~CallContext::kUseStrict; + } else if (mempolicy == CallContext::kUseStrict) { + pymeth->fMethodInfo->fFlags |= CallContext::kUseStrict; + pymeth->fMethodInfo->fFlags &= ~CallContext::kUseHeuristics; + } else { + PyErr_SetString(PyExc_ValueError, + "expected kMemoryStrict or kMemoryHeuristics as value for __mempolicy__"); + return -1; + } + + return 0; } + //---------------------------------------------------------------------------- #define CPPYY_BOOLEAN_PROPERTY(name, flag, label) \ static PyObject* mp_get##name(CPPOverload* pymeth, void*) { \ @@ -621,15 +582,13 @@ static PyGetSetDef mp_getset[] = { {(char*)"func_globals", (getter)mp_func_globals, nullptr, nullptr, nullptr}, {(char*)"func_doc", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, {(char*)"func_name", (getter)mp_name, nullptr, nullptr, nullptr}, - {(char*)"func_overloads_types", (getter)mp_func_overloads_types, nullptr, nullptr, nullptr}, - {(char*)"func_overloads_names", (getter)mp_func_overloads_names, nullptr, nullptr, nullptr}, // flags to control behavior {(char*)"__creates__", (getter)mp_getcreates, (setter)mp_setcreates, (char*)"For ownership rules of result: if true, objects are python-owned", nullptr}, - {(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy, - (char*)"Unused", nullptr}, + {(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy, + (char*)"For argument ownership rules: like global, either heuristic or strict", nullptr}, {(char*)"__set_lifeline__", (getter)mp_getlifeline, (setter)mp_setlifeline, (char*)"If true, set a lifeline from the return value onto self", nullptr}, {(char*)"__release_gil__", (getter)mp_getthreaded, (setter)mp_setthreaded, @@ -671,6 +630,8 @@ static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds) CallContext ctxt{}; const auto mflags = pymeth->fMethodInfo->fFlags; + const auto mempolicy = (mflags & (CallContext::kUseHeuristics | CallContext::kUseStrict)); + ctxt.fFlags |= mempolicy ? mempolicy : (uint64_t)CallContext::sMemoryPolicy; ctxt.fFlags |= (mflags & CallContext::kReleaseGIL); ctxt.fFlags |= (mflags & CallContext::kProtected); if (IsConstructor(pymeth->fMethodInfo->fFlags)) ctxt.fFlags |= CallContext::kIsConstructor; @@ -751,6 +712,9 @@ static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds) } } + // clear collected errors + if (!errors.empty()) + std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear); return HandleReturn(pymeth, im_self, result); } @@ -794,7 +758,7 @@ static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds) // first summarize, then add details PyObject* topmsg = CPyCppyy_PyText_FromFormat( "none of the %d overloaded methods succeeded. Full details:", (int)nMethods); - SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); + SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */); // report failure return nullptr; @@ -1115,19 +1079,10 @@ void CPyCppyy::CPPOverload::Set(const std::string& name, std::vectorfFlags |= (CallContext::kIsCreator | CallContext::kIsConstructor); -// special case, in heuristics mode also tag *Clone* methods as creators. Only -// check that Clone is present in the method name, not in the template argument -// list. - if (CallContext::GlobalPolicyFlags() & CallContext::kUseHeuristics) { - std::string_view name_maybe_template = name; - auto begin_template = name_maybe_template.find_first_of('<'); - if (begin_template <= name_maybe_template.size()) { - name_maybe_template = name_maybe_template.substr(0, begin_template); - } - if (name_maybe_template.find("Clone") != std::string_view::npos) { - fMethodInfo->fFlags |= CallContext::kIsCreator; - } - } +// special case, in heuristics mode also tag *Clone* methods as creators + if (CallContext::sMemoryPolicy == CallContext::kUseHeuristics && \ + name.find("Clone") != std::string::npos) + fMethodInfo->fFlags |= CallContext::kIsCreator; #if PY_VERSION_HEX >= 0x03080000 fVectorCall = (vectorcallfunc)mp_vectorcall; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h index 8bd3b2de234a9..b392bf4ed7f20 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h @@ -25,11 +25,7 @@ inline uint64_t HashSignature(CPyCppyy_PyArgs_t args, size_t nargsf) // improved overloads for implicit conversions PyObject* pyobj = CPyCppyy_PyArgs_GET_ITEM(args, i); hash += (uint64_t)Py_TYPE(pyobj); -#if PY_VERSION_HEX >= 0x030e0000 - hash += (uint64_t)(PyUnstable_Object_IsUniqueReferencedTemporary(pyobj) ? 1 : 0); -#else - hash += (uint64_t)(Py_REFCNT(pyobj) == 1 ? 1 : 0); -#endif + hash += (uint64_t)(pyobj->ob_refcnt == 1 ? 1 : 0); hash += (hash << 10); hash ^= (hash >> 6); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx index e7823bb0004ca..8ae79432ede0b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx @@ -105,15 +105,14 @@ static PyObject* meta_getmodule(CPPScope* scope, void*) return CPyCppyy_PyText_FromString(scope->fModuleName); // get C++ representation of outer scope - std::string modname = - TypeManip::extract_namespace(Cppyy::GetScopedFinalName(scope->fCppType)); - if (modname.empty()) + Cppyy::TCppScope_t parent_scope = Cppyy::GetParentScope(scope->fCppType); + if (parent_scope == Cppyy::GetGlobalScope()) return CPyCppyy_PyText_FromString(const_cast("cppyy.gbl")); // now peel scopes one by one, pulling in the python naming (which will // simply recurse if not overridden in python) PyObject* pymodule = nullptr; - PyObject* pyscope = CPyCppyy::GetScopeProxy(Cppyy::GetScope(modname)); + PyObject* pyscope = CPyCppyy::GetScopeProxy(parent_scope); if (pyscope) { // get the module of our module pymodule = PyObject_GetAttr(pyscope, PyStrings::gModule); @@ -133,6 +132,7 @@ static PyObject* meta_getmodule(CPPScope* scope, void*) PyErr_Clear(); // lookup through python failed, so simply cook up a '::' -> '.' replacement + std::string modname = Cppyy::GetScopedFinalName(parent_scope); TypeManip::cppscope_to_pyscope(modname); return CPyCppyy_PyText_FromString(("cppyy.gbl."+modname).c_str()); } @@ -258,7 +258,6 @@ static PyObject* pt_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) // so make it accessible (the __cpp_cross__ data member also signals that // this is a cross-inheritance class) PyObject* bname = CPyCppyy_PyText_FromString(Cppyy::GetBaseName(result->fCppType, 0).c_str()); - PyErr_Clear(); if (PyObject_SetAttrString((PyObject*)result, "__cpp_cross__", bname) == -1) PyErr_Clear(); Py_DECREF(bname); @@ -275,8 +274,8 @@ static PyObject* pt_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) // maps for using namespaces and tracking objects if (!Cppyy::IsNamespace(result->fCppType)) { - static Cppyy::TCppType_t exc_type = (Cppyy::TCppType_t)Cppyy::GetScope("std::exception"); - if (Cppyy::IsSubtype(result->fCppType, exc_type)) + static Cppyy::TCppType_t exc_type = (Cppyy::TCppType_t)Cppyy::GetScope("exception", Cppyy::GetScope("std")); + if (Cppyy::IsSubclass(result->fCppType, exc_type)) result->fFlags |= CPPScope::kIsException; if (!(result->fFlags & CPPScope::kIsPython)) result->fImp.fCppObjects = new CppToPyMap_t; @@ -330,6 +329,8 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) attr = nullptr; } PyErr_Clear(); + } else if (CPPScope_Check(attr) && CPPScope_Check(pyclass) && ((CPPScope*)attr)->fFlags & CPPScope::kIsException) { + return CreateExcScopeProxy(attr, pyname, pyclass); } else return attr; } @@ -361,16 +362,16 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) // namespaces may have seen updates in their list of global functions, which // are available as "methods" even though they're not really that - if (klass->fFlags & CPPScope::kIsNamespace) { + if ((klass->fFlags & CPPScope::kIsNamespace) || + scope == Cppyy::GetGlobalScope()) { // tickle lazy lookup of functions - const std::vector methods = - Cppyy::GetMethodIndicesFromName(scope, name); + const std::vector methods = + Cppyy::GetMethodsFromName(scope, name); if (!methods.empty()) { // function exists, now collect overloads std::vector overloads; - for (auto idx : methods) { - overloads.push_back( - new CPPFunction(scope, Cppyy::GetMethod(scope, idx))); + for (auto method : methods) { + overloads.push_back(new CPPFunction(scope, method)); } // Note: can't re-use Utility::AddClass here, as there's the risk of @@ -382,23 +383,17 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) attr = (PyObject*)CPPOverload_New(name, overloads); templated_functions_checked = true; } - - // tickle lazy lookup of data members - if (!attr) { - Cppyy::TCppIndex_t dmi = Cppyy::GetDatamemberIndex(scope, name); - if (dmi != (Cppyy::TCppIndex_t)-1) attr = (PyObject*)CPPDataMember_New(scope, dmi); - } } - // this may be a typedef that resolves to a sugared type if (!attr) { - const std::string& lookup = Cppyy::GetScopedFinalName(klass->fCppType) + "::" + name; - const std::string& resolved = Cppyy::ResolveName(lookup); - if (resolved != lookup) { - const std::string& cpd = TypeManip::compound(resolved); + Cppyy::TCppScope_t lookup_result = Cppyy::GetNamed(name, scope); + if (Cppyy::IsVariable(lookup_result) || Cppyy::IsEnumConstant(lookup_result)) { + attr = (PyObject*)CPPDataMember_New(scope, lookup_result); + } else if (Cppyy::IsTypedefed(lookup_result)) { + Cppyy::TCppType_t resolved_type = Cppyy::ResolveType(Cppyy::GetTypeFromScope(lookup_result)); + const std::string& cpd = TypeManip::compound(Cppyy::GetTypeAsString(resolved_type)); if (cpd == "*") { - const std::string& clean = TypeManip::clean_type(resolved, false, true); - Cppyy::TCppType_t tcl = Cppyy::GetScope(clean); + Cppyy::TCppScope_t tcl = Cppyy::GetScopeFromType(Cppyy::ResolveType(resolved_type)); if (tcl) { typedefpointertoclassobject* tpc = PyObject_New(typedefpointertoclassobject, &TypedefPointerToClass_Type); @@ -423,11 +418,10 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) // enums types requested as type (rather than the constants) if (!attr) { - // TODO: IsEnum should deal with the scope, using klass->GetListOfEnums()->FindObject() - const std::string& ename = scope == Cppyy::gGlobalScope ? name : Cppyy::GetScopedFinalName(scope)+"::"+name; - if (Cppyy::IsEnum(ename)) { + Cppyy::TCppScope_t enumerator = Cppyy::GetUnderlyingScope(Cppyy::GetNamed(name, scope)); + if (Cppyy::IsEnumScope(enumerator)) { // enum types (incl. named and class enums) - attr = (PyObject*)CPPEnum_New(name, scope); + attr = (PyObject*)CPPEnum_New(name, enumerator); } else { // for completeness in error reporting PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ enum", name.c_str()); @@ -439,7 +433,9 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) // cache the result if (CPPDataMember_Check(attr)) { PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pyname, attr); + Py_DECREF(attr); + // The call below goes through "dm_get" attr = PyType_Type.tp_getattro(pyclass, pyname); if (!attr && PyErr_Occurred()) Utility::FetchError(errors); @@ -504,11 +500,15 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) } if (attr) { + std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear); PyErr_Clear(); } else { // not found: prepare a full error report PyObject* topmsg = nullptr; + PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); PyObject* sklass = PyObject_Str(pyclass); + PyErr_Restore(pytype, pyvalue, pytrace); if (sklass) { topmsg = CPyCppyy_PyText_FromFormat("%s has no attribute \'%s\'. Full details:", CPyCppyy_PyText_AsString(sklass), CPyCppyy_PyText_AsString(pyname)); @@ -517,7 +517,7 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) topmsg = CPyCppyy_PyText_FromFormat("no such attribute \'%s\'. Full details:", CPyCppyy_PyText_AsString(pyname)); } - SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_AttributeError /* default error */); + SetDetailedException(errors, topmsg /* steals */, PyExc_AttributeError /* default error */); } return attr; @@ -531,16 +531,13 @@ static int meta_setattro(PyObject* pyclass, PyObject* pyname, PyObject* pyval) // the C++ side, b/c there is no descriptor yet. This triggers the creation for // for such data as necessary. The many checks to narrow down the specific case // are needed to prevent unnecessary lookups and recursion. - if (((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace) { // skip if the given pyval is a descriptor already, or an unassignable class - if (!CPyCppyy::CPPDataMember_Check(pyval) && !CPyCppyy::CPPScope_Check(pyval)) { - std::string name = CPyCppyy_PyText_AsString(pyname); - Cppyy::TCppIndex_t dmi = Cppyy::GetDatamemberIndex(((CPPScope*)pyclass)->fCppType, name); - if (dmi != (Cppyy::TCppIndex_t)-1) - meta_getattro(pyclass, pyname); // triggers creation - } + if (!CPyCppyy::CPPDataMember_Check(pyval) && !CPyCppyy::CPPScope_Check(pyval)) { + std::string name = CPyCppyy_PyText_AsString(pyname); + if (Cppyy::GetNamed(name, ((CPPScope*)pyclass)->fCppType)) + meta_getattro(pyclass, pyname); // triggers creation } - PyErr_Clear(); // creation might have failed + return PyType_Type.tp_setattro(pyclass, pyname, pyval); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h index 7a6959fa260e6..6aecfdca05b9c 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h @@ -51,9 +51,9 @@ class CPPScope { kNoPrettyPrint = 0x0400 }; public: - PyHeapTypeObject fType; - Cppyy::TCppType_t fCppType; - uint32_t fFlags; + PyHeapTypeObject fType; + Cppyy::TCppScope_t fCppType; + uint32_t fFlags; union { CppToPyMap_t* fCppObjects; // classes only std::vector* fUsing; // namespaces only @@ -69,7 +69,7 @@ typedef CPPScope CPPClass; class CPPSmartClass : public CPPClass { public: - Cppyy::TCppType_t fUnderlyingType; + Cppyy::TCppScope_t fUnderlyingType; Cppyy::TCppMethod_t fDereferencer; }; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx index 5c5d374254c11..4feedb5dc171f 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx @@ -36,6 +36,8 @@ PyObject* Instance_FromVoidPtr( #include +std::map TypeReductionMap; + // Note: as of py3.11, dictionary objects no longer carry a function pointer for // the lookup, so it can no longer be shimmed and "from cppyy.interactive import *" // thus no longer works. @@ -222,13 +224,23 @@ static PyTypeObject PyDefault_t_Type = { namespace { -struct { - PyObject_HEAD -} _CPyCppyy_NullPtrStruct{PyObject_HEAD_INIT(&PyNullPtr_t_Type)}; +PyObject _CPyCppyy_NullPtrStruct = {_PyObject_EXTRA_INIT +// In 3.12.0-beta this field was changed from a ssize_t to a union +#if PY_VERSION_HEX >= 0x30c00b1 + {1}, +#else + 1, +#endif + &PyNullPtr_t_Type}; -struct { - PyObject_HEAD -} _CPyCppyy_DefaultStruct{PyObject_HEAD_INIT(&PyDefault_t_Type)}; +PyObject _CPyCppyy_DefaultStruct = {_PyObject_EXTRA_INIT +// In 3.12.0-beta this field was changed from a ssize_t to a union +#if PY_VERSION_HEX >= 0x30c00b1 + {1}, +#else + 1, +#endif + &PyDefault_t_Type}; // TODO: refactor with Converters.cxx struct CPyCppyy_tagCDataObject { // non-public (but stable) @@ -448,7 +460,7 @@ static PyObject* SetCppLazyLookup(PyObject*, PyObject* args) } //---------------------------------------------------------------------------- -static PyObject* MakeCppTemplateClass(PyObject*, PyObject* args) +static PyObject* MakeCppTemplateClass(PyObject* /* self */, PyObject* args) { // Create a binding for a templated class instantiation. @@ -458,14 +470,31 @@ static PyObject* MakeCppTemplateClass(PyObject*, PyObject* args) PyErr_Format(PyExc_TypeError, "too few arguments for template instantiation"); return nullptr; } + PyObject *cppscope = PyTuple_GET_ITEM(args, 0); + void * tmpl = PyLong_AsVoidPtr(cppscope); // build "< type, type, ... >" part of class name (modifies pyname) - const std::string& tmpl_name = - Utility::ConstructTemplateArgs(PyTuple_GET_ITEM(args, 0), args, nullptr, Utility::kNone, 1); - if (!tmpl_name.size()) - return nullptr; + std::vector types = + Utility::GetTemplateArgsTypes(cppscope, args, nullptr, Utility::kNone, 1); + if (PyErr_Occurred()) + return nullptr; + + Cppyy::TCppScope_t scope = + Cppyy::InstantiateTemplate(tmpl, types.data(), types.size()); + for (Cpp::TemplateArgInfo i: types) { + if (i.m_IntegralValue) + std::free((void*)i.m_IntegralValue); + } - return CreateScopeProxy(tmpl_name); + if (!scope) { + PyErr_Format(PyExc_TypeError, + "Template instantiation failed: '%s' with args: '%s\n'", + Cppyy::GetScopedFinalName(cppscope).c_str(), + CPyCppyy_PyText_AsString(PyObject_Repr(args))); + return nullptr; + } + + return CreateScopeProxy(scope); } //---------------------------------------------------------------------------- @@ -674,7 +703,7 @@ static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) } // convert 2nd argument first (used for both pointer value and instance cases) - Cppyy::TCppType_t cast_type = 0; + Cppyy::TCppScope_t cast_type = 0; PyObject* arg1 = PyTuple_GET_ITEM(args, 1); if (!CPyCppyy_PyText_Check(arg1)) { // not string, then class if (CPPScope_Check(arg1)) @@ -685,7 +714,7 @@ static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) Py_INCREF(arg1); if (!cast_type && arg1) { - cast_type = (Cppyy::TCppType_t)Cppyy::GetScope(CPyCppyy_PyText_AsString(arg1)); + cast_type = (Cppyy::TCppScope_t)Cppyy::GetScope(CPyCppyy_PyText_AsString(arg1)); Py_DECREF(arg1); } @@ -702,7 +731,7 @@ static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) // if this instance's class has a relation to the requested one, calculate the // offset, erase if from any caches, and update the pointer and type CPPInstance* arg0_pyobj = (CPPInstance*)arg0; - Cppyy::TCppType_t cur_type = arg0_pyobj->ObjectIsA(false /* check_smart */); + Cppyy::TCppScope_t cur_type = arg0_pyobj->ObjectIsA(false /* check_smart */); bool isPython = CPPScope_Check(arg1) && \ (((CPPClass*)arg1)->fFlags & CPPScope::kIsPython); @@ -713,12 +742,12 @@ static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) } int direction = 0; - Cppyy::TCppType_t base = 0, derived = 0; - if (Cppyy::IsSubtype(cast_type, cur_type)) { + Cppyy::TCppScope_t base = 0, derived = 0; + if (Cppyy::IsSubclass(cast_type, cur_type)) { derived = cast_type; base = cur_type; direction = -1; // down-cast - } else if (Cppyy::IsSubtype(cur_type, cast_type)) { + } else if (Cppyy::IsSubclass(cur_type, cast_type)) { base = cast_type; derived = cur_type; direction = 1; // up-cast @@ -778,7 +807,7 @@ static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) // not a pre-existing object; get the address and bind void* addr = nullptr; - if (arg0 != gNullPtrObject) { + if (arg0 != &_CPyCppyy_NullPtrStruct) { addr = CPyCppyy_PyCapsule_GetPointer(arg0, nullptr); if (PyErr_Occurred()) { PyErr_Clear(); @@ -892,7 +921,9 @@ static PyObject* AddTypeReducer(PyObject*, PyObject* args) if (!PyArg_ParseTuple(args, const_cast("ss"), &reducable, &reduced)) return nullptr; - Cppyy::AddTypeReducer(reducable, reduced); + Cppyy::TCppType_t reducable_type = Cppyy::GetTypeFromScope(Cppyy::GetScope(reducable)); + Cppyy::TCppType_t reduced_type = Cppyy::GetTypeFromScope(Cppyy::GetScope(reduced)); + TypeReductionMap[reducable_type] = reduced_type; Py_RETURN_NONE; } @@ -1052,6 +1083,8 @@ static struct PyModuleDef moduledef = { }; #endif + +#define CPYCPPYY_INIT_ERROR return nullptr namespace CPyCppyy { //---------------------------------------------------------------------------- @@ -1090,7 +1123,7 @@ PyObject* Init() gThisModule = Py_InitModule(const_cast("libcppyy"), gCPyCppyyMethods); #endif if (!gThisModule) - return nullptr; + CPYCPPYY_INIT_ERROR; // keep gThisModule, but do not increase its reference count even as it is borrowed, // or a self-referencing cycle would be created @@ -1104,58 +1137,58 @@ PyObject* Init() // inject meta type if (!Utility::InitProxy(gThisModule, &CPPScope_Type, "CPPScope")) - return nullptr; + CPYCPPYY_INIT_ERROR; // inject object proxy type if (!Utility::InitProxy(gThisModule, &CPPInstance_Type, "CPPInstance")) - return nullptr; + CPYCPPYY_INIT_ERROR; // inject exception object proxy type if (!Utility::InitProxy(gThisModule, &CPPExcInstance_Type, "CPPExcInstance")) - return nullptr; + CPYCPPYY_INIT_ERROR; // inject method proxy type if (!Utility::InitProxy(gThisModule, &CPPOverload_Type, "CPPOverload")) - return nullptr; + CPYCPPYY_INIT_ERROR; // inject template proxy type if (!Utility::InitProxy(gThisModule, &TemplateProxy_Type, "TemplateProxy")) - return nullptr; + CPYCPPYY_INIT_ERROR; // inject property proxy type if (!Utility::InitProxy(gThisModule, &CPPDataMember_Type, "CPPDataMember")) - return nullptr; + CPYCPPYY_INIT_ERROR; // inject custom data types #if PY_VERSION_HEX < 0x03000000 if (!Utility::InitProxy(gThisModule, &RefFloat_Type, "Double")) - return nullptr; + CPYCPPYY_INIT_ERROR; if (!Utility::InitProxy(gThisModule, &RefInt_Type, "Long")) - return nullptr; + CPYCPPYY_INIT_ERROR; #endif if (!Utility::InitProxy(gThisModule, &CustomInstanceMethod_Type, "InstanceMethod")) - return nullptr; + CPYCPPYY_INIT_ERROR; if (!Utility::InitProxy(gThisModule, &TupleOfInstances_Type, "InstanceArray")) - return nullptr; + CPYCPPYY_INIT_ERROR; if (!Utility::InitProxy(gThisModule, &LowLevelView_Type, "LowLevelView")) - return nullptr; + CPYCPPYY_INIT_ERROR; if (!Utility::InitProxy(gThisModule, &PyNullPtr_t_Type, "nullptr_t")) - return nullptr; + CPYCPPYY_INIT_ERROR; // custom iterators if (PyType_Ready(&InstanceArrayIter_Type) < 0) - return nullptr; + CPYCPPYY_INIT_ERROR; if (PyType_Ready(&IndexIter_Type) < 0) - return nullptr; + CPYCPPYY_INIT_ERROR; if (PyType_Ready(&VectorIter_Type) < 0) - return nullptr; + CPYCPPYY_INIT_ERROR; // inject identifiable nullptr and default gNullPtrObject = (PyObject*)&_CPyCppyy_NullPtrStruct; @@ -1179,6 +1212,12 @@ PyObject* Init() gAbrtException = PyErr_NewException((char*)"cppyy.ll.AbortSignal", cppfatal, nullptr); PyModule_AddObject(gThisModule, (char*)"AbortSignal", gAbrtException); +// policy labels + PyModule_AddObject(gThisModule, (char*)"kMemoryHeuristics", + PyInt_FromLong((int)CallContext::kUseHeuristics)); + PyModule_AddObject(gThisModule, (char*)"kMemoryStrict", + PyInt_FromLong((int)CallContext::kUseStrict)); + // gbl namespace is injected in cppyy.py // create the memory regulator @@ -1186,8 +1225,8 @@ PyObject* Init() #if PY_VERSION_HEX >= 0x03000000 Py_INCREF(gThisModule); -#endif return gThisModule; +#endif } } // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx index 416741d0caab1..794c2e4ed294c 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx @@ -9,6 +9,15 @@ uint32_t &CPyCppyy::CallContext::GlobalPolicyFlags() return flags; } +//- data _____________________________________________________________________ +namespace CPyCppyy { + + CallContext::ECallFlags CallContext::sMemoryPolicy = CallContext::kUseStrict; +// this is just a data holder for linking; actual value is set in CPyCppyyModule.cxx + CallContext::ECallFlags CallContext::sSignalPolicy = CallContext::kNone; + +} // namespace CPyCppyy + //----------------------------------------------------------------------------- void CPyCppyy::CallContext::AddTemporary(PyObject* pyobj) { if (pyobj) { @@ -35,6 +44,26 @@ void CPyCppyy::CallContext::Cleanup() { } //----------------------------------------------------------------------------- +bool CPyCppyy::CallContext::SetMemoryPolicy(ECallFlags e) +{ +// Set the global memory policy, which affects object ownership when objects +// are passed as function arguments. + if (kUseHeuristics == e || e == kUseStrict) { + sMemoryPolicy = e; + return true; + } + return false; +} + +//----------------------------------------------------------------------------- +bool CPyCppyy::CallContext::SetGlobalSignalPolicy(bool setProtected) +{ +// Set the global signal policy, which determines whether a jmp address +// should be saved to return to after a C++ segfault. + bool old = sSignalPolicy == kProtected; + sSignalPolicy = setProtected ? kProtected : kNone; + return old; +} bool CPyCppyy::CallContext::SetGlobalPolicy(ECallFlags toggleFlag, bool enabled) { auto &flags = GlobalPolicyFlags(); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h index 4b88bfdd3a6fb..da15d997ff41b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h @@ -69,6 +69,7 @@ struct CallContext { kUseHeuristics = 0x000100, // if method applies heuristics memory policy kImplicitSmartPtrConversion = 0x000200, // enable implicit conversion to smart pointers kReleaseGIL = 0x000400, // if method should release the GIL + kUseStrict = 0x000600, // if method applies strict memory policy kSetLifeLine = 0x000800, // if return value is part of 'this' kNeverLifeLine = 0x001000, // if the return value is never part of 'this' kPyException = 0x002000, // Python exception during method execution @@ -78,6 +79,10 @@ struct CallContext { kIsPseudoFunc = 0x020000, // internal, used for introspection }; +// memory handling + static ECallFlags sMemoryPolicy; + static bool SetMemoryPolicy(ECallFlags e); + // Policies about memory handling and signal safety static bool SetGlobalPolicy(ECallFlags e, bool enabled); @@ -86,6 +91,10 @@ struct CallContext { void AddTemporary(PyObject* pyobj); void Cleanup(); +// signal safety + static ECallFlags sSignalPolicy; + static bool SetGlobalSignalPolicy(bool setProtected); + Parameter* GetArgs(size_t sz) { if (sz != (size_t)-1) fNArgs = sz; if (fNArgs <= SMALL_ARGS_N) return fArgs; @@ -151,6 +160,15 @@ inline bool UseStrictOwnership() { return !(CC::GlobalPolicyFlags() & CC::kUseHeuristics); } +inline bool UseStrictOwnership(CallContext* ctxt) { + if (ctxt && (ctxt->fFlags & CallContext::kUseStrict)) + return true; + if (ctxt && (ctxt->fFlags & CallContext::kUseHeuristics)) + return false; + + return CallContext::sMemoryPolicy == CallContext::kUseStrict; +} + template class CallContextRAII { public: diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index ffd38fb14a8bd..c8063b9f28533 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -29,9 +29,6 @@ #include #include #include -#if __cplusplus >= 202002L -#include -#endif // codecvt does not exist for gcc4.8.5 and is in principle deprecated; it is // only used in py2 for char -> wchar_t conversion for std::wstring; if not @@ -64,9 +61,6 @@ namespace CPyCppyy { static std::regex s_fnptr("\\((\\w*:*)*\\*&*\\)"); } -// Define our own PyUnstable_Object_IsUniqueReferencedTemporary function if the -// Python version is lower than 3.14, the version where that function got introduced. -#if PY_VERSION_HEX < 0x030e0000 #if PY_VERSION_HEX < 0x03000000 const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; #elif PY_VERSION_HEX < 0x03080000 @@ -79,10 +73,6 @@ const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 2; // since py3.8, vector calls behave again as expected const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; #endif -inline bool PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *pyobject) { - return Py_REFCNT(pyobject) <= MOVE_REFCOUNT_CUTOFF; -} -#endif //- pretend-ctypes helpers --------------------------------------------------- struct CPyCppyy_tagCDataObject { // non-public (but stable) @@ -134,9 +124,7 @@ struct CPyCppyy_tagPyCArgObject { // not public (but stable; note that olde #define ct_c_complex 22 #define ct_c_pointer 23 #define ct_c_funcptr 24 -#define ct_c_int16 25 -#define ct_c_int32 26 -#define NTYPES 27 +#define NTYPES 25 static std::array gCTypesNames = { "c_bool", "c_char", "c_wchar", "c_byte", "c_ubyte", "c_short", "c_ushort", "c_uint16", @@ -401,10 +389,6 @@ static inline type CPyCppyy_PyLong_As##name(PyObject* pyobject) \ CPPYY_PYLONG_AS_TYPE(UInt8, uint8_t, 0, UCHAR_MAX) CPPYY_PYLONG_AS_TYPE(Int8, int8_t, SCHAR_MIN, SCHAR_MAX) -CPPYY_PYLONG_AS_TYPE(UInt16, uint16_t, 0, UINT16_MAX) -CPPYY_PYLONG_AS_TYPE(Int16, int16_t, INT16_MIN, INT16_MAX) -CPPYY_PYLONG_AS_TYPE(UInt32, uint32_t, 0, UINT32_MAX) -CPPYY_PYLONG_AS_TYPE(Int32, int32_t, INT32_MIN, INT32_MAX) CPPYY_PYLONG_AS_TYPE(UShort, unsigned short, 0, USHRT_MAX) CPPYY_PYLONG_AS_TYPE(Short, short, SHRT_MIN, SHRT_MAX) CPPYY_PYLONG_AS_TYPE(StrictInt, int, INT_MIN, INT_MAX) @@ -553,9 +537,10 @@ bool CPyCppyy::Converter::ToMemory(PyObject*, void*, PyObject* /* ctxt */) if (val == (type)-1 && PyErr_Occurred()) { \ static PyTypeObject* ctypes_type = nullptr; \ if (!ctypes_type) { \ - auto error = CPyCppyy::Utility::FetchPyError(); \ + PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; \ + PyErr_Fetch(&pytype, &pyvalue, &pytrace); \ ctypes_type = GetCTypesType(ct_##ctype); \ - CPyCppyy::Utility::RestorePyError(error); \ + PyErr_Restore(pytype, pyvalue, pytrace); \ } \ if (Py_TYPE(pyobject) == ctypes_type) { \ PyErr_Clear(); \ @@ -800,10 +785,6 @@ CPPYY_IMPL_BASIC_CONST_CHAR_REFCONVERTER(UChar, unsigned char, c_uchar, 0 CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Bool, bool, c_bool, CPyCppyy_PyLong_AsBool) CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int8, int8_t, c_int8, CPyCppyy_PyLong_AsInt8) CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt8, uint8_t, c_uint8, CPyCppyy_PyLong_AsUInt8) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int16, int16_t, c_int16, CPyCppyy_PyLong_AsInt16) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt16, uint16_t, c_uint16, CPyCppyy_PyLong_AsUInt16) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int32, int32_t, c_int32, CPyCppyy_PyLong_AsInt32) -CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UInt32, uint32_t, c_uint32, CPyCppyy_PyLong_AsUInt32) CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Short, short, c_short, CPyCppyy_PyLong_AsShort) CPPYY_IMPL_BASIC_CONST_REFCONVERTER(UShort, unsigned short, c_ushort, CPyCppyy_PyLong_AsUShort) CPPYY_IMPL_BASIC_CONST_REFCONVERTER(Int, int, c_int, CPyCppyy_PyLong_AsStrictInt) @@ -879,10 +860,6 @@ CPPYY_IMPL_REFCONVERTER(SChar, c_byte, signed char, 'b'); CPPYY_IMPL_REFCONVERTER(UChar, c_ubyte, unsigned char, 'B'); CPPYY_IMPL_REFCONVERTER(Int8, c_int8, int8_t, 'b'); CPPYY_IMPL_REFCONVERTER(UInt8, c_uint8, uint8_t, 'B'); -CPPYY_IMPL_REFCONVERTER(Int16, c_int16, int16_t, 'h'); -CPPYY_IMPL_REFCONVERTER(UInt16, c_uint16, uint16_t, 'H'); -CPPYY_IMPL_REFCONVERTER(Int32, c_int32, int32_t, 'i'); -CPPYY_IMPL_REFCONVERTER(UInt32, c_uint32, uint32_t, 'I'); CPPYY_IMPL_REFCONVERTER(Short, c_short, short, 'h'); CPPYY_IMPL_REFCONVERTER(UShort, c_ushort, unsigned short, 'H'); CPPYY_IMPL_REFCONVERTER_FROM_MEMORY(Int, c_int); @@ -1041,14 +1018,6 @@ CPPYY_IMPL_BASIC_CONVERTER_IB( Int8, int8_t, long, c_int8, PyInt_FromLong, CPyCppyy_PyLong_AsInt8, 'l') CPPYY_IMPL_BASIC_CONVERTER_IB( UInt8, uint8_t, long, c_uint8, PyInt_FromLong, CPyCppyy_PyLong_AsUInt8, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int16, int16_t, long, c_int16, PyInt_FromLong, CPyCppyy_PyLong_AsInt16, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UInt16, uint16_t, long, c_uint16, PyInt_FromLong, CPyCppyy_PyLong_AsUInt16, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - Int32, int32_t, long, c_int32, PyInt_FromLong, CPyCppyy_PyLong_AsInt32, 'l') -CPPYY_IMPL_BASIC_CONVERTER_IB( - UInt32, uint32_t, long, c_uint32, PyInt_FromLong, CPyCppyy_PyLong_AsUInt32, 'l') CPPYY_IMPL_BASIC_CONVERTER_IB( Short, short, long, c_short, PyInt_FromLong, CPyCppyy_PyLong_AsShort, 'l') CPPYY_IMPL_BASIC_CONVERTER_IB( @@ -1125,7 +1094,7 @@ CPPYY_IMPL_BASIC_CONVERTER_NB( LDouble, PY_LONG_DOUBLE, PY_LONG_DOUBLE, c_longdouble, PyFloat_FromDouble, PyFloat_AsDouble, 'g') CPyCppyy::ComplexDConverter::ComplexDConverter(bool keepControl) : - InstanceConverter(Cppyy::GetScope("std::complex"), keepControl) {} + InstanceConverter(Cppyy::GetFullScope("std::complex"), keepControl) {} // special case for std::complex, maps it to/from Python's complex bool CPyCppyy::ComplexDConverter::SetArg( @@ -1184,7 +1153,7 @@ bool CPyCppyy::DoubleRefConverter::SetArg( // alternate, pass pointer from buffer Py_ssize_t buflen = Utility::GetBuffer(pyobject, 'd', sizeof(double), para.fValue.fVoidp); - if (para.fValue.fVoidp && buflen) { + if (buflen && para.fValue.fVoidp) { para.fTypeCode = 'V'; return true; } @@ -1291,21 +1260,22 @@ bool CPyCppyy::CStringConverter::SetArg( const char* cstr = CPyCppyy_PyText_AsStringAndSize(pyobject, &len); if (!cstr) { // special case: allow ctypes c_char_p - auto error = CPyCppyy::Utility::FetchPyError(); + PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); if (Py_TYPE(pyobject) == GetCTypesType(ct_c_char_p)) { SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; para.fTypeCode = 'V'; + Py_XDECREF(pytype); Py_XDECREF(pyvalue); Py_XDECREF(pytrace); return true; } - CPyCppyy::Utility::RestorePyError(error); + PyErr_Restore(pytype, pyvalue, pytrace); return false; } // verify (too long string will cause truncation, no crash) if (fMaxSize != std::string::npos && fMaxSize < fBuffer.size()) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) - return false; + PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)"); if (!ctxt->fPyContext) { // use internal buffer as workaround @@ -1350,8 +1320,7 @@ bool CPyCppyy::CStringConverter::ToMemory(PyObject* value, void* address, PyObje // verify (too long string will cause truncation, no crash) if (fMaxSize != std::string::npos && fMaxSize < (std::string::size_type)len) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) - return false; + PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)"); // if address is available, and it wasn't set by this converter, assume a byte-wise copy; // otherwise assume a pointer copy (this relies on the converter to be used for properties, @@ -1428,8 +1397,7 @@ bool CPyCppyy::WCStringConverter::ToMemory(PyObject* value, void* address, PyObj // verify (too long string will cause truncation, no crash) if (fMaxSize != std::wstring::npos && fMaxSize < (std::wstring::size_type)len) - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for wchar_t array (truncated)", 1) < 0) - return false; + PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for wchar_t array (truncated)"); Py_ssize_t res = -1; if (fMaxSize != std::wstring::npos) @@ -1488,10 +1456,7 @@ bool CPyCppyy::name##Converter::ToMemory(PyObject* value, void* address, PyObjec \ /* verify (too long string will cause truncation, no crash) */ \ if (fMaxSize != std::wstring::npos && maxbytes < len) { \ - if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for "#type" array (truncated)", 1) < 0) { \ - Py_DECREF(bstr); \ - return false; \ - } \ + PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for "#type" array (truncated)");\ len = maxbytes; \ } \ \ @@ -1558,13 +1523,14 @@ bool CPyCppyy::VoidArrayConverter::GetAddressSpecialCase(PyObject* pyobject, voi //---------------------------------------------------------------------------- bool CPyCppyy::VoidArrayConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/) + PyObject* pyobject, Parameter& para, CallContext* ctxt) { // just convert pointer if it is a C++ object CPPInstance* pyobj = GetCppInstance(pyobject); + para.fValue.fVoidp = nullptr; if (pyobj) { // depending on memory policy, some objects are no longer owned when passed to C++ - if (!fKeepControl && !UseStrictOwnership()) + if (!fKeepControl && !UseStrictOwnership(ctxt)) pyobj->CppOwns(); // set pointer (may be null) and declare success @@ -1629,7 +1595,7 @@ bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyOb CPPInstance* pyobj = GetCppInstance(value); if (pyobj) { // depending on memory policy, some objects are no longer owned when passed to C++ - if (!fKeepControl && !UseStrictOwnership()) + if (!fKeepControl && CallContext::sMemoryPolicy != CallContext::kUseStrict) pyobj->CppOwns(); // set pointer (may be null) and declare success @@ -1654,114 +1620,6 @@ bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyOb return true; } -#if __cplusplus >= 202002L - -namespace CPyCppyy { - -class StdSpanConverter : public InstanceConverter { -public: - StdSpanConverter(std::string const &typeName, Cppyy::TCppType_t klass, bool keepControl = false) - : InstanceConverter{klass, keepControl}, fTypeName{typeName} - { - } - - ~StdSpanConverter() - { - if (fHasBuffer) { - PyBuffer_Release(&fBufinfo); - } - } - - bool SetArg(PyObject *, Parameter &, CallContext * = nullptr) override; - bool HasState() override { return true; } - -private: - std::string fTypeName; - std::span fBuffer; - bool fHasBuffer = false; - Py_buffer fBufinfo; -}; - -} // namespace CPyCppyy - -//---------------------------------------------------------------------------- -bool CPyCppyy::StdSpanConverter::SetArg(PyObject *pyobject, Parameter ¶, CallContext *ctxt) -{ - auto typecodeFound = Utility::TypecodeMap().find(fTypeName); - -// attempt to get buffer if the C++ type maps to a buffer type - if (typecodeFound == Utility::TypecodeMap().end() || !PyObject_CheckBuffer(pyobject)) { - // Fall back to regular InstanceConverter - return this->InstanceConverter::SetArg(pyobject, para, ctxt); - } - - Py_ssize_t buflen = 0; - char typecode = typecodeFound->second; - memset(&fBufinfo, 0, sizeof(Py_buffer)); - - if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { - if (!strchr(fBufinfo.format, typecode)) { - PyErr_Format(PyExc_TypeError, - "buffer has incompatible type: expected '%c' for C++ type '%s', but got format '%s'", typecode, - fTypeName.c_str(), fBufinfo.format ? fBufinfo.format : ""); - PyBuffer_Release(&fBufinfo); - return false; - } - buflen = Utility::GetBuffer(pyobject, typecode, 1, para.fValue.fVoidp, false); - } - -// ok if buffer exists (can't perform any useful size checks) - if (para.fValue.fVoidp && buflen != 0) { - // We assume the layout for any std::span is the same, and just use - // std::span as a placeholder. Not elegant, but works. - fBuffer = std::span{(std::size_t *)para.fValue.fVoidp, static_cast(buflen)}; - fHasBuffer = true; - para.fValue.fVoidp = &fBuffer; - para.fTypeCode = 'V'; - return true; - } - - return false; -} - -#endif // __cplusplus >= 202002L - -namespace { - -// Copy a buffer to memory address with an array converter. -template -bool ToArrayFromBuffer(PyObject* owner, void* address, PyObject* ctxt, - const void * buf, Py_ssize_t buflen, - CPyCppyy::dims_t& shape, bool isFixed) -{ - if (buflen == 0) - return false; - - Py_ssize_t oldsz = 1; - for (Py_ssize_t idim = 0; idim < shape.ndim(); ++idim) { - if (shape[idim] == CPyCppyy::UNKNOWN_SIZE) { - oldsz = -1; - break; - } - oldsz *= shape[idim]; - } - if (shape.ndim() != CPyCppyy::UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { - PyErr_SetString(PyExc_ValueError, "buffer too large for value"); - return false; - } - - if (isFixed) - memcpy(*(type**)address, buf, (0 < buflen ? buflen : 1)*sizeof(type)); - else { - *(type**)address = (type*)buf; - shape.ndim(1); - shape[0] = buflen; - SetLifeLine(ctxt, owner, (intptr_t)address); - } - return true; -} - -} //---------------------------------------------------------------------------- #define CPPYY_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ @@ -1842,7 +1700,31 @@ bool CPyCppyy::name##ArrayConverter::ToMemory( \ if (fShape.ndim() <= 1 || fIsFixed) { \ void* buf = nullptr; \ Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(type), buf);\ - return ToArrayFromBuffer(value, address, ctxt, buf, buflen, fShape, fIsFixed);\ + if (buflen == 0) \ + return false; \ + \ + Py_ssize_t oldsz = 1; \ + for (Py_ssize_t idim = 0; idim < fShape.ndim(); ++idim) { \ + if (fShape[idim] == UNKNOWN_SIZE) { \ + oldsz = -1; \ + break; \ + } \ + oldsz *= fShape[idim]; \ + } \ + if (fShape.ndim() != UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { \ + PyErr_SetString(PyExc_ValueError, "buffer too large for value"); \ + return false; \ + } \ + \ + if (fIsFixed) \ + memcpy(*(type**)address, buf, (0 < buflen ? buflen : 1)*sizeof(type));\ + else { \ + *(type**)address = (type*)buf; \ + fShape.ndim(1); \ + fShape[0] = buflen; \ + SetLifeLine(ctxt, value, (intptr_t)address); \ + } \ + \ } else { /* multi-dim, non-flat array; assume structure matches */ \ void* buf = nullptr; /* TODO: GetBuffer() assumes flat? */ \ Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(void*), buf);\ @@ -1860,11 +1742,7 @@ CPPYY_IMPL_ARRAY_CONVERTER(SChar, c_char, signed char, 'b', ) CPPYY_IMPL_ARRAY_CONVERTER(UChar, c_ubyte, unsigned char, 'B', ) CPPYY_IMPL_ARRAY_CONVERTER(Byte, c_ubyte, std::byte, 'B', ) CPPYY_IMPL_ARRAY_CONVERTER(Int8, c_byte, int8_t, 'b', _i8) -CPPYY_IMPL_ARRAY_CONVERTER(Int16, c_int16, int16_t, 'h', _i16) -CPPYY_IMPL_ARRAY_CONVERTER(Int32, c_int32, int32_t, 'i', _i32) CPPYY_IMPL_ARRAY_CONVERTER(UInt8, c_ubyte, uint8_t, 'B', _i8) -CPPYY_IMPL_ARRAY_CONVERTER(UInt16, c_uint16, uint16_t, 'H', _i16) -CPPYY_IMPL_ARRAY_CONVERTER(UInt32, c_uint32, uint32_t, 'I', _i32) CPPYY_IMPL_ARRAY_CONVERTER(Short, c_short, short, 'h', ) CPPYY_IMPL_ARRAY_CONVERTER(UShort, c_ushort, unsigned short, 'H', ) CPPYY_IMPL_ARRAY_CONVERTER(Int, c_int, int, 'i', ) @@ -1943,18 +1821,6 @@ PyObject* CPyCppyy::CStringArrayConverter::FromMemory(void* address) return CreateLowLevelViewString(*(const char***)address, fShape); } -//---------------------------------------------------------------------------- -bool CPyCppyy::CStringArrayConverter::ToMemory(PyObject* value, void* address, PyObject* ctxt) -{ -// As a special array converter, the CStringArrayConverter one can also copy strings in the array, -// and not only buffers. - Py_ssize_t len; - if (const char* cstr = CPyCppyy_PyText_AsStringAndSize(value, &len)) { - return ToArrayFromBuffer(value, address, ctxt, cstr, len, fShape, fIsFixed); - } - return SCharArrayConverter::ToMemory(value, address, ctxt); -} - //---------------------------------------------------------------------------- PyObject* CPyCppyy::NonConstCStringArrayConverter::FromMemory(void* address) { @@ -2008,7 +1874,7 @@ static inline bool CPyCppyy_PyUnicodeAsBytes2Buffer(PyObject* pyobject, T& buffe #define CPPYY_IMPL_STRING_AS_PRIMITIVE_CONVERTER(name, type, F1, F2) \ CPyCppyy::name##Converter::name##Converter(bool keepControl) : \ - InstanceConverter(Cppyy::GetScope(#type), keepControl) {} \ + InstanceConverter(Cppyy::GetFullScope(#type), keepControl) {} \ \ bool CPyCppyy::name##Converter::SetArg( \ PyObject* pyobject, Parameter& para, CallContext* ctxt) \ @@ -2049,7 +1915,7 @@ CPPYY_IMPL_STRING_AS_PRIMITIVE_CONVERTER(STLString, std::string, c_str, size) CPyCppyy::STLWStringConverter::STLWStringConverter(bool keepControl) : - InstanceConverter(Cppyy::GetScope("std::wstring"), keepControl) {} + InstanceConverter(Cppyy::GetFullScope("std::wstring"), keepControl) {} bool CPyCppyy::STLWStringConverter::SetArg( PyObject* pyobject, Parameter& para, CallContext* ctxt) @@ -2112,7 +1978,7 @@ bool CPyCppyy::STLWStringConverter::ToMemory(PyObject* value, void* address, PyO CPyCppyy::STLStringViewConverter::STLStringViewConverter(bool keepControl) : - InstanceConverter(Cppyy::GetScope("std::string_view"), keepControl) {} + InstanceConverter(Cppyy::GetFullScope("std::string_view"), keepControl) {} bool CPyCppyy::STLStringViewConverter::SetArg( PyObject* pyobject, Parameter& para, CallContext* ctxt) @@ -2144,7 +2010,7 @@ bool CPyCppyy::STLStringViewConverter::SetArg( // special case of a C++ std::string object; life-time management is left to // the caller to ensure any external changes propagate correctly if (CPPInstance_Check(pyobject)) { - static Cppyy::TCppScope_t sStringID = Cppyy::GetScope("std::string"); + static Cppyy::TCppScope_t sStringID = Cppyy::GetUnderlyingScope(Cppyy::GetFullScope("std::string")); CPPInstance* pyobj = (CPPInstance*)pyobject; if (pyobj->ObjectIsA() == sStringID) { void* ptr = pyobj->GetObject(); @@ -2202,7 +2068,7 @@ bool CPyCppyy::STLStringMoveConverter::SetArg( if (pyobj->fFlags & CPPInstance::kIsRValue) { pyobj->fFlags &= ~CPPInstance::kIsRValue; moveit_reason = 2; - } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { + } else if (pyobject->ob_refcnt <= MOVE_REFCOUNT_CUTOFF) { moveit_reason = 1; } else moveit_reason = 0; @@ -2243,9 +2109,9 @@ bool CPyCppyy::InstancePtrConverter::SetArg( return false; Cppyy::TCppType_t oisa = pyobj->ObjectIsA(); - if (oisa && (oisa == fClass || Cppyy::IsSubtype(oisa, fClass))) { + if (oisa && (oisa == fClass || Cppyy::IsSubclass(oisa, fClass))) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) + if (!KeepControl() && !UseStrictOwnership(ctxt)) pyobj->CppOwns(); // calculate offset between formal and actual arguments @@ -2290,9 +2156,9 @@ bool CPyCppyy::InstancePtrConverter::ToMemory(PyObject* value, void* ad return false; } - if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) { + if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) + if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) ((CPPInstance*)value)->CppOwns(); *(void**)address = pyobj->GetObject(); @@ -2312,7 +2178,10 @@ bool CPyCppyy::InstanceConverter::SetArg( CPPInstance* pyobj = GetCppInstance(pyobject, fClass); if (pyobj) { auto oisa = pyobj->ObjectIsA(); - if (oisa && (oisa == fClass || Cppyy::IsSubtype(oisa, fClass))) { + if (oisa && ((oisa == (Cppyy::IsTypedefed(fClass) + ? Cppyy::GetUnderlyingScope(fClass) + : fClass)) || + Cppyy::IsSubclass(oisa, fClass))) { // calculate offset between formal and actual arguments para.fValue.fVoidp = pyobj->GetObject(); if (!para.fValue.fVoidp) @@ -2379,7 +2248,7 @@ bool CPyCppyy::InstanceRefConverter::SetArg( Cppyy::TCppType_t cls = 0; if (pyobj->IsSmart()) { cls = pyobj->ObjectIsA(false); - if (cls && Cppyy::IsSubtype(cls, fClass)) { + if (cls && Cppyy::IsSubclass(cls, fClass)) { para.fValue.fVoidp = pyobj->GetObjectRaw(); argset = true; } @@ -2387,7 +2256,7 @@ bool CPyCppyy::InstanceRefConverter::SetArg( if (!argset) { cls = pyobj->ObjectIsA(); - if (cls && Cppyy::IsSubtype(cls, fClass)) { + if (cls && Cppyy::IsSubclass(cls, fClass)) { para.fValue.fVoidp = pyobj->GetObject(); argset = true; } @@ -2439,7 +2308,7 @@ bool CPyCppyy::InstanceMoveConverter::SetArg( if (pyobj->fFlags & CPPInstance::kIsRValue) { pyobj->fFlags &= ~CPPInstance::kIsRValue; moveit_reason = 2; - } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { + } else if (pyobject->ob_refcnt <= MOVE_REFCOUNT_CUTOFF) { moveit_reason = 1; } @@ -2457,7 +2326,7 @@ bool CPyCppyy::InstanceMoveConverter::SetArg( //---------------------------------------------------------------------------- template bool CPyCppyy::InstancePtrPtrConverter::SetArg( - PyObject* pyobject, Parameter& para, CallContext* /*ctxt*/) + PyObject* pyobject, Parameter& para, CallContext* ctxt) { // convert to C++ instance**, set arg for call CPPInstance* pyobj = GetCppInstance(pyobject); @@ -2471,9 +2340,9 @@ bool CPyCppyy::InstancePtrPtrConverter::SetArg( return false; // not a cppyy object (TODO: handle SWIG etc.) } - if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) { + if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) + if (!KeepControl() && !UseStrictOwnership(ctxt)) pyobj->CppOwns(); // set pointer (may be null) and declare success @@ -2512,9 +2381,9 @@ bool CPyCppyy::InstancePtrPtrConverter::ToMemory( return false; // not a cppyy object (TODO: handle SWIG etc.) } - if (Cppyy::IsSubtype(pyobj->ObjectIsA(), fClass)) { + if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership()) + if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) pyobj->CppOwns(); // register the value for potential recycling @@ -2554,7 +2423,7 @@ bool CPyCppyy::InstanceArrayConverter::SetArg( if (!CPPInstance_Check(first)) return false; // should not happen - if (Cppyy::IsSubtype(((CPPInstance*)first)->ObjectIsA(), fClass)) { + if (Cppyy::IsSubclass(((CPPInstance*)first)->ObjectIsA(), fClass)) { // no memory policies supported; set pointer (may be null) and declare success para.fValue.fVoidp = ((CPPInstance*)first)->GetObject(); para.fTypeCode = 'p'; @@ -2616,8 +2485,8 @@ bool CPyCppyy::VoidPtrRefConverter::SetArg( } //---------------------------------------------------------------------------- -CPyCppyy::VoidPtrPtrConverter::VoidPtrPtrConverter(cdims_t dims) : - fShape(dims) { +CPyCppyy::VoidPtrPtrConverter::VoidPtrPtrConverter(cdims_t dims, const std::string &failureMsg) : + fShape(dims), fFailureMsg (failureMsg) { fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; } @@ -2743,6 +2612,12 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, // function pointer. The former is direct, the latter involves a JIT-ed wrapper. static PyObject* sWrapperCacheEraser = PyCFunction_New(&gWrapperCacheEraserMethodDef, nullptr); + // FIXME: avoid string comparisons and parsing + std::string true_signature = signature; + + if (true_signature.rfind("(void)") != std::string::npos) + true_signature = true_signature.substr(0, true_signature.size() - 6) + "()"; + using namespace CPyCppyy; if (CPPOverload_Check(pyobject)) { @@ -2770,7 +2645,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, if (pytmpl->fTemplateArgs) fullname += CPyCppyy_PyText_AsString(pytmpl->fTemplateArgs); Cppyy::TCppScope_t scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - Cppyy::TCppMethod_t cppmeth = Cppyy::GetMethodTemplate(scope, fullname, signature); + Cppyy::TCppMethod_t cppmeth = Cppyy::GetMethodTemplate(scope, fullname, true_signature); if (cppmeth) { void* fptr = (void*)Cppyy::GetFunctionAddress(cppmeth, false); if (fptr) return fptr; @@ -2793,7 +2668,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, void* wpraddress = nullptr; // re-use existing wrapper if possible - auto key = rettype+signature; + auto key = rettype+true_signature; const auto& lookup = sWrapperLookup.find(key); if (lookup != sWrapperLookup.end()) { const auto& existing = lookup->second.find(pyobject); @@ -2821,7 +2696,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, return nullptr; // extract argument types - const std::vector& argtypes = TypeManip::extract_arg_types(signature); + const std::vector& argtypes = TypeManip::extract_arg_types(true_signature); int nArgs = (int)argtypes.size(); // wrapper name @@ -2865,8 +2740,8 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, // TODO: is there no easier way? static Cppyy::TCppScope_t scope = Cppyy::GetScope("__cppyy_internal"); - const auto& idx = Cppyy::GetMethodIndicesFromName(scope, wname.str()); - wpraddress = Cppyy::GetFunctionAddress(Cppyy::GetMethod(scope, idx[0]), false); + const auto& methods = Cppyy::GetMethodsFromName(scope, wname.str()); + wpraddress = Cppyy::GetFunctionAddress(methods[0], false); sWrapperReference[wpraddress] = ref; // cache the new wrapper @@ -3001,9 +2876,9 @@ bool CPyCppyy::SmartPtrConverter::SetArg( // for the case where we have a 'hidden' smart pointer: if (Cppyy::TCppType_t tsmart = pyobj->GetSmartIsA()) { - if (Cppyy::IsSubtype(tsmart, fSmartPtrType)) { + if (Cppyy::IsSubclass(tsmart, fSmartPtrType)) { // depending on memory policy, some objects need releasing when passed into functions - if (!fKeepControl && !UseStrictOwnership()) + if (!fKeepControl && !UseStrictOwnership(ctxt)) ((CPPInstance*)pyobject)->CppOwns(); // calculate offset between formal and actual arguments @@ -3020,7 +2895,7 @@ bool CPyCppyy::SmartPtrConverter::SetArg( } // for the case where we have an 'exposed' smart pointer: - if (!pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fSmartPtrType)) { + if (!pyobj->IsSmart() && Cppyy::IsSubclass(oisa, fSmartPtrType)) { // calculate offset between formal and actual arguments para.fValue.fVoidp = pyobj->GetObject(); if (oisa != fSmartPtrType) { @@ -3034,7 +2909,7 @@ bool CPyCppyy::SmartPtrConverter::SetArg( } // for the case where we have an ordinary object to convert - if ((ctxt->fFlags & CallContext::kImplicitSmartPtrConversion) && !pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fUnderlyingType)) { + if (!pyobj->IsSmart() && Cppyy::IsSubclass(oisa, fUnderlyingType)) { // create the relevant smart pointer and make the pyobject "smart" CPPInstance* pysmart = (CPPInstance*)ConvertImplicit(fSmartPtrType, pyobject, para, ctxt, false); if (!CPPInstance_Check(pysmart)) { @@ -3053,7 +2928,7 @@ bool CPyCppyy::SmartPtrConverter::SetArg( } // final option, try mapping pointer types held (TODO: do not allow for non-const ref) - if (pyobj->IsSmart() && Cppyy::IsSubtype(oisa, fUnderlyingType)) { + if (pyobj->IsSmart() && Cppyy::IsSubclass(oisa, fUnderlyingType)) { para.fValue.fVoidp = ((CPPInstance*)pyobject)->GetSmartObject(); para.fTypeCode = 'V'; return true; @@ -3120,7 +2995,7 @@ CPyCppyy::InitializerListConverter::InitializerListConverter(Cppyy::TCppType_t k : InstanceConverter{klass}, fValueTypeName{value_type}, fValueType{Cppyy::GetScope(value_type)}, - fValueSize{Cppyy::SizeOf(value_type)} + fValueSize{Cppyy::SizeOfType(Cppyy::GetType(value_type, true))} { } @@ -3214,9 +3089,8 @@ bool CPyCppyy::InitializerListConverter::SetArg( PyObject* item = PySequence_GetItem(pyobject, i); bool convert_ok = false; if (item) { - if (fConverters.empty()) - fConverters.emplace_back(CreateConverter(fValueTypeName)); - if (!fConverters.back()) { + Converter *converter = CreateConverter(fValueTypeName); + if (!converter) { if (CPPInstance_Check(item)) { // by convention, use byte copy memcpy((char*)fake->_M_array + i*fValueSize, @@ -3235,11 +3109,9 @@ bool CPyCppyy::InitializerListConverter::SetArg( entries += 1; } if (memloc) { - if (i >= fConverters.size()) { - fConverters.emplace_back(CreateConverter(fValueTypeName)); - } - convert_ok = fConverters[i]->ToMemory(item, memloc); + convert_ok = converter->ToMemory(item, memloc); } + fConverters.emplace_back(converter); } @@ -3268,25 +3140,11 @@ bool CPyCppyy::InitializerListConverter::SetArg( #endif } -namespace CPyCppyy { - -// raising converter to take out overloads -class NotImplementedConverter : public Converter { -public: - NotImplementedConverter(PyObject *errorType, std::string const &message) : fErrorType{errorType}, fMessage{message} {} - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; -private: - PyObject *fErrorType; - std::string fMessage; -}; - -} // namespace CPyCppyy - //---------------------------------------------------------------------------- bool CPyCppyy::NotImplementedConverter::SetArg(PyObject*, Parameter&, CallContext*) { // raise a NotImplemented exception to take a method out of overload resolution - PyErr_SetString(fErrorType, fMessage.c_str()); + PyErr_SetString(PyExc_NotImplementedError, "this method cannot (yet) be called"); return false; } @@ -3352,14 +3210,6 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim const std::string& cpd = TypeManip::compound(resolvedType); std::string realType = TypeManip::clean_type(resolvedType, false, true); -// mutable pointer references (T*&) are incompatible with Python's object model - if (cpd == "*&") { - return new NotImplementedConverter{PyExc_TypeError, - "argument type '" + resolvedType + "' is not supported: non-const references to pointers (T*&) allow a" - " function to replace the pointer itself. Python cannot represent this safely. Consider changing the" - " C++ API to return the new pointer or use a wrapper"}; - } - // accept unqualified type (as python does not know about qualifiers) h = gConvFactories.find((isConst ? "const " : "") + realType + cpd); if (h != gConvFactories.end()) @@ -3411,7 +3261,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim } //-- special case: initializer list - if (realType.compare(0, 16, "initializer_list") == 0) { + if (realType.compare(0, 21, "std::initializer_list") == 0) { // get the type of the list and create a converter (TODO: get hold of value_type?) auto pos = realType.find('<'); std::string value_type = realType.substr(pos+1, realType.size()-pos-2); @@ -3422,9 +3272,8 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim bool control = cpd == "&" || isConst; //-- special case: std::function - auto pos = resolvedType.find("function<"); - if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || - pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { + auto pos = resolvedType.find("std::function<"); + if (pos == 0 /* std:: */ || pos == 6 /* const std:: */ ) { // get actual converter for normal passing Converter* cnv = selectInstanceCnv( @@ -3432,46 +3281,22 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim if (cnv) { // get the type of the underlying (TODO: use target_type?) - auto pos1 = resolvedType.find("(", pos+9); + auto pos1 = resolvedType.find("(", pos+14); auto pos2 = resolvedType.rfind(")"); if (pos1 != std::string::npos && pos2 != std::string::npos) { - auto sz1 = pos1-pos-9; - if (resolvedType[pos+9+sz1-1] == ' ') sz1 -= 1; + auto sz1 = pos1-pos-14; + if (resolvedType[pos+14+sz1-1] == ' ') sz1 -= 1; return new StdFunctionConverter(cnv, - resolvedType.substr(pos+9, sz1), resolvedType.substr(pos1, pos2-pos1+1)); + resolvedType.substr(pos+14, sz1), resolvedType.substr(pos1, pos2-pos1+1)); } else if (cnv->HasState()) delete cnv; } } -#if __cplusplus >= 202002L -//-- special case: std::span - pos = resolvedType.find("span<"); - if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || - pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { - - auto pos1 = realType.find('<'); - auto pos21 = realType.find(','); // for the case there are more template args - auto pos22 = realType.find('>'); - auto len = std::min(pos21 - pos1, pos22 - pos1) - 1; - std::string value_type = realType.substr(pos1+1, len); - - // strip leading "const " - const std::string cprefix = "const "; - if (value_type.compare(0, cprefix.size(), cprefix) == 0) { - value_type = value_type.substr(cprefix.size()); - } - - std::string span_type = "std::span<" + value_type + ">"; - - return new StdSpanConverter{value_type, Cppyy::GetScope(span_type)}; - } -#endif - // converters for known C++ classes and default (void*) Converter* result = nullptr; - if (Cppyy::TCppScope_t klass = Cppyy::GetScope(realType)) { + if (Cppyy::TCppScope_t klass = Cppyy::GetFullScope(realType)) { Cppyy::TCppType_t raw{0}; if (Cppyy::GetSmartPtrInfo(realType, &raw, nullptr)) { if (cpd == "") { @@ -3502,6 +3327,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim resolvedType.substr(0, pos1), resolvedType.substr(pos1+sm.length(), pos2-1)); } } + const std::string failure_msg("Failed to convert type: " + fullType + "; resolved: " + resolvedType + "; real: " + realType + "; cpd: " + cpd); if (!result && cpd == "&&") { // for builtin, can use const-ref for r-ref @@ -3509,7 +3335,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim if (h != gConvFactories.end()) return (h->second)(dims); // else, unhandled moves - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; + result = new NotImplementedConverter(failure_msg); } if (!result && h != gConvFactories.end()) @@ -3518,12 +3344,219 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim else if (!result) { // default to something reasonable, assuming "user knows best" if (cpd.size() == 2 && cpd != "&&") // "**", "*[]", "*&" - result = new VoidPtrPtrConverter(dims.ndim()); + result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); else if (!cpd.empty()) - result = new VoidArrayConverter(); // "user knows best" + result = new VoidArrayConverter(/* keepControl= */ true, failure_msg); // "user knows best" else - // fails on use - result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; + result = new NotImplementedConverter(failure_msg); // fails on use + } + + return result; +} + +CPYCPPYY_EXPORT +CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t dims) +{ +// The matching of the fulltype to a converter factory goes through up to five levels: +// 1) full, exact match +// 2) match of decorated, unqualified type +// 3) accept const ref as by value +// 4) accept ref as pointer +// 5) generalized cases (covers basically all C++ classes) +// +// If all fails, void is used, which will generate a run-time warning when used. + +// an exactly matching converter is best + std::string fullType = Cppyy::GetTypeAsString(type); + ConvFactories_t::iterator h = gConvFactories.find(fullType); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + +// resolve typedefs etc. + Cppyy::TCppType_t resolvedType = Cppyy::ResolveType(type); + const std::string& resolvedTypeStr = Cppyy::GetTypeAsString(resolvedType); + +// a full, qualified matching converter is preferred + if (resolvedTypeStr != fullType) { + h = gConvFactories.find(resolvedTypeStr); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + } + +//-- nothing? ok, collect information about the type and possible qualifiers/decorators + bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedTypeStr); + Cppyy::TCppType_t realType = Cppyy::ResolveType(Cppyy::GetRealType(type)); + std::string realTypeStr = Cppyy::GetTypeAsString(realType); + std::string realUnresolvedTypeStr = TypeManip::clean_type(fullType, false, true); + +// accept unqualified type (as python does not know about qualifiers) + h = gConvFactories.find((isConst ? "const " : "") + realTypeStr + cpd); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + +// drop const, as that is mostly meaningless to python (with the exception +// of c-strings, but those are specialized in the converter map) + if (isConst) { + h = gConvFactories.find(realTypeStr + cpd); + if (h != gConvFactories.end()) { + return (h->second)(dims); + } + } + +//-- still nothing? try pointer instead of array (for builtins) + if (cpd.compare(0, 3, "*[]") == 0) { + // special case, array of pointers + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) { + // upstream treats the pointer type as the array element type, but that pointer is + // treated as a low-level view as well, unless it's a void*/char* so adjust the dims + if (realTypeStr != "void" && realTypeStr != "char") { + dim_t newdim = dims.ndim() == UNKNOWN_SIZE ? 2 : dims.ndim()+1; + dims_t newdims = dims_t(newdim); + // TODO: sometimes the array size is known and can thus be verified; however, + // currently the meta layer does not provide this information + newdims[0] = dims ? dims[0] : UNKNOWN_SIZE; // the array + newdims[1] = UNKNOWN_SIZE; // the pointer + if (2 < newdim) { + for (int i = 2; i < (newdim-1); ++i) + newdims[i] = dims[i-1]; + } + + return (h->second)(newdims); + } + return (h->second)(dims); + } + + } else if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { + // simple array; set or resize as necessary + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) + return (h->second)((!dims && 1 < cpd.size()) ? dims_t(cpd.size()) : dims); + + } else if (2 <= cpd.size() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '[') == cpd.size() / 2) { + // fixed array, dims will have size if available + h = gConvFactories.find(realTypeStr + " ptr"); + if (h != gConvFactories.end()) + return (h->second)(dims); + } + +//-- special case: initializer list + if (realTypeStr.compare(0, 21, "std::initializer_list") == 0) { + // get the type of the list and create a converter (TODO: get hold of value_type?) + auto pos = realTypeStr.find('<'); + std::string value_type = realTypeStr.substr(pos+1, realTypeStr.size()-pos-2); + Converter* cnv = nullptr; bool use_byte_cnv = false; + if (cpd == "" && Cppyy::GetScope(value_type)) { + // initializer list of object values does not work as the target is raw + // memory; simply use byte copies + + // by convention, leave cnv as nullptr + use_byte_cnv = true; + } else + cnv = CreateConverter(value_type); + if (cnv || use_byte_cnv) + return new InitializerListConverter(Cppyy::GetScopeFromType(realType), value_type); + } + +//-- still nothing? use a generalized converter + bool control = cpd == "&" || isConst; + +//-- special case: std::function + auto pos = resolvedTypeStr.find("std::function<"); + if (pos == 0 /* std:: */ || pos == 6 /* const std:: */ ) { + + // get actual converter for normal passing + Converter* cnv = selectInstanceCnv( + Cppyy::GetScopeFromType(realType), cpd, dims, isConst, control); + + if (cnv) { + // get the type of the underlying (TODO: use target_type?) + auto pos1 = resolvedTypeStr.find("(", pos+14); + auto pos2 = resolvedTypeStr.rfind(")"); + if (pos1 != std::string::npos && pos2 != std::string::npos) { + auto sz1 = pos1-pos-14; + if (resolvedTypeStr[pos+14+sz1-1] == ' ') sz1 -= 1; + + const std::string &argsStr = resolvedTypeStr.substr(pos1, pos2-pos1+1).c_str(); + return new StdFunctionConverter(cnv, + resolvedTypeStr.substr(pos+14, sz1), argsStr == "(void)"? "()" : argsStr); + } else if (cnv->HasState()) + delete cnv; + } + } + +// converters for known C++ classes and default (void*) + Converter* result = nullptr; + Cppyy::TCppScope_t klass = Cppyy::GetScopeFromType(realType); + if (resolvedTypeStr.find("(*)") != std::string::npos || + (resolvedTypeStr.find("::*)") != std::string::npos)) { + // this is a function function pointer + // TODO: find better way of finding the type + auto pos1 = resolvedTypeStr.find('('); + auto pos2 = resolvedTypeStr.find("*)"); + auto pos3 = resolvedTypeStr.rfind(')'); + std::string return_type = resolvedTypeStr.substr(0, pos1); + result = new FunctionPointerConverter( + return_type.erase(return_type.find_last_not_of(" ") + 1), resolvedTypeStr.substr(pos2+2, pos3-pos2-1)); + } else if ((realTypeStr != "std::byte") && (klass || (klass = Cppyy::GetFullScope(realTypeStr)))) { + // std::byte is a special enum class used to access raw memory + Cppyy::TCppType_t raw{0}; + if (Cppyy::GetSmartPtrInfo(realTypeStr, &raw, nullptr)) { + if (cpd == "") { + result = new SmartPtrConverter(klass, raw, control); + } else if (cpd == "&") { + result = new SmartPtrConverter(klass, raw); + } else if (cpd == "*" && dims.ndim() == UNKNOWN_SIZE) { + result = new SmartPtrConverter(klass, raw, control, true); + } + } + + if (!result) { + // CLING WORKAROUND -- special case for STL iterators + if (realTypeStr.rfind("__gnu_cxx::__normal_iterator", 0) /* vector */ == 0 +#ifdef __APPLE__ + || realTypeStr.rfind("__wrap_iter", 0) == 0 +#endif + // TODO: Windows? + ) { + static STLIteratorConverter c; + result = &c; + } else { + // -- CLING WORKAROUND + result = selectInstanceCnv(klass, cpd, dims, isConst, control); + } + } + } + const std::string failure_msg("Failed to convert type: " + fullType + "; resolved: " + resolvedTypeStr + "; real: " + realTypeStr + "; realUnresolvedType: " + realUnresolvedTypeStr + "; cpd: " + cpd); + + if (!result && cpd == "&&") { + // for builtin, can use const-ref for r-ref + h = gConvFactories.find("const " + realTypeStr + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + h = gConvFactories.find("const " + realUnresolvedTypeStr + "&"); + if (h != gConvFactories.end()) + return (h->second)(dims); + // else, unhandled moves + result = new NotImplementedConverter(failure_msg); + } + + if (!result && h != gConvFactories.end()) { + // converter factory available, use it to create converter + result = (h->second)(dims); + } else if (!result) { + // default to something reasonable, assuming "user knows best" + if (cpd.size() == 2 && cpd != "&&") {// "**", "*[]", "*&" + result = new VoidPtrPtrConverter(dims.ndim(), failure_msg); + } else if (!cpd.empty()) { + result = new VoidArrayConverter(/* keepControl= */ true, failure_msg); // "user knows best" + } else { + result = new NotImplementedConverter(failure_msg); // fails on use + } } return result; @@ -3591,7 +3624,7 @@ std::string::size_type dims2stringsz(cdims_t d) { return (d && d.ndim() != UNKNOWN_SIZE) ? d[0] : std::string::npos; } -#define STRINGVIEW "basic_string_view >" +#define STRINGVIEW "std::basic_string_view" #define WSTRING1 "std::basic_string" #define WSTRING2 "std::basic_string,std::allocator>" @@ -3632,21 +3665,9 @@ static struct InitConvFactories_t { gf["int8_t"] = (cf_t)+[](cdims_t) { static Int8Converter c{}; return &c; }; gf["const int8_t&"] = (cf_t)+[](cdims_t) { static ConstInt8RefConverter c{}; return &c; }; gf["int8_t&"] = (cf_t)+[](cdims_t) { static Int8RefConverter c{}; return &c; }; - gf["int16_t"] = (cf_t)+[](cdims_t) { static Int16Converter c{}; return &c; }; - gf["const int16_t&"] = (cf_t)+[](cdims_t) { static ConstInt16RefConverter c{}; return &c; }; - gf["int16_t&"] = (cf_t)+[](cdims_t) { static Int16RefConverter c{}; return &c; }; - gf["int32_t"] = (cf_t)+[](cdims_t) { static Int32Converter c{}; return &c; }; - gf["const int32_t&"] = (cf_t)+[](cdims_t) { static ConstInt32RefConverter c{}; return &c; }; - gf["int32_t&"] = (cf_t)+[](cdims_t) { static Int32RefConverter c{}; return &c; }; gf["uint8_t"] = (cf_t)+[](cdims_t) { static UInt8Converter c{}; return &c; }; gf["const uint8_t&"] = (cf_t)+[](cdims_t) { static ConstUInt8RefConverter c{}; return &c; }; gf["uint8_t&"] = (cf_t)+[](cdims_t) { static UInt8RefConverter c{}; return &c; }; - gf["uint16_t"] = (cf_t)+[](cdims_t) { static UInt16Converter c{}; return &c; }; - gf["const uint16_t&"] = (cf_t)+[](cdims_t) { static ConstUInt16RefConverter c{}; return &c; }; - gf["uint16_t&"] = (cf_t)+[](cdims_t) { static UInt16RefConverter c{}; return &c; }; - gf["uint32_t"] = (cf_t)+[](cdims_t) { static UInt32Converter c{}; return &c; }; - gf["const uint32_t&"] = (cf_t)+[](cdims_t) { static ConstUInt32RefConverter c{}; return &c; }; - gf["uint32_t&"] = (cf_t)+[](cdims_t) { static UInt32RefConverter c{}; return &c; }; gf["short"] = (cf_t)+[](cdims_t) { static ShortConverter c{}; return &c; }; gf["const short&"] = (cf_t)+[](cdims_t) { static ConstShortRefConverter c{}; return &c; }; gf["short&"] = (cf_t)+[](cdims_t) { static ShortRefConverter c{}; return &c; }; @@ -3697,11 +3718,7 @@ static struct InitConvFactories_t { gf["UCharAsInt[]"] = gf["unsigned char ptr"]; gf["std::byte ptr"] = (cf_t)+[](cdims_t d) { return new ByteArrayConverter{d}; }; gf["int8_t ptr"] = (cf_t)+[](cdims_t d) { return new Int8ArrayConverter{d}; }; - gf["int16_t ptr"] = (cf_t)+[](cdims_t d) { return new Int16ArrayConverter{d}; }; - gf["int32_t ptr"] = (cf_t)+[](cdims_t d) { return new Int32ArrayConverter{d}; }; gf["uint8_t ptr"] = (cf_t)+[](cdims_t d) { return new UInt8ArrayConverter{d}; }; - gf["uint16_t ptr"] = (cf_t)+[](cdims_t d) { return new UInt16ArrayConverter{d}; }; - gf["uint32_t ptr"] = (cf_t)+[](cdims_t d) { return new UInt32ArrayConverter{d}; }; gf["short ptr"] = (cf_t)+[](cdims_t d) { return new ShortArrayConverter{d}; }; gf["unsigned short ptr"] = (cf_t)+[](cdims_t d) { return new UShortArrayConverter{d}; }; gf["int ptr"] = (cf_t)+[](cdims_t d) { return new IntArrayConverter{d}; }; @@ -3721,9 +3738,7 @@ static struct InitConvFactories_t { gf["signed char"] = gf["char"]; gf["const signed char&"] = gf["const char&"]; gf["std::byte"] = gf["uint8_t"]; - gf["byte"] = gf["uint8_t"]; gf["const std::byte&"] = gf["const uint8_t&"]; - gf["const byte&"] = gf["const uint8_t&"]; gf["std::byte&"] = gf["uint8_t&"]; gf["byte&"] = gf["uint8_t&"]; gf["std::int8_t"] = gf["int8_t"]; @@ -3814,6 +3829,9 @@ static struct InitConvFactories_t { gf["const std::wstring&"] = gf["std::wstring"]; gf["const " WSTRING1 "&"] = gf["std::wstring"]; gf["const " WSTRING2 "&"] = gf["std::wstring"]; + gf["const std::wstring &"] = gf["std::wstring"]; + gf["const " WSTRING1 " &"] = gf["std::wstring"]; + gf["const " WSTRING2 " &"] = gf["std::wstring"]; gf["void*&"] = (cf_t)+[](cdims_t) { static VoidPtrRefConverter c{}; return &c; }; gf["void**"] = (cf_t)+[](cdims_t d) { return new VoidPtrPtrConverter{d}; }; gf["void ptr"] = gf["void**"]; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.h b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.h index 052e1f2ae1a4d..92cccbacb87a9 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.h @@ -29,10 +29,12 @@ class CPYCPPYY_CLASS_EXPORT Converter { virtual PyObject* FromMemory(void* address); virtual bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr); virtual bool HasState() { return false; } + virtual std::string GetFailureMsg() { return "[Converter]"; } }; // create/destroy converter from fully qualified type (public API) CPYCPPYY_EXPORT Converter* CreateConverter(const std::string& fullType, cdims_t dims = 0); +CPYCPPYY_EXPORT Converter* CreateConverter(Cppyy::TCppType_t type, cdims_t dims = 0); CPYCPPYY_EXPORT void DestroyConverter(Converter* p); typedef Converter* (*cf_t)(cdims_t d); CPYCPPYY_EXPORT bool RegisterConverter(const std::string& name, cf_t fac); @@ -43,17 +45,20 @@ CPYCPPYY_EXPORT bool UnregisterConverter(const std::string& name); // converters for special cases (only here b/c of external use of StrictInstancePtrConverter) class VoidArrayConverter : public Converter { public: - VoidArrayConverter(bool keepControl = true) { fKeepControl = keepControl; } + VoidArrayConverter(bool keepControl = true, const std::string &failureMsg = std::string()) + : fFailureMsg(failureMsg) { fKeepControl = keepControl; } public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* ctxt = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[VoidArrayConverter] " + fFailureMsg; } protected: virtual bool GetAddressSpecialCase(PyObject* pyobject, void*& address); bool KeepControl() { return fKeepControl; } + const std::string fFailureMsg; private: bool fKeepControl; @@ -62,8 +67,8 @@ class VoidArrayConverter : public Converter { template class InstancePtrConverter : public VoidArrayConverter { public: - InstancePtrConverter(Cppyy::TCppType_t klass, bool keepControl = false) : - VoidArrayConverter(keepControl), fClass(klass) {} + InstancePtrConverter(Cppyy::TCppScope_t klass, bool keepControl = false, const std::string &failureMsg = std::string()) : + VoidArrayConverter(keepControl, failureMsg), fClass(Cppyy::GetUnderlyingScope(klass)) {} public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h index cc1340033bbaf..2212ff3330c03 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h @@ -9,7 +9,11 @@ #include // import/export (after precommondefs.h from PyPy) +#ifdef _MSC_VER +#define CPPYY_IMPORT extern __declspec(dllimport) +#else #define CPPYY_IMPORT extern +#endif // some more types; assumes Cppyy.h follows Python.h #ifndef PY_LONG_LONG @@ -32,14 +36,22 @@ typedef unsigned long long PY_ULONG_LONG; typedef long double PY_LONG_DOUBLE; #endif +namespace Cpp { + struct TemplateArgInfo { + void* m_Type; + const char* m_IntegralValue; + TemplateArgInfo(void* type, const char* integral_value = nullptr) + : m_Type(type), m_IntegralValue(integral_value) {} + }; +} // end namespace Cpp namespace Cppyy { - typedef size_t TCppScope_t; + typedef void* TCppScope_t; typedef TCppScope_t TCppType_t; typedef void* TCppEnum_t; typedef void* TCppObject_t; - typedef intptr_t TCppMethod_t; + typedef void* TCppMethod_t; typedef size_t TCppIndex_t; typedef void* TCppFuncAddr_t; @@ -53,21 +65,57 @@ namespace Cppyy { // name to opaque C++ scope representation ----------------------------------- CPPYY_IMPORT std::string ResolveName(const std::string& cppitem_name); + + CPPYY_IMPORT + TCppType_t ResolveEnumReferenceType(TCppType_t type); + CPPYY_IMPORT + TCppType_t ResolveEnumPointerType(TCppType_t type); + + CPPYY_IMPORT + TCppType_t ResolveType(TCppType_t type); + CPPYY_IMPORT + TCppType_t GetRealType(TCppType_t type); + CPPYY_IMPORT + TCppType_t GetReferencedType(TCppType_t type, bool rvalue); + CPPYY_IMPORT + TCppType_t GetPointerType(TCppType_t type); + CPPYY_IMPORT + std::string ResolveEnum(TCppScope_t enum_type); + CPPYY_IMPORT + TCppScope_t GetScope(const std::string& name, TCppScope_t parent_scope = 0); + CPPYY_IMPORT + TCppScope_t GetUnderlyingScope(TCppScope_t scope); CPPYY_IMPORT - std::string ResolveEnum(const std::string& enum_type); + TCppScope_t GetFullScope(const std::string& scope_name); CPPYY_IMPORT - TCppScope_t GetScope(const std::string& scope_name); + TCppScope_t GetTypeScope(TCppScope_t klass); CPPYY_IMPORT - TCppType_t GetActualClass(TCppType_t klass, TCppObject_t obj); + TCppScope_t GetNamed(const std::string& scope_name, + TCppScope_t parent_scope = 0); CPPYY_IMPORT - size_t SizeOf(TCppType_t klass); + TCppScope_t GetParentScope(TCppScope_t scope); + CPPYY_IMPORT + TCppScope_t GetScopeFromType(TCppScope_t type); + CPPYY_IMPORT + TCppType_t GetTypeFromScope(TCppScope_t klass); + CPPYY_IMPORT + TCppScope_t GetGlobalScope(); + CPPYY_IMPORT + TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); + CPPYY_IMPORT + size_t SizeOf(TCppScope_t klass); + CPPYY_IMPORT + size_t SizeOfType(TCppType_t type); CPPYY_IMPORT size_t SizeOf(const std::string& type_name); CPPYY_IMPORT bool IsBuiltin(const std::string& type_name); CPPYY_IMPORT - bool IsComplete(const std::string& type_name); + bool IsComplete(TCppScope_t scope); + + CPPYY_IMPORT + bool IsPointerType(TCppType_t type); CPPYY_IMPORT TCppScope_t gGlobalScope; // for fast access @@ -108,7 +156,7 @@ namespace Cppyy { CPPYY_IMPORT char* CallS(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, size_t* length); CPPYY_IMPORT - TCppObject_t CallConstructor(TCppMethod_t method, TCppType_t type, size_t nargs, void* args); + TCppObject_t CallConstructor(TCppMethod_t method, TCppScope_t klass, size_t nargs, void* args); CPPYY_IMPORT void CallDestructor(TCppType_t type, TCppObject_t self); CPPYY_IMPORT @@ -131,15 +179,27 @@ namespace Cppyy { CPPYY_IMPORT bool IsNamespace(TCppScope_t scope); CPPYY_IMPORT - bool IsTemplate(const std::string& template_name); + bool IsClass(TCppScope_t scope); + CPPYY_IMPORT + bool IsTemplate(TCppScope_t handle); + CPPYY_IMPORT + bool IsTemplateInstantiation(TCppScope_t handle); + CPPYY_IMPORT + bool IsTypedefed(TCppScope_t handle); CPPYY_IMPORT bool IsAbstract(TCppType_t type); CPPYY_IMPORT - bool IsEnum(const std::string& type_name); + bool IsEnumScope(TCppScope_t scope); + CPPYY_IMPORT + bool IsEnumConstant(TCppScope_t scope); + CPPYY_IMPORT + bool IsEnumType(TCppType_t type); CPPYY_IMPORT bool IsAggregate(TCppType_t type); CPPYY_IMPORT - bool IsDefaultConstructable(TCppType_t type); + bool IsDefaultConstructable(TCppScope_t scope); + CPPYY_IMPORT + bool IsVariable(TCppScope_t scope); CPPYY_IMPORT void GetAllCppNames(TCppScope_t scope, std::set& cppnames); @@ -164,7 +224,9 @@ namespace Cppyy { CPPYY_IMPORT std::string GetBaseName(TCppType_t type, TCppIndex_t ibase); CPPYY_IMPORT - bool IsSubtype(TCppType_t derived, TCppType_t base); + TCppScope_t GetBaseScope(TCppType_t type, TCppIndex_t ibase); + CPPYY_IMPORT + bool IsSubclass(TCppType_t derived, TCppType_t base); CPPYY_IMPORT bool IsSmartPtr(TCppType_t type); CPPYY_IMPORT @@ -182,9 +244,9 @@ namespace Cppyy { // method/function reflection information ------------------------------------ CPPYY_IMPORT - TCppIndex_t GetNumMethods(TCppScope_t scope, bool accept_namespace = false); + void GetClassMethods(TCppScope_t scope, std::vector &methods); CPPYY_IMPORT - std::vector GetMethodIndicesFromName(TCppScope_t scope, const std::string& name); + std::vector GetMethodsFromName(TCppScope_t scope, const std::string& name); CPPYY_IMPORT TCppMethod_t GetMethod(TCppScope_t scope, TCppIndex_t imeth); @@ -196,7 +258,9 @@ namespace Cppyy { CPPYY_IMPORT std::string GetMethodMangledName(TCppMethod_t); CPPYY_IMPORT - std::string GetMethodResultType(TCppMethod_t); + TCppType_t GetMethodReturnType(TCppMethod_t); + CPPYY_IMPORT + std::string GetMethodReturnTypeAsString(TCppMethod_t); CPPYY_IMPORT TCppIndex_t GetMethodNumArgs(TCppMethod_t); CPPYY_IMPORT @@ -204,39 +268,45 @@ namespace Cppyy { CPPYY_IMPORT std::string GetMethodArgName(TCppMethod_t, TCppIndex_t iarg); CPPYY_IMPORT - std::string GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); + TCppType_t GetMethodArgType(TCppMethod_t, TCppIndex_t iarg); + CPPYY_IMPORT + std::string GetMethodArgTypeAsString(TCppMethod_t, TCppIndex_t iarg); + CPPYY_IMPORT + std::string GetMethodArgCanonTypeAsString(TCppMethod_t, TCppIndex_t iarg); CPPYY_IMPORT TCppIndex_t CompareMethodArgType(TCppMethod_t, TCppIndex_t iarg, const std::string &req_type); CPPYY_IMPORT std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); CPPYY_IMPORT - std::string GetMethodSignature(TCppMethod_t, bool show_formalargs, TCppIndex_t maxargs = (TCppIndex_t)-1); + std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, TCppIndex_t max_args = (TCppIndex_t)-1); CPPYY_IMPORT - std::string GetMethodPrototype(TCppScope_t scope, TCppMethod_t, bool show_formalargs); + std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); CPPYY_IMPORT bool IsConstMethod(TCppMethod_t); - + CPPYY_IMPORT + void GetTemplatedMethods(TCppScope_t scope, std::vector &methods); CPPYY_IMPORT TCppIndex_t GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace = false); CPPYY_IMPORT std::string GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth); CPPYY_IMPORT - bool IsTemplatedConstructor(TCppScope_t scope, TCppIndex_t imeth); - CPPYY_IMPORT bool ExistsMethodTemplate(TCppScope_t scope, const std::string& name); CPPYY_IMPORT - bool IsStaticTemplate(TCppScope_t scope, const std::string& name); + bool IsTemplatedMethod(TCppMethod_t method); CPPYY_IMPORT - bool IsMethodTemplate(TCppScope_t scope, TCppIndex_t imeth); + bool IsStaticTemplate(TCppScope_t scope, const std::string& name); CPPYY_IMPORT TCppMethod_t GetMethodTemplate( TCppScope_t scope, const std::string& name, const std::string& proto); CPPYY_IMPORT - TCppIndex_t GetGlobalOperator( - TCppType_t scope, const std::string& lc, const std::string& rc, const std::string& op); + TCppMethod_t GetGlobalOperator(TCppType_t scope, const std::string &lc, + const std::string &rc, + const std::string &op); // method properties --------------------------------------------------------- + CPPYY_IMPORT + bool IsDeletedMethod(TCppMethod_t method); CPPYY_IMPORT bool IsPublicMethod(TCppMethod_t method); CPPYY_IMPORT @@ -247,45 +317,73 @@ namespace Cppyy { bool IsDestructor(TCppMethod_t method); CPPYY_IMPORT bool IsStaticMethod(TCppMethod_t method); - CPPYY_IMPORT - bool IsExplicit(TCppMethod_t method); // data member reflection information ---------------------------------------- CPPYY_IMPORT - TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace = false); + void GetDatamembers(TCppScope_t scope, std::vector& datamembers); CPPYY_IMPORT std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata); CPPYY_IMPORT - std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata); + TCppScope_t ReduceReturnType(TCppScope_t fn, TCppType_t reduce); + bool IsLambdaClass(TCppType_t type); CPPYY_IMPORT - intptr_t GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata); + TCppScope_t WrapLambdaFromVariable(TCppScope_t var); CPPYY_IMPORT - TCppIndex_t GetDatamemberIndex(TCppScope_t scope, const std::string& name); + TCppScope_t AdaptFunctionForLambdaReturn(TCppScope_t fn); + CPPYY_IMPORT + TCppType_t GetDatamemberType(TCppScope_t var); + CPPYY_IMPORT + std::string GetTypeAsString(TCppType_t type); + CPPYY_IMPORT + bool IsClassType(TCppType_t type); + CPPYY_IMPORT + bool IsFunctionPointerType(TCppType_t type); + CPPYY_IMPORT + TCppType_t GetType(const std::string& name, bool enable_slow_lookup = false); + CPPYY_IMPORT + bool AppendTypesSlow(const std::string &name, + std::vector& types, + TCppScope_t parent = nullptr); + CPPYY_IMPORT + TCppType_t GetComplexType(const std::string& element_type); + CPPYY_IMPORT + std::string GetDatamemberTypeAsString(TCppScope_t var); + CPPYY_IMPORT + intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); + CPPYY_IMPORT + bool CheckDatamember(TCppScope_t scope, const std::string& name); // data member properties ---------------------------------------------------- CPPYY_IMPORT - bool IsPublicData(TCppScope_t scope, TCppIndex_t idata); + bool IsPublicData(TCppScope_t data); CPPYY_IMPORT - bool IsProtectedData(TCppScope_t scope, TCppIndex_t idata); + bool IsProtectedData(TCppScope_t var); CPPYY_IMPORT - bool IsStaticData(TCppScope_t scope, TCppIndex_t idata); + bool IsStaticDatamember(TCppScope_t var); CPPYY_IMPORT - bool IsConstData(TCppScope_t scope, TCppIndex_t idata); + bool IsConstVar(TCppScope_t var); CPPYY_IMPORT bool IsEnumData(TCppScope_t scope, TCppIndex_t idata); CPPYY_IMPORT - int GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension); + std::vector GetDimensions(TCppType_t type); // enum properties ----------------------------------------------------------- + // CPPYY_IMPORT + // TCppEnum_t GetEnum(TCppScope_t scope, const std::string& enum_name); + CPPYY_IMPORT + TCppScope_t GetEnumScope(TCppScope_t); CPPYY_IMPORT - TCppEnum_t GetEnum(TCppScope_t scope, const std::string& enum_name); + std::vector GetEnumConstants(TCppScope_t scope); CPPYY_IMPORT - TCppIndex_t GetNumEnumData(TCppEnum_t); + TCppType_t GetEnumConstantType(TCppScope_t scope); CPPYY_IMPORT - std::string GetEnumDataName(TCppEnum_t, TCppIndex_t idata); + long long GetEnumDataValue(TCppScope_t scope); CPPYY_IMPORT - long long GetEnumDataValue(TCppEnum_t, TCppIndex_t idata); + TCppScope_t InstantiateTemplate( + TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size); + CPPYY_IMPORT + TCppScope_t DumpScope(TCppScope_t scope); } // namespace Cppyy #endif // !CPYCPPYY_CPPYY_H diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h index 3f8ed377d3bd5..302027ff652f0 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h @@ -17,36 +17,41 @@ namespace { #define CPPYY_DECLARE_BASIC_CONVERTER(name) \ class name##Converter : public Converter { \ public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + virtual std::string GetFailureMsg() { return "[" #name "Converter]"; } \ }; \ \ class Const##name##RefConverter : public Converter { \ public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + virtual std::string GetFailureMsg() { return "[Const" #name "RefConverter]"; }\ } #define CPPYY_DECLARE_BASIC_CONVERTER2(name, base) \ class name##Converter : public base##Converter { \ public: \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + virtual std::string GetFailureMsg() { return "[" #name "Converter]"; } \ }; \ \ class Const##name##RefConverter : public Converter { \ public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + virtual std::string GetFailureMsg() { return "[Const" #name "RefConverter]"; }\ } #define CPPYY_DECLARE_REFCONVERTER(name) \ class name##RefConverter : public Converter { \ public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + virtual std::string GetFailureMsg() { return "[" #name "RefConverter]"; }\ }; #define CPPYY_DECLARE_ARRAY_CONVERTER(name) \ @@ -55,10 +60,11 @@ public: \ name##ArrayConverter(cdims_t dims); \ name##ArrayConverter(const name##ArrayConverter&) = delete; \ name##ArrayConverter& operator=(const name##ArrayConverter&) = delete; \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void*) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - bool HasState() override { return true; } \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void*) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + bool HasState() { return true; } \ + virtual std::string GetFailureMsg() { return "[" #name "ArrayConverter]"; }\ protected: \ dims_t fShape; \ bool fIsFixed; \ @@ -84,11 +90,7 @@ CPPYY_DECLARE_BASIC_CONVERTER(WChar); CPPYY_DECLARE_BASIC_CONVERTER(Char16); CPPYY_DECLARE_BASIC_CONVERTER(Char32); CPPYY_DECLARE_BASIC_CONVERTER(Int8); -CPPYY_DECLARE_BASIC_CONVERTER(Int16); -CPPYY_DECLARE_BASIC_CONVERTER(Int32); CPPYY_DECLARE_BASIC_CONVERTER(UInt8); -CPPYY_DECLARE_BASIC_CONVERTER(UInt16); -CPPYY_DECLARE_BASIC_CONVERTER(UInt32); CPPYY_DECLARE_BASIC_CONVERTER(Short); CPPYY_DECLARE_BASIC_CONVERTER(UShort); CPPYY_DECLARE_BASIC_CONVERTER(Int); @@ -108,11 +110,7 @@ CPPYY_DECLARE_REFCONVERTER(Char32); CPPYY_DECLARE_REFCONVERTER(SChar); CPPYY_DECLARE_REFCONVERTER(UChar); CPPYY_DECLARE_REFCONVERTER(Int8); -CPPYY_DECLARE_REFCONVERTER(Int16); -CPPYY_DECLARE_REFCONVERTER(Int32); CPPYY_DECLARE_REFCONVERTER(UInt8); -CPPYY_DECLARE_REFCONVERTER(UInt16); -CPPYY_DECLARE_REFCONVERTER(UInt32); CPPYY_DECLARE_REFCONVERTER(Short); CPPYY_DECLARE_REFCONVERTER(UShort); CPPYY_DECLARE_REFCONVERTER(UInt); @@ -139,6 +137,7 @@ class CStringConverter : public Converter { PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[CStringConverter]"; } protected: std::string fBuffer; @@ -152,6 +151,7 @@ class NonConstCStringConverter : public CStringConverter { public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; + virtual std::string GetFailureMsg() { return "[NonConstCStringConverter]"; } }; class WCStringConverter : public Converter { @@ -167,6 +167,7 @@ class WCStringConverter : public Converter { PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[WCStringConverter]"; }; protected: wchar_t* fBuffer; @@ -186,6 +187,7 @@ class CString16Converter : public Converter { PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[CString16Converter]"; }; protected: char16_t* fBuffer; @@ -205,6 +207,7 @@ class CString32Converter : public Converter { PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[CString32Converter]"; }; protected: char32_t* fBuffer; @@ -217,11 +220,7 @@ CPPYY_DECLARE_ARRAY_CONVERTER(SChar); CPPYY_DECLARE_ARRAY_CONVERTER(UChar); CPPYY_DECLARE_ARRAY_CONVERTER(Byte); CPPYY_DECLARE_ARRAY_CONVERTER(Int8); -CPPYY_DECLARE_ARRAY_CONVERTER(Int16); -CPPYY_DECLARE_ARRAY_CONVERTER(Int32); CPPYY_DECLARE_ARRAY_CONVERTER(UInt8); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt16); -CPPYY_DECLARE_ARRAY_CONVERTER(UInt32); CPPYY_DECLARE_ARRAY_CONVERTER(Short); CPPYY_DECLARE_ARRAY_CONVERTER(UShort); CPPYY_DECLARE_ARRAY_CONVERTER(Int); @@ -244,7 +243,7 @@ class CStringArrayConverter : public SCharArrayConverter { using SCharArrayConverter::SCharArrayConverter; bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + virtual std::string GetFailureMsg() { return "[CStringArrayConverter]"; }; private: std::vector fBuffer; @@ -254,6 +253,7 @@ class NonConstCStringArrayConverter : public CStringArrayConverter { public: using CStringArrayConverter::CStringArrayConverter; PyObject* FromMemory(void* address) override; + virtual std::string GetFailureMsg() { return "[NonConstCStringArrayConverter]"; }; }; // converters for special cases @@ -268,6 +268,7 @@ class InstanceConverter : public StrictInstancePtrConverter { bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void*) override; bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; + virtual std::string GetFailureMsg() { return "[InstanceConverter]"; }; }; class InstanceRefConverter : public Converter { @@ -279,6 +280,7 @@ class InstanceRefConverter : public Converter { bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[InstanceRefConverter]"; }; protected: Cppyy::TCppType_t fClass; @@ -289,6 +291,7 @@ class InstanceMoveConverter : public InstanceRefConverter { public: InstanceMoveConverter(Cppyy::TCppType_t klass) : InstanceRefConverter(klass, true) {} bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + virtual std::string GetFailureMsg() { return "[InstanceMoveConverter]"; }; }; template @@ -300,6 +303,7 @@ class InstancePtrPtrConverter : public InstancePtrConverter { bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + virtual std::string GetFailureMsg() { return "[InstancePtrPtrConverter]"; }; }; class InstanceArrayConverter : public InstancePtrConverter { @@ -313,6 +317,7 @@ class InstanceArrayConverter : public InstancePtrConverter { bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + virtual std::string GetFailureMsg() { return "[InstanceArrayConverter]"; }; protected: dims_t fShape; @@ -328,6 +333,7 @@ class ComplexDConverter: public InstanceConverter { PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[ComplexDConverter]"; }; private: std::complex fBuffer; @@ -339,6 +345,7 @@ class ComplexDConverter: public InstanceConverter { class STLIteratorConverter : public Converter { public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + virtual std::string GetFailureMsg() { return "[STLIteratorConverter]"; }; }; // -- END Cling WORKAROUND @@ -346,20 +353,21 @@ class STLIteratorConverter : public Converter { class VoidPtrRefConverter : public Converter { public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + virtual std::string GetFailureMsg() { return "[VoidPtrRefConverter]"; }; }; class VoidPtrPtrConverter : public Converter { public: - VoidPtrPtrConverter(cdims_t dims); - -public: + VoidPtrPtrConverter(cdims_t dims, const std::string &failureMsg = std::string()); bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[VoidPtrPtrConverter] " + fFailureMsg; } protected: dims_t fShape; bool fIsFixed; + const std::string fFailureMsg; }; CPPYY_DECLARE_BASIC_CONVERTER(PyObject); @@ -371,11 +379,11 @@ public: \ name##Converter(bool keepControl = true); \ \ public: \ - bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ - PyObject* FromMemory(void* address) override; \ - bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - bool HasState() override { return true; } \ - \ + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ + PyObject* FromMemory(void* address) override; \ + bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ + bool HasState() override { return true; } \ + virtual std::string GetFailureMsg() { return "[" #name "Converter]"; }; \ protected: \ strtype fBuffer; \ } @@ -390,6 +398,7 @@ class STLStringMoveConverter : public STLStringConverter { public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; + virtual std::string GetFailureMsg() { return "[STLStringMoveConverter]"; }; }; @@ -404,6 +413,7 @@ class FunctionPointerConverter : public Converter { PyObject* FromMemory(void* address) override; bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[FunctionPointerConverter]"; }; protected: std::string fRetType; @@ -426,6 +436,7 @@ class StdFunctionConverter : public FunctionPointerConverter { bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; PyObject* FromMemory(void* address) override; bool ToMemory(PyObject* value, void* address, PyObject* = nullptr) override; + virtual std::string GetFailureMsg() { return "[StdFunctionConverter]"; }; protected: Converter* fConverter; @@ -447,6 +458,7 @@ class SmartPtrConverter : public Converter { PyObject* FromMemory(void* address) override; bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[SmartPtrConverter]"; }; protected: virtual bool GetAddressSpecialCase(PyObject*, void*&) { return false; } @@ -469,6 +481,7 @@ class InitializerListConverter : public InstanceConverter { public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; bool HasState() override { return true; } + virtual std::string GetFailureMsg() { return "[FunctionPointerConverter]"; }; protected: void Clear(); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h index 888f63be96e63..8a4d246e26a8b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h @@ -94,24 +94,24 @@ class InstancePtrExecutor : public Executor { bool HasState() override { return true; } protected: - Cppyy::TCppType_t fClass; + Cppyy::TCppScope_t fClass; }; class InstanceExecutor : public Executor { public: - InstanceExecutor(Cppyy::TCppType_t klass); + InstanceExecutor(Cppyy::TCppScope_t klass); PyObject* Execute( Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; bool HasState() override { return true; } protected: - Cppyy::TCppType_t fClass; - uint32_t fFlags; + Cppyy::TCppScope_t fClass; + uint32_t fFlags; }; class IteratorExecutor : public InstanceExecutor { public: - IteratorExecutor(Cppyy::TCppType_t klass); + IteratorExecutor(Cppyy::TCppScope_t klass); }; CPPYY_DECL_EXEC(Constructor); @@ -146,12 +146,12 @@ CPPYY_DECL_REFEXEC(STLString); // special cases class InstanceRefExecutor : public RefExecutor { public: - InstanceRefExecutor(Cppyy::TCppType_t klass) : fClass(klass) {} + InstanceRefExecutor(Cppyy::TCppScope_t klass) : fClass(klass) {} PyObject* Execute( Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; protected: - Cppyy::TCppType_t fClass; + Cppyy::TCppScope_t fClass; }; class InstancePtrPtrExecutor : public InstanceRefExecutor { @@ -170,7 +170,7 @@ class InstancePtrRefExecutor : public InstanceRefExecutor { class InstanceArrayExecutor : public InstancePtrExecutor { public: - InstanceArrayExecutor(Cppyy::TCppType_t klass, dim_t array_size) + InstanceArrayExecutor(Cppyy::TCppScope_t klass, dim_t array_size) : InstancePtrExecutor(klass), fSize(array_size) {} PyObject* Execute( Cppyy::TCppMethod_t, Cppyy::TCppObject_t, CallContext*) override; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx index cf766225a08fa..5affdd2120317 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx @@ -10,25 +10,23 @@ //----------------------------------------------------------------------------- PyObject* CPyCppyy::DispatchPtr::Get(bool borrowed) const { - PyGILState_STATE state = PyGILState_Ensure(); - PyObject* result = nullptr; if (fPyHardRef) { if (!borrowed) Py_INCREF(fPyHardRef); - result = fPyHardRef; - } else if (fPyWeakRef) { - result = CPyCppyy_GetWeakRef(fPyWeakRef); - if (result) { // dispatcher object disappeared? - if (borrowed) Py_DECREF(result); + return fPyHardRef; + } + if (fPyWeakRef) { + PyObject* disp = CPyCppyy_GetWeakRef(fPyWeakRef); + if (disp) { // dispatcher object disappeared? + if (borrowed) Py_DECREF(disp); + return disp; } } - PyGILState_Release(state); - return result; + return nullptr; } //----------------------------------------------------------------------------- CPyCppyy::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) : fPyHardRef(nullptr) { - PyGILState_STATE state = PyGILState_Ensure(); if (strong) { Py_INCREF(pyobj); fPyHardRef = pyobj; @@ -38,18 +36,15 @@ CPyCppyy::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) : fPyHardRef(nu fPyWeakRef = PyWeakref_NewRef(pyobj, nullptr); } ((CPPInstance*)pyobj)->SetDispatchPtr(this); - PyGILState_Release(state); } //----------------------------------------------------------------------------- CPyCppyy::DispatchPtr::DispatchPtr(const DispatchPtr& other, void* cppinst) : fPyWeakRef(nullptr) { - PyGILState_STATE state = PyGILState_Ensure(); PyObject* pyobj = other.Get(false /* not borrowed */); fPyHardRef = pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); Py_XDECREF(pyobj); - PyGILState_Release(state); } //----------------------------------------------------------------------------- @@ -58,7 +53,6 @@ CPyCppyy::DispatchPtr::~DispatchPtr() { // of a dispatcher intermediate, then this delete is from the C++ side, and Python // is "notified" by nulling out the reference and an exception will be raised on // continued access - PyGILState_STATE state = PyGILState_Ensure(); if (fPyWeakRef) { PyObject* pyobj = CPyCppyy_GetWeakRef(fPyWeakRef); if (pyobj && ((CPPScope*)Py_TYPE(pyobj))->fFlags & CPPScope::kIsPython) @@ -69,13 +63,11 @@ CPyCppyy::DispatchPtr::~DispatchPtr() { ((CPPInstance*)fPyHardRef)->GetObjectRaw() = nullptr; Py_DECREF(fPyHardRef); } - PyGILState_Release(state); } //----------------------------------------------------------------------------- CPyCppyy::DispatchPtr& CPyCppyy::DispatchPtr::assign(const DispatchPtr& other, void* cppinst) { - PyGILState_STATE state = PyGILState_Ensure(); if (this != &other) { Py_XDECREF(fPyWeakRef); fPyWeakRef = nullptr; Py_XDECREF(fPyHardRef); @@ -84,7 +76,6 @@ CPyCppyy::DispatchPtr& CPyCppyy::DispatchPtr::assign(const DispatchPtr& other, v if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); Py_XDECREF(pyobj); } - PyGILState_Release(state); return *this; } @@ -102,10 +93,8 @@ void CPyCppyy::DispatchPtr::PythonOwns() void CPyCppyy::DispatchPtr::CppOwns() { // C++ maintains the hardref, keeping the PyObject alive w/o outstanding ref - PyGILState_STATE state = PyGILState_Ensure(); if (fPyWeakRef) { fPyHardRef = CPyCppyy_GetWeakRef(fPyWeakRef); Py_DECREF(fPyWeakRef); fPyWeakRef = nullptr; } - PyGILState_Release(state); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx index 63bd3b9e7dc0e..ace5b87b73e73 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx @@ -19,14 +19,14 @@ static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& m using namespace CPyCppyy; // method declaration - std::string retType = Cppyy::GetMethodResultType(method); + std::string retType = Cppyy::GetMethodReturnTypeAsString(method); code << " " << retType << " " << mtCppName << "("; // build out the signature with predictable formal names Cppyy::TCppIndex_t nArgs = Cppyy::GetMethodNumArgs(method); std::vector argtypes; argtypes.reserve(nArgs); for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { - argtypes.push_back(Cppyy::GetMethodArgType(method, i)); + argtypes.push_back(Cppyy::GetMethodArgCanonTypeAsString(method, i)); if (i != 0) code << ", "; code << argtypes.back() << " arg" << i; } @@ -41,24 +41,21 @@ static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& m // possible crash code << " PyObject* iself = (PyObject*)_internal_self;\n" " if (!iself || iself == Py_None) {\n" - " PyGILState_STATE state = PyGILState_Ensure();\n" " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on deleted python-side proxy\");\n" - " PyGILState_Release(state);\n" " return"; if (retType != "void") { if (retType.back() != '*') - code << " (" << CPyCppyy::TypeManip::remove_const(retType) << "){}"; + code << " " << CPyCppyy::TypeManip::remove_const(retType) << "{}"; else code << " nullptr"; } code << ";\n" - " }\n"; + " }\n" + " Py_INCREF(iself);\n"; // start actual function body Utility::ConstructCallbackPreamble(retType, argtypes, code); - code << " Py_INCREF(iself);\n"; - // perform actual method call #if PY_VERSION_HEX < 0x03000000 code << " PyObject* mtPyName = PyString_FromString(\"" << mtCppName << "\");\n" // TODO: intern? @@ -120,7 +117,7 @@ static void build_constructors( size_t offset = (i != 0 ? arg_tots[i-1] : 0); for (size_t j = 0; j < nArgs; ++j) { if (i != 0 || j != 0) code << ", "; - code << Cppyy::GetMethodArgType(cinfo.first, j) << " a" << (j+offset); + code << Cppyy::GetMethodArgCanonTypeAsString(cinfo.first, j) << " a" << (j+offset); } } code << ") : "; @@ -133,7 +130,7 @@ static void build_constructors( for (size_t j = first; j < arg_tots[i]; ++j) { if (j != first) code << ", "; bool isRValue = CPyCppyy::TypeManip::compound(\ - Cppyy::GetMethodArgType(methods[i].first, j-first)) == "&&"; + Cppyy::GetMethodArgCanonTypeAsString(methods[i].first, j-first)) == "&&"; if (isRValue) code << "std::move("; code << "a" << j; if (isRValue) code << ")"; @@ -149,14 +146,14 @@ namespace { using namespace Cppyy; static inline -std::vector FindBaseMethod(TCppScope_t tbase, const std::string mtCppName) +std::vector FindBaseMethod(TCppScope_t tbase, const std::string mtCppName) { // Recursively walk the inheritance tree to find the overloads of the named method - std::vector result; - result = GetMethodIndicesFromName(tbase, mtCppName); + std::vector result; + result = GetMethodsFromName(tbase, mtCppName); if (result.empty()) { for (TCppIndex_t ibase = 0; ibase < GetNumBases(tbase); ++ibase) { - TCppScope_t b = GetScope(GetBaseName(tbase, ibase)); + TCppScope_t b = Cppyy::GetBaseScope(tbase, ibase); result = FindBaseMethod(b, mtCppName); if (!result.empty()) break; @@ -242,7 +239,6 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // object goes before the C++ one, only __del__ is called) if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) { code << " virtual ~" << derivedName << "() {\n" - "PyGILState_STATE state = PyGILState_Ensure();\n" " PyObject* iself = (PyObject*)_internal_self;\n" " if (!iself || iself == Py_None)\n" " return;\n" // safe, as destructor always returns void @@ -255,7 +251,6 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // magic C++ exception ... code << " if (!pyresult) PyErr_Print();\n" " else { Py_DECREF(pyresult); }\n" - " PyGILState_Release(state);\n" " }\n"; } else code << " virtual ~" << derivedName << "() {}\n"; @@ -283,18 +278,18 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, for (BaseInfos_t::size_type ibase = 0; ibase < base_infos.size(); ++ibase) { const auto& binfo = base_infos[ibase]; - const Cppyy::TCppIndex_t nMethods = Cppyy::GetNumMethods(binfo.btype); + std::vector methods; + Cppyy::GetClassMethods(binfo.btype, methods); bool cctor_found = false, default_found = false, any_ctor_found = false; - for (Cppyy::TCppIndex_t imeth = 0; imeth < nMethods; ++imeth) { - Cppyy::TCppMethod_t method = Cppyy::GetMethod(binfo.btype, imeth); - + for (auto &method : methods) { if (Cppyy::IsConstructor(method)) { any_ctor_found = true; - if (Cppyy::IsPublicMethod(method) || Cppyy::IsProtectedMethod(method)) { + if ((Cppyy::IsPublicMethod(method) || Cppyy::IsProtectedMethod(method)) && + !Cppyy::IsDeletedMethod(method)) { Cppyy::TCppIndex_t nreq = Cppyy::GetMethodReqArgs(method); if (nreq == 0) default_found = true; else if (!cctor_found && nreq == 1) { - const std::string& argtype = Cppyy::GetMethodArgType(method, 0); + const std::string& argtype = Cppyy::GetMethodArgCanonTypeAsString(method, 0); if (TypeManip::compound(argtype) == "&" && TypeManip::clean_type(argtype, false) == binfo.bname_scoped) cctor_found = true; } @@ -315,11 +310,11 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, if (Cppyy::IsProtectedMethod(method)) { protected_names.insert(mtCppName); - code << " " << Cppyy::GetMethodResultType(method) << " " << mtCppName << "("; + code << " " << Cppyy::GetMethodReturnTypeAsString(method) << " " << mtCppName << "("; Cppyy::TCppIndex_t nArgs = Cppyy::GetMethodNumArgs(method); for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { if (i != 0) code << ", "; - code << Cppyy::GetMethodArgType(method, i) << " arg" << i; + code << Cppyy::GetMethodArgCanonTypeAsString(method, i) << " arg" << i; } code << ") "; if (Cppyy::IsConstMethod(method)) code << "const "; @@ -343,9 +338,10 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // support for templated ctors in single inheritance (TODO: also multi possible?) if (base_infos.size() == 1) { - const Cppyy::TCppIndex_t nTemplMethods = Cppyy::GetNumTemplatedMethods(binfo.btype); - for (Cppyy::TCppIndex_t imeth = 0; imeth < nTemplMethods; ++imeth) { - if (Cppyy::IsTemplatedConstructor(binfo.btype, imeth)) { + std::vector templ_methods; + Cppyy::GetTemplatedMethods(binfo.btype, templ_methods); + for (auto &method : templ_methods) { + if (Cppyy::IsConstructor(method)) { any_ctor_found = true; has_tmpl_ctors += 1; break; // one suffices to map as argument packs are used @@ -364,18 +360,18 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, if (PyDict_Size(clbs)) { size_t nbases = Cppyy::GetNumBases(binfo.btype); for (size_t ibase = 0; ibase < nbases; ++ibase) { - Cppyy::TCppScope_t tbase = (Cppyy::TCppScope_t)Cppyy::GetScope( \ - Cppyy::GetBaseName(binfo.btype, ibase)); + Cppyy::TCppScope_t tbase = + (Cppyy::TCppScope_t) Cppyy::GetBaseScope(binfo.btype, ibase); PyObject* keys = PyDict_Keys(clbs); for (Py_ssize_t i = 0; i < PyList_GET_SIZE(keys); ++i) { // TODO: should probably invert this looping; but that makes handling overloads clunky PyObject* key = PyList_GET_ITEM(keys, i); std::string mtCppName = CPyCppyy_PyText_AsString(key); - const auto& v = FindBaseMethod(tbase, mtCppName); - for (auto idx : v) - InjectMethod(Cppyy::GetMethod(tbase, idx), mtCppName, code); - if (!v.empty()) { + const auto& methods = FindBaseMethod(tbase, mtCppName); + for (auto method : methods) + InjectMethod(method, mtCppName, code); + if (!methods.empty()) { if (PyDict_DelItem(clbs, key) != 0) PyErr_Clear(); } } @@ -416,12 +412,13 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // pull in data members that are protected bool setPublic = false; for (const auto& binfo : base_infos) { - Cppyy::TCppIndex_t nData = Cppyy::GetNumDatamembers(binfo.btype); - for (Cppyy::TCppIndex_t idata = 0; idata < nData; ++idata) { - if (Cppyy::IsProtectedData(binfo.btype, idata)) { - const std::string dm_name = Cppyy::GetDatamemberName(binfo.btype, idata); + std::vector datamems; + Cppyy::GetDatamembers(binfo.btype, datamems); + for (auto data : datamems) { + if (Cppyy::IsProtectedData(data)) { + const std::string dm_name = Cppyy::GetFinalName(data); if (dm_name != "_internal_self") { - const std::string& dname = Cppyy::GetDatamemberName(binfo.btype, idata); + const std::string& dname = Cppyy::GetFinalName(data); protected_names.insert(dname); if (!setPublic) { code << "public:\n"; @@ -438,7 +435,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, code << "public:\n static void _init_dispatchptr(" << derivedName << "* inst, PyObject* self) {\n"; if (1 < base_infos.size()) { for (const auto& binfo : base_infos) { - if (Cppyy::GetDatamemberIndex(binfo.btype, "_internal_self") != (Cppyy::TCppIndex_t)-1) { + if (Cppyy::CheckDatamember(binfo.btype, "_internal_self")) { code << " " << binfo.bname << "::_init_dispatchptr(inst, self);\n"; disp_inited += 1; } @@ -456,11 +453,8 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // provide an accessor to re-initialize after round-tripping from C++ (internal) code << "\n static PyObject* _get_dispatch(" << derivedName << "* inst) {\n" - " PyGILState_STATE state = PyGILState_Ensure();\n" " PyObject* res = (PyObject*)inst->_internal_self;\n" - " Py_XINCREF(res);\n" - " PyGILState_Release(state);\n" - " return res;\n }"; + " Py_XINCREF(res); return res;\n }"; // finish class declaration code << "};\n}"; @@ -473,7 +467,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // keep track internally of the actual C++ type (this is used in // CPPConstructor to call the dispatcher's one instead of the base) - Cppyy::TCppScope_t disp = Cppyy::GetScope("__cppyy_internal::"+derivedName); + Cppyy::TCppScope_t disp = Cppyy::GetFullScope("__cppyy_internal::"+derivedName); if (!disp) { err << "failed to retrieve the internal dispatcher"; return false; @@ -484,7 +478,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // that are part of the hierarchy in Python, so create it, which will cache it for // later use by e.g. the MemoryRegulator unsigned int flags = (unsigned int)(klass->fFlags & CPPScope::kIsMultiCross); - PyObject* disp_proxy = CPyCppyy::CreateScopeProxy(disp, flags); + PyObject* disp_proxy = CPyCppyy::CreateScopeProxy(disp, 0, flags); if (flags) ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsMultiCross; ((CPPScope*)disp_proxy)->fFlags |= CPPScope::kIsPython; @@ -493,12 +487,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // Python class to keep the inheritance tree intact) for (const auto& name : protected_names) { PyObject* disp_dct = PyObject_GetAttr(disp_proxy, PyStrings::gDict); -#if PY_VERSION_HEX < 0x30d00f0 PyObject* pyf = PyMapping_GetItemString(disp_dct, (char*)name.c_str()); -#else - PyObject* pyf = nullptr; - PyMapping_GetOptionalItemString(disp_dct, (char*)name.c_str(), &pyf); -#endif if (pyf) { PyObject_SetAttrString((PyObject*)klass, (char*)name.c_str(), pyf); Py_DECREF(pyf); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx index fa0aefa0931fa..9e10bd13a3a82 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx @@ -1,5 +1,6 @@ // Bindings #include "CPyCppyy.h" +#include "Cppyy.h" #include "DeclareExecutors.h" #include "CPPInstance.h" #include "LowLevelViews.h" @@ -78,20 +79,21 @@ CPPYY_IMPL_GILCALL(PY_LONG_DOUBLE, LD) CPPYY_IMPL_GILCALL(void*, R) static inline Cppyy::TCppObject_t GILCallO(Cppyy::TCppMethod_t method, - Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt, Cppyy::TCppType_t klass) + Cppyy::TCppObject_t self, CPyCppyy::CallContext* ctxt, Cppyy::TCppScope_t klass) { + Cppyy::TCppType_t klass_ty = Cppyy::GetTypeFromScope(klass); #ifdef WITH_THREAD if (!ReleasesGIL(ctxt)) #endif - return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass); + return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass_ty); #ifdef WITH_THREAD GILControl gc{}; - return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass); + return Cppyy::CallO(method, self, ctxt->GetEncodedSize(), ctxt->GetArgs(), klass_ty); #endif } static inline Cppyy::TCppObject_t GILCallConstructor( - Cppyy::TCppMethod_t method, Cppyy::TCppType_t klass, CPyCppyy::CallContext* ctxt) + Cppyy::TCppMethod_t method, Cppyy::TCppScope_t klass, CPyCppyy::CallContext* ctxt) { #ifdef WITH_THREAD if (!ReleasesGIL(ctxt)) @@ -389,9 +391,17 @@ PyObject* CPyCppyy::STLStringRefExecutor::Execute( Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) { // execute with argument , return python string return value + static Cppyy::TCppScope_t sSTLStringScope = Cppyy::GetFullScope("std::string"); + std::string* result = (std::string*)GILCallR(method, self, ctxt); if (!fAssignable) { - return CPyCppyy_PyText_FromStringAndSize(result->c_str(), result->size()); + std::string* rescp = new std::string{*result}; + return BindCppObjectNoCast((void*)rescp, sSTLStringScope, CPPInstance::kIsOwner); + } + + if (!CPyCppyy_PyText_Check(fAssignable)) { + PyErr_Format(PyExc_TypeError, "wrong type in assignment (string expected)"); + return nullptr; } *result = std::string( @@ -556,18 +566,16 @@ PyObject* CPyCppyy::STLStringExecutor::Execute( // execute with argument , construct python string return value // TODO: make use of GILLCallS (?!) - static Cppyy::TCppScope_t sSTLStringScope = Cppyy::GetScope("std::string"); + static Cppyy::TCppScope_t sSTLStringScope = Cppyy::GetFullScope("std::string"); std::string* result = (std::string*)GILCallO(method, self, ctxt, sSTLStringScope); - if (!result) { - Py_INCREF(PyStrings::gEmptyString); - return PyStrings::gEmptyString; + if (!result) + result = new std::string{}; + else if (PyErr_Occurred()) { + delete result; + return nullptr; } - PyObject* pyresult = - CPyCppyy_PyText_FromStringAndSize(result->c_str(), result->size()); - delete result; // Cppyy::CallO allocates and constructs a string, so it must be properly destroyed - - return pyresult; + return BindCppObjectNoCast((void*)result, sSTLStringScope, CPPInstance::kIsOwner); } //---------------------------------------------------------------------------- @@ -575,7 +583,7 @@ PyObject* CPyCppyy::STLWStringExecutor::Execute( Cppyy::TCppMethod_t method, Cppyy::TCppObject_t self, CallContext* ctxt) { // execute with argument , construct python string return value - static Cppyy::TCppScope_t sSTLWStringScope = Cppyy::GetScope("std::wstring"); + static Cppyy::TCppScope_t sSTLWStringScope = Cppyy::GetFullScope("std::wstring"); std::wstring* result = (std::wstring*)GILCallO(method, self, ctxt, sSTLWStringScope); if (!result) { wchar_t w = L'\0'; @@ -597,7 +605,7 @@ PyObject* CPyCppyy::InstancePtrExecutor::Execute( } //---------------------------------------------------------------------------- -CPyCppyy::InstanceExecutor::InstanceExecutor(Cppyy::TCppType_t klass) : +CPyCppyy::InstanceExecutor::InstanceExecutor(Cppyy::TCppScope_t klass) : fClass(klass), fFlags(CPPInstance::kIsValue | CPPInstance::kIsOwner) { /* empty */ @@ -628,7 +636,7 @@ PyObject* CPyCppyy::InstanceExecutor::Execute( //---------------------------------------------------------------------------- -CPyCppyy::IteratorExecutor::IteratorExecutor(Cppyy::TCppType_t klass) : +CPyCppyy::IteratorExecutor::IteratorExecutor(Cppyy::TCppScope_t klass) : InstanceExecutor(klass) { fFlags |= CPPInstance::kNoMemReg | CPPInstance::kNoWrapConv; // adds to flags from base class @@ -748,7 +756,7 @@ PyObject* CPyCppyy::ConstructorExecutor::Execute( { // package return address in PyObject* for caller to handle appropriately (see // CPPConstructor for the actual build of the PyObject) - return (PyObject*)GILCallConstructor(method, (Cppyy::TCppType_t)klass, ctxt); + return (PyObject*)GILCallConstructor(method, (Cppyy::TCppScope_t)klass, ctxt); } //---------------------------------------------------------------------------- @@ -790,13 +798,16 @@ CPyCppyy::Executor* CPyCppyy::CreateExecutor(const std::string& fullType, cdims_ // // If all fails, void is used, which will cause the return type to be ignored on use + if (fullType.empty()) + return nullptr; + // an exactly matching executor is best ExecFactories_t::iterator h = gExecFactories.find(fullType); if (h != gExecFactories.end()) return (h->second)(dims); // resolve typedefs etc. - const std::string& resolvedType = Cppyy::ResolveName(fullType); + const std::string resolvedType = Cppyy::ResolveName(fullType); // a full, qualified matching executor is preferred if (resolvedType != fullType) { @@ -840,7 +851,7 @@ CPyCppyy::Executor* CPyCppyy::CreateExecutor(const std::string& fullType, cdims_ // C++ classes and special cases Executor* result = 0; - if (Cppyy::TCppType_t klass = Cppyy::GetScope(realType)) { + if (Cppyy::TCppType_t klass = Cppyy::GetFullScope(realType)) { if (Utility::IsSTLIterator(realType) || gIteratorTypes.find(fullType) != gIteratorTypes.end()) { if (cpd == "") return new IteratorExecutor(klass); @@ -883,6 +894,131 @@ CPyCppyy::Executor* CPyCppyy::CreateExecutor(const std::string& fullType, cdims_ return result; // may still be null } +CPyCppyy::Executor* CPyCppyy::CreateExecutor(Cppyy::TCppType_t type, cdims_t dims) +{ +// The matching of the fulltype to an executor factory goes through up to 4 levels: +// 1) full, qualified match +// 2) drop '&' as by ref/full type is often pretty much the same python-wise +// 3) C++ classes, either by ref/ptr or by value +// 4) additional special case for enums +// +// If all fails, void is used, which will cause the return type to be ignored on use + +// an exactly matching executor is best + std::string fullType = Cppyy::GetTypeAsString(type); + if (fullType.ends_with(" &")) + fullType = fullType.substr(0, fullType.size() - 2) + "&"; + + ExecFactories_t::iterator h = gExecFactories.find(fullType); + if (h != gExecFactories.end()) + return (h->second)(dims); + +// resolve typedefs etc. + Cppyy::TCppType_t resolvedType = Cppyy::ResolveType(type); + { + // if resolvedType is a reference to enum + // then it should be reduced to reference + // to the underlying interger + resolvedType = Cppyy::ResolveEnumReferenceType(resolvedType); + // similarly for pointers + resolvedType = Cppyy::ResolveEnumPointerType(resolvedType); + } + // FIXME: avoid string comparisons and parsing + std::string resolvedTypeStr = Cppyy::GetTypeAsString(resolvedType); + if (Cppyy::IsFunctionPointerType(resolvedType)) { + resolvedTypeStr.erase(std::remove(resolvedTypeStr.begin(), resolvedTypeStr.end(), ' '), resolvedTypeStr.end()); + if (resolvedTypeStr.rfind("(void)") != std::string::npos) + resolvedTypeStr = resolvedTypeStr.substr(0, resolvedTypeStr.size() - 6) + "()"; + } + +// a full, qualified matching executor is preferred + if (resolvedTypeStr != fullType) { + h = gExecFactories.find(resolvedTypeStr); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + +//-- nothing? ok, collect information about the type and possible qualifiers/decorators + bool isConst = strncmp(resolvedTypeStr.c_str(), "const", 5) == 0; + const std::string& cpd = TypeManip::compound(resolvedTypeStr); + Cppyy::TCppType_t realType = Cppyy::IsFunctionPointerType(resolvedType) ? resolvedType : Cppyy::GetRealType(resolvedType); + std::string realTypeStr = Cppyy::IsFunctionPointerType(resolvedType) ? resolvedTypeStr : Cppyy::GetTypeAsString(realType); + const std::string compounded = cpd.empty() ? realTypeStr : realTypeStr + cpd; + +// accept unqualified type (as python does not know about qualifiers) + h = gExecFactories.find(compounded); + if (h != gExecFactories.end()) + return (h->second)(dims); + +// drop const, as that is mostly meaningless to python (with the exception +// of c-strings, but those are specialized in the converter map) + if (isConst) { + realTypeStr = TypeManip::remove_const(realTypeStr); + h = gExecFactories.find(compounded); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + +// simple array types + if (!cpd.empty() && (std::string::size_type)std::count(cpd.begin(), cpd.end(), '*') == cpd.size()) { + h = gExecFactories.find(realTypeStr + " ptr"); + if (h != gExecFactories.end()) + return (h->second)((!dims || dims.ndim() < (dim_t)cpd.size()) ? dims_t(cpd.size()) : dims); + } + +//-- still nothing? try pointer instead of array (for builtins) + if (cpd == "[]") { + h = gExecFactories.find(realTypeStr + "*"); + if (h != gExecFactories.end()) + return (h->second)(dims); + } + +// C++ classes and special cases + Executor* result = 0; + if (Cppyy::IsClassType(realType)) { + Cppyy::TCppScope_t klass = Cppyy::GetScopeFromType(realType); + if (resolvedTypeStr.find("iterator") != std::string::npos || gIteratorTypes.find(fullType) != gIteratorTypes.end()) { + if (cpd == "") + return new IteratorExecutor(klass); + } + + if (cpd == "") + result = new InstanceExecutor(klass); + else if (cpd == "&") + result = new InstanceRefExecutor(klass); + else if (cpd == "**" || cpd == "*[]" || cpd == "&*") + result = new InstancePtrPtrExecutor(klass); + else if (cpd == "*&") + result = new InstancePtrRefExecutor(klass); + else if (cpd == "[]") { + Py_ssize_t asize = TypeManip::array_size(resolvedTypeStr); + if (0 < asize) + result = new InstanceArrayExecutor(klass, asize); + else + result = new InstancePtrRefExecutor(klass); + } else + result = new InstancePtrExecutor(klass); + } else if (realTypeStr.find("(*)") != std::string::npos || + (realTypeStr.find("::*)") != std::string::npos)) { + // this is a function pointer + // TODO: find better way of finding the type + auto pos1 = realTypeStr.find('('); + auto pos2 = realTypeStr.find("*)"); + auto pos3 = realTypeStr.rfind(')'); + result = new FunctionPointerExecutor( + realTypeStr.substr(0, pos1), realTypeStr.substr(pos2+2, pos3-pos2-1)); + } else { + // unknown: void* may work ("user knows best"), void will fail on use of return value + h = (cpd == "") ? gExecFactories.find("void") : gExecFactories.find("void ptr"); + } + + if (!result && h != gExecFactories.end()) + // executor factory available, use it to create executor + result = (h->second)(dims); + + return result; // may still be null +} + //---------------------------------------------------------------------------- CPYCPPYY_EXPORT void CPyCppyy::DestroyExecutor(Executor* p) @@ -1047,9 +1183,7 @@ struct InitExecFactories_t { gf["internal_enum_type_t&"] = gf["int&"]; gf["internal_enum_type_t ptr"] = gf["int ptr"]; gf["std::byte"] = gf["uint8_t"]; - gf["byte"] = gf["uint8_t"]; gf["std::byte&"] = gf["uint8_t&"]; - gf["byte&"] = gf["uint8_t&"]; gf["const std::byte&"] = gf["const uint8_t&"]; gf["const byte&"] = gf["const uint8_t&"]; gf["std::int8_t"] = gf["int8_t"]; @@ -1095,13 +1229,11 @@ struct InitExecFactories_t { gf["wchar_t*"] = (ef_t)+[](cdims_t) { static WCStringExecutor e{}; return &e;}; gf["char16_t*"] = (ef_t)+[](cdims_t) { static CString16Executor e{}; return &e;}; gf["char32_t*"] = (ef_t)+[](cdims_t) { static CString32Executor e{}; return &e;}; - gf["std::string"] = (ef_t)+[](cdims_t) { static STLStringExecutor e{}; return &e; }; - gf["string"] = gf["std::string"]; - gf["std::string&"] = (ef_t)+[](cdims_t) { return new STLStringRefExecutor{}; }; - gf["string&"] = gf["std::string&"]; - gf["std::wstring"] = (ef_t)+[](cdims_t) { static STLWStringExecutor e{}; return &e; }; - gf[WSTRING1] = gf["std::wstring"]; - gf[WSTRING2] = gf["std::wstring"]; + gf["std::basic_string"] = (ef_t)+[](cdims_t) { static STLStringExecutor e{}; return &e; }; + gf["std::basic_string&"] = (ef_t)+[](cdims_t) { return new STLStringRefExecutor{}; }; + gf["std::basic_string"] = (ef_t)+[](cdims_t) { static STLWStringExecutor e{}; return &e; }; + gf[WSTRING1] = gf["std::basic_string"]; + gf[WSTRING2] = gf["std::basic_string"]; gf["__init__"] = (ef_t)+[](cdims_t) { static ConstructorExecutor e{}; return &e; }; gf["PyObject*"] = (ef_t)+[](cdims_t) { static PyObjectExecutor e{}; return &e; }; gf["_object*"] = gf["PyObject*"]; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.h b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.h index e043bf164fa55..50eab0a336c04 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.h @@ -33,6 +33,7 @@ class RefExecutor : public Executor { // create/destroy executor from fully qualified type (public API) CPYCPPYY_EXPORT Executor* CreateExecutor(const std::string& fullType, cdims_t = 0); +CPYCPPYY_EXPORT Executor* CreateExecutor(Cppyy::TCppType_t type, cdims_t = 0); CPYCPPYY_EXPORT void DestroyExecutor(Executor* p); typedef Executor* (*ef_t) (cdims_t); CPYCPPYY_EXPORT bool RegisterExecutor(const std::string& name, ef_t fac); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx index c71df38b71223..e51015ff8aedd 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx @@ -1197,43 +1197,3 @@ PyObject* CPyCppyy::CreateLowLevelView_i8(uint8_t** address, cdims_t shape) { LowLevelView* ll = CreateLowLevelViewT(address, shape, "B", "uint8_t"); CPPYY_RET_W_CREATOR(uint8_t**, CreateLowLevelView_i8); } - -PyObject* CPyCppyy::CreateLowLevelView_i16(int16_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "h", "int16_t"); - CPPYY_RET_W_CREATOR(int16_t*, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i16(int16_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "h", "int16_t"); - CPPYY_RET_W_CREATOR(int16_t**, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i16(uint16_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "H", "uint16_t"); - CPPYY_RET_W_CREATOR(uint16_t*, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i16(uint16_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "H", "uint16_t"); - CPPYY_RET_W_CREATOR(uint16_t**, CreateLowLevelView_i16); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(int32_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "i", "int32_t"); - CPPYY_RET_W_CREATOR(int32_t*, CreateLowLevelView_i32); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(int32_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "i", "int32_t"); - CPPYY_RET_W_CREATOR(int32_t**, CreateLowLevelView_i32); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(uint32_t* address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "I", "uint32_t"); - CPPYY_RET_W_CREATOR(uint32_t*, CreateLowLevelView_i32); -} - -PyObject* CPyCppyy::CreateLowLevelView_i32(uint32_t** address, cdims_t shape) { - LowLevelView* ll = CreateLowLevelViewT(address, shape, "I", "uint32_t"); - CPPYY_RET_W_CREATOR(uint32_t**, CreateLowLevelView_i32); -} diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h index b3b6c8daa16ca..0edb32954a60b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h @@ -52,15 +52,6 @@ PyObject* CreateLowLevelView_i8(int8_t*, cdims_t shape); PyObject* CreateLowLevelView_i8(int8_t**, cdims_t shape); PyObject* CreateLowLevelView_i8(uint8_t*, cdims_t shape); PyObject* CreateLowLevelView_i8(uint8_t**, cdims_t shape); -PyObject* CreateLowLevelView_i16(int16_t*, cdims_t shape); -PyObject* CreateLowLevelView_i16(int16_t**, cdims_t shape); -PyObject* CreateLowLevelView_i16(uint16_t*, cdims_t shape); -PyObject* CreateLowLevelView_i16(uint16_t**, cdims_t shape); -PyObject* CreateLowLevelView_i32(int32_t*, cdims_t shape); -PyObject* CreateLowLevelView_i32(int32_t**, cdims_t shape); -PyObject* CreateLowLevelView_i32(uint32_t*, cdims_t shape); -PyObject* CreateLowLevelView_i32(uint32_t**, cdims_t shape); - CPPYY_DECL_VIEW_CREATOR(short); CPPYY_DECL_VIEW_CREATOR(unsigned short); CPPYY_DECL_VIEW_CREATOR(int); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx index 52d8261bc1fda..8103c1cb1357a 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx @@ -35,25 +35,13 @@ static PyMappingMethods CPyCppyy_NoneType_mapping = { //----------------------------------------------------------------------------- namespace { -// Py_SET_REFCNT was only introduced in Python 3.9 -#if PY_VERSION_HEX < 0x03090000 -inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { - assert(refcnt >= 0); -#if SIZEOF_VOID_P > 4 - ob->ob_refcnt = (PY_UINT32_T)refcnt; -#else - ob->ob_refcnt = refcnt; -#endif -} -#endif - struct InitCPyCppyy_NoneType_t { InitCPyCppyy_NoneType_t() { // create a CPyCppyy NoneType (for references that went dodo) from NoneType memset(&CPyCppyy_NoneType, 0, sizeof(CPyCppyy_NoneType)); ((PyObject&)CPyCppyy_NoneType).ob_type = &PyType_Type; - Py_SET_REFCNT((PyObject*)&CPyCppyy_NoneType, 1); + ((PyObject&)CPyCppyy_NoneType).ob_refcnt = 1; ((PyVarObject&)CPyCppyy_NoneType).ob_size = 0; CPyCppyy_NoneType.tp_name = const_cast("CPyCppyy_NoneType"); @@ -159,10 +147,10 @@ bool CPyCppyy::MemoryRegulator::RecursiveRemove( } // notify any other weak referents by playing dead - Py_ssize_t refcnt = Py_REFCNT((PyObject*)pyobj); - Py_SET_REFCNT((PyObject*)pyobj, 0); + Py_ssize_t refcnt = ((PyObject*)pyobj)->ob_refcnt; + ((PyObject*)pyobj)->ob_refcnt = 0; PyObject_ClearWeakRefs((PyObject*)pyobj); - Py_SET_REFCNT((PyObject*)pyobj, refcnt); + ((PyObject*)pyobj)->ob_refcnt = refcnt; // cleanup object internals pyobj->CppOwns(); // held object is out of scope now anyway diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx index 35a76b0552763..4e809d94e3769 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx @@ -90,16 +90,16 @@ static PyObject* CreateNewCppProxyClass(Cppyy::TCppScope_t klass, PyObject* pyba static inline void AddPropertyToClass(PyObject* pyclass, - Cppyy::TCppScope_t scope, Cppyy::TCppIndex_t idata) + Cppyy::TCppScope_t scope, Cppyy::TCppScope_t data) { - CPyCppyy::CPPDataMember* property = CPyCppyy::CPPDataMember_New(scope, idata); + CPyCppyy::CPPDataMember* property = CPyCppyy::CPPDataMember_New(scope, data); PyObject* pname = CPyCppyy_PyText_InternFromString(const_cast(property->GetName().c_str())); // allow access at the instance level PyType_Type.tp_setattro(pyclass, pname, (PyObject*)property); // allow access at the class level (always add after setting instance level) - if (Cppyy::IsStaticData(scope, idata)) + if (Cppyy::IsStaticDatamember(data) || Cppyy::IsEnumConstant(data)) PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pname, (PyObject*)property); // cleanup @@ -159,7 +159,7 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons // some properties that'll affect building the dictionary bool isNamespace = Cppyy::IsNamespace(scope); - bool isAbstract = Cppyy::IsAbstract(scope); + bool isComplete = Cppyy::IsComplete(scope); bool hasConstructor = false; Cppyy::TCppMethod_t potGetItem = (Cppyy::TCppMethod_t)0; @@ -174,9 +174,10 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons // functions in namespaces are properly found through lazy lookup, so do not // create them until needed (the same is not true for data members) - const Cppyy::TCppIndex_t nMethods = isNamespace ? 0 : Cppyy::GetNumMethods(scope); - for (Cppyy::TCppIndex_t imeth = 0; imeth < nMethods; ++imeth) { - Cppyy::TCppMethod_t method = Cppyy::GetMethod(scope, imeth); + std::vector methods; + if (isComplete) Cppyy::GetClassMethods(scope, methods); + + for (auto &method : methods) { // do not expose non-public methods as the Cling wrappers as those won't compile if (!Cppyy::IsPublicMethod(method)) @@ -188,7 +189,9 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons // special case trackers bool setupSetItem = false; bool isConstructor = Cppyy::IsConstructor(method); - bool isTemplate = isConstructor ? false : Cppyy::IsMethodTemplate(scope, imeth); + + // Here isTemplate means to ignore templated constructors, that is handled in the next loop. + bool isTemplate = Cppyy::IsTemplatedMethod(method) && !isConstructor; bool isStubbedOperator = false; // filter empty names (happens for namespaces, is bug?) @@ -208,7 +211,7 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons // operator[]/() returning a reference type will be used for __setitem__ bool isCall = mtName == "__call__"; if (isCall || mtName == "__getitem__") { - const std::string& qual_return = Cppyy::ResolveName(Cppyy::GetMethodResultType(method)); + const std::string& qual_return = Cppyy::GetMethodReturnTypeAsString(method); const std::string& cpd = TypeManip::compound(qual_return); if (!cpd.empty() && cpd[cpd.size()-1] == '&' && \ qual_return.find("const", 0, 5) == std::string::npos) { @@ -224,8 +227,7 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons } // template members; handled by adding a dispatcher to the class - bool storeOnTemplate = - isTemplate ? true : (!isConstructor && Cppyy::ExistsMethodTemplate(scope, mtCppName)); + bool storeOnTemplate = isTemplate; if (storeOnTemplate) { sync_templates(pyclass, mtCppName, mtName); // continue processing to actually add the method so that the proxy can find @@ -241,7 +243,7 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons else if (isConstructor) { // ctor mtName = "__init__"; hasConstructor = true; - if (!isAbstract) { + if (!Cppyy::IsAbstract(scope)) { if (flags & CPPScope::kIsMultiCross) { pycall = new CPPMultiConstructor(scope, method); } else @@ -292,14 +294,16 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons } } + // add proxies for un-instantiated/non-overloaded templated methods - const Cppyy::TCppIndex_t nTemplMethods = isNamespace ? 0 : Cppyy::GetNumTemplatedMethods(scope); - for (Cppyy::TCppIndex_t imeth = 0; imeth < nTemplMethods; ++imeth) { - const std::string mtCppName = Cppyy::GetTemplatedMethodName(scope, imeth); + std::vector templ_methods; + Cppyy::GetTemplatedMethods(scope, templ_methods); + for (auto &method : templ_methods) { + const std::string mtCppName = Cppyy::GetMethodName(method); // the number of arguments isn't known until instantiation and as far as C++ is concerned, all // same-named operators are simply overloads; so will pre-emptively add both names if with and // without arguments differ, letting the normal overload mechanism resolve on call - bool isConstructor = Cppyy::IsTemplatedConstructor(scope, imeth); + bool isConstructor = Cppyy::IsConstructor(method); // first add with no arguments std::string mtName0 = isConstructor ? "__init__" : Utility::MapOperatorName(mtCppName, false); @@ -316,16 +320,19 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons // add a pseudo-default ctor, if none defined if (!hasConstructor) { PyCallable* defctor = nullptr; - if (isAbstract) - defctor = new CPPAbstractClassConstructor(scope, (Cppyy::TCppMethod_t)0); - else if (isNamespace) - defctor = new CPPNamespaceConstructor(scope, (Cppyy::TCppMethod_t)0); - else if (!Cppyy::IsComplete(Cppyy::GetScopedFinalName(scope))) { + if (!isComplete) { ((CPPScope*)pyclass)->fFlags |= CPPScope::kIsInComplete; defctor = new CPPIncompleteClassConstructor(scope, (Cppyy::TCppMethod_t)0); - } else + } else if (Cppyy::IsAbstract(scope)) { + defctor = new CPPAbstractClassConstructor(scope, (Cppyy::TCppMethod_t)0); + } else if (isNamespace) { + defctor = new CPPNamespaceConstructor(scope, (Cppyy::TCppMethod_t)0); + } else { defctor = new CPPAllPrivateClassConstructor(scope, (Cppyy::TCppMethod_t)0); - cache["__init__"].push_back(defctor); + } + + if (defctor) + cache["__init__"].push_back(defctor); } // map __call__ to __getitem__ if also mapped to __setitem__ @@ -363,22 +370,19 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons Py_DECREF(dct); // collect data members (including enums) - const Cppyy::TCppIndex_t nDataMembers = Cppyy::GetNumDatamembers(scope); - for (Cppyy::TCppIndex_t idata = 0; idata < nDataMembers; ++idata) { + std::vector datamembers; + Cppyy::GetDatamembers(scope, datamembers); + for (auto &datamember : datamembers) { // allow only public members - if (!Cppyy::IsPublicData(scope, idata)) + if (!Cppyy::IsPublicData(datamember)) continue; // enum datamembers (this in conjunction with previously collected enums above) - if (Cppyy::IsEnumData(scope, idata) && Cppyy::IsStaticData(scope, idata)) { - // some implementation-specific data members have no address: ignore them - if (!Cppyy::GetDatamemberOffset(scope, idata)) - continue; - + if (Cppyy::IsEnumType(Cppyy::GetDatamemberType(datamember)) && Cppyy::IsEnumConstant(datamember)) { // two options: this is a static variable, or it is the enum value, the latter // already exists, so check for it and move on if set PyObject* eset = PyObject_GetAttrString(pyclass, - const_cast(Cppyy::GetDatamemberName(scope, idata).c_str())); + const_cast(Cppyy::GetFinalName(datamember).c_str())); if (eset) { Py_DECREF(eset); continue; @@ -388,15 +392,15 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons // it could still be that this is an anonymous enum, which is not in the list // provided by the class - if (strstr(Cppyy::GetDatamemberType(scope, idata).c_str(), "(anonymous)") != 0 || - strstr(Cppyy::GetDatamemberType(scope, idata).c_str(), "(unnamed)") != 0) { - AddPropertyToClass(pyclass, scope, idata); + if (strstr(Cppyy::GetDatamemberTypeAsString(datamember).c_str(), "(anonymous)") != 0 || + strstr(Cppyy::GetDatamemberTypeAsString(datamember).c_str(), "(unnamed)") != 0) { + AddPropertyToClass(pyclass, scope, datamember); continue; } } // properties (aka public (static) data members) - AddPropertyToClass(pyclass, scope, idata); + AddPropertyToClass(pyclass, scope, datamember); } // restore custom __getattr__ @@ -407,26 +411,24 @@ static int BuildScopeProxyDict(Cppyy::TCppScope_t scope, PyObject* pyclass, cons } //---------------------------------------------------------------------------- -static void CollectUniqueBases(Cppyy::TCppType_t klass, std::deque& uqb) +static void CollectUniqueBases(Cppyy::TCppScope_t klass, std::deque& uqb) { // collect bases in acceptable mro order, while removing duplicates (this may // break the overload resolution in esoteric cases, but otherwise the class can // not be used at all, as CPython will refuse the mro). size_t nbases = Cppyy::GetNumBases(klass); - std::deque bids; for (size_t ibase = 0; ibase < nbases; ++ibase) { - const std::string& name = Cppyy::GetBaseName(klass, ibase); + Cppyy::TCppScope_t tp = Cppyy::GetBaseScope(klass, ibase); int decision = 2; - Cppyy::TCppType_t tp = Cppyy::GetScope(name); if (!tp) continue; // means this base with not be available Python-side for (size_t ibase2 = 0; ibase2 < uqb.size(); ++ibase2) { - if (uqb[ibase2] == name) { // not unique ... skip + if (uqb[ibase2] == tp) { // not unique ... skip decision = 0; break; } - if (Cppyy::IsSubtype(tp, bids[ibase2])) { + if (Cppyy::IsSubclass(tp, uqb[ibase2])) { // mro requirement: sub-type has to follow base decision = 1; break; @@ -434,20 +436,18 @@ static void CollectUniqueBases(Cppyy::TCppType_t klass, std::deque& } if (decision == 1) { - uqb.push_front(name); - bids.push_front(tp); + uqb.push_front(tp); } else if (decision == 2) { - uqb.push_back(name); - bids.push_back(tp); + uqb.push_back(tp); } // skipped if decision == 0 (not unique) } } -static PyObject* BuildCppClassBases(Cppyy::TCppType_t klass) +static PyObject* BuildCppClassBases(Cppyy::TCppScope_t klass) { // Build a tuple of python proxy classes of all the bases of the given 'klass'. - std::deque uqb; + std::deque uqb; CollectUniqueBases(klass, uqb); // allocate a tuple for the base classes, special case for first base @@ -462,7 +462,7 @@ static PyObject* BuildCppClassBases(Cppyy::TCppType_t klass) Py_INCREF((PyObject*)(void*)&CPPInstance_Type); PyTuple_SET_ITEM(pybases, 0, (PyObject*)(void*)&CPPInstance_Type); } else { - for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { + for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { PyObject* pyclass = CreateScopeProxy(uqb[ibase]); if (!pyclass) { Py_DECREF(pybases); @@ -506,16 +506,27 @@ PyObject* CPyCppyy::GetScopeProxy(Cppyy::TCppScope_t scope) return nullptr; } - -//---------------------------------------------------------------------------- -PyObject* CPyCppyy::CreateScopeProxy(Cppyy::TCppScope_t scope, const unsigned flags) -{ -// Convenience function with a lookup first through the known existing proxies. - PyObject* pyclass = GetScopeProxy(scope); - if (pyclass) - return pyclass; - - return CreateScopeProxy(Cppyy::GetScopedFinalName(scope), nullptr, flags); +namespace CPyCppyy { +PyObject *CppType_To_PyObject(Cppyy::TCppType_t type, std::string name, Cppyy::TCppScope_t parent_scope, PyObject *parent) { + Cppyy::TCppType_t resolved_type = Cppyy::ResolveType(type); + if (gPyTypeMap) { + const std::string& resolved = Cppyy::GetTypeAsString(resolved_type); + PyObject* tc = PyDict_GetItemString(gPyTypeMap, resolved.c_str()); // borrowed + if (tc && PyCallable_Check(tc)) { + const std::string& scName = Cppyy::GetScopedFinalName(parent_scope); + PyObject* nt = PyObject_CallFunction(tc, (char*)"ss", name.c_str(), scName != "" ? scName.c_str() : ""); + if (nt) { + if (parent) { + AddScopeToParent(parent, name, nt); + Py_DECREF(parent); + } + return nt; + } + PyErr_Clear(); + } + } + return nullptr; +} } //---------------------------------------------------------------------------- @@ -535,10 +546,10 @@ PyObject* CPyCppyy::CreateScopeProxy(const std::string& name, PyObject* parent, // Build a python shadow class for the named C++ class or namespace. // determine complete scope name, if a python parent has been given - std::string scName = ""; + Cppyy::TCppScope_t parent_scope = 0; if (parent) { if (CPPScope_Check(parent)) - scName = Cppyy::GetScopedFinalName(((CPPScope*)parent)->fCppType); + parent_scope = ((CPPScope*)parent)->fCppType; else { PyObject* parname = PyObject_GetAttr(parent, PyStrings::gName); if (!parname) { @@ -547,66 +558,107 @@ PyObject* CPyCppyy::CreateScopeProxy(const std::string& name, PyObject* parent, } // should be a string - scName = CPyCppyy_PyText_AsString(parname); + std::string scName = CPyCppyy_PyText_AsString(parname); Py_DECREF(parname); if (PyErr_Occurred()) return nullptr; + parent_scope = Cppyy::GetScope(scName); } - // accept this parent scope and use it's name for prefixing Py_INCREF(parent); } // retrieve C++ class (this verifies name, and is therefore done first) - const std::string& lookup = scName.empty() ? name : (scName+"::"+name); - Cppyy::TCppScope_t klass = Cppyy::GetScope(lookup); + if (name == "") { + Cppyy::TCppScope_t klass = Cppyy::GetGlobalScope(); + Py_INCREF(gThisModule); + parent = gThisModule; + return CreateScopeProxy(klass, parent, flags); + } else if (Cppyy::TCppScope_t klass = Cppyy::GetScope(name, parent_scope)) { + if (Cppyy::IsTypedefed(klass) && + Cppyy::IsPointerType(Cppyy::GetTypeFromScope(klass)) && + Cppyy::IsClass(Cppyy::GetUnderlyingScope(klass))) + return nullptr; // this is handled by the caller; typedef to class pointer + return CreateScopeProxy(klass, parent, flags); + } else if (Cppyy::IsBuiltin(name)) { + Cppyy::TCppType_t type = Cppyy::GetType(name); + PyObject *result = nullptr; + if (type) + result = CppType_To_PyObject(type, name, parent_scope, parent); + if (result) + return result; + } + // all options have been exhausted: it doesn't exist as such + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", name.c_str()); + Py_XDECREF(parent); + return nullptr; +} + +//---------------------------------------------------------------------------- +PyObject* CPyCppyy::CreateScopeProxy(Cppyy::TCppScope_t scope, PyObject* parent, const unsigned flags) +{ +// Convenience function with a lookup first through the known existing proxies. + PyObject* pyclass = GetScopeProxy(scope); + if (pyclass) + return pyclass; - if (!(bool)klass && Cppyy::IsTemplate(lookup)) { - // a "naked" templated class is requested: return callable proxy for instantiations + Cppyy::TCppScope_t parent_scope = nullptr; + if (CPPScope_Check(parent)) + parent_scope = ((CPPScope*)parent)->fCppType; + if (!parent_scope) + parent_scope = Cppyy::GetParentScope(scope); + + if (!parent) { + if (parent_scope) + parent = CreateScopeProxy(parent_scope); + else { + Py_INCREF(gThisModule); + parent = gThisModule; + } + } + + std::string name = Cppyy::GetFinalName(scope); + bool is_typedef = false; + std::string typedefed_name = ""; + if (Cppyy::IsTypedefed(scope)) { + is_typedef = true; + typedefed_name = Cppyy::GetMethodFullName(scope); + Cppyy::TCppScope_t underlying_scope = Cppyy::GetUnderlyingScope(scope); + if ((underlying_scope) && (underlying_scope != scope)) { + scope = underlying_scope; + } else { + if (PyObject *result = CppType_To_PyObject(Cppyy::GetTypeFromScope(scope), name, parent_scope, parent)) + return result; + + PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", Cppyy::GetScopedFinalName(scope).c_str()); + Py_XDECREF(parent); + return nullptr; + } + } + + if (Cppyy::IsTemplate(scope)) { + // a "naked" templated class is requested: return callable proxy for instantiations PyObject* pytcl = PyObject_GetAttr(gThisModule, PyStrings::gTemplate); + PyObject* cppscope = PyLong_FromVoidPtr(scope); PyObject* pytemplate = PyObject_CallFunction( - pytcl, const_cast("s"), const_cast(lookup.c_str())); + pytcl, const_cast("sO"), + const_cast(Cppyy::GetScopedFinalName(scope).c_str()), + cppscope); Py_DECREF(pytcl); // cache the result - AddScopeToParent(parent ? parent : gThisModule, name, pytemplate); + AddScopeToParent(parent, name, pytemplate); // done, next step should be a call into this template Py_XDECREF(parent); return pytemplate; } - if (!(bool)klass) { - // could be an enum, which are treated separately in CPPScope (TODO: maybe they - // should be handled here instead anyway??) - if (Cppyy::IsEnum(lookup)) - return nullptr; - - // final possibility is a typedef of a builtin; these are mapped on the python side - std::string resolved = Cppyy::ResolveName(lookup); - if (gPyTypeMap) { - PyObject* tc = PyDict_GetItemString(gPyTypeMap, resolved.c_str()); // borrowed - if (tc && PyCallable_Check(tc)) { - PyObject* nt = PyObject_CallFunction(tc, (char*)"ss", name.c_str(), scName.c_str()); - if (nt) { - if (parent) { - AddScopeToParent(parent, name, nt); - Py_DECREF(parent); - } - return nt; - } - PyErr_Clear(); - } - } - - // all options have been exhausted: it doesn't exist as such - PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ class", lookup.c_str()); - Py_XDECREF(parent); + if (Cppyy::IsEnumScope(scope)) return nullptr; - } // locate class by ID, if possible, to prevent parsing scopes/templates anew - PyObject* pyscope = GetScopeProxy(klass); + PyObject* pyscope = GetScopeProxy(scope); if (pyscope) { if (parent) { AddScopeToParent(parent, name, pyscope); @@ -615,86 +667,20 @@ PyObject* CPyCppyy::CreateScopeProxy(const std::string& name, PyObject* parent, return pyscope; } -// now have a class ... get the actual, fully scoped class name, so that typedef'ed -// classes are created in the right place - const std::string& actual = Cppyy::GetScopedFinalName(klass); - if (actual != lookup) { - pyscope = CreateScopeProxy(actual); - if (!pyscope) PyErr_Clear(); - } - -// locate the parent, if necessary, for memoizing the class if not specified - std::string::size_type last = 0; - if (!parent) { - // TODO: move this to TypeManip, which already has something similar in - // the form of 'extract_namespace' - // need to deal with template parameters that can have scopes themselves - int tpl_open = 0; - for (std::string::size_type pos = 0; pos < name.size(); ++pos) { - std::string::value_type c = name[pos]; - - // count '<' and '>' to be able to skip template contents - if (c == '<') - ++tpl_open; - else if (c == '>') - --tpl_open; - - // by only checking for "::" the last part (class name) is dropped - else if (tpl_open == 0 && \ - c == ':' && pos+1 < name.size() && name[ pos+1 ] == ':') { - // found a new scope part - const std::string& part = name.substr(last, pos-last); - - PyObject* next = PyObject_GetAttrString( - parent ? parent : gThisModule, const_cast(part.c_str())); - - if (!next) { // lookup failed, try to create it - PyErr_Clear(); - next = CreateScopeProxy(part, parent); - } - Py_XDECREF(parent); - - if (!next) // create failed, give up - return nullptr; - - // found scope part - parent = next; - - // done with part (note that pos is moved one ahead here) - last = pos+2; ++pos; - } - } - - if (parent && !CPPScope_Check(parent)) { - // Special case: parent found is not one of ours (it's e.g. a pure Python module), so - // continuing would fail badly. One final lookup, then out of here ... - std::string unscoped = name.substr(last, std::string::npos); - PyObject* ret = PyObject_GetAttrString(parent, unscoped.c_str()); - Py_DECREF(parent); - return ret; - } - } - -// use the module as a fake scope if no outer scope found - if (!parent) { - Py_INCREF(gThisModule); - parent = gThisModule; - } - -// if the scope was earlier found as actual, then we're done already, otherwise -// build a new scope proxy + // if the scope was earlier found as actual, then we're done already, otherwise + // build a new scope proxy if (!pyscope) { // construct the base classes - PyObject* pybases = BuildCppClassBases(klass); + PyObject* pybases = BuildCppClassBases(scope); if (pybases != 0) { // create a fresh Python class, given bases, name, and empty dictionary - pyscope = CreateNewCppProxyClass(klass, pybases); + pyscope = CreateNewCppProxyClass(scope, pybases); Py_DECREF(pybases); } // fill the dictionary, if successful if (pyscope) { - if (BuildScopeProxyDict(klass, pyscope, flags)) { + if (BuildScopeProxyDict(scope, pyscope, flags)) { // something failed in building the dictionary Py_DECREF(pyscope); pyscope = nullptr; @@ -703,11 +689,11 @@ PyObject* CPyCppyy::CreateScopeProxy(const std::string& name, PyObject* parent, // store a ref from cppyy scope id to new python class if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { - gPyClasses[klass] = PyWeakref_NewRef(pyscope, nullptr); + gPyClasses[scope] = PyWeakref_NewRef(pyscope, nullptr); if (!(((CPPScope*)pyscope)->fFlags & CPPScope::kIsNamespace)) { // add python-style features to classes only - if (!Pythonize(pyscope, Cppyy::GetScopedFinalName(klass))) { + if (!Pythonize(pyscope, scope)) { Py_DECREF(pyscope); pyscope = nullptr; } @@ -725,8 +711,14 @@ PyObject* CPyCppyy::CreateScopeProxy(const std::string& name, PyObject* parent, } // store on parent if found/created and complete - if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) + if (pyscope && !(((CPPScope*)pyscope)->fFlags & CPPScope::kIsInComplete)) { + // FIXME: This is to mimic original behaviour. Still required? + if (Cppyy::IsTemplateInstantiation(scope)) + name = Cppyy::GetScopedFinalName(scope); + if (is_typedef && !typedefed_name.empty()) + AddScopeToParent(parent, typedefed_name, pyscope); AddScopeToParent(parent, name, pyscope); + } Py_DECREF(parent); // all done @@ -743,7 +735,7 @@ PyObject* CPyCppyy::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyO // derives from Pythons Exception class // start with creation of CPPExcInstance type base classes - std::deque uqb; + std::deque uqb; CollectUniqueBases(((CPPScope*)pyscope)->fCppType, uqb); size_t nbases = uqb.size(); @@ -765,19 +757,18 @@ PyObject* CPyCppyy::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyO } else { PyObject* best_base = nullptr; - for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { + for (std::deque::size_type ibase = 0; ibase < nbases; ++ibase) { // retrieve bases through their enclosing scope to guarantee treatment as // exception classes and proper caching - const std::string& finalname = Cppyy::GetScopedFinalName(Cppyy::GetScope(uqb[ibase])); - const std::string& parentname = TypeManip::extract_namespace(finalname); - PyObject* base_parent = CreateScopeProxy(parentname); + Cppyy::TCppScope_t parent_scope = Cppyy::GetParentScope(uqb[ibase]); + PyObject* base_parent = CreateScopeProxy(parent_scope); if (!base_parent) { Py_DECREF(pybases); return nullptr; } PyObject* excbase = PyObject_GetAttrString(base_parent, - parentname.empty() ? finalname.c_str() : finalname.substr(parentname.size()+2, std::string::npos).c_str()); + Cppyy::GetFinalName(uqb[ibase]).c_str()); Py_DECREF(base_parent); if (!excbase) { Py_DECREF(pybases); @@ -787,7 +778,8 @@ PyObject* CPyCppyy::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyO if (PyType_IsSubtype((PyTypeObject*)excbase, &CPPExcInstance_Type)) { Py_XDECREF(best_base); best_base = excbase; - if (finalname != "std::exception") + + if (Cppyy::GetScopedFinalName(uqb[ibase]) != "std::exception") break; } else { // just skip: there will be at least one exception derived base class @@ -821,7 +813,7 @@ PyObject* CPyCppyy::CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyO //---------------------------------------------------------------------------- PyObject* CPyCppyy::BindCppObjectNoCast(Cppyy::TCppObject_t address, - Cppyy::TCppType_t klass, const unsigned flags) + Cppyy::TCppScope_t klass, const unsigned flags) { // only known or knowable objects will be bound (null object is ok) if (!klass) { @@ -908,7 +900,7 @@ PyObject* CPyCppyy::BindCppObjectNoCast(Cppyy::TCppObject_t address, //---------------------------------------------------------------------------- PyObject* CPyCppyy::BindCppObject(Cppyy::TCppObject_t address, - Cppyy::TCppType_t klass, const unsigned flags) + Cppyy::TCppScope_t klass, const unsigned flags) { // if the object is a null pointer, return a typed one (as needed for overloading) if (!address) @@ -927,24 +919,19 @@ PyObject* CPyCppyy::BindCppObject(Cppyy::TCppObject_t address, // successful, no down-casting is attempted? // TODO: optimize for final classes unsigned new_flags = flags; - if (gPinnedTypes.empty() || gPinnedTypes.find(klass) == gPinnedTypes.end()) { - if (!isRef) { - Cppyy::TCppType_t clActual = Cppyy::GetActualClass(klass, address); - - if (clActual) { - if (clActual != klass) { - intptr_t offset = Cppyy::GetBaseOffset( - clActual, klass, address, -1 /* down-cast */, true /* report errors */); - if (offset != -1) { // may fail if clActual not fully defined - address = (void*)((intptr_t)address + offset); - klass = clActual; - } + if (!isRef && (gPinnedTypes.empty() || gPinnedTypes.find(klass) == gPinnedTypes.end())) { + Cppyy::TCppType_t clActual = Cppyy::GetActualClass(klass, address); + + if (clActual) { + if (clActual != klass) { + intptr_t offset = Cppyy::GetBaseOffset( + clActual, klass, address, -1 /* down-cast */, true /* report errors */); + if (offset != -1) { // may fail if clActual not fully defined + address = (void*)((intptr_t)address + offset); + klass = clActual; } - new_flags |= CPPInstance::kIsActual; } - } else { - Cppyy::TCppType_t clActual = Cppyy::GetActualClass(klass, *(void**)address); - klass = clActual; + new_flags |= CPPInstance::kIsActual; } } @@ -954,7 +941,7 @@ PyObject* CPyCppyy::BindCppObject(Cppyy::TCppObject_t address, //---------------------------------------------------------------------------- PyObject* CPyCppyy::BindCppObjectArray( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims) + Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims) { // TODO: this function exists for symmetry; need to figure out if it's useful return TupleOfInstances_New(address, klass, dims); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h index cfa4360294d08..7a074f8db98ef 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h @@ -12,21 +12,21 @@ namespace CPyCppyy { // construct a Python shadow class for the named C++ class PyObject* GetScopeProxy(Cppyy::TCppScope_t); -PyObject* CreateScopeProxy(Cppyy::TCppScope_t, const unsigned flags = 0); PyObject* CreateScopeProxy(PyObject*, PyObject* args); PyObject* CreateScopeProxy( const std::string& scope_name, PyObject* parent = nullptr, const unsigned flags = 0); +PyObject* CreateScopeProxy(Cppyy::TCppScope_t scope, PyObject* parent = nullptr, const unsigned flags = 0); // C++ exceptions form a special case b/c they have to derive from BaseException PyObject* CreateExcScopeProxy(PyObject* pyscope, PyObject* pyname, PyObject* parent); // bind a C++ object into a Python proxy object (flags are CPPInstance::Default) PyObject* BindCppObjectNoCast(Cppyy::TCppObject_t object, - Cppyy::TCppType_t klass, const unsigned flags = 0); + Cppyy::TCppScope_t klass, const unsigned flags = 0); PyObject* BindCppObject(Cppyy::TCppObject_t object, - Cppyy::TCppType_t klass, const unsigned flags = 0); + Cppyy::TCppScope_t klass, const unsigned flags = 0); PyObject* BindCppObjectArray( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims); + Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims); } // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h b/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h index d2a072c5b389e..62ce3aa1af693 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h @@ -18,8 +18,6 @@ class PyCallable { public: virtual PyObject* GetSignature(bool show_formalargs = true) = 0; - virtual PyObject* GetSignatureNames() = 0; - virtual PyObject* GetSignatureTypes() = 0; virtual PyObject* GetPrototype(bool show_formalargs = true) = 0; virtual PyObject* GetTypeName() { return GetPrototype(false); } virtual PyObject* GetDocString() { return GetPrototype(); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx index f478ab33f98f4..2929683fe0667 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx @@ -28,17 +28,8 @@ CPyCppyy::PyException::PyException() PyGILState_STATE state = PyGILState_Ensure(); #endif -#if PY_VERSION_HEX >= 0x030c0000 - PyObject *pyvalue = PyErr_GetRaisedException(); - PyObject *pytype = pyvalue ? (PyObject *)Py_TYPE(pyvalue) : nullptr; - PyObject* traceback = pyvalue ? PyException_GetTraceback(pyvalue) : nullptr; -#else PyObject* pytype = nullptr, *pyvalue = nullptr, *pytrace = nullptr; PyErr_Fetch(&pytype, &pyvalue, &pytrace); - PyObject* traceback = pytrace; // to keep the original unchanged - Py_XINCREF(traceback); -#endif - if (pytype && pyvalue) { const char* tname = PyExceptionClass_Name(pytype); if (tname) { @@ -55,6 +46,9 @@ CPyCppyy::PyException::PyException() } } + PyObject* traceback = pytrace; // to keep the original unchanged + Py_XINCREF(traceback); + std::string locName; std::string locFile; int locLine = 0; @@ -94,11 +88,7 @@ CPyCppyy::PyException::PyException() Py_XDECREF(traceback); -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(pyvalue); -#else PyErr_Restore(pytype, pyvalue, pytrace); -#endif if (fMsg.empty()) fMsg = "python exception"; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx index 2ea01910ca82b..6a188f7da2369 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.cxx @@ -60,6 +60,7 @@ PyObject* CPyCppyy::PyStrings::gTemplate = nullptr; PyObject* CPyCppyy::PyStrings::gVectorAt = nullptr; PyObject* CPyCppyy::PyStrings::gInsert = nullptr; PyObject* CPyCppyy::PyStrings::gValueType = nullptr; +PyObject* CPyCppyy::PyStrings::gValueTypePtr = nullptr; PyObject* CPyCppyy::PyStrings::gValueSize = nullptr; PyObject* CPyCppyy::PyStrings::gCppReal = nullptr; @@ -147,6 +148,7 @@ bool CPyCppyy::CreatePyStrings() { CPPYY_INITIALIZE_STRING(gVectorAt, _vector__at); CPPYY_INITIALIZE_STRING(gInsert, insert); CPPYY_INITIALIZE_STRING(gValueType, value_type); + CPPYY_INITIALIZE_STRING(gValueTypePtr, _value_type); CPPYY_INITIALIZE_STRING(gValueSize, value_size); CPPYY_INITIALIZE_STRING(gCppReal, __cpp_real); @@ -221,6 +223,7 @@ PyObject* CPyCppyy::DestroyPyStrings() { Py_DECREF(PyStrings::gVectorAt); PyStrings::gVectorAt = nullptr; Py_DECREF(PyStrings::gInsert); PyStrings::gInsert = nullptr; Py_DECREF(PyStrings::gValueType); PyStrings::gValueType = nullptr; + Py_DECREF(PyStrings::gValueTypePtr);PyStrings::gValueTypePtr= nullptr; Py_DECREF(PyStrings::gValueSize); PyStrings::gValueSize = nullptr; Py_DECREF(PyStrings::gCppReal); PyStrings::gCppReal = nullptr; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h b/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h index 61d37b3ffc1c9..ed6a6775ee6e2 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/PyStrings.h @@ -63,6 +63,7 @@ namespace PyStrings { extern PyObject* gVectorAt; extern PyObject* gInsert; extern PyObject* gValueType; + extern PyObject* gValueTypePtr; extern PyObject* gValueSize; extern PyObject* gCppReal; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index cec92cbeff02d..84896443b64d7 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -5,6 +5,7 @@ #include "CPPInstance.h" #include "CPPFunction.h" #include "CPPOverload.h" +#include "Cppyy.h" #include "CustomPyTypes.h" #include "LowLevelViews.h" #include "ProxyWrappers.h" @@ -67,7 +68,7 @@ PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { inline bool IsTemplatedSTLClass(const std::string& name, const std::string& klass) { // Scan the name of the class and determine whether it is a template instantiation. auto pos = name.find(klass); - return (pos == 0 || pos == 5) && name.find("::", name.rfind(">")) == std::string::npos; + return pos == 5 && name.rfind("std::", 0, 5) == 0 && name.find("::", name.rfind(">")) == std::string::npos; } // to prevent compiler warnings about const char* -> char* @@ -187,7 +188,6 @@ PyObject* DeRefGetAttr(PyObject* self, PyObject* name) return nullptr; } - return smart_follow(self, name, PyStrings::gDeref); } @@ -441,11 +441,11 @@ static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter) if (fi && (PyTuple_CheckExact(fi) || PyList_CheckExact(fi))) { // use emplace_back to construct the vector entries one by one PyObject* eb_call = PyObject_GetAttrString(vecin, (char*)"emplace_back"); - PyObject* vtype = GetAttrDirect((PyObject*)Py_TYPE(vecin), PyStrings::gValueType); + PyObject* vtype = GetAttrDirect((PyObject*)Py_TYPE(vecin), PyStrings::gValueTypePtr); bool value_is_vector = false; - if (vtype && CPyCppyy_PyText_Check(vtype)) { + if (vtype && PyLong_Check(vtype)) { // if the value_type is a vector, then allow for initialization from sequences - if (std::string(CPyCppyy_PyText_AsString(vtype)).rfind("std::vector", 0) != std::string::npos) + if (Cppyy::GetTypeAsString(PyLong_AsVoidPtr(vtype)).rfind("std::vector", 0) != std::string::npos) value_is_vector = true; } else PyErr_Clear(); @@ -550,20 +550,9 @@ PyObject* VectorIAdd(PyObject* self, PyObject* args, PyObject* /* kwds */) if (PyObject_CheckBuffer(fi) && !(CPyCppyy_PyText_Check(fi) || PyBytes_Check(fi))) { PyObject* vend = PyObject_CallMethodNoArgs(self, PyStrings::gEnd); if (vend) { - // when __iadd__ is overriden, the operation does not end with - // calling the __iadd__ method, but also assigns the result to the - // lhs of the iadd. For example, performing vec += arr, Python - // first calls our override, and then does vec = vec.iadd(arr). - PyObject *it = PyObject_CallMethodObjArgs(self, PyStrings::gInsert, vend, fi, nullptr); + PyObject* result = PyObject_CallMethodObjArgs(self, PyStrings::gInsert, vend, fi, nullptr); Py_DECREF(vend); - - if (!it) - return nullptr; - - Py_DECREF(it); - // Assign the result of the __iadd__ override to the std::vector - Py_INCREF(self); - return self; + return result; } } } @@ -640,13 +629,6 @@ PyObject* VectorData(PyObject* self, PyObject*) } -// This function implements __array__, added to std::vector python proxies and causes -// a bug (see explanation at Utility::AddToClass(pyclass, "__array__"...) in CPyCppyy::Pythonize) -// The recursive nature of this function, passes each subarray (pydata) to the next call and only -// the final buffer is cast to a lowlevel view and resized (in VectorData), resulting in only the -// first 1D array to be returned. See https://github.com/root-project/root/issues/17729 -// It is temporarily removed to prevent errors due to -Wunused-function, since it is no longer added. -#if 0 //--------------------------------------------------------------------------- PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) { @@ -657,27 +639,22 @@ PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) Py_DECREF(pydata); return newarr; } -#endif + //----------------------------------------------------------------------------- static PyObject* vector_iter(PyObject* v) { vectoriterobject* vi = PyObject_GC_New(vectoriterobject, &VectorIter_Type); if (!vi) return nullptr; + Py_INCREF(v); vi->ii_container = v; // tell the iterator code to set a life line if this container is a temporary vi->vi_flags = vectoriterobject::kDefault; -#if PY_VERSION_HEX >= 0x030e0000 - if (PyUnstable_Object_IsUniqueReferencedTemporary(v) || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) -#else - if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) -#endif + if (v->ob_refcnt <= 2 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) vi->vi_flags = vectoriterobject::kNeedLifeLine; - Py_INCREF(v); - - PyObject* pyvalue_type = PyObject_GetAttr((PyObject*)Py_TYPE(v), PyStrings::gValueType); + PyObject* pyvalue_type = PyObject_GetAttr((PyObject*)Py_TYPE(v), PyStrings::gValueTypePtr); if (pyvalue_type) { PyObject* pyvalue_size = GetAttrDirect((PyObject*)Py_TYPE(v), PyStrings::gValueSize); if (pyvalue_size) { @@ -688,29 +665,32 @@ static PyObject* vector_iter(PyObject* v) { vi->vi_stride = 0; } - if (CPyCppyy_PyText_Check(pyvalue_type)) { - std::string value_type = CPyCppyy_PyText_AsString(pyvalue_type); - value_type = Cppyy::ResolveName(value_type); - vi->vi_klass = Cppyy::GetScope(value_type); + if (PyLong_Check(pyvalue_type)) { + Cppyy::TCppType_t value_type = PyLong_AsVoidPtr(pyvalue_type); + value_type = Cppyy::ResolveType(value_type); + vi->vi_klass = Cppyy::GetScopeFromType(value_type); if (!vi->vi_klass) { // look for a special case of pointer to a class type (which is a builtin, but it // is more useful to treat it polymorphically by allowing auto-downcasts) - const std::string& clean_type = TypeManip::clean_type(value_type, false, false); + const std::string& clean_type = TypeManip::clean_type(Cppyy::GetTypeAsString(value_type), false, false); Cppyy::TCppScope_t c = Cppyy::GetScope(clean_type); - if (c && TypeManip::compound(value_type) == "*") { + if (c && TypeManip::compound(Cppyy::GetTypeAsString(value_type)) == "*") { vi->vi_klass = c; vi->vi_flags = vectoriterobject::kIsPolymorphic; } } + if (Cppyy::IsPointerType(value_type)) + vi->vi_flags = vectoriterobject::kIsPolymorphic; if (vi->vi_klass) { vi->vi_converter = nullptr; if (!vi->vi_flags) { - if (value_type.back() != '*') // meaning, object stored by-value + value_type = Cppyy::ResolveType(value_type); + if (Cppyy::GetTypeAsString(value_type).back() != '*') // meaning, object stored by-value vi->vi_flags = vectoriterobject::kNeedLifeLine; } } else vi->vi_converter = CPyCppyy::CreateConverter(value_type); - if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOf(value_type); + if (!vi->vi_stride) vi->vi_stride = Cppyy::SizeOfType(value_type); } else if (CPPScope_Check(pyvalue_type)) { vi->vi_klass = ((CPPClass*)pyvalue_type)->fCppType; @@ -1243,15 +1223,15 @@ static int PyObject_Compare(PyObject* one, PyObject* other) { } #endif static inline -PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) { +PyObject* CPyCppyy_PyString_FromCppString(std::string* s, bool native=true) { if (native) - return PyBytes_FromStringAndSize(s.data(), s.size()); - return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size()); + return PyBytes_FromStringAndSize(s->data(), s->size()); + return CPyCppyy_PyText_FromStringAndSize(s->data(), s->size()); } static inline -PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) { - PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size()); +PyObject* CPyCppyy_PyString_FromCppString(std::wstring* s, bool native=true) { + PyObject* pyobj = PyUnicode_FromWideChar(s->data(), s->size()); if (pyobj && native) { PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict"); Py_DECREF(pyobj); @@ -1266,7 +1246,7 @@ PyObject* name##StringGetData(PyObject* self, bool native=true) \ { \ if (CPyCppyy::CPPInstance_Check(self)) { \ type* obj = ((type*)((CPPInstance*)self)->GetObject()); \ - if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \ + if (obj) return CPyCppyy_PyString_FromCppString(obj, native); \ } \ PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \ return nullptr; \ @@ -1343,7 +1323,6 @@ PyObject* name##StringCompare(PyObject* self, PyObject* obj) \ CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string, STL) CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::wstring, STLW) -CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string_view, STLView) static inline std::string* GetSTLString(CPPInstance* self) { if (!CPPInstance_Check(self)) { @@ -1469,7 +1448,6 @@ PyObject* STLStringGetAttr(CPPInstance* self, PyObject* attr_name) } -#if 0 PyObject* UTF8Repr(PyObject* self) { // force C++ string types conversion to Python str per Python __repr__ requirements @@ -1491,7 +1469,6 @@ PyObject* UTF8Str(PyObject* self) Py_DECREF(res); return str_res; } -#endif Py_hash_t STLStringHash(PyObject* self) { @@ -1725,8 +1702,10 @@ bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vectortp_iter) { if (HasAttrDirect(pyclass, PyStrings::gBegin) && HasAttrDirect(pyclass, PyStrings::gEnd)) { // obtain the name of the return type - const auto& v = Cppyy::GetMethodIndicesFromName(klass->fCppType, "begin"); - if (!v.empty()) { + const auto& methods = Cppyy::GetMethodsFromName(klass->fCppType, "begin"); + if (!methods.empty()) { // check return type; if not explicitly an iterator, add it to the "known" return // types to add the "next" method on use - Cppyy::TCppMethod_t meth = Cppyy::GetMethod(klass->fCppType, v[0]); - const std::string& resname = Cppyy::GetMethodResultType(meth); + Cppyy::TCppMethod_t meth = methods[0]; + const std::string& resname = Cppyy::GetMethodReturnTypeAsString(meth); bool isIterator = gIteratorTypes.find(resname) != gIteratorTypes.end(); if (!isIterator && Cppyy::GetScope(resname)) { if (resname.find("iterator") == std::string::npos) @@ -1814,7 +1793,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) // comparisons to None; if no operator is available, a hook is installed for lazy // lookups in the global and/or class namespace if (HasAttrDirect(pyclass, PyStrings::gEq, true) && \ - Cppyy::GetMethodIndicesFromName(klass->fCppType, "__eq__").empty()) { + Cppyy::GetMethodsFromName(klass->fCppType, "__eq__").empty()) { PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gEq); if (!klass->fOperators) klass->fOperators = new Utility::PyOperators(); klass->fOperators->fEq = cppol; @@ -1831,7 +1810,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) } if (HasAttrDirect(pyclass, PyStrings::gNe, true) && \ - Cppyy::GetMethodIndicesFromName(klass->fCppType, "__ne__").empty()) { + Cppyy::GetMethodsFromName(klass->fCppType, "__ne__").empty()) { PyObject* cppol = PyObject_GetAttr(pyclass, PyStrings::gNe); if (!klass->fOperators) klass->fOperators = new Utility::PyOperators(); klass->fOperators->fNe = cppol; @@ -1846,7 +1825,6 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) PyObject_SetAttr(pyclass, PyStrings::gNe, top_ne); } -#if 0 if (HasAttrDirect(pyclass, PyStrings::gRepr, true)) { // guarantee that the result of __repr__ is a Python string Utility::AddToClass(pyclass, "__cpp_repr", "__repr__"); @@ -1858,14 +1836,13 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) Utility::AddToClass(pyclass, "__cpp_str", "__str__"); Utility::AddToClass(pyclass, "__str__", (PyCFunction)UTF8Str, METH_NOARGS); } -#endif - if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0 && - name.compare(0, 6, "tuple<", 6) != 0) { + if (Cppyy::IsAggregate(((CPPClass*)pyclass)->fCppType) && name.compare(0, 5, "std::", 5) != 0) { // create a pseudo-constructor to allow initializer-style object creation Cppyy::TCppType_t kls = ((CPPClass*)pyclass)->fCppType; - Cppyy::TCppIndex_t ndata = Cppyy::GetNumDatamembers(kls); - if (ndata) { + std::vector datamems; + Cppyy::GetDatamembers(kls, datamems); + if (!datamems.empty()) { std::string rname = name; TypeManip::cppscope_to_legalname(rname); @@ -1874,23 +1851,30 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) << "void init_" << rname << "(" << name << "** self"; bool codegen_ok = true; std::vector arg_types, arg_names, arg_defaults; + const int ndata = datamems.size(); arg_types.reserve(ndata); arg_names.reserve(ndata); arg_defaults.reserve(ndata); - for (Cppyy::TCppIndex_t i = 0; i < ndata; ++i) { - if (Cppyy::IsStaticData(kls, i) || !Cppyy::IsPublicData(kls, i)) + for (auto data : datamems) { + if (Cppyy::IsStaticDatamember(data) || !Cppyy::IsPublicData(data)) continue; - const std::string& txt = Cppyy::GetDatamemberType(kls, i); - const std::string& res = Cppyy::IsEnum(txt) ? txt : Cppyy::ResolveName(txt); + Cppyy::TCppType_t datammember_type = + Cppyy::GetDatamemberType(data); + const std::string &res = + Cppyy::IsEnumType(datammember_type) + ? Cppyy::GetScopedFinalName( + Cppyy::GetScopeFromType(datammember_type)) + : Cppyy::GetTypeAsString( + Cppyy::ResolveType(datammember_type)); const std::string& cpd = TypeManip::compound(res); std::string res_clean = TypeManip::clean_type(res, false, true); if (res_clean == "internal_enum_type_t") - res_clean = txt; // restore (properly scoped name) + res_clean = res; // restore (properly scoped name) if (res.rfind(']') == std::string::npos && res.rfind(')') == std::string::npos) { if (!cpd.empty()) arg_types.push_back(res_clean+cpd); else arg_types.push_back("const "+res_clean+"&"); - arg_names.push_back(Cppyy::GetDatamemberName(kls, i)); + arg_names.push_back(Cppyy::GetFinalName(data)); if ((!cpd.empty() && cpd.back() == '*') || Cppyy::IsBuiltin(res_clean)) arg_defaults.push_back("0"); else { @@ -1918,10 +1902,10 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) if (Cppyy::Compile(initdef.str(), true /* silent */)) { Cppyy::TCppScope_t cis = Cppyy::GetScope("__cppyy_internal"); - const auto& mix = Cppyy::GetMethodIndicesFromName(cis, "init_"+rname); - if (mix.size()) { + const auto& methods = Cppyy::GetMethodsFromName(cis, "init_" + rname); + if (methods.size()) { if (!Utility::AddToClass(pyclass, "__init__", - new CPPFunction(cis, Cppyy::GetMethod(cis, mix[0])))) + new CPPFunction(cis, methods[0]))) PyErr_Clear(); } } @@ -1961,18 +1945,10 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) // data with size Utility::AddToClass(pyclass, "__real_data", "data"); - PyErr_Clear(); // AddToClass might have failed for data Utility::AddToClass(pyclass, "data", (PyCFunction)VectorData); - // The addition of the __array__ utility to std::vector Python proxies causes a - // bug where the resulting array is a single dimension, causing loss of data when - // converting to numpy arrays, for >1dim vectors. Since this C++ pythonization - // was added with the upgrade in 6.32, and is only defined and used recursively, - // the safe option is to disable this function and no longer add it. -#if 0 // numpy array conversion Utility::AddToClass(pyclass, "__array__", (PyCFunction)VectorArray, METH_VARARGS | METH_KEYWORDS /* unused */); -#endif // checked getitem if (HasAttrDirect(pyclass, PyStrings::gLen)) { @@ -1987,14 +1963,18 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) Utility::AddToClass(pyclass, "__iadd__", (PyCFunction)VectorIAdd, METH_VARARGS | METH_KEYWORDS); // helpers for iteration - const std::string& vtype = Cppyy::ResolveName(name+"::value_type"); - if (vtype.rfind("value_type") == std::string::npos) { // actually resolved? - PyObject* pyvalue_type = CPyCppyy_PyText_FromString(vtype.c_str()); + Cppyy::TCppType_t value_type = Cppyy::GetTypeFromScope(Cppyy::GetNamed("value_type", scope)); + Cppyy::TCppType_t vtype = Cppyy::ResolveType(value_type); + if (vtype) { // actually resolved? + PyObject* pyvalue_type = PyLong_FromVoidPtr(vtype); + PyObject_SetAttr(pyclass, PyStrings::gValueTypePtr, pyvalue_type); + Py_DECREF(pyvalue_type); + pyvalue_type = PyUnicode_FromString(Cppyy::GetTypeAsString(vtype).c_str()); PyObject_SetAttr(pyclass, PyStrings::gValueType, pyvalue_type); Py_DECREF(pyvalue_type); } - size_t typesz = Cppyy::SizeOf(name+"::value_type"); + size_t typesz = Cppyy::SizeOfType(vtype); if (typesz) { PyObject* pyvalue_size = PyLong_FromSsize_t(typesz); PyObject_SetAttr(pyclass, PyStrings::gValueSize, pyvalue_size); @@ -2048,7 +2028,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) Utility::AddToClass(pyclass, "__iter__", (PyCFunction)PyObject_SelfIter, METH_NOARGS); } - else if (name == "string" || name == "std::string") { // TODO: ask backend as well + else if (name == "std::basic_string") { // TODO: ask backend as well Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLStringRepr, METH_NOARGS); Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLStringStr, METH_NOARGS); Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLStringBytes, METH_NOARGS); @@ -2072,15 +2052,9 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) ((PyTypeObject*)pyclass)->tp_hash = (hashfunc)STLStringHash; } - else if (name == "basic_string_view >" || name == "std::basic_string_view") { + else if (name == "std::basic_string_view") { Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, METH_VARARGS | METH_KEYWORDS); - Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLViewStringBytes, METH_NOARGS); - Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLViewStringCompare, METH_O); - Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLViewStringIsEqual, METH_O); - Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLViewStringIsNotEqual, METH_O); - Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLViewStringRepr, METH_NOARGS); - Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, METH_NOARGS); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, METH_VARARGS | METH_KEYWORDS); } // The first condition was already present in upstream CPyCppyy. The other two diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h index cbdec834a24cd..673f5740a73ff 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.h @@ -8,7 +8,7 @@ namespace CPyCppyy { // make the named C++ class more python-like -bool Pythonize(PyObject* pyclass, const std::string& name); +bool Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope); } // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h b/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h index 453d8eb6ee388..14742b894099d 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h @@ -40,6 +40,10 @@ using CppyyExceptionContext_t = CppyyLegacy::ExceptionContext_t; using CppyyExceptionContext_t = ExceptionContext_t; #endif +// FIXME: This is a dummy, replace with cling equivalent of gException +static CppyyExceptionContext_t DummyException; +static CppyyExceptionContext_t *gException = &DummyException; + #ifdef NEED_SIGJMP # define CLING_EXCEPTION_SETJMP(buf) sigsetjmp(buf,1) #else @@ -71,11 +75,6 @@ using CppyyExceptionContext_t = ExceptionContext_t; gException = R__old; \ } -// extern, defined in ROOT Core -#ifdef _MSC_VER -extern __declspec(dllimport) CppyyExceptionContext_t *gException; -#else -extern CppyyExceptionContext_t *gException; -#endif +CPYCPPYY_IMPORT CppyyExceptionContext_t *gException; #endif diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx index 12982ddb7e2db..5fbb01d392eaa 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx @@ -76,11 +76,9 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname, std::string proto = ""; #if PY_VERSION_HEX >= 0x03080000 -// adjust arguments for self if this is a rebound (global) function +// adjust arguments for self if this is a rebound global function bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace); - if (!isNS && CPyCppyy_PyArgs_GET_SIZE(args, nargsf) && \ - (!fSelf || - (fSelf == Py_None && !Cppyy::IsStaticTemplate(((CPPScope*)fTI->fPyClass)->fCppType, fname)))) { + if (!isNS && !fSelf && CPyCppyy_PyArgs_GET_SIZE(args, nargsf)) { args += 1; nargsf -= 1; } @@ -142,12 +140,6 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname, } else PyErr_Clear(); - if (!bArgSet && (Py_TYPE(itemi) == &TemplateProxy_Type)) { - TemplateProxy *tp = (TemplateProxy*)itemi; - PyObject *tmpl_name = CPyCppyy_PyText_FromFormat("decltype(%s%s)", tp->fTI->fCppName.c_str(), tp->fTemplateArgs ? CPyCppyy_PyText_AsString(tp->fTemplateArgs) : ""); - PyTuple_SET_ITEM(tpArgs, i, tmpl_name); - bArgSet = true; - } if (!bArgSet) { // normal case (may well fail) PyErr_Clear(); @@ -432,7 +424,8 @@ static int tpp_doc_set(TemplateProxy* pytmpl, PyObject *val, void *) //= CPyCppyy template proxy callable behavior ================================ #define TPPCALL_RETURN \ -{ errors.clear(); \ +{ if (!errors.empty()) \ + std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear);\ return result; } static inline std::string targs2str(TemplateProxy* pytmpl) @@ -639,7 +632,7 @@ static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) PyObject* topmsg = CPyCppyy_PyText_FromFormat( "Could not find \"%s\" (set cppyy.set_debug() for C++ errors):", CPyCppyy_PyText_AsString(pyfullname)); Py_DECREF(pyfullname); - Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); + Utility::SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */); return nullptr; } @@ -680,7 +673,7 @@ static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) // error reporting is fraud, given the numerous steps taken, but more details seems better if (!errors.empty()) { PyObject* topmsg = CPyCppyy_PyText_FromString("Template method resolution failed:"); - Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); + Utility::SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */); } else { PyErr_Format(PyExc_TypeError, "cannot resolve method template call for \'%s\'", pytmpl->fTI->fCppName.c_str()); @@ -748,21 +741,6 @@ static int tpp_setuseffi(CPPOverload*, PyObject*, void*) return 0; // dummy (__useffi__ unused) } -//----------------------------------------------------------------------------- -static PyObject* tpp_gettemplateargs(TemplateProxy* self, void*) { - if (!self->fTemplateArgs) { - Py_RETURN_NONE; - } - - Py_INCREF(self->fTemplateArgs); - return self->fTemplateArgs; -} - -//----------------------------------------------------------------------------- -static int tpp_settemplateargs(TemplateProxy*, PyObject*, void*) { - PyErr_SetString(PyExc_AttributeError, "__template_args__ is read-only"); - return -1; -} //---------------------------------------------------------------------------- static PyMappingMethods tpp_as_mapping = { @@ -773,9 +751,7 @@ static PyGetSetDef tpp_getset[] = { {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr}, {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi, (char*)"unused", nullptr}, - {(char*)"__template_args__", (getter)tpp_gettemplateargs, (setter)tpp_settemplateargs, - (char*)"the template arguments for this method", nullptr}, - {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr} }; @@ -806,7 +782,6 @@ static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) { // Select and call a specific C++ overload, based on its signature. const char* sigarg = nullptr; - const char* tmplarg = nullptr; PyObject* sigarg_tuple = nullptr; int want_const = -1; @@ -837,11 +812,6 @@ static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; cppmeth = Cppyy::GetMethodTemplate( scope, pytmpl->fTI->fCppName, proto.substr(1, proto.size()-2)); - } else if (PyArg_ParseTuple(args, const_cast("ss:__overload__"), &sigarg, &tmplarg)) { - scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType; - std::string full_name = std::string(pytmpl->fTI->fCppName) + "<" + tmplarg + ">"; - - cppmeth = Cppyy::GetMethodTemplate(scope, full_name, sigarg); } else if (PyArg_ParseTuple(args, const_cast("O|i:__overload__"), &sigarg_tuple, &want_const)) { PyErr_Clear(); want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const; @@ -880,11 +850,17 @@ static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) } // else attempt instantiation + PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + if (!cppmeth) { + PyErr_Restore(pytype, pyvalue, pytrace); return nullptr; } - PyErr_Clear(); + Py_XDECREF(pytype); + Py_XDECREF(pyvalue); + Py_XDECREF(pytrace); // TODO: the next step should be consolidated with Instantiate() PyCallable* meth = nullptr; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx index 794684e245a94..6ef09a240d8d0 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx @@ -128,7 +128,7 @@ PyTypeObject InstanceArrayIter_Type = { //= support for C-style arrays of objects ==================================== PyObject* TupleOfInstances_New( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims) + Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims) { // recursively set up tuples of instances on all dimensions if (dims.ndim() == UNKNOWN_SIZE || dims[0] == UNKNOWN_SIZE /* unknown shape or size */) { diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h index de95f29b2f8ba..d903872a3b7df 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.h @@ -30,7 +30,7 @@ inline bool TupleOfInstances_CheckExact(T* object) } PyObject* TupleOfInstances_New( - Cppyy::TCppObject_t address, Cppyy::TCppType_t klass, cdims_t dims); + Cppyy::TCppObject_t address, Cppyy::TCppScope_t klass, cdims_t dims); } // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx index 98bc183d25d55..bfead0bfc9575 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TypeManip.cxx @@ -155,11 +155,14 @@ std::string CPyCppyy::TypeManip::template_base(const std::string& cppname) //---------------------------------------------------------------------------- std::string CPyCppyy::TypeManip::compound(const std::string& name) { +// FIXME: temporary fix for translation unit decl being passed in +// CreateExecutor from InitExecutor_ (run test10_object_identity), remove later + if (name.empty()) return ""; // Break down the compound of a fully qualified type name. std::string cleanName = remove_const(name); auto idx = find_qualifier_index(cleanName); - const std::string& cpd = cleanName.substr(idx, std::string::npos); + std::string cpd = cleanName.substr(idx, std::string::npos); // for easy identification of fixed size arrays if (!cpd.empty() && cpd.back() == ']') { @@ -168,9 +171,12 @@ std::string CPyCppyy::TypeManip::compound(const std::string& name) std::ostringstream scpd; scpd << cpd.substr(0, cpd.find('[')) << "[]"; - return scpd.str(); + cpd = scpd.str(); } + // XXX: remove this hack + if (!cpd.empty() && cpd[0] == ' ') return cpd.substr(1, cpd.length() - 1); + return cpd; } @@ -190,7 +196,7 @@ void CPyCppyy::TypeManip::cppscope_to_legalname(std::string& cppscope) { // Change characters illegal in a variable name into '_' to form a legal name. for (char& c : cppscope) { - for (char needle : {':', '>', '<', ' ', ',', '&', '=', '*'}) + for (char needle : {':', '>', '<', ' ', ',', '&', '=', '*', '-'}) if (c == needle) c = '_'; } } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx index 7c3e209f31cd8..31e79e8694549 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx @@ -201,7 +201,10 @@ bool CPyCppyy::Utility::AddToClass( PyObject* func = PyCFunction_New(pdef, nullptr); PyObject* name = CPyCppyy_PyText_InternFromString(pdef->ml_name); PyObject* method = CustomInstanceMethod_New(func, nullptr, pyclass); + PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); bool isOk = PyType_Type.tp_setattro(pyclass, name, method) == 0; + PyErr_Restore(pytype, pyvalue, pytrace); Py_DECREF(method); Py_DECREF(name); Py_DECREF(func); @@ -266,14 +269,11 @@ CPyCppyy::PyCallable* BuildOperator(const std::string& lcname, const std::string const char* op, Cppyy::TCppScope_t scope, bool reverse=false) { // Helper to find a function with matching signature in 'funcs'. - std::string opname = "operator"; - opname += op; - Cppyy::TCppIndex_t idx = Cppyy::GetGlobalOperator(scope, lcname, rcname, opname); - if (idx == (Cppyy::TCppIndex_t)-1) + Cppyy::TCppMethod_t meth = Cppyy::GetGlobalOperator(scope, lcname, rcname, op); + if (!meth) return nullptr; - Cppyy::TCppMethod_t meth = Cppyy::GetMethod(scope, idx); if (!reverse) return new CPyCppyy::CPPFunction(scope, meth); return new CPyCppyy::CPPReverseBinary(scope, meth); @@ -332,15 +332,17 @@ CPyCppyy::PyCallable* CPyCppyy::Utility::FindBinaryOperator( if (!scope) { // TODO: the following should remain sync with what clingwrapper does in its // type remapper; there must be a better way? - if (lcname == "str" || lcname == "unicode" || lcname == "complex") + if (lcname == "str" || lcname == "unicode" || lcname == "complex" || lcname.find("std::") == 0) scope = Cppyy::GetScope("std"); - else scope = Cppyy::GetScope(TypeManip::extract_namespace(lcname)); } if (scope) pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); + if (!pyfunc) + if ((scope = Cppyy::GetScope(TypeManip::extract_namespace(lcname)))) + pyfunc = BuildOperator(lcname, rcname, op, scope, reverse); - if (!pyfunc && scope != Cppyy::gGlobalScope) // search in global scope anyway - pyfunc = BuildOperator(lcname, rcname, op, Cppyy::gGlobalScope, reverse); + if (!pyfunc && scope != Cppyy::GetGlobalScope())// search in global scope anyway + pyfunc = BuildOperator(lcname, rcname, op, Cppyy::GetGlobalScope(), reverse); if (!pyfunc) { // For GNU on clang, search the internal __gnu_cxx namespace for binary operators (is @@ -355,7 +357,7 @@ CPyCppyy::PyCallable* CPyCppyy::Utility::FindBinaryOperator( if (!pyfunc) { // Same for clang (on Mac only?). TODO: find proper pre-processor magic to only use those // specific namespaces that are actually around; although to be sure, this isn't expensive. - static Cppyy::TCppScope_t std__1 = Cppyy::GetScope("std::__1"); + static Cppyy::TCppScope_t std__1 = Cppyy::GetFullScope("std::__1"); if (std__1 #ifdef __APPLE__ @@ -494,7 +496,8 @@ static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, } if (CPPScope_Check(tn)) { - tmpl_name.append(full_scope(Cppyy::GetScopedFinalName(((CPPClass*)tn)->fCppType))); + auto cpp_type = Cppyy::GetScopedFinalName(((CPPClass*)tn)->fCppType); + tmpl_name.append(full_scope(cpp_type)); if (arg) { // try to specialize the type match for the given object CPPInstance* pyobj = (CPPInstance*)arg; @@ -558,9 +561,9 @@ static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, PyErr_Clear(); // ctypes function pointer - PyObject* argtypes = nullptr; - PyObject* ret = nullptr; - if ((argtypes = PyObject_GetAttrString(arg, "argtypes")) && (ret = PyObject_GetAttrString(arg, "restype"))) { + PyObject* argtypes = PyObject_GetAttrString(arg, "argtypes"); + PyObject* ret = PyObject_GetAttrString(arg, "restype"); + if (argtypes && ret) { std::ostringstream tpn; PyObject* pytc = PyObject_GetAttr(ret, PyStrings::gCTypesType); tpn << CT2CppNameS(pytc, false) @@ -675,6 +678,250 @@ std::string CPyCppyy::Utility::ConstructTemplateArgs( } //---------------------------------------------------------------------------- +static bool AddTypeName(std::vector& types, PyObject* tn, + PyObject* arg, CPyCppyy::Utility::ArgPreference pref, int* pcnt = nullptr) +{ +// Determine the appropriate C++ type for a given Python type; this is a helper because +// it can recurse if the type is list or tuple and needs matching on std::vector. + using namespace CPyCppyy; + using namespace CPyCppyy::Utility; + + if (tn == (PyObject*)&PyInt_Type) { + if (arg) { +#if PY_VERSION_HEX < 0x03000000 + long l = PyInt_AS_LONG(arg); + types.push_back(Cppyy::GetType((l < INT_MIN || INT_MAX < l) ? "long" : "int")); +#else + PY_LONG_LONG ll = PyLong_AsLongLong(arg); + if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); + if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + types.push_back(Cppyy::GetType("int")); // still out of range, will fail later + } else + types.push_back(Cppyy::GetType("unsigned long long")); // since already failed long long + } else + types.push_back(Cppyy::GetType((ll < INT_MIN || INT_MAX < ll) ? \ + ((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long") : "int")); +#endif + } else { + types.push_back(Cppyy::GetType("int")); + } + + return true; + } + +#if PY_VERSION_HEX < 0x03000000 + if (tn == (PyObject*)&PyLong_Type) { + if (arg) { + PY_LONG_LONG ll = PyLong_AsLongLong(arg); + if (ll == (PY_LONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + PY_ULONG_LONG ull = PyLong_AsUnsignedLongLong(arg); + if (ull == (PY_ULONG_LONG)-1 && PyErr_Occurred()) { + PyErr_Clear(); + types.push_back(Cppyy::GetType("long")); // still out of range, will fail later + } else + types.push_back(Cppyy::GetType("unsigned long long")); // since already failed long long + } else + types.push_back(Cppyy::GetType((ll < LONG_MIN || LONG_MAX < ll) ? "long long" : "long")); + } else + types.push_back(Cppyy::GetType("long")); + + return true; + } +#endif + + if (tn == (PyObject*)&PyFloat_Type) { + // special case for floats (Python-speak for double) if from argument (only) + types.push_back(Cppyy::GetType(arg ? "double" : "float")); + return true; + } + +#if PY_VERSION_HEX < 0x03000000 + if (tn == (PyObject*)&PyString_Type) { +#else + if (tn == (PyObject*)&PyUnicode_Type) { +#endif + types.push_back(Cppyy::GetType("std::string", /* enable_slow_lookup */ true)); + return true; + } + + if (tn == (PyObject*)&PyList_Type || tn == (PyObject*)&PyTuple_Type) { + if (arg && PySequence_Size(arg)) { + std::string subtype{"std::initializer_list<"}; + PyObject* item = PySequence_GetItem(arg, 0); + ArgPreference subpref = pref == kValue ? kValue : kPointer; + if (AddTypeName(subtype, (PyObject*)Py_TYPE(item), item, subpref)) { + subtype.append(">"); + types.push_back(Cppyy::GetType(subtype)); + } + Py_DECREF(item); + } + + return true; + } + + if (CPPScope_Check(tn)) { + auto cpp_type = Cppyy::GetTypeFromScope(((CPPClass*)tn)->fCppType); + if (arg) { + // try to specialize the type match for the given object + CPPInstance* pyobj = (CPPInstance*)arg; + if (CPPInstance_Check(pyobj)) { + if (pyobj->fFlags & CPPInstance::kIsRValue) + cpp_type = + Cppyy::GetReferencedType(cpp_type, /*rvalue=*/true); + else { + if (pcnt) *pcnt += 1; + if ((pyobj->fFlags & CPPInstance::kIsReference) || pref == kPointer) + cpp_type = Cppyy::GetPointerType(cpp_type); + else if (pref != kValue) + cpp_type = + Cppyy::GetReferencedType(cpp_type, /*rvalue=*/false); + } + } + } + types.push_back(cpp_type); + return true; + } + + if (tn == (PyObject*)&CPPOverload_Type) { + PyObject* tpName = arg ? \ + PyObject_GetAttr(arg, PyStrings::gCppName) : \ + CPyCppyy_PyText_FromString("void* (*)(...)"); + types.push_back(Cppyy::GetType(CPyCppyy_PyText_AsString(tpName), /* enable_slow_lookup */ true)); + Py_DECREF(tpName); + + return true; + } + + if (arg && PyCallable_Check(arg)) { + PyObject* annot = PyObject_GetAttr(arg, PyStrings::gAnnotations); + if (annot) { + if (PyDict_Check(annot) && 1 < PyDict_Size(annot)) { + PyObject* ret = PyDict_GetItemString(annot, "return"); + if (ret) { + // dict is ordered, with the last value being the return type + std::ostringstream tpn; + tpn << (CPPScope_Check(ret) ? ClassName(ret) : CPyCppyy_PyText_AsString(ret)) + << " (*)("; + + PyObject* values = PyDict_Values(annot); + for (Py_ssize_t i = 0; i < (PyList_GET_SIZE(values)-1); ++i) { + if (i) tpn << ", "; + PyObject* item = PyList_GET_ITEM(values, i); + tpn << (CPPScope_Check(item) ? ClassName(item) : CPyCppyy_PyText_AsString(item)); + } + Py_DECREF(values); + + tpn << ')'; + // tmpl_name.append(tpn.str()); + // FIXME: find a way to add it to types + throw std::runtime_error( + "This path is not yet implemented (AddTypeName) \n"); + + return true; + + } else + PyErr_Clear(); + } + Py_DECREF(annot); + } else + PyErr_Clear(); + + PyObject* tpName = PyObject_GetAttr(arg, PyStrings::gCppName); + if (tpName) { + types.push_back(Cppyy::GetType(CPyCppyy_PyText_AsString(tpName), /* enable_slow_lookup */ true)); + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + for (auto nn : {PyStrings::gCppName, PyStrings::gName}) { + PyObject* tpName = PyObject_GetAttr(tn, nn); + if (tpName) { + Cppyy::TCppType_t type = Cppyy::GetType(CPyCppyy_PyText_AsString(tpName), /* enable_slow_lookup */ true); + if (Cppyy::IsEnumType(type)) { + PyObject *value_int = PyNumber_Index(tn); + if (!value_int) { + types.push_back(type); + PyErr_Clear(); + } else { + PyObject* pystr = PyObject_Str(tn); + std::string num = CPyCppyy_PyText_AsString(pystr); + types.push_back({type, strdup(num.c_str())}); + Py_DECREF(pystr); + Py_DECREF(value_int); + } + } else { + types.push_back(type); + } + Py_DECREF(tpName); + return true; + } + PyErr_Clear(); + } + + if (PyInt_Check(tn) || PyLong_Check(tn) || PyFloat_Check(tn)) { + // last ditch attempt, works for things like int values; since this is a + // source of errors otherwise, it is limited to specific types and not + // generally used (str(obj) can print anything ...) + PyObject* pystr = PyObject_Str(tn); + std::string num = CPyCppyy_PyText_AsString(pystr); + if (num == "True") + num = "1"; + else if (num == "False") + num = "0"; + types.push_back({Cppyy::GetType("int"), strdup(num.c_str())}); + Py_DECREF(pystr); + return true; + } + + return false; +} + +std::vector CPyCppyy::Utility::GetTemplateArgsTypes( + PyObject* /*scope*/, PyObject* tpArgs, PyObject* args, ArgPreference pref, int argoff, int* pcnt) +{ +// Helper to construct the "" part of a templated name (either +// for a class or method lookup + bool justOne = !PyTuple_CheckExact(tpArgs); + +// Note: directly appending to string is a lot faster than stringstream + std::vector types; + types.reserve(8); + + if (pcnt) *pcnt = 0; // count number of times 'pref' is used + + Py_ssize_t nArgs = justOne ? 1 : PyTuple_GET_SIZE(tpArgs); + for (int i = argoff; i < nArgs; ++i) { + // add type as string to name + PyObject* tn = justOne ? tpArgs : PyTuple_GET_ITEM(tpArgs, i); + if (CPyCppyy_PyText_Check(tn)) { + const char * tn_string = CPyCppyy_PyText_AsString(tn); + + if (Cppyy::AppendTypesSlow(tn_string, types)) { + PyErr_Format(PyExc_TypeError, + "Cannot find Templated Arg: %s", tn_string); + return {}; + } + + // some commmon numeric types (separated out for performance: checking for + // __cpp_name__ and/or __name__ is rather expensive) + } else { + if (!AddTypeName(types, tn, (args ? PyTuple_GET_ITEM(args, i) : nullptr), pref, pcnt)) { + PyErr_SetString(PyExc_TypeError, + "could not construct C++ name from provided template argument."); + return {}; + } + } + } + + return types; +} + std::string CPyCppyy::Utility::CT2CppNameS(PyObject* pytc, bool allow_voidp) { // helper to convert ctypes' `_type_` info to the equivalent C++ name @@ -718,7 +965,7 @@ void CPyCppyy::Utility::ConstructCallbackPreamble(const std::string& retType, int nArgs = (int)argtypes.size(); // return value and argument type converters - bool isVoid = retType == "void"; + bool isVoid = retType.find("void") == 0; // might contain trailing space if (!isVoid) code << " CPYCPPYY_STATIC std::unique_ptr> " "retconv{CPyCppyy::CreateConverter(\"" @@ -780,7 +1027,7 @@ void CPyCppyy::Utility::ConstructCallbackPreamble(const std::string& retType, void CPyCppyy::Utility::ConstructCallbackReturn(const std::string& retType, int nArgs, std::ostringstream& code) { // Generate code for return value conversion and error handling. - bool isVoid = retType == "void"; + bool isVoid = retType.find("void") == 0; // might contain trailing space bool isPtr = Cppyy::ResolveName(retType).back() == '*'; if (nArgs) @@ -935,7 +1182,7 @@ Py_ssize_t CPyCppyy::Utility::GetBuffer(PyObject* pyobject, char tc, int size, v if (PyObject_CheckBuffer(pyobject)) { if (PySequence_Check(pyobject) && !PySequence_Size(pyobject)) return 0; // PyObject_GetBuffer() crashes on some platforms for some zero-sized seqeunces - PyErr_Clear(); + Py_buffer bufinfo; memset(&bufinfo, 0, sizeof(Py_buffer)); if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { @@ -1021,14 +1268,15 @@ Py_ssize_t CPyCppyy::Utility::GetBuffer(PyObject* pyobject, char tc, int size, v buf = 0; // not compatible // clarify error message - auto error = FetchPyError(); + PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); PyObject* pyvalue2 = CPyCppyy_PyText_FromFormat( (char*)"%s and given element size (%ld) do not match needed (%d)", - CPyCppyy_PyText_AsString(error.fValue.get()), + CPyCppyy_PyText_AsString(pyvalue), seqmeths->sq_length ? (long)(buflen/(*(seqmeths->sq_length))(pyobject)) : (long)buflen, size); - error.fValue.reset(pyvalue2); - RestorePyError(error); + Py_DECREF(pyvalue); + PyErr_Restore(pytype, pyvalue2, pytrace); } } @@ -1056,13 +1304,7 @@ std::string CPyCppyy::Utility::MapOperatorName(const std::string& name, bool bTa if (gOpRemove.find(op) != gOpRemove.end()) return ""; - // check first if none, to prevent spurious deserializing downstream TC2POperatorMapping_t::iterator pop = gC2POperatorMapping.find(op); - if (pop == gC2POperatorMapping.end() && gOpSkip.find(op) == gOpSkip.end()) { - op = Cppyy::ResolveName(op); - pop = gC2POperatorMapping.find(op); - } - // map C++ operator to python equivalent, or made up name if no equivalent exists if (pop != gC2POperatorMapping.end()) { return pop->second; @@ -1185,50 +1427,20 @@ PyObject* CPyCppyy::Utility::PyErr_Occurred_WithGIL() } -//---------------------------------------------------------------------------- -CPyCppyy::Utility::PyError_t CPyCppyy::Utility::FetchPyError() -{ - // create a PyError_t RAII object that will capture and store the exception data - CPyCppyy::Utility::PyError_t error{}; -#if PY_VERSION_HEX >= 0x030c0000 - error.fValue.reset(PyErr_GetRaisedException()); -#else - PyObject *pytype = nullptr; - PyObject *pyvalue = nullptr; - PyObject *pytrace = nullptr; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - error.fType.reset(pytype); - error.fValue.reset(pyvalue); - error.fTrace.reset(pytrace); -#endif - return error; -} - - -//---------------------------------------------------------------------------- -void CPyCppyy::Utility::RestorePyError(CPyCppyy::Utility::PyError_t &error) -{ -#if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(error.fValue.release()); -#else - PyErr_Restore(error.fType.release(), error.fValue.release(), error.fTrace.release()); -#endif -} - - //---------------------------------------------------------------------------- size_t CPyCppyy::Utility::FetchError(std::vector& errors, bool is_cpp) { // Fetch the current python error, if any, and store it for future use. if (PyErr_Occurred()) { - errors.emplace_back(FetchPyError()); - errors.back().fIsCpp = is_cpp; + PyError_t e{is_cpp}; + PyErr_Fetch(&e.fType, &e.fValue, &e.fTrace); + errors.push_back(e); } return errors.size(); } //---------------------------------------------------------------------------- -void CPyCppyy::Utility::SetDetailedException(std::vector&& errors, PyObject* topmsg, PyObject* defexc) +void CPyCppyy::Utility::SetDetailedException(std::vector& errors, PyObject* topmsg, PyObject* defexc) { // Use the collected exceptions to build up a detailed error log. if (errors.empty()) { @@ -1259,18 +1471,14 @@ void CPyCppyy::Utility::SetDetailedException(std::vector&& errors, Py // bind the original C++ object, rather than constructing from topmsg, as it // is expected to have informative state - RestorePyError(*unique_from_cpp); + Py_INCREF(unique_from_cpp->fType); Py_INCREF(unique_from_cpp->fValue); Py_XINCREF(unique_from_cpp->fTrace); + PyErr_Restore(unique_from_cpp->fType, unique_from_cpp->fValue, unique_from_cpp->fTrace); } else { // try to consolidate Python exceptions, otherwise select default PyObject* exc_type = nullptr; for (auto& e : errors) { -#if PY_VERSION_HEX >= 0x030c0000 - PyObject* pytype = (PyObject*)Py_TYPE(e.fValue.get()); -#else - PyObject* pytype = e.fType.get(); -#endif - if (!exc_type) exc_type = pytype; - else if (exc_type != pytype) { + if (!exc_type) exc_type = e.fType; + else if (exc_type != e.fType) { exc_type = defexc; break; } @@ -1279,15 +1487,14 @@ void CPyCppyy::Utility::SetDetailedException(std::vector&& errors, Py // add the details to the topmsg PyObject* separator = CPyCppyy_PyText_FromString("\n "); for (auto& e : errors) { - PyObject *pyvalue = e.fValue.get(); CPyCppyy_PyText_Append(&topmsg, separator); - if (CPyCppyy_PyText_Check(pyvalue)) { - CPyCppyy_PyText_Append(&topmsg, pyvalue); - } else if (pyvalue) { - PyObject* excstr = PyObject_Str(pyvalue); + if (CPyCppyy_PyText_Check(e.fValue)) { + CPyCppyy_PyText_Append(&topmsg, e.fValue); + } else if (e.fValue) { + PyObject* excstr = PyObject_Str(e.fValue); if (!excstr) { PyErr_Clear(); - excstr = PyObject_Str((PyObject*)Py_TYPE(pyvalue)); + excstr = PyObject_Str((PyObject*)Py_TYPE(e.fValue)); } CPyCppyy_PyText_AppendAndDel(&topmsg, excstr); } else { @@ -1302,6 +1509,8 @@ void CPyCppyy::Utility::SetDetailedException(std::vector&& errors, Py PyErr_SetString(exc_type, CPyCppyy_PyText_AsString(topmsg)); } +// cleanup stored errors and done with topmsg (whether used or not) + std::for_each(errors.begin(), errors.end(), PyError_t::Clear); Py_DECREF(topmsg); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h index 087dac2d456f8..86cd268dc996f 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h @@ -3,7 +3,6 @@ // Standard #include -#include #include #include @@ -40,6 +39,9 @@ PyCallable* FindBinaryOperator(const std::string& lcname, const std::string& rcn enum ArgPreference { kNone, kPointer, kReference, kValue }; std::string ConstructTemplateArgs( PyObject* pyname, PyObject* tpArgs, PyObject* args = nullptr, ArgPreference = kNone, int argoff = 0, int* pcnt = nullptr); +std::vector GetTemplateArgsTypes( + PyObject* scope, PyObject* tpArgs, PyObject* args = nullptr, ArgPreference = kNone, int argoff = 0, int* pcnt = nullptr); + std::string CT2CppNameS(PyObject* pytc, bool allow_voidp); inline PyObject* CT2CppName(PyObject* pytc, const char* cpd, bool allow_voidp) { @@ -98,23 +100,22 @@ PyObject* PyErr_Occurred_WithGIL(); // helpers for collecting/maintaining python exception data struct PyError_t { - struct PyObjectDeleter { - void operator()(PyObject *obj) { Py_XDECREF(obj); } - }; -#if PY_VERSION_HEX < 0x030c0000 - std::unique_ptr fType; - std::unique_ptr fTrace; -#endif - std::unique_ptr fValue; - bool fIsCpp = false; -}; + PyError_t(bool is_cpp = false) : fIsCpp(is_cpp) { fType = fValue = fTrace = 0; } -PyError_t FetchPyError(); -void RestorePyError(PyError_t &error); + static void Clear(PyError_t& e) + { + // Remove exception information. + Py_XDECREF(e.fType); Py_XDECREF(e.fValue); Py_XDECREF(e.fTrace); + e.fType = e.fValue = e.fTrace = 0; + } + + PyObject *fType, *fValue, *fTrace; + bool fIsCpp; +}; size_t FetchError(std::vector&, bool is_cpp = false); void SetDetailedException( - std::vector&& errors /* clears */, PyObject* topmsg /* steals ref */, PyObject* defexc); + std::vector& errors /* clears */, PyObject* topmsg /* steals ref */, PyObject* defexc); // setup Python API for callbacks bool IncludePython(); From 6663c7d7cb5365a29854a60fd325c2177072fc6e Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 27 Oct 2025 16:37:13 +0100 Subject: [PATCH 03/29] minimal updates to cppyy frontend to make new cppyy work --- .../cppyy/cppyy/python/cppyy/__init__.py | 155 ++++++++++-------- .../cppyy/python/cppyy/_cpython_cppyy.py | 57 +++++-- 2 files changed, 125 insertions(+), 87 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py index c8dd458bfdd70..0a6b13ad4fedd 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py @@ -50,11 +50,21 @@ 'set_debug', # enable/disable debug output ] -import ctypes -import os -import sys -import sysconfig -import warnings +from ._version import __version__ + +import ctypes, os, sys, sysconfig, warnings + +if not 'CLING_STANDARD_PCH' in os.environ: + def _set_pch(): + try: + import cppyy_backend as cpb + local_pch = os.path.join(os.path.dirname(__file__), 'allDict.cxx.pch.'+str(cpb.__version__)) + if os.path.exists(local_pch): + os.putenv('CLING_STANDARD_PCH', local_pch) + os.environ['CLING_STANDARD_PCH'] = local_pch + except (ImportError, AttributeError): + pass + _set_pch(); del _set_pch try: import __pypy__ @@ -63,9 +73,6 @@ except ImportError: ispypy = False -from . import _typemap -from ._version import __version__ - # import separately instead of in the above try/except block for easier to # understand tracebacks if ispypy: @@ -79,7 +86,17 @@ sys.modules['cppyy.gbl.std'] = gbl.std +#- force creation of std.exception ------------------------------------------------------- +_e = gbl.std.exception + + +#- enable auto-loading ------------------------------------------------------- +try: gbl.cling.runtime.gCling.EnableAutoLoading() +except: pass + + #- external typemap ---------------------------------------------------------- +from . import _typemap _typemap.initialize(_backend) # also creates (u)int8_t mapper try: @@ -113,15 +130,18 @@ def tuple_getitem(self, idx, get=cppyy.gbl.std.get): raise IndexError(idx) pyclass.__getitem__ = tuple_getitem - # pythonization of std::string; placed here because it's simpler to write the + # pythonization of std::basic_string; placed here because it's simpler to write the # custom "npos" object (to allow easy result checking of find/rfind) in Python - elif pyclass.__cpp_name__ == "std::string": - class NPOS(0x3000000 <= sys.hexversion and int or long): + elif pyclass.__cpp_name__ == "std::basic_string": + class NPOS(int): + def __init__(self, npos): + self.__cpp_npos = npos def __eq__(self, other): - return other == -1 or int(self) == other + return other == -1 or other == self.__cpp_npos def __ne__(self, other): - return other != -1 and int(self) != other - del pyclass.__class__.npos # drop b/c is const data + return other != -1 and other != self.__cpp_npos + if hasattr(pyclass.__class__, 'npos'): + del pyclass.__class__.npos # drop b/c is const data pyclass.npos = NPOS(pyclass.npos) return True @@ -158,24 +178,24 @@ def __getitem__(self, cls): return py_make_smartptr(cls, self.ptrcls) except AttributeError: pass - if isinstance(cls, str) and not cls in ('int', 'float'): + if type(cls) == str and not cls in ('int', 'float'): return py_make_smartptr(getattr(gbl, cls), self.ptrcls) return self.maker[cls] -gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) -gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) +# gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) +# gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) del make_smartptr #--- interface to Cling ------------------------------------------------------ class _stderr_capture(object): def __init__(self): - self._capture = not gbl.gDebug and True or False - self.err = "" + self._capture = not gbl.Cpp.IsDebugOutputEnabled() + self.err = "" def __enter__(self): if self._capture: - _begin_capture_stderr() + _begin_capture_stderr() return self def __exit__(self, tp, val, trace): @@ -200,8 +220,12 @@ def _cling_report(msg, errcode, msg_is_error=False): def cppdef(src): """Declare C++ source to Cling.""" with _stderr_capture() as err: - errcode = gbl.gInterpreter.Declare(src) - _cling_report(err.err, int(not errcode), msg_is_error=True) + errcode = gbl.Cpp.Declare(src, False) + if not errcode == 0 or err.err: + if 'warning' in err.err.lower() and not 'error' in err.err.lower(): + warnings.warn(err.err, SyntaxWarning) + return True + raise SyntaxError('Failed to parse the given C++ code%s' % err.err) return True def cppexec(stmt): @@ -209,23 +233,26 @@ def cppexec(stmt): if stmt and stmt[-1] != ';': stmt += ';' - # capture stderr, but note that ProcessLine could legitimately be writing to + # capture stderr, but note that Process could legitimately be writing to # std::cerr, in which case the captured output needs to be printed as normal with _stderr_capture() as err: errcode = ctypes.c_int(0) try: - gbl.gInterpreter.ProcessLine(stmt, ctypes.pointer(errcode)) + errcode = gbl.Cpp.Process(stmt) except Exception as e: sys.stderr.write("%s\n\n" % str(e)) - if not errcode.value: - errcode.value = 1 + if not errcode.value: errcode.value = 1 - _cling_report(err.err, errcode.value) - if err.err and err.err[1:] != '\n': + if not errcode == 0: + raise SyntaxError('Failed to parse the given C++ code%s' % err.err) + elif err.err and err.err[1:] != '\n': sys.stderr.write(err.err[1:]) return True +def evaluate(input, HadError = _backend.nullptr): + return gbl.Cpp.Evaluate(input, HadError) + def macro(cppm): """Attempt to evalute a C/C++ pre-processor macro as a constant""" @@ -243,35 +270,27 @@ def macro(cppm): def load_library(name): """Explicitly load a shared library.""" with _stderr_capture() as err: - gSystem = gbl.gSystem - if name[:3] != 'lib': - if not gSystem.FindDynamicLibrary(gbl.TString(name), True) and\ - gSystem.FindDynamicLibrary(gbl.TString('lib'+name), True): - name = 'lib'+name - sc = gSystem.Load(name) - if sc == -1: - # special case for Windows as of python3.8: use winmode=0, otherwise the default - # will not consider regular search paths (such as $PATH) - if 0x3080000 <= sys.hexversion and 'win32' in sys.platform and os.path.isabs(name): - return ctypes.CDLL(name, ctypes.RTLD_GLOBAL, winmode=0) # raises on error - raise RuntimeError('Unable to load library "%s"%s' % (name, err.err)) + result = gbl.Cpp.LoadLibrary(name, True) + if result == False: + raise RuntimeError('Could not load library "%s": %s' % (name, err.err)) + return True def include(header): """Load (and JIT) header file
into Cling.""" with _stderr_capture() as err: - errcode = gbl.gInterpreter.Declare('#include "%s"' % header) - if not errcode: + errcode = gbl.Cpp.Declare('#include "%s"' % header, False) + if not errcode == 0: raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) return True def c_include(header): """Load (and JIT) header file
into Cling.""" with _stderr_capture() as err: - errcode = gbl.gInterpreter.Declare("""extern "C" { -#include "%s" -}""" % header) - if not errcode: + errcode = gbl.Cpp.Declare("""extern "C" { + #include "%s" + }""" % header, False) + if not errcode == 0: raise ImportError('Failed to load header file "%s"%s' % (header, err.err)) return True @@ -279,7 +298,7 @@ def add_include_path(path): """Add a path to the include paths available to Cling.""" if not os.path.isdir(path): raise OSError('No such directory: %s' % path) - gbl.gInterpreter.AddIncludePath(path) + gbl.Cpp.AddIncludePath(path) def add_library_path(path): """Add a path to the library search paths available to Cling.""" @@ -300,20 +319,15 @@ def add_library_path(path): if os.getenv('CONDA_PREFIX'): # MacOS, Linux include_path = os.path.join(os.getenv('CONDA_PREFIX'), 'include') - if os.path.exists(include_path): - add_include_path(include_path) + if os.path.exists(include_path): add_include_path(include_path) # Windows include_path = os.path.join(os.getenv('CONDA_PREFIX'), 'Library', 'include') - if os.path.exists(include_path): - add_include_path(include_path) + if os.path.exists(include_path): add_include_path(include_path) -# assuming that we are in PREFIX/lib/python/site-packages/cppyy, -# add PREFIX/include to the search path -include_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), *(4*[os.path.pardir]+['include']))) -if os.path.exists(include_path): - add_include_path(include_path) +# assuming that we are in PREFIX/lib/python/site-packages/cppyy, add PREFIX/include to the search path +include_path = os.path.abspath(os.path.join(os.path.dirname(__file__), *(4*[os.path.pardir]+['include']))) +if os.path.exists(include_path): add_include_path(include_path) del include_path, apipath, ispypy @@ -321,17 +335,14 @@ def add_autoload_map(fname): """Add the entries from a autoload (.rootmap) file to Cling.""" if not os.path.isfile(fname): raise OSError("no such file: %s" % fname) - gbl.gInterpreter.LoadLibraryMap(fname) + gbl.cling.runtime.gCling.LoadLibraryMap(fname) def set_debug(enable=True): """Enable/disable debug output.""" - if enable: - gbl.gDebug = 10 - else: - gbl.gDebug = 0 + gbl.Cpp.EnableDebugOutput(enable) def _get_name(tt): - if isinstance(tt, str): + if type(tt) == str: return tt try: ttname = tt.__cpp_name__ @@ -342,7 +353,7 @@ def _get_name(tt): _sizes = {} def sizeof(tt): """Returns the storage size (in chars) of C++ type .""" - if not isinstance(tt, type) and not isinstance(tt, str): + if not isinstance(tt, type) and not type(tt) == str: tt = type(tt) try: return _sizes[tt] @@ -350,7 +361,7 @@ def sizeof(tt): try: sz = ctypes.sizeof(tt) except TypeError: - sz = gbl.gInterpreter.ProcessLine("sizeof(%s);" % (_get_name(tt),)) + sz = gbl.Cpp.Evaluate("sizeof(%s)" % (_get_name(tt),), nullptr) _sizes[tt] = sz return sz @@ -363,7 +374,7 @@ def typeid(tt): return _typeids[tt] except KeyError: tidname = 'typeid_'+str(len(_typeids)) - gbl.gInterpreter.ProcessLine( + cppexec( "namespace _cppyy_internal { auto* %s = &typeid(%s); }" %\ (tidname, _get_name(tt),)) tid = getattr(gbl._cppyy_internal, tidname) @@ -373,9 +384,15 @@ def typeid(tt): def multi(*bases): # after six, see also _typemap.py """Resolve metaclasses for multiple inheritance.""" # contruct a "no conflict" meta class; the '_meta' is needed by convention - nc_meta = type.__new__( - type, 'cppyy_nc_meta', tuple(type(b) for b in bases if type(b) is not type), {}) + nc_meta = type.__new__(type, 'cppyy_nc_meta', tuple(type(b) for b in bases if type(b) is not type), {}) class faux_meta(type): - def __new__(mcs, name, this_bases, d): + def __new__(cls, name, this_bases, d): return nc_meta(name, bases, d) return type.__new__(faux_meta, 'faux_meta', (), {}) + + +#- workaround (TODO: may not be needed with Clang9) -------------------------- +if 'win32' in sys.platform: + cppdef("""template<> + std::basic_ostream>& __cdecl std::endl>( + std::basic_ostream>&);""") diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py index f98a34a697c1b..e82888de25448 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py @@ -19,17 +19,31 @@ '_end_capture_stderr' ] +# First load the dependency libraries of the backend, then pull in the libcppyy +# extension module. If the backed can't be loaded, it was probably linked +# statically into the extension module, so we don't error out at this point. +try: + from cppyy_backend import loader + c = loader.load_cpp_backend() +except ModuleNotFoundError: + c = None + if platform.system() == "Windows": # On Windows, the library has to be searched without prefix import libcppyy as _backend else: import cppyy.libcppyy as _backend +if c is not None: + _backend._cpp_backend = c + # explicitly expose APIs from libcppyy +import ctypes _w = ctypes.CDLL(_backend.__file__, ctypes.RTLD_GLOBAL) # some beautification for inspect (only on p2) +import sys if sys.hexversion < 0x3000000: # TODO: this reliese on CPPOverload cooking up a func_code object, which atm # is simply not implemented for p3 :/ @@ -61,10 +75,11 @@ class Template(object): # expected/used by ProxyWrappers.cxx in CPyCppyy stl_fixed_size_types = ['std::array'] stl_mapping_types = ['std::map', 'std::unordered_map'] - def __init__(self, name): + def __init__(self, name, scope): self.__name__ = name self.__cpp_name__ = name self._instantiations = dict() + self.__scope__ = scope def __repr__(self): return "" % (self.__name__, hex(id(self))) @@ -81,7 +96,7 @@ def __getitem__(self, *args): pass # construct the type name from the types or their string representation - newargs = [self.__name__] + newargs = [self.__scope__] for arg in args: if isinstance(arg, str): arg = ','.join(map(lambda x: x.strip(), arg.split(','))) @@ -96,13 +111,11 @@ def __getitem__(self, *args): if 'reserve' in pyclass.__dict__: def iadd(self, ll): self.reserve(len(ll)) - for x in ll: - self.push_back(x) + for x in ll: self.push_back(x) return self else: def iadd(self, ll): - for x in ll: - self.push_back(x) + for x in ll: self.push_back(x) return self pyclass.__iadd__ = iadd @@ -117,7 +130,7 @@ def __call__(self, *args): # most common cases are covered if args: args0 = args[0] - if args0 and isinstance(args0, (tuple, list)): + if args0 and (type(args0) is tuple or type(args0) is list): t = type(args0[0]) if t is float: t = 'double' @@ -128,7 +141,7 @@ def __call__(self, *args): if self.__name__ in self.stl_unrolled_types: return self[tuple(type(a) for a in args0)](*args0) - if args0 and isinstance(args0, dict): + if args0 and type(args0) is dict: if self.__name__ in self.stl_mapping_types: try: pair = args0.items().__iter__().__next__() @@ -159,27 +172,26 @@ def __call__(self, *args): #- add to the dynamic path as needed ----------------------------------------- import os def add_default_paths(): - gSystem = gbl.gSystem + libCppInterOp = gbl.Cpp if os.getenv('CONDA_PREFIX'): # MacOS, Linux lib_path = os.path.join(os.getenv('CONDA_PREFIX'), 'lib') - if os.path.exists(lib_path): gSystem.AddDynamicPath(lib_path) + if os.path.exists(lib_path): libCppInterOp.AddSearchPath(lib_path, True, False) # Windows lib_path = os.path.join(os.getenv('CONDA_PREFIX'), 'Library', 'lib') - if os.path.exists(lib_path): gSystem.AddDynamicPath(lib_path) + if os.path.exists(lib_path): libCppInterOp.AddSearchPath(lib_path, True, False) # assuming that we are in PREFIX/lib/python/site-packages/cppyy, add PREFIX/lib to the search path - lib_path = os.path.abspath( - os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir)) - if os.path.exists(lib_path): gSystem.AddDynamicPath(lib_path) + lib_path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir, os.path.pardir)) + if os.path.exists(lib_path): libCppInterOp.AddSearchPath(lib_path, True, False) try: with open('/etc/ld.so.conf') as ldconf: for line in ldconf: f = line.strip() if (os.path.exists(f)): - gSystem.AddDynamicPath(f) + libCppInterOp.AddSearchPath(f, True, False) except IOError: pass add_default_paths() @@ -193,9 +205,18 @@ def add_default_paths(): default = _backend.default def load_reflection_info(name): - sc = gbl.gSystem.Load(name) - if sc == -1: - raise RuntimeError("Unable to load reflection library "+name) +# with _stderr_capture() as err: + #FIXME: Remove the .so and add logic in libcppinterop + name = name + ".so" + result = gbl.Cpp.LoadLibrary(name, True) + if name.endswith("Dict.so"): + header = name[:-7] + ".h" + gbl.Cpp.Declare('#include "' + header +'"', False) + + if result == False: + raise RuntimeError('Could not load library "%s"' % (name)) + + return True def _begin_capture_stderr(): _backend._begin_capture_stderr() From 631a0a664818760e8f6d5a73d60c3a36b3033ce2 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Feb 2026 16:50:17 +0100 Subject: [PATCH 04/29] [CPyCppyy] Update to latest --- .../cppyy/CPyCppyy/src/CPPClassMethod.cxx | 2 +- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 121 +++++++++++++----- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.h | 2 + .../pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx | 57 +++++++++ bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 29 +++-- .../pyroot/cppyy/CPyCppyy/src/PyCallable.h | 2 + .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 4 +- 7 files changed, 173 insertions(+), 44 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx index f55e50f7a3e1a..e7351a2f397cf 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPClassMethod.cxx @@ -35,7 +35,7 @@ PyObject* CPyCppyy::CPPClassMethod::Call(CPPInstance*& if ((!self || (PyObject*)self == Py_None) && nargs) { PyObject* arg0 = CPyCppyy_PyArgs_GET_ITEM(args, 0); if (CPPInstance_Check(arg0) && fArgsRequired <= nargs - 1 && - Cppyy::IsSubtype(reinterpret_cast(arg0)->ObjectIsA(), GetScope())) { + Cppyy::IsSubclass(reinterpret_cast(arg0)->ObjectIsA(), GetScope())) { args += 1; // drops first argument nargsf -= 1; } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index 55f094cce07ef..abebaf90dafdc 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx @@ -294,27 +294,28 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) PyObject *evalue = nullptr; PyObject *etrace = nullptr; - PyObject *etype = nullptr, *evalue = nullptr; - if (PyErr_Occurred()) { - PyObject* etrace = nullptr; - +if (PyErr_Occurred()) { PyErr_Fetch(&etype, &evalue, &etrace); - - if (evalue) { - PyObject* descr = PyObject_Str(evalue); - if (descr) { - details = CPyCppyy_PyText_AsString(descr); - Py_DECREF(descr); - } - } - - Py_XDECREF(etrace); } +#endif + + const bool isCppExc = evalue && PyType_IsSubtype((PyTypeObject*)etype, &CPPExcInstance_Type); + // If the error is not a CPPExcInstance, the error from Python itself is + // already complete and messing with it would only make it less informative. + // Just restore and return. + if (evalue && !isCppExc) { +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(evalue); +#else + PyErr_Restore(etype, evalue, etrace); +#endif + return; + } PyObject* doc = GetDocString(); - PyObject* errtype = etype; - if (!errtype) - errtype = PyExc_TypeError; + const char* cdoc = CPyCppyy_PyText_AsString(doc); + const char* cmsg = msg ? CPyCppyy_PyText_AsString(msg) : nullptr; + PyObject* errtype = etype ? etype : PyExc_TypeError; PyObject* pyname = PyObject_GetAttr(errtype, PyStrings::gName); const char* cname = pyname ? CPyCppyy_PyText_AsString(pyname) : "Exception"; @@ -329,24 +330,17 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) if (msg) { topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: %s | ", cdoc, cname, cmsg); } else { - PyErr_Format(errtype, "%s =>\n %s: %s", - CPyCppyy_PyText_AsString(doc), cname, details.c_str()); + topMessage = CPyCppyy_PyText_FromFormat("%s =>\n %s: ", cdoc, cname); } - } else { - Py_XDECREF(((CPPExcInstance*)evalue)->fTopMessage); - if (msg) { - ((CPPExcInstance*)evalue)->fTopMessage = CPyCppyy_PyText_FromFormat(\ - "%s =>\n %s: %s | ", CPyCppyy_PyText_AsString(doc), cname, CPyCppyy_PyText_AsString(msg)); - } else { - ((CPPExcInstance*)evalue)->fTopMessage = CPyCppyy_PyText_FromFormat(\ - "%s =>\n %s: ", CPyCppyy_PyText_AsString(doc), cname); - } - PyErr_SetObject(errtype, evalue); + // restore the updated error +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(evalue); +#else + PyErr_Restore(etype, evalue, etrace); +#endif } Py_XDECREF(pyname); - Py_XDECREF(evalue); - Py_XDECREF(etype); Py_DECREF(doc); Py_XDECREF(msg); } @@ -1073,6 +1067,71 @@ PyObject* CPyCppyy::CPPMethod::GetSignature(bool fa) return CPyCppyy_PyText_FromString(GetSignatureString(fa).c_str()); } + +/** + * @brief Returns a tuple with the names of the input parameters of this method. + * + * For example given a function with prototype: + * + * double foo(int a, float b, double c) + * + * this function returns: + * + * ('a', 'b', 'c') + */ +PyObject *CPyCppyy::CPPMethod::GetSignatureNames() +{ + // Build a tuple of the argument names for this signature. + int argcount = GetMaxArgs(); + PyObject *signature_names = PyTuple_New(argcount); + + for (int iarg = 0; iarg < argcount; ++iarg) { + const std::string &argname_cpp = Cppyy::GetMethodArgName(fMethod, iarg); + PyObject *argname_py = CPyCppyy_PyText_FromString(argname_cpp.c_str()); + PyTuple_SET_ITEM(signature_names, iarg, argname_py); + } + + return signature_names; +} + +/** + * @brief Returns a dictionary with the types of the signature of this method. + * + * This dictionary will store both the return type and the input parameter + * types of this method, respectively with keys "return_type" and + * "input_types", for example given a function with prototype: + * + * double foo(int a, float b, double c) + * + * this function returns: + * + * {'input_types': ('int', 'float', 'double'), 'return_type': 'double'} + */ +PyObject *CPyCppyy::CPPMethod::GetSignatureTypes() +{ + + PyObject *signature_types_dict = PyDict_New(); + + // Insert the return type first + std::string return_type = GetReturnTypeName(); + PyObject *return_type_py = CPyCppyy_PyText_FromString(return_type.c_str()); + PyDict_SetItem(signature_types_dict, CPyCppyy_PyText_FromString("return_type"), return_type_py); + + // Build a tuple of the argument types for this signature. + int argcount = GetMaxArgs(); + PyObject *parameter_types = PyTuple_New(argcount); + + for (int iarg = 0; iarg < argcount; ++iarg) { + const std::string &argtype_cpp = Cppyy::GetMethodArgTypeAsString(fMethod, iarg); + PyObject *argtype_py = CPyCppyy_PyText_FromString(argtype_cpp.c_str()); + PyTuple_SET_ITEM(parameter_types, iarg, argtype_py); + } + + PyDict_SetItem(signature_types_dict, CPyCppyy_PyText_FromString("input_types"), parameter_types); + + return signature_types_dict; +} + //---------------------------------------------------------------------------- std::string CPyCppyy::CPPMethod::GetReturnTypeName() { diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h index 88e49621ef629..e9558f5c6869b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h @@ -51,6 +51,8 @@ class CPPMethod : public PyCallable { public: PyObject* GetSignature(bool show_formalargs = true) override; + PyObject* GetSignatureNames() override; + PyObject* GetSignatureTypes() override; PyObject* GetPrototype(bool show_formalargs = true) override; PyObject* GetTypeName() override; PyObject* Reflex(Cppyy::Reflex::RequestId_t request, diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx index 92e87a3bc3d98..62625b2b8b78a 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx @@ -62,6 +62,12 @@ class TPythonCallback : public PyCallable { PyObject* GetSignature(bool /*show_formalargs*/ = true) override { return CPyCppyy_PyText_FromString("*args, **kwargs"); } + PyObject* GetSignatureNames() override { + return PyTuple_New(0); + } + PyObject* GetSignatureTypes() override { + return PyTuple_New(0); + } PyObject* GetPrototype(bool /*show_formalargs*/ = true) override { return CPyCppyy_PyText_FromString(""); } @@ -285,6 +291,55 @@ static int mp_doc_set(CPPOverload* pymeth, PyObject *val, void *) return 0; } + +/** + * @brief Returns a dictionary with the input parameter names for all overloads. + * + * This dictionary may look like: + * + * {'double ::foo(int a, float b, double c)': ('a', 'b', 'c'), + * 'float ::foo(float b)': ('b',), + * 'int ::foo(int a)': ('a',), + * 'int ::foo(int a, float b)': ('a', 'b')} + */ +static PyObject *mp_func_overloads_names(CPPOverload *pymeth) +{ + + const CPPOverload::Methods_t &methods = pymeth->fMethodInfo->fMethods; + + PyObject *overloads_names_dict = PyDict_New(); + + for (PyCallable *method : methods) { + PyDict_SetItem(overloads_names_dict, method->GetPrototype(), method->GetSignatureNames()); + } + + return overloads_names_dict; +} + +/** + * @brief Returns a dictionary with the types of all overloads. + * + * This dictionary may look like: + * + * {'double ::foo(int a, float b, double c)': {'input_types': ('int', 'float', 'double'), 'return_type': 'double'}, + * 'float ::foo(float b)': {'input_types': ('float',), 'return_type': 'float'}, + * 'int ::foo(int a)': {'input_types': ('int',), 'return_type': 'int'}, + * 'int ::foo(int a, float b)': {'input_types': ('int', 'float'), 'return_type': 'int'}} + */ +static PyObject *mp_func_overloads_types(CPPOverload *pymeth) +{ + + const CPPOverload::Methods_t &methods = pymeth->fMethodInfo->fMethods; + + PyObject *overloads_types_dict = PyDict_New(); + + for (PyCallable *method : methods) { + PyDict_SetItem(overloads_types_dict, method->GetPrototype(), method->GetSignatureTypes()); + } + + return overloads_types_dict; +} + //---------------------------------------------------------------------------- static PyObject* mp_meth_func(CPPOverload* pymeth, void*) { @@ -582,6 +637,8 @@ static PyGetSetDef mp_getset[] = { {(char*)"func_globals", (getter)mp_func_globals, nullptr, nullptr, nullptr}, {(char*)"func_doc", (getter)mp_doc, (setter)mp_doc_set, nullptr, nullptr}, {(char*)"func_name", (getter)mp_name, nullptr, nullptr, nullptr}, + {(char*)"func_overloads_types", (getter)mp_func_overloads_types, nullptr, nullptr, nullptr}, + {(char*)"func_overloads_names", (getter)mp_func_overloads_names, nullptr, nullptr, nullptr}, // flags to control behavior diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h index 2212ff3330c03..12612639050f4 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h @@ -36,14 +36,23 @@ typedef unsigned long long PY_ULONG_LONG; typedef long double PY_LONG_DOUBLE; #endif -namespace Cpp { - struct TemplateArgInfo { - void* m_Type; - const char* m_IntegralValue; - TemplateArgInfo(void* type, const char* integral_value = nullptr) - : m_Type(type), m_IntegralValue(integral_value) {} - }; -} // end namespace Cpp +// FIXME: We should not duplicate these definitions here and in CppInterOp.h +// The current setup relies on finding an identical symbol definition in +// libcppyybackend.so which is fragile and requires updating both locations when +// changing. Ideally we should have the ability to set/get the template arg info +// provided through some factory methods in CppInterOp API, so the clients can +// rely completely on opaque pointers like we do for the rest of the argument +// types. +namespace CppImpl { +struct TemplateArgInfo { + void* m_Type; + const char* m_IntegralValue; + TemplateArgInfo(void* type, const char* integral_value = nullptr) + : m_Type(type), m_IntegralValue(integral_value) {} +}; +} // namespace CppImpl + +namespace Cpp = CppImpl; namespace Cppyy { @@ -117,8 +126,8 @@ namespace Cppyy { CPPYY_IMPORT bool IsPointerType(TCppType_t type); - CPPYY_IMPORT - TCppScope_t gGlobalScope; // for fast access + // CPPYY_IMPORT + // TCppScope_t gGlobalScope; // for fast access // memory management --------------------------------------------------------- CPPYY_IMPORT diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h b/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h index 62ce3aa1af693..d2a072c5b389e 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h @@ -18,6 +18,8 @@ class PyCallable { public: virtual PyObject* GetSignature(bool show_formalargs = true) = 0; + virtual PyObject* GetSignatureNames() = 0; + virtual PyObject* GetSignatureTypes() = 0; virtual PyObject* GetPrototype(bool show_formalargs = true) = 0; virtual PyObject* GetTypeName() { return GetPrototype(false); } virtual PyObject* GetDocString() { return GetPrototype(); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index 84896443b64d7..cb5720131e323 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -376,7 +376,7 @@ PyObject *spanBegin() static PyObject *pyFunc = nullptr; if (!pyFunc) { compileSpanHelpers(); - PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::gGlobalScope); + PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::GetGlobalScope()); pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin"); if (!pyFunc) { PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper " @@ -391,7 +391,7 @@ PyObject *spanEnd() static PyObject *pyFunc = nullptr; if (!pyFunc) { compileSpanHelpers(); - PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::gGlobalScope); + PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::GetGlobalScope()); pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end"); if (!pyFunc) { PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper " From 5fe8e0ca40eb3bfd92b1ea02c47389fcf4f59360 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Feb 2026 16:51:14 +0100 Subject: [PATCH 05/29] [cppyy-backend] Sync with compres forks, pass header location to clingwrapper --- .../pyroot/cppyy/cppyy-backend/CMakeLists.txt | 11 + .../clingwrapper/src/callcontext.h | 3 - .../clingwrapper/src/clingwrapper.cxx | 616 +----------------- .../clingwrapper/src/cpp_cppyy.h | 18 +- 4 files changed, 42 insertions(+), 606 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt index ccdfd3eb62ec3..6469a9bcdd3f0 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt +++ b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt @@ -12,6 +12,17 @@ target_include_directories(cppyy_backend PRIVATE ${CMAKE_SOURCE_DIR}/interpreter/CppInterOp/include ) +target_compile_definitions(cppyy_backend PRIVATE + # FIXME: These headers need to be installed and the location must be provided to clingwrapper in a robust way + CPPINTEROP_DIR="${CMAKE_SOURCE_DIR}/interpreter/CppInterOp" + CMAKE_SHARED_LIBRARY_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" + # The CppInterOp JitCall debug assertions (AreArgumentsValid, + # ReportInvokeStart) use Clang AST internals and cannot be resolved + # through the dispatch mechanism. Disable them in this translation unit. + NDEBUG +) + + target_link_libraries(cppyy_backend Core) if(NOT MSVC) target_compile_options(cppyy_backend PRIVATE -fPIC) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h index 5f729410e4c2c..9f33daa4271cd 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h @@ -4,9 +4,6 @@ // Standard #include -//Bindings -#include "cpp_cppyy.h" - //ROOT // #include "Rtypes.h" diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index 9872cf75fecff..5f292f584b90e 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -6,7 +6,6 @@ #endif // Bindings -#include "capi.h" #include "cpp_cppyy.h" #include "callcontext.h" @@ -340,6 +339,25 @@ class ApplicationStarter { // helper for multiple inheritance Cpp::Declare("namespace __cppyy_internal { struct Sep; }", /*silent=*/false); + + // retrieve all initial (ROOT) C++ names in the global scope to allow filtering later + gROOT->GetListOfGlobals(true); // force initialize + gROOT->GetListOfGlobalFunctions(true); // id. + std::set initial; + Cppyy::GetAllCppNames(Cppyy::GetGlobalScope(), initial); + gInitialNames = initial; + +#ifndef WIN32 + gRootSOs.insert("libCore.so "); + gRootSOs.insert("libRIO.so "); + gRootSOs.insert("libThread.so "); + gRootSOs.insert("libMathCore.so "); +#else + gRootSOs.insert("libCore.dll "); + gRootSOs.insert("libRIO.dll "); + gRootSOs.insert("libThread.dll "); + gRootSOs.insert("libMathCore.dll "); +#endif // std::string libInterOp = I->getDynamicLibraryManager()->lookupLibrary("libcling"); // void *interopDL = dlopen(libInterOp.c_str(), RTLD_LAZY); @@ -2220,599 +2238,3 @@ void Cppyy::DumpScope(TCppScope_t scope) { Cpp::DumpScope(scope); } - -//- C-linkage wrappers ------------------------------------------------------- - -extern "C" { -// direct interpreter access ---------------------------------------------- -int cppyy_compile(const char* code) { - return Cppyy::Compile(code); -} - -int cppyy_compile_silent(const char* code) { - return Cppyy::Compile(code, true /* silent */); -} - -char* cppyy_to_string(cppyy_type_t klass, cppyy_object_t obj) { - return cppstring_to_cstring(Cppyy::ToString((Cppyy::TCppType_t) klass, obj)); -} - - -// name to opaque C++ scope representation -------------------------------- -// char* cppyy_resolve_name(const char* cppitem_name) { -// return cppstring_to_cstring(Cppyy::ResolveName(cppitem_name)); -// } - -// char* cppyy_resolve_enum(const char* enum_type) { -// return cppstring_to_cstring(Cppyy::ResolveEnum(enum_type)); -// } - -cppyy_scope_t cppyy_get_scope(const char* scope_name) { - return cppyy_scope_t(Cppyy::GetScope(scope_name)); -} -// -// cppyy_type_t cppyy_actual_class(cppyy_type_t klass, cppyy_object_t obj) { -// return cppyy_type_t(Cppyy::GetActualClass(klass, (void*)obj)); -// } - -size_t cppyy_size_of_klass(cppyy_type_t klass) { - return Cppyy::SizeOf((Cppyy::TCppType_t) klass); -} - -// size_t cppyy_size_of_type(const char* type_name) { -// return Cppyy::SizeOf(type_name); -// } -// -// int cppyy_is_builtin(const char* type_name) { -// return (int)Cppyy::IsBuiltin(type_name); -// } -// -// int cppyy_is_complete(const char* type_name) { -// return (int)Cppyy::IsComplete(type_name); -// } -// -// -// [> memory management ------------------------------------------------------ <] -// cppyy_object_t cppyy_allocate(cppyy_scope_t type) { -// return cppyy_object_t(Cppyy::Allocate(type)); -// } -// -// void cppyy_deallocate(cppyy_scope_t type, cppyy_object_t self) { -// Cppyy::Deallocate(type, (void*)self); -// } -// -// cppyy_object_t cppyy_construct(cppyy_type_t type) { -// return (cppyy_object_t)Cppyy::Construct(type); -// } -// -// void cppyy_destruct(cppyy_type_t type, cppyy_object_t self) { -// Cppyy::Destruct(type, (void*)self); -// } -// -// -// [> method/function dispatching -------------------------------------------- <] -// [> Exception types: -// 1: default (unknown exception) -// 2: standard exception -// */ -// #define CPPYY_HANDLE_EXCEPTION \ -// catch (std::exception& e) { \ -// cppyy_exctype_t* etype = (cppyy_exctype_t*)((Parameter*)args+nargs); \ -// *etype = (cppyy_exctype_t)2; \ -// *((char**)(etype+1)) = cppstring_to_cstring(e.what()); \ -// } \ -// catch (...) { \ -// cppyy_exctype_t* etype = (cppyy_exctype_t*)((Parameter*)args+nargs); \ -// *etype = (cppyy_exctype_t)1; \ -// *((char**)(etype+1)) = \ -// cppstring_to_cstring("unhandled, unknown C++ exception"); \ -// } -// -// void cppyy_call_v(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// Cppyy::CallV(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// } -// -// unsigned char cppyy_call_b(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (unsigned char)Cppyy::CallB(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (unsigned char)-1; -// } -// -// char cppyy_call_c(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (char)Cppyy::CallC(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (char)-1; -// } -// -// short cppyy_call_h(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (short)Cppyy::CallH(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (short)-1; -// } -// -// int cppyy_call_i(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (int)Cppyy::CallI(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (int)-1; -// } -// -// long cppyy_call_l(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (long)Cppyy::CallL(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (long)-1; -// } -// -// long long cppyy_call_ll(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (long long)Cppyy::CallLL(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (long long)-1; -// } -// -// float cppyy_call_f(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (float)Cppyy::CallF(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (float)-1; -// } -// -// double cppyy_call_d(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (double)Cppyy::CallD(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (double)-1; -// } -// -// long double cppyy_call_ld(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (long double)Cppyy::CallLD(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (long double)-1; -// } -// -// double cppyy_call_nld(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// return (double)cppyy_call_ld(method, self, nargs, args); -// } -// -// void* cppyy_call_r(cppyy_method_t method, cppyy_object_t self, int nargs, void* args) { -// try { -// return (void*)Cppyy::CallR(method, (void*)self, nargs, args); -// } CPPYY_HANDLE_EXCEPTION -// return (void*)nullptr; -// } -// -// char* cppyy_call_s( -// cppyy_method_t method, cppyy_object_t self, int nargs, void* args, size_t* lsz) { -// try { -// return Cppyy::CallS(method, (void*)self, nargs, args, lsz); -// } CPPYY_HANDLE_EXCEPTION -// return (char*)nullptr; -// } -// -// cppyy_object_t cppyy_constructor( -// cppyy_method_t method, cppyy_type_t klass, int nargs, void* args) { -// try { -// return cppyy_object_t(Cppyy::CallConstructor(method, klass, nargs, args)); -// } CPPYY_HANDLE_EXCEPTION -// return (cppyy_object_t)0; -// } -// -// void cppyy_destructor(cppyy_type_t klass, cppyy_object_t self) { -// Cppyy::CallDestructor(klass, self); -// } -// -// cppyy_object_t cppyy_call_o(cppyy_method_t method, cppyy_object_t self, -// int nargs, void* args, cppyy_type_t result_type) { -// try { -// return cppyy_object_t(Cppyy::CallO(method, (void*)self, nargs, args, result_type)); -// } CPPYY_HANDLE_EXCEPTION -// return (cppyy_object_t)0; -// } -// -// cppyy_funcaddr_t cppyy_function_address(cppyy_method_t method) { -// return cppyy_funcaddr_t(Cppyy::GetFunctionAddress(method, true)); -// } -// -// -// [> handling of function argument buffer ----------------------------------- <] -// void* cppyy_allocate_function_args(int nargs) { -// // for calls through C interface, require extra space for reporting exceptions -// return malloc(nargs*sizeof(Parameter)+sizeof(cppyy_exctype_t)+sizeof(char**)); -// } -// -// void cppyy_deallocate_function_args(void* args) { -// free(args); -// } -// -// size_t cppyy_function_arg_sizeof() { -// return (size_t)Cppyy::GetFunctionArgSizeof(); -// } -// -// size_t cppyy_function_arg_typeoffset() { -// return (size_t)Cppyy::GetFunctionArgTypeoffset(); -// } - - -// scope reflection information ------------------------------------------- -int cppyy_is_namespace(cppyy_scope_t scope) { - return (int)Cppyy::IsNamespace((Cppyy::TCppScope_t) scope); -} - -// int cppyy_is_template(const char* template_name) { -// return (int)Cppyy::IsTemplate(template_name); -// } -// -// int cppyy_is_abstract(cppyy_type_t type) { -// return (int)Cppyy::IsAbstract(type); -// } -// -// int cppyy_is_enum(const char* type_name) { -// return (int)Cppyy::IsEnum(type_name); -// } -// -// int cppyy_is_aggregate(cppyy_type_t type) { -// return (int)Cppyy::IsAggregate(type); -// } -// -// int cppyy_is_default_constructable(cppyy_type_t type) { -// return (int)Cppyy::IsDefaultConstructable(type); -// } -// -// const char** cppyy_get_all_cpp_names(cppyy_scope_t scope, size_t* count) { -// std::set cppnames; -// Cppyy::GetAllCppNames(scope, cppnames); -// const char** c_cppnames = (const char**)malloc(cppnames.size()*sizeof(const char*)); -// int i = 0; -// for (const auto& name : cppnames) { -// c_cppnames[i] = cppstring_to_cstring(name); -// ++i; -// } -// *count = cppnames.size(); -// return c_cppnames; -// } -// -// -// [> namespace reflection information --------------------------------------- <] -// cppyy_scope_t* cppyy_get_using_namespaces(cppyy_scope_t scope) { -// const std::vector& uv = Cppyy::GetUsingNamespaces((Cppyy::TCppScope_t)scope); -// -// if (uv.empty()) -// return (cppyy_index_t*)nullptr; -// -// cppyy_scope_t* llresult = (cppyy_scope_t*)malloc(sizeof(cppyy_scope_t)*(uv.size()+1)); -// for (int i = 0; i < (int)uv.size(); ++i) llresult[i] = uv[i]; -// llresult[uv.size()] = (cppyy_scope_t)0; -// return llresult; -// } -// -// -// [> class reflection information ------------------------------------------- <] -// char* cppyy_final_name(cppyy_type_t type) { -// return cppstring_to_cstring(Cppyy::GetFinalName(type)); -// } -// -// char* cppyy_scoped_final_name(cppyy_type_t type) { -// return cppstring_to_cstring(Cppyy::GetScopedFinalName(type)); -// } -// -// int cppyy_has_virtual_destructor(cppyy_type_t type) { -// return (int)Cppyy::HasVirtualDestructor(type); -// } -// -// int cppyy_has_complex_hierarchy(cppyy_type_t type) { -// return (int)Cppyy::HasComplexHierarchy(type); -// } -// -// int cppyy_num_bases(cppyy_type_t type) { -// return (int)Cppyy::GetNumBases(type); -// } -// -// char* cppyy_base_name(cppyy_type_t type, int base_index) { -// return cppstring_to_cstring(Cppyy::GetBaseName (type, base_index)); -// } -// -// int cppyy_is_subtype(cppyy_type_t derived, cppyy_type_t base) { -// return (int)Cppyy::IsSubclass(derived, base); -// } - - int cppyy_is_smartptr(cppyy_type_t type) { - return (int)Cppyy::IsSmartPtr((Cppyy::TCppType_t)type); - } - -// int cppyy_smartptr_info(const char* name, cppyy_type_t* raw, cppyy_method_t* deref) { -// return (int)Cppyy::GetSmartPtrInfo(name, raw, deref); -// } -// -// void cppyy_add_smartptr_type(const char* type_name) { -// Cppyy::AddSmartPtrType(type_name); -// } -// -// void cppyy_add_type_reducer(const char* reducable, const char* reduced) { -// Cppyy::AddTypeReducer(reducable, reduced); -// } -// -// -// [> calculate offsets between declared and actual type, up-cast: direction > 0; down-cast: direction < 0 <] -// ptrdiff_t cppyy_base_offset(cppyy_type_t derived, cppyy_type_t base, cppyy_object_t address, int direction) { -// return (ptrdiff_t)Cppyy::GetBaseOffset(derived, base, (void*)address, direction, 0); -// } -// -// -// [> method/function reflection information --------------------------------- <] -// int cppyy_num_methods(cppyy_scope_t scope) { -// return (int)Cppyy::GetNumMethods(scope); -// } -// -// int cppyy_num_methods_ns(cppyy_scope_t scope) { -// return (int)Cppyy::GetNumMethods(scope, true); -// } -// -// cppyy_index_t* cppyy_method_indices_from_name(cppyy_scope_t scope, const char* name) -// { -// std::vector result = Cppyy::GetMethodIndicesFromName(scope, name); -// -// if (result.empty()) -// return (cppyy_index_t*)nullptr; -// -// cppyy_index_t* llresult = (cppyy_index_t*)malloc(sizeof(cppyy_index_t)*(result.size()+1)); -// for (int i = 0; i < (int)result.size(); ++i) llresult[i] = result[i]; -// llresult[result.size()] = -1; -// return llresult; -// } -// -// cppyy_method_t cppyy_get_method(cppyy_scope_t scope, cppyy_index_t idx) { -// return cppyy_method_t(Cppyy::GetMethod(scope, idx)); -// } -// -// char* cppyy_method_name(cppyy_method_t method) { -// return cppstring_to_cstring(Cppyy::GetMethodName((Cppyy::TCppMethod_t)method)); -// } -// -// char* cppyy_method_full_name(cppyy_method_t method) { -// return cppstring_to_cstring(Cppyy::GetMethodFullName((Cppyy::TCppMethod_t)method)); -// } -// -// char* cppyy_method_mangled_name(cppyy_method_t method) { -// return cppstring_to_cstring(Cppyy::GetMethodMangledName((Cppyy::TCppMethod_t)method)); -// } -// -// char* cppyy_method_result_type(cppyy_method_t method) { -// return cppstring_to_cstring(Cppyy::GetMethodReturnTypeAsString((Cppyy::TCppMethod_t)method)); -// } -// -// int cppyy_method_num_args(cppyy_method_t method) { -// return (int)Cppyy::GetMethodNumArgs((Cppyy::TCppMethod_t)method); -// } -// -// int cppyy_method_req_args(cppyy_method_t method) { -// return (int)Cppyy::GetMethodReqArgs((Cppyy::TCppMethod_t)method); -// } -// -// char* cppyy_method_arg_name(cppyy_method_t method, int arg_index) { -// return cppstring_to_cstring(Cppyy::GetMethodArgName((Cppyy::TCppMethod_t)method, (Cppyy::TCppIndex_t)arg_index)); -// } -// -// char* cppyy_method_arg_type(cppyy_method_t method, int arg_index) { -// return cppstring_to_cstring(Cppyy::GetMethodArgType((Cppyy::TCppMethod_t)method, (Cppyy::TCppIndex_t)arg_index)); -// } -// -// char* cppyy_method_arg_default(cppyy_method_t method, int arg_index) { -// return cppstring_to_cstring(Cppyy::GetMethodArgDefault((Cppyy::TCppMethod_t)method, (Cppyy::TCppIndex_t)arg_index)); -// } -// -// char* cppyy_method_signature(cppyy_method_t method, int show_formalargs) { -// return cppstring_to_cstring(Cppyy::GetMethodSignature((Cppyy::TCppMethod_t)method, (bool)show_formalargs)); -// } -// -// char* cppyy_method_signature_max(cppyy_method_t method, int show_formalargs, int maxargs) { -// return cppstring_to_cstring(Cppyy::GetMethodSignature((Cppyy::TCppMethod_t)method, (bool)show_formalargs, (Cppyy::TCppIndex_t)maxargs)); -// } -// -// char* cppyy_method_prototype(cppyy_scope_t scope, cppyy_method_t method, int show_formalargs) { -// return cppstring_to_cstring(Cppyy::GetMethodPrototype( -// (Cppyy::TCppScope_t)scope, (Cppyy::TCppMethod_t)method, (bool)show_formalargs)); -// } -// -// int cppyy_is_const_method(cppyy_method_t method) { -// return (int)Cppyy::IsConstMethod((Cppyy::TCppMethod_t)method); -// } -// -// int cppyy_get_num_templated_methods(cppyy_scope_t scope) { -// return (int)Cppyy::GetNumTemplatedMethods((Cppyy::TCppScope_t)scope); -// } -// -// int cppyy_get_num_templated_methods_ns(cppyy_scope_t scope) { -// return (int)Cppyy::GetNumTemplatedMethods((Cppyy::TCppScope_t)scope, true); -// } -// -// char* cppyy_get_templated_method_name(cppyy_scope_t scope, cppyy_index_t imeth) { -// return cppstring_to_cstring(Cppyy::GetTemplatedMethodName((Cppyy::TCppScope_t)scope, (Cppyy::TCppIndex_t)imeth)); -// } -// -// int cppyy_is_templated_constructor(cppyy_scope_t scope, cppyy_index_t imeth) { -// return Cppyy::IsTemplatedConstructor((Cppyy::TCppScope_t)scope, (Cppyy::TCppIndex_t)imeth); -// } -// -// int cppyy_exists_method_template(cppyy_scope_t scope, const char* name) { -// return (int)Cppyy::ExistsMethodTemplate((Cppyy::TCppScope_t)scope, name); -// } -// -// int cppyy_method_is_template(cppyy_scope_t scope, cppyy_index_t idx) { -// return (int)Cppyy::IsMethodTemplate((Cppyy::TCppScope_t)scope, idx); -// } -// -// cppyy_method_t cppyy_get_method_template(cppyy_scope_t scope, const char* name, const char* proto) { -// return cppyy_method_t(Cppyy::GetMethodTemplate((Cppyy::TCppScope_t)scope, name, proto)); -// } -// -// cppyy_index_t cppyy_get_global_operator(cppyy_scope_t scope, cppyy_scope_t lc, cppyy_scope_t rc, const char* op) { -// return cppyy_index_t(Cppyy::GetGlobalOperator(scope, Cppyy::GetScopedFinalName(lc), Cppyy::GetScopedFinalName(rc), op)); -// } -// -// -// [> method properties ------------------------------------------------------ <] -// int cppyy_is_publicmethod(cppyy_method_t method) { -// return (int)Cppyy::IsPublicMethod((Cppyy::TCppMethod_t)method); -// } -// -// int cppyy_is_protectedmethod(cppyy_method_t method) { -// return (int)Cppyy::IsProtectedMethod((Cppyy::TCppMethod_t)method); -// } -// -// int cppyy_is_constructor(cppyy_method_t method) { -// return (int)Cppyy::IsConstructor((Cppyy::TCppMethod_t)method); -// } -// -// int cppyy_is_destructor(cppyy_method_t method) { -// return (int)Cppyy::IsDestructor((Cppyy::TCppMethod_t)method); -// } -// -// int cppyy_is_staticmethod(cppyy_method_t method) { -// return (int)Cppyy::IsStaticMethod((Cppyy::TCppMethod_t)method); -// } -// -// -// [> data member reflection information ------------------------------------- <] -// int cppyy_num_datamembers(cppyy_scope_t scope) { -// return (int)Cppyy::GetNumDatamembers(scope); -// } -// -// int cppyy_num_datamembers_ns(cppyy_scope_t scope) { -// return (int)Cppyy::GetNumDatamembers(scope, true); -// } -// -// char* cppyy_datamember_name(cppyy_scope_t scope, int datamember_index) { -// return cppstring_to_cstring(Cppyy::GetDatamemberName(scope, datamember_index)); -// } -// -// char* cppyy_datamember_type(cppyy_scope_t scope, int datamember_index) { -// return cppstring_to_cstring(Cppyy::GetDatamemberType(scope, datamember_index)); -// } -// -// intptr_t cppyy_datamember_offset(cppyy_scope_t scope, int datamember_index) { -// return intptr_t(Cppyy::GetDatamemberOffset(scope, datamember_index)); -// } -// -// int cppyy_datamember_index(cppyy_scope_t scope, const char* name) { -// return (int)Cppyy::GetDatamemberIndex(scope, name); -// } -// -// int cppyy_datamember_index_enumerated(cppyy_scope_t scope, int datamember_index) { -// return (int)Cppyy::GetDatamemberIndexEnumerated(scope, datamember_index); -// } -// -// -// [> data member properties ------------------------------------------------- <] -// int cppyy_is_publicdata(cppyy_type_t type, cppyy_index_t datamember_index) { -// return (int)Cppyy::IsPublicData(type, datamember_index); -// } -// -// int cppyy_is_protecteddata(cppyy_type_t type, cppyy_index_t datamember_index) { -// return (int)Cppyy::IsProtectedData(type, datamember_index); -// } -// -// int cppyy_is_staticdata(cppyy_type_t type, cppyy_index_t datamember_index) { -// return (int)Cppyy::IsStaticData(type, datamember_index); -// } -// -// int cppyy_is_const_data(cppyy_scope_t scope, cppyy_index_t idata) { -// return (int)Cppyy::IsConstData(scope, idata); -// } -// -// int cppyy_is_enum_data(cppyy_scope_t scope, cppyy_index_t idata) { -// return (int)Cppyy::IsEnumData(scope, idata); -// } -// -// int cppyy_get_dimension_size(cppyy_scope_t scope, cppyy_index_t idata, int dimension) { -// return Cppyy::GetDimensionSize(scope, idata, dimension); -// } -// -// -// [> enum properties -------------------------------------------------------- <] -// cppyy_enum_t cppyy_get_enum(cppyy_scope_t scope, const char* enum_name) { -// return Cppyy::GetEnum(scope, enum_name); -// } -// -// cppyy_index_t cppyy_get_num_enum_data(cppyy_enum_t e) { -// return Cppyy::GetNumEnumData(e); -// } -// -// const char* cppyy_get_enum_data_name(cppyy_enum_t e, cppyy_index_t idata) { -// return cppstring_to_cstring(Cppyy::GetEnumDataName(e, idata)); -// } -// -// long long cppyy_get_enum_data_value(cppyy_enum_t e, cppyy_index_t idata) { -// return Cppyy::GetEnumDataValue(e, idata); -// } -// -// -// [> misc helpers ----------------------------------------------------------- <] -// RPY_EXTERN -// void* cppyy_load_dictionary(const char* lib_name) { -// int result = gSystem->Load(lib_name); -// return (void*)(result == 0 [> success */ || result == 1 /* already loaded <]); -// } -// -// #if defined(_MSC_VER) -// long long cppyy_strtoll(const char* str) { -// return _strtoi64(str, NULL, 0); -// } -// -// extern "C" { -// unsigned long long cppyy_strtoull(const char* str) { -// return _strtoui64(str, NULL, 0); -// } -// } -// #else -// long long cppyy_strtoll(const char* str) { -// return strtoll(str, NULL, 0); -// } -// -// extern "C" { -// unsigned long long cppyy_strtoull(const char* str) { -// return strtoull(str, NULL, 0); -// } -// } -// #endif -// -// void cppyy_free(void* ptr) { -// free(ptr); -// } -// -// cppyy_object_t cppyy_charp2stdstring(const char* str, size_t sz) { -// return (cppyy_object_t)new std::string(str, sz); -// } -// -// const char* cppyy_stdstring2charp(cppyy_object_t ptr, size_t* lsz) { -// *lsz = ((std::string*)ptr)->size(); -// return ((std::string*)ptr)->data(); -// } -// -// cppyy_object_t cppyy_stdstring2stdstring(cppyy_object_t ptr) { -// return (cppyy_object_t)new std::string(*(std::string*)ptr); -// } -// -// double cppyy_longdouble2double(void* p) { -// return (double)*(long double*)p; -// } -// -// void cppyy_double2longdouble(double d, void* p) { -// *(long double*)p = d; -// } -// -// int cppyy_vectorbool_getitem(cppyy_object_t ptr, int idx) { -// return (int)(*(std::vector*)ptr)[idx]; -// } -// -// void cppyy_vectorbool_setitem(cppyy_object_t ptr, int idx, int value) { -// (*(std::vector*)ptr)[idx] = (bool)value; -// } - -} // end C-linkage wrappers diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h index 8d5696d3dc297..a8027ba142889 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -10,12 +10,11 @@ #include #include #include -#include #include "callcontext.h" -// some more types; assumes Cppyy.h follows Python.h +#include "precommondefs.h" -// using CppFinal +// some more types; assumes Cppyy.h follows Python.h #ifndef PY_LONG_LONG #ifdef _WIN32 typedef __int64 PY_LONG_LONG; @@ -57,8 +56,6 @@ namespace Cppyy { typedef intptr_t TCppFuncAddr_t; // // direct interpreter access ------------------------------------------------- - // RPY_EXPORTED - // void AddSearchPath(const char* dir, bool isUser = true, bool prepend = false); RPY_EXPORTED bool Compile(const std::string& code, bool silent = false); RPY_EXPORTED @@ -82,6 +79,10 @@ namespace Cppyy { RPY_EXPORTED std::string ResolveEnum(TCppScope_t enum_scope); RPY_EXPORTED + bool IsLValueReferenceType(TCppType_t type); + RPY_EXPORTED + bool IsRValueReferenceType(TCppType_t type); + RPY_EXPORTED bool IsClassType(TCppType_t type); RPY_EXPORTED bool IsPointerType(TCppType_t type); @@ -301,6 +302,8 @@ namespace Cppyy { RPY_EXPORTED std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); RPY_EXPORTED + std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); + RPY_EXPORTED bool IsConstMethod(TCppMethod_t); // // Templated method/function reflection information ------------------------------------ RPY_EXPORTED @@ -340,6 +343,8 @@ namespace Cppyy { bool IsDestructor(TCppMethod_t method); RPY_EXPORTED bool IsStaticMethod(TCppMethod_t method); + RPY_EXPORTED + bool IsExplicit(TCppMethod_t method); // // data member reflection information ---------------------------------------- // GetNumDatamembers is unused. @@ -407,4 +412,5 @@ namespace Cppyy { void DumpScope(TCppScope_t scope); } // namespace Cppyy -#endif // !CPYCPPYY_CPPYY_H \ No newline at end of file + +#endif // !CPYCPPYY_CPPYY_H From d078e80904e8db93e61e042b1a84aa2545ad498c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Feb 2026 17:11:31 +0100 Subject: [PATCH 06/29] Don't use C++ 20 feature std::map::contains in CPPMethod Resolves build failures seen on CI --- bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index abebaf90dafdc..d22ab5e86d991 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx @@ -354,7 +354,7 @@ CPyCppyy::CPPMethod::CPPMethod( fArgsRequired(-1) { Cppyy::TCppType_t result = Cppyy::ResolveType(Cppyy::GetMethodReturnType(fMethod)); - if (TypeReductionMap.contains(result)) + if (TypeReductionMap.find(result) != TypeReductionMap.end()) fMethod = Cppyy::ReduceReturnType(fMethod, TypeReductionMap[result]); if (result && Cppyy::IsLambdaClass(result)) fMethod = Cppyy::AdaptFunctionForLambdaReturn(fMethod); From 251f6ca43d8bd1b32ea9baf4cbee820d72ce89a5 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Feb 2026 17:22:59 +0100 Subject: [PATCH 07/29] Don't use C++ 20 feature ends_with in CreateExecutor --- bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx index 9e10bd13a3a82..78755e52bd2dd 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx @@ -906,7 +906,7 @@ CPyCppyy::Executor* CPyCppyy::CreateExecutor(Cppyy::TCppType_t type, cdims_t dim // an exactly matching executor is best std::string fullType = Cppyy::GetTypeAsString(type); - if (fullType.ends_with(" &")) + if (fullType.size() >= 2 && fullType.compare(fullType.size() - 2, 2, " &") == 0) fullType = fullType.substr(0, fullType.size() - 2) + "&"; ExecFactories_t::iterator h = gExecFactories.find(fullType); From 70f050f69f67d037f200e2632dabdadd67ac0f12 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Feb 2026 18:01:20 +0100 Subject: [PATCH 08/29] Expose CppGetProcAddress in libCling linker script --- core/metacling/src/libCling.script | 1 + 1 file changed, 1 insertion(+) diff --git a/core/metacling/src/libCling.script b/core/metacling/src/libCling.script index f5bab12f41d82..cc6160a9b248e 100644 --- a/core/metacling/src/libCling.script +++ b/core/metacling/src/libCling.script @@ -8,6 +8,7 @@ ROOT_rootcling_Driver; _ZN5cling*; cling_runtime_internal_throwIfInvalidPointer; + CppGetProcAddress; local: *; }; From 91f7be0b34a499209df7ec416955ebf98a382616 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 13 Feb 2026 12:09:41 +0100 Subject: [PATCH 09/29] fix for gInterpreter and codegen for gInterpreter->Declare --- .../clingwrapper/src/clingwrapper.cxx | 25 ++++++++++++++++--- .../cppyy/cppyy/python/cppyy/__init__.py | 5 ---- .../pythonizations/python/ROOT/_facade.py | 2 +- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 2 -- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index 5f292f584b90e..a99d908c5ec5a 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -836,6 +836,13 @@ Cppyy::TCppScope_t Cppyy::GetUnderlyingScope(TCppScope_t scope) Cppyy::TCppScope_t Cppyy::GetScope(const std::string& name, TCppScope_t parent_scope) { +// CppInterOp directly looks at the AST which is not enough. +// We require lazy module loading that ROOT relies on, so we do it here first. +// Use TClass::GetClass to trigger auto-loading of dictionaries and +// modules. + if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) + TClass::GetClass(name.c_str(), true /* load */, true /* silent */); + if (Cppyy::TCppScope_t scope = Cpp::GetScope(name, parent_scope)) return scope; if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) @@ -933,8 +940,16 @@ Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { std::string mangled_name = typ->name(); std::string demangled_name = Cpp::Demangle(mangled_name); - if (TCppScope_t scope = Cppyy::GetScope(demangled_name)) - return scope; + if (TCppScope_t scope = Cppyy::GetScope(demangled_name)) { + // Only return the derived type if it has a complete definition in the + // interpreter. Internal classes like TCling have no public header and + // no dictionary, so their CXXRecordDecl has no DefinitionData. + // returning them crashes when querying bases/offsets. Fall back + // to the declared base type instead (equivalent to a + // clActual->GetClassInfo() guard). + if (Cpp::IsComplete(scope)) + return scope; + } return klass; } @@ -1474,7 +1489,11 @@ bool Cppyy::GetSmartPtrInfo( // type offsets -------------------------------------------------------------- ptrdiff_t Cppyy::GetBaseOffset(TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror) -{ +{ + // Either base or derived class is incomplete, treat silently + if (!Cpp::IsComplete(derived) || !Cpp::IsComplete(base)) + return rerror ? (ptrdiff_t)-1 : 0; + intptr_t offset = Cpp::GetBaseClassOffset(derived, base); if (offset == -1) // Cling error, treat silently diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py index 0a6b13ad4fedd..0d888c436b0ad 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py @@ -90,11 +90,6 @@ def _set_pch(): _e = gbl.std.exception -#- enable auto-loading ------------------------------------------------------- -try: gbl.cling.runtime.gCling.EnableAutoLoading() -except: pass - - #- external typemap ---------------------------------------------------------- from . import _typemap _typemap.initialize(_backend) # also creates (u)int8_t mapper diff --git a/bindings/pyroot/pythonizations/python/ROOT/_facade.py b/bindings/pyroot/pythonizations/python/ROOT/_facade.py index 9254b1341dc3e..827c7044bbd45 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_facade.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_facade.py @@ -201,7 +201,7 @@ def _finalSetup(self): self.__dict__["gROOT"] = self._cppyy.gbl.ROOT.GetROOT() # Make sure the interpreter is initialized once gROOT has been initialized - self._cppyy.gbl.TInterpreter.Instance() + self.__dict__["gInterpreter"] = self._cppyy.gbl.TInterpreter.Instance() # Setup interactive usage from Python self.__dict__["app"] = PyROOTApplication(self.PyConfig, self._is_ipython) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 85588f0fa14aa..773ea025203d8 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -2283,8 +2283,6 @@ void make_narg_call(const FunctionDecl* FD, const std::string& return_type, else callbuf << "((" << class_name << "*)obj)->"; - if (op_flag) - callbuf << class_name << "::"; } else if (isa(get_non_transparent_decl_context(FD))) { // This is a namespace member. if (op_flag || N <= 1) From 100ab91000150fee06f78cc01d1460d0e04162b3 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 13 Feb 2026 12:12:52 +0100 Subject: [PATCH 10/29] silence erroneous `__typeof__` declarations in Cppyy::GetType These errors are expected but should not be visible. Follow up work is to mimise the associated parsing --- .../cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index a99d908c5ec5a..ffa950037fb43 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -775,7 +775,7 @@ Cppyy::TCppType_t Cppyy::GetType(const std::string &name, bool enable_slow_looku std::string id = "__Cppyy_GetType_" + std::to_string(var_count++); std::string using_clause = "using " + id + " = __typeof__(" + name + ");\n"; - if (!Cpp::Declare(using_clause.c_str(), /*silent=*/false)) { + if (!Cpp::Declare(using_clause.c_str(), /*silent=*/true)) { TCppScope_t lookup = Cpp::GetNamed(id, 0); TCppType_t lookup_ty = Cpp::GetTypeFromScope(lookup); return Cpp::GetCanonicalType(lookup_ty); From 635c3dbf87f80114375126dd90aa1a1219fc88fc Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 18 Feb 2026 14:34:20 +0100 Subject: [PATCH 11/29] CPyCppyy: fix kUseStrict enum in CallContext --- bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h index da15d997ff41b..d1b846513ad5f 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h @@ -69,7 +69,6 @@ struct CallContext { kUseHeuristics = 0x000100, // if method applies heuristics memory policy kImplicitSmartPtrConversion = 0x000200, // enable implicit conversion to smart pointers kReleaseGIL = 0x000400, // if method should release the GIL - kUseStrict = 0x000600, // if method applies strict memory policy kSetLifeLine = 0x000800, // if return value is part of 'this' kNeverLifeLine = 0x001000, // if the return value is never part of 'this' kPyException = 0x002000, // Python exception during method execution @@ -77,6 +76,7 @@ struct CallContext { kProtected = 0x008000, // if method should return on signals kUseFFI = 0x010000, // not implemented kIsPseudoFunc = 0x020000, // internal, used for introspection + kUseStrict = 0x040000, // if method applies strict memory policy }; // memory handling From 0a3e64bfeaf9529ca2307f1ab753c51e890a7eed Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 11 Mar 2026 15:49:59 +0100 Subject: [PATCH 12/29] [cppyy-backend] Syncup with upstream compres-forks --- .../clingwrapper/src/callcontext.h | 3 +- .../clingwrapper/src/clingwrapper.cxx | 253 +++++++++++------- .../clingwrapper/src/cpp_cppyy.h | 8 +- 3 files changed, 154 insertions(+), 110 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h index 9f33daa4271cd..860114b1b0b39 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h @@ -4,8 +4,7 @@ // Standard #include -//ROOT -// #include "Rtypes.h" + namespace CPyCppyy { diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index ffa950037fb43..2d186a278e5bb 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -1,4 +1,4 @@ -#ifndef WIN32 +#ifndef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS // silence warnings about getenv, strncpy, etc. #define _CRT_SECURE_NO_WARNINGS @@ -37,12 +37,12 @@ #include "TSystem.h" #include "TThread.h" -#ifndef WIN32 +#ifndef _WIN32 #include #endif // Standard -#include +#include #include // for std::count, std::remove #include #include @@ -50,17 +50,13 @@ #include #include #include -#include -#include // for getenv -#include +#include +#include // for getenv +#include #include #include #include - - -// temp -#include -// --temp +#include // data for life time management --------------------------------------------- // typedef std::vector ClassRefs_t; @@ -99,6 +95,7 @@ // static GlobalVars_t g_globalvars; // static GlobalVarsIndices_t g_globalidx; +std::recursive_mutex InterOpMutex; // builtin types static std::set g_builtins = @@ -204,7 +201,7 @@ class ApplicationStarter { Cpp::TInterp_t Interp; public: ApplicationStarter() { - + std::lock_guard Lock(InterOpMutex); (void)gROOT; char *libcling = gSystem->DynamicPathName("libCling"); @@ -428,12 +425,14 @@ char* cppstring_to_cstring(const std::string& cppstr) // Returns false on failure and true on success bool Cppyy::Compile(const std::string& code, bool silent) { + std::lock_guard Lock(InterOpMutex); // Declare returns an enum which equals 0 on success return !Cpp::Declare(code.c_str(), silent); } std::string Cppyy::ToString(TCppType_t klass, TCppObject_t obj) { + std::lock_guard Lock(InterOpMutex); if (klass && obj && !Cpp::IsNamespace((TCppScope_t)klass)) return Cpp::ObjToString(Cpp::GetQualifiedCompleteName(klass).c_str(), (void*)obj); @@ -527,6 +526,7 @@ std::string Cppyy::ResolveName(const std::string& name) { // } Cppyy::TCppType_t Cppyy::ResolveEnumReferenceType(TCppType_t type) { +std::lock_guard Lock(InterOpMutex); if (Cpp::GetValueKind(type) != Cpp::ValueKind::LValue) return type; @@ -539,6 +539,7 @@ Cppyy::TCppType_t Cppyy::ResolveEnumReferenceType(TCppType_t type) { } Cppyy::TCppType_t Cppyy::ResolveEnumPointerType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); if (!Cpp::IsPointerType(type)) return type; @@ -566,6 +567,7 @@ Cppyy::TCppType_t int_like_type(Cppyy::TCppType_t type) { Cppyy::TCppType_t Cppyy::ResolveType(TCppType_t type) { if (!type) return type; + std::lock_guard Lock(InterOpMutex); TCppType_t check_int_typedefs = int_like_type(type); if (check_int_typedefs) return type; @@ -573,7 +575,7 @@ Cppyy::TCppType_t Cppyy::ResolveType(TCppType_t type) { Cppyy::TCppType_t canonType = Cpp::GetCanonicalType(type); if (Cpp::IsEnumType(canonType)) { - if (Cppyy::GetTypeAsString(type) != "std::byte") + if (Cpp::GetTypeAsString(type) != "std::byte") return Cpp::GetIntegerTypeFromEnumType(canonType); } if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) { @@ -584,6 +586,7 @@ Cppyy::TCppType_t Cppyy::ResolveType(TCppType_t type) { } Cppyy::TCppType_t Cppyy::GetRealType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); TCppType_t check_int_typedefs = int_like_type(type); if (check_int_typedefs) return check_int_typedefs; @@ -591,10 +594,12 @@ Cppyy::TCppType_t Cppyy::GetRealType(TCppType_t type) { } Cppyy::TCppType_t Cppyy::GetPointerType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetPointerType(type); } Cppyy::TCppType_t Cppyy::GetReferencedType(TCppType_t type, bool rvalue) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetReferencedType(type, rvalue); } @@ -705,15 +710,17 @@ bool Cppyy::AppendTypesSlow(const std::string& name, std::string resolved_name = name; replace_all(resolved_name, "std::initializer_list<", "std::vector<"); // replace initializer_list with vector + std::lock_guard Lock(InterOpMutex); // We might have an entire expression such as int, double. static unsigned long long struct_count = 0; std::string code = "template struct __Cppyy_AppendTypesSlow {};\n"; if (!struct_count) - Cpp::Declare(code.c_str(), /*silent=*/false); // initialize the trampoline + Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline std::string var = "__Cppyy_s" + std::to_string(struct_count++); // FIXME: We cannot use silent because it erases our error code from Declare! if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + resolved_name + "> " + var +";\n").c_str(), /*silent=*/false)) { + std::lock_guard Lock(InterOpMutex); TCppType_t varN = Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr)); TCppScope_t instance_class = Cpp::GetScopeFromType(varN); @@ -737,7 +744,7 @@ bool Cppyy::AppendTypesSlow(const std::string& name, Cppyy::TCppType_t type = nullptr; type = GetType(i, /*enable_slow_lookup=*/true); - if (!type && parent && (Cpp::IsNamespace(parent) || Cpp::IsClass(parent))) { + if (!type && parent && (Cppyy::IsNamespace(parent) || Cppyy::IsClass(parent))) { type = Cppyy::GetTypeFromScope(Cppyy::GetNamed(resolved_name, parent)); } @@ -758,6 +765,7 @@ bool Cppyy::AppendTypesSlow(const std::string& name, } Cppyy::TCppType_t Cppyy::GetType(const std::string &name, bool enable_slow_lookup /* = false */) { + std::lock_guard Lock(InterOpMutex); static unsigned long long var_count = 0; if (auto type = Cpp::GetType(name)) @@ -785,6 +793,7 @@ Cppyy::TCppType_t Cppyy::GetType(const std::string &name, bool enable_slow_looku Cppyy::TCppType_t Cppyy::GetComplexType(const std::string &name) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetComplexType(Cpp::GetType(name)); } @@ -821,6 +830,7 @@ Cppyy::TCppType_t Cppyy::GetComplexType(const std::string &name) { std::string Cppyy::ResolveEnum(TCppScope_t handle) { + std::lock_guard Lock(InterOpMutex); std::string type = Cpp::GetTypeAsString( Cpp::GetIntegerTypeFromEnumScope(handle)); if (type == "signed char") @@ -830,12 +840,14 @@ std::string Cppyy::ResolveEnum(TCppScope_t handle) Cppyy::TCppScope_t Cppyy::GetUnderlyingScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetUnderlyingScope(scope); } Cppyy::TCppScope_t Cppyy::GetScope(const std::string& name, TCppScope_t parent_scope) { + std::lock_guard Lock(InterOpMutex); // CppInterOp directly looks at the AST which is not enough. // We require lazy module loading that ROOT relies on, so we do it here first. // Use TClass::GetClass to trigger auto-loading of dictionaries and @@ -863,10 +875,13 @@ Cppyy::TCppScope_t Cppyy::GetScope(const std::string& name, if (Cppyy::IsTemplate(scope)) { std::vector templ_params; - if (!Cppyy::AppendTypesSlow(params, templ_params)) + InterOpMutex.unlock(); // unlock to allow AppendTypesSlow + if (!Cppyy::AppendTypesSlow(params, templ_params)) { + std::lock_guard Lock(InterOpMutex); return Cpp::InstantiateTemplate(scope, templ_params.data(), templ_params.size(), /*instantiate_body=*/false); + } } } return nullptr; @@ -879,6 +894,7 @@ Cppyy::TCppScope_t Cppyy::GetFullScope(const std::string& name) Cppyy::TCppScope_t Cppyy::GetTypeScope(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetScopeFromType( Cpp::GetVariableType(var)); } @@ -886,26 +902,31 @@ Cppyy::TCppScope_t Cppyy::GetTypeScope(TCppScope_t var) Cppyy::TCppScope_t Cppyy::GetNamed(const std::string& name, TCppScope_t parent_scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetNamed(name, parent_scope); } Cppyy::TCppScope_t Cppyy::GetParentScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetParentScope(scope); } Cppyy::TCppScope_t Cppyy::GetScopeFromType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetScopeFromType(type); } Cppyy::TCppType_t Cppyy::GetTypeFromScope(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeFromScope(klass); } Cppyy::TCppScope_t Cppyy::GetGlobalScope() { + std::lock_guard Lock(InterOpMutex); return Cpp::GetGlobalScope(); } @@ -932,6 +953,8 @@ class AutoCastRTTI { } // namespace Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { + std::lock_guard Lock(InterOpMutex); + if (!Cpp::IsClassPolymorphic(klass)) return klass; @@ -941,12 +964,11 @@ Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { std::string demangled_name = Cpp::Demangle(mangled_name); if (TCppScope_t scope = Cppyy::GetScope(demangled_name)) { - // Only return the derived type if it has a complete definition in the - // interpreter. Internal classes like TCling have no public header and + // Only return the derived type if theres a complete definition in the + // interpreter. internal classes like TCling have no public header and // no dictionary, so their CXXRecordDecl has no DefinitionData. - // returning them crashes when querying bases/offsets. Fall back - // to the declared base type instead (equivalent to a - // clActual->GetClassInfo() guard). + // returning them crashes when querying offsets. Fall back to the base + // type if the derived type is incomplete. if (Cpp::IsComplete(scope)) return scope; } @@ -956,11 +978,13 @@ Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { size_t Cppyy::SizeOf(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); return Cpp::SizeOf(klass); } size_t Cppyy::SizeOfType(TCppType_t klass) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetSizeOfType(klass); } @@ -995,28 +1019,35 @@ bool Cppyy::IsBuiltin(TCppType_t type) bool Cppyy::IsComplete(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::IsComplete(scope); } // // memory management --------------------------------------------------------- Cppyy::TCppObject_t Cppyy::Allocate(TCppScope_t scope) { - return Cpp::Allocate(scope, /*count=*/1); + std::lock_guard Lock(InterOpMutex); + return Cpp::Allocate(scope, /*count=*/1); } void Cppyy::Deallocate(TCppScope_t scope, TCppObject_t instance) { + std::lock_guard Lock(InterOpMutex); Cpp::Deallocate(scope, instance, /*count=*/1); } Cppyy::TCppObject_t Cppyy::Construct(TCppScope_t scope, void* arena/*=nullptr*/) { - return Cpp::Construct(scope, arena, /*count=*/1); + // TODO: this shouldn't locks the JIT call + std::lock_guard Lock(InterOpMutex); + return Cpp::Construct(scope, arena, /*count=*/1); } void Cppyy::Destruct(TCppScope_t scope, TCppObject_t instance) { - Cpp::Destruct(instance, scope, true, /*count=*/0); + // TODO: this shouldn't locks the JIT call + std::lock_guard Lock(InterOpMutex); + Cpp::Destruct(instance, scope, true, /*count=*/0); } static inline @@ -1063,8 +1094,9 @@ bool WrapperCall(Cppyy::TCppMethod_t method, size_t nargs, void* args_, void* se // if (!is_ready(wrap, is_direct)) // return false; // happens with compilation error - + InterOpMutex.lock(); if (Cpp::JitCall JC = Cpp::MakeFunctionCallable(method)) { + InterOpMutex.unlock(); bool runRelease = false; //const auto& fgen = /* is_direct ? faceptr.fDirect : */ faceptr; if (nargs <= SMALL_ARGS_N) { @@ -1084,6 +1116,7 @@ bool WrapperCall(Cppyy::TCppMethod_t method, size_t nargs, void* args_, void* se return true; } + InterOpMutex.unlock(); return false; } @@ -1162,13 +1195,15 @@ Cppyy::TCppObject_t Cppyy::CallConstructor( void Cppyy::CallDestructor(TCppScope_t scope, TCppObject_t self) { - Cpp::Destruct(self, scope, /*withFree=*/false, /*count=*/0); + // TODO: this shouldn't locks the JIT call + std::lock_guard Lock(InterOpMutex); + Cpp::Destruct(self, scope, /*withFree=*/false, /*count=*/0); } Cppyy::TCppObject_t Cppyy::CallO(TCppMethod_t method, TCppObject_t self, size_t nargs, void* args, TCppType_t result_type) { - void* obj = ::operator new(Cpp::GetSizeOfType(result_type)); + void* obj = ::operator new(Cppyy::SizeOfType(result_type)); if (WrapperCall(method, nargs, args, self, obj)) return (TCppObject_t)obj; ::operator delete(obj); @@ -1177,6 +1212,7 @@ Cppyy::TCppObject_t Cppyy::CallO(TCppMethod_t method, Cppyy::TCppFuncAddr_t Cppyy::GetFunctionAddress(TCppMethod_t method, bool check_enabled) { + std::lock_guard Lock(InterOpMutex); return (TCppFuncAddr_t) Cpp::GetFunctionAddress(method); } @@ -1208,6 +1244,7 @@ bool Cppyy::IsNamespace(TCppScope_t scope) { if (!scope) return false; + std::lock_guard Lock(InterOpMutex); // Test if this scope represents a namespace. return Cpp::IsNamespace(scope) || Cpp::GetGlobalScope() == scope; @@ -1232,7 +1269,7 @@ bool Cppyy::IsEnumScope(TCppScope_t scope) bool Cppyy::IsEnumConstant(TCppScope_t scope) { - return Cpp::IsEnumConstant(Cpp::GetUnderlyingScope(scope)); + return Cpp::IsEnumConstant(Cppyy::GetUnderlyingScope(scope)); } bool Cppyy::IsEnumType(TCppType_t type) @@ -1248,6 +1285,7 @@ bool Cppyy::IsAggregate(TCppType_t type) bool Cppyy::IsDefaultConstructable(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); // Test if this type has a default constructor or is a "plain old data" type return Cpp::HasDefaultConstructor(scope); } @@ -1337,6 +1375,7 @@ void Cppyy::GetAllCppNames(TCppScope_t scope, std::set& cppnames) // Collect all known names of C++ entities under scope. This is useful for IDEs // employing tab-completion, for example. Note that functions names need not be // unique as they can be overloaded. + std::lock_guard Lock(InterOpMutex); Cpp::GetAllCppNames(scope, cppnames); } @@ -1344,22 +1383,26 @@ void Cppyy::GetAllCppNames(TCppScope_t scope, std::set& cppnames) // // class reflection information ---------------------------------------------- std::vector Cppyy::GetUsingNamespaces(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetUsingNamespaces(scope); } // // class reflection information ---------------------------------------------- std::string Cppyy::GetFinalName(TCppType_t klass) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetCompleteName(Cpp::GetUnderlyingScope(klass)); } std::string Cppyy::GetScopedFinalName(TCppType_t klass) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetQualifiedCompleteName(klass); } bool Cppyy::HasVirtualDestructor(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); TCppMethod_t func = Cpp::GetDestructor(scope); return Cpp::IsVirtualMethod(func); } @@ -1390,6 +1433,7 @@ bool Cppyy::HasVirtualDestructor(TCppScope_t scope) Cppyy::TCppIndex_t Cppyy::GetNumBases(TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); // Get the total number of base classes that this class has. return Cpp::GetNumBases(klass); } @@ -1426,16 +1470,19 @@ Cppyy::TCppIndex_t Cppyy::GetNumBasesLongestBranch(TCppScope_t klass) { std::string Cppyy::GetBaseName(TCppType_t klass, TCppIndex_t ibase) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetName(Cpp::GetBaseClass(klass, ibase)); } Cppyy::TCppScope_t Cppyy::GetBaseScope(TCppScope_t klass, TCppIndex_t ibase) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetBaseClass(klass, ibase); } bool Cppyy::IsSubclass(TCppScope_t derived, TCppScope_t base) { + std::lock_guard Lock(InterOpMutex); return Cpp::IsSubclass(derived, base); } @@ -1465,13 +1512,16 @@ bool Cppyy::GetSmartPtrInfo( return false; std::vector ops; - Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops, - /*kind=*/Cpp::OperatorArity::kBoth); + { + std::lock_guard Lock(InterOpMutex); + Cpp::GetOperator(scope, Cpp::Operator::OP_Arrow, ops, + /*kind=*/Cpp::OperatorArity::kBoth); + } if (ops.size() != 1) return false; if (deref) *deref = ops[0]; - if (raw) *raw = Cppyy::GetScopeFromType(Cpp::GetFunctionReturnType(ops[0])); + if (raw) *raw = Cppyy::GetScopeFromType(Cppyy::GetMethodReturnType(ops[0])); return (!deref || *deref) && (!raw || *raw); } @@ -1489,7 +1539,8 @@ bool Cppyy::GetSmartPtrInfo( // type offsets -------------------------------------------------------------- ptrdiff_t Cppyy::GetBaseOffset(TCppScope_t derived, TCppScope_t base, TCppObject_t address, int direction, bool rerror) -{ +{ + std::lock_guard Lock(InterOpMutex); // Either base or derived class is incomplete, treat silently if (!Cpp::IsComplete(derived) || !Cpp::IsComplete(base)) return rerror ? (ptrdiff_t)-1 : 0; @@ -1536,12 +1587,14 @@ ptrdiff_t Cppyy::GetBaseOffset(TCppScope_t derived, TCppScope_t base, void Cppyy::GetClassMethods(TCppScope_t scope, std::vector &methods) { + std::lock_guard Lock(InterOpMutex); Cpp::GetClassMethods(scope, methods); } std::vector Cppyy::GetMethodsFromName( TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionsUsingName(scope, name); } @@ -1560,11 +1613,13 @@ std::vector Cppyy::GetMethodsFromName( // std::string Cppyy::GetMethodName(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetName(method); } std::string Cppyy::GetMethodFullName(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetCompleteName(method); } @@ -1577,12 +1632,14 @@ std::string Cppyy::GetMethodFullName(TCppMethod_t method) Cppyy::TCppType_t Cppyy::GetMethodReturnType(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionReturnType(method); } std::string Cppyy::GetMethodReturnTypeAsString(TCppMethod_t method) { - return + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeAsString( Cpp::GetCanonicalType( Cpp::GetFunctionReturnType(method))); @@ -1590,11 +1647,13 @@ std::string Cppyy::GetMethodReturnTypeAsString(TCppMethod_t method) Cppyy::TCppIndex_t Cppyy::GetMethodNumArgs(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionNumArgs(method); } Cppyy::TCppIndex_t Cppyy::GetMethodReqArgs(TCppMethod_t method) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionRequiredArgs(method); } @@ -1602,23 +1661,27 @@ std::string Cppyy::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) { if (!method) return ""; + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionArgName(method, iarg); } Cppyy::TCppType_t Cppyy::GetMethodArgType(TCppMethod_t method, TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionArgType(method, iarg); } std::string Cppyy::GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier( Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const)); } std::string Cppyy::GetMethodArgCanonTypeAsString(TCppMethod_t method, TCppIndex_t iarg) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeAsString( Cpp::GetCanonicalType( @@ -1629,6 +1692,8 @@ std::string Cppyy::GetMethodArgDefault(TCppMethod_t method, TCppIndex_t iarg) { if (!method) return ""; + + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionArgDefault(method, iarg); } @@ -1689,6 +1754,7 @@ std::string Cppyy::GetMethodPrototype(TCppMethod_t method, bool show_formal_args std::string Cppyy::GetDoxygenComment(TCppScope_t scope, bool strip_markers) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetDoxygenComment(scope, strip_markers); } @@ -1696,16 +1762,19 @@ bool Cppyy::IsConstMethod(TCppMethod_t method) { if (!method) return false; + std::lock_guard Lock(InterOpMutex); return Cpp::IsConstMethod(method); } void Cppyy::GetTemplatedMethods(TCppScope_t scope, std::vector &methods) { + std::lock_guard Lock(InterOpMutex); Cpp::GetFunctionTemplatedDecls(scope, methods); } Cppyy::TCppIndex_t Cppyy::GetNumTemplatedMethods(TCppScope_t scope, bool accept_namespace) { + std::lock_guard Lock(InterOpMutex); std::vector mc; Cpp::GetFunctionTemplatedDecls(scope, mc); return mc.size(); @@ -1713,16 +1782,18 @@ Cppyy::TCppIndex_t Cppyy::GetNumTemplatedMethods(TCppScope_t scope, bool accept_ std::string Cppyy::GetTemplatedMethodName(TCppScope_t scope, TCppIndex_t imeth) { + std::lock_guard Lock(InterOpMutex); std::vector mc; Cpp::GetFunctionTemplatedDecls(scope, mc); - if (imeth < mc.size()) return GetMethodName(mc[imeth]); + if (imeth < mc.size()) return Cpp::GetName(mc[imeth]); return ""; } bool Cppyy::ExistsMethodTemplate(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); return Cpp::ExistsFunctionTemplate(name, scope); } @@ -1754,9 +1825,9 @@ Cppyy::TCppMethod_t Cppyy::GetMethodTemplate( pureName = name; } + std::lock_guard Lock(InterOpMutex); std::vector unresolved_candidate_methods; - Cpp::GetClassTemplatedMethods(pureName, scope, - unresolved_candidate_methods); + Cpp::GetClassTemplatedMethods(pureName, scope, unresolved_candidate_methods); if (unresolved_candidate_methods.empty() && name.find("operator") == 0) { // try operators Cppyy::GetClassOperators(scope, pureName, unresolved_candidate_methods); @@ -1768,14 +1839,16 @@ Cppyy::TCppMethod_t Cppyy::GetMethodTemplate( Cppyy::AppendTypesSlow(proto, arg_types, scope); Cppyy::AppendTypesSlow(explicit_params, templ_params, scope); - Cppyy::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( + Cppyy::TCppMethod_t cppmeth = nullptr; + cppmeth = Cpp::BestOverloadFunctionMatch( unresolved_candidate_methods, templ_params, arg_types); if (!cppmeth && unresolved_candidate_methods.size() == 1 && - !templ_params.empty()) + !templ_params.empty()) { cppmeth = Cpp::InstantiateTemplate( unresolved_candidate_methods[0], templ_params.data(), templ_params.size(), /*instantiate_body=*/false); + } return cppmeth; @@ -1796,8 +1869,6 @@ static inline std::string type_remap(const std::string& n1, return "std::basic_string&"; // probably best bet } else if (n1 == "std::basic_string") { return "std::basic_string&"; - } else if (n1 == "float") { - return "double"; // debatable, but probably intended } else if (n1 == "complex") { return "std::complex"; } @@ -1807,6 +1878,7 @@ static inline std::string type_remap(const std::string& n1, void Cppyy::GetClassOperators(Cppyy::TCppScope_t klass, const std::string& opname, std::vector& operators) { + std::lock_guard Lock(InterOpMutex); std::string op = opname.substr(8); Cpp::GetOperator(klass, Cpp::GetOperatorFromSpelling(op), operators, /*kind=*/Cpp::OperatorArity::kBoth); @@ -1817,75 +1889,31 @@ Cppyy::TCppMethod_t Cppyy::GetGlobalOperator( { std::string rc_type = type_remap(rc, lc); std::string lc_type = type_remap(lc, rc); - bool is_templated = false; - if ((lc_type.find('<') != std::string::npos) || - (rc_type.find('<') != std::string::npos)) { - is_templated = true; - } std::vector overloads; Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads, /*kind=*/Cpp::OperatorArity::kBoth); - std::vector unresolved_candidate_methods; - for (auto overload: overloads) { - if (Cpp::IsTemplatedFunction(overload)) { - unresolved_candidate_methods.push_back(overload); - continue; - } else { - TCppType_t lhs_type = Cpp::GetFunctionArgType(overload, 0); - if (lc_type != - Cpp::GetTypeAsString(Cpp::GetUnderlyingType(lhs_type))) - continue; - - if (!rc_type.empty()) { - if (Cpp::GetFunctionNumArgs(overload) != 2) - continue; - TCppType_t rhs_type = Cpp::GetFunctionArgType(overload, 1); - if (rc_type != - Cpp::GetTypeAsString(Cpp::GetUnderlyingType(rhs_type))) - continue; - } - return overload; - } - } - if (is_templated) { - std::string lc_template = lc_type.substr( - lc_type.find("<") + 1, lc_type.rfind(">") - lc_type.find("<") - 1); - std::string rc_template = rc_type.substr( - rc_type.find("<") + 1, rc_type.rfind(">") - rc_type.find("<") - 1); - - std::vector arg_types; - if (auto l = Cppyy::GetType(lc_type, true)) - arg_types.emplace_back(l); + std::vector arg_types; + if (auto l = Cppyy::GetScope(lc_type, 0)) + arg_types.emplace_back(Cppyy::GetReferencedType(Cppyy::GetTypeFromScope(l))); + else if (auto l = Cppyy::GetType(lc_type)) + arg_types.emplace_back(l); + else + return nullptr; + + if (!rc_type.empty()) { + if (auto r = Cppyy::GetScope(rc_type, 0)) + arg_types.emplace_back(Cppyy::GetReferencedType(Cppyy::GetTypeFromScope(r))); + else if (auto r = Cppyy::GetType(rc_type)) + arg_types.emplace_back(r); else return nullptr; - - if (!rc_type.empty()) { - if (auto r = Cppyy::GetType(rc_type, true)) - arg_types.emplace_back(r); - else - return nullptr; - } - Cppyy::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( - unresolved_candidate_methods, {}, arg_types); - if (cppmeth) - return cppmeth; - } - { - // we are trying to do a madeup IntegralToFloating implicit cast emulating clang - bool flag = false; - if (rc_type == "int") { - rc_type = "double"; - flag = true; - } - if (lc_type == "int") { - lc_type = "double"; - flag = true; - } - if (flag) - return GetGlobalOperator(scope, lc_type, rc_type, opname); } + Cppyy::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( + overloads, {}, arg_types); + if (cppmeth) + return cppmeth; return nullptr; } @@ -1949,12 +1977,14 @@ bool Cppyy::IsExplicit(TCppMethod_t method) void Cppyy::GetDatamembers(TCppScope_t scope, std::vector& datamembers) { + std::lock_guard Lock(InterOpMutex); Cpp::GetDatamembers(scope, datamembers); Cpp::GetStaticDatamembers(scope, datamembers); Cpp::GetEnumConstantDatamembers(scope, datamembers, false); } bool Cppyy::CheckDatamember(TCppScope_t scope, const std::string& name) { + std::lock_guard Lock(InterOpMutex); return (bool) Cpp::LookupDatamember(name, scope); } @@ -1963,6 +1993,7 @@ bool Cppyy::IsLambdaClass(TCppType_t type) { } Cppyy::TCppScope_t Cppyy::WrapLambdaFromVariable(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); std::ostringstream code; std::string name = Cppyy::GetFinalName(var); code << "namespace __cppyy_internal_wrap_g {\n" @@ -1978,6 +2009,8 @@ Cppyy::TCppScope_t Cppyy::WrapLambdaFromVariable(TCppScope_t var) { } Cppyy::TCppScope_t Cppyy::AdaptFunctionForLambdaReturn(TCppScope_t fn) { + std::lock_guard Lock(InterOpMutex); + std::string fn_name = Cpp::GetQualifiedCompleteName(fn); std::string signature = Cppyy::GetMethodSignature(fn, true); @@ -2030,22 +2063,26 @@ Cppyy::TCppScope_t Cppyy::AdaptFunctionForLambdaReturn(TCppScope_t fn) { Cppyy::TCppType_t Cppyy::GetDatamemberType(TCppScope_t var) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetVariableType(Cpp::GetUnderlyingScope(var)); } std::string Cppyy::GetDatamemberTypeAsString(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeAsString( Cpp::GetVariableType(Cpp::GetUnderlyingScope(scope))); } std::string Cppyy::GetTypeAsString(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetTypeAsString(type); } intptr_t Cppyy::GetDatamemberOffset(TCppScope_t var, TCppScope_t klass) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetVariableOffset(Cpp::GetUnderlyingScope(var), klass); } @@ -2138,7 +2175,7 @@ bool Cppyy::IsPrivateData(TCppScope_t datamem) bool Cppyy::IsStaticDatamember(TCppScope_t var) { - return Cpp::IsStaticVariable(Cpp::GetUnderlyingScope(var)); + return Cpp::IsStaticVariable(Cppyy::GetUnderlyingScope(var)); } bool Cppyy::IsConstVar(TCppScope_t var) @@ -2147,6 +2184,8 @@ bool Cppyy::IsConstVar(TCppScope_t var) } Cppyy::TCppScope_t Cppyy::ReduceReturnType(TCppScope_t fn, TCppType_t reduce) { + std::lock_guard Lock(InterOpMutex); + std::string fn_name = Cpp::GetQualifiedCompleteName(fn); std::string signature = Cppyy::GetMethodSignature(fn, true); std::string result_type = Cppyy::GetTypeAsString(reduce); @@ -2216,22 +2255,26 @@ Cppyy::TCppScope_t Cppyy::ReduceReturnType(TCppScope_t fn, TCppType_t reduce) { std::vector Cppyy::GetDimensions(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetDimensions(type); } // enum properties ----------------------------------------------------------- std::vector Cppyy::GetEnumConstants(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetEnumConstants(scope); } Cppyy::TCppType_t Cppyy::GetEnumConstantType(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope)); } Cppyy::TCppIndex_t Cppyy::GetEnumDataValue(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); return Cpp::GetEnumConstantValue(scope); } @@ -2249,11 +2292,13 @@ Cppyy::TCppIndex_t Cppyy::GetEnumDataValue(TCppScope_t scope) Cppyy::TCppScope_t Cppyy::InstantiateTemplate( TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size) { - return Cpp::InstantiateTemplate(tmpl, args, args_size, - /*instantiate_body=*/false); + std::lock_guard Lock(InterOpMutex); + return Cpp::InstantiateTemplate(tmpl, args, args_size, + /*instantiate_body=*/false); } void Cppyy::DumpScope(TCppScope_t scope) { + std::lock_guard Lock(InterOpMutex); Cpp::DumpScope(scope); } diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h index a8027ba142889..44260c4d2eeee 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -60,7 +60,7 @@ namespace Cppyy { bool Compile(const std::string& code, bool silent = false); RPY_EXPORTED std::string ToString(TCppType_t klass, TCppObject_t obj); -// + // // name to opaque C++ scope representation ----------------------------------- RPY_EXPORTED std::string ResolveName(const std::string& cppitem_name); @@ -385,8 +385,9 @@ namespace Cppyy { bool IsConstVar(TCppScope_t var); RPY_EXPORTED TCppScope_t ReduceReturnType(TCppScope_t fn, TCppType_t reduce); -// RPY_EXPORTED -// bool IsEnumData(TCppScope_t scope, TCppIndex_t idata); + // IsEnumData is unused. + // RPY_EXPORTED + // bool IsEnumData(TCppScope_t scope, TCppIndex_t idata); RPY_EXPORTED std::vector GetDimensions(TCppType_t type); @@ -412,5 +413,4 @@ namespace Cppyy { void DumpScope(TCppScope_t scope); } // namespace Cppyy - #endif // !CPYCPPYY_CPPYY_H From c422dba02a2a2ba8f52bb0a3831b4f007ad6ff4c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 11 Mar 2026 18:55:12 +0100 Subject: [PATCH 13/29] [CPyCppyy] Fix more tests - Syncup with upstream (compres-forks) --- .../CPyCppyy/include/CPyCppyy/DispatchPtr.h | 9 +- bindings/pyroot/cppyy/CPyCppyy/src/API.cxx | 25 +- .../cppyy/CPyCppyy/src/CPPConstructor.cxx | 12 +- .../pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx | 9 +- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 49 ++- .../pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx | 14 +- .../pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx | 6 +- .../pyroot/cppyy/CPyCppyy/src/CPPOverload.h | 6 +- .../pyroot/cppyy/CPyCppyy/src/CPPScope.cxx | 9 +- bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h | 24 +- .../cppyy/CPyCppyy/src/CPyCppyyModule.cxx | 68 ++-- .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 311 ++++++++++-------- bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 22 +- .../pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx | 7 + .../pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx | 15 +- .../cppyy/CPyCppyy/src/MemoryRegulator.cxx | 20 +- .../cppyy/CPyCppyy/src/ProxyWrappers.cxx | 8 + .../pyroot/cppyy/CPyCppyy/src/PyException.cxx | 26 +- .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 32 +- .../cppyy/CPyCppyy/src/TemplateProxy.cxx | 43 ++- .../cppyy/CPyCppyy/src/TupleOfInstances.cxx | 7 +- .../pyroot/cppyy/CPyCppyy/src/Utility.cxx | 98 ++++-- bindings/pyroot/cppyy/CPyCppyy/src/Utility.h | 26 +- 23 files changed, 530 insertions(+), 316 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h index bd098f6917fa0..1cef6facbb8de 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h +++ b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/DispatchPtr.h @@ -16,9 +16,16 @@ // Bindings #include "CPyCppyy/CommonDefs.h" - +#include namespace CPyCppyy { +class PythonGILRAII { + PyGILState_STATE state; + +public: + PythonGILRAII() : state(PyGILState_Ensure()) {} + ~PythonGILRAII() { PyGILState_Release(state); } +}; class CPYCPPYY_CLASS_EXTERN DispatchPtr { public: diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx index 120f465a64777..eb773c5a297f1 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/API.cxx @@ -7,6 +7,7 @@ #include "CPPInstance.h" #include "CPPOverload.h" #include "CPPScope.h" +#include "CPyCppyy/DispatchPtr.h" #include "ProxyWrappers.h" #include "PyStrings.h" @@ -85,6 +86,7 @@ static bool Initialize() } if (!gMainDict) { + CPyCppyy::PythonGILRAII python_gil_raii; // retrieve the main dictionary gMainDict = PyModule_GetDict( PyImport_AddModule(const_cast("__main__"))); @@ -121,6 +123,8 @@ void* CPyCppyy::Instance_AsVoidPtr(PyObject* pyobject) if (!Initialize()) return nullptr; + PythonGILRAII python_gil_raii; + // check validity of cast if (!CPPInstance_Check(pyobject)) return nullptr; @@ -137,6 +141,8 @@ PyObject* CPyCppyy::Instance_FromVoidPtr( if (!Initialize()) return nullptr; + PythonGILRAII python_gil_raii; + // perform cast (the call will check TClass and addr, and set python errors) PyObject* pyobject = BindCppObjectNoCast(addr, Cppyy::GetScope(classname), false); @@ -155,6 +161,8 @@ PyObject* CPyCppyy::Instance_FromVoidPtr( if (!Initialize()) return nullptr; + PythonGILRAII python_gil_raii; + // perform cast (the call will check TClass and addr, and set python errors) PyObject* pyobject = BindCppObjectNoCast(addr, klass_scope, false); @@ -178,6 +186,7 @@ bool CPyCppyy::Scope_Check(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; return CPPScope_Check(pyobject); } @@ -188,6 +197,7 @@ bool CPyCppyy::Scope_CheckExact(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; return CPPScope_CheckExact(pyobject); } @@ -198,6 +208,7 @@ bool CPyCppyy::Instance_Check(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; // detailed walk through inheritance hierarchy return CPPInstance_Check(pyobject); } @@ -209,6 +220,7 @@ bool CPyCppyy::Instance_CheckExact(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; // direct pointer comparison of type member return CPPInstance_CheckExact(pyobject); } @@ -216,6 +228,7 @@ bool CPyCppyy::Instance_CheckExact(PyObject* pyobject) //----------------------------------------------------------------------------- bool CPyCppyy::Sequence_Check(PyObject* pyobject) { + PythonGILRAII python_gil_raii; // Extends on PySequence_Check() to determine whether an object can be iterated // over (technically, all objects can b/c of C++ pointer arithmetic, hence this // check isn't 100% accurate, but neither is PySequence_Check()). @@ -249,13 +262,14 @@ bool CPyCppyy::Sequence_Check(PyObject* pyobject) //----------------------------------------------------------------------------- bool CPyCppyy::Instance_IsLively(PyObject* pyobject) { + PythonGILRAII python_gil_raii; // Test whether the given instance can safely return to C++ if (!CPPInstance_Check(pyobject)) return true; // simply don't know // the instance fails the lively test if it owns the C++ object while having a // reference count of 1 (meaning: it could delete the C++ instance any moment) - if (pyobject->ob_refcnt <= 1 && (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsOwner)) + if (Py_REFCNT(pyobject) <= 1 && (((CPPInstance*)pyobject)->fFlags & CPPInstance::kIsOwner)) return false; return true; @@ -268,6 +282,7 @@ bool CPyCppyy::Overload_Check(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; // detailed walk through inheritance hierarchy return CPPOverload_Check(pyobject); } @@ -279,6 +294,7 @@ bool CPyCppyy::Overload_CheckExact(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; // direct pointer comparison of type member return CPPOverload_CheckExact(pyobject); } @@ -296,6 +312,8 @@ bool CPyCppyy::Import(const std::string& mod_name) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; + PyObject* mod = PyImport_ImportModule(mod_name.c_str()); if (!mod) { PyErr_Print(); @@ -359,6 +377,8 @@ void CPyCppyy::ExecScript(const std::string& name, const std::vector(cmd.c_str()), Py_file_input, gMainDict, gMainDict); @@ -458,6 +479,7 @@ const CPyCppyy::PyResult CPyCppyy::Eval(const std::string& expr) if (!Initialize()) return PyResult(); + PythonGILRAII python_gil_raii; // evaluate the expression PyObject* result = PyRun_String(const_cast(expr.c_str()), Py_eval_input, gMainDict, gMainDict); @@ -509,6 +531,7 @@ void CPyCppyy::Prompt() { if (!Initialize()) return; + PythonGILRAII python_gil_raii; // enter i/o interactive mode PyRun_InteractiveLoop(stdin, const_cast("\0")); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx index d121364d839ba..5cd03df34bbcd 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx @@ -79,6 +79,16 @@ PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, return nullptr; } + const auto cppScopeFlags = ((CPPScope*)Py_TYPE(self))->fFlags; + +// Do nothing if the constructor is explicit and we are in an implicit +// conversion context. See also the ConvertImplicit() helper in Converters.cxx. + if((cppScopeFlags & CPPScope::kActiveImplicitCall) && Cppyy::IsExplicit(GetMethod())) { + // FIXME: Cases with explicit marked std::complex constructors where we expect implicit conversionss + if (Cppyy::GetMethodSignature(GetMethod(), true).find("std::complex") == std::string::npos) + return nullptr; + } + // self provides the python context for lifelines if (!ctxt->fPyContext) ctxt->fPyContext = (PyObject*)cargs.fSelf; // no Py_INCREF as no ownership @@ -127,7 +137,7 @@ PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, } else { // translate the arguments - if (((CPPClass*)Py_TYPE(self))->fFlags & CPPScope::kNoImplicit) + if (cppScopeFlags & CPPScope::kActiveImplicitCall) ctxt->fFlags |= CallContext::kNoImplicit; if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) return nullptr; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx index 6027db9776e35..ca21f6a5b7161 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx @@ -213,9 +213,12 @@ CPyCppyy::CPPEnum* CPyCppyy::CPPEnum_New(const std::string& name, Cppyy::TCppSco PyObject* pydname = CPyCppyy_PyText_FromString(dname.c_str()); PyObject_SetAttr(pyenum, pydname, val); Py_DECREF(pydname); - PyObject* pydcppname = CPyCppyy_PyText_FromString((ename.empty() ? dname : (ename+"::"+dname)).c_str()); - PyObject_SetAttr(val, PyStrings::gCppName, pydcppname); - Py_DECREF(pydcppname); + if (resolved != "bool") { + // bool is special cased enum look at pyval_from_enum + PyObject* pydcppname = CPyCppyy_PyText_FromString((ename.empty() ? dname : (ename+"::"+dname)).c_str()); + PyObject_SetAttr(val, PyStrings::gCppName, pydcppname); + Py_DECREF(pydcppname); + } Py_DECREF(val); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index d22ab5e86d991..d0d5d53e2902f 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx @@ -278,13 +278,15 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) // Helper to report errors in a consistent format (derefs msg). // // Handles three cases: -// 1. No Python error occurred yet: +// 1. No Python error occured yet: // Set a new TypeError with the message "msg" and the docstring of this // C++ method to give some context. -// 2. A C++ exception has occurred: +// 2. A C++ exception has occured: // Augment the exception message with the docstring of this method -// 3. A Python exception has occurred: +// 3. A Python exception has occured with a traceback: // Do nothing, Python exceptions are already informative enough +// 4. If the Python exception has no traceback hinting to an internally set error stack, +// extract its message and wrap it with C++ method docstring context. #if PY_VERSION_HEX >= 0x030c0000 PyObject *evalue = PyErr_Occurred() ? PyErr_GetRaisedException() : nullptr; @@ -294,22 +296,38 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) PyObject *evalue = nullptr; PyObject *etrace = nullptr; -if (PyErr_Occurred()) { + if (PyErr_Occurred()) { PyErr_Fetch(&etype, &evalue, &etrace); } #endif const bool isCppExc = evalue && PyType_IsSubtype((PyTypeObject*)etype, &CPPExcInstance_Type); - // If the error is not a CPPExcInstance, the error from Python itself is - // already complete and messing with it would only make it less informative. + std::string details; + + // If the error is not a CPPExcInstance and has a traceback, the error from + // Python itself is already complete and messing with it would only make it + // less informative. // Just restore and return. if (evalue && !isCppExc) { #if PY_VERSION_HEX >= 0x030c0000 - PyErr_SetRaisedException(evalue); + PyObject* tb = PyException_GetTraceback(evalue); + if (tb) { + Py_DECREF(tb); + PyErr_SetRaisedException(evalue); + return; + } #else - PyErr_Restore(etype, evalue, etrace); + if (etrace) { + PyErr_Restore(etype, evalue, etrace); + return; + } #endif - return; + // no traceback, extract its message and fall through + PyObject* descr = PyObject_Str(evalue); + if (descr) { + details = CPyCppyy_PyText_AsString(descr); + Py_DECREF(descr); + } } PyObject* doc = GetDocString(); @@ -320,9 +338,15 @@ if (PyErr_Occurred()) { const char* cname = pyname ? CPyCppyy_PyText_AsString(pyname) : "Exception"; if (!isCppExc) { - // this is the case where no Python error has occurred yet, and we set a new - // error with context info - PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, cmsg ? cmsg : ""); + // this is the case where no Python error has occured yet, or an internal + // one without traceback set a new error with context + if (details.empty()) { + PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, cmsg ? cmsg : ""); + } else if (cmsg) { + PyErr_Format(errtype, "%s =>\n %s: %s (%s)", cdoc, cname, cmsg, details.c_str()); + } else { + PyErr_Format(errtype, "%s =>\n %s: %s", cdoc, cname, details.c_str()); + } } else { // augment the top message with context information PyObject *&topMessage = ((CPPExcInstance*)evalue)->fTopMessage; @@ -1067,7 +1091,6 @@ PyObject* CPyCppyy::CPPMethod::GetSignature(bool fa) return CPyCppyy_PyText_FromString(GetSignatureString(fa).c_str()); } - /** * @brief Returns a tuple with the names of the input parameters of this method. * diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx index 9aacc3dd50528..4832136d74cd2 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOperator.cxx @@ -2,6 +2,7 @@ #include "CPyCppyy.h" #include "CPPOperator.h" #include "CPPInstance.h" +#include "Utility.h" //- constructor -------------------------------------------------------------- @@ -49,17 +50,14 @@ PyObject* CPyCppyy::CPPOperator::Call(CPPInstance*& self, return result; } - PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); +// fetch the current error, resetting the error buffer + auto error = CPyCppyy::Utility::FetchPyError(); result = fStub((PyObject*)self, CPyCppyy_PyArgs_GET_ITEM(args, idx_other)); - if (!result) - PyErr_Restore(pytype, pyvalue, pytrace); - else { - Py_XDECREF(pytype); - Py_XDECREF(pyvalue); - Py_XDECREF(pytrace); +// if there was still a problem, restore the Python error buffer + if (!result) { + CPyCppyy::Utility::RestorePyError(error); } return result; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx index 62625b2b8b78a..649c1143fc0ed 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx @@ -291,7 +291,6 @@ static int mp_doc_set(CPPOverload* pymeth, PyObject *val, void *) return 0; } - /** * @brief Returns a dictionary with the input parameter names for all overloads. * @@ -769,9 +768,6 @@ static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds) } } - // clear collected errors - if (!errors.empty()) - std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear); return HandleReturn(pymeth, im_self, result); } @@ -815,7 +811,7 @@ static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds) // first summarize, then add details PyObject* topmsg = CPyCppyy_PyText_FromFormat( "none of the %d overloaded methods succeeded. Full details:", (int)nMethods); - SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */); + SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); // report failure return nullptr; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h index b392bf4ed7f20..8bd3b2de234a9 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.h @@ -25,7 +25,11 @@ inline uint64_t HashSignature(CPyCppyy_PyArgs_t args, size_t nargsf) // improved overloads for implicit conversions PyObject* pyobj = CPyCppyy_PyArgs_GET_ITEM(args, i); hash += (uint64_t)Py_TYPE(pyobj); - hash += (uint64_t)(pyobj->ob_refcnt == 1 ? 1 : 0); +#if PY_VERSION_HEX >= 0x030e0000 + hash += (uint64_t)(PyUnstable_Object_IsUniqueReferencedTemporary(pyobj) ? 1 : 0); +#else + hash += (uint64_t)(Py_REFCNT(pyobj) == 1 ? 1 : 0); +#endif hash += (hash << 10); hash ^= (hash >> 6); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx index 8ae79432ede0b..af4074515d07d 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx @@ -468,6 +468,8 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) PyType_Type.tp_setattro(pyclass, llname, pyuscope); Py_DECREF(llname); Py_DECREF(pyuscope); + } else { + PyErr_Clear(); } } } @@ -500,7 +502,6 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) } if (attr) { - std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear); PyErr_Clear(); } else { // not found: prepare a full error report @@ -517,7 +518,7 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) topmsg = CPyCppyy_PyText_FromFormat("no such attribute \'%s\'. Full details:", CPyCppyy_PyText_AsString(pyname)); } - SetDetailedException(errors, topmsg /* steals */, PyExc_AttributeError /* default error */); + SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_AttributeError /* default error */); } return attr; @@ -532,7 +533,9 @@ static int meta_setattro(PyObject* pyclass, PyObject* pyname, PyObject* pyval) // for such data as necessary. The many checks to narrow down the specific case // are needed to prevent unnecessary lookups and recursion. // skip if the given pyval is a descriptor already, or an unassignable class - if (!CPyCppyy::CPPDataMember_Check(pyval) && !CPyCppyy::CPPScope_Check(pyval)) { + if (((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace + && !CPyCppyy::CPPDataMember_Check(pyval) + && !CPyCppyy::CPPScope_Check(pyval)) { std::string name = CPyCppyy_PyText_AsString(pyname); if (Cppyy::GetNamed(name, ((CPPScope*)pyclass)->fCppType)) meta_getattro(pyclass, pyname); // triggers creation diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h index 6aecfdca05b9c..370224ab69de4 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h @@ -37,18 +37,18 @@ namespace Utility { struct PyOperators; } class CPPScope { public: enum EFlags { - kNone = 0x0, - kIsMeta = 0x0001, - kIsNamespace = 0x0002, - kIsException = 0x0004, - kIsSmart = 0x0008, - kIsPython = 0x0010, - kIsMultiCross = 0x0020, - kIsInComplete = 0x0040, - kNoImplicit = 0x0080, - kNoOSInsertion = 0x0100, - kGblOSInsertion = 0x0200, - kNoPrettyPrint = 0x0400 }; + kNone = 0x0, + kIsMeta = 0x0001, + kIsNamespace = 0x0002, + kIsException = 0x0004, + kIsSmart = 0x0008, + kIsPython = 0x0010, + kIsMultiCross = 0x0020, + kIsInComplete = 0x0040, + kActiveImplicitCall = 0x0080, + kNoOSInsertion = 0x0100, + kGblOSInsertion = 0x0200, + kNoPrettyPrint = 0x0400 }; public: PyHeapTypeObject fType; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx index 4feedb5dc171f..4f4409eb6d3c2 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx @@ -224,23 +224,13 @@ static PyTypeObject PyDefault_t_Type = { namespace { -PyObject _CPyCppyy_NullPtrStruct = {_PyObject_EXTRA_INIT -// In 3.12.0-beta this field was changed from a ssize_t to a union -#if PY_VERSION_HEX >= 0x30c00b1 - {1}, -#else - 1, -#endif - &PyNullPtr_t_Type}; +struct { + PyObject_HEAD +} _CPyCppyy_NullPtrStruct{PyObject_HEAD_INIT(&PyNullPtr_t_Type)}; -PyObject _CPyCppyy_DefaultStruct = {_PyObject_EXTRA_INIT -// In 3.12.0-beta this field was changed from a ssize_t to a union -#if PY_VERSION_HEX >= 0x30c00b1 - {1}, -#else - 1, -#endif - &PyDefault_t_Type}; +struct { + PyObject_HEAD +} _CPyCppyy_DefaultStruct{PyObject_HEAD_INIT(&PyDefault_t_Type)}; // TODO: refactor with Converters.cxx struct CPyCppyy_tagCDataObject { // non-public (but stable) @@ -585,6 +575,12 @@ static PyObject* addressof(PyObject* /* dummy */, PyObject* args, PyObject* kwds return PyLong_FromLongLong((intptr_t)caddr); } + // LowLevelViews + if (LowLevelView_CheckExact(arg0)) { + auto *llv = (LowLevelView*)arg0; + return PyLong_FromLongLong((intptr_t)llv->get_buf()); + } + // final attempt: any type of buffer Utility::GetBuffer(arg0, '*', 1, addr, false); if (addr) return PyLong_FromLongLong((intptr_t)addr); @@ -807,7 +803,7 @@ static PyObject* BindObject(PyObject*, PyObject* args, PyObject* kwds) // not a pre-existing object; get the address and bind void* addr = nullptr; - if (arg0 != &_CPyCppyy_NullPtrStruct) { + if (arg0 != gNullPtrObject) { addr = CPyCppyy_PyCapsule_GetPointer(arg0, nullptr); if (PyErr_Occurred()) { PyErr_Clear(); @@ -1081,10 +1077,10 @@ static struct PyModuleDef moduledef = { cpycppyymodule_clear, nullptr }; -#endif -#define CPYCPPYY_INIT_ERROR return nullptr +#endif + namespace CPyCppyy { //---------------------------------------------------------------------------- @@ -1123,7 +1119,7 @@ PyObject* Init() gThisModule = Py_InitModule(const_cast("libcppyy"), gCPyCppyyMethods); #endif if (!gThisModule) - CPYCPPYY_INIT_ERROR; + return nullptr; // keep gThisModule, but do not increase its reference count even as it is borrowed, // or a self-referencing cycle would be created @@ -1137,58 +1133,58 @@ PyObject* Init() // inject meta type if (!Utility::InitProxy(gThisModule, &CPPScope_Type, "CPPScope")) - CPYCPPYY_INIT_ERROR; + return nullptr; // inject object proxy type if (!Utility::InitProxy(gThisModule, &CPPInstance_Type, "CPPInstance")) - CPYCPPYY_INIT_ERROR; + return nullptr; // inject exception object proxy type if (!Utility::InitProxy(gThisModule, &CPPExcInstance_Type, "CPPExcInstance")) - CPYCPPYY_INIT_ERROR; + return nullptr; // inject method proxy type if (!Utility::InitProxy(gThisModule, &CPPOverload_Type, "CPPOverload")) - CPYCPPYY_INIT_ERROR; + return nullptr; // inject template proxy type if (!Utility::InitProxy(gThisModule, &TemplateProxy_Type, "TemplateProxy")) - CPYCPPYY_INIT_ERROR; + return nullptr; // inject property proxy type if (!Utility::InitProxy(gThisModule, &CPPDataMember_Type, "CPPDataMember")) - CPYCPPYY_INIT_ERROR; + return nullptr; // inject custom data types #if PY_VERSION_HEX < 0x03000000 if (!Utility::InitProxy(gThisModule, &RefFloat_Type, "Double")) - CPYCPPYY_INIT_ERROR; + return nullptr; if (!Utility::InitProxy(gThisModule, &RefInt_Type, "Long")) - CPYCPPYY_INIT_ERROR; + return nullptr; #endif if (!Utility::InitProxy(gThisModule, &CustomInstanceMethod_Type, "InstanceMethod")) - CPYCPPYY_INIT_ERROR; + return nullptr; if (!Utility::InitProxy(gThisModule, &TupleOfInstances_Type, "InstanceArray")) - CPYCPPYY_INIT_ERROR; + return nullptr; if (!Utility::InitProxy(gThisModule, &LowLevelView_Type, "LowLevelView")) - CPYCPPYY_INIT_ERROR; + return nullptr; if (!Utility::InitProxy(gThisModule, &PyNullPtr_t_Type, "nullptr_t")) - CPYCPPYY_INIT_ERROR; + return nullptr; // custom iterators if (PyType_Ready(&InstanceArrayIter_Type) < 0) - CPYCPPYY_INIT_ERROR; + return nullptr; if (PyType_Ready(&IndexIter_Type) < 0) - CPYCPPYY_INIT_ERROR; + return nullptr; if (PyType_Ready(&VectorIter_Type) < 0) - CPYCPPYY_INIT_ERROR; + return nullptr; // inject identifiable nullptr and default gNullPtrObject = (PyObject*)&_CPyCppyy_NullPtrStruct; @@ -1223,10 +1219,8 @@ PyObject* Init() // create the memory regulator static MemoryRegulator s_memory_regulator; -#if PY_VERSION_HEX >= 0x03000000 Py_INCREF(gThisModule); return gThisModule; -#endif } } // namespace CPyCppyy diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index c8063b9f28533..340b2f9d30496 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -61,6 +61,9 @@ namespace CPyCppyy { static std::regex s_fnptr("\\((\\w*:*)*\\*&*\\)"); } +// Define our own PyUnstable_Object_IsUniqueReferencedTemporary function if the +// Python version is lower than 3.14, the version where that function got introduced. +#if PY_VERSION_HEX < 0x030e0000 #if PY_VERSION_HEX < 0x03000000 const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; #elif PY_VERSION_HEX < 0x03080000 @@ -73,6 +76,10 @@ const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 2; // since py3.8, vector calls behave again as expected const Py_ssize_t MOVE_REFCOUNT_CUTOFF = 1; #endif +inline bool PyUnstable_Object_IsUniqueReferencedTemporary(PyObject *pyobject) { + return Py_REFCNT(pyobject) <= MOVE_REFCOUNT_CUTOFF; +} +#endif //- pretend-ctypes helpers --------------------------------------------------- struct CPyCppyy_tagCDataObject { // non-public (but stable) @@ -86,11 +93,24 @@ struct CPyCppyy_tagPyCArgObject { // not public (but stable; note that olde void* pffi_type; char tag; union { // for convenience, kept only relevant vals + char c; + char b; + short h; + int i; + long l; long long q; - long double D; + long double g; void *p; +#if PY_VERSION_HEX >= 0x030e0000 + double D[2]; + float F[2]; + long double G[2]; +#endif } value; PyObject* obj; +#if PY_VERSION_HEX >= 0x030e0000 + Py_ssize_t size; +#endif }; // indices of ctypes types into the array caches (note that c_complex and c_fcomplex @@ -482,14 +502,14 @@ static inline CPyCppyy::CPPInstance* ConvertImplicit(Cppyy::TCppType_t klass, PyObject* args = PyTuple_New(1); Py_INCREF(pyobject); PyTuple_SET_ITEM(args, 0, pyobject); - ((CPPScope*)pyscope)->fFlags |= CPPScope::kNoImplicit; + ((CPPScope*)pyscope)->fFlags |= CPPScope::kActiveImplicitCall; CPPInstance* pytmp = (CPPInstance*)PyObject_Call(pyscope, args, NULL); if (!pytmp && PyTuple_CheckExact(pyobject)) { // special case: allow implicit conversion from given set of arguments in tuple PyErr_Clear(); pytmp = (CPPInstance*)PyObject_Call(pyscope, pyobject, NULL); } - ((CPPScope*)pyscope)->fFlags &= ~CPPScope::kNoImplicit; + ((CPPScope*)pyscope)->fFlags &= ~CPPScope::kActiveImplicitCall; Py_DECREF(args); Py_DECREF(pyscope); @@ -537,10 +557,9 @@ bool CPyCppyy::Converter::ToMemory(PyObject*, void*, PyObject* /* ctxt */) if (val == (type)-1 && PyErr_Occurred()) { \ static PyTypeObject* ctypes_type = nullptr; \ if (!ctypes_type) { \ - PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; \ - PyErr_Fetch(&pytype, &pyvalue, &pytrace); \ + auto error = CPyCppyy::Utility::FetchPyError(); \ ctypes_type = GetCTypesType(ct_##ctype); \ - PyErr_Restore(pytype, pyvalue, pytrace); \ + CPyCppyy::Utility::RestorePyError(error); \ } \ if (Py_TYPE(pyobject) == ctypes_type) { \ PyErr_Clear(); \ @@ -1260,16 +1279,14 @@ bool CPyCppyy::CStringConverter::SetArg( const char* cstr = CPyCppyy_PyText_AsStringAndSize(pyobject, &len); if (!cstr) { // special case: allow ctypes c_char_p - PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); + auto error = CPyCppyy::Utility::FetchPyError(); if (Py_TYPE(pyobject) == GetCTypesType(ct_c_char_p)) { SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr; para.fTypeCode = 'V'; - Py_XDECREF(pytype); Py_XDECREF(pyvalue); Py_XDECREF(pytrace); return true; } - PyErr_Restore(pytype, pyvalue, pytrace); + CPyCppyy::Utility::RestorePyError(error); return false; } @@ -1622,119 +1639,131 @@ bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyOb //---------------------------------------------------------------------------- -#define CPPYY_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ -CPyCppyy::name##ArrayConverter::name##ArrayConverter(cdims_t dims) : \ - fShape(dims) { \ - fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; \ -} \ - \ -bool CPyCppyy::name##ArrayConverter::SetArg( \ - PyObject* pyobject, Parameter& para, CallContext* ctxt) \ -{ \ - /* filter ctypes first b/c their buffer conversion will be wrong */ \ - bool convOk = false; \ - \ - /* 2-dim case: ptr-ptr types */ \ - if (fShape.ndim() == 2) { \ - if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (Py_TYPE(pyobject) == GetCTypesType(ct_c_void_p)) { \ - /* special case: pass address of c_void_p buffer to return the address */\ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (LowLevelView_Check(pyobject) && \ - ((LowLevelView*)pyobject)->fBufInfo.ndim == 2 && \ - strchr(((LowLevelView*)pyobject)->fBufInfo.format, code)) { \ - para.fValue.fVoidp = ((LowLevelView*)pyobject)->get_buf(); \ - para.fTypeCode = 'p'; \ - convOk = true; \ - } \ - } \ - \ - /* 1-dim (accept pointer), or unknown (accept pointer as cast) */ \ - if (!convOk) { \ - PyTypeObject* ctypes_type = GetCTypesType(ct_##ctype); \ - if (Py_TYPE(pyobject) == ctypes_type) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } else if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)pyobject)->b_ptr;\ - para.fTypeCode = 'V'; \ - convOk = true; \ - } else if (IsPyCArgObject(pyobject)) { \ - CPyCppyy_tagPyCArgObject* carg = (CPyCppyy_tagPyCArgObject*)pyobject;\ - if (carg->obj && Py_TYPE(carg->obj) == ctypes_type) { \ - para.fValue.fVoidp = (void*)((CPyCppyy_tagCDataObject*)carg->obj)->b_ptr;\ - para.fTypeCode = 'p'; \ - convOk = true; \ - } \ - } \ - } \ - \ - /* cast pointer type */ \ - if (!convOk) { \ - bool ismulti = fShape.ndim() > 1; \ - convOk = CArraySetArg(pyobject, para, code, ismulti ? sizeof(void*) : sizeof(type), true);\ - } \ - \ - /* memory management and offsetting */ \ - if (convOk) SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); \ - \ - return convOk; \ -} \ - \ -PyObject* CPyCppyy::name##ArrayConverter::FromMemory(void* address) \ -{ \ - if (!fIsFixed) \ - return CreateLowLevelView##suffix((type**)address, fShape); \ - return CreateLowLevelView##suffix(*(type**)address, fShape); \ -} \ - \ -bool CPyCppyy::name##ArrayConverter::ToMemory( \ - PyObject* value, void* address, PyObject* ctxt) \ -{ \ - if (fShape.ndim() <= 1 || fIsFixed) { \ - void* buf = nullptr; \ - Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(type), buf);\ - if (buflen == 0) \ - return false; \ - \ - Py_ssize_t oldsz = 1; \ - for (Py_ssize_t idim = 0; idim < fShape.ndim(); ++idim) { \ - if (fShape[idim] == UNKNOWN_SIZE) { \ - oldsz = -1; \ - break; \ - } \ - oldsz *= fShape[idim]; \ - } \ - if (fShape.ndim() != UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { \ - PyErr_SetString(PyExc_ValueError, "buffer too large for value"); \ - return false; \ - } \ - \ - if (fIsFixed) \ - memcpy(*(type**)address, buf, (0 < buflen ? buflen : 1)*sizeof(type));\ - else { \ - *(type**)address = (type*)buf; \ - fShape.ndim(1); \ - fShape[0] = buflen; \ - SetLifeLine(ctxt, value, (intptr_t)address); \ - } \ - \ - } else { /* multi-dim, non-flat array; assume structure matches */ \ - void* buf = nullptr; /* TODO: GetBuffer() assumes flat? */ \ - Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(void*), buf);\ - if (buflen == 0) return false; \ - *(type**)address = (type*)buf; \ - SetLifeLine(ctxt, value, (intptr_t)address); \ - } \ - return true; \ -} - +#define CPPYY_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ + CPyCppyy::name##ArrayConverter::name##ArrayConverter(cdims_t dims) \ + : fShape(dims) { \ + fIsFixed = dims ? fShape[0] != UNKNOWN_SIZE : false; \ + } \ + \ + bool CPyCppyy::name##ArrayConverter::SetArg( \ + PyObject *pyobject, Parameter ¶, CallContext *ctxt) { \ + /* filter ctypes first b/c their buffer conversion will be wrong */ \ + bool convOk = false; \ + \ + /* 2-dim case: ptr-ptr types */ \ + if (!convOk && fShape.ndim() == 2) { \ + if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ + para.fValue.fVoidp = \ + (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (Py_TYPE(pyobject) == GetCTypesType(ct_c_void_p)) { \ + /* special case: pass address of c_void_p buffer to return the address \ + */ \ + para.fValue.fVoidp = \ + (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (LowLevelView_Check(pyobject) && \ + ((LowLevelView *)pyobject)->fBufInfo.ndim == 2 && \ + strchr(((LowLevelView *)pyobject)->fBufInfo.format, code)) { \ + para.fValue.fVoidp = ((LowLevelView *)pyobject)->get_buf(); \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } \ + \ + /* 1-dim (accept pointer), or unknown (accept pointer as cast) */ \ + if (!convOk) { \ + PyTypeObject *ctypes_type = GetCTypesType(ct_##ctype); \ + if (Py_TYPE(pyobject) == ctypes_type) { \ + para.fValue.fVoidp = \ + (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } else if (Py_TYPE(pyobject) == GetCTypesPtrType(ct_##ctype)) { \ + para.fValue.fVoidp = \ + (void *)((CPyCppyy_tagCDataObject *)pyobject)->b_ptr; \ + para.fTypeCode = 'V'; \ + convOk = true; \ + } else if (IsPyCArgObject(pyobject)) { \ + CPyCppyy_tagPyCArgObject *carg = (CPyCppyy_tagPyCArgObject *)pyobject; \ + if (carg->obj && Py_TYPE(carg->obj) == ctypes_type) { \ + para.fValue.fVoidp = \ + (void *)((CPyCppyy_tagCDataObject *)carg->obj)->b_ptr; \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } else if (LowLevelView_Check(pyobject) && \ + strchr(((LowLevelView *)pyobject)->fBufInfo.format, code)) { \ + para.fValue.fVoidp = ((LowLevelView *)pyobject)->get_buf(); \ + para.fTypeCode = 'p'; \ + convOk = true; \ + } \ + } \ + \ + /* cast pointer type */ \ + if (!convOk) { \ + bool ismulti = fShape.ndim() > 1; \ + convOk = CArraySetArg(pyobject, para, code, \ + ismulti ? sizeof(void *) : sizeof(type), true); \ + } \ + \ + /* memory management and offsetting */ \ + if (convOk) \ + SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); \ + \ + return convOk; \ + } \ + \ + PyObject *CPyCppyy::name##ArrayConverter::FromMemory(void *address) { \ + if (!fIsFixed) \ + return CreateLowLevelView##suffix((type **)address, fShape); \ + return CreateLowLevelView##suffix(*(type **)address, fShape); \ + } \ + \ + bool CPyCppyy::name##ArrayConverter::ToMemory( \ + PyObject *value, void *address, PyObject *ctxt) { \ + if (fShape.ndim() <= 1 || fIsFixed) { \ + void *buf = nullptr; \ + Py_ssize_t buflen = Utility::GetBuffer(value, code, sizeof(type), buf); \ + if (buflen == 0) \ + return false; \ + \ + Py_ssize_t oldsz = 1; \ + for (Py_ssize_t idim = 0; idim < fShape.ndim(); ++idim) { \ + if (fShape[idim] == UNKNOWN_SIZE) { \ + oldsz = -1; \ + break; \ + } \ + oldsz *= fShape[idim]; \ + } \ + if (fShape.ndim() != UNKNOWN_SIZE && 0 < oldsz && oldsz < buflen) { \ + PyErr_SetString(PyExc_ValueError, "buffer too large for value"); \ + return false; \ + } \ + \ + if (fIsFixed) \ + memcpy(*(type **)address, buf, \ + (0 < buflen ? buflen : 1) * sizeof(type)); \ + else { \ + *(type **)address = (type *)buf; \ + fShape.ndim(1); \ + fShape[0] = buflen; \ + SetLifeLine(ctxt, value, (intptr_t)address); \ + } \ + \ + } else { /* multi-dim, non-flat array; assume structure matches */ \ + void *buf = nullptr; /* TODO: GetBuffer() assumes flat? */ \ + Py_ssize_t buflen = \ + Utility::GetBuffer(value, code, sizeof(void *), buf); \ + if (buflen == 0) \ + return false; \ + *(type **)address = (type *)buf; \ + SetLifeLine(ctxt, value, (intptr_t)address); \ + } \ + return true; \ + } //---------------------------------------------------------------------------- CPPYY_IMPL_ARRAY_CONVERTER(Bool, c_bool, bool, '?', ) @@ -1976,7 +2005,6 @@ bool CPyCppyy::STLWStringConverter::ToMemory(PyObject* value, void* address, PyO return InstanceConverter::ToMemory(value, address, ctxt); } - CPyCppyy::STLStringViewConverter::STLStringViewConverter(bool keepControl) : InstanceConverter(Cppyy::GetFullScope("std::string_view"), keepControl) {} @@ -2068,7 +2096,7 @@ bool CPyCppyy::STLStringMoveConverter::SetArg( if (pyobj->fFlags & CPPInstance::kIsRValue) { pyobj->fFlags &= ~CPPInstance::kIsRValue; moveit_reason = 2; - } else if (pyobject->ob_refcnt <= MOVE_REFCOUNT_CUTOFF) { + } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { moveit_reason = 1; } else moveit_reason = 0; @@ -2308,7 +2336,7 @@ bool CPyCppyy::InstanceMoveConverter::SetArg( if (pyobj->fFlags & CPPInstance::kIsRValue) { pyobj->fFlags &= ~CPPInstance::kIsRValue; moveit_reason = 2; - } else if (pyobject->ob_refcnt <= MOVE_REFCOUNT_CUTOFF) { + } else if (PyUnstable_Object_IsUniqueReferencedTemporary(pyobject)) { moveit_reason = 1; } @@ -2411,6 +2439,12 @@ bool CPyCppyy::InstanceArrayConverter::SetArg( PyObject* pyobject, Parameter& para, CallContext* /* txt */) { // convert to C++ instance**, set arg for call + while (PyTuple_Check(pyobject) && !TupleOfInstances_CheckExact(pyobject)) { + if (PyTuple_Size(pyobject) > 0) + pyobject = PyTuple_GetItem(pyobject, 0); + else + return false; + } if (!TupleOfInstances_CheckExact(pyobject)) return false; // no guarantee that the tuple is okay @@ -2628,7 +2662,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, // find the overload with matching signature for (auto& m : ol->fMethodInfo->fMethods) { PyObject* sig = m->GetSignature(false); - bool found = signature == CPyCppyy_PyText_AsString(sig); + bool found = true_signature == CPyCppyy_PyText_AsString(sig); Py_DECREF(sig); if (found) { void* fptr = (void*)m->GetFunctionAddress(); @@ -2636,6 +2670,9 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, break; // fall-through, with calling through Python } } + // FIXME: maybe we should try BestOverloadFunctionMatch before failing + // FIXME: Should we fall-through, with calling through Python + return nullptr; } if (TemplateProxy_Check(pyobject)) { @@ -2650,7 +2687,8 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, void* fptr = (void*)Cppyy::GetFunctionAddress(cppmeth, false); if (fptr) return fptr; } - // fall-through, with calling through Python + // FIXME: Should we fall-through, with calling through Python + return nullptr; } if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) { @@ -2659,7 +2697,6 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, return fptr; } - if (PyCallable_Check(pyobject) && (allowCppInstance || !CPPInstance_Check(pyobject))) { // generic python callable: create a C++ wrapper function // Sometimes we don't want to take this branch if the object is a C++ @@ -3105,7 +3142,7 @@ bool CPyCppyy::InitializerListConverter::SetArg( // need not be a C++ object memloc = (void*)Cppyy::Construct(fValueType, memloc); // We checked above that we are able to construct default objects of fValueType. - assert(memloc); + assert(memloc && ("failed to default construct object for type " + fValueTypeName).c_str()); entries += 1; } if (memloc) { @@ -3823,6 +3860,20 @@ static struct InitConvFactories_t { gf["std::string_view&"] = gf["std::string_view"]; gf["const std::string_view&"] = gf["std::string_view"]; gf["const " STRINGVIEW "&"] = gf["std::string_view"]; + + // compres forks, we may need these without the std namespace for the migration to work + gf["std::basic_string"] = (cf_t)+[](cdims_t) { return new STLStringConverter{}; }; + gf["const std::basic_string&"] = gf["std::basic_string"]; + gf["std::basic_string&&"] = (cf_t)+[](cdims_t) { return new STLStringMoveConverter{}; }; + gf["const std::basic_string &"] = gf["std::basic_string"]; + gf["std::basic_string &&"] = (cf_t)+[](cdims_t) { return new STLStringMoveConverter{}; }; + gf["std::basic_string_view"] = (cf_t)+[](cdims_t) { return new STLStringViewConverter{}; }; + gf[STRINGVIEW] = gf["std::basic_string_view"]; + gf["std::basic_string_view&"] = gf["std::basic_string_view"]; + gf["const " STRINGVIEW "&"] = gf["std::basic_string_view"]; + gf["std::basic_string_view &"] = gf["std::basic_string_view"]; + gf["const " STRINGVIEW " &"] = gf["std::basic_string_view"]; + gf["std::wstring"] = (cf_t)+[](cdims_t) { return new STLWStringConverter{}; }; gf[WSTRING1] = gf["std::wstring"]; gf[WSTRING2] = gf["std::wstring"]; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h index 12612639050f4..d5336474c692f 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h @@ -56,14 +56,14 @@ namespace Cpp = CppImpl; namespace Cppyy { - typedef void* TCppScope_t; + typedef void* TCppScope_t; typedef TCppScope_t TCppType_t; - typedef void* TCppEnum_t; - typedef void* TCppObject_t; - typedef void* TCppMethod_t; + typedef void* TCppEnum_t; + typedef void* TCppObject_t; + typedef void* TCppMethod_t; - typedef size_t TCppIndex_t; - typedef void* TCppFuncAddr_t; + typedef size_t TCppIndex_t; + typedef void* TCppFuncAddr_t; // direct interpreter access ------------------------------------------------- CPPYY_IMPORT @@ -126,8 +126,8 @@ namespace Cppyy { CPPYY_IMPORT bool IsPointerType(TCppType_t type); - // CPPYY_IMPORT - // TCppScope_t gGlobalScope; // for fast access + CPPYY_IMPORT + TCppScope_t gGlobalScope; // for fast access // memory management --------------------------------------------------------- CPPYY_IMPORT @@ -326,6 +326,8 @@ namespace Cppyy { bool IsDestructor(TCppMethod_t method); CPPYY_IMPORT bool IsStaticMethod(TCppMethod_t method); + CPPYY_IMPORT + bool IsExplicit(TCppMethod_t method); // data member reflection information ---------------------------------------- CPPYY_IMPORT @@ -344,6 +346,10 @@ namespace Cppyy { CPPYY_IMPORT std::string GetTypeAsString(TCppType_t type); CPPYY_IMPORT + bool IsRValueReferenceType(TCppType_t type); + CPPYY_IMPORT + bool IsLValueReferenceType(TCppType_t type); + CPPYY_IMPORT bool IsClassType(TCppType_t type); CPPYY_IMPORT bool IsFunctionPointerType(TCppType_t type); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx index 5affdd2120317..da1682d2ba9f0 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx @@ -10,6 +10,7 @@ //----------------------------------------------------------------------------- PyObject* CPyCppyy::DispatchPtr::Get(bool borrowed) const { + PythonGILRAII python_gil_raii; if (fPyHardRef) { if (!borrowed) Py_INCREF(fPyHardRef); return fPyHardRef; @@ -27,6 +28,7 @@ PyObject* CPyCppyy::DispatchPtr::Get(bool borrowed) const //----------------------------------------------------------------------------- CPyCppyy::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) : fPyHardRef(nullptr) { + PythonGILRAII python_gil_raii; if (strong) { Py_INCREF(pyobj); fPyHardRef = pyobj; @@ -41,6 +43,7 @@ CPyCppyy::DispatchPtr::DispatchPtr(PyObject* pyobj, bool strong) : fPyHardRef(nu //----------------------------------------------------------------------------- CPyCppyy::DispatchPtr::DispatchPtr(const DispatchPtr& other, void* cppinst) : fPyWeakRef(nullptr) { + PythonGILRAII python_gil_raii; PyObject* pyobj = other.Get(false /* not borrowed */); fPyHardRef = pyobj ? (PyObject*)((CPPInstance*)pyobj)->Copy(cppinst) : nullptr; if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); @@ -53,6 +56,7 @@ CPyCppyy::DispatchPtr::~DispatchPtr() { // of a dispatcher intermediate, then this delete is from the C++ side, and Python // is "notified" by nulling out the reference and an exception will be raised on // continued access + PythonGILRAII python_gil_raii; if (fPyWeakRef) { PyObject* pyobj = CPyCppyy_GetWeakRef(fPyWeakRef); if (pyobj && ((CPPScope*)Py_TYPE(pyobj))->fFlags & CPPScope::kIsPython) @@ -68,6 +72,7 @@ CPyCppyy::DispatchPtr::~DispatchPtr() { //----------------------------------------------------------------------------- CPyCppyy::DispatchPtr& CPyCppyy::DispatchPtr::assign(const DispatchPtr& other, void* cppinst) { + PythonGILRAII python_gil_raii; if (this != &other) { Py_XDECREF(fPyWeakRef); fPyWeakRef = nullptr; Py_XDECREF(fPyHardRef); @@ -82,6 +87,7 @@ CPyCppyy::DispatchPtr& CPyCppyy::DispatchPtr::assign(const DispatchPtr& other, v //----------------------------------------------------------------------------- void CPyCppyy::DispatchPtr::PythonOwns() { + PythonGILRAII python_gil_raii; // Python maintains the hardref, so only allowed a weakref here if (fPyHardRef) { fPyWeakRef = PyWeakref_NewRef(fPyHardRef, nullptr); @@ -92,6 +98,7 @@ void CPyCppyy::DispatchPtr::PythonOwns() //----------------------------------------------------------------------------- void CPyCppyy::DispatchPtr::CppOwns() { + PythonGILRAII python_gil_raii; // C++ maintains the hardref, keeping the PyObject alive w/o outstanding ref if (fPyWeakRef) { fPyHardRef = CPyCppyy_GetWeakRef(fPyWeakRef); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx index ace5b87b73e73..b4c67542e6e68 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx @@ -39,13 +39,15 @@ static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& m // program shutdown); note that this means that the actual result will be the default // and the caller may need to act on that, but that's still an improvement over a // possible crash - code << " PyObject* iself = (PyObject*)_internal_self;\n" + code << " CPyCppyy::PythonGILRAII python_gil_raii;\n" // acquire GIL + " PyObject* iself = (PyObject*)_internal_self;\n" " if (!iself || iself == Py_None) {\n" - " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on deleted python-side proxy\");\n" + " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on " + "deleted python-side proxy\");\n" " return"; if (retType != "void") { if (retType.back() != '*') - code << " " << CPyCppyy::TypeManip::remove_const(retType) << "{}"; + code << " (" << CPyCppyy::TypeManip::remove_const(retType) << "){}"; else code << " nullptr"; } @@ -239,6 +241,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // object goes before the C++ one, only __del__ is called) if (PyMapping_HasKeyString(dct, (char*)"__destruct__")) { code << " virtual ~" << derivedName << "() {\n" + " CPyCppyy::PythonGILRAII python_gil_raii;\n" " PyObject* iself = (PyObject*)_internal_self;\n" " if (!iself || iself == Py_None)\n" " return;\n" // safe, as destructor always returns void @@ -321,7 +324,10 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, code << "{\n return " << binfo.bname << "::" << mtCppName << "("; for (Cppyy::TCppIndex_t i = 0; i < nArgs; ++i) { if (i != 0) code << ", "; - code << "arg" << i; + if (Cppyy::IsRValueReferenceType(Cppyy::GetMethodArgType(method, i))) + code << "std::move(arg" << i << ")"; + else + code << "arg" << i; } code << ");\n }\n"; } @@ -453,6 +459,7 @@ bool CPyCppyy::InsertDispatcher(CPPScope* klass, PyObject* bases, PyObject* dct, // provide an accessor to re-initialize after round-tripping from C++ (internal) code << "\n static PyObject* _get_dispatch(" << derivedName << "* inst) {\n" + " CPyCppyy::PythonGILRAII python_gil_raii;\n" " PyObject* res = (PyObject*)inst->_internal_self;\n" " Py_XINCREF(res); return res;\n }"; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx index 8103c1cb1357a..52d8261bc1fda 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/MemoryRegulator.cxx @@ -35,13 +35,25 @@ static PyMappingMethods CPyCppyy_NoneType_mapping = { //----------------------------------------------------------------------------- namespace { +// Py_SET_REFCNT was only introduced in Python 3.9 +#if PY_VERSION_HEX < 0x03090000 +inline void Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) { + assert(refcnt >= 0); +#if SIZEOF_VOID_P > 4 + ob->ob_refcnt = (PY_UINT32_T)refcnt; +#else + ob->ob_refcnt = refcnt; +#endif +} +#endif + struct InitCPyCppyy_NoneType_t { InitCPyCppyy_NoneType_t() { // create a CPyCppyy NoneType (for references that went dodo) from NoneType memset(&CPyCppyy_NoneType, 0, sizeof(CPyCppyy_NoneType)); ((PyObject&)CPyCppyy_NoneType).ob_type = &PyType_Type; - ((PyObject&)CPyCppyy_NoneType).ob_refcnt = 1; + Py_SET_REFCNT((PyObject*)&CPyCppyy_NoneType, 1); ((PyVarObject&)CPyCppyy_NoneType).ob_size = 0; CPyCppyy_NoneType.tp_name = const_cast("CPyCppyy_NoneType"); @@ -147,10 +159,10 @@ bool CPyCppyy::MemoryRegulator::RecursiveRemove( } // notify any other weak referents by playing dead - Py_ssize_t refcnt = ((PyObject*)pyobj)->ob_refcnt; - ((PyObject*)pyobj)->ob_refcnt = 0; + Py_ssize_t refcnt = Py_REFCNT((PyObject*)pyobj); + Py_SET_REFCNT((PyObject*)pyobj, 0); PyObject_ClearWeakRefs((PyObject*)pyobj); - ((PyObject*)pyobj)->ob_refcnt = refcnt; + Py_SET_REFCNT((PyObject*)pyobj, refcnt); // cleanup object internals pyobj->CppOwns(); // held object is out of scope now anyway diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx index 4e809d94e3769..33b6441d50989 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx @@ -97,6 +97,14 @@ void AddPropertyToClass(PyObject* pyclass, // allow access at the instance level PyType_Type.tp_setattro(pyclass, pname, (PyObject*)property); + if (PyErr_Occurred()) { + // CPPDataMember.tp_descr_set raises an error + // when all of the following conditions are met + // 1. data is static const + // 2. scope is a derived class + // 3. data is defined in both parent and base class (using parent::attribute;) + PyErr_Clear(); + } // allow access at the class level (always add after setting instance level) if (Cppyy::IsStaticDatamember(data) || Cppyy::IsEnumConstant(data)) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx index 2929683fe0667..df4b664edfc37 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx @@ -4,6 +4,7 @@ // Bindings #include "CPyCppyy.h" #define CPYCPPYY_INTERNAL 1 +#include "CPyCppyy/DispatchPtr.h" #include "CPyCppyy/PyException.h" #undef CPYCPPYY_INTERNAL @@ -25,11 +26,20 @@ CPyCppyy::PyException::PyException() { #ifdef WITH_THREAD - PyGILState_STATE state = PyGILState_Ensure(); + PythonGILRAII python_gil_raii; #endif +#if PY_VERSION_HEX >= 0x030c0000 + PyObject *pyvalue = PyErr_GetRaisedException(); + PyObject *pytype = pyvalue ? (PyObject *)Py_TYPE(pyvalue) : nullptr; + PyObject* traceback = pyvalue ? PyException_GetTraceback(pyvalue) : nullptr; +#else PyObject* pytype = nullptr, *pyvalue = nullptr, *pytrace = nullptr; PyErr_Fetch(&pytype, &pyvalue, &pytrace); + PyObject* traceback = pytrace; // to keep the original unchanged + Py_XINCREF(traceback); +#endif + if (pytype && pyvalue) { const char* tname = PyExceptionClass_Name(pytype); if (tname) { @@ -46,9 +56,6 @@ CPyCppyy::PyException::PyException() } } - PyObject* traceback = pytrace; // to keep the original unchanged - Py_XINCREF(traceback); - std::string locName; std::string locFile; int locLine = 0; @@ -88,7 +95,11 @@ CPyCppyy::PyException::PyException() Py_XDECREF(traceback); +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(pyvalue); +#else PyErr_Restore(pytype, pyvalue, pytrace); +#endif if (fMsg.empty()) fMsg = "python exception"; @@ -105,10 +116,6 @@ CPyCppyy::PyException::PyException() fMsg += ")"; } - -#ifdef WITH_THREAD - PyGILState_Release(state); -#endif } CPyCppyy::PyException::~PyException() noexcept @@ -126,6 +133,9 @@ const char* CPyCppyy::PyException::what() const noexcept void CPyCppyy::PyException::clear() const noexcept { +#ifdef WITH_THREAD + PythonGILRAII python_gil_raii; +#endif // clear Python error, to allow full error handling C++ side PyErr_Clear(); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index cb5720131e323..6177723de7eae 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -651,9 +651,15 @@ static PyObject* vector_iter(PyObject* v) { // tell the iterator code to set a life line if this container is a temporary vi->vi_flags = vectoriterobject::kDefault; - if (v->ob_refcnt <= 2 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) +#if PY_VERSION_HEX >= 0x030e0000 + if (PyUnstable_Object_IsUniqueReferencedTemporary(v) || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) +#else + if (Py_REFCNT(v) <= 1 || (((CPPInstance*)v)->fFlags & CPPInstance::kIsValue)) +#endif vi->vi_flags = vectoriterobject::kNeedLifeLine; + Py_INCREF(v); + PyObject* pyvalue_type = PyObject_GetAttr((PyObject*)Py_TYPE(v), PyStrings::gValueTypePtr); if (pyvalue_type) { PyObject* pyvalue_size = GetAttrDirect((PyObject*)Py_TYPE(v), PyStrings::gValueSize); @@ -1223,15 +1229,15 @@ static int PyObject_Compare(PyObject* one, PyObject* other) { } #endif static inline -PyObject* CPyCppyy_PyString_FromCppString(std::string* s, bool native=true) { +PyObject* CPyCppyy_PyString_FromCppString(std::string_view s, bool native=true) { if (native) - return PyBytes_FromStringAndSize(s->data(), s->size()); - return CPyCppyy_PyText_FromStringAndSize(s->data(), s->size()); + return PyBytes_FromStringAndSize(s.data(), s.size()); + return CPyCppyy_PyText_FromStringAndSize(s.data(), s.size()); } static inline -PyObject* CPyCppyy_PyString_FromCppString(std::wstring* s, bool native=true) { - PyObject* pyobj = PyUnicode_FromWideChar(s->data(), s->size()); +PyObject* CPyCppyy_PyString_FromCppString(std::wstring_view s, bool native=true) { + PyObject* pyobj = PyUnicode_FromWideChar(s.data(), s.size()); if (pyobj && native) { PyObject* pybytes = PyUnicode_AsEncodedString(pyobj, "UTF-8", "strict"); Py_DECREF(pyobj); @@ -1246,7 +1252,7 @@ PyObject* name##StringGetData(PyObject* self, bool native=true) \ { \ if (CPyCppyy::CPPInstance_Check(self)) { \ type* obj = ((type*)((CPPInstance*)self)->GetObject()); \ - if (obj) return CPyCppyy_PyString_FromCppString(obj, native); \ + if (obj) return CPyCppyy_PyString_FromCppString(*obj, native); \ } \ PyErr_Format(PyExc_TypeError, "object mismatch (%s expected)", #type); \ return nullptr; \ @@ -1323,6 +1329,7 @@ PyObject* name##StringCompare(PyObject* self, PyObject* obj) \ CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string, STL) CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::wstring, STLW) +CPPYY_IMPL_STRING_PYTHONIZATION_CMP(std::string_view, STLView) static inline std::string* GetSTLString(CPPInstance* self) { if (!CPPInstance_Check(self)) { @@ -2003,7 +2010,6 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) // constructor that takes python associative collections Utility::AddToClass(pyclass, "__real_init", "__init__"); Utility::AddToClass(pyclass, "__init__", (PyCFunction)SetInit, METH_VARARGS | METH_KEYWORDS); - #if __cplusplus <= 202002L // From C++20, std::set already implements a contains() method. Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLContainsWithFind, METH_O); @@ -2036,7 +2042,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLStringIsEqual, METH_O); Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLStringIsNotEqual, METH_O); #if __cplusplus <= 202302L - // From C++23, std::sting already implements a contains() method. + // From C++23, std::string already implements a contains() method. Utility::AddToClass(pyclass, "__contains__", (PyCFunction)STLStringContains, METH_O); #endif Utility::AddToClass(pyclass, "decode", (PyCFunction)STLStringDecode, METH_VARARGS | METH_KEYWORDS); @@ -2054,7 +2060,13 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) else if (name == "std::basic_string_view") { Utility::AddToClass(pyclass, "__real_init", "__init__"); - Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, METH_VARARGS | METH_KEYWORDS); + Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLViewStringBytes, METH_NOARGS); + Utility::AddToClass(pyclass, "__cmp__", (PyCFunction)STLViewStringCompare, METH_O); + Utility::AddToClass(pyclass, "__eq__", (PyCFunction)STLViewStringIsEqual, METH_O); + Utility::AddToClass(pyclass, "__ne__", (PyCFunction)STLViewStringIsNotEqual, METH_O); + Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLViewStringRepr, METH_NOARGS); + Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, METH_NOARGS); } // The first condition was already present in upstream CPyCppyy. The other two diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx index 5fbb01d392eaa..40cb72017803e 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx @@ -16,6 +16,12 @@ namespace CPyCppyy { +static inline std::string targs2str(TemplateProxy* pytmpl) +{ + if (!pytmpl || !pytmpl->fTemplateArgs) return ""; + return CPyCppyy_PyText_AsString(pytmpl->fTemplateArgs); +} + //---------------------------------------------------------------------------- TemplateInfo::TemplateInfo() : fPyClass(nullptr), fNonTemplated(nullptr), fTemplated(nullptr), fLowPriority(nullptr), fDoc(nullptr) @@ -93,8 +99,14 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname, bool bArgSet = false; // special case for arrays - PyObject* pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode); - if (pytc) { + if (TemplateProxy_CheckExact(itemi)) { + TemplateProxy *tn = (TemplateProxy*)itemi; + PyObject *f = PyUnicode_FromFormat("%s%s", tn->fTI->fCppName.c_str(), targs2str(tn).c_str()); + PyTuple_SET_ITEM(tpArgs, i, f); + bArgSet = true; + } + PyObject* pytc; + if (!bArgSet && (pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode))) { Py_buffer bufinfo; memset(&bufinfo, 0, sizeof(Py_buffer)); std::string ptrdef; @@ -424,16 +436,9 @@ static int tpp_doc_set(TemplateProxy* pytmpl, PyObject *val, void *) //= CPyCppyy template proxy callable behavior ================================ #define TPPCALL_RETURN \ -{ if (!errors.empty()) \ - std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear);\ +{ errors.clear(); \ return result; } -static inline std::string targs2str(TemplateProxy* pytmpl) -{ - if (!pytmpl || !pytmpl->fTemplateArgs) return ""; - return CPyCppyy_PyText_AsString(pytmpl->fTemplateArgs); -} - static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs, uint64_t sighash, CPPOverload* pymeth) { // Memoize a method in the dispatch map after successful call; replace old if need be (may be @@ -597,7 +602,7 @@ static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) // attempt call if found (this may fail if there are specializations) if (CPPOverload_Check(pymeth)) { // since the template args are fully explicit, allow implicit conversion of arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash); + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, sighash); if (result) { Py_DECREF(pyfullname); TPPCALL_RETURN; @@ -620,7 +625,7 @@ static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) CPyCppyy_PyText_AsString(pyfullname), args, nargsf, Utility::kNone); if (pymeth) { // attempt actual call; same as above, allow implicit conversion of arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash); + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, sighash); if (result) { Py_DECREF(pyfullname); TPPCALL_RETURN; @@ -632,7 +637,7 @@ static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) PyObject* topmsg = CPyCppyy_PyText_FromFormat( "Could not find \"%s\" (set cppyy.set_debug() for C++ errors):", CPyCppyy_PyText_AsString(pyfullname)); Py_DECREF(pyfullname); - Utility::SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */); + Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); return nullptr; } @@ -657,7 +662,7 @@ static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) pymeth = pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt); if (pymeth) { // attempt actual call; even if argument based, allow implicit conversions, for example for non-template arguments - result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash); + result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, /*impOK=*/true, sighash); if (result) TPPCALL_RETURN; } Utility::FetchError(errors); @@ -673,7 +678,7 @@ static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds) // error reporting is fraud, given the numerous steps taken, but more details seems better if (!errors.empty()) { PyObject* topmsg = CPyCppyy_PyText_FromString("Template method resolution failed:"); - Utility::SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */); + Utility::SetDetailedException(std::move(errors), topmsg /* steals */, PyExc_TypeError /* default error */); } else { PyErr_Format(PyExc_TypeError, "cannot resolve method template call for \'%s\'", pytmpl->fTI->fCppName.c_str()); @@ -850,17 +855,11 @@ static PyObject* tpp_overload(TemplateProxy* pytmpl, PyObject* args) } // else attempt instantiation - PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); - if (!cppmeth) { - PyErr_Restore(pytype, pyvalue, pytrace); return nullptr; } - Py_XDECREF(pytype); - Py_XDECREF(pyvalue); - Py_XDECREF(pytrace); + PyErr_Clear(); // TODO: the next step should be consolidated with Instantiate() PyCallable* meth = nullptr; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx index 6ef09a240d8d0..622bc5d16231b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx @@ -146,8 +146,11 @@ PyObject* TupleOfInstances_New( return (PyObject*)ia; } else if (1 < dims.ndim()) { // not the innermost dimension, descend one level - size_t block_size = 0; - for (Py_ssize_t i = 1; i < dims.ndim(); ++i) block_size += (size_t)dims[i]; + size_t block_size = 1; + for (Py_ssize_t i = 1; i < dims.ndim(); ++i) { + if (dims[i] != 0) + block_size *= (size_t)dims[i]; + } block_size *= Cppyy::SizeOf(klass); Py_ssize_t nelems = dims[0]; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx index 31e79e8694549..0f8c84c3626b2 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.cxx @@ -4,6 +4,7 @@ #include "CPPFunction.h" #include "CPPInstance.h" #include "CPPOverload.h" +#include "CPyCppyy/DispatchPtr.h" #include "ProxyWrappers.h" #include "PyCallable.h" #include "PyStrings.h" @@ -561,9 +562,10 @@ static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, PyErr_Clear(); // ctypes function pointer - PyObject* argtypes = PyObject_GetAttrString(arg, "argtypes"); - PyObject* ret = PyObject_GetAttrString(arg, "restype"); - if (argtypes && ret) { + PyObject* argtypes = nullptr; + PyObject* ret = nullptr; + if ((argtypes = PyObject_GetAttrString(arg, "argtypes")) + && (ret = PyObject_GetAttrString(arg, "restype"))) { std::ostringstream tpn; PyObject* pytc = PyObject_GetAttr(ret, PyStrings::gCTypesType); tpn << CT2CppNameS(pytc, false) @@ -662,6 +664,8 @@ std::string CPyCppyy::Utility::ConstructTemplateArgs( // __cpp_name__ and/or __name__ is rather expensive) } else { if (!AddTypeName(tmpl_name, tn, (args ? PyTuple_GET_ITEM(args, i) : nullptr), pref, pcnt)) { + PyErr_SetString(PyExc_SyntaxError, + "could not construct C++ name from provided template argument."); return ""; } } @@ -1003,9 +1007,6 @@ void CPyCppyy::Utility::ConstructCallbackPreamble(const std::string& retType, if (!isVoid) code << " " << retType << " ret{};\n"; -// acquire GIL - code << " PyGILState_STATE state = PyGILState_Ensure();\n"; - // build argument tuple if needed if (nArgs) { code << " std::vector pyargs;\n"; @@ -1019,7 +1020,7 @@ void CPyCppyy::Utility::ConstructCallbackPreamble(const std::string& retType, } code << " } catch(int) {\n" << " for (auto pyarg : pyargs) Py_XDECREF(pyarg);\n" - << " CPyCppyy::PyException pyexc; PyGILState_Release(state); throw pyexc;\n" + << " CPyCppyy::PyException pyexc; throw pyexc;\n" << " }\n"; } } @@ -1050,9 +1051,8 @@ void CPyCppyy::Utility::ConstructCallbackReturn(const std::string& retType, int #ifdef _WIN32 " /* do nothing */ }\n" #else - " CPyCppyy::PyException pyexc; PyGILState_Release(state); throw pyexc; }\n" + " CPyCppyy::PyException pyexc; throw pyexc; }\n" #endif - " PyGILState_Release(state);\n" " return"; code << (isVoid ? ";\n }\n" : " ret;\n }\n"); } @@ -1182,6 +1182,11 @@ Py_ssize_t CPyCppyy::Utility::GetBuffer(PyObject* pyobject, char tc, int size, v if (PyObject_CheckBuffer(pyobject)) { if (PySequence_Check(pyobject) && !PySequence_Size(pyobject)) return 0; // PyObject_GetBuffer() crashes on some platforms for some zero-sized seqeunces + if (PyErr_Occurred()) { + // PySequence_Size errored with + // TypeError: object of type 'LP_c_type' has no len() + PyErr_Clear(); + } Py_buffer bufinfo; memset(&bufinfo, 0, sizeof(Py_buffer)); @@ -1268,15 +1273,14 @@ Py_ssize_t CPyCppyy::Utility::GetBuffer(PyObject* pyobject, char tc, int size, v buf = 0; // not compatible // clarify error message - PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0; - PyErr_Fetch(&pytype, &pyvalue, &pytrace); + auto error = FetchPyError(); PyObject* pyvalue2 = CPyCppyy_PyText_FromFormat( (char*)"%s and given element size (%ld) do not match needed (%d)", - CPyCppyy_PyText_AsString(pyvalue), + CPyCppyy_PyText_AsString(error.fValue.get()), seqmeths->sq_length ? (long)(buflen/(*(seqmeths->sq_length))(pyobject)) : (long)buflen, size); - Py_DECREF(pyvalue); - PyErr_Restore(pytype, pyvalue2, pytrace); + error.fValue.reset(pyvalue2); + RestorePyError(error); } } @@ -1414,9 +1418,8 @@ PyObject* CPyCppyy::Utility::PyErr_Occurred_WithGIL() // released; note that the p2.2 code assumes that there are no callbacks in // C++ to python (or at least none returning errors). #if PY_VERSION_HEX >= 0x02030000 - PyGILState_STATE gstate = PyGILState_Ensure(); + PythonGILRAII python_gil_raii; PyObject* e = PyErr_Occurred(); - PyGILState_Release(gstate); #else if (PyThreadState_GET()) return PyErr_Occurred(); @@ -1427,20 +1430,50 @@ PyObject* CPyCppyy::Utility::PyErr_Occurred_WithGIL() } +//---------------------------------------------------------------------------- +CPyCppyy::Utility::PyError_t CPyCppyy::Utility::FetchPyError() +{ + // create a PyError_t RAII object that will capture and store the exception data + CPyCppyy::Utility::PyError_t error{}; +#if PY_VERSION_HEX >= 0x030c0000 + error.fValue.reset(PyErr_GetRaisedException()); +#else + PyObject *pytype = nullptr; + PyObject *pyvalue = nullptr; + PyObject *pytrace = nullptr; + PyErr_Fetch(&pytype, &pyvalue, &pytrace); + error.fType.reset(pytype); + error.fValue.reset(pyvalue); + error.fTrace.reset(pytrace); +#endif + return error; +} + + +//---------------------------------------------------------------------------- +void CPyCppyy::Utility::RestorePyError(CPyCppyy::Utility::PyError_t &error) +{ +#if PY_VERSION_HEX >= 0x030c0000 + PyErr_SetRaisedException(error.fValue.release()); +#else + PyErr_Restore(error.fType.release(), error.fValue.release(), error.fTrace.release()); +#endif +} + + //---------------------------------------------------------------------------- size_t CPyCppyy::Utility::FetchError(std::vector& errors, bool is_cpp) { // Fetch the current python error, if any, and store it for future use. if (PyErr_Occurred()) { - PyError_t e{is_cpp}; - PyErr_Fetch(&e.fType, &e.fValue, &e.fTrace); - errors.push_back(e); + errors.emplace_back(FetchPyError()); + errors.back().fIsCpp = is_cpp; } return errors.size(); } //---------------------------------------------------------------------------- -void CPyCppyy::Utility::SetDetailedException(std::vector& errors, PyObject* topmsg, PyObject* defexc) +void CPyCppyy::Utility::SetDetailedException(std::vector&& errors, PyObject* topmsg, PyObject* defexc) { // Use the collected exceptions to build up a detailed error log. if (errors.empty()) { @@ -1471,14 +1504,18 @@ void CPyCppyy::Utility::SetDetailedException(std::vector& errors, PyO // bind the original C++ object, rather than constructing from topmsg, as it // is expected to have informative state - Py_INCREF(unique_from_cpp->fType); Py_INCREF(unique_from_cpp->fValue); Py_XINCREF(unique_from_cpp->fTrace); - PyErr_Restore(unique_from_cpp->fType, unique_from_cpp->fValue, unique_from_cpp->fTrace); + RestorePyError(*unique_from_cpp); } else { // try to consolidate Python exceptions, otherwise select default PyObject* exc_type = nullptr; for (auto& e : errors) { - if (!exc_type) exc_type = e.fType; - else if (exc_type != e.fType) { +#if PY_VERSION_HEX >= 0x030c0000 + PyObject* pytype = (PyObject*)Py_TYPE(e.fValue.get()); +#else + PyObject* pytype = e.fType.get(); +#endif + if (!exc_type) exc_type = pytype; + else if (exc_type != pytype) { exc_type = defexc; break; } @@ -1487,14 +1524,15 @@ void CPyCppyy::Utility::SetDetailedException(std::vector& errors, PyO // add the details to the topmsg PyObject* separator = CPyCppyy_PyText_FromString("\n "); for (auto& e : errors) { + PyObject *pyvalue = e.fValue.get(); CPyCppyy_PyText_Append(&topmsg, separator); - if (CPyCppyy_PyText_Check(e.fValue)) { - CPyCppyy_PyText_Append(&topmsg, e.fValue); - } else if (e.fValue) { - PyObject* excstr = PyObject_Str(e.fValue); + if (CPyCppyy_PyText_Check(pyvalue)) { + CPyCppyy_PyText_Append(&topmsg, pyvalue); + } else if (pyvalue) { + PyObject* excstr = PyObject_Str(pyvalue); if (!excstr) { PyErr_Clear(); - excstr = PyObject_Str((PyObject*)Py_TYPE(e.fValue)); + excstr = PyObject_Str((PyObject*)Py_TYPE(pyvalue)); } CPyCppyy_PyText_AppendAndDel(&topmsg, excstr); } else { @@ -1509,8 +1547,6 @@ void CPyCppyy::Utility::SetDetailedException(std::vector& errors, PyO PyErr_SetString(exc_type, CPyCppyy_PyText_AsString(topmsg)); } -// cleanup stored errors and done with topmsg (whether used or not) - std::for_each(errors.begin(), errors.end(), PyError_t::Clear); Py_DECREF(topmsg); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h index 86cd268dc996f..51ee0f7ebb227 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h @@ -3,6 +3,7 @@ // Standard #include +#include #include #include @@ -100,22 +101,23 @@ PyObject* PyErr_Occurred_WithGIL(); // helpers for collecting/maintaining python exception data struct PyError_t { - PyError_t(bool is_cpp = false) : fIsCpp(is_cpp) { fType = fValue = fTrace = 0; } - - static void Clear(PyError_t& e) - { - // Remove exception information. - Py_XDECREF(e.fType); Py_XDECREF(e.fValue); Py_XDECREF(e.fTrace); - e.fType = e.fValue = e.fTrace = 0; - } - - PyObject *fType, *fValue, *fTrace; - bool fIsCpp; + struct PyObjectDeleter { + void operator()(PyObject *obj) { Py_XDECREF(obj); } + }; +#if PY_VERSION_HEX < 0x030c0000 + std::unique_ptr fType; + std::unique_ptr fTrace; +#endif + std::unique_ptr fValue; + bool fIsCpp = false; }; +PyError_t FetchPyError(); +void RestorePyError(PyError_t &error); + size_t FetchError(std::vector&, bool is_cpp = false); void SetDetailedException( - std::vector& errors /* clears */, PyObject* topmsg /* steals ref */, PyObject* defexc); + std::vector&& errors /* clears */, PyObject* topmsg /* steals ref */, PyObject* defexc); // setup Python API for callbacks bool IncludePython(); From a79415f391bf35e9e036724c4b1785a59ef8e4a6 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 11 Mar 2026 19:26:01 +0100 Subject: [PATCH 14/29] [windows] fix incompatible Py_ssize_t with vector --- bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx index 109aa2be54f55..7b84b171c8918 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPDataMember.cxx @@ -361,7 +361,8 @@ void CPyCppyy::CPPDataMember::Set(Cppyy::TCppScope_t scope, Cppyy::TCppScope_t d fFlags |= kIsConstData; } - std::vector dims = Cppyy::GetDimensions(type); + auto ldims = Cppyy::GetDimensions(type); + std::vector dims(ldims.begin(), ldims.end()); if (!dims.empty()) fFlags |= kIsArrayType; From 7b58ccd89f373e86207c6debe8846759609c9e3e Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Wed, 11 Mar 2026 19:26:17 +0100 Subject: [PATCH 15/29] [windows] drop dummy static that confused msvc --- bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h b/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h index 14742b894099d..2dcb248f96566 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h @@ -40,9 +40,6 @@ using CppyyExceptionContext_t = CppyyLegacy::ExceptionContext_t; using CppyyExceptionContext_t = ExceptionContext_t; #endif -// FIXME: This is a dummy, replace with cling equivalent of gException -static CppyyExceptionContext_t DummyException; -static CppyyExceptionContext_t *gException = &DummyException; #ifdef NEED_SIGJMP # define CLING_EXCEPTION_SETJMP(buf) sigsetjmp(buf,1) From db98a500a33af21593724e1df604bad62e517fb4 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Mar 2026 08:52:18 +0100 Subject: [PATCH 16/29] Add missing Cling PushTransactionRAII for Lookups --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 773ea025203d8..a1c9b4ae5ac77 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -927,6 +927,8 @@ int64_t GetBaseClassOffset(TCppScope_t derived, TCppScope_t base) { assert(derived || base); + compat::SynthesizingCodeRAII RAII(&getInterp()); + auto* DD = (Decl*)derived; auto* BD = (Decl*)base; if (!isa(DD) || !isa(BD)) @@ -1053,8 +1055,13 @@ std::vector GetFunctionsUsingName(TCppScope_t scope, DeclarationName DName = &getASTContext().Idents.get(name); clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, For_Visible_Redeclaration); - - CppInternal::utils::Lookup::Named(&S, R, Decl::castToDeclContext(D)); + auto* Within = Decl::castToDeclContext(D); + #ifdef CPPINTEROP_USE_CLING + if (Within) + Within->getPrimaryContext()->buildLookup(); + #endif + compat::SynthesizingCodeRAII RAII(&getInterp()); + CppInternal::utils::Lookup::Named(&S, R, Within); if (R.empty()) return funcs; @@ -1198,6 +1205,11 @@ bool ExistsFunctionTemplate(const std::string& name, TCppScope_t parent) { Within = llvm::dyn_cast(D); } +#ifdef CPPINTEROP_USE_CLING + if (Within) + Within->getPrimaryContext()->buildLookup(); +#endif + compat::SynthesizingCodeRAII RAII(&getInterp()); auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if ((intptr_t)ND == (intptr_t)0) @@ -1243,6 +1255,11 @@ bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, For_Visible_Redeclaration); auto* DC = clang::Decl::castToDeclContext(D); +#ifdef CPPINTEROP_USE_CLING + if (DC) + DC->getPrimaryContext()->buildLookup(); +#endif + compat::SynthesizingCodeRAII RAII(&getInterp()); CppInternal::utils::Lookup::Named(&S, R, DC); if (R.getResultKind() == clang_LookupResult_Not_Found && funcs.empty()) @@ -1556,6 +1573,11 @@ TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { Within = llvm::dyn_cast(D); } +#ifdef CPPINTEROP_USE_CLING + if (Within) + Within->getPrimaryContext()->buildLookup(); +#endif + compat::SynthesizingCodeRAII RAII(&getInterp()); auto* ND = CppInternal::utils::Lookup::Named(&getSema(), name, Within); if (ND && ND != (clang::NamedDecl*)-1) { if (llvm::isa_and_nonnull(ND)) { @@ -1602,6 +1624,7 @@ intptr_t GetVariableOffset(compat::Interpreter& I, Decl* D, return 0; auto& C = I.getSema().getASTContext(); + compat::SynthesizingCodeRAII RAII(&getInterp()); if (auto* FD = llvm::dyn_cast(D)) { clang::RecordDecl* FieldParentRecordDecl = FD->getParent(); From ea9fd7bc527f793e3e860dfa8567ea7e9665a6cc Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Mar 2026 09:02:53 +0100 Subject: [PATCH 17/29] [windows] Drop intptr_t usage and return Cpp::TCppIndex_t in EnumDataValue --- bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 2 +- .../pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h index d5336474c692f..da6c3ab7e4ff1 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h @@ -392,7 +392,7 @@ namespace Cppyy { CPPYY_IMPORT TCppType_t GetEnumConstantType(TCppScope_t scope); CPPYY_IMPORT - long long GetEnumDataValue(TCppScope_t scope); + TCppIndex_t GetEnumDataValue(TCppScope_t scope); CPPYY_IMPORT TCppScope_t InstantiateTemplate( TCppScope_t tmpl, Cpp::TemplateArgInfo* args, size_t args_size); diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h index 44260c4d2eeee..2ed3a883c38e7 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -53,7 +53,7 @@ namespace Cppyy { typedef Cpp::TCppScope_t TCppObject_t; typedef Cpp::TCppFunction_t TCppMethod_t; typedef Cpp::TCppIndex_t TCppIndex_t; - typedef intptr_t TCppFuncAddr_t; + typedef Cpp::TCppFuncAddr_t TCppFuncAddr_t; // // direct interpreter access ------------------------------------------------- RPY_EXPORTED From 445da1db4c0fd1d1e984e1b5aac32a6bbe7c6223 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 12 Mar 2026 12:35:08 +0100 Subject: [PATCH 18/29] [windows] undef `LoadLibrary` from windows.h before CppInterOp's Dispatch API We lose `CppImpl::LoadLibrary` otherwise --- interpreter/CppInterOp/include/CppInterOp/Dispatch.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h index d9f0e9dd291f7..9f2d60746371b 100644 --- a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h +++ b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h @@ -21,12 +21,6 @@ #error "To use the Dispatch mechanism, do not include CppInterOp.h directly." #endif -#include - -#include -#include -#include - #ifdef _WIN32 #include #undef LoadLibrary @@ -34,6 +28,12 @@ #include #endif +#include + +#include +#include +#include + using CppFnPtrTy = void (*)(); ///\param[in] procname - the name of the FunctionEntry in the symbol lookup /// table. From c5859fb147ed60ace835faf80695245d29510de0 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 13 Mar 2026 15:40:42 +0100 Subject: [PATCH 19/29] [backend] reconcile minor format diff with compres fork --- .../cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index 2d186a278e5bb..13fab948aad5b 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -1661,8 +1661,8 @@ std::string Cppyy::GetMethodArgName(TCppMethod_t method, TCppIndex_t iarg) { if (!method) return ""; - std::lock_guard Lock(InterOpMutex); + std::lock_guard Lock(InterOpMutex); return Cpp::GetFunctionArgName(method, iarg); } @@ -1675,7 +1675,7 @@ Cppyy::TCppType_t Cppyy::GetMethodArgType(TCppMethod_t method, TCppIndex_t iarg) std::string Cppyy::GetMethodArgTypeAsString(TCppMethod_t method, TCppIndex_t iarg) { std::lock_guard Lock(InterOpMutex); - return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier( + return Cpp::GetTypeAsString(Cpp::RemoveTypeQualifier( Cpp::GetFunctionArgType(method, iarg), Cpp::QualKind::Const)); } @@ -1826,6 +1826,7 @@ Cppyy::TCppMethod_t Cppyy::GetMethodTemplate( } 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) { @@ -1838,7 +1839,6 @@ Cppyy::TCppMethod_t Cppyy::GetMethodTemplate( std::vector templ_params; Cppyy::AppendTypesSlow(proto, arg_types, scope); Cppyy::AppendTypesSlow(explicit_params, templ_params, scope); - Cppyy::TCppMethod_t cppmeth = nullptr; cppmeth = Cpp::BestOverloadFunctionMatch( unresolved_candidate_methods, templ_params, arg_types); From 8e90ad320d577566ce519b38f190eaa5d598a613 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 27 Mar 2026 18:12:56 +0100 Subject: [PATCH 20/29] Add more RAII objects --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index a1c9b4ae5ac77..a498767141bb0 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -760,6 +760,7 @@ TCppScope_t GetScope(const std::string& name, TCppScope_t parent) { if (name == "") return GetGlobalScope(); + compat::SynthesizingCodeRAII RAII(&getInterp()); auto* ND = (NamedDecl*)GetNamed(name, parent); if (!ND || ND == (NamedDecl*)-1) @@ -828,7 +829,9 @@ TCppScope_t GetParentScope(TCppScope_t scope) { TCppIndex_t GetNumBases(TCppScope_t klass) { auto* D = (Decl*)klass; - + + compat::SynthesizingCodeRAII RAII(&getInterp()); + if (auto* CTSD = llvm::dyn_cast_or_null(D)) if (!CTSD->hasDefinition()) compat::InstantiateClassTemplateSpecialization(getInterp(), CTSD); @@ -841,6 +844,9 @@ TCppIndex_t GetNumBases(TCppScope_t klass) { } TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase) { + + compat::SynthesizingCodeRAII RAII(&getInterp()); + auto* D = (Decl*)klass; auto* CXXRD = llvm::dyn_cast_or_null(D); if (!CXXRD || CXXRD->getNumBases() <= ibase) @@ -865,6 +871,8 @@ bool IsSubclass(TCppScope_t derived, TCppScope_t base) { auto* derived_D = (clang::Decl*)derived; auto* base_D = (clang::Decl*)base; + compat::SynthesizingCodeRAII RAII(&getInterp()); + if (!isa(derived_D) || !isa(base_D)) return false; @@ -880,6 +888,9 @@ bool IsSubclass(TCppScope_t derived, TCppScope_t base) { static unsigned ComputeBaseOffset(const ASTContext& Context, const CXXRecordDecl* DerivedRD, const CXXBasePath& Path) { + + compat::SynthesizingCodeRAII RAII(&getInterp()); + CharUnits NonVirtualOffset = CharUnits::Zero(); unsigned NonVirtualStart = 0; @@ -948,7 +959,9 @@ static void GetClassDecls(TCppScope_t klass, std::vector& methods) { if (!klass) return; - + + compat::SynthesizingCodeRAII RAII(&getInterp()); + auto* D = (clang::Decl*)klass; if (auto* TD = dyn_cast(D)) From 042f6014c994169eb749678c2174714f38082f1e Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 27 Mar 2026 18:17:24 +0100 Subject: [PATCH 21/29] revert changes in cppyy __init__ --- .../cppyy/cppyy/python/cppyy/__init__.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py index 0d888c436b0ad..fd9d292ed2309 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py @@ -177,8 +177,8 @@ def __getitem__(self, cls): return py_make_smartptr(getattr(gbl, cls), self.ptrcls) return self.maker[cls] -# gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) -# gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) +gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) +gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) del make_smartptr @@ -265,9 +265,18 @@ def macro(cppm): def load_library(name): """Explicitly load a shared library.""" with _stderr_capture() as err: - result = gbl.Cpp.LoadLibrary(name, True) - if result == False: - raise RuntimeError('Could not load library "%s": %s' % (name, err.err)) + gSystem = gbl.gSystem + if name[:3] != 'lib': + if not gSystem.FindDynamicLibrary(gbl.TString(name), True) and\ + gSystem.FindDynamicLibrary(gbl.TString('lib'+name), True): + name = 'lib'+name + sc = gSystem.Load(name) + if sc == -1: + # special case for Windows as of python3.8: use winmode=0, otherwise the default + # will not consider regular search paths (such as $PATH) + if 0x3080000 <= sys.hexversion and 'win32' in sys.platform and os.path.isabs(name): + return ctypes.CDLL(name, ctypes.RTLD_GLOBAL, winmode=0) # raises on error + raise RuntimeError('Unable to load library "%s"%s' % (name, err.err)) return True From 66eb4161153f0cd68ffb480356fc96fdd106fe9c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 28 Mar 2026 20:41:31 +0100 Subject: [PATCH 22/29] [CPyCppyy] Sync up with compiler-research forks --- .../pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx | 2 +- .../pyroot/cppyy/CPyCppyy/src/CPPInstance.h | 3 +- .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 156 +++++++++++++++++- 3 files changed, 149 insertions(+), 12 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx index e435ddcbd981a..cc0b6b7e3c3fa 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx @@ -405,7 +405,7 @@ PyCFunction &CPPInstance::ReduceMethod() { return reducer; } -PyObject *op_reduce(PyObject *self, PyObject * args) +PyObject *op_reduce(PyObject *self, PyObject *args) { auto &reducer = CPPInstance::ReduceMethod(); if (!reducer) { diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h index e17c324dc444e..32c99ee1b2d3d 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h @@ -16,7 +16,6 @@ // Standard #include -#include #include @@ -87,7 +86,7 @@ class CPPInstance { // implementation of the __reduce__ method: doesn't wrap any function by // default but can be re-assigned by libraries that add C++ object // serialization support, like ROOT -static std::function &ReduceMethod(); + static PyCFunction &ReduceMethod(); private: diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index 340b2f9d30496..b156b37f194af 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -29,6 +29,9 @@ #include #include #include +#if __cplusplus >= 202002L +#include +#endif // codecvt does not exist for gcc4.8.5 and is in principle deprecated; it is // only used in py2 for char -> wchar_t conversion for std::wstring; if not @@ -1292,7 +1295,8 @@ bool CPyCppyy::CStringConverter::SetArg( // verify (too long string will cause truncation, no crash) if (fMaxSize != std::string::npos && fMaxSize < fBuffer.size()) - PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)"); + if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) + return false; if (!ctxt->fPyContext) { // use internal buffer as workaround @@ -1337,7 +1341,8 @@ bool CPyCppyy::CStringConverter::ToMemory(PyObject* value, void* address, PyObje // verify (too long string will cause truncation, no crash) if (fMaxSize != std::string::npos && fMaxSize < (std::string::size_type)len) - PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)"); + if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for char array (truncated)", 1) < 0) + return false; // if address is available, and it wasn't set by this converter, assume a byte-wise copy; // otherwise assume a pointer copy (this relies on the converter to be used for properties, @@ -1414,7 +1419,8 @@ bool CPyCppyy::WCStringConverter::ToMemory(PyObject* value, void* address, PyObj // verify (too long string will cause truncation, no crash) if (fMaxSize != std::wstring::npos && fMaxSize < (std::wstring::size_type)len) - PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for wchar_t array (truncated)"); + if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for wchar_t array (truncated)", 1) < 0) + return false; Py_ssize_t res = -1; if (fMaxSize != std::wstring::npos) @@ -1473,7 +1479,10 @@ bool CPyCppyy::name##Converter::ToMemory(PyObject* value, void* address, PyObjec \ /* verify (too long string will cause truncation, no crash) */ \ if (fMaxSize != std::wstring::npos && maxbytes < len) { \ - PyErr_Warn(PyExc_RuntimeWarning, (char*)"string too long for "#type" array (truncated)");\ + if (PyErr_WarnEx(PyExc_RuntimeWarning, (char*)"string too long for "#type" array (truncated)", 1) < 0) { \ + Py_DECREF(bstr); \ + return false; \ + } \ len = maxbytes; \ } \ \ @@ -1637,6 +1646,78 @@ bool CPyCppyy::VoidArrayConverter::ToMemory(PyObject* value, void* address, PyOb return true; } +#if __cplusplus >= 202002L + +namespace CPyCppyy { + +class StdSpanConverter : public InstanceConverter { +public: + StdSpanConverter(std::string const &typeName, Cppyy::TCppType_t klass, bool keepControl = false) + : InstanceConverter{klass, keepControl}, fTypeName{typeName} + { + } + + ~StdSpanConverter() + { + if (fHasBuffer) { + PyBuffer_Release(&fBufinfo); + } + } + + bool SetArg(PyObject *, Parameter &, CallContext * = nullptr) override; + bool HasState() override { return true; } + +private: + std::string fTypeName; + std::span fBuffer; + bool fHasBuffer = false; + Py_buffer fBufinfo; +}; + +} // namespace CPyCppyy + +//---------------------------------------------------------------------------- +bool CPyCppyy::StdSpanConverter::SetArg(PyObject *pyobject, Parameter ¶, CallContext *ctxt) +{ + auto typecodeFound = Utility::TypecodeMap().find(fTypeName); + +// attempt to get buffer if the C++ type maps to a buffer type + if (typecodeFound == Utility::TypecodeMap().end() || !PyObject_CheckBuffer(pyobject)) { + // Fall back to regular InstanceConverter + return this->InstanceConverter::SetArg(pyobject, para, ctxt); + } + + Py_ssize_t buflen = 0; + char typecode = typecodeFound->second; + memset(&fBufinfo, 0, sizeof(Py_buffer)); + + if (PyObject_GetBuffer(pyobject, &fBufinfo, PyBUF_FORMAT) == 0) { + if (!strchr(fBufinfo.format, typecode)) { + PyErr_Format(PyExc_TypeError, + "buffer has incompatible type: expected '%c' for C++ type '%s', but got format '%s'", typecode, + fTypeName.c_str(), fBufinfo.format ? fBufinfo.format : ""); + PyBuffer_Release(&fBufinfo); + return false; + } + buflen = Utility::GetBuffer(pyobject, typecode, 1, para.fValue.fVoidp, false); + } + +// ok if buffer exists (can't perform any useful size checks) + if (para.fValue.fVoidp && buflen != 0) { + // We assume the layout for any std::span is the same, and just use + // std::span as a placeholder. Not elegant, but works. + fBuffer = std::span{(std::size_t *)para.fValue.fVoidp, static_cast(buflen)}; + fHasBuffer = true; + para.fValue.fVoidp = &fBuffer; + para.fTypeCode = 'V'; + return true; + } + + return false; +} + +#endif // __cplusplus >= 202002L + //---------------------------------------------------------------------------- #define CPPYY_IMPL_ARRAY_CONVERTER(name, ctype, type, code, suffix) \ @@ -3177,11 +3258,25 @@ bool CPyCppyy::InitializerListConverter::SetArg( #endif } +namespace CPyCppyy { + +// raising converter to take out overloads +class NotImplementedConverter : public Converter { +public: + NotImplementedConverter(PyObject *errorType, std::string const &message) : fErrorType{errorType}, fMessage{message} {} + bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; +private: + PyObject *fErrorType; + std::string fMessage; +}; + +} // namespace CPyCppyy + //---------------------------------------------------------------------------- bool CPyCppyy::NotImplementedConverter::SetArg(PyObject*, Parameter&, CallContext*) { // raise a NotImplemented exception to take a method out of overload resolution - PyErr_SetString(PyExc_NotImplementedError, "this method cannot (yet) be called"); + PyErr_SetString(fErrorType, fMessage.c_str()); return false; } @@ -3247,6 +3342,14 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim const std::string& cpd = TypeManip::compound(resolvedType); std::string realType = TypeManip::clean_type(resolvedType, false, true); +// mutable pointer references (T*&) are incompatible with Python's object model + if (cpd == "*&") { + return new NotImplementedConverter{PyExc_TypeError, + "argument type '" + resolvedType + "' is not supported: non-const references to pointers (T*&) allow a" + " function to replace the pointer itself. Python cannot represent this safely. Consider changing the" + " C++ API to return the new pointer or use a wrapper"}; + } + // accept unqualified type (as python does not know about qualifiers) h = gConvFactories.find((isConst ? "const " : "") + realType + cpd); if (h != gConvFactories.end()) @@ -3331,6 +3434,31 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim } } +// FIXME: Taken from ROOT, update this to use CppInterOp for span check and extracting value type +#if __cplusplus >= 202002L +//-- special case: std::span + pos = resolvedType.find("span<"); + if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || + pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { + + auto pos1 = realType.find('<'); + auto pos21 = realType.find(','); // for the case there are more template args + auto pos22 = realType.find('>'); + auto len = std::min(pos21 - pos1, pos22 - pos1) - 1; + std::string value_type = realType.substr(pos1+1, len); + + // strip leading "const " + const std::string cprefix = "const "; + if (value_type.compare(0, cprefix.size(), cprefix) == 0) { + value_type = value_type.substr(cprefix.size()); + } + + std::string span_type = "std::span<" + value_type + ">"; + + return new StdSpanConverter{value_type, Cppyy::GetScope(span_type)}; + } +#endif + // converters for known C++ classes and default (void*) Converter* result = nullptr; if (Cppyy::TCppScope_t klass = Cppyy::GetFullScope(realType)) { @@ -3372,7 +3500,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim if (h != gConvFactories.end()) return (h->second)(dims); // else, unhandled moves - result = new NotImplementedConverter(failure_msg); + result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; } if (!result && h != gConvFactories.end()) @@ -3385,7 +3513,8 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim else if (!cpd.empty()) result = new VoidArrayConverter(/* keepControl= */ true, failure_msg); // "user knows best" else - result = new NotImplementedConverter(failure_msg); // fails on use + // fails on use + result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; } return result; @@ -3429,6 +3558,14 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d std::string realTypeStr = Cppyy::GetTypeAsString(realType); std::string realUnresolvedTypeStr = TypeManip::clean_type(fullType, false, true); +// mutable pointer references (T*&) are incompatible with Python's object model + if (cpd == "*&") { + return new NotImplementedConverter{PyExc_TypeError, + "argument type '" + resolvedTypeStr + "' is not supported: non-const references to pointers (T*&) allow a" + " function to replace the pointer itself. Python cannot represent this safely. Consider changing the" + " C++ API to return the new pointer or use a wrapper"}; + } + // accept unqualified type (as python does not know about qualifiers) h = gConvFactories.find((isConst ? "const " : "") + realTypeStr + cpd); if (h != gConvFactories.end()) { @@ -3579,7 +3716,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d if (h != gConvFactories.end()) return (h->second)(dims); // else, unhandled moves - result = new NotImplementedConverter(failure_msg); + result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; } if (!result && h != gConvFactories.end()) { @@ -3592,7 +3729,8 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d } else if (!cpd.empty()) { result = new VoidArrayConverter(/* keepControl= */ true, failure_msg); // "user knows best" } else { - result = new NotImplementedConverter(failure_msg); // fails on use + // fails on use + result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; } } From 59af7b310afe3b3813603ed341bfca5a99102eaa Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 28 Mar 2026 21:53:36 +0100 Subject: [PATCH 23/29] drop extra RAII object --- interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index a498767141bb0..130d80607e307 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -971,7 +971,6 @@ static void GetClassDecls(TCppScope_t klass, return; auto* CXXRD = dyn_cast(D); - compat::SynthesizingCodeRAII RAII(&getInterp()); if (CXXRD->hasDefinition()) CXXRD = CXXRD->getDefinition(); getSema().ForceDeclarationOfImplicitMembers(CXXRD); From 760a84aa7a0dd212dcf7f6080f04a7562792b9df Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sun, 29 Mar 2026 13:23:26 +0200 Subject: [PATCH 24/29] [dispatch] disable verbose loadapi message unless debug From https://github.com/compiler-research/CppInterOp/commit/9ccb9b2fb0fc9e9df59e0830ce601614136cc94a --- interpreter/CppInterOp/include/CppInterOp/Dispatch.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h index 9f2d60746371b..891af72084016 100644 --- a/interpreter/CppInterOp/include/CppInterOp/Dispatch.h +++ b/interpreter/CppInterOp/include/CppInterOp/Dispatch.h @@ -253,8 +253,10 @@ CPPINTEROP_API_TABLE /// \param[in] customLibPath Optional custom path to libclangCppInterOp /// \returns true if initialization succeeded, false otherwise inline bool LoadDispatchAPI(const char* customLibPath = nullptr) { +#ifndef NDEBUG std::cout << "[CppInterOp Dispatch] Loading CppInterOp API from " << (customLibPath ? customLibPath : "default library path") << '\n'; +#endif // NDEBUG if (customLibPath) { void* test = dlGetProcAddress("GetInterpreter", customLibPath); if (!test) { From 6c127237dcbcb132d7530443ee1563471834bc7e Mon Sep 17 00:00:00 2001 From: Vipul Cariappa Date: Thu, 26 Mar 2026 16:04:00 +0100 Subject: [PATCH 25/29] [cppyy] Update test tags and revert changes in _cpython_cppyy --- .../cppyy/python/cppyy/_cpython_cppyy.py | 17 +- .../cppyy/cppyy/test/test_aclassloader.py | 4 +- .../cppyy/cppyy/test/test_advancedcpp.py | 490 +++++++++--------- bindings/pyroot/cppyy/cppyy/test/test_api.py | 2 +- .../pyroot/cppyy/cppyy/test/test_boost.py | 2 +- .../cppyy/cppyy/test/test_concurrent.py | 6 +- .../cppyy/cppyy/test/test_conversions.py | 7 +- .../cppyy/cppyy/test/test_cpp11features.py | 28 +- .../cppyy/cppyy/test/test_crossinheritance.py | 21 +- .../pyroot/cppyy/cppyy/test/test_datatypes.py | 40 +- .../cppyy/cppyy/test/test_doc_features.py | 16 +- .../pyroot/cppyy/cppyy/test/test_eigen.py | 4 +- .../pyroot/cppyy/cppyy/test/test_fragile.py | 310 ++++++----- .../pyroot/cppyy/cppyy/test/test_leakcheck.py | 2 +- .../pyroot/cppyy/cppyy/test/test_lowlevel.py | 9 +- .../pyroot/cppyy/cppyy/test/test_numba.py | 26 +- .../pyroot/cppyy/cppyy/test/test_operators.py | 6 +- .../pyroot/cppyy/cppyy/test/test_overloads.py | 2 +- .../pyroot/cppyy/cppyy/test/test_pythonify.py | 6 +- .../cppyy/cppyy/test/test_pythonization.py | 4 +- .../cppyy/cppyy/test/test_regression.py | 37 +- .../pyroot/cppyy/cppyy/test/test_stltypes.py | 23 +- .../pyroot/cppyy/cppyy/test/test_streams.py | 4 +- .../pyroot/cppyy/cppyy/test/test_templates.py | 67 +-- 24 files changed, 606 insertions(+), 527 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py index e82888de25448..0cfc9153a811c 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py @@ -205,21 +205,12 @@ def add_default_paths(): default = _backend.default def load_reflection_info(name): -# with _stderr_capture() as err: - #FIXME: Remove the .so and add logic in libcppinterop - name = name + ".so" - result = gbl.Cpp.LoadLibrary(name, True) - if name.endswith("Dict.so"): - header = name[:-7] + ".h" - gbl.Cpp.Declare('#include "' + header +'"', False) - - if result == False: - raise RuntimeError('Could not load library "%s"' % (name)) - - return True + sc = gbl.gSystem.Load(name) + if sc == -1: + raise RuntimeError("Unable to load reflection library "+name) def _begin_capture_stderr(): - _backend._begin_capture_stderr() + _backend._begin_capture_stderr() # TODO: fix this def _end_capture_stderr(): err = _backend._end_capture_stderr() diff --git a/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py b/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py index 3a096069e4204..53248d1a7f5fe 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py @@ -1,6 +1,6 @@ import pytest, os from pytest import raises, mark -from support import setup_make, IS_WINDOWS +from .support import setup_make, IS_WINDOWS test_dct = "libexample_cxx" @@ -10,7 +10,7 @@ class TestACLASSLOADER: def setup_class(cls): import cppyy - @mark.xfail(strict=True, condition=not IS_WINDOWS, reason="rootmap files are a legacy feature") + @mark.xfail(condition=not IS_WINDOWS, reason="rootmap files are a legacy feature") def test01_class_autoloading(self): """Test whether a class can be found through .rootmap.""" import cppyy diff --git a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py b/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py index 03307937c8ab1..08f13e5ceb21b 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py @@ -1,6 +1,9 @@ -import pytest, os +import os + +import pytest from pytest import mark, raises, skip -from support import setup_make, pylong, IS_WINDOWS, ispypy + +from .support import IS_WINDOWS, ispypy, pylong, setup_make test_dct = "advancedcpp_cxx" @@ -9,14 +12,16 @@ class TestADVANCEDCPP: def setup_class(cls): cls.test_dct = test_dct import cppyy + cls.advanced = cppyy.load_reflection_info(cls.test_dct) def test01_default_arguments(self): """Test usage of default arguments""" import cppyy + def test_defaulter(n, t): - defaulter = getattr(cppyy.gbl, '%s_defaulter' % n) + defaulter = getattr(cppyy.gbl, "%s_defaulter" % n) d = defaulter() assert d.m_a == t(11) @@ -25,51 +30,52 @@ def test_defaulter(n, t): d.__destruct__() d = defaulter(0) - assert d.m_a == t(0) + assert d.m_a == t(0) assert d.m_b == t(22) assert d.m_c == t(33) d.__destruct__() d = defaulter(1, 2) - assert d.m_a == t(1) - assert d.m_b == t(2) + assert d.m_a == t(1) + assert d.m_b == t(2) assert d.m_c == t(33) d.__destruct__() d = defaulter(3, 4, 5) - assert d.m_a == t(3) - assert d.m_b == t(4) - assert d.m_c == t(5) + assert d.m_a == t(3) + assert d.m_b == t(4) + assert d.m_c == t(5) d.__destruct__() - defaulter_func = getattr(cppyy.gbl, '%s_defaulter_func' %n) + defaulter_func = getattr(cppyy.gbl, "%s_defaulter_func" % n) answers = [11, 22, 33, 3] for idx in range(4): assert defaulter_func(idx) == answers[idx] - test_defaulter('short', int) - test_defaulter('ushort', int) - test_defaulter('int', int) - test_defaulter('uint', int) - test_defaulter('long', pylong) - test_defaulter('ulong', pylong) - test_defaulter('llong', pylong) - test_defaulter('ullong', pylong) - test_defaulter('float', float) - test_defaulter('double', float) - - assert cppyy.gbl.string_defaulter_func(0) == "aap" - assert cppyy.gbl.string_defaulter_func(0, "zus") == "zus" - assert cppyy.gbl.string_defaulter_func(1) == "noot" - assert cppyy.gbl.string_defaulter_func(1, "zus") == "noot" + test_defaulter("short", int) + test_defaulter("ushort", int) + test_defaulter("int", int) + test_defaulter("uint", int) + test_defaulter("long", pylong) + test_defaulter("ulong", pylong) + test_defaulter("llong", pylong) + test_defaulter("ullong", pylong) + test_defaulter("float", float) + test_defaulter("double", float) + + assert cppyy.gbl.string_defaulter_func(0) == "aap" + assert cppyy.gbl.string_defaulter_func(0, "zus") == "zus" + assert cppyy.gbl.string_defaulter_func(1) == "noot" + assert cppyy.gbl.string_defaulter_func(1, "zus") == "noot" assert cppyy.gbl.string_defaulter_func(1, "zus", "jet") == "jet" - assert cppyy.gbl.string_defaulter_func(2) == "mies" + assert cppyy.gbl.string_defaulter_func(2) == "mies" def test02_simple_inheritance(self): """Test binding of a basic inheritance structure""" import cppyy - base_class = cppyy.gbl.base_class + + base_class = cppyy.gbl.base_class derived_class = cppyy.gbl.derived_class assert issubclass(derived_class, base_class) @@ -79,15 +85,15 @@ def test02_simple_inheritance(self): assert isinstance(b, base_class) assert not isinstance(b, derived_class) - assert b.m_b == 1 - assert b.get_value() == 1 - assert b.m_db == 1.1 + assert b.m_b == 1 + assert b.get_value() == 1 + assert b.m_db == 1.1 assert b.get_base_value() == 1.1 b.m_b, b.m_db = 11, 11.11 - assert b.m_b == 11 - assert b.get_value() == 11 - assert b.m_db == 11.11 + assert b.m_b == 11 + assert b.get_value() == 11 + assert b.m_db == 11.11 assert b.get_base_value() == 11.11 b.__destruct__() @@ -96,26 +102,26 @@ def test02_simple_inheritance(self): assert isinstance(d, derived_class) assert isinstance(d, base_class) - assert d.m_d == 2 - assert d.get_value() == 2 - assert d.m_dd == 2.2 + assert d.m_d == 2 + assert d.get_value() == 2 + assert d.m_dd == 2.2 assert d.get_derived_value() == 2.2 - assert d.m_b == 1 - assert d.m_db == 1.1 - assert d.get_base_value() == 1.1 + assert d.m_b == 1 + assert d.m_db == 1.1 + assert d.get_base_value() == 1.1 d.m_b, d.m_db = 11, 11.11 d.m_d, d.m_dd = 22, 22.22 - assert d.m_d == 22 - assert d.get_value() == 22 - assert d.m_dd == 22.22 + assert d.m_d == 22 + assert d.get_value() == 22 + assert d.m_dd == 22.22 assert d.get_derived_value() == 22.22 - assert d.m_b == 11 - assert d.m_db == 11.11 - assert d.get_base_value() == 11.11 + assert d.m_b == 11 + assert d.m_db == 11.11 + assert d.get_base_value() == 11.11 d.__destruct__() @@ -123,28 +129,29 @@ def test03_namespaces(self): """Test access to namespaces and inner classes""" import cppyy + gbl = cppyy.gbl - assert gbl.a_ns is gbl.a_ns + assert gbl.a_ns is gbl.a_ns assert gbl.a_ns.d_ns is gbl.a_ns.d_ns - assert gbl.a_ns.b_class is gbl.a_ns.b_class - assert gbl.a_ns.b_class.c_class is gbl.a_ns.b_class.c_class - assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class + assert gbl.a_ns.b_class is gbl.a_ns.b_class + assert gbl.a_ns.b_class.c_class is gbl.a_ns.b_class.c_class + assert gbl.a_ns.d_ns.e_class is gbl.a_ns.d_ns.e_class assert gbl.a_ns.d_ns.e_class.f_class is gbl.a_ns.d_ns.e_class.f_class - assert gbl.a_ns.g_a == 11 - assert gbl.a_ns.get_g_a() == 11 - assert gbl.a_ns.b_class.s_b == 22 - assert gbl.a_ns.b_class().m_b == -2 - assert gbl.a_ns.b_class.c_class.s_c == 33 - assert gbl.a_ns.b_class.c_class().m_c == -3 - assert gbl.a_ns.d_ns.g_d == 44 - assert gbl.a_ns.d_ns.get_g_d() == 44 - assert gbl.a_ns.d_ns.e_class.s_e == 55 - assert gbl.a_ns.d_ns.e_class().m_e == -5 - assert gbl.a_ns.d_ns.e_class.f_class.s_f == 66 - assert gbl.a_ns.d_ns.e_class.f_class().m_f == -6 + assert gbl.a_ns.g_a == 11 + assert gbl.a_ns.get_g_a() == 11 + assert gbl.a_ns.b_class.s_b == 22 + assert gbl.a_ns.b_class().m_b == -2 + assert gbl.a_ns.b_class.c_class.s_c == 33 + assert gbl.a_ns.b_class.c_class().m_c == -3 + assert gbl.a_ns.d_ns.g_d == 44 + assert gbl.a_ns.d_ns.get_g_d() == 44 + assert gbl.a_ns.d_ns.e_class.s_e == 55 + assert gbl.a_ns.d_ns.e_class().m_e == -5 + assert gbl.a_ns.d_ns.e_class.f_class.s_f == 66 + assert gbl.a_ns.d_ns.e_class.f_class().m_f == -6 raises(TypeError, gbl.a_ns) @@ -152,35 +159,37 @@ def test03a_namespace_lookup_on_update(self): """Test whether namespaces can be shared across dictionaries.""" import cppyy + gbl = cppyy.gbl lib2 = cppyy.load_reflection_info("advancedcpp2_cxx") - assert gbl.a_ns is gbl.a_ns + assert gbl.a_ns is gbl.a_ns assert gbl.a_ns.d_ns is gbl.a_ns.d_ns - assert gbl.a_ns.g_class is gbl.a_ns.g_class - assert gbl.a_ns.g_class.h_class is gbl.a_ns.g_class.h_class - assert gbl.a_ns.d_ns.i_class is gbl.a_ns.d_ns.i_class + assert gbl.a_ns.g_class is gbl.a_ns.g_class + assert gbl.a_ns.g_class.h_class is gbl.a_ns.g_class.h_class + assert gbl.a_ns.d_ns.i_class is gbl.a_ns.d_ns.i_class assert gbl.a_ns.d_ns.i_class.j_class is gbl.a_ns.d_ns.i_class.j_class - assert gbl.a_ns.g_g == 77 - assert gbl.a_ns.get_g_g() == 77 - assert gbl.a_ns.g_class.s_g == 88 - assert gbl.a_ns.g_class().m_g == -7 - assert gbl.a_ns.g_class.h_class.s_h == 99 - assert gbl.a_ns.g_class.h_class().m_h == -8 - assert gbl.a_ns.d_ns.g_i == 111 - assert gbl.a_ns.d_ns.get_g_i() == 111 - assert gbl.a_ns.d_ns.i_class.s_i == 222 - assert gbl.a_ns.d_ns.i_class().m_i == -9 - assert gbl.a_ns.d_ns.i_class.j_class.s_j == 333 - assert gbl.a_ns.d_ns.i_class.j_class().m_j == -10 + assert gbl.a_ns.g_g == 77 + assert gbl.a_ns.get_g_g() == 77 + assert gbl.a_ns.g_class.s_g == 88 + assert gbl.a_ns.g_class().m_g == -7 + assert gbl.a_ns.g_class.h_class.s_h == 99 + assert gbl.a_ns.g_class.h_class().m_h == -8 + assert gbl.a_ns.d_ns.g_i == 111 + assert gbl.a_ns.d_ns.get_g_i() == 111 + assert gbl.a_ns.d_ns.i_class.s_i == 222 + assert gbl.a_ns.d_ns.i_class().m_i == -9 + assert gbl.a_ns.d_ns.i_class.j_class.s_j == 333 + assert gbl.a_ns.d_ns.i_class.j_class().m_j == -10 def test04_template_types(self): """Test bindings of templated types""" import cppyy + gbl = cppyy.gbl assert gbl.T1 is gbl.T1 @@ -189,64 +198,62 @@ def test04_template_types(self): assert not gbl.T1 is gbl.T2 assert not gbl.T2 is gbl.T3 - assert gbl.T1('int') is gbl.T1('int') - assert gbl.T1(int) is gbl.T1('int') - assert gbl.T2('T1') is gbl.T2('T1') - assert gbl.T2(gbl.T1('int')) is gbl.T2('T1') - assert gbl.T2(gbl.T1(int)) is gbl.T2('T1') - assert gbl.T3('int,double') is gbl.T3('int,double') - assert gbl.T3('int', 'double') is gbl.T3('int,double') - assert gbl.T3(int, 'double') is gbl.T3('int,double') - assert gbl.T3('T1,T2 >') is gbl.T3('T1,T2 >') - assert gbl.T3('T1', gbl.T2(gbl.T1(int))) is gbl.T3('T1,T2 >') - - assert gbl.a_ns.T4(int) is gbl.a_ns.T4('int') - assert gbl.a_ns.T4('a_ns::T4 >')\ - is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, 'double'))) - - #----- mix in some of the alternative syntax - assert gbl.T1['int'] is gbl.T1('int') - assert gbl.T1[int] is gbl.T1('int') - assert gbl.T2['T1'] is gbl.T2('T1') - assert gbl.T2[gbl.T1('int')] is gbl.T2('T1') - assert gbl.T2[gbl.T1(int)] is gbl.T2('T1') - assert gbl.T3['int,double'] is gbl.T3('int,double') - assert gbl.T3['int', 'double'] is gbl.T3('int,double') - assert gbl.T3[int, 'double'] is gbl.T3('int,double') - assert gbl.T3['T1,T2 >'] is gbl.T3('T1,T2 >') - assert gbl.T3['T1', gbl.T2[gbl.T1[int]]] is gbl.T3('T1,T2 >') - - assert gbl.a_ns.T4[int] is gbl.a_ns.T4('int') - assert gbl.a_ns.T4['a_ns::T4 >']\ - is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, 'double'))) - - #----- + assert gbl.T1("int") is gbl.T1("int") + assert gbl.T1(int) is gbl.T1("int") + assert gbl.T2("T1") is gbl.T2("T1") + assert gbl.T2(gbl.T1("int")) is gbl.T2("T1") + assert gbl.T2(gbl.T1(int)) is gbl.T2("T1") + assert gbl.T3("int,double") is gbl.T3("int,double") + assert gbl.T3("int", "double") is gbl.T3("int,double") + assert gbl.T3(int, "double") is gbl.T3("int,double") + assert gbl.T3("T1,T2 >") is gbl.T3("T1,T2 >") + assert gbl.T3("T1", gbl.T2(gbl.T1(int))) is gbl.T3("T1,T2 >") + + assert gbl.a_ns.T4(int) is gbl.a_ns.T4("int") + assert gbl.a_ns.T4("a_ns::T4 >") is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, "double"))) + + # ----- mix in some of the alternative syntax + assert gbl.T1["int"] is gbl.T1("int") + assert gbl.T1[int] is gbl.T1("int") + assert gbl.T2["T1"] is gbl.T2("T1") + assert gbl.T2[gbl.T1("int")] is gbl.T2("T1") + assert gbl.T2[gbl.T1(int)] is gbl.T2("T1") + assert gbl.T3["int,double"] is gbl.T3("int,double") + assert gbl.T3["int", "double"] is gbl.T3("int,double") + assert gbl.T3[int, "double"] is gbl.T3("int,double") + assert gbl.T3["T1,T2 >"] is gbl.T3("T1,T2 >") + assert gbl.T3["T1", gbl.T2[gbl.T1[int]]] is gbl.T3("T1,T2 >") + + assert gbl.a_ns.T4[int] is gbl.a_ns.T4("int") + assert gbl.a_ns.T4["a_ns::T4 >"] is gbl.a_ns.T4(gbl.a_ns.T4(gbl.T3(int, "double"))) + + # ----- t1 = gbl.T1(int)() - assert t1.m_t1 == 1 + assert t1.m_t1 == 1 assert t1.get_value() == 1 t1.__destruct__() - #----- + # ----- t1 = gbl.T1(int)(11) - assert t1.m_t1 == 11 + assert t1.m_t1 == 11 assert t1.get_value() == 11 t1.m_t1 = 111 assert t1.get_value() == 111 - assert t1.m_t1 == 111 + assert t1.m_t1 == 111 t1.__destruct__() - #----- + # ----- t2 = gbl.T2(gbl.T1(int))(gbl.T1(int)(32)) t2.m_t2.m_t1 = 32 assert t2.m_t2.get_value() == 32 - assert t2.m_t2.m_t1 == 32 + assert t2.m_t2.m_t1 == 32 t2.__destruct__() - def test05_abstract_classes(self): """Test non-instatiatability of abstract classes""" import cppyy + gbl = cppyy.gbl raises(TypeError, gbl.a_class) @@ -262,11 +269,12 @@ def test06_datamembers(self): """Test data member access when using virtual inheritence""" import cppyy - a_class = cppyy.gbl.a_class - b_class = cppyy.gbl.b_class + + a_class = cppyy.gbl.a_class + b_class = cppyy.gbl.b_class c_class_1 = cppyy.gbl.c_class_1 c_class_2 = cppyy.gbl.c_class_2 - d_class = cppyy.gbl.d_class + d_class = cppyy.gbl.d_class assert issubclass(b_class, a_class) assert issubclass(c_class_1, a_class) @@ -277,78 +285,78 @@ def test06_datamembers(self): assert issubclass(d_class, b_class) assert issubclass(d_class, c_class_2) - #----- + # ----- b = b_class() - assert b.m_a == 1 - assert b.m_da == 1.1 - assert b.m_b == 2 - assert b.m_db == 2.2 + assert b.m_a == 1 + assert b.m_da == 1.1 + assert b.m_b == 2 + assert b.m_db == 2.2 b.m_a = 11 - assert b.m_a == 11 - assert b.m_b == 2 + assert b.m_a == 11 + assert b.m_b == 2 b.m_da = 11.11 - assert b.m_da == 11.11 - assert b.m_db == 2.2 + assert b.m_da == 11.11 + assert b.m_db == 2.2 b.m_b = 22 - assert b.m_a == 11 - assert b.m_da == 11.11 - assert b.m_b == 22 - assert b.get_value() == 22 + assert b.m_a == 11 + assert b.m_da == 11.11 + assert b.m_b == 22 + assert b.get_value() == 22 b.m_db = 22.22 - assert b.m_db == 22.22 + assert b.m_db == 22.22 b.__destruct__() - #----- + # ----- c1 = c_class_1() - assert c1.m_a == 1 - assert c1.m_b == 2 - assert c1.m_c == 3 + assert c1.m_a == 1 + assert c1.m_b == 2 + assert c1.m_c == 3 c1.m_a = 11 - assert c1.m_a == 11 + assert c1.m_a == 11 c1.m_b = 22 - assert c1.m_a == 11 - assert c1.m_b == 22 + assert c1.m_a == 11 + assert c1.m_b == 22 c1.m_c = 33 - assert c1.m_a == 11 - assert c1.m_b == 22 - assert c1.m_c == 33 + assert c1.m_a == 11 + assert c1.m_b == 22 + assert c1.m_c == 33 assert c1.get_value() == 33 c1.__destruct__() - #----- + # ----- d = d_class() - assert d.m_a == 1 - assert d.m_b == 2 - assert d.m_c == 3 - assert d.m_d == 4 + assert d.m_a == 1 + assert d.m_b == 2 + assert d.m_c == 3 + assert d.m_d == 4 d.m_a = 11 - assert d.m_a == 11 + assert d.m_a == 11 d.m_b = 22 - assert d.m_a == 11 - assert d.m_b == 22 + assert d.m_a == 11 + assert d.m_b == 22 d.m_c = 33 - assert d.m_a == 11 - assert d.m_b == 22 - assert d.m_c == 33 + assert d.m_a == 11 + assert d.m_b == 22 + assert d.m_c == 33 d.m_d = 44 - assert d.m_a == 11 - assert d.m_b == 22 - assert d.m_c == 33 - assert d.m_d == 44 - assert d.get_value() == 44 + assert d.m_a == 11 + assert d.m_b == 22 + assert d.m_c == 33 + assert d.m_d == 44 + assert d.get_value() == 44 d.__destruct__() @@ -356,19 +364,20 @@ def test07_pass_by_reference(self): """Test reference passing when using virtual inheritance""" import cppyy + gbl = cppyy.gbl b_class = gbl.b_class c_class = gbl.c_class_2 d_class = gbl.d_class - #----- + # ----- b = b_class() b.m_a, b.m_b = 11, 22 assert gbl.get_a(b) == 11 assert gbl.get_b(b) == 22 b.__destruct__() - #----- + # ----- c = c_class() c.m_a, c.m_b, c.m_c = 11, 22, 33 assert gbl.get_a(c) == 11 @@ -376,7 +385,7 @@ def test07_pass_by_reference(self): assert gbl.get_c(c) == 33 c.__destruct__() - #----- + # ----- d = d_class() d.m_a, d.m_b, d.m_c, d.m_d = 11, 22, 33, 44 assert gbl.get_a(d) == 11 @@ -389,7 +398,8 @@ def test08_void_pointer_passing(self): """Test passing of variants of void pointer arguments""" import cppyy - pointer_pass = cppyy.gbl.pointer_pass + + pointer_pass = cppyy.gbl.pointer_pass some_concrete_class = cppyy.gbl.some_concrete_class pp = pointer_pass() @@ -400,9 +410,10 @@ def test08_void_pointer_passing(self): assert cppyy.addressof(o) == pp.gime_address_ptr_ref(o) if IS_WINDOWS != 64: - # there is no 8-byte integer type array on Windows 64b + # there is no 8-byte integer type array on Windows 64b import array - addressofo = array.array('l', [cppyy.addressof(o)]) + + addressofo = array.array("l", [cppyy.addressof(o)]) assert addressofo[0] == pp.gime_address_ptr_ptr(addressofo) assert 0 == pp.gime_address_ptr(0) @@ -413,8 +424,8 @@ def test08_void_pointer_passing(self): pp.set_address_ptr_ref(ptr) assert cppyy.addressof(ptr) == 0x1234 - # alternate path through static method handling, which does NOT - # provide 'pp' as argument (and thus need no removing) + # alternate path through static method handling, which does NOT + # provide 'pp' as argument (and thus need no removing) sf = pp.set_address_ptr_ref sf(ptr) assert cppyy.addressof(ptr) == 0x1234 @@ -424,12 +435,13 @@ def test08_void_pointer_passing(self): assert cppyy.addressof(cppyy.nullptr) == 0 raises(TypeError, cppyy.addressof, None) - assert cppyy.addressof(0) == 0 + assert cppyy.addressof(0) == 0 def test09_opaque_pointer_passing(self): """Test passing around of opaque pointers""" import cppyy + some_concrete_class = cppyy.gbl.some_concrete_class o = some_concrete_class() @@ -437,13 +449,13 @@ def test09_opaque_pointer_passing(self): # TODO: figure out the PyPy equivalent of CObject (may have to do this # through the C-API from C++) - #cobj = cppyy.as_cobject(o) + # cobj = cppyy.as_cobject(o) addr = cppyy.addressof(o) - #assert o == cppyy.bind_object(cobj, some_concrete_class) - #assert o == cppyy.bind_object(cobj, type(o)) - #assert o == cppyy.bind_object(cobj, o.__class__) - #assert o == cppyy.bind_object(cobj, "some_concrete_class") + # assert o == cppyy.bind_object(cobj, some_concrete_class) + # assert o == cppyy.bind_object(cobj, type(o)) + # assert o == cppyy.bind_object(cobj, o.__class__) + # assert o == cppyy.bind_object(cobj, "some_concrete_class") assert cppyy.addressof(o) == cppyy.addressof(cppyy.bind_object(addr, some_concrete_class)) assert o is cppyy.bind_object(addr, some_concrete_class) assert o is cppyy.bind_object(addr, type(o)) @@ -456,7 +468,8 @@ def test10_object_identity(self): """Test object identity""" import cppyy - some_concrete_class = cppyy.gbl.some_concrete_class + + some_concrete_class = cppyy.gbl.some_concrete_class some_class_with_data = cppyy.gbl.some_class_with_data o = some_concrete_class() @@ -504,15 +517,16 @@ def test11_multi_methods(self): """Test calling of methods from multiple inheritance""" import cppyy + multi = cppyy.gbl.multi assert cppyy.gbl.multi1 is multi.__bases__[0] assert cppyy.gbl.multi2 is multi.__bases__[1] dict_keys = list(multi.__dict__.keys()) - assert dict_keys.count('get_my_own_int') == 1 - assert dict_keys.count('get_multi1_int') == 0 - assert dict_keys.count('get_multi2_int') == 0 + assert dict_keys.count("get_my_own_int") == 1 + assert dict_keys.count("get_multi1_int") == 0 + assert dict_keys.count("get_multi2_int") == 0 m = multi(1, 2, 3) assert m.get_multi1_int() == 1 @@ -523,6 +537,7 @@ def test12_actual_type(self): """Test that a pointer to base return does an auto-downcast""" import cppyy + base_class = cppyy.gbl.base_class base_class.clone.__creates__ = True derived_class = cppyy.gbl.derived_class @@ -545,7 +560,7 @@ def test12_actual_type(self): assert isinstance(b.cycle(d), derived_class) assert isinstance(d.cycle(d), derived_class) - base_class.clone.__creates__ = True + base_class.clone.__creates__ = True assert isinstance(b.clone(), base_class) derived_class.clone.__creates__ = True assert isinstance(d.clone(), derived_class) @@ -592,6 +607,7 @@ def test14_new_overloader(self): nl.__destruct__() import gc + gc.collect() assert cppyy.gbl.new_overloader.s_instances == 0 @@ -608,6 +624,7 @@ def test15_template_instantiation_with_vector_of_float(self): b.m_b.push_back(i) assert round(b.m_b[i], 5) == float(i) + @mark.xfail(reason="fails on CppInterOp") def test16_template_global_functions(self): """Test template global function lookup and calls""" @@ -615,16 +632,16 @@ def test16_template_global_functions(self): f = cppyy.gbl.my_templated_function - assert f('c') == 'c' - assert type(f('c')) == type('c') - assert f(3.) == 3. - assert type(f(4.)) == type(4.) + assert f("c") == "c" + assert type(f("c")) == type("c") + assert f(3.0) == 3.0 + assert type(f(4.0)) == type(4.0) def test17_assign_to_return_byref(self): """Test assignment to an instance returned by reference""" if ispypy: - skip('segfaults in pypy') + skip("segfaults in pypy") from cppyy import gbl @@ -645,14 +662,14 @@ def test18_math_converters(self): a = gbl.some_convertible() a.m_i = 1234 - a.m_d = 4321. + a.m_d = 4321.0 - assert int(a) == 1234 - assert int(a) == a.m_i + assert int(a) == 1234 + assert int(a) == a.m_i assert pylong(a) == a.m_i - assert float(a) == 4321. - assert float(a) == a.m_d + assert float(a) == 4321.0 + assert float(a) == a.m_d def test19_comparator(self): """Check that the global operator!=/== is picked up""" @@ -680,18 +697,18 @@ def test20_overload_order_with_proper_return(self): assert cppyy.gbl.overload_one_way().gime() == 1 assert cppyy.gbl.overload_the_other_way().gime() == "aap" - @mark.xfail(strict=True) + @mark.xfail() def test21_access_to_global_variables(self): """Access global_variables_and_pointers""" import cppyy - assert cppyy.gbl.my_global_double == 12. + assert cppyy.gbl.my_global_double == 12.0 assert len(cppyy.gbl.my_global_array) == 500 assert cppyy.gbl.my_global_string1 == "aap noot mies" assert cppyy.gbl.my_global_string2 == "zus jet teun" assert list(cppyy.gbl.my_global_string3) == ["aap", "noot", "mies"] - assert cppyy.gbl.my_global_ptr[0] == 1234. + assert cppyy.gbl.my_global_ptr[0] == 1234.0 v = cppyy.gbl.my_global_int_holders assert len(v) == 5 @@ -719,9 +736,10 @@ def test22_exceptions(self): """Catching of C++ exceptions""" import cppyy + Thrower = cppyy.gbl.Thrower - Thrower.throw_anything.__useffi__ = False + Thrower.throw_anything.__useffi__ = False Thrower.throw_exception.__useffi__ = False t = Thrower() @@ -742,35 +760,35 @@ def test23_using(self): import cppyy - assert cppyy.gbl.UsingBase1().vcheck() == 'A' + assert cppyy.gbl.UsingBase1().vcheck() == "A" D1 = cppyy.gbl.UsingDerived1 - assert not 'UsingBase1' in D1.__init__.__doc__ + # assert not 'UsingBase1' in D1.__init__.__doc__ d1a = D1() - assert d1a.m_int == 13 - assert d1a.m_int2 == 42 - assert d1a.vcheck() == 'B' + assert d1a.m_int == 13 + assert d1a.m_int2 == 42 + assert d1a.vcheck() == "B" d1b = D1(10) - assert d1b.m_int == 10 - assert d1b.m_int2 == 42 - assert d1b.vcheck() == 'B' + assert d1b.m_int == 10 + assert d1b.m_int2 == 42 + assert d1b.vcheck() == "B" d1c = D1(d1b) - assert d1c.m_int == 10 - assert d1c.m_int2 == 42 - assert d1c.vcheck() == 'B' + assert d1c.m_int == 10 + assert d1c.m_int2 == 42 + assert d1c.vcheck() == "B" D2 = cppyy.gbl.UsingDerived2 - assert 'vcheck(int)' in D2.vcheck.__doc__ - assert 'vcheck()' in D2.vcheck.__doc__ + assert "vcheck(int)" in D2.vcheck.__doc__ + assert "vcheck()" in D2.vcheck.__doc__ d2 = D2() - assert d2.vcheck() == 'A' - assert d2.vcheck(1) == 'B' + assert d2.vcheck() == "A" + assert d2.vcheck(1) == "B" - @mark.xfail(strict=True) + @mark.xfail() def test24_typedef_to_private_class(self): """Typedefs to private classes should not resolve""" @@ -778,7 +796,7 @@ def test24_typedef_to_private_class(self): assert cppyy.gbl.TypedefToPrivateClass().f().m_val == 42 - @mark.xfail(strict=True) + @mark.xfail(run=False, reason="toString, I guess") def test25_ostream_printing(self): """Mapping of __str__ through operator<<(ostream&)""" @@ -787,14 +805,16 @@ def test25_ostream_printing(self): ns = cppyy.gbl.Cpp2PyPrinting assert str(ns.Printable1()) == "Printable1::operator<<" - for tst in [(ns.Printable2, "Cpp2PyPrinting::operator<<"), - (ns.Printable3, "::operator<<(3)"), - (cppyy.gbl.Printable4, "::operator<<(4)"), - (cppyy.gbl.Printable6, "Printable6")]: + for tst in [ + (ns.Printable2, "Cpp2PyPrinting::operator<<"), + (ns.Printable3, "::operator<<(3)"), + (cppyy.gbl.Printable4, "::operator<<(4)"), + (cppyy.gbl.Printable6, "Printable6"), + ]: assert str(tst[0]()) == tst[1] - if '__lshiftc__' in tst[0].__dict__: - # only cached for global functions and in principle should - # not be needed anymore ... + if "__lshiftc__" in tst[0].__dict__: + # only cached for global functions and in principle should + # not be needed anymore ... assert tst[0].__lshiftc__ del tst[0].__lshiftc__ assert str(tst[0]()) == tst[1] @@ -803,10 +823,10 @@ def test25_ostream_printing(self): tst[0].__lshiftc__(s, tst[0]()) assert s.str() == tst[1] - # print through base class (used to fail with compilation error) + # print through base class (used to fail with compilation error) assert str(cppyy.gbl.Printable5()) == "Ok." - # print through friend + # print through friend cppyy.cppdef("""\ namespace PrintingNS { class X { @@ -820,10 +840,10 @@ class Y { } """) x = cppyy.gbl.PrintingNS.X() - assert str(x) == 'X' + assert str(x) == "X" y = cppyy.gbl.PrintingNS.Y() - assert str(y) == 'Y' + assert str(y) == "Y" def test26_using_directive(self): """Test using directive in namespaces""" @@ -831,7 +851,7 @@ def test26_using_directive(self): import cppyy assert cppyy.gbl.UserDirs.foo1() == cppyy.gbl.UsedSpace1.foo1() - assert cppyy.gbl.UserDirs.bar() == cppyy.gbl.UsedSpace2.bar() + assert cppyy.gbl.UserDirs.bar() == cppyy.gbl.UsedSpace2.bar() assert cppyy.gbl.UserDirs.foo2() == cppyy.gbl.UsedSpace1.inner.foo2() def test27_shadowed_typedef(self): @@ -857,12 +877,12 @@ def test27_shadowed_typedef(self): ns = cppyy.gbl.ShadowedTypedef - ns.A.Ptr # pull A::Ptr first - ns.A.Val # id. A::Val - ns.B.Ptr # used to be A.Ptr through python-side dict lookup - ns.B.Val # id. B::Val - ns.C.Ptr # is A.Ptr - ns.C.Val # is A.Val + ns.A.Ptr # pull A::Ptr first + ns.A.Val # id. A::Val + ns.B.Ptr # used to be A.Ptr through python-side dict lookup + ns.B.Val # id. B::Val + ns.C.Ptr # is A.Ptr + ns.C.Val # is A.Val assert ns.A.Ptr == ns.A.Ptr assert ns.B.Ptr == ns.B.Ptr @@ -872,11 +892,10 @@ def test27_shadowed_typedef(self): # TODO: currently only classes are checked; typedefs of builtin types are # mapped through the type mapper and as such can be anything - #assert ns.A.Val != ns.B.Val - #assert type(ns.A.Val(1)) == int - #assert type(ns.B.Val(1)) == float + # assert ns.A.Val != ns.B.Val + # assert type(ns.A.Val(1)) == int + # assert type(ns.B.Val(1)) == float - @mark.xfail(strict=True) def test28_extern_C_in_namespace(self): """Access to extern "C" declared functions in namespaces""" @@ -893,7 +912,7 @@ def test28_extern_C_in_namespace(self): ns = cppyy.gbl.extern_c_in_ns - assert ns.some_func() == 21 + assert ns.some_func() == 21 assert ns.some_func_xc() == 21 assert ns.deeper.some_other_func_xc() == 42 @@ -901,9 +920,10 @@ def test28_extern_C_in_namespace(self): def test29_castcpp(self): """Allow casting a Python class to a C++ one""" - import cppyy import math + import cppyy + cppyy.cppdef("""\ namespace castcpp { struct MyPoint { @@ -948,7 +968,7 @@ def __cast_cpp__(self): p1 = MyPyPoint1(5, 10) p2 = MyPyPoint2(p1.x, p1.y) p3 = MyPyPoint3(p1.x, p1.y) - pynorm = math.sqrt(p2.x**2+p2.y**2) + pynorm = math.sqrt(p2.x**2 + p2.y**2) for norm in [ns.norm_cr, ns.norm_r, ns.norm_v, ns.norm_p]: with raises(TypeError): @@ -961,4 +981,4 @@ def __cast_cpp__(self): if __name__ == "__main__": - exit(pytest.main(args=['-sv', '-ra', __file__])) + exit(pytest.main(args=["-sv", "-ra", __file__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_api.py b/bindings/pyroot/cppyy/cppyy/test/test_api.py index f30bc4c65d3fe..5cdd8b9f70ef9 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_api.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_api.py @@ -1,6 +1,6 @@ import pytest from pytest import raises, skip -from support import ispypy +from .support import ispypy class TestAPI: diff --git a/bindings/pyroot/cppyy/cppyy/test/test_boost.py b/bindings/pyroot/cppyy/cppyy/test/test_boost.py index 11678bb6062f8..758235bc8d23b 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_boost.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_boost.py @@ -1,6 +1,6 @@ import os, pytest from pytest import mark, raises, skip -from support import setup_make +from .support import setup_make noboost = False if not (os.path.exists(os.path.join(os.path.sep, 'usr', 'include', 'boost')) or \ diff --git a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py b/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py index 748c8fae4533c..700e1d8553b81 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py @@ -1,6 +1,7 @@ import pytest from pytest import raises, skip, mark -from support import IS_MAC_ARM, IS_WINDOWS +from .support import IS_MAC_ARM, IS_WINDOWS + class TestCONCURRENT: @@ -91,7 +92,7 @@ def test03_timeout(self): if t.is_alive(): # was timed-out cppyy.gbl.test12_timeout.stopit[0] = True - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test04_cpp_threading_with_exceptions(self): """Threads and Python exceptions""" @@ -236,6 +237,7 @@ def process(self, data, channels, samples): p = Processor() cppyy.gbl.FloatDim2.callback(p) + @mark.xfail(run=False, reason="Thread-Safety error at CppInterOp") def test06_overload_reuse_in_threads(self): """Threads reuse overload objects; check for clashes""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_conversions.py b/bindings/pyroot/cppyy/cppyy/test/test_conversions.py index 201569539e5d4..b490df4654dc1 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_conversions.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_conversions.py @@ -1,6 +1,6 @@ -import pytest, os +import py, pytest, os from pytest import raises, mark -from support import setup_make, IS_WINDOWS +from .support import setup_make, IS_WINDOWS test_dct = "conversions_cxx" @@ -11,6 +11,7 @@ def setup_class(cls): import cppyy cls.conversion = cppyy.load_reflection_info(cls.test_dct) + @mark.xfail(reason="sumit look fails in ROOT") def test01_implicit_vector_conversions(self): """Test implicit conversions of std::vector""" @@ -83,7 +84,7 @@ def test03_error_handling(self): gc.collect() assert CC.s_count == 0 - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test04_implicit_conversion_from_tuple(self): """Allow implicit conversions from tuples as arguments {}-like""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py b/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py index 71c30153dfe02..d540adf8daef8 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises -from support import setup_make, ispypy, IS_MAC_ARM, IS_WINDOWS +from .support import setup_make, ispypy, IS_MAC_ARM, IS_WINDOWS test_dct = "cpp11features_cxx" @@ -300,7 +300,7 @@ def test08_initializer_list(self): for l in (['x'], ['x', 'y', 'z']): assert ns.foo(l) == std.vector['std::string'](l) - @mark.xfail(strict=True) + @mark.xfail() def test09_lambda_calls(self): """Call (global) lambdas""" @@ -312,10 +312,11 @@ def test09_lambda_calls(self): assert cppyy.gbl.gMyLambda(2) == 42 assert cppyy.gbl.gMyLambda(40) == 80 - cppyy.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") - l1 = cppyy.gbl.gime_a_lambda1() - assert l1 - assert l1() == 42 + if cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) >= 201402: + cppyy.cppdef("auto gime_a_lambda1() { return []() { return 42; }; }") + l1 = cppyy.gbl.gime_a_lambda1() + assert l1 + assert l1() == 42 cppyy.cppdef("auto gime_a_lambda2() { int a = 4; return [a](int b) { return 42+a+b; }; }") l2 = cppyy.gbl.gime_a_lambda2() @@ -332,8 +333,9 @@ def test10_optional(self): import cppyy - assert cppyy.gbl.std.optional - assert cppyy.gbl.std.nullopt + if 201703 <= cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr): + assert cppyy.gbl.std.optional + assert cppyy.gbl.std.nullopt cppyy.cppdef(""" enum Enum { A = -1 }; @@ -357,7 +359,7 @@ def test11_chrono(self): # following used to fail with compilation error t = std.chrono.system_clock.now() + std.chrono.seconds(1) - @mark.xfail(strict=True) + @mark.xfail() def test12_stdfunction(self): """Use of std::function with arguments in a namespace""" @@ -412,7 +414,7 @@ def test13_stdhash(self): assert hash(sw) == 17 assert hash(sw) == 17 - @mark.xfail(strict=True) + @mark.xfail() def test14_shared_ptr_passing(self): """Ability to pass normal pointers through shared_ptr by value""" @@ -438,7 +440,7 @@ def test14_shared_ptr_passing(self): gc.collect() assert TestSmartPtr.s_counter == 0 - @mark.xfail(strict=True, condition=IS_WINDOWS | IS_MAC_ARM, reason='ValueError: Could not find "make_unique"') + @mark.xfail(condition=IS_WINDOWS | IS_MAC_ARM, reason='ValueError: Could not find "make_unique"') def test15_unique_ptr_template_deduction(self): """Argument type deduction with std::unique_ptr""" @@ -458,7 +460,7 @@ def test15_unique_ptr_template_deduction(self): with raises(ValueError): # not an RValue cppyy.gbl.UniqueTempl.returnptr[int](uptr_in) - @mark.xfail(strict=True, condition=IS_WINDOWS | IS_MAC_ARM, reason='TypeError: Could not find "make_unique"') + @mark.xfail(condition=IS_WINDOWS | IS_MAC_ARM, reason='TypeError: Could not find "make_unique"') def test16_unique_ptr_moves(self): """std::unique_ptr requires moves""" @@ -545,7 +547,7 @@ def test18_unique_ptr_identity(self): p2 = c.pget() assert p1 is p2 - @mark.xfail(strict=True) + @mark.xfail() def test19_smartptr_from_callback(self): """Return a smart pointer from a callback""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py b/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py index e3699888d0e9c..3fe60f315deb9 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py @@ -1,9 +1,13 @@ import os, pytest from pytest import raises, skip, mark -from support import setup_make, pylong, IS_MAC_ARM, IS_WINDOWS +from .support import setup_make, pylong, IS_MAC_ARM, IS_WINDOWS test_dct = "crossinheritance_cxx" +def has_asserts(): + import cppyy + + return True # "asserts" in cppyy.gbl.gROOT.GetConfigFeatures() class TestCROSSINHERITANCE: def setup_class(cls): @@ -11,7 +15,7 @@ def setup_class(cls): import cppyy cls.example01 = cppyy.load_reflection_info(cls.test_dct) - @mark.xfail(strict=True) + @mark.xfail(run=False, reason="fails on ROOT, but crashes with InterOp") def test01_override_function(self): """Test ability to override a simple function""" @@ -225,7 +229,7 @@ def get_value(self): p1 = TPyDerived1() assert p1.get_value() == 13 - @mark.xfail(strict=True, condition=IS_MAC_ARM | IS_WINDOWS, reason = "Crashes on OS X ARM with" \ + @mark.xfail(condition=IS_MAC_ARM | IS_WINDOWS, reason = "Crashes on OS X ARM with" \ "libc++abi: terminating due to uncaught exception") def test08_error_handling(self): """Python errors should propagate through wrapper""" @@ -476,7 +480,7 @@ class MyPyDerived3(VD.MyClass3): class MyPyDerived4(VD.MyClass4[int]): pass - @mark.xfail(strict=True) + @mark.xfail() def test14_protected_access(self): """Derived classes should have access to protected members""" @@ -1021,7 +1025,7 @@ def return_const(self): assert a.return_const().m_value == "abcdef" assert ns.callit(a).m_value == "abcdef" - @mark.xfail(strict=True) + @mark.xfail() def test24_non_copyable(self): """Inheriting from a non-copyable base class""" @@ -1389,7 +1393,7 @@ class Base { class PyDerived(ns.Base): pass - @mark.xfail(strict=True) + @mark.xfail() def test31_object_rebind(self): """Usage of bind_object to cast with Python derived objects""" @@ -1547,7 +1551,7 @@ def extra_func(self, d): assert p.func(d) == 42 + 2 * d.value - @mark.xfail(strict=True) + @mark.xfail() def test33_direct_base_methods(self): """Call base class methods directly""" @@ -1779,7 +1783,7 @@ def f3(self): assert pysub.f3() == "Python: PySub::f3()" assert ns.call_fs(pysub) == pysub.f1() + pysub.f2() + pysub.f3() - @mark.xfail(strict=True) + @mark.xfail() def test38_protected_data(self): """Multiple cross inheritance with protected data""" @@ -1817,6 +1821,7 @@ def __init__(self, x, y, z): assert derived.s == "Hello" assert derived.t == "World" + @mark.xfail(run=False, condition=has_asserts(), reason="Transaction.cpp:98: Assertion `!m_Unloading && \"Must not nest within unloading transaction\"' failed.") def test39_returning_multi_keyword_types(self): """Supporting dispatcher for functions that return multi-keyword types like `unsigned int`""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py b/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py index f82d79969c8d5..f80f1f2472b47 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises, skip -from support import setup_make, pylong, pyunicode, IS_MAC, IS_MAC_ARM, IS_WINDOWS +from .support import setup_make, pylong, pyunicode, IS_MAC, IS_MAC_ARM, IS_WINDOWS test_dct = "datatypes_cxx" @@ -8,13 +8,13 @@ def has_cpp_20(): import cppyy - return cppyy.gbl.gInterpreter.ProcessLine("__cplusplus;") >= 202002 + return cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) >= 202002 def is_modules_off(): import cppyy - return "runtime_cxxmodules" not in cppyy.gbl.gROOT.GetConfigFeatures() + return True # "runtime_cxxmodules" not in cppyy.gbl.gROOT.GetConfigFeatures() class TestDATATYPES: @@ -25,7 +25,11 @@ def setup_class(cls): cls.datatypes = cppyy.load_reflection_info(cls.test_dct) cls.N = cppyy.gbl.N - @mark.xfail(strict=True) + at_least_17 = 201402 < cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) + cls.has_byte = at_least_17 + cls.has_optional = at_least_17 + + @mark.xfail() def test01_instance_data_read_access(self): """Read access to instance public data and verify values""" @@ -196,7 +200,7 @@ def test01_instance_data_read_access(self): c.__destruct__() - @mark.xfail(strict=True) + @mark.xfail() def test02_instance_data_write_access(self): """Test write access to instance public data and verify values""" @@ -733,7 +737,7 @@ def test10_enum(self): assert gbl.EnumSpace.AA == 1 assert gbl.EnumSpace.BB == 2 - @mark.xfail(strict=True) + @mark.xfail() def test11_typed_enums(self): """Determine correct types of enums""" @@ -776,7 +780,7 @@ class Test { assert type(sc.vraioufaux.faux) == bool # no bool as base class assert isinstance(sc.vraioufaux.faux, bool) - @mark.xfail(strict=True) + @mark.xfail() def test12_enum_scopes(self): """Enum accessibility and scopes""" @@ -1102,7 +1106,7 @@ def test21_object_validity(self): assert not d2 - @mark.xfail(strict=True) + @mark.xfail() def test22_buffer_shapes(self): """Correctness of declared buffer shapes""" @@ -1266,7 +1270,7 @@ def run(self, f, buf, total): run(self, cppyy.gbl.sum_uc_data, buf, total) run(self, cppyy.gbl.sum_byte_data, buf, total) - @mark.xfail(strict=True, run=not IS_MAC and not IS_WINDOWS, reason="Fails on all platforms; crashes on macOS with " \ + @mark.xfail(run=not IS_MAC and not IS_WINDOWS, reason="Fails on all platforms; crashes on macOS with " \ "libc++abi: terminating due to uncaught exception") def test26_function_pointers(self): """Function pointer passing""" @@ -1545,7 +1549,7 @@ def test30_multi_dim_arrays_of_builtins(test): p = (ctype * len(buf)).from_buffer(buf) assert [p[j] for j in range(width*height)] == [2*j for j in range(width*height)] - @mark.xfail(strict=True) + @mark.xfail() def test31_anonymous_union(self): """Anonymous unions place there fields in the parent scope""" @@ -1639,7 +1643,7 @@ def test31_anonymous_union(self): assert type(p.data_c[0]) == float assert p.intensity == 5. - @mark.xfail(strict=True) + @mark.xfail() def test32_anonymous_struct(self): """Anonymous struct creates an unnamed type""" @@ -1688,7 +1692,7 @@ class Foo2 { assert 'foo' in dir(ns.libuntitled1_ExportedSymbols().kotlin.root.com.justamouse.kmmdemo) - @mark.xfail(strict=True) + @mark.xfail() def test33_pointer_to_array(self): """Usability of pointer to array""" @@ -1954,7 +1958,7 @@ def test38_plain_old_data(self): assert len(f1.fPtrArr) == 3 assert list(f1.fPtrArr) == [1., 2., 3] - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Test doesn't work on Windows") + @mark.xfail(condition=IS_WINDOWS, reason="Test doesn't work on Windows") def test39_aggregates(self): """Initializer construction of aggregates""" @@ -2049,7 +2053,7 @@ def test40_more_aggregates(self, capfd): output = (captured.out + captured.err).lower() assert "error:" not in output - @mark.xfail(strict=True) + @mark.xfail() def test41_complex_numpy_arrays(self, capfd): """Usage of complex numpy arrays""" @@ -2103,7 +2107,7 @@ def pycompdot(a, b, N): output = (captured.out + captured.err).lower() assert "call to implicitly-deleted copy constructor" not in output - @mark.xfail(strict=True, condition=IS_MAC or IS_WINDOWS, reason="Argument conversion error on macOS and Windows") + @mark.xfail(condition=IS_MAC or IS_WINDOWS, reason="Argument conversion error on macOS and Windows") def test42_mixed_complex_arithmetic(self): """Mixin of Python and C++ std::complex in arithmetic""" @@ -2233,7 +2237,7 @@ def test45_const_ref_data(self): b = ns.B() assert b.body1.name == b.body2.name - @mark.xfail(strict=True) + @mark.xfail() def test46_small_int_enums(self): """Proper typing of small int enums""" @@ -2288,7 +2292,7 @@ def test46_small_int_enums(self): assert ns.func_int8() == -1 assert ns.func_uint8() == 255 - @mark.xfail(strict=True) + @mark.xfail() def test47_hidden_name_enum(self): """Usage of hidden name enum""" @@ -2351,7 +2355,7 @@ def test48_bool_typemap(self): assert str(bt(1)) == 'True' assert str(bt(0)) == 'False' - @mark.xfail(strict=True, run=not IS_WINDOWS, condition=IS_MAC_ARM or (not has_cpp_20() and is_modules_off()), reason="Crashes on mac-beta ARM64 and fails on Windows \ + @mark.xfail(run=not IS_WINDOWS, condition=IS_MAC_ARM or (not has_cpp_20() and is_modules_off()), reason="Crashes on mac-beta ARM64 and fails on Windows \ assertion error for runtime_cxxmodules=OFF build that is explained in GitHub issue #21005") def test49_addressof_method(self): """Use of addressof for (const) methods""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py b/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py index e4f6d01bc36b8..c4279e6f2cef5 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises, skip -from support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM +from .support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM test_dct = "doc_helper_cxx" @@ -248,8 +248,8 @@ def test_keyword_arguments(self): def test_doc_strings(self): import cppyy from cppyy.gbl import Concrete - assert 'void Concrete::array_method(int* ad, int size)' in Concrete.array_method.__doc__ - assert 'void Concrete::array_method(double* ad, int size)' in Concrete.array_method.__doc__ + assert 'void Concrete::array_method(int* ad, int size)'.replace(" ", "") in Concrete.array_method.__doc__.replace(" ", "") + assert 'void Concrete::array_method(double* ad, int size)'.replace(" ", "") in Concrete.array_method.__doc__.replace(" ", "") def test_enums(self): import cppyy @@ -781,7 +781,7 @@ def test02_use_c_void_p(self): Advert02.Picam_OpenFirstCamera(cam) assert Advert02.Picam_CloseCamera(cam) - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test03_use_of_ctypes_and_enum(self): """Use of (opaque) enum through ctypes.c_void_p""" @@ -1123,7 +1123,7 @@ def add(self, i): assert v.back().add(17) == 4+42+2*17 - @mark.xfail(strict=True) + @mark.xfail() def test_fallbacks(self): """Template instantation switches based on value sizes""" @@ -1168,7 +1168,7 @@ def f(val): assert CC.callPtr(lambda i: 5*i, 4) == 20 assert CC.callFun(lambda i: 6*i, 4) == 24 - @mark.xfail(strict=True) + @mark.xfail() def test_templated_callback(self): """Templated callback example""" @@ -1221,7 +1221,7 @@ def test_autocast_and_identiy(self): assert type(b) == CC.Derived assert d is b - @mark.xfail(strict=True, condition=IS_WINDOWS, reason = "Crashes on Windows") + @mark.xfail(condition=IS_WINDOWS, reason = "Crashes on Windows") def test_exceptions(self): """Exceptions example""" @@ -1246,7 +1246,7 @@ class MyException : public std::exception { with raises(CC.MyException): CC.throw_error() - @mark.xfail(strict=True) + @mark.xfail() def test_unicode(self): """Unicode non-UTF-8 example""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_eigen.py b/bindings/pyroot/cppyy/cppyy/test/test_eigen.py index 58aecc872f1eb..a970591a14891 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_eigen.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_eigen.py @@ -1,6 +1,6 @@ import py, os, pytest from pytest import mark, raises -from support import setup_make +from .support import setup_make inc_paths = [os.path.join(os.path.sep, 'usr', 'include'), os.path.join(os.path.sep, 'usr', 'local', 'include')] @@ -100,7 +100,7 @@ def test02_comma_insertion(self): for i in range(5): assert v(i) == i+1 - @mark.xfail(strict=True) + @mark.xfail() def test03_matrices_and_vectors(self): """Matrices and vectors""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_fragile.py b/bindings/pyroot/cppyy/cppyy/test/test_fragile.py index b1a07477aa394..4fe91449150f6 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_fragile.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_fragile.py @@ -1,7 +1,10 @@ -import os, sys, pytest +import os +import sys + +import pytest from pytest import mark, raises, skip -from support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM +from .support import IS_MAC_ARM, IS_WINDOWS, ispypy, setup_make test_dct = "fragile_cxx" @@ -9,30 +12,33 @@ def has_cpp_20(): import cppyy - return cppyy.gbl.gInterpreter.ProcessLine("__cplusplus;") >= 202002 + return cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) >= 202002 + def has_asserts(): import cppyy - return "asserts" in cppyy.gbl.gROOT.GetConfigFeatures() + return True # "asserts" in cppyy.gbl.gROOT.GetConfigFeatures() def is_modules_off(): import cppyy - return "runtime_cxxmodules" not in cppyy.gbl.gROOT.GetConfigFeatures() + return True # "runtime_cxxmodules" not in cppyy.gbl.gROOT.GetConfigFeatures() class TestFRAGILE: def setup_class(cls): cls.test_dct = test_dct import cppyy + cls.fragile = cppyy.load_reflection_info(cls.test_dct) def test01_load_failure(self): """Test failure to load dictionary""" import cppyy + raises(RuntimeError, cppyy.load_reflection_info, "does_not_exist") try: @@ -56,16 +62,16 @@ def test02_missing_classes(self): assert fragile.C is fragile.C assert fragile.C == fragile.C - assert fragile.C().check() == ord('C') + assert fragile.C().check() == ord("C") assert fragile.B is fragile.B assert fragile.B == fragile.B - assert fragile.B().check() == ord('B') + assert fragile.B().check() == ord("B") assert not fragile.B().gime_no_such() assert fragile.C is fragile.C assert fragile.C == fragile.C - assert fragile.C().check() == ord('C') + assert fragile.C().check() == ord("C") raises(TypeError, fragile.C().use_no_such, None) def test03_arguments(self): @@ -77,13 +83,13 @@ def test03_arguments(self): fragile = cppyy.gbl.fragile assert fragile.D == fragile.D - assert fragile.D().check() == ord('D') + assert fragile.D().check() == ord("D") d = fragile.D() raises(TypeError, d.overload, None) raises(TypeError, d.overload, None, None, None) - d.overload('a') + d.overload("a") d.overload(1) def test04_unsupported_arguments(self): @@ -95,13 +101,13 @@ def test04_unsupported_arguments(self): fragile = cppyy.gbl.fragile assert fragile.E == fragile.E - assert fragile.E().check() == ord('E') + assert fragile.E().check() == ord("E") e = fragile.E() raises(TypeError, e.overload, None) # allowing access to e.m_pp_no_such is debatable, but it allows a typed address # to be passed back into C++, which may be useful ... - assert cppyy.addressof(e.m_pp_no_such[0]) == 0xdead + assert cppyy.addressof(e.m_pp_no_such[0]) == 0xDEAD def test05_wrong_arg_addressof(self): """Test addressof() error reporting""" @@ -112,7 +118,7 @@ def test05_wrong_arg_addressof(self): fragile = cppyy.gbl.fragile assert fragile.F == fragile.F - assert fragile.F().check() == ord('F') + assert fragile.F().check() == ord("F") f = fragile.F() o = object() @@ -122,7 +128,7 @@ def test05_wrong_arg_addressof(self): raises(TypeError, cppyy.addressof, 1) # regression (m_int is 0 by default, but its address is not) - assert cppyy.addressof(f, 'm_int') + assert cppyy.addressof(f, "m_int") # see also test08_void_pointer_passing in test_advancedcpp.py @@ -135,10 +141,10 @@ def test06_wrong_this(self): fragile = cppyy.gbl.fragile a = fragile.A() - assert fragile.A.check(a) == ord('A') + assert fragile.A.check(a) == ord("A") b = fragile.B() - assert fragile.B.check(b) == ord('B') + assert fragile.B.check(b) == ord("B") raises(TypeError, fragile.A.check, b) raises(TypeError, fragile.B.check, a) @@ -180,7 +186,7 @@ def test09_operator_bool(self): g = cppyy.gbl.fragile.gI assert not g - @mark.xfail(strict=True) + @mark.xfail() def test10_documentation(self): """Check contents of documentation""" @@ -191,7 +197,7 @@ def test10_documentation(self): d = fragile.D() try: - d.check(None) # raises TypeError + d.check(None) # raises TypeError assert 0 except TypeError as e: assert "fragile::D::check()" in str(e) @@ -199,7 +205,7 @@ def test10_documentation(self): assert "TypeError: takes at least 2 arguments (1 given)" in str(e) try: - d.overload(None) # raises TypeError + d.overload(None) # raises TypeError assert 0 except TypeError as e: # TODO: pypy-c does not indicate which argument failed to convert, CPython does @@ -207,11 +213,11 @@ def test10_documentation(self): assert "fragile::D::overload()" in str(e) assert "TypeError: takes at most 0 arguments (1 given)" in str(e) assert "fragile::D::overload(fragile::no_such_class*)" in str(e) - #assert "no converter available for 'fragile::no_such_class*'" in str(e) + # assert "no converter available for 'fragile::no_such_class*'" in str(e) assert "void fragile::D::overload(char, int i = 0)" in str(e) - #assert "char or small int type expected" in str(e) + # assert "char or small int type expected" in str(e) assert "void fragile::D::overload(int, fragile::no_such_class* p = 0)" in str(e) - #assert "int/long conversion expects an integer object" in str(e) + # assert "int/long conversion expects an integer object" in str(e) j = fragile.J() assert fragile.J.method1.__doc__ == j.method1.__doc__ @@ -221,32 +227,32 @@ def test10_documentation(self): assert f.__doc__ == "void fragile::fglobal(int, double, char)" try: - o = fragile.O() # raises TypeError + o = fragile.O() # raises TypeError assert 0 except TypeError as e: assert "cannot instantiate abstract class 'fragile::O'" in str(e) - @mark.xfail(strict=True) + @mark.xfail() def test11_dir(self): """Test __dir__ method""" import cppyy members = dir(cppyy.gbl.fragile) - assert 'A' in members - assert 'B' in members - assert 'C' in members - assert 'D' in members # classes + assert "A" in members + assert "B" in members + assert "C" in members + assert "D" in members # classes - assert 'nested1' in members # namespace + assert "nested1" in members # namespace # TODO: think this through ... probably want this, but interferes with # the (new) policy of lazy lookups - #assert 'fglobal' in members # function - assert 'gI'in members # variable + # assert 'fglobal' in members # function + assert "gI" in members # variable - # GetAllCppNames() behaves differently from python dir() but providing the full - # set, which is then filtered in dir(); check both + # GetAllCppNames() behaves differently from python dir() but providing the full + # set, which is then filtered in dir(); check both cppyy.cppdef("""\ #ifdef _MSC_VER #define CPPYY_IMPORT extern __declspec(dllimport) @@ -271,10 +277,8 @@ def test11_dir(self): S(int a, int c): _a{a}, _c{c} { } S(): _a{0}, _c{0} { } bool operator<(int i) { return i < (_a+_c); } - }; }"""); - - - assert 'S' in dir(cppyy.gbl.GG) + }; }""") + assert "S" in dir(cppyy.gbl.GG) handle = cppyy.gbl.Cppyy.GetScope("GG::S") assert handle @@ -282,19 +286,19 @@ def test11_dir(self): cppnames = cppyy.gbl.std.set[str]() cppyy.gbl.Cppyy.GetAllCppNames(handle, cppnames) - assert 'S' in cppnames - assert '_a' in cppnames - assert '_c' in cppnames + assert "S" in cppnames + assert "_a" in cppnames + assert "_c" in cppnames - assert 'operator<' in cppnames + assert "operator<" in cppnames dirS = dir(cppyy.gbl.GG.S) - assert 'S' not in dirS # is __init__ - assert '_a' in dirS - assert '_c' in dirS + assert "S" not in dirS # is __init__ + assert "_a" in dirS + assert "_c" in dirS - assert 'operator<' not in dirS + assert "operator<" not in dirS def test12_imports(self): """Test ability to import from namespace (or fail with ImportError)""" @@ -307,9 +311,11 @@ def test12_imports(self): def fail_import(): from cppyy.gbl import does_not_exist + raises(ImportError, fail_import) from cppyy.gbl.fragile import A, B, C, D + assert cppyy.gbl.fragile.A is A assert cppyy.gbl.fragile.B is B assert cppyy.gbl.fragile.C is C @@ -318,52 +324,56 @@ def fail_import(): # according to warnings, can't test "import *" ... from cppyy.gbl.fragile import nested1 + assert cppyy.gbl.fragile.nested1 is nested1 - assert nested1.__name__ == 'nested1' - assert nested1.__module__ == 'cppyy.gbl.fragile' - assert nested1.__cpp_name__ == 'fragile::nested1' + assert nested1.__name__ == "nested1" + assert nested1.__module__ == "cppyy.gbl.fragile" + assert nested1.__cpp_name__ == "fragile::nested1" from cppyy.gbl.fragile.nested1 import A, nested2 + assert cppyy.gbl.fragile.nested1.A is A - assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1' - assert A.__cpp_name__ == 'fragile::nested1::A' + assert A.__name__ == "A" + assert A.__module__ == "cppyy.gbl.fragile.nested1" + assert A.__cpp_name__ == "fragile::nested1::A" assert cppyy.gbl.fragile.nested1.nested2 is nested2 - assert nested2.__name__ == 'nested2' - assert nested2.__module__ == 'cppyy.gbl.fragile.nested1' - assert nested2.__cpp_name__ == 'fragile::nested1::nested2' + assert nested2.__name__ == "nested2" + assert nested2.__module__ == "cppyy.gbl.fragile.nested1" + assert nested2.__cpp_name__ == "fragile::nested1::nested2" from cppyy.gbl.fragile.nested1.nested2 import A, nested3 + assert cppyy.gbl.fragile.nested1.nested2.A is A - assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1.nested2' - assert A.__cpp_name__ == 'fragile::nested1::nested2::A' + assert A.__name__ == "A" + assert A.__module__ == "cppyy.gbl.fragile.nested1.nested2" + assert A.__cpp_name__ == "fragile::nested1::nested2::A" assert cppyy.gbl.fragile.nested1.nested2.nested3 is nested3 - assert nested3.__name__ == 'nested3' - assert nested3.__module__ == 'cppyy.gbl.fragile.nested1.nested2' - assert nested3.__cpp_name__ == 'fragile::nested1::nested2::nested3' + assert nested3.__name__ == "nested3" + assert nested3.__module__ == "cppyy.gbl.fragile.nested1.nested2" + assert nested3.__cpp_name__ == "fragile::nested1::nested2::nested3" from cppyy.gbl.fragile.nested1.nested2.nested3 import A + assert cppyy.gbl.fragile.nested1.nested2.nested3.A is nested3.A - assert A.__name__ == 'A' - assert A.__module__ == 'cppyy.gbl.fragile.nested1.nested2.nested3' - assert A.__cpp_name__ == 'fragile::nested1::nested2::nested3::A' + assert A.__name__ == "A" + assert A.__module__ == "cppyy.gbl.fragile.nested1.nested2.nested3" + assert A.__cpp_name__ == "fragile::nested1::nested2::nested3::A" # test writability of __module__ nested3.__module__ = "peanut butter" assert nested3.__module__ == "peanut butter" # classes in namespace should inherit - assert A.__module__ == 'peanut butter.nested3' - assert 'peanut butter' in repr(A) - assert 'class' in repr(A) - assert 'peanut butter' in repr(nested3) - assert 'namespace' in repr(nested3) + assert A.__module__ == "peanut butter.nested3" + assert "peanut butter" in repr(A) + assert "class" in repr(A) + assert "peanut butter" in repr(nested3) + assert "namespace" in repr(nested3) # as should objects a = A() - assert 'peanut butter' in repr(a) - assert 'object' in repr(a) + assert "peanut butter" in repr(a) + assert "object" in repr(a) def test13_missing_casts(self): """Test proper handling when a hierarchy is not fully available""" @@ -425,42 +435,41 @@ def test16_opaque_handle(self): assert cppyy.addressof(handle) == 0x42 raises(TypeError, cppyy.gbl.fragile.OpaqueType) - assert not 'OpaqueType' in cppyy.gbl.fragile.__dict__ + assert not "OpaqueType" in cppyy.gbl.fragile.__dict__ handle = cppyy.gbl.fragile.OpaqueHandle_t() assert not handle - addr = cppyy.gbl.fragile.create_handle(handle); + addr = cppyy.gbl.fragile.create_handle(handle) assert addr assert not not handle - assert cppyy.gbl.fragile.destroy_handle(handle, addr); - + assert cppyy.gbl.fragile.destroy_handle(handle, addr) # now define OpaqueType cppyy.cppdef("namespace fragile { class OpaqueType { public: int m_int; }; }") # get fresh (should not have been cached while incomplete) o = cppyy.gbl.fragile.OpaqueType() - assert hasattr(o, 'm_int') + assert hasattr(o, "m_int") - assert 'OpaqueType' in cppyy.gbl.fragile.__dict__ + assert "OpaqueType" in cppyy.gbl.fragile.__dict__ def test17_interactive(self): """Test the usage of 'from cppyy.interactive import *'""" import sys - if 0x030b0000 <= sys.hexversion: + if 0x030B0000 <= sys.hexversion: skip('"from cppyy.interactive import *" is no longer supported') oldsp = sys.path[:] - sys.path.append('.') + sys.path.append(".") try: import assert_interactive finally: sys.path = oldsp - @mark.xfail(strict=True) + @mark.xfail() def test18_overload(self): """Test usage of __overload__""" @@ -471,12 +480,18 @@ def test18_overload(self): Variable(int) {} };""") - for sig in ['double, double, double, bool, bool, const std::string&', - 'double,double,double,bool,bool,const std::string&', - 'double lb, double ub, double value, bool binary, bool integer, const std::string& name']: + for sig in [ + "double, double, double, bool, bool, const std::string&", + "double,double,double,bool,bool,const std::string&", + "double lb, double ub, double value, bool binary, bool integer, const std::string& name", + ]: assert cppyy.gbl.Variable.__init__.__overload__(sig) - @mark.xfail(strict=True, run=not is_modules_off(), condition=IS_WINDOWS or is_modules_off(), reason="Fails on Windows, crashes on alma9 with modules off") + @mark.xfail( + run=not is_modules_off(), + condition=IS_WINDOWS or is_modules_off(), + reason="Fails on Windows, crashes on alma9 with modules off", + ) def test19_gbl_contents(self): """Assure cppyy.gbl is mostly devoid of ROOT thingies""" @@ -484,9 +499,9 @@ def test19_gbl_contents(self): dd = dir(cppyy.gbl) - assert not 'TCanvasImp' in dd - assert not 'ESysConstants' in dd - assert not 'kDoRed' in dd + assert not "TCanvasImp" in dd + assert not "ESysConstants" in dd + assert not "kDoRed" in dd def test20_capture_output(self): """Capture cerr into a string""" @@ -520,26 +535,30 @@ def test20_capture_output(self): def test21_failing_cppcode(self): """Check error behavior of failing C++ code""" - import cppyy, string, re + import re + import string + + import cppyy + + allspace = re.compile(r"\s+") - allspace = re.compile(r'\s+') def get_errmsg(exc, allspace=allspace): err = str(exc.value) - return re.sub(allspace, '', err) + return re.sub(allspace, "", err) with raises(ImportError) as include_exc: cppyy.include("doesnotexist.h") err = get_errmsg(include_exc) - assert "Failedtoloadheaderfile\"doesnotexist.h\"" in err + assert 'Failedtoloadheaderfile"doesnotexist.h"' in err assert "fatalerror:" in err - assert "\'doesnotexist.h\'filenotfound" in err + assert "'doesnotexist.h'filenotfound" in err with raises(ImportError) as c_include_exc: cppyy.c_include("doesnotexist.h") err = get_errmsg(c_include_exc) - assert "Failedtoloadheaderfile\"doesnotexist.h\"" in err + assert 'Failedtoloadheaderfile"doesnotexist.h"' in err assert "fatalerror:" in err - assert "\'doesnotexist.h\'filenotfound" in err + assert "'doesnotexist.h'filenotfound" in err with raises(SyntaxError) as cppdef_exc: cppyy.cppdef("1aap = 42;") @@ -549,7 +568,7 @@ def get_errmsg(exc, allspace=allspace): assert "invaliddigit" in err assert "1aap=42;" in err - @mark.xfail(strict=True, condition=not IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(condition=not IS_WINDOWS, reason="Fails on Windows") def test22_cppexec(self): """Interactive access to the Cling global scope""" @@ -559,7 +578,7 @@ def test22_cppexec(self): assert cppyy.gbl.interactive_b == 4 with raises(SyntaxError): - cppyy.cppexec("doesnotexist"); + cppyy.cppexec("doesnotexist") @mark.skip(reason="This test is very verbose since it sets gDebug to True") def test23_set_debug(self): @@ -570,35 +589,41 @@ def test23_set_debug(self): cppyy.set_debug() assert cppyy.gbl.CppyyLegacy.gDebug == 10 cppyy.set_debug(False) - assert cppyy.gbl.CppyyLegacy.gDebug == 0 + assert cppyy.gbl.CppyyLegacy.gDebug == 0 cppyy.set_debug(True) assert cppyy.gbl.CppyyLegacy.gDebug == 10 cppyy.set_debug(False) - assert cppyy.gbl.CppyyLegacy.gDebug == 0 + assert cppyy.gbl.CppyyLegacy.gDebug == 0 @mark.skip(reason="Not actually a cppyy test") def test24_asan(self): """Check availability of ASAN with gcc""" - import cppyy import sys - if not 'linux' in sys.platform: + import cppyy + + if not "linux" in sys.platform: return - cppyy.include('sanitizer/asan_interface.h') + cppyy.include("sanitizer/asan_interface.h") - @mark.xfail(run=False, condition=has_asserts(), - reason="Transaction.cpp:98: void cling::Transaction::addNestedTransaction(cling::Transaction*): Assertion `!m_Unloading && \"Must not nest within unloading transaction\"' failed.") + @mark.xfail( + run=False, + condition=has_asserts(), + reason='Transaction.cpp:98: void cling::Transaction::addNestedTransaction(cling::Transaction*): Assertion `!m_Unloading && "Must not nest within unloading transaction"\' failed.', + ) def test25_cppdef_error_reporting(self): """Check error reporting of cppyy.cppdef""" - import cppyy, warnings + import warnings + + import cppyy - assert cppyy.gbl.fragile.add42(1) == 43 # brings in symbol from library + assert cppyy.gbl.fragile.add42(1) == 43 # brings in symbol from library with raises(SyntaxError): - # redefine symbol, leading to duplicate + # redefine symbol, leading to duplicate cppyy.cppdef("""\ namespace fragile { int add42(int i) { return i + 42; } @@ -617,9 +642,9 @@ def test25_cppdef_error_reporting(self): assert "return" in str(exc.value) - # mix of error and warning + # mix of error and warning with raises(SyntaxError): - # redefine symbol, leading to duplicate + # redefine symbol, leading to duplicate cppyy.cppdef("""\ namespace fragile { float add42f(float d) { d + 42.f; } @@ -638,15 +663,16 @@ def test26_macro(self): with raises(ValueError): cppyy.macro("SOME_INT") - cppyy.cppdef('#define SOME_INT 42') + cppyy.cppdef("#define SOME_INT 42") assert cppyy.macro("SOME_INT") == 42 def test27_pickle_enums(self): """Pickling of enum types""" - import cppyy import pickle + import cppyy + cppyy.cppdef(""" enum MyPickleEnum { PickleFoo, PickleBar }; namespace MyPickleNamespace { @@ -654,23 +680,25 @@ def test27_pickle_enums(self): }""") e1 = cppyy.gbl.MyPickleEnum - assert e1.__module__ == 'cppyy.gbl' + assert e1.__module__ == "cppyy.gbl" assert pickle.dumps(e1.PickleFoo) e2 = cppyy.gbl.MyPickleNamespace.MyPickleEnum - assert e2.__module__ == 'cppyy.gbl.MyPickleNamespace' + assert e2.__module__ == "cppyy.gbl.MyPickleNamespace" assert pickle.dumps(e2.PickleBar) def test28_memoryview_of_empty(self): """memoryview of an empty array""" - import cppyy, array + import array + + import cppyy cppyy.cppdef("void f(unsigned char const *buf) {}") try: - cppyy.gbl.f(memoryview(array.array('B', []))) + cppyy.gbl.f(memoryview(array.array("B", []))) except TypeError: - pass # used to crash in PyObject_CheckBuffer on Linux + pass # used to crash in PyObject_CheckBuffer on Linux def test29_vector_datamember(self): """Offset calculation of vector datamember""" @@ -678,7 +706,7 @@ def test29_vector_datamember(self): import cppyy cppyy.cppdef("struct VectorDatamember { std::vector v; };") - cppyy.gbl.VectorDatamember # used to crash on Mac arm64 + cppyy.gbl.VectorDatamember # used to crash on Mac arm64 @mark.xfail(run=False, reason="Fatal Python error: Aborted") def test30_two_nested_ambiguity(self, capfd): @@ -708,14 +736,14 @@ def test30_two_nested_ambiguity(self, capfd): from cppyy.gbl import Test p = Test.Family1.Parent() - p.children # used to crash + p.children # used to crash # Fail if there was an interpreter error captured = capfd.readouterr() output = (captured.out + captured.err).lower() assert "error:" not in output - @mark.xfail(strict=True) + @mark.xfail() def test31_template_with_class_enum(self): """Template instantiated with class enum""" @@ -744,8 +772,7 @@ def test31_template_with_class_enum(self): template class EnumTemplate; }""") - for ns, val in [(cppyy.gbl, 42), - (cppyy.gbl.ClassEnumNS, 37)]: + for ns, val in [(cppyy.gbl, 42), (cppyy.gbl.ClassEnumNS, 37)]: assert ns.EnumTemplate[ns.ClassEnumA.A]().foo() == val @@ -753,18 +780,19 @@ class TestSIGNALS: def setup_class(cls): cls.test_dct = test_dct import cppyy + cls.fragile = cppyy.load_reflection_info(cls.test_dct) # This test has unclear failure conditions. On the ROOT CI PR builds if # passes, but it fails in the nightlies with: # "Failed: DID NOT RAISE " - # We can therefore not use strict=True and a meaningful failure condition. - @mark.xfail() + # We can therefore not use and a meaningful failure condition. + @mark.xfail(run=False) def test01_abortive_signals(self): """Conversion from abortive signals to Python exceptions""" if ispypy: - skip('signals not yet implemented') + skip("signals not yet implemented") if IS_MAC_ARM: skip("JIT exceptions from signals not supported on Mac ARM") @@ -777,13 +805,14 @@ def test01_abortive_signals(self): f = cppyy.gbl.fragile - assert issubclass(cppyy.ll.BusError, cppyy.ll.FatalError) - assert issubclass(cppyy.ll.SegmentationViolation, cppyy.ll.FatalError) - assert issubclass(cppyy.ll.IllegalInstruction, cppyy.ll.FatalError) - assert issubclass(cppyy.ll.AbortSignal, cppyy.ll.FatalError) + assert issubclass(cppyy.ll.BusError, cppyy.ll.FatalError) + assert issubclass(cppyy.ll.SegmentationViolation, cppyy.ll.FatalError) + assert issubclass(cppyy.ll.IllegalInstruction, cppyy.ll.FatalError) + assert issubclass(cppyy.ll.AbortSignal, cppyy.ll.FatalError) import os - os.putenv('CPPYY_CRASH_QUIET', '1') + + os.putenv("CPPYY_CRASH_QUIET", "1") with raises((cppyy.ll.SegmentationViolation, cppyy.ll.IllegalInstruction)): with cppyy.ll.signals_as_exception(): @@ -793,9 +822,9 @@ def test01_abortive_signals(self): with cppyy.ll.signals_as_exception(): f.sigabort() - # can only recover once from each error on Windows, which is functionally - # enough, but precludes further testing here (change: now drop all, see above, - # as on some MSVC builds, no signals are caught ??) + # can only recover once from each error on Windows, which is functionally + # enough, but precludes further testing here (change: now drop all, see above, + # as on some MSVC builds, no signals are caught ??) if not IS_WINDOWS: cppyy.ll.set_signals_as_exception(True) with raises((cppyy.ll.SegmentationViolation, cppyy.ll.IllegalInstruction)): @@ -814,7 +843,12 @@ def test01_abortive_signals(self): class TestSTDNOTINGLOBAL: - @mark.xfail(strict=True) + def setup_class(cls): + import cppyy + + cls.has_byte = 201402 < cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) + + @mark.xfail() def test01_stl_in_std(self): """STL classes should live in std:: only""" @@ -827,7 +861,7 @@ def test01_stl_in_std(self): with raises(AttributeError): getattr(cppyy.gbl, name) - # inject a vector in the global namespace + # inject a vector in the global namespace cppyy.cppdef("class vector{};") v = cppyy.gbl.vector() assert cppyy.gbl.vector is not cppyy.gbl.std.vector @@ -837,7 +871,7 @@ def test02_ctypes_in_both(self): import cppyy - for name in ['int8_t', 'uint8_t']: + for name in ["int8_t", "uint8_t"]: getattr(cppyy.gbl.std, name) getattr(cppyy.gbl, name) @@ -845,7 +879,7 @@ def test02_ctypes_in_both(self): assert cppyy.gbl.std.int8_t(-42) == cppyy.gbl.int8_t(-42) assert cppyy.gbl.std.uint8_t(42) == cppyy.gbl.uint8_t(42) - @mark.xfail(strict=True) + @mark.xfail() def test03_clashing_using_in_global(self): """Redefines of std:: typedefs should be possible in global""" @@ -856,12 +890,12 @@ def test03_clashing_using_in_global(self): using ushort = unsigned short; using uchar = unsigned char; using byte = unsigned char; - """ ) + """) - for name in ['int', 'uint', 'ushort', 'uchar', 'byte']: + for name in ["int", "uint", "ushort", "uchar", "byte"]: getattr(cppyy.gbl, name) - @mark.xfail(strict=True) + @mark.xfail() def test04_no_legacy(self): """Test some functions that previously crashed""" @@ -896,4 +930,4 @@ def test05_span_compatibility(self): if __name__ == "__main__": - exit(pytest.main(args=['-v', '-ra', __file__])) + exit(pytest.main(args=["-v", "-ra", __file__])) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py b/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py index 2fe380c6f195c..7c6453a570bb8 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py @@ -1,6 +1,6 @@ import py, os, sys, pytest from pytest import mark, skip -from support import setup_make, pylong, pyunicode +from .support import setup_make, pylong, pyunicode nopsutil = False try: diff --git a/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py b/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py index 71c1ed63e939a..39ae3dced7735 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises, skip -from support import setup_make, pylong, pyunicode, IS_WINDOWS, ispypy +from .support import setup_make, pylong, pyunicode, IS_WINDOWS, ispypy test_dct = "datatypes_cxx" @@ -14,6 +14,9 @@ def setup_class(cls): cls.datatypes = cppyy.load_reflection_info(cls.test_dct) cls.N = cppyy.gbl.N + at_least_17 = 201402 < cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) + cls.has_nested_namespace = at_least_17 + def test00_import_all(self): """Validity of `from cppyy.ll import *`""" @@ -132,7 +135,7 @@ def test05_array_as_ref(self): f = array('f', [0]); ctd.set_float_r(f); assert f[0] == 5. f = array('d', [0]); ctd.set_double_r(f); assert f[0] == -5. - @mark.xfail(strict=True) + @mark.xfail() def test06_ctypes_as_ref_and_ptr(self): """Use ctypes for pass-by-ref/ptr""" @@ -489,7 +492,7 @@ def test14_templated_arrays(self): assert cppyy.gbl.std.vector[cppyy.gbl.std.vector[int]].value_type == 'std::vector' assert cppyy.gbl.std.vector['int[1]'].value_type == 'int[1]' - @mark.xfail(strict=True) + @mark.xfail() def test15_templated_arrays_gmpxx(self): """Use of gmpxx array types in templates""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_numba.py b/bindings/pyroot/cppyy/cppyy/test/test_numba.py index 414aaef069ead..b8c39524475ab 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_numba.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_numba.py @@ -1,7 +1,7 @@ import os, pytest import math, time from pytest import mark, raises -from support import setup_make, IS_MAC, IS_WINDOWS +from .support import setup_make, IS_MAC, IS_WINDOWS try: import numba @@ -82,7 +82,7 @@ class MyData_d1 { assert ns.MyData_d1.__dict__['m_double'].__cpp_reflex__(r.OFFSET) == cppyy.addressof(d, 'm_double') - daddr -@mark.skipif(has_numba == False, reason="numba not found") +@mark.skip(reason="Crashes run with the test suit") class TestNUMBA: def setup_class(cls): import cppyy @@ -126,7 +126,7 @@ def go_fast(a): assert (go_fast(x) == go_slow(x)).all() assert self.compare(go_slow, go_fast, 300000, x) - @mark.xfail(strict=True) + @mark.xfail() def test02_JITed_template_free_func(self): """Numba-JITing of Cling-JITed templated free function""" @@ -228,7 +228,7 @@ def go_fast(a, d): assert((go_fast(x, d) == go_slow(x, d)).all()) assert self.compare(go_slow, go_fast, 10000, x, d) - @mark.xfail(strict=True, condition=IS_WINDOWS == 32, reason="Fails on Windows 32 bit") + @mark.xfail(condition=IS_WINDOWS == 32, reason="Fails on Windows 32 bit") def test05_multiple_arguments_function(self): """Numba-JITing of functions with multiple arguments""" @@ -255,7 +255,7 @@ def loop_add(x): assert sum == loop_add(x) - @mark.xfail(strict=True, condition=IS_WINDOWS == 32, reason="Fails on Windows 32 bit") + @mark.xfail(condition=IS_WINDOWS == 32, reason="Fails on Windows 32 bit") def test06_multiple_arguments_template_freefunction(self): """Numba-JITing of a free template function that recieves more than one template arg""" @@ -281,7 +281,7 @@ def tma(x): assert sum == tma(x) - @mark.xfail(strict=True) + @mark.xfail() def test07_datatype_mapping(self): """Numba-JITing of various data types""" @@ -349,7 +349,7 @@ def go_fast(a): assert((go_fast(x) == go_slow(x)).all()) assert self.compare(go_slow, go_fast, 100000, x) - @mark.xfail(strict=True, condition=IS_WINDOWS == 32, reason="Fails on Windows 32 bit") + @mark.xfail(condition=IS_WINDOWS == 32, reason="Fails on Windows 32 bit") def test09_non_typed_templates(self): """Numba-JITing of a free template function that recieves multiple template args with non types""" @@ -375,7 +375,7 @@ def tma(x): assert sum == tma(x) - @mark.xfail(strict=True, condition=IS_MAC | IS_WINDOWS, reason="Fails on macOS and Windows") + @mark.xfail(condition=IS_MAC | IS_WINDOWS, reason="Fails on macOS and Windows") def test10_returning_a_reference(self): import cppyy import numpy as np @@ -412,7 +412,7 @@ def fast_add(X): X = np.arange(100, dtype=np.int64).reshape(50, 2) assert fast_add(X) == slow_add(X) - @mark.xfail(strict=True) + @mark.xfail() def test11_ptr_ref_support(self): """Numba-JITing of a increment method belonging to a class, and also swaps the pointers and reflects the change on the python ctypes variables""" import cppyy @@ -479,7 +479,7 @@ def inc_c(d, k): assert b.value == z + k assert c.value == y + k - @mark.xfail(strict=True) + @mark.xfail() def test12_std_vector_pass_by_ref(self): """Numba-JITing of a method that performs scalar addition to a std::vector initialised through pointers """ import cppyy @@ -575,7 +575,7 @@ def square_vec_slow(x): assert (np.array(y) == np_square_res).all() assert (np.array(x) == np_add_res).all() - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test13_std_vector_dot_product(self): """Numba-JITing of a dot_product method of a class that stores pointers to std::vectors on the python side""" import cppyy, cppyy.ll @@ -737,7 +737,7 @@ def mul_njit(m, x): assert(result == matrix2) -@mark.skipif(has_numba == False, reason="numba not found") +@mark.skip(reason="Crashes with Nested transactions with run eith the test suit") class TestNUMBA_DOC: def setup_class(cls): import cppyy @@ -772,7 +772,7 @@ def tsa(a): assert type(tsa(a)) == int assert tsa(a) == 285 - @mark.xfail(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(condition=IS_WINDOWS, reason="Fails on Windows") def test02_class_features(self): """Numba support documentation example: class features""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_operators.py b/bindings/pyroot/cppyy/cppyy/test/test_operators.py index a6df819054551..dde651bd8155f 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_operators.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_operators.py @@ -1,6 +1,6 @@ import pytest, os from pytest import raises, skip, mark -from support import setup_make, pylong, maxvalue, IS_WINDOWS, IS_MAC +from .support import setup_make, pylong, maxvalue, IS_WINDOWS, IS_MAC test_dct = "operators_cxx" @@ -227,7 +227,7 @@ def test08_call_to_getsetitem_mapping(self): assert m[1] == 74 assert m(1,2) == 74 - @mark.xfail(strict=True, reason="Compilation of unused call wrappers emits errors") + @mark.xfail(reason="Compilation of unused call wrappers emits errors") def test09_templated_operator(self, capfd): """Templated operator<()""" @@ -346,7 +346,7 @@ def test14_single_argument_call(self): b = ns.Bar() assert b[42] == 42 - @mark.xfail(strict=True, condition=IS_MAC or compiled_with_gcc16(), reason="Fails on macOS or gcc 16") + @mark.xfail(condition=IS_MAC or compiled_with_gcc16(), reason="Fails on macOS or gcc 16") def test15_class_and_global_mix(self): """Iterator methods have both class and global overloads""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_overloads.py b/bindings/pyroot/cppyy/cppyy/test/test_overloads.py index 4993e8f7be2c8..dbbebc6339c8f 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_overloads.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_overloads.py @@ -1,6 +1,6 @@ import pytest, os from pytest import raises, skip, mark -from support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM +from .support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM test_dct = "overloads_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py b/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py index 5282592362dc5..423b603547672 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py @@ -1,6 +1,6 @@ -import pytest, os -from pytest import mark, raises, skip -from support import setup_make, pylong, ispypy +import py, pytest, os +from pytest import raises, skip, mark +from .support import setup_make, pylong, ispypy test_dct = "example01_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py b/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py index 594c34c370f75..49ffa6b4c210e 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises -from support import setup_make, pylong +from .support import setup_make, pylong test_dct = "pythonizables_cxx" @@ -11,7 +11,7 @@ def setup_class(cls): import cppyy cls.pyzables = cppyy.load_reflection_info(cls.test_dct) - @mark.xfail(strict=True) + @mark.xfail() def test00_api(self): """Test basic semantics of the pythonization API""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_regression.py b/bindings/pyroot/cppyy/cppyy/test/test_regression.py index a3cc699609df8..af092549c612c 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_regression.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_regression.py @@ -1,6 +1,6 @@ import os, sys, pytest from pytest import mark, raises, skip -from support import setup_make, IS_WINDOWS, ispypy, IS_MAC, IS_MAC_ARM +from .support import setup_make, IS_WINDOWS, ispypy, IS_MAC, IS_MAC_ARM class TestREGRESSION: @@ -380,7 +380,7 @@ def test15_vector_vs_initializer_list(self): sizeit = cppyy.gbl.vec_vs_init.sizeit assert sizeit(list(range(10))) == 10 - @mark.xfail(strict=True) + @mark.xfail() def test16_iterable_enum(self): """Use template to iterate over an enum""" # from: https://stackoverflow.com/questions/52459530/pybind11-emulate-python-enum-behaviour @@ -473,7 +473,7 @@ class Derived2 : public Base { assert a != b # derived class' C++ operator!= called - @mark.xfail(strict=True) + @mark.xfail() def test18_operator_plus_overloads(self): """operator+(string, string) should return a string""" @@ -783,7 +783,7 @@ def test28_exception_as_shared_ptr(self): null = cppyy.gbl.exception_as_shared_ptr.get_shared_null() assert not null - @mark.xfail(strict=True) + @mark.xfail() def test29_callback_pointer_values(self, capfd): """Make sure pointer comparisons in callbacks work as expected""" @@ -859,7 +859,7 @@ def changeCallback(self, b): output = (captured.out + captured.err).lower() assert "taking address of non-addressable standard library function" not in output - @mark.xfail(strict=True, condition=IS_MAC or IS_WINDOWS, reason="int64_t and uint64_t not automatically materialized on macOS and Windows") + @mark.xfail(condition=IS_MAC or IS_WINDOWS, reason="int64_t and uint64_t not automatically materialized on macOS and Windows") def test30_uint64_t(self): """Failure due to typo""" @@ -893,7 +893,7 @@ def test30_uint64_t(self): assert ns.TTest(True).fT == True assert type(ns.TTest(True).fT) == bool - @mark.xfail(strict=True) + @mark.xfail() def test31_enum_in_dir(self): """Failed to pick up enum data""" @@ -916,7 +916,7 @@ def test31_enum_in_dir(self): required = {'prod', 'a', 'b', 'smth', 'my_enum'} assert all_names.intersection(required) == required - @mark.xfail(strict=True) + @mark.xfail() def test32_typedef_class_enum(self): """Use of class enum with typedef'd type""" @@ -954,7 +954,7 @@ def test32_typedef_class_enum(self): assert o.x == Foo.BAZ assert o.y == 1 - @mark.xfail(strict=True) + @mark.xfail() def test33_explicit_template_in_namespace(self): """Lookup of explicit template in namespace""" @@ -1005,6 +1005,7 @@ class ReferenceWavefunction {}; pt_type = cppyy.gbl.property_types.ReferenceWavefunction['double'] assert cppyy.gbl.std.get[0](cppyy.gbl.property_types.run_as[pt_type]()) == 20. + @mark.xfail(run=False, reason="toString not implemented") def test34_print_empty_collection(self): """Print empty collection through Cling""" @@ -1023,14 +1024,15 @@ def test35_filesytem(self): import cppyy - cppyy.cppdef("""\ - #include - std::string stack_std_path() { - std::filesystem::path p = "/usr"; - std::ostringstream os; - os << p; - return os.str(); - }""") + if cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) > 201402: + cppyy.cppdef("""\ + #include + std::string stack_std_path() { + std::filesystem::path p = "/usr"; + std::ostringstream os; + os << p; + return os.str(); + }""") assert cppyy.gbl.stack_std_path() == '"/usr"' @@ -1356,7 +1358,7 @@ def test44_heuristic_mem_policy(self): finally: cppyy._backend.SetHeuristicMemoryPolicy(old_memory_policy) - @mark.xfail(strict=True) + @mark.xfail() def test45_typedef_resolution(self): """Typedefs starting with 'c'""" @@ -1435,6 +1437,7 @@ def test49_overloads_with_runtime_errors(self): ) with raises(std.runtime_error): + # breakpoint() cppyy.gbl.fun("", []) with raises(std.runtime_error): cppyy.gbl.fun(std.string_view("hello world"), std.vector[std.string]()) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py b/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py index 3c9ac4c8859dd..d4f405c71db88 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py @@ -1,7 +1,7 @@ # -*- coding: UTF-8 -*- import sys, pytest, os from pytest import mark, raises, skip -from support import setup_make, pylong, pyunicode, maxvalue, ispypy, IS_WINDOWS +from .support import setup_make, pylong, pyunicode, maxvalue, ispypy, IS_WINDOWS test_dct = "stltypes_cxx" @@ -615,7 +615,7 @@ def test17_vector_cpp17_style(self): v = cppyy.gbl.std.vector(l) assert list(l) == l - @mark.xfail(strict=True) + @mark.xfail() def test18_array_interface(self): """Test usage of __array__ from numpy""" @@ -928,6 +928,7 @@ def test03_string_with_null_character(self): assert repr(std.string('ab\0c')) == repr(b'ab\0c') assert str(std.string('ab\0c')) == str('ab\0c') + @mark.skip(reason="Takes too long") def test04_array_of_strings(self): """Access to global arrays of strings""" @@ -1017,7 +1018,7 @@ def test07_stlstring_in_dictionaries(self): assert d[x] == 0 assert d['x'] == 0 - @mark.xfail(strict=True) + @mark.xfail() def test08_string_operators(self): """Mixing of C++ and Python types in global operators""" @@ -1045,7 +1046,7 @@ def test08_string_operators(self): assert s1+s2 == "Hello, World!" assert s2+s1 == ", World!Hello" - @mark.xfail(strict=True, condition=IS_WINDOWS == 64, reason="AttributeError: has no attribute 'size_type'") + @mark.xfail(condition=IS_WINDOWS == 64, reason="AttributeError: has no attribute 'size_type'") def test09_string_as_str_bytes(self): """Python-style methods of str/bytes on std::string""" @@ -1108,7 +1109,7 @@ def EQ(result, init, methodname, *args): assert s.rfind('c') < 0 assert s.rfind('c') == s.npos - @mark.xfail(strict=True) + @mark.xfail() def test10_string_in_repr_and_str_bytes(self): """Special cases for __str__/__repr__""" @@ -1698,6 +1699,9 @@ def test01_string_through_string_view(self): """Usage of std::string_view as formal argument""" import cppyy + if cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) <= 201402: + # string_view exists as of C++17 + return countit = cppyy.gbl.StringViewTest.count countit_cr = cppyy.gbl.StringViewTest.count_cr @@ -1716,6 +1720,9 @@ def test02_string_view_from_unicode(self): """Life-time management of converted unicode strings""" import cppyy, gc + if cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) <= 201402: + # string_view exists as of C++17 + return # view on (converted) unicode text = cppyy.gbl.std.string_view('''\ @@ -1941,7 +1948,7 @@ def test02_tuple_size(self): t = std.make_tuple("aap", 42, 5.) assert std.tuple_size(type(t)).value == 3 - @mark.xfail(strict=True) + @mark.xfail() def test03_tuple_iter(self): """Pack/unpack tuples""" @@ -1956,7 +1963,7 @@ def test03_tuple_iter(self): assert b == '2' assert c == 5. - @mark.xfail(strict=True) + @mark.xfail() def test04_tuple_lifeline(self): """Tuple memory management""" @@ -2192,7 +2199,7 @@ def test04_from_cpp(self): def has_cpp_20(): import cppyy - return cppyy.gbl.gInterpreter.ProcessLine("__cplusplus;") >= 202002 + return cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) >= 202002 @mark.skipif(not has_cpp_20(), reason="std::span requires C++20") diff --git a/bindings/pyroot/cppyy/cppyy/test/test_streams.py b/bindings/pyroot/cppyy/cppyy/test/test_streams.py index 15a3a78d01f34..25bc60a26c747 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_streams.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_streams.py @@ -1,6 +1,6 @@ import pytest, os from pytest import mark, raises -from support import setup_make +from .support import setup_make test_dct = "std_streams_cxx" @@ -45,7 +45,7 @@ def test03_consistent_naming_if_char_traits(self): cppyy.gbl.stringstream_base.pass_through_base(s) assert s.str() == "TEST STRING" - @mark.xfail(strict=True) + @mark.xfail() def test04_naming_of_ostringstream(self): """Naming consistency of ostringstream""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_templates.py b/bindings/pyroot/cppyy/cppyy/test/test_templates.py index f5d7904e0b9d0..3bdce10271dd6 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_templates.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_templates.py @@ -1,6 +1,6 @@ import pytest, os from pytest import mark, raises -from support import setup_make, pylong, IS_WINDOWS +from .support import setup_make, pylong test_dct = "templates_cxx" @@ -12,6 +12,11 @@ def setup_class(cls): import cppyy cls.templates = cppyy.load_reflection_info(cls.test_dct) + at_least_17 = 201402 < cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) + cls.has_integral_v = at_least_17 + cls.has_disjunction_v = at_least_17 + cls.has_pack_fold = at_least_17 + def test00_template_back_reference(self): """Template reflection""" @@ -148,7 +153,7 @@ def test05_variadic_overload(self): assert cppyy.gbl.isSomeInt() == False assert cppyy.gbl.isSomeInt(1, 2, 3) == False - @mark.xfail(strict=True, reason="This test causes the interpreter to raises errors") + @mark.xfail(reason="This test causes the interpreter to raises errors") def test06_variadic_sfinae(self, capfd): """Attribute testing through SFINAE""" @@ -277,7 +282,7 @@ class RTTest_SomeClassWithTCtor { assert round(RTTest2[int](1, 3.1).m_double - 4.1, 8) == 0. assert round(RTTest2[int]().m_double + 1., 8) == 0. - @mark.xfail(strict=True) + @mark.xfail() def test12_template_aliases(self): """Access to templates made available with 'using'""" @@ -316,18 +321,19 @@ def test12_template_aliases(self): }; """) - assert nsup.matryoshka[int, 3].type - assert nsup.matryoshka[int, 3, 4].type - assert nsup.make_vector[int , 3] - assert nsup.make_vector[int , 3]().m_val == 3 - assert nsup.make_vector[int , 4]().m_val == 4 + if cppyy.gbl.Cpp.Evaluate("__cplusplus", cppyy.nullptr) > 201402: + assert nsup.matryoshka[int, 3].type + assert nsup.matryoshka[int, 3, 4].type + assert nsup.make_vector[int , 3] + assert nsup.make_vector[int , 3]().m_val == 3 + assert nsup.make_vector[int , 4]().m_val == 4 # with inner types using - assert cppyy.gbl.gInterpreter.CheckClassTemplate("using_problem::Bar::Foo") - assert nsup.Foo - assert nsup.Bar.Foo # used to fail + assert cppyy.gbl.gCling.CheckClassTemplate("using_problem::Bar::Foo") + assert nsup.Foo + assert nsup.Bar.Foo # used to fail - @mark.xfail(strict=True) + @mark.xfail() def test13_using_templated_method(self): """Access to base class templated methods through 'using'""" @@ -351,7 +357,7 @@ def test13_using_templated_method(self): assert type(d.get3()) == int assert d.get3() == 5 - @mark.xfail(strict=True) + @mark.xfail() def test14_templated_return_type(self): """Use of a templated return type""" @@ -509,7 +515,7 @@ class CustomVec { c = gbl.OperatorAddTest.CustomVec['double'](5.3) d = gbl.OperatorAddTest.CustomVec['int'](1) - q = c + d + q = c + d # XXX: codegen fails to compile, but the generate code is same with clang assert round(q.X() - 6.3, 8) == 0. @@ -592,7 +598,7 @@ def test23_overloaded_setitem(self): v = MyVec["float"](2) v[0] = 1 # used to throw TypeError - @mark.xfail(strict=True) + @mark.xfail() def test24_stdfunction_templated_arguments(self): """Use of std::function with templated arguments""" @@ -619,7 +625,7 @@ def callback(x): assert cppyy.gbl.std.function['double(std::vector)'] - @mark.xfail(strict=True) + @mark.xfail() def test25_stdfunction_ref_and_ptr_args(self): """Use of std::function with reference or pointer args""" @@ -916,7 +922,7 @@ class Templated: public NonTemplated { ns.Templated() # used to crash - @mark.xfail(strict=True) + @mark.xfail() def test31_ltlt_in_template_name(self): """Verify lookup of template names with << in the name""" @@ -982,7 +988,7 @@ def test31_ltlt_in_template_name(self): assert len(cppyy.gbl.gLutData6) == (1<<3)+1 assert len(cppyy.gbl.gLutData8) == 14<<2 - @mark.xfail(strict=True) + @mark.xfail() def test32_template_of_function_with_templated_args(self): """Lookup of templates of function with templated args used to fail""" @@ -1142,7 +1148,7 @@ def test33_using_template_argument(self): assert ns.testptr assert cppyy.gbl.std.vector[ns.testptr] - @mark.xfail(strict=True) + @mark.xfail() def test34_cstring_template_argument(self): """`const char*` use over std::string""" @@ -1227,13 +1233,13 @@ def test01_using(self): assert 'in_type' in dir(tct[int, dum, 4]) assert in_type.__name__ == 'in_type' - assert in_type.__cpp_name__ == 'TemplatedTypedefs::DerivedWithUsing::in_type' + assert in_type.__cpp_name__ == 'TemplatedTypedefs::DerivedWithUsing::in_type' in_type_tt = tct[int, dum, 4].in_type_tt assert 'in_type_tt' in dir(tct[int, dum, 4]) assert in_type_tt.__name__ == 'in_type_tt' - assert in_type_tt.__cpp_name__ == 'TemplatedTypedefs::DerivedWithUsing::in_type_tt' + assert in_type_tt.__cpp_name__ == 'TemplatedTypedefs::DerivedWithUsing::in_type_tt' def test02_mapped_type_as_internal(self): """Test that mapped types can be used as builtin""" @@ -1278,7 +1284,7 @@ def test03_mapped_type_as_template_arg(self): assert tct['long double', dum, 4] is tct[in_type, dum, 4] assert tct['double', dum, 4] is not tct[in_type, dum, 4] - @mark.xfail(strict=True) + @mark.xfail() def test04_type_deduction(self): """Usage of type reducer""" @@ -1294,7 +1300,7 @@ def test04_type_deduction(self): three = w.whatis(3) assert three == 3 - @mark.xfail(strict=True) + @mark.xfail() def test05_type_deduction_and_extern(self): """Usage of type reducer with extern template""" @@ -1332,7 +1338,10 @@ class ShadowC {}; namespace ShadowY { namespace ShadowZ { - template void f() {} + template + int f() { + return 1; + } } namespace ShadowX { @@ -1343,12 +1352,10 @@ class ShadowD {}; ns = cppyy.gbl.ShadowY.ShadowZ C = cppyy.gbl.ShadowX.ShadowC - # lookup of shadowed class will fail - raises(TypeError, ns.f.__getitem__(C.__cpp_name__)) - # direct instantiation now succeeds - ns.f[C]() - ns.f['::'+C.__cpp_name__]() + assert ns.f[C]() == 97 + assert ns.f[C.__cpp_name__]() == 97 + assert ns.f['::'+C.__cpp_name__]() == 97 class TestTEMPLATE_TYPE_REDUCTION: @@ -1357,7 +1364,7 @@ def setup_class(cls): import cppyy cls.templates = cppyy.load_reflection_info(cls.test_dct) - @mark.xfail(strict=True) + @mark.xfail() def test01_reduce_binary(self): """Squash template expressions for binary operations (like in gmpxx)""" From 9c5a4b8699dec3153d2985802540d75278df6d15 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sun, 29 Mar 2026 13:33:00 +0200 Subject: [PATCH 26/29] add gPad to ROOT facade --- bindings/pyroot/pythonizations/python/ROOT/_facade.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_facade.py b/bindings/pyroot/pythonizations/python/ROOT/_facade.py index 827c7044bbd45..5bdaec72aeb39 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_facade.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_facade.py @@ -202,6 +202,7 @@ def _finalSetup(self): # Make sure the interpreter is initialized once gROOT has been initialized self.__dict__["gInterpreter"] = self._cppyy.gbl.TInterpreter.Instance() + self.__dict__["gPad"] = self._cppyy.gbl.TVirtualPad.Pad() # Setup interactive usage from Python self.__dict__["app"] = PyROOTApplication(self.PyConfig, self._is_ipython) From d3cdb555d4ad0da6e8b351ada4c71d9006460146 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 30 Mar 2026 12:00:59 +0200 Subject: [PATCH 27/29] revert changes that prevented import of support --- bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py | 1 + bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_api.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_boost.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_concurrent.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_conversions.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_datatypes.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_doc_features.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_eigen.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_fragile.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_numba.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_operators.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_overloads.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_pythonify.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_pythonization.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_regression.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_stltypes.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_streams.py | 2 +- bindings/pyroot/cppyy/cppyy/test/test_templates.py | 2 +- 24 files changed, 24 insertions(+), 23 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py index fd9d292ed2309..988fb06095c9d 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py @@ -179,6 +179,7 @@ def __getitem__(self, cls): gbl.std.make_shared = make_smartptr(gbl.std.shared_ptr, gbl.std.make_shared) gbl.std.make_unique = make_smartptr(gbl.std.unique_ptr, gbl.std.make_unique) +gbl.gInterpreter = gbl.TInterpreter.Instance() del make_smartptr diff --git a/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py b/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py index 53248d1a7f5fe..4af7e9d90119b 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_aclassloader.py @@ -1,6 +1,6 @@ import pytest, os from pytest import raises, mark -from .support import setup_make, IS_WINDOWS +from support import setup_make, IS_WINDOWS test_dct = "libexample_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py b/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py index 08f13e5ceb21b..c72fa7e2d6e05 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py @@ -3,7 +3,7 @@ import pytest from pytest import mark, raises, skip -from .support import IS_WINDOWS, ispypy, pylong, setup_make +from support import IS_WINDOWS, ispypy, pylong, setup_make test_dct = "advancedcpp_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_api.py b/bindings/pyroot/cppyy/cppyy/test/test_api.py index 5cdd8b9f70ef9..f30bc4c65d3fe 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_api.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_api.py @@ -1,6 +1,6 @@ import pytest from pytest import raises, skip -from .support import ispypy +from support import ispypy class TestAPI: diff --git a/bindings/pyroot/cppyy/cppyy/test/test_boost.py b/bindings/pyroot/cppyy/cppyy/test/test_boost.py index 758235bc8d23b..11678bb6062f8 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_boost.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_boost.py @@ -1,6 +1,6 @@ import os, pytest from pytest import mark, raises, skip -from .support import setup_make +from support import setup_make noboost = False if not (os.path.exists(os.path.join(os.path.sep, 'usr', 'include', 'boost')) or \ diff --git a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py b/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py index 700e1d8553b81..6a37d1961f975 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py @@ -1,6 +1,6 @@ import pytest from pytest import raises, skip, mark -from .support import IS_MAC_ARM, IS_WINDOWS +from support import IS_MAC_ARM, IS_WINDOWS diff --git a/bindings/pyroot/cppyy/cppyy/test/test_conversions.py b/bindings/pyroot/cppyy/cppyy/test/test_conversions.py index b490df4654dc1..d94cecd3e16d6 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_conversions.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_conversions.py @@ -1,6 +1,6 @@ import py, pytest, os from pytest import raises, mark -from .support import setup_make, IS_WINDOWS +from support import setup_make, IS_WINDOWS test_dct = "conversions_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py b/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py index d540adf8daef8..6db04ad71baa6 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises -from .support import setup_make, ispypy, IS_MAC_ARM, IS_WINDOWS +from support import setup_make, ispypy, IS_MAC_ARM, IS_WINDOWS test_dct = "cpp11features_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py b/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py index 3fe60f315deb9..7687f1e4426e4 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py @@ -1,6 +1,6 @@ import os, pytest from pytest import raises, skip, mark -from .support import setup_make, pylong, IS_MAC_ARM, IS_WINDOWS +from support import setup_make, pylong, IS_MAC_ARM, IS_WINDOWS test_dct = "crossinheritance_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py b/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py index f80f1f2472b47..28c37766b25fb 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises, skip -from .support import setup_make, pylong, pyunicode, IS_MAC, IS_MAC_ARM, IS_WINDOWS +from support import setup_make, pylong, pyunicode, IS_MAC, IS_MAC_ARM, IS_WINDOWS test_dct = "datatypes_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py b/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py index c4279e6f2cef5..1929d02cc8791 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises, skip -from .support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM +from support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM test_dct = "doc_helper_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_eigen.py b/bindings/pyroot/cppyy/cppyy/test/test_eigen.py index a970591a14891..5c82690a46508 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_eigen.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_eigen.py @@ -1,6 +1,6 @@ import py, os, pytest from pytest import mark, raises -from .support import setup_make +from support import setup_make inc_paths = [os.path.join(os.path.sep, 'usr', 'include'), os.path.join(os.path.sep, 'usr', 'local', 'include')] diff --git a/bindings/pyroot/cppyy/cppyy/test/test_fragile.py b/bindings/pyroot/cppyy/cppyy/test/test_fragile.py index 4fe91449150f6..91510a3e491bc 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_fragile.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_fragile.py @@ -4,7 +4,7 @@ import pytest from pytest import mark, raises, skip -from .support import IS_MAC_ARM, IS_WINDOWS, ispypy, setup_make +from support import IS_MAC_ARM, IS_WINDOWS, ispypy, setup_make test_dct = "fragile_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py b/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py index 7c6453a570bb8..2fe380c6f195c 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_leakcheck.py @@ -1,6 +1,6 @@ import py, os, sys, pytest from pytest import mark, skip -from .support import setup_make, pylong, pyunicode +from support import setup_make, pylong, pyunicode nopsutil = False try: diff --git a/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py b/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py index 39ae3dced7735..c91210227072a 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises, skip -from .support import setup_make, pylong, pyunicode, IS_WINDOWS, ispypy +from support import setup_make, pylong, pyunicode, IS_WINDOWS, ispypy test_dct = "datatypes_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_numba.py b/bindings/pyroot/cppyy/cppyy/test/test_numba.py index b8c39524475ab..44ac2aeef9d04 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_numba.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_numba.py @@ -1,7 +1,7 @@ import os, pytest import math, time from pytest import mark, raises -from .support import setup_make, IS_MAC, IS_WINDOWS +from support import setup_make, IS_MAC, IS_WINDOWS try: import numba diff --git a/bindings/pyroot/cppyy/cppyy/test/test_operators.py b/bindings/pyroot/cppyy/cppyy/test/test_operators.py index dde651bd8155f..c98b3588af3af 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_operators.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_operators.py @@ -1,6 +1,6 @@ import pytest, os from pytest import raises, skip, mark -from .support import setup_make, pylong, maxvalue, IS_WINDOWS, IS_MAC +from support import setup_make, pylong, maxvalue, IS_WINDOWS, IS_MAC test_dct = "operators_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_overloads.py b/bindings/pyroot/cppyy/cppyy/test/test_overloads.py index dbbebc6339c8f..4993e8f7be2c8 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_overloads.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_overloads.py @@ -1,6 +1,6 @@ import pytest, os from pytest import raises, skip, mark -from .support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM +from support import setup_make, ispypy, IS_WINDOWS, IS_MAC_ARM test_dct = "overloads_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py b/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py index 423b603547672..59ee1ee22bc4f 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_pythonify.py @@ -1,6 +1,6 @@ import py, pytest, os from pytest import raises, skip, mark -from .support import setup_make, pylong, ispypy +from support import setup_make, pylong, ispypy test_dct = "example01_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py b/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py index 49ffa6b4c210e..f31410a4fee01 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_pythonization.py @@ -1,6 +1,6 @@ import sys, pytest, os from pytest import mark, raises -from .support import setup_make, pylong +from support import setup_make, pylong test_dct = "pythonizables_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_regression.py b/bindings/pyroot/cppyy/cppyy/test/test_regression.py index af092549c612c..3ad76bb257b5e 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_regression.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_regression.py @@ -1,6 +1,6 @@ import os, sys, pytest from pytest import mark, raises, skip -from .support import setup_make, IS_WINDOWS, ispypy, IS_MAC, IS_MAC_ARM +from support import setup_make, IS_WINDOWS, ispypy, IS_MAC, IS_MAC_ARM class TestREGRESSION: diff --git a/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py b/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py index d4f405c71db88..4e49f9a62dcc9 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py @@ -1,7 +1,7 @@ # -*- coding: UTF-8 -*- import sys, pytest, os from pytest import mark, raises, skip -from .support import setup_make, pylong, pyunicode, maxvalue, ispypy, IS_WINDOWS +from support import setup_make, pylong, pyunicode, maxvalue, ispypy, IS_WINDOWS test_dct = "stltypes_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_streams.py b/bindings/pyroot/cppyy/cppyy/test/test_streams.py index 25bc60a26c747..abf717948d5d7 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_streams.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_streams.py @@ -1,6 +1,6 @@ import pytest, os from pytest import mark, raises -from .support import setup_make +from support import setup_make test_dct = "std_streams_cxx" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_templates.py b/bindings/pyroot/cppyy/cppyy/test/test_templates.py index 3bdce10271dd6..e4f7cd32bbff6 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_templates.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_templates.py @@ -1,6 +1,6 @@ import pytest, os from pytest import mark, raises -from .support import setup_make, pylong +from support import setup_make, pylong test_dct = "templates_cxx" From 1329ccd6585111e817b1b3890b269e30a4564ec0 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 30 Mar 2026 18:27:11 +0200 Subject: [PATCH 28/29] upstream patch CppInterOp#880 --- .../CppInterOp/lib/CppInterOp/Compatibility.h | 52 +++++++++++++++++++ .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 26 ++-------- .../lib/CppInterOp/CppInterOpInterpreter.h | 16 +++--- .../CppInterOp/unittests/CMakeLists.txt | 7 +++ .../unittests/CppInterOp/InterpreterTest.cpp | 29 +++++++++++ .../CppInterOp/unittests/CppInterOp/Utils.cpp | 2 +- 6 files changed, 103 insertions(+), 29 deletions(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h index e8bd64864d6cd..283f70c7cf632 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h +++ b/interpreter/CppInterOp/lib/CppInterOp/Compatibility.h @@ -7,12 +7,17 @@ #include "clang/AST/DeclTemplate.h" #include "clang/AST/GlobalDecl.h" +#include "clang/Basic/Diagnostic.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/Specifiers.h" #include "clang/Basic/Version.h" #include "clang/Config/config.h" #include "clang/Sema/Sema.h" +#include "llvm/Support/raw_ostream.h" + +#include + #ifdef _MSC_VER #define dup _dup #define dup2 _dup2 @@ -31,6 +36,53 @@ static inline char* GetEnv(const char* Var_Name) { #endif } +/// A very lightweight RAII switcher. It manages diagnostic silencing +/// and provides a unified interface for handling LLVM Errors. +class DiagnosticGuard { + bool m_is_silent = false; + clang::DiagnosticsEngine& m_diags; + clang::DiagnosticConsumer* m_oldClient = nullptr; + bool m_oldShouldOwn = false; + clang::IgnoringDiagConsumer m_ignoring_consumer; + +public: + explicit DiagnosticGuard(clang::DiagnosticsEngine& diags, bool silent) + : m_is_silent(silent), m_diags(diags) { + if (m_is_silent) { + m_oldShouldOwn = m_diags.ownsClient(); + if (m_oldShouldOwn) + m_oldClient = m_diags.takeClient().release(); + else + m_oldClient = m_diags.getClient(); + m_diags.setClient(&m_ignoring_consumer, /*ShouldOwnClient=*/false); + } + } + + ~DiagnosticGuard() { + // Only restore if we actually swapped the client + if (m_is_silent) { + m_diags.setClient(m_oldClient, m_oldShouldOwn); + } + } + + // Explicitly non-copyable + DiagnosticGuard(const DiagnosticGuard&) = delete; + DiagnosticGuard(const DiagnosticGuard&&) = delete; + DiagnosticGuard& operator=(const DiagnosticGuard&) = delete; + DiagnosticGuard& operator=(const DiagnosticGuard&&) = delete; + + /// Consumes or logs the error based on the scope's silence setting. + void handleOrConsume(llvm::Error err, const char* msg) const { + if (!err) + return; // Safety check for "success" errors + + if (m_is_silent) + llvm::consumeError(std::move(err)); + else + llvm::logAllUnhandledErrors(std::move(err), llvm::errs(), msg); + } +}; + #if CLANG_VERSION_MAJOR < 21 #define Print_Canonical_Types PrintCanonicalTypes #else diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 130d80607e307..0ddf49af72ee6 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -3668,30 +3668,14 @@ void GetIncludePaths(std::vector& IncludePaths, bool withSystem, IncludePaths.push_back(i); } -namespace { - -class clangSilent { -public: - clangSilent(clang::DiagnosticsEngine& diag) : fDiagEngine(diag) { - fOldDiagValue = fDiagEngine.getSuppressAllDiagnostics(); - fDiagEngine.setSuppressAllDiagnostics(true); - } - - ~clangSilent() { fDiagEngine.setSuppressAllDiagnostics(fOldDiagValue); } - -protected: - clang::DiagnosticsEngine& fDiagEngine; - bool fOldDiagValue; -}; -} // namespace int Declare(compat::Interpreter& I, const char* code, bool silent) { - if (silent) { - clangSilent diagSuppr(I.getSema().getDiagnostics()); - return I.declare(code); - } - +#ifdef CPPINTEROP_USE_CLING + DiagnosticGuard Guard(I.getSema().getDiagnostics(), silent); return I.declare(code); +#else + return I.declare(code, silent); +#endif // CPPINTEROP_USE_CLING } int Declare(const char* code, bool silent) { diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h index 83f17df7d3dbf..b51f188b23d1f 100644 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOpInterpreter.h @@ -46,6 +46,7 @@ #include #include #include +#include #include #include #include @@ -391,21 +392,23 @@ class Interpreter { return nullptr; } - CompilationResult declare(const std::string& input, + CompilationResult declare(const std::string& input, bool silent = false, clang::PartialTranslationUnit** PTU = nullptr) { - return process(input, /*Value=*/nullptr, PTU); + return process(input, /*V=*/nullptr, silent, PTU); } - ///\brief Maybe transform the input line to implement cint command line + /// Maybe transform the input line to implement cint command line /// semantics (declarations are global) and compile to produce a module. /// CompilationResult process(const std::string& input, clang::Value* V = 0, + bool silent = false, clang::PartialTranslationUnit** PTU = nullptr, bool disableValuePrinting = false) { + DiagnosticGuard Guard(getSema().getDiagnostics(), silent); auto PTUOrErr = Parse(input); if (!PTUOrErr) { - llvm::logAllUnhandledErrors(PTUOrErr.takeError(), llvm::errs(), - "Failed to parse via ::process:"); + Guard.handleOrConsume(PTUOrErr.takeError(), + "Failed to parse via ::process:"); return Interpreter::kFailure; } @@ -413,8 +416,7 @@ class Interpreter { *PTU = &*PTUOrErr; if (auto Err = Execute(*PTUOrErr)) { - llvm::logAllUnhandledErrors(std::move(Err), llvm::errs(), - "Failed to execute via ::process:"); + Guard.handleOrConsume(std::move(Err), "Failed to execute via ::process:"); return Interpreter::kFailure; } return Interpreter::kSuccess; diff --git a/interpreter/CppInterOp/unittests/CMakeLists.txt b/interpreter/CppInterOp/unittests/CMakeLists.txt index 295f1b0a9f182..5faf6657ad487 100644 --- a/interpreter/CppInterOp/unittests/CMakeLists.txt +++ b/interpreter/CppInterOp/unittests/CMakeLists.txt @@ -41,6 +41,13 @@ function(add_cppinterop_unittest name) ${ARGN} ) add_executable(${name} EXCLUDE_FROM_ALL ${ARG_UNPARSED_ARGUMENTS}) + if(NOT LLVM_ENABLE_RTTI) + if(MSVC) + target_compile_options(${name} PRIVATE "/GR-") + else() + target_compile_options(${name} PRIVATE "-fno-rtti") + endif() + endif() add_dependencies(CppInterOpUnitTests ${name}) target_include_directories(${name} PUBLIC ${CMAKE_CURRENT_BINARY_DIR} ${GTEST_INCLUDE_DIR}) set_property(TARGET ${name} PROPERTY RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp index 2c0e70f37df5d..2479d96e7a5ba 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/InterpreterTest.cpp @@ -199,6 +199,35 @@ TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_EmscriptenExceptionHandling) { EXPECT_TRUE(Cpp::Process(tryCatchCode) == 0); } +TYPED_TEST(CPPINTEROP_TEST_MODE, Silent_Declare_Behavior) { + auto* I = TestFixture::CreateInterpreter(); + ASSERT_TRUE(I); + + EXPECT_FALSE(Cpp::Declare("int valid_var = 42;", /*silent=*/true)); + + // Should produce output + { + testing::internal::CaptureStderr(); + // Intentionally broken code: "int x = ;" + bool success = Cpp::Declare("int broken_syntax = ;", /*silent=*/false); + std::string output = testing::internal::GetCapturedStderr(); + EXPECT_TRUE(success); + EXPECT_FALSE(output.empty()) << "We expect to see a Clang error message\n"; + } + + // Should NOT produce output + { + testing::internal::CaptureStderr(); + // Intentionally broken code + bool success = Cpp::Declare("invalid_type error_var = 0;", /*silent=*/true); + std::string output = testing::internal::GetCapturedStderr(); + EXPECT_TRUE(success); + EXPECT_TRUE(output.empty()) + << "Silent Declare leaked an error to stderr: " << output; + } +} + + TYPED_TEST(CPPINTEROP_TEST_MODE, Interpreter_CreateInterpreter) { auto* I = TestFixture::CreateInterpreter(); EXPECT_TRUE(I); diff --git a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp index d603737da36f2..2686e43a11d5f 100644 --- a/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp +++ b/interpreter/CppInterOp/unittests/CppInterOp/Utils.cpp @@ -72,7 +72,7 @@ void TestUtils::GetAllTopLevelDecls( } #else PartialTranslationUnit *T = nullptr; - Interp->process(code, /*Value*/nullptr, &T); + Interp->process(code, /*Value*/ nullptr, /*silent=*/false, &T); for (auto *D : T->TUPart->decls()) { if (filter_implicitGenerated && D->isImplicit()) continue; From 66fbcd04f84a57e11a000990df0ed1b0bc300c55 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 30 Mar 2026 15:49:31 +0200 Subject: [PATCH 29/29] upstream patch cppyy-backend#192 From Vassil: silence lookups relying on error --- .../cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index 13fab948aad5b..a32233c6058fa 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -718,8 +718,7 @@ bool Cppyy::AppendTypesSlow(const std::string& name, Cpp::Declare(code.c_str(), /*silent=*/true); // initialize the trampoline std::string var = "__Cppyy_s" + std::to_string(struct_count++); - // FIXME: We cannot use silent because it erases our error code from Declare! - if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + resolved_name + "> " + var +";\n").c_str(), /*silent=*/false)) { + if (!Cpp::Declare(("__Cppyy_AppendTypesSlow<" + resolved_name + "> " + var +";\n").c_str(), /*silent=*/true)) { std::lock_guard Lock(InterOpMutex); TCppType_t varN = Cpp::GetVariableType(Cpp::GetNamed(var.c_str(), /*parent=*/nullptr));