From 3ff803dd653b1b41a913aee4a1140961920fb329 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 14:18:01 +0200 Subject: [PATCH 01/45] [cppinterop] Add Cling-specific RAII, lookup and dispatch patches [upstream] Adapts upstream CppInterOp for use with ROOT & Cling: - Add SynthesizingCodeRAII guard for scope/class reflection - Call buildLookup in lookup paths, to populate Cling's lazy lookup tables - Drop class_name:: scope qualification in make_narg_call so virtual dispatch works for pure-virtual methods (TInterpreter::Declare) - Export CppGetProcAddress from libCling's linker script for dispatch mechanism - GetTypeAsString: revert the PrintingPolicy ctor to PrintingPolicy((LangOptions())) instead of deriving it from the ASTContext. Using default LangOptions restores the string form (std::basic_string) that the CPyCppyy factory expects --- core/metacling/src/libCling.script | 1 + .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 50 ++++++++++++++++--- 2 files changed, 45 insertions(+), 6 deletions(-) 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: *; }; diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index b00684a190c34..57ea2665b3b94 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -685,6 +685,7 @@ size_t SizeOf(TCppScope_t scope) { return INTEROP_RETURN(0); if (auto* RD = dyn_cast(static_cast(scope))) { + compat::SynthesizingCodeRAII RAII(&getInterp()); ASTContext& Context = RD->getASTContext(); const ASTRecordLayout& Layout = Context.getASTRecordLayout(RD); return INTEROP_RETURN(Layout.getSize().getQuantity()); @@ -1064,6 +1065,7 @@ TCppScope_t GetScope(const std::string& name, TCppScope_t parent) { if (name == "") return INTEROP_RETURN(GetGlobalScope()); + compat::SynthesizingCodeRAII RAII(&getInterp()); auto* ND = (NamedDecl*)GetNamed(name, parent); if (!ND || ND == (NamedDecl*)-1) @@ -1259,6 +1261,8 @@ TCppIndex_t GetNumBases(TCppScope_t klass) { INTEROP_TRACE(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); @@ -1272,6 +1276,9 @@ TCppIndex_t GetNumBases(TCppScope_t klass) { TCppScope_t GetBaseClass(TCppScope_t klass, TCppIndex_t ibase) { INTEROP_TRACE(klass, ibase); + + compat::SynthesizingCodeRAII RAII(&getInterp()); + auto* D = (Decl*)klass; auto* CXXRD = llvm::dyn_cast_or_null(D); if (!CXXRD || CXXRD->getNumBases() <= ibase) @@ -1297,6 +1304,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 INTEROP_RETURN(false); @@ -1313,6 +1322,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; @@ -1361,6 +1373,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)) @@ -1396,6 +1410,8 @@ static void GetClassDecls(TCppScope_t klass, if (!klass) return; + compat::SynthesizingCodeRAII RAII(&getInterp()); + auto* D = (clang::Decl*)klass; if (auto* TD = dyn_cast(D)) @@ -1405,7 +1421,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); @@ -1512,8 +1527,13 @@ std::vector GetFunctionsUsingName(TCppScope_t scope, DeclarationName DName = &getASTContext().Idents.get(name); clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, RedeclarationKind::ForVisibleRedeclaration); - - 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 INTEROP_RETURN(funcs); @@ -1686,6 +1706,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) @@ -1734,6 +1759,11 @@ bool GetClassTemplatedMethods(const std::string& name, TCppScope_t parent, clang::LookupResult R(S, DName, SourceLocation(), Sema::LookupOrdinaryName, RedeclarationKind::ForVisibleRedeclaration); 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()) @@ -2070,6 +2100,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)) { @@ -2082,6 +2117,7 @@ TCppScope_t LookupDatamember(const std::string& name, TCppScope_t parent) { bool IsLambdaClass(TCppType_t type) { INTEROP_TRACE(type); + compat::SynthesizingCodeRAII RAII(&getInterp()); QualType QT = QualType::getFromOpaquePtr(type); if (auto* CXXRD = QT->getAsCXXRecordDecl()) { return INTEROP_RETURN(CXXRD->isLambda()); @@ -2118,6 +2154,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(); @@ -2364,6 +2401,8 @@ TCppType_t GetPointerType(TCppType_t type) { TCppType_t GetReferencedType(TCppType_t type, bool rvalue) { INTEROP_TRACE(type, rvalue); + if (!type) + return INTEROP_RETURN(nullptr); QualType QT = QualType::getFromOpaquePtr(type); if (rvalue) return INTEROP_RETURN( @@ -2404,7 +2443,8 @@ TCppType_t GetUnderlyingType(TCppType_t type) { std::string GetTypeAsString(TCppType_t var) { INTEROP_TRACE(var); QualType QT = QualType::getFromOpaquePtr(var); - PrintingPolicy Policy(getASTContext().getPrintingPolicy()); + // FIXME: Get the default printing policy from the ASTContext. + PrintingPolicy Policy((LangOptions())); Policy.Bool = true; // Print bool instead of _Bool. Policy.SuppressTagKeyword = true; // Do not print `class std::string`. Policy.Suppress_Elab = true; @@ -2921,8 +2961,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 cda7a80e47389205e6c8a35f90b2021e3e480c2c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 11 May 2026 17:04:34 +0200 Subject: [PATCH 02/45] [cppinterop] Fix GetEnumConstantValue when value can't be within int64_t [upstream] --- interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 57ea2665b3b94..7432d7cbf7420 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -870,7 +870,11 @@ TCppIndex_t GetEnumConstantValue(TCppScope_t handle) { auto* D = (clang::Decl*)handle; if (auto* ECD = llvm::dyn_cast_or_null(D)) { const llvm::APSInt& Val = ECD->getInitVal(); - return INTEROP_RETURN(Val.getExtValue()); + if (Val.isRepresentableByInt64()) + return INTEROP_RETURN(Val.getExtValue()); + llvm::SmallString<40> StrVal; + Val.toString(StrVal); + return INTEROP_RETURN(std::stoul(StrVal.c_str())); } return INTEROP_RETURN(0); } From a2f13b07f4eb8ba53a4b9a82e1a2256f5e946c3e Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 11 May 2026 17:05:15 +0200 Subject: [PATCH 03/45] [cppinterop] Fix JitCall codegen for deleted copy ctor in args [upstream] --- .../CppInterOp/lib/CppInterOp/CppInterOp.cpp | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index 7432d7cbf7420..cfa31080a7a0c 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -2832,6 +2832,33 @@ void collect_type_info(const FunctionDecl* FD, QualType& QT, get_type_as_string(QT, type_name, C, Policy); } +static bool IsCopyConstructorDeleted(QualType QT) { + CXXRecordDecl* RD = QT->getAsCXXRecordDecl(); + if (!RD) { + // For types that are not C++ records (such as PODs), we assume that they + // are copyable, ie their copy constructor is not deleted. + return false; + } + + RD = RD->getDefinition(); + assert(RD && "expecting a definition"); + + if (RD->hasSimpleCopyConstructor()) + return false; + + for (auto* Ctor : RD->ctors()) { + if (Ctor->isCopyConstructor()) { + return Ctor->isDeleted(); + } + } + + assert(0 && "did not find a copy constructor?"); + // Should never happen and the return value is somewhat arbitrary, but we did + // not see a deleted copy ctor. The user will be told if the generated code + // doesn't compile. + return false; +} + void make_narg_ctor(const FunctionDecl* FD, const unsigned N, std::ostringstream& typedefbuf, std::ostringstream& callbuf, const std::string& class_name, int indent_level, @@ -2876,7 +2903,18 @@ void make_narg_ctor(const FunctionDecl* FD, const unsigned N, } else if (isPointer) { callbuf << "*(" << type_name.c_str() << "**)args[" << i << "]"; } else { + // By-value construction: Figure out if the type can be + // copy-constructed. This is tricky and cannot be done in a fully + // reliable way, also because std::vector always defines a copy + // constructor, even if the type T is only moveable. As a heuristic, we + // only check if the copy constructor is deleted, or would be if + // implicit. + bool Move = IsCopyConstructorDeleted(QT); + if (Move) + callbuf << "static_cast<" << type_name << "&&>("; callbuf << "*(" << type_name.c_str() << "*)args[" << i << "]"; + if (Move) + callbuf << ")"; } } callbuf << ")"; From 2e16fca4ec0657bbdc9818536ac7c8a32a89c1d2 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 22 May 2026 01:18:39 +0200 Subject: [PATCH 04/45] [cppinterop] Use canonical return type in wrapper codegen [upstream] --- interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp index cfa31080a7a0c..f1f64a561ca6e 100755 --- a/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp +++ b/interpreter/CppInterOp/lib/CppInterOp/CppInterOp.cpp @@ -3229,7 +3229,7 @@ void make_narg_call_with_return(compat::Interpreter& I, const FunctionDecl* FD, make_narg_ctor_with_return(FD, N, class_name, buf, indent_level); return; } - QualType QT = FD->getReturnType(); + QualType QT = FD->getReturnType().getCanonicalType(); if (QT->isVoidType()) { std::ostringstream typedefbuf; std::ostringstream callbuf; From 8e9a86c02564f05228cd2d1e10981223d1b01a12 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 14:20:24 +0200 Subject: [PATCH 05/45] [cppyy-backend] Replace clingwrapper with compres forks [fork-baseline] Source: compres forks cppyy-backend/master 597bcf4 Drops ROOT's old clingwrapper in favour of the CppInterOp-based implementation from the compres fork. Replaces gInterpreter/ROOT-meta TCling API calls with CppInterOp via the dispatch mechanism (cppinterop_dispatch.cxx to load API) Adds recursive_mutex thread safety, switches execution to JitCall. To be followed with ROOT-specific patches. --- .../clingwrapper/src/callcontext.h | 5 - .../clingwrapper/src/clingwrapper.cxx | 3637 ++++++++--------- .../clingwrapper/src/cpp_cppyy.h | 283 +- .../clingwrapper/src/cppinterop_dispatch.cxx | 11 + 4 files changed, 1822 insertions(+), 2114 deletions(-) create mode 100644 bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cppinterop_dispatch.cxx diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h index edd7ca522c864..b0ae54af5a43b 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/callcontext.h @@ -4,11 +4,6 @@ // Standard #include -//Bindings -#include "cpp_cppyy.h" - -//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 86954bba721cd..07af11250ba0e 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -1,226 +1,82 @@ -#ifndef WIN32 +#ifndef _WIN32 #ifndef _CRT_SECURE_NO_WARNINGS // silence warnings about getenv, strncpy, etc. #define _CRT_SECURE_NO_WARNINGS #endif #endif +#include "precommondefs.h" // This defines several system feature macros and should be included before any system header. + + // Bindings -#include "precommondefs.h" #include "cpp_cppyy.h" #include "callcontext.h" -// ROOT -#include "TBaseClass.h" -#include "TClass.h" -#include "TClassRef.h" -#include "TClassTable.h" -#include "TClassEdit.h" -#include "TCollection.h" -#include "TDataMember.h" -#include "TDataType.h" -#include "TEnum.h" -#include "TEnumConstant.h" -#include "TEnv.h" -#include "TError.h" -#include "TException.h" -#include "TFunction.h" -#include "TFunctionTemplate.h" -#include "TGlobal.h" -#include "THashList.h" -#include "TInterpreter.h" -#include "TList.h" -#include "TListOfDataMembers.h" -#include "TListOfEnums.h" -#include "TMethod.h" -#include "TMethodArg.h" -#include "TROOT.h" -#include "TSystem.h" -#include "TThread.h" +#ifndef _WIN32 +#include +#endif // Standard #include #include // for std::count, std::remove -#include #include #include #include +#include #include #include #include #include // for getenv #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; -} +#include +#include // 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; + +std::recursive_mutex InterOpMutex; + +// 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 +89,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 +127,99 @@ 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(); -} +// 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); +// } +// }; -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); - - // 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; - - // setup dummy holders for global and std namespaces - assert(g_classrefs.size() == GLOBAL_HANDLE); - g_name2classrefidx[""] = GLOBAL_HANDLE; - g_classrefs.push_back(TClassRef("")); + std::lock_guard Lock(InterOpMutex); + if (!Cpp::LoadDispatchAPI( + CPPINTEROP_DIR + "/lib/libclangCppInterOp" CMAKE_SHARED_LIBRARY_SUFFIX)) { + std::cerr << "[cppyy-backend] Failed to load CppInterOp" << std::endl; + return; + } + // 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 +227,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; - -#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 + Cpp::Declare("namespace __cppyy_internal { struct Sep; }", + /*silent=*/false); + + // 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,511 +356,633 @@ 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) +{ + std::lock_guard Lock(InterOpMutex); + // 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; + std::lock_guard Lock(InterOpMutex); + 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) { +std::lock_guard Lock(InterOpMutex); + 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) { + std::lock_guard Lock(InterOpMutex); + 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()); -} + std::lock_guard Lock(InterOpMutex); -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 ""; -} + TCppType_t check_int_typedefs = int_like_type(type); + if (check_int_typedefs) + return type; + Cppyy::TCppType_t canonType = Cpp::GetCanonicalType(type); -// 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. + if (Cpp::IsEnumType(canonType)) { + if (Cpp::GetTypeAsString(type) != "std::byte") + return Cpp::GetIntegerTypeFromEnumType(canonType); + } + if (Cpp::HasTypeQualifier(canonType, Cpp::QualKind::Restrict)) { + return Cpp::RemoveTypeQualifier(canonType, Cpp::QualKind::Restrict); + } -// try memoized type cache, in case seen before - std::string memoized = find_memoized_resolved_name(cppitem_name); - if (!memoized.empty()) return memoized; + return canonType; +} -// 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::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; + return Cpp::GetUnderlyingType(type); +} -// classes (most common) - tclean = TClassEdit::CleanType(tclean.c_str()); - if (tclean.empty() /* unknown, eg. an operator */) return cppitem_name; +Cppyy::TCppType_t Cppyy::GetPointerType(TCppType_t type) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetPointerType(type); +} -// reduce [N] to [] - if (tclean[tclean.size()-1] == ']') - tclean = tclean.substr(0, tclean.rfind('[')) + "[]"; +Cppyy::TCppType_t Cppyy::GetReferencedType(TCppType_t type, bool rvalue) { + std::lock_guard Lock(InterOpMutex); + return Cpp::GetReferencedType(type, rvalue); +} - if (tclean.rfind("byte", 0) == 0 || tclean.rfind("std::byte", 0) == 0) - return tclean; +bool Cppyy::IsRValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::RValue; +} -// remove __restrict and __restrict__ - auto pos = tclean.rfind("__restrict"); - if (pos != std::string::npos) - tclean = tclean.substr(0, pos); +bool Cppyy::IsLValueReferenceType(TCppType_t type) { + return Cpp::GetValueKind(type) == Cpp::ValueKind::LValue; +} - if (tclean.compare(0, 9, "std::byte") == 0) - return tclean; +bool Cppyy::IsClassType(TCppType_t type) { + return Cpp::IsRecordType(type); +} -// check data types list (accept only builtins as typedefs will -// otherwise not be resolved) - if (IsBuiltin(tclean)) return tclean; +bool Cppyy::IsIntegerType(TCppType_t type, bool* is_signed /*= nullptr*/) { + if (is_signed) { + Cpp::Signedness sign; + bool res = Cpp::IsIntegerType(type, &sign); + *is_signed = (sign == Cpp::Signedness::kSigned); + return res; + } + return Cpp::IsIntegerType(type, nullptr); +} -// special case for enums - if (IsEnum(cppitem_name)) - return ResolveEnum(cppitem_name); +bool Cppyy::IsPointerType(TCppType_t type) { + return Cpp::IsPointerType(type); +} -// 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); +bool Cppyy::IsFunctionPointerType(TCppType_t type) { + return Cpp::IsFunctionPointerType(type); +} - 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 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); +} - 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; +// 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; + int matching_angular_brackets = 0; + while (end_pos < trimed_name.size()) { + switch (trimed_name[end_pos]) { + case ',': { + if (!matching_angular_brackets) { + if(end_pos > start_pos) + types.push_back( + trim(trimed_name.substr(start_pos, end_pos - start_pos))); + start_pos = end_pos + 1; + } + break; } - -// 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; + case '<': { + matching_angular_brackets++; + break; + } + case '>': { + 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; +} - if (tclean.compare(0, 6, "const ") != 0) - return TClassEdit::ShortType(tclean.c_str(), 2); - return "const " + TClassEdit::ShortType(tclean.c_str(), 2); +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); } -#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; +// 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; - int tpl_open = 0; - for (std::string::size_type pos = name.size()-1; 0 < pos; --pos) { - std::string::value_type c = name[pos]; + 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 + + 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=*/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); + 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; - // count '<' and '>' to be able to skip template contents - if (c == '>') - ++tpl_open; - else if (c == '<') - --tpl_open; + for (std::string& i : individual_types) { + // Try going via Cppyy::GetType first. + const char* integral_value = nullptr; + Cppyy::TCppType_t type = nullptr; - // 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); - } + type = GetType(i, /*enable_slow_lookup=*/true); + if (!type && parent && (Cppyy::IsNamespace(parent) || Cppyy::IsClass(parent))) { + type = Cppyy::GetTypeFromScope(Cppyy::GetNamed(resolved_name, parent)); } -// no namespace; assume outer scope - return ""; + if (!type) { + types.clear(); + return true; + } + + 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; } -#endif -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"}) { - // This is pure type introspection: silence any deprecation warning that - // would otherwise be emitted just because the enum being resolved (or its - // scope) happens to be marked deprecated. - decl << "_Pragma(\"clang diagnostic push\")" - "_Pragma(\"clang diagnostic ignored \\\"-Wdeprecated-declarations\\\"\")" - << "std::is_same<" - << itype - << ", std::underlying_type<" - << et_short - << ">::type>::value;" - "_Pragma(\"clang diagnostic pop\")"; - 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; - } - } +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)) + return type; + + if (!enable_slow_lookup) { + if (name.find("::") != std::string::npos) + throw std::runtime_error("Calling Cppyy::GetType with qualified name '" + + name + "'\n"); + return nullptr; } -// 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; + // Here we might need to deal with integral types such as 3.14. + + 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=*/true)) { + 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; + +Cppyy::TCppType_t Cppyy::GetComplexType(const std::string &name) { + std::lock_guard Lock(InterOpMutex); + 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::lock_guard Lock(InterOpMutex); + std::string type = Cpp::GetTypeAsString( + Cpp::GetIntegerTypeFromEnumScope(handle)); + if (type == "signed char") + return "char"; + return type; +} + +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); + 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; + 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; +} -// 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::TCppScope_t Cppyy::GetFullScope(const std::string& name) +{ + return Cppyy::GetScope(name); +} - 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::GetTypeScope(TCppScope_t var) +{ + std::lock_guard Lock(InterOpMutex); + return Cpp::GetScopeFromType( + Cpp::GetVariableType(var)); +} - 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::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); +} - g_classrefs.push_back(TClassRef(scope_name.c_str())); +Cppyy::TCppScope_t Cppyy::GetScopeFromType(TCppType_t type) +{ + std::lock_guard Lock(InterOpMutex); + return Cpp::GetScopeFromType(type); +} - return (TCppScope_t)sz; +Cppyy::TCppType_t Cppyy::GetTypeFromScope(TCppScope_t klass) +{ + std::lock_guard Lock(InterOpMutex); + return Cpp::GetTypeFromScope(klass); } -bool Cppyy::IsTemplate(const std::string& template_name) +Cppyy::TCppScope_t Cppyy::GetGlobalScope() { - return (bool)gInterpreter->CheckClassTemplate(template_name.c_str()); + std::lock_guard Lock(InterOpMutex); + return Cpp::GetGlobalScope(); } -namespace { - class AutoCastRTTI { - public: - virtual ~AutoCastRTTI() {} - }; +bool Cppyy::IsTemplate(TCppScope_t handle) +{ + return Cpp::IsTemplate(handle); +} + +bool Cppyy::IsTemplateInstantiation(TCppScope_t handle) +{ + return Cpp::IsTemplateSpecialization(handle); } -Cppyy::TCppType_t Cppyy::GetActualClass(TCppType_t klass, TCppObject_t obj) +bool Cppyy::IsTypedefed(TCppScope_t handle) { - TClassRef& cr = type_from_handle(klass); - if (!cr.GetClass() || !obj) return klass; + return Cpp::IsTypedefed(handle); +} + +namespace { +class AutoCastRTTI { +public: + virtual ~AutoCastRTTI() {} +}; +} // namespace - if (!(cr->ClassProperty() & kClassHasVirtual)) - return klass; // not polymorphic: no RTTI info available +Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { + std::lock_guard Lock(InterOpMutex); -// 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) + if (!Cpp::IsClassPolymorphic(klass)) return klass; -#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()); - } - - return klass; // do not fall through if no RTTI info available - } + const std::type_info *typ = &typeid(*(AutoCastRTTI *)obj); - // 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 + std::string mangled_name = typ->name(); + std::string demangled_name = Cpp::Demangle(mangled_name); - 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()); - } + 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; + std::lock_guard Lock(InterOpMutex); + 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)); + std::lock_guard Lock(InterOpMutex); + 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())); + std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); + 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); + std::lock_guard Lock(InterOpMutex); + 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); - } - } + std::lock_guard Lock(InterOpMutex); // TODO: this shouldn't locks the JIT call + 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; + std::lock_guard Lock(InterOpMutex); // TODO: this shouldn't locks the JIT call + Cpp::Destruct(instance, scope, true, /*count=*/0); } static inline @@ -987,60 +1008,49 @@ 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 (faceptr.fKind == TInterpreter::CallFuncIFacePtr_t::kGeneric) { + // 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) { void* smallbuf[SMALL_ARGS_N]; if (nargs) runRelease = copy_args(args, nargs, smallbuf); - faceptr.fGeneric(self, (int)nargs, smallbuf, result); - } else { - std::vector buf(nargs); - runRelease = copy_args(args, nargs, buf.data()); - faceptr.fGeneric(self, (int)nargs, buf.data(), result); - } - 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); + // 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.fCtor(buf.data(), result, (unsigned long)nargs); + // 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::kDtor) { - std::cerr << " DESTRUCTOR NOT IMPLEMENTED YET! " << std::endl; - return false; - } - + InterOpMutex.unlock(); return false; } @@ -1051,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); \ } @@ -1071,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) { @@ -1088,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); @@ -1101,35 +1120,23 @@ 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); + std::lock_guard Lock(InterOpMutex); // TODO: this shouldn't locks the JIT call + 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(Cppyy::SizeOfType(result_type)); if (WrapperCall(method, nargs, args, self, obj)) return (TCppObject_t)obj; ::operator delete(obj); @@ -1138,54 +1145,8 @@ 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()); + std::lock_guard Lock(InterOpMutex); + return (TCppFuncAddr_t) Cpp::GetFunctionAddress(method); } @@ -1214,351 +1175,204 @@ 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. + std::lock_guard Lock(InterOpMutex); + 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; -} - -bool Cppyy::IsEnum(const std::string& type_name) -{ - 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 scope represents a namespace. + return Cpp::IsClass(scope); } - -bool Cppyy::IsAggregate(TCppType_t type) +// +bool Cppyy::IsAbstract(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; + // Test if this type may not be instantiated. + return Cpp::IsAbstract(scope); } -bool Cppyy::IsIntegerType(const std::string &type_name) +bool Cppyy::IsEnumScope(TCppScope_t scope) { - // Test if the named type is an integer type - TypeInfo_t *ti = gInterpreter->TypeInfo_Factory(type_name.c_str()); - if (!ti) - return false; - void *qtp = gInterpreter->TypeInfo_QualTypePtr(ti); - bool result = qtp ? gInterpreter->IsIntegerType(qtp) : false; - gInterpreter->TypeInfo_Delete(ti); - return result; + 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(Cppyy::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); - } - } - -// 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())); - } -} + std::lock_guard Lock(InterOpMutex); +// 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 + std::lock_guard Lock(InterOpMutex); + 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; + std::lock_guard Lock(InterOpMutex); + 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; + std::lock_guard Lock(InterOpMutex); + 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) + 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); +} + +// 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; + std::lock_guard Lock(InterOpMutex); + 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. @@ -1578,159 +1392,90 @@ 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; + std::lock_guard Lock(InterOpMutex); + 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(); + std::lock_guard Lock(InterOpMutex); + 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; + std::lock_guard Lock(InterOpMutex); + 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; + { + 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(Cppyy::GetMethodReturnType(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; - } - - offset = gInterpreter->ClassInfo_GetBaseOffset( - cd->GetClassInfo(), cb->GetClassInfo(), (void*)address, direction > 0); + std::lock_guard Lock(InterOpMutex); + intptr_t offset = Cpp::GetBaseClassOffset(derived, base); + if (offset == -1) // Cling error, treat silently return rerror ? (ptrdiff_t)offset : 0; @@ -1738,931 +1483,763 @@ 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) +{ + std::lock_guard Lock(InterOpMutex); + 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; -} - + std::lock_guard Lock(InterOpMutex); + 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 ""; + std::lock_guard Lock(InterOpMutex); + 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 ""; + std::lock_guard Lock(InterOpMutex); + 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 ""; + std::lock_guard Lock(InterOpMutex); + 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 ""; + std::lock_guard Lock(InterOpMutex); + return + Cpp::GetTypeAsString( + Cpp::GetCanonicalType( + Cpp::GetFunctionReturnType(method))); } Cppyy::TCppIndex_t Cppyy::GetMethodNumArgs(TCppMethod_t method) { - if (method) - return m2f(method)->GetNargs(); - return 0; + std::lock_guard Lock(InterOpMutex); + 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; + std::lock_guard Lock(InterOpMutex); + 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 ""; + + std::lock_guard Lock(InterOpMutex); + 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 + std::lock_guard Lock(InterOpMutex); + return Cpp::GetFunctionArgType(method, iarg); +} - return arg->GetTypeNormalizedName(); - } - return ""; +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)); } -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 + std::lock_guard Lock(InterOpMutex); + 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; - } + if (!method) + return ""; - return ""; + std::lock_guard Lock(InterOpMutex); + 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; + std::lock_guard Lock(InterOpMutex); + 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; + std::lock_guard Lock(InterOpMutex); + 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(); - } + std::lock_guard Lock(InterOpMutex); + 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::lock_guard Lock(InterOpMutex); + 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::lock_guard Lock(InterOpMutex); + 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 Cpp::GetName(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; + std::lock_guard Lock(InterOpMutex); + 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; + std::vector candidate_methods; + Cpp::GetClassTemplatedMethods(name, scope, candidate_methods); + bool is_static = true; + for (auto i: candidate_methods) { + if (!Cpp::IsStaticMethod(i)) { + is_static = false; + break; + } } - - assert(scope == (Cppyy::TCppType_t)GLOBAL_HANDLE); - if (((CallWrapper*)idx)->fName.find('<') != std::string::npos) return true; - 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; + return is_static; } 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; } + + std::lock_guard Lock(InterOpMutex); - 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); + Cppyy::TCppMethod_t cppmeth = nullptr; + cppmeth = Cpp::BestOverloadFunctionMatch( + unresolved_candidate_methods, templ_params, arg_types); - // disregard this non-templated method as it will be considered when appropriate - return (TCppMethod_t)nullptr; + 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); } -// 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; - } - } - } - } + 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 - } else if (n1 == "float") { - return "double"; // debatable, but probably intended +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 == "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::lock_guard Lock(InterOpMutex); + 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); -// failure ... - return (TCppIndex_t)-1; -} + std::vector overloads; + Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads, + /*kind=*/Cpp::OperatorArity::kBoth); -// method properties --------------------------------------------------------- + // Avoid pushing nullptr into arg_types which would crash + // BestOverloadFunctionMatch when it dereferences each entry's QualType. + auto resolve_arg_type = [](const std::string& name) -> Cppyy::TCppType_t { + if (auto s = Cppyy::GetScope(name, 0)) + if (auto t = Cppyy::GetTypeFromScope(s)) + return Cppyy::GetReferencedType(t); + return Cppyy::GetType(name, /*enable_slow_lookup=*/true); + }; + + std::vector arg_types; + if (auto l = resolve_arg_type(lc_type)) + arg_types.emplace_back(l); + else + return nullptr; -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 (auto r = resolve_arg_type(rc_type)) + arg_types.emplace_back(r); + else + return nullptr; + } + Cppyy::TCppMethod_t cppmeth = Cpp::BestOverloadFunctionMatch( + overloads, {}, arg_types); + if (cppmeth) + return cppmeth; + 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? +// } + +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); } -// 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? -} +bool Cppyy::IsLambdaClass(TCppType_t type) { + return Cpp::IsLambdaClass(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" + << " " << "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::lock_guard Lock(InterOpMutex); + + 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) +{ + 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); +} + +// 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; +// } -std::string Cppyy::GetDatamemberName(TCppScope_t scope, TCppIndex_t idata) +// data member properties ---------------------------------------------------- +bool Cppyy::IsPublicData(TCppScope_t datamem) { - 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(); + return Cpp::IsPublicVariable(datamem); } -static inline -int count_scopes(const std::string& tpname) +bool Cppyy::IsProtectedData(TCppScope_t datamem) { - int count = 0; - std::string::size_type pos = tpname.find("::", 0); - while (pos != std::string::npos) { - count++; - pos = tpname.find("::", pos+1); - } - return count; + return Cpp::IsProtectedVariable(datamem); } -std::string Cppyy::GetDatamemberType(TCppScope_t scope, TCppIndex_t idata) +bool Cppyy::IsPrivateData(TCppScope_t datamem) { - 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 ""; + return Cpp::IsPrivateVariable(datamem); } -intptr_t Cppyy::GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata) +bool Cppyy::IsStaticDatamember(TCppScope_t var) { - 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::IsStaticVariable(Cppyy::GetUnderlyingScope(var)); } -static inline -Cppyy::TCppIndex_t gb2idx(TGlobal* gb) +bool Cppyy::IsConstVar(TCppScope_t var) { - 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()); - } - } + return Cpp::IsConstVariable(var); +} - 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; - } +Cppyy::TCppScope_t Cppyy::ReduceReturnType(TCppScope_t fn, TCppType_t reduce) { + std::lock_guard Lock(InterOpMutex); - return gb2idx(gb); + std::string fn_name = Cpp::GetQualifiedCompleteName(fn); + std::string signature = Cppyy::GetMethodSignature(fn, true); + std::string result_type = Cppyy::GetTypeAsString(reduce); - } 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); - } + 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 << ", "; } - - 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); + 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; } - - return idata; + 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. -// data member properties ---------------------------------------------------- -bool Cppyy::IsPublicData(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() & kIsPublic; -} +// if (scope == GLOBAL_HANDLE) { +// TGlobal* gbl = g_globalvars[idata]; -bool Cppyy::IsProtectedData(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() & kIsProtected; -} +// // 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); +// } -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; -} +// TClassRef& cr = type_from_handle(scope); +// if (cr.GetClass()) { +// TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); +// std::string ti = m->GetTypeName(); -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(); - } - TClassRef& cr = type_from_handle(scope); - if (cr.GetClass()) { - TDataMember* m = (TDataMember*)cr->GetListOfDataMembers()->At((int)idata); - property = m->Property(); - } +// // 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; -// 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); -} +// // 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()); +// } +// } +// } -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]; - - // 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()); - } - } - } - -// this default return only means that the data will be writable, not that it will -// be unreadable or otherwise misrepresented - return false; -} +// // this default return only means that the data will be writable, not that it will +// // be unreadable or otherwise misrepresented +// return false; +// } -int Cppyy::GetDimensionSize(TCppScope_t scope, TCppIndex_t idata, int dimension) +std::vector Cppyy::GetDimensions(TCppType_t type) { - 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; + std::lock_guard Lock(InterOpMutex); + return Cpp::GetDimensions(type); } - // enum properties ----------------------------------------------------------- -Cppyy::TCppEnum_t Cppyy::GetEnum(TCppScope_t scope, const std::string& enum_name) +std::vector Cppyy::GetEnumConstants(TCppScope_t scope) { - 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; + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstants(scope); } -Cppyy::TCppIndex_t Cppyy::GetNumEnumData(TCppEnum_t etype) +Cppyy::TCppType_t Cppyy::GetEnumConstantType(TCppScope_t scope) { - return (TCppIndex_t)((TEnum*)etype)->GetConstants()->GetSize(); + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstantType(Cpp::GetUnderlyingScope(scope)); } -std::string Cppyy::GetEnumDataName(TCppEnum_t etype, TCppIndex_t idata) +Cppyy::TCppIndex_t Cppyy::GetEnumDataValue(TCppScope_t scope) { - return ((TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata))->GetName(); + std::lock_guard Lock(InterOpMutex); + return Cpp::GetEnumConstantValue(scope); } -long long Cppyy::GetEnumDataValue(TCppEnum_t etype, TCppIndex_t idata) +// 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) { - TEnumConstant* ecst = (TEnumConstant*)((TEnum*)etype)->GetConstants()->At((int)idata); - return (long long)ecst->GetValue(); + 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 fbbbf21012931..33c812c4f789d 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -1,13 +1,18 @@ #ifndef CPYCPPYY_CPPYY_H #define CPYCPPYY_CPPYY_H +#include + // Standard +#include #include #include #include #include #include +#include "callcontext.h" + // some more types; assumes Cppyy.h follows Python.h #ifndef PY_LONG_LONG #ifdef _WIN32 @@ -29,54 +34,118 @@ 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 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 IsLValueReferenceType(TCppType_t type); + RPY_EXPORTED + bool IsRValueReferenceType(TCppType_t type); + RPY_EXPORTED + bool IsClassType(TCppType_t type); + RPY_EXPORTED + bool IsIntegerType(TCppType_t type, bool* is_signed = nullptr); + RPY_EXPORTED + bool IsPointerType(TCppType_t type); + RPY_EXPORTED + bool IsFunctionPointerType(TCppType_t type); + RPY_EXPORTED + TCppType_t GetType(const std::string &name, bool enable_slow_lookup = false); + RPY_EXPORTED + bool AppendTypesSlow(const std::string &name, + std::vector& types, Cppyy::TCppScope_t parent = nullptr); + RPY_EXPORTED + TCppType_t GetComplexType(const std::string &element_type); + RPY_EXPORTED + 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 GetScope(const std::string& scope_name); + TCppScope_t GetGlobalScope(); RPY_EXPORTED - TCppType_t GetActualClass(TCppType_t klass, TCppObject_t obj); + TCppScope_t GetActualClass(TCppScope_t klass, TCppObject_t obj); RPY_EXPORTED - size_t SizeOf(TCppType_t klass); + size_t SizeOf(TCppScope_t klass); RPY_EXPORTED - size_t SizeOf(const std::string& type_name); + 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 +174,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,30 +193,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 IsTemplate(TCppScope_t scope); + RPY_EXPORTED + 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 IsAbstract(TCppType_t type); + bool IsEnumConstant(TCppScope_t scope); RPY_EXPORTED - bool IsEnum(const std::string& type_name); + bool IsEnumType(TCppType_t type); RPY_EXPORTED bool IsAggregate(TCppType_t type); RPY_EXPORTED - bool IsIntegerType(const std::string &type_name); + bool IsDefaultConstructable(TCppScope_t scope); RPY_EXPORTED - bool IsDefaultConstructable(TCppType_t type); + 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 @@ -155,47 +234,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(TCppScope_t klass); RPY_EXPORTED - TCppIndex_t GetNumBases(TCppType_t type); + TCppIndex_t GetNumBasesLongestBranch(TCppScope_t klass); RPY_EXPORTED - TCppIndex_t GetNumBasesLongestBranch(TCppType_t type); + std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase); RPY_EXPORTED - std::string GetBaseName(TCppType_t type, TCppIndex_t ibase); + TCppScope_t GetBaseScope(TCppScope_t klass, TCppIndex_t ibase); RPY_EXPORTED - bool IsSubtype(TCppType_t derived, TCppType_t base); + bool IsSubclass(TCppType_t derived, TCppType_t base); RPY_EXPORTED - bool IsSmartPtr(TCppType_t type); + 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); + std::string GetMethodMangledName(TCppMethod_t) { assert(0 && "GetMethodMangledName"); return ""; } RPY_EXPORTED - std::string GetMethodResultType(TCppMethod_t); + TCppType_t GetMethodReturnType(TCppMethod_t); + RPY_EXPORTED + std::string GetMethodReturnTypeAsString(TCppMethod_t); RPY_EXPORTED TCppIndex_t GetMethodNumArgs(TCppMethod_t); RPY_EXPORTED @@ -203,44 +288,57 @@ 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(TCppMethod_t, bool show_formal_args); RPY_EXPORTED - std::string GetMethodPrototype(TCppScope_t scope, TCppMethod_t, bool show_formalargs); + std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); 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); @@ -249,44 +347,71 @@ namespace Cppyy { 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 + 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 + bool IsLambdaClass(TCppType_t type); + RPY_EXPORTED + TCppScope_t WrapLambdaFromVariable(TCppScope_t var); RPY_EXPORTED - TCppIndex_t GetNumDatamembers(TCppScope_t scope, bool accept_namespace = false); + TCppScope_t AdaptFunctionForLambdaReturn(TCppScope_t fn); RPY_EXPORTED - std::string GetDatamemberName(TCppScope_t scope, TCppIndex_t idata); + TCppType_t GetDatamemberType(TCppScope_t data); RPY_EXPORTED - std::string GetDatamemberType(TCppScope_t scope, TCppIndex_t idata); + std::string GetDatamemberTypeAsString(TCppScope_t var); RPY_EXPORTED - intptr_t GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata); + std::string GetTypeAsString(TCppType_t type); RPY_EXPORTED - TCppIndex_t GetDatamemberIndex(TCppScope_t scope, const std::string& name); + intptr_t GetDatamemberOffset(TCppScope_t var, TCppScope_t klass = nullptr); RPY_EXPORTED - TCppIndex_t GetDatamemberIndexEnumerated(TCppScope_t scope, TCppIndex_t idata); + 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); + // IsEnumData is unused. + // 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 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..076f47e35ce91 --- /dev/null +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cppinterop_dispatch.cxx @@ -0,0 +1,11 @@ +#include +#if __has_include("CppInterOp/CppInterOpAPI.inc") +using namespace CppImpl; +#define CPPINTEROP_API_FUNC(DN, CN, Ret, DeclArgs, CallArgs, RawTypes) \ + Ret (*CppInternal::DispatchRaw::DN) RawTypes = nullptr; +#include "CppInterOp/CppInterOpAPI.inc" +#else +#define DISPATCH_API(name, type) CppAPIType::name Cpp::name = nullptr; +CPPINTEROP_API_TABLE +#undef DISPATCH_API +#endif From 79c4d61e1f779db209b67301144fe4c4839cf41c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 11 May 2026 17:03:35 +0200 Subject: [PATCH 06/45] [cppyy-backend] fix GetActualClass for null [upstream] --- .../cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx | 4 +++- 1 file changed, 3 insertions(+), 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 07af11250ba0e..600ed7ae26b30 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -899,10 +899,12 @@ class AutoCastRTTI { Cppyy::TCppScope_t Cppyy::GetActualClass(TCppScope_t klass, TCppObject_t obj) { std::lock_guard Lock(InterOpMutex); - if (!Cpp::IsClassPolymorphic(klass)) + if (!obj || !Cpp::IsClassPolymorphic(klass)) return klass; const std::type_info *typ = &typeid(*(AutoCastRTTI *)obj); + if (!typ) + return klass; std::string mangled_name = typ->name(); std::string demangled_name = Cpp::Demangle(mangled_name); From 7507140d9b26927472e2cc050cad19214bfd17f2 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 22 May 2026 01:19:04 +0200 Subject: [PATCH 07/45] [cppyy-backend] Add function-type comparison APIs [upstream] --- .../clingwrapper/src/clingwrapper.cxx | 42 ++++++++++++++++--- .../clingwrapper/src/cpp_cppyy.h | 11 ++++- 2 files changed, 45 insertions(+), 8 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index 600ed7ae26b30..9a2ea658bf825 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -1677,10 +1677,40 @@ std::string Cppyy::GetMethodSignature(TCppMethod_t method, bool show_formal_args return sig.str(); } -std::string Cppyy::GetMethodPrototype(TCppMethod_t method, bool show_formal_args) -{ - assert(0 && "Unused"); - return ""; // return Cpp::GetFunctionPrototype(method, show_formal_args); +Cppyy::TCppType_t Cppyy::GetFnTypeFromStdFn(TCppType_t fn_type) { + fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) : fn_type; + fn_type = Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; + TCppScope_t scope = Cpp::GetScopeFromType(fn_type); + std::vector args; + Cpp::GetClassTemplateArgs(scope, args); + assert(args.size() == 1); + if (args.size() == 1) + return args[0].m_Type; + return nullptr; +} + +void Cppyy::GetFnTypeSig(TCppType_t fn_type, std::vector& arg_types) { + fn_type = Cpp::IsReferenceType(fn_type) ? Cpp::GetNonReferenceType(fn_type) : fn_type; + fn_type = Cpp::IsPointerType(fn_type) ? Cpp::GetPointeeType(fn_type) : fn_type; + Cpp::GetFnTypeSignature(fn_type, arg_types); +} + +bool Cppyy::IsSameType(TCppType_t typ1, TCppType_t typ2) { + return Cpp::IsSameType(typ1, typ2); +} + +bool Cppyy::IsFunctionType(TCppType_t typ) { + typ = Cpp::IsReferenceType(typ) ? Cpp::GetNonReferenceType(typ) : typ; + typ = Cpp::IsPointerType(typ) ? Cpp::GetPointeeType(typ) : typ; + return Cpp::IsFunctionProtoType(typ); +} + +bool Cppyy::IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2) { + typ1 = Cpp::IsReferenceType(typ1) ? Cpp::GetNonReferenceType(typ1) : typ1; + typ2 = Cpp::IsReferenceType(typ2) ? Cpp::GetNonReferenceType(typ2) : typ2; + typ1 = Cpp::IsPointerType(typ1) ? Cpp::GetPointeeType(typ1) : typ1; + typ2 = Cpp::IsPointerType(typ2) ? Cpp::GetPointeeType(typ2) : typ2; + return Cpp::IsSameType(typ1, typ2); } std::string Cppyy::GetDoxygenComment(TCppScope_t scope, bool strip_markers) @@ -1831,7 +1861,7 @@ Cppyy::TCppMethod_t Cppyy::GetGlobalOperator( std::vector overloads; Cpp::GetOperator(scope, Cpp::GetOperatorFromSpelling(opname), overloads, /*kind=*/Cpp::OperatorArity::kBoth); - + // Avoid pushing nullptr into arg_types which would crash // BestOverloadFunctionMatch when it dereferences each entry's QualType. auto resolve_arg_type = [](const std::string& name) -> Cppyy::TCppType_t { @@ -1840,7 +1870,7 @@ Cppyy::TCppMethod_t Cppyy::GetGlobalOperator( return Cppyy::GetReferencedType(t); return Cppyy::GetType(name, /*enable_slow_lookup=*/true); }; - + std::vector arg_types; if (auto l = resolve_arg_type(lc_type)) arg_types.emplace_back(l); 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 33c812c4f789d..b3737f4d94031 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -299,9 +299,16 @@ namespace Cppyy { std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); RPY_EXPORTED std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, TCppIndex_t max_args = (TCppIndex_t)-1); - // GetMethodPrototype is unused. RPY_EXPORTED - std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); + bool IsFunctionType(TCppType_t typ); + RPY_EXPORTED + TCppType_t GetFnTypeFromStdFn(TCppType_t fn_type); + RPY_EXPORTED + void GetFnTypeSig(TCppType_t fn_type, std::vector& arg_types); + RPY_EXPORTED + bool IsSameType(TCppType_t typ1, TCppType_t typ2); + RPY_EXPORTED + bool IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2); RPY_EXPORTED std::string GetDoxygenComment(TCppScope_t scope, bool strip_markers = true); RPY_EXPORTED From abef878158170e524a16db1b57c030105373ea7f Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 19 May 2026 14:26:17 +0200 Subject: [PATCH 08/45] [cppyy-backend] Pass CPPINTEROP_DIR directly to AddIncludePath [ROOT-patch] CPPINTEROP_DIR is the include-root that contains the CppInterOp/ subdir with Dispatch.h and the tablegen-generated CppInterOpAPI.inc. Pass it straight to Cpp::AddIncludePath instead of concatenating "/include", matching where the build system actually places those headers. --- .../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 9a2ea658bf825..d47d881f0549f 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -248,7 +248,7 @@ class ApplicationStarter { 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::AddIncludePath(CPPINTEROP_DIR); Cpp::LoadLibrary("libstdc++", /* lookup= */ true); // load frequently used headers From 95ce8513e56342dd3a6da29c3efaefbc04042860 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 14:35:13 +0200 Subject: [PATCH 09/45] [cppyy-backend] Apply ROOT patches on compres fork baseline [ROOT-patch] Based on ROOT: - Load libCling via gSystem->DynamicPathName instead of the fork's CPPINTEROP_DIR/lib path - include ROOT core headers and call TClass::GetClass in GetScope for dictionary/module autoloading. - Guard GetActualClass and GetBaseOffset with Cpp::IsComplete to handle incomplete types (e.g. TCling with no public header) - Initialise ROOT globals and required dylibs in ApplicationStarter - Move precommondefs.h include into cpp_cppyy.h; use Cpp::TCppFuncAddr_t for proper alignment - Update CMakeLists for ROOT build-tree integration --- .../pyroot/cppyy/cppyy-backend/CMakeLists.txt | 20 ++++- .../clingwrapper/src/clingwrapper.cxx | 83 +++++++++++++++++-- .../clingwrapper/src/cpp_cppyy.h | 3 +- 3 files changed, 96 insertions(+), 10 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt index 8b142b4d30a60..6469a9bcdd3f0 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt +++ b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt @@ -4,7 +4,25 @@ # 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_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/clingwrapper.cxx b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx index d47d881f0549f..da5a7a33d21b9 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/clingwrapper.cxx @@ -5,13 +5,38 @@ #endif #endif -#include "precommondefs.h" // This defines several system feature macros and should be included before any system header. - - // Bindings #include "cpp_cppyy.h" #include "callcontext.h" +// ROOT +#include "TBaseClass.h" +#include "TClass.h" +#include "TClassRef.h" +#include "TClassTable.h" +#include "TClassEdit.h" +#include "TCollection.h" +#include "TDataMember.h" +#include "TDataType.h" +#include "TEnum.h" +#include "TEnumConstant.h" +#include "TEnv.h" +#include "TError.h" +#include "TException.h" +#include "TFunction.h" +#include "TFunctionTemplate.h" +#include "TGlobal.h" +#include "THashList.h" +#include "TInterpreter.h" +#include "TList.h" +#include "TListOfDataMembers.h" +#include "TListOfEnums.h" +#include "TMethod.h" +#include "TMethodArg.h" +#include "TROOT.h" +#include "TSystem.h" +#include "TThread.h" + #ifndef _WIN32 #include #endif @@ -177,9 +202,15 @@ class ApplicationStarter { public: ApplicationStarter() { std::lock_guard Lock(InterOpMutex); - if (!Cpp::LoadDispatchAPI( - CPPINTEROP_DIR - "/lib/libclangCppInterOp" CMAKE_SHARED_LIBRARY_SUFFIX)) { + + (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; } @@ -305,6 +336,25 @@ class ApplicationStarter { 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); // if (!interopDL) { @@ -799,6 +849,12 @@ 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 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()) @@ -909,8 +965,15 @@ 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 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 offsets. Fall back to the base + // type if the derived type is incomplete. + if (Cpp::IsComplete(scope)) + return scope; + } return klass; } @@ -1476,6 +1539,10 @@ 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; + intptr_t offset = Cpp::GetBaseClassOffset(derived, base); if (offset == -1) // Cling error, treat silently 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 b3737f4d94031..1063086ec687e 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h +++ b/bindings/pyroot/cppyy/cppyy-backend/clingwrapper/src/cpp_cppyy.h @@ -1,6 +1,7 @@ #ifndef CPYCPPYY_CPPYY_H #define CPYCPPYY_CPPYY_H +#include "precommondefs.h" #include // Standard @@ -52,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 960680aacf5014ed30b5d637b665576942f709fa Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Tue, 19 May 2026 14:26:17 +0200 Subject: [PATCH 10/45] [cppyy-backend] Stage CppInterOp headers via etc/cppinterop [ROOT-patch] Dispatch.h transitively includes the tablegen-generated CppInterOpAPI.inc, which lives in the build tree, not the source tree. ROOT stages CppInterOp's public headers + .inc files into ${BINARY_DIR}/etc/cppinterop/CppInterOp/ via the CppInterOpEtc custom target. Point cppyy_backend's include dir and the runtime CPPINTEROP_DIR macro at that staging dir, and depend on CppInterOpEtc so staging completes before this library compiles. --- bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt index 6469a9bcdd3f0..5a295ce8e9ac7 100644 --- a/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt +++ b/bindings/pyroot/cppyy/cppyy-backend/CMakeLists.txt @@ -9,12 +9,11 @@ add_library(cppyy_backend STATIC clingwrapper/src/cppinterop_dispatch.cxx) target_include_directories(cppyy_backend PRIVATE - ${CMAKE_SOURCE_DIR}/interpreter/CppInterOp/include + ${CMAKE_BINARY_DIR}/etc/cppinterop ) 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" + CPPINTEROP_DIR="${CMAKE_BINARY_DIR}/etc/cppinterop" CMAKE_SHARED_LIBRARY_SUFFIX="${CMAKE_SHARED_LIBRARY_SUFFIX}" # The CppInterOp JitCall debug assertions (AreArgumentsValid, # ReportInvokeStart) use Clang AST internals and cannot be resolved @@ -22,8 +21,10 @@ target_compile_definitions(cppyy_backend PRIVATE NDEBUG ) - target_link_libraries(cppyy_backend Core) +# Ensure CppInterOpEtc responsible for staging the tablegen-generated .inc files is built before cppyy_backend. +add_dependencies(cppyy_backend CppInterOpEtc) + if(NOT MSVC) target_compile_options(cppyy_backend PRIVATE -fPIC) endif() From 94e6dc2e76ad837afd39dc8ed5f7456443004cd3 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 18:18:09 +0200 Subject: [PATCH 11/45] [cppyy] Replace frontend with compres forks [fork-baseline] Source: compres forks cppyy/master 273ed88 - Calls gCling.EnableAutoLoading() - Adds cppyy.evaluate / cppyy.macro() / cppyy.ll.as_memoryview - Numba pointer/reference support - Metaclass naming fix, isinstance() idiom cleanup - Bug fixes in basic_string / span / npos pythonizations - Fallback decoding paths for non-UTF-8 compiler output To be followed with ROOT-specific patches. --- .../cppyy/cppyy/python/cppyy/__init__.py | 218 +++++++++++------- .../cppyy/python/cppyy/_cpython_cppyy.py | 53 +++-- .../cppyy/cppyy/python/cppyy/_pypy_cppyy.py | 3 +- .../cppyy/python/cppyy/_pythonization.py | 35 ++- .../cppyy/cppyy/python/cppyy/_stdcpp_fix.py | 2 +- .../cppyy/cppyy/python/cppyy/_typemap.py | 12 +- .../cppyy/cppyy/python/cppyy/_version.py | 2 +- .../cppyy/cppyy/python/cppyy/interactive.py | 7 +- .../pyroot/cppyy/cppyy/python/cppyy/ll.py | 48 ++-- .../cppyy/cppyy/python/cppyy/numba_ext.py | 15 +- .../pyroot/cppyy/cppyy/python/cppyy/types.py | 2 +- 11 files changed, 231 insertions(+), 166 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py index c8dd458bfdd70..b5f2cce659c69 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py @@ -45,16 +45,24 @@ 'typeid', # typeid of a C++ type 'multi', # helper for multiple inheritance 'add_include_path', # add a path to search for headers - 'add_library_path', # add a path to search for headers + 'add_library_path', # add a path to search for libraries 'add_autoload_map', # explicitly include an autoload map 'set_debug', # enable/disable debug output ] -import ctypes -import os -import sys -import sysconfig -import warnings +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__ @@ -79,6 +87,15 @@ 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 ---------------------------------------------------------- _typemap.initialize(_backend) # also creates (u)int8_t mapper @@ -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 @@ -170,38 +190,27 @@ def __getitem__(self, cls): #--- 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): if self._capture: self.err = _end_capture_stderr() -def _cling_report(msg, errcode, msg_is_error=False): - # errcode should be authorative, but at least on MacOS, Cling does not report an - # error when it should, so also check for the typical compilation signature that - # Cling puts out as an indicator than an error occurred - if 'input_line' in msg: - if 'warning' in msg and not 'error' in msg: - warnings.warn(msg, SyntaxWarning) - msg_is_error=False - - if 'error' in msg: - errcode = 1 - - if errcode or (msg and msg_is_error): - raise SyntaxError('Failed to parse the given C++ code%s' % msg) - -def cppdef(src): +def cppdef(src, verbose = True): """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, not verbose) + 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 +218,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 +255,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,13 +283,13 @@ 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.""" if not os.path.isdir(path): raise OSError('No such directory: %s' % path) - gbl.gSystem.AddDynamicPath(path) + gbl.Cpp.AddSearchPath(path, True, False) # add access to Python C-API headers apipath = sysconfig.get_path('include', 'posix_prefix' if os.name == 'posix' else os.name) @@ -297,23 +301,78 @@ def add_library_path(path): if os.path.exists(apipath) and os.path.exists(os.path.join(apipath, 'Python.h')): add_include_path(apipath) +# add access to extra headers for dispatcher (CPyCppyy only (?)) +if not ispypy: + try: + apipath_extra = os.environ['CPPYY_API_PATH'] + if os.path.basename(apipath_extra) == 'CPyCppyy': + apipath_extra = os.path.dirname(apipath_extra) + except KeyError: + apipath_extra = None + + if apipath_extra is None: + try: + import pkg_resources as pr + + d = pr.get_distribution('CPyCppyy') + for line in d.get_metadata_lines('RECORD'): + if 'API.h' in line: + part = line[0:line.find(',')] + + ape = os.path.join(d.location, part) + if os.path.exists(ape): + apipath_extra = os.path.dirname(os.path.dirname(ape)) + + del part, d, pr + except Exception: + pass + + if apipath_extra is None: + ldversion = sysconfig.get_config_var('LDVERSION') + if not ldversion: ldversion = sys.version[:3] + + apipath_extra = os.path.join(os.path.dirname(apipath), 'site', 'python'+ldversion) + if not os.path.exists(os.path.join(apipath_extra, 'CPyCppyy')): + import glob, libcppyy + ape = os.path.dirname(libcppyy.__file__) + # a "normal" structure finds the include directory up to 3 levels up, + # ie. dropping lib/pythonx.y[md]/site-packages + for i in range(3): + if os.path.exists(os.path.join(ape, 'include')): + break + ape = os.path.dirname(ape) + + ape = os.path.join(ape, 'include') + if os.path.exists(os.path.join(ape, 'CPyCppyy')): + apipath_extra = ape + else: + # add back pythonx.y or site/pythonx.y if present + for p in glob.glob(os.path.join(ape, 'python'+sys.version[:3]+'*'))+\ + glob.glob(os.path.join(ape, '*', 'python'+sys.version[:3]+'*')): + if os.path.exists(os.path.join(p, 'CPyCppyy')): + apipath_extra = p + break + + if apipath_extra.lower() != 'none': + if not os.path.exists(os.path.join(apipath_extra, 'CPyCppyy')): + warnings.warn("CPyCppyy API not found (tried: %s); set CPPYY_API_PATH envar to the 'CPyCppyy' API directory to fix" % apipath_extra) + else: + add_include_path(apipath_extra) + + del apipath_extra + 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,14 +380,11 @@ 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): @@ -350,7 +406,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),), _backend.nullptr) _sizes[tt] = sz return sz @@ -363,7 +419,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 +429,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): 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..4a7226b91ee29 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py @@ -2,10 +2,10 @@ """ import ctypes -import platform import sys from . import _stdcpp_fix +from cppyy_backend import loader __all__ = [ 'gbl', @@ -19,11 +19,11 @@ '_end_capture_stderr' ] -if platform.system() == "Windows": - # On Windows, the library has to be searched without prefix - import libcppyy as _backend -else: - import cppyy.libcppyy as _backend +# first load the dependency libraries of the backend, then pull in the +# libcppyy extension module +c = loader.load_cpp_backend() +import libcppyy as _backend +_backend._cpp_backend = c # explicitly expose APIs from libcppyy _w = ctypes.CDLL(_backend.__file__, ctypes.RTLD_GLOBAL) @@ -61,10 +61,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 +82,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 +97,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 @@ -154,32 +153,31 @@ def __call__(self, *args): gbl.std = _backend.CreateScopeProxy('std') # for move, we want our "pythonized" one, not the C++ template gbl.std.move = _backend.move - +# CppInterOp proxy object to access its API +Cpp = gbl.Cpp #- add to the dynamic path as needed ----------------------------------------- import os def add_default_paths(): - gSystem = gbl.gSystem 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): Cpp.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): Cpp.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): Cpp.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) + Cpp.AddSearchPath(f, True, False) except IOError: pass add_default_paths() @@ -193,9 +191,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 = Cpp.LoadLibrary(name, True) + if name.endswith("Dict.so"): + header = name[:-7] + ".h" + 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() diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py index 1e57791a67680..f096eeb13eda8 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_pypy_cppyy.py @@ -3,8 +3,7 @@ from . import _stdcpp_fix -import os -import sys +import os, sys from cppyy_backend import loader __all__ = [ diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py index f8b2a4d0f870f..a45c136025923 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_pythonization.py @@ -133,14 +133,14 @@ def __call__(self, obj, name): if not self.match_class.match(name): return for k in dir(obj): #.__dict__: - try: - tmp = getattr(obj, k) - except AttributeError: - continue - if self.match_method.match(k): - try: - tmp.__add_overload__(overload) - except AttributeError: pass + try: + tmp = getattr(obj, k) + except: + continue + if self.match_method.match(k): + try: + tmp.__add_overload__(overload) + except AttributeError: pass return method_pythonizor(match_class, match_method, overload) @@ -160,7 +160,7 @@ def __call__(self, obj, name): continue try: f = getattr(obj, k) - except AttributeError: + except: continue def make_fun(f, g): def h(self, *args, **kwargs): @@ -185,7 +185,7 @@ def __call__(self, obj, name): for k in dir(obj): #.__dict__: try: tmp = getattr(obj, k) - except AttributeError: + except: continue if self.match_method.match(k): setattr(tmp, self.prop, self.value) @@ -218,14 +218,10 @@ def __init__(self, match_class, match_get, match_set, match_del, prop_name): self.match_many = match_many_getters if not (self.match_many or prop_name): - raise ValueError( - "If not matching properties by regex, " - "need a property name with exactly one substitution field") + raise ValueError("If not matching properties by regex, need a property name with exactly one substitution field") if self.match_many and prop_name: if prop_name.format(').!:(') == prop_name: - raise ValueError( - "If matching properties by regex and providing a property name, " - "the name needs exactly one substitution field") + raise ValueError("If matching properties by regex and providing a property name, the name needs exactly one substitution field") self.prop_name = prop_name @@ -263,7 +259,7 @@ def __call__(self, obj, name): match = self.match_get.match(k) try: tmp = getattr(obj, k) - except AttributeError: + except: continue if match and hasattr(tmp, '__call__'): if self.match_many: @@ -278,7 +274,7 @@ def __call__(self, obj, name): match = self.match_set.match(k) try: tmp = getattr(obj, k) - except AttributeError: + except: continue if match and hasattr(tmp, '__call__'): if self.match_many: @@ -293,7 +289,7 @@ def __call__(self, obj, name): match = self.match_del.match(k) try: tmp = getattr(obj, k) - except AttributeError: + except: continue if match and hasattr(tmp, '__call__'): if self.match_many: @@ -313,6 +309,7 @@ def __call__(self, obj, name): names += list(named_deleters.keys()) names = set(names) + properties = [] for name in names: if name in named_getters: fget = self.make_get_del_proxy(named_getters[name]) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py index 0004c87803b72..90c3687b41696 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_stdcpp_fix.py @@ -1,6 +1,6 @@ import sys -# It may be that the interpreter (whether python or pypy-c) was not linked +# It may be that the interpreter (wether python or pypy-c) was not linked # with C++; force its loading before doing anything else (note that not # linking with C++ spells trouble anyway for any C++ libraries ...) if 'linux' in sys.platform and 'GCC' in sys.version: diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py index fd09510c4af8f..272b4c2863180 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_typemap.py @@ -15,8 +15,7 @@ def mapper(name, scope): cppname = name modname = 'cppyy.gbl' dct = {'__cpp_name__' : cppname, '__module__' : modname} - if extra_dct: - dct.update(extra_dct) + if extra_dct: dct.update(extra_dct) return type(name, (cls,), dct) return mapper @@ -66,10 +65,9 @@ def __prepare__(cls, name, this_bases): # --- end from six.py class _BoolMeta(type): - def __call__(cls, val = bool()): - if val: - return True - return False + def __call__(self, val = bool()): + if val: return True + else: return False class _Bool(with_metaclass(_BoolMeta, object)): pass @@ -115,4 +113,4 @@ def voidp_init(self, arg=0): import cppyy, ctypes if arg == cppyy.nullptr: arg = 0 ctypes.c_void_p.__init__(self, arg) - tm['void*'] = _create_mapper(ctypes.c_void_p, {'__init__' : voidp_init}) + tm['void *'] = _create_mapper(ctypes.c_void_p, {'__init__' : voidp_init}) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py index 01bd03cec6483..4eb28e38265ac 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_version.py @@ -1 +1 @@ -__version__ = '3.5.0' +__version__ = '3.0.0' diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py index 0b4bdd7ce471f..88b7ca95e8a12 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/interactive.py @@ -26,9 +26,10 @@ def __getattr__(self, attr): caller = sys.modules[sys._getframe(1).f_globals['__name__']] cppyy._backend._set_cpp_lazy_lookup(caller.__dict__) return cppyy.__all__ - self.__dict__['g'] = cppyy.gbl - self.__dict__['std'] = cppyy.gbl.std - return ['g', 'std']+cppyy.__all__ + else: + self.__dict__['g'] = cppyy.gbl + self.__dict__['std'] = cppyy.gbl.std + return ['g', 'std']+cppyy.__all__ return getattr(cppyy, attr) sys.modules['cppyy.interactive'] = InteractiveLazy(\ diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py index 9fb6034ce0843..7bd535fc76603 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/ll.py @@ -36,12 +36,11 @@ # convenience functions to create C-style argv/argc def argv(): - """Return C's argv for use with cppyy/ctypes.""" + argc = len(sys.argv) cargsv = (ctypes.c_char_p * len(sys.argv))(*(x.encode() for x in sys.argv)) return ctypes.POINTER(ctypes.c_char_p)(cargsv) def argc(): - """Return C's argc for use with cppyy/ctypes.""" return len(sys.argv) # import low-level python converters @@ -53,33 +52,33 @@ def argc(): pass del _name +# create low-level helpers once +if not hasattr(cppyy.gbl, "__cppyy_internal") or \ + not hasattr(cppyy.gbl.__cppyy_internal, "cppyy_cast"): + cppyy.cppdef("""namespace __cppyy_internal { + // type casting + template + T cppyy_cast(U val) { return (T)val; } -# create low-level helpers -cppyy.cppdef("""namespace __cppyy_internal { -// type casting - template - T cppyy_cast(U val) { return (T)val; } + template + T cppyy_static_cast(U val) { return static_cast(val); } - template - T cppyy_static_cast(U val) { return static_cast(val); } + template + T cppyy_reinterpret_cast(U val) { return reinterpret_cast(val); } - template - T cppyy_reinterpret_cast(U val) { return reinterpret_cast(val); } + template + T* cppyy_dynamic_cast(S* obj) { return dynamic_cast(obj); } - template - T* cppyy_dynamic_cast(S* obj) { return dynamic_cast(obj); } + // memory allocation/free-ing + template + T* cppyy_malloc(size_t count=1) { return (T*)malloc(sizeof(T*)*count); } -// memory allocation/free-ing - template - T* cppyy_malloc(size_t count=1) { return (T*)malloc(sizeof(T*)*count); } - - template - T* cppyy_array_new(size_t count) { return new T[count]; } - - template - void cppyy_array_delete(T* ptr) { delete[] ptr; } -}""") + template + T* cppyy_array_new(size_t count) { return new T[count]; } + template + void cppyy_array_delete(T* ptr) { delete[] ptr; } + }""") # helper for sizing arrays class ArraySizer(object): @@ -92,8 +91,7 @@ def __call__(self, size, managed=False): res = self.func[self.array_type](size) try: res.reshape((size,)+res.shape[1:]) - if managed: - res.__python_owns__ = True + if managed: res.__python_owns__ = True except AttributeError: res.__reshape__((size,)) if managed: diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py index a83985b909094..f5e88e0d310ea 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/numba_ext.py @@ -1,6 +1,7 @@ """ cppyy extensions for numba """ +import sys import cppyy import cppyy.types as cpp_types import cppyy.reflex as cpp_refl @@ -32,7 +33,9 @@ class Qualified: ir_byte = ir.IntType(8) ir_voidptr = ir.PointerType(ir_byte) # by convention ir_byteptr = ir_voidptr # for clarity -ir_intptr_t = ir.IntType(cppyy.sizeof('void*')*8) +# FIXME: Revert this to only use cppyy.sizeof once Cpp::Evaluate on macOS is fixed +sz = 64 if 'darwin' in sys.platform else cppyy.sizeof('void*') * 8 +ir_intptr_t = ir.IntType(sz) # special case access to unboxing/boxing APIs cppyy_as_voidptr = cppyy.addressof('Instance_AsVoidPtr') @@ -81,7 +84,7 @@ def cpp2numba(val): elif val[-1] == '*' or val[-1] == '&': if val.startswith('const'): return nb_types.CPointer(cpp2numba(resolve_const_types(val))) - return nb_types.CPointer(_cpp2numba[val[:-1]]) + return nb_types.CPointer(_cpp2numba[val[:-2]]) return _cpp2numba[val] _numba2cpp = dict() @@ -127,13 +130,13 @@ def numba_arg_convertor(args): def to_ref(type_list): ref_list = [] for l in type_list: - ref_list.append(l + '&') + ref_list.append(l + ' &') return ref_list # TODO: looks like Numba treats unsigned types as signed when lowering, # which seems to work as they're just reinterpret_casts _cpp2ir = { - 'char*' : ir_byteptr, + 'char *' : ir_byteptr, 'int8_t' : ir.IntType(8), 'uint8_t' : ir.IntType(8), 'short' : ir.IntType(nb_types.short.bitwidth), @@ -160,10 +163,10 @@ def cpp2ir(val): ## TODO should be possible to obtain the vector length from the CPPDataMember val type_arr = ir.VectorType(cpp2ir(resolve_std_vector(val)), 3) return type_arr - elif val != "char*" and val[-1] == "*": + elif val != "char *" and val[-1] == "*": if val.startswith('const'): return ir.PointerType(cpp2ir(resolve_const_types(val))) - type_2 = _cpp2ir[val[:-1]] + type_2 = _cpp2ir[val[:-2]] return ir.PointerType(type_2) # diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/types.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/types.py index 4a979ac59c285..d51dc6c05ea66 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/types.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/types.py @@ -17,7 +17,7 @@ 'DataMember', 'Instance', 'Function', - 'Method', + 'Method' 'Scope', 'InstanceArray', 'LowLevelView', From c63b7f3badae52fbdd8975c27fd216f88268efa9 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 30 Apr 2026 13:54:14 +0200 Subject: [PATCH 12/45] [cppyy] Use qualified complete name for basic_string NPOS check [upstream] Same as in Pythonize.cxx basic_string name from GetQualifiedCompleteName so the NPOS object is registered for the canonical class name shapes. --- bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py index b5f2cce659c69..c73ca513c1687 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py @@ -132,7 +132,10 @@ def tuple_getitem(self, idx, get=cppyy.gbl.std.get): # 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::basic_string": + elif pyclass.__cpp_name__ in ( + "std::basic_string", + "std::basic_string, std::allocator >", + ): class NPOS(int): def __init__(self, npos): self.__cpp_npos = npos From 767995baf8a0af1a53fe9a6a3d29bb351198c379 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 18:21:11 +0200 Subject: [PATCH 13/45] [cppyy] Apply ROOT-specific patches [ROOT-patch] ROOT-specific patches on top of the compres fork cppyy/master baseline: __init__.py: - set gInterpereter to gbl.TInterpreter.Instance() - load_library(): use gSystem.Load/FindDynamicLibrary, add Windows winmode=0 for search path - add_library_path(): use gSystem.AddDynamicPath - Drop the CPPYY_API_PATH apipath_extra dispatcher-headers (ROOT installs CPyCppyy API headers) _cpython_cppyy.py: - Wrap `from cppyy_backend import loader` in try/except, fall back to c = None (ROOT does not ship cppyy_backend.so) - Platform: `import libcppyy` vs `import cppyy.libcppyy` (standalone cppyy uses the former; ROOT exposes it as cppyy.libcppyy) - load_reflection_info(): use gSystem.Load ROOT/_facade.py: - Store gInterpreter and gPad in __dict__ --- .../cppyy/cppyy/python/cppyy/__init__.py | 78 ++++--------------- .../cppyy/python/cppyy/_cpython_cppyy.py | 37 +++++---- .../pythonizations/python/ROOT/_facade.py | 3 +- 3 files changed, 37 insertions(+), 81 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py index c73ca513c1687..630c93a015c83 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/__init__.py @@ -187,6 +187,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 @@ -258,9 +259,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 @@ -292,7 +302,7 @@ def add_library_path(path): """Add a path to the library search paths available to Cling.""" if not os.path.isdir(path): raise OSError('No such directory: %s' % path) - gbl.Cpp.AddSearchPath(path, True, False) + gbl.gSystem.AddDynamicPath(path) # add access to Python C-API headers apipath = sysconfig.get_path('include', 'posix_prefix' if os.name == 'posix' else os.name) @@ -304,66 +314,6 @@ def add_library_path(path): if os.path.exists(apipath) and os.path.exists(os.path.join(apipath, 'Python.h')): add_include_path(apipath) -# add access to extra headers for dispatcher (CPyCppyy only (?)) -if not ispypy: - try: - apipath_extra = os.environ['CPPYY_API_PATH'] - if os.path.basename(apipath_extra) == 'CPyCppyy': - apipath_extra = os.path.dirname(apipath_extra) - except KeyError: - apipath_extra = None - - if apipath_extra is None: - try: - import pkg_resources as pr - - d = pr.get_distribution('CPyCppyy') - for line in d.get_metadata_lines('RECORD'): - if 'API.h' in line: - part = line[0:line.find(',')] - - ape = os.path.join(d.location, part) - if os.path.exists(ape): - apipath_extra = os.path.dirname(os.path.dirname(ape)) - - del part, d, pr - except Exception: - pass - - if apipath_extra is None: - ldversion = sysconfig.get_config_var('LDVERSION') - if not ldversion: ldversion = sys.version[:3] - - apipath_extra = os.path.join(os.path.dirname(apipath), 'site', 'python'+ldversion) - if not os.path.exists(os.path.join(apipath_extra, 'CPyCppyy')): - import glob, libcppyy - ape = os.path.dirname(libcppyy.__file__) - # a "normal" structure finds the include directory up to 3 levels up, - # ie. dropping lib/pythonx.y[md]/site-packages - for i in range(3): - if os.path.exists(os.path.join(ape, 'include')): - break - ape = os.path.dirname(ape) - - ape = os.path.join(ape, 'include') - if os.path.exists(os.path.join(ape, 'CPyCppyy')): - apipath_extra = ape - else: - # add back pythonx.y or site/pythonx.y if present - for p in glob.glob(os.path.join(ape, 'python'+sys.version[:3]+'*'))+\ - glob.glob(os.path.join(ape, '*', 'python'+sys.version[:3]+'*')): - if os.path.exists(os.path.join(p, 'CPyCppyy')): - apipath_extra = p - break - - if apipath_extra.lower() != 'none': - if not os.path.exists(os.path.join(apipath_extra, 'CPyCppyy')): - warnings.warn("CPyCppyy API not found (tried: %s); set CPPYY_API_PATH envar to the 'CPyCppyy' API directory to fix" % apipath_extra) - else: - add_include_path(apipath_extra) - - del apipath_extra - if os.getenv('CONDA_PREFIX'): # MacOS, Linux include_path = os.path.join(os.getenv('CONDA_PREFIX'), 'include') diff --git a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py index 4a7226b91ee29..dde76065e95a1 100644 --- a/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py +++ b/bindings/pyroot/cppyy/cppyy/python/cppyy/_cpython_cppyy.py @@ -2,10 +2,10 @@ """ import ctypes +import platform import sys from . import _stdcpp_fix -from cppyy_backend import loader __all__ = [ 'gbl', @@ -19,11 +19,23 @@ '_end_capture_stderr' ] -# first load the dependency libraries of the backend, then pull in the -# libcppyy extension module -c = loader.load_cpp_backend() -import libcppyy as _backend -_backend._cpp_backend = c +# 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 _w = ctypes.CDLL(_backend.__file__, ctypes.RTLD_GLOBAL) @@ -191,16 +203,9 @@ 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 = Cpp.LoadLibrary(name, True) - if name.endswith("Dict.so"): - header = name[:-7] + ".h" - Cpp.Declare('#include "' + header +'"', False) - - if result == False: - raise RuntimeError('Could not load library "%s"' % (name)) + sc = gbl.gSystem.Load(name) + if sc == -1: + raise RuntimeError("Unable to load reflection library "+name) return True diff --git a/bindings/pyroot/pythonizations/python/ROOT/_facade.py b/bindings/pyroot/pythonizations/python/ROOT/_facade.py index 179944bd4bf2b..8006329d904dc 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_facade.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_facade.py @@ -304,7 +304,8 @@ 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() + self.__dict__["gPad"] = self._cppyy.gbl.TVirtualPad.Pad() # Release the GIL on the heavy TInterpreter functions. This lets # background Python threads make progress - in particular, JupyROOT's From 1e279e69ee1f762495b332c7e06e1bd2e4c158f4 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 15:36:27 +0200 Subject: [PATCH 14/45] [CPyCppyy] Replace with compres forks [fork-baseline] Source: compres forks CPyCppyy/master 482ccb7 Brings in fork-ahead developments missing from ROOT's old copy and compatibility with the CppInterOp based backend: - CppInterOp-aligned type system (Cppyy.h) - Type-based CreateConverter / CreateExecutor factory overloads - Instance_FromVoidPtr(scope) overload - PythonGILRAII for exception-safe GIL management - PyError_t RAII rewrite (unique_ptr-based) - GetTemplateArgsTypes for type-based template instantiation - ReduceReturnType/LambdaClass handling - Rvalue forwarding in Dispatcher - GetFailureMsg in Converters - STL string executor returns bound C++ object ROOT-specific CPyCppyyModule.h and CPyCppyyPyModule.cxx are preserved from master. --- .../cppyy/CPyCppyy/include/CPyCppyy/API.h | 11 +- .../CPyCppyy/include/CPyCppyy/CommonDefs.h | 8 +- .../CPyCppyy/include/CPyCppyy/DispatchPtr.h | 9 +- bindings/pyroot/cppyy/CPyCppyy/src/API.cxx | 40 +- .../cppyy/CPyCppyy/src/CPPClassMethod.cxx | 2 +- .../cppyy/CPyCppyy/src/CPPConstructor.cxx | 19 +- .../cppyy/CPyCppyy/src/CPPDataMember.cxx | 97 +-- .../pyroot/cppyy/CPyCppyy/src/CPPDataMember.h | 7 +- .../pyroot/cppyy/CPyCppyy/src/CPPEnum.cxx | 52 +- bindings/pyroot/cppyy/CPyCppyy/src/CPPEnum.h | 2 + .../pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx | 23 +- .../pyroot/cppyy/CPyCppyy/src/CPPInstance.h | 15 +- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 69 +- .../pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx | 60 +- .../pyroot/cppyy/CPyCppyy/src/CPPScope.cxx | 76 +- bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h | 32 +- .../cppyy/CPyCppyy/src/CPyCppyyModule.cxx | 123 ++- .../pyroot/cppyy/CPyCppyy/src/CallContext.cxx | 40 +- .../pyroot/cppyy/CPyCppyy/src/CallContext.h | 26 +- .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 725 +++++++++++------- .../pyroot/cppyy/CPyCppyy/src/Converters.h | 11 +- bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 197 +++-- .../cppyy/CPyCppyy/src/DeclareConverters.h | 85 +- .../cppyy/CPyCppyy/src/DeclareExecutors.h | 16 +- .../pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx | 34 +- .../pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx | 100 ++- .../pyroot/cppyy/CPyCppyy/src/Executors.cxx | 192 ++++- .../pyroot/cppyy/CPyCppyy/src/Executors.h | 1 + .../cppyy/CPyCppyy/src/LowLevelViews.cxx | 40 - .../pyroot/cppyy/CPyCppyy/src/LowLevelViews.h | 9 - .../cppyy/CPyCppyy/src/ProxyWrappers.cxx | 391 +++++----- .../pyroot/cppyy/CPyCppyy/src/ProxyWrappers.h | 8 +- .../pyroot/cppyy/CPyCppyy/src/PyException.cxx | 10 +- .../pyroot/cppyy/CPyCppyy/src/PyStrings.cxx | 3 + .../pyroot/cppyy/CPyCppyy/src/PyStrings.h | 1 + .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 320 ++------ .../pyroot/cppyy/CPyCppyy/src/Pythonize.h | 2 +- .../cppyy/CPyCppyy/src/SignalTryCatch.h | 11 +- .../cppyy/CPyCppyy/src/TemplateProxy.cxx | 66 +- .../cppyy/CPyCppyy/src/TupleOfInstances.cxx | 9 +- .../cppyy/CPyCppyy/src/TupleOfInstances.h | 2 +- .../pyroot/cppyy/CPyCppyy/src/TypeManip.cxx | 12 +- .../pyroot/cppyy/CPyCppyy/src/Utility.cxx | 303 +++++++- bindings/pyroot/cppyy/CPyCppyy/src/Utility.h | 3 + 44 files changed, 1932 insertions(+), 1330 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h b/bindings/pyroot/cppyy/CPyCppyy/include/CPyCppyy/API.h index e619348e11c0d..71d9ba3131faf 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; @@ -123,6 +123,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); @@ -153,6 +154,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); @@ -183,7 +185,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/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 48cb1f5377ed9..800df39c8815c 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); @@ -147,6 +153,25 @@ 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; + + PythonGILRAII python_gil_raii; + +// 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) { @@ -161,6 +186,7 @@ bool CPyCppyy::Scope_Check(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; return CPPScope_Check(pyobject); } @@ -171,6 +197,7 @@ bool CPyCppyy::Scope_CheckExact(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; return CPPScope_CheckExact(pyobject); } @@ -181,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); } @@ -192,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); } @@ -225,6 +254,7 @@ void CPyCppyy::Instance_SetCppOwns(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()). @@ -258,6 +288,7 @@ 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 @@ -277,6 +308,7 @@ bool CPyCppyy::Overload_Check(PyObject* pyobject) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; // detailed walk through inheritance hierarchy return CPPOverload_Check(pyobject); } @@ -288,6 +320,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); } @@ -305,6 +338,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(); @@ -364,6 +399,8 @@ void CPyCppyy::ExecScript(const std::string& name, const std::vector wargv(argc); - wargv[0] = Py_DecodeLocale(name.c_str(), nullptr); for (int i = 1; i < argc; ++i) { @@ -438,6 +474,7 @@ bool CPyCppyy::Exec(const std::string& cmd) if (!Initialize()) return false; + PythonGILRAII python_gil_raii; // execute the command PyObject* result = PyRun_String(const_cast(cmd.c_str()), Py_file_input, gMainDict, gMainDict); @@ -459,6 +496,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/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/CPPConstructor.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPConstructor.cxx index f935d3d5371cb..5cd03df34bbcd 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 @@ -81,11 +82,12 @@ PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, 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; +// 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) @@ -135,7 +137,7 @@ PyObject* CPyCppyy::CPPConstructor::Call(CPPInstance*& self, } else { // translate the arguments - if (cppScopeFlags & CPPScope::kNoImplicit) + if (cppScopeFlags & CPPScope::kActiveImplicitCall) ctxt->fFlags |= CallContext::kNoImplicit; if (!this->ConvertAndSetArgs(cargs.fArgs, cargs.fNArgsf, ctxt)) return nullptr; @@ -182,6 +184,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 4246ba487543b..c716ae601e056 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); @@ -314,53 +317,55 @@ 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 ? "*" : "[]"; - } - } + auto ldims = Cppyy::GetDimensions(type); + std::vector dims(ldims.begin(), ldims.end()); + + 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..ca21f6a5b7161 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,21 +201,24 @@ 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); - 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); } @@ -235,4 +243,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 ac702c25769f7..2d384d1c65027 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx @@ -285,7 +285,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. @@ -416,7 +416,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) { @@ -499,6 +499,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); @@ -512,11 +516,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); @@ -548,8 +547,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 */); @@ -714,7 +713,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); @@ -745,7 +744,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); @@ -806,7 +805,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 d6b7adcbe350b..2725116229723 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h @@ -64,7 +64,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(); @@ -91,6 +91,7 @@ class CPPInstance { // serialization support, like ROOT static PyCFunction &ReduceMethod(); + private: void CreateExtension(); void* GetExtendedObject(); @@ -118,9 +119,9 @@ inline void* CPPInstance::GetObject() return GetExtendedObject(); } -//---------------------------------------------------------------------------- #ifndef Py_LIMITED_API -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; @@ -128,14 +129,8 @@ inline Cppyy::TCppType_t CPPInstance::ObjectIsA(bool check_smart) const } #endif - //- 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; #ifndef Py_LIMITED_API template diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index 3ea6e6a8734b0..d0d5d53e2902f 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; @@ -277,12 +278,12 @@ 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 with a traceback: +// 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. @@ -368,13 +369,19 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) 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.find(result) != TypeReductionMap.end()) + fMethod = Cppyy::ReduceReturnType(fMethod, TypeReductionMap[result]); + if (result && Cppyy::IsLambdaClass(result)) + fMethod = Cppyy::AdaptFunctionForLambdaReturn(fMethod); } //---------------------------------------------------------------------------- @@ -420,7 +427,7 @@ CPyCppyy::CPPMethod::~CPPMethod() * namespace c { * int foo(int x); * }}} - * + * * This function returns: * * 'int foo(int x)' @@ -434,12 +441,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()); } @@ -503,7 +508,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) @@ -552,7 +560,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 @@ -560,7 +568,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; @@ -578,6 +586,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; } @@ -592,8 +604,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; @@ -617,7 +629,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 += " "; @@ -934,10 +946,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 @@ -989,7 +1001,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; } @@ -1004,7 +1016,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 { @@ -1132,7 +1145,7 @@ PyObject *CPyCppyy::CPPMethod::GetSignatureTypes() PyObject *parameter_types = PyTuple_New(argcount); for (int iarg = 0; iarg < argcount; ++iarg) { - const std::string &argtype_cpp = Cppyy::GetMethodArgType(fMethod, 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); } @@ -1145,5 +1158,5 @@ PyObject *CPyCppyy::CPPMethod::GetSignatureTypes() //---------------------------------------------------------------------------- std::string CPyCppyy::CPPMethod::GetReturnTypeName() { - return Cppyy::GetMethodResultType(fMethod); + return Cppyy::GetMethodReturnTypeAsString(fMethod); } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx index 6d2a2edfb5aee..2aa50d1a63b43 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx @@ -549,25 +549,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*) { \ @@ -628,8 +643,8 @@ static PyGetSetDef mp_getset[] = { // 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 +686,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; @@ -1110,19 +1127,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/CPPScope.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.cxx index 21f512593370b..a7a1efcd79781 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); @@ -472,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(); } } } @@ -508,7 +506,10 @@ static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) } 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)); @@ -531,16 +532,15 @@ 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 (((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 } - 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..370224ab69de4 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h @@ -37,23 +37,23 @@ 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; - 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 8d12ff5dc6740..e224ec3fd6f0c 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx @@ -42,6 +42,8 @@ dict_lookup_func gDictLookupOrg = nullptr; } // namespace CPyCppyy #endif +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. @@ -444,7 +446,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. @@ -454,14 +456,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); } //---------------------------------------------------------------------------- @@ -552,6 +571,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); @@ -670,7 +695,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)) @@ -681,7 +706,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); } @@ -698,7 +723,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); @@ -709,12 +734,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 @@ -888,28 +913,48 @@ 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; } -#define DEFINE_CALL_POLICY_TOGGLE(name, flagname) \ -static PyObject* name(PyObject*, PyObject* args) \ -{ \ - PyObject* enabled = 0; \ - if (!PyArg_ParseTuple(args, const_cast("O"), &enabled)) \ - return nullptr; \ - \ - if (CallContext::SetGlobalPolicy(CallContext::flagname, PyObject_IsTrue(enabled))) { \ - Py_RETURN_TRUE; \ - } \ - \ - Py_RETURN_FALSE; \ +//---------------------------------------------------------------------------- +static PyObject* SetMemoryPolicy(PyObject*, PyObject* args) +{ +// Set the global memory policy, which affects object ownership when objects +// are passed as function arguments. + PyObject* policy = nullptr; + if (!PyArg_ParseTuple(args, const_cast("O!"), &PyInt_Type, &policy)) + return nullptr; + + long old = (long)CallContext::sMemoryPolicy; + + long l = PyInt_AS_LONG(policy); + if (CallContext::SetMemoryPolicy((CallContext::ECallFlags)l)) { + return PyInt_FromLong(old); + } + + PyErr_Format(PyExc_ValueError, "Unknown policy %ld", l); + return nullptr; } -DEFINE_CALL_POLICY_TOGGLE(SetHeuristicMemoryPolicy, kUseHeuristics); -DEFINE_CALL_POLICY_TOGGLE(SetImplicitSmartPointerConversion, kImplicitSmartPtrConversion); -DEFINE_CALL_POLICY_TOGGLE(SetGlobalSignalPolicy, kProtected); +//---------------------------------------------------------------------------- +static PyObject* SetGlobalSignalPolicy(PyObject*, PyObject* args) +{ +// Set the global signal policy, which determines whether a jmp address +// should be saved to return to after a C++ segfault. + PyObject* setProtected = 0; + if (!PyArg_ParseTuple(args, const_cast("O"), &setProtected)) + return nullptr; + + if (CallContext::SetGlobalSignalPolicy(PyObject_IsTrue(setProtected))) { + Py_RETURN_TRUE; + } + + Py_RETURN_FALSE; +} //---------------------------------------------------------------------------- static PyObject* SetOwnership(PyObject*, PyObject* args) @@ -996,13 +1041,10 @@ static PyMethodDef gCPyCppyyMethods[] = { METH_O, (char*)"Install a type pinning."}, {(char*) "_add_type_reducer", (PyCFunction)AddTypeReducer, METH_VARARGS, (char*)"Add a type reducer."}, - {(char*) "SetHeuristicMemoryPolicy", (PyCFunction)SetHeuristicMemoryPolicy, - METH_VARARGS, (char*)"Set the global memory policy, which affects object ownership when objects are passed as function arguments."}, - {(char*) "SetImplicitSmartPointerConversion", (PyCFunction)SetImplicitSmartPointerConversion, - METH_VARARGS, (char*)"Enable or disable the implicit conversion to smart pointers in function calls (on by default)."}, - {(char *)"SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, METH_VARARGS, - (char *)"Set the global signal policy, which determines whether a jmp address should be saved to return to after a " - "C++ segfault. In practical terms: trap signals in safe mode to prevent interpreter abort."}, + {(char*) "SetMemoryPolicy", (PyCFunction)SetMemoryPolicy, + METH_VARARGS, (char*)"Determines object ownership model."}, + {(char*) "SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, + METH_VARARGS, (char*)"Trap signals in safe mode to prevent interpreter abort."}, {(char*) "SetOwnership", (PyCFunction)SetOwnership, METH_VARARGS, (char*)"Modify held C++ object ownership."}, {(char*) "AddSmartPtrType", (PyCFunction)AddSmartPtrType, @@ -1046,12 +1088,13 @@ static struct PyModuleDef moduledef = { cpycppyymodule_clear, nullptr }; + #endif namespace CPyCppyy { //---------------------------------------------------------------------------- -PyObject* Init() +extern "C" PyObject* PyInit_libcppyy() { // Initialization of extension module libcppyy. @@ -1175,14 +1218,18 @@ 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 static MemoryRegulator s_memory_regulator; -#if PY_VERSION_HEX >= 0x03000000 Py_INCREF(gThisModule); -#endif return gThisModule; } diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx index 416741d0caab1..759765b242750 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx @@ -2,12 +2,15 @@ #include "CPyCppyy.h" #include "CallContext.h" -//----------------------------------------------------------------------------- -uint32_t &CPyCppyy::CallContext::GlobalPolicyFlags() -{ - static uint32_t flags = 0; - 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) { @@ -35,13 +38,24 @@ void CPyCppyy::CallContext::Cleanup() { } //----------------------------------------------------------------------------- -bool CPyCppyy::CallContext::SetGlobalPolicy(ECallFlags toggleFlag, bool enabled) +bool CPyCppyy::CallContext::SetMemoryPolicy(ECallFlags e) { - auto &flags = GlobalPolicyFlags(); - bool old = flags & toggleFlag; - if (enabled) - flags |= toggleFlag; - else - flags &= ~toggleFlag; +// 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; } + diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h index 4b88bfdd3a6fb..98cb15622598a 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h @@ -22,11 +22,7 @@ struct Parameter { union Value { bool fBool; int8_t fInt8; - int16_t fInt16; - int32_t fInt32; uint8_t fUInt8; - uint16_t fUInt16; - uint32_t fUInt32; short fShort; unsigned short fUShort; int fInt; @@ -76,16 +72,20 @@ 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 }; -// Policies about memory handling and signal safety - static bool SetGlobalPolicy(ECallFlags e, bool enabled); - - static uint32_t& GlobalPolicyFlags(); +// memory handling + static ECallFlags sMemoryPolicy; + static bool SetMemoryPolicy(ECallFlags e); 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; @@ -146,9 +146,13 @@ inline bool ReleasesGIL(CallContext* ctxt) { return ctxt ? (ctxt->fFlags & CallContext::kReleaseGIL) : false; } -inline bool UseStrictOwnership() { - using CC = CPyCppyy::CallContext; - 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 diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index d3e472350afe2..094b74b85bf26 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -96,11 +96,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 @@ -134,9 +147,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 +412,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) @@ -511,14 +518,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); @@ -813,10 +820,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) @@ -892,10 +895,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); @@ -1054,14 +1053,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( @@ -1138,7 +1129,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( @@ -1197,7 +1188,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; } @@ -1571,13 +1562,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 @@ -1642,7 +1634,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 @@ -1739,133 +1731,133 @@ bool CPyCppyy::StdSpanConverter::SetArg(PyObject *pyobject, Parameter ¶, Cal #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) \ -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);\ - return ToArrayFromBuffer(value, address, ctxt, buf, buflen, fShape, fIsFixed);\ - } 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, '?', ) @@ -1873,11 +1865,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', ) @@ -1956,18 +1944,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) { @@ -2021,7 +1997,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) \ @@ -2062,7 +2038,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) @@ -2123,9 +2099,8 @@ bool CPyCppyy::STLWStringConverter::ToMemory(PyObject* value, void* address, PyO return InstanceConverter::ToMemory(value, address, ctxt); } - 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) @@ -2157,7 +2132,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(); @@ -2256,9 +2231,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 @@ -2303,9 +2278,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(); @@ -2325,7 +2300,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) @@ -2392,7 +2370,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; } @@ -2400,7 +2378,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; } @@ -2470,7 +2448,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); @@ -2484,9 +2462,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 @@ -2525,9 +2503,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 @@ -2555,6 +2533,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 @@ -2567,7 +2551,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'; @@ -2629,8 +2613,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; } @@ -2756,6 +2740,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)) { @@ -2766,7 +2756,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(); @@ -2774,6 +2764,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)) { @@ -2783,12 +2776,13 @@ 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; } - // 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))) { @@ -2797,7 +2791,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++ @@ -2806,7 +2799,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); @@ -2834,7 +2827,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 @@ -2849,7 +2842,8 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, code << argtypes[i] << " arg" << i; if (i != nArgs-1) code << ", "; } - code << ") {\n"; + code << ") {\n" + << " CPyCppyy::PythonGILRAII python_gil_raii;\n"; // start function body Utility::ConstructCallbackPreamble(rettype, argtypes, code); @@ -2878,8 +2872,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 @@ -3014,9 +3008,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 @@ -3033,7 +3027,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) { @@ -3047,7 +3041,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)) { @@ -3071,7 +3065,7 @@ bool CPyCppyy::SmartPtrConverter::SetArg( // std::unique_ptr holding a Derived must not be accepted where a // std::unique_ptr is expected: the held smart pointer is still a // unique_ptr and does not convert to unique_ptr. - if (pyobj->IsSmart() && Cppyy::IsSubtype(pyobj->GetSmartUnderlyingType(), fUnderlyingType)) { + if (pyobj->IsSmart() && Cppyy::IsSubclass(pyobj->GetSmartUnderlyingType(), fUnderlyingType)) { para.fValue.fVoidp = ((CPPInstance*)pyobject)->GetSmartObject(); para.fTypeCode = 'V'; return true; @@ -3138,7 +3132,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))} { } @@ -3232,9 +3226,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, @@ -3249,15 +3242,13 @@ 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) { - 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); } @@ -3370,19 +3361,19 @@ 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()) return (h->second)(dims); +// mutable pointer references (T*&) are incompatible with Python's object model + if (!isConst && 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"}; + } + // 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) { @@ -3429,7 +3420,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); @@ -3440,9 +3431,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( @@ -3450,19 +3440,20 @@ 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; } } +// 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<"); @@ -3489,7 +3480,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim // 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 == "") { @@ -3520,6 +3511,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 @@ -3536,9 +3528,9 @@ 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"}; @@ -3547,6 +3539,222 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim 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); + +// mutable pointer references (T*&) are incompatible with Python's object model + if (!isConst && 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"}; + } + +// 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{PyExc_NotImplementedError, "this method cannot (yet) be called"}; + } + + 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 { + // fails on use + result = new NotImplementedConverter{PyExc_NotImplementedError, "this method cannot (yet) be called"}; + } + } + + return result; +} + //---------------------------------------------------------------------------- CPYCPPYY_EXPORT void CPyCppyy::DestroyConverter(Converter* p) @@ -3609,7 +3817,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>" @@ -3650,21 +3858,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; }; @@ -3715,11 +3911,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}; }; @@ -3739,11 +3931,8 @@ 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"]; gf["const std::int8_t&"] = gf["const int8_t&"]; gf["std::int8_t&"] = gf["int8_t&"]; @@ -3815,24 +4004,28 @@ static struct InitConvFactories_t { gf["const char*[]"] = (cf_t)+[](cdims_t d) { return new CStringArrayConverter{d, false}; }; gf["char*[]"] = (cf_t)+[](cdims_t d) { return new NonConstCStringArrayConverter{d, false}; }; gf["char ptr"] = gf["char*[]"]; - gf["std::string"] = (cf_t)+[](cdims_t) { return new STLStringConverter{}; }; - gf["const std::string&"] = gf["std::string"]; - gf["string"] = gf["std::string"]; - gf["const string&"] = gf["std::string"]; - gf["std::string&&"] = (cf_t)+[](cdims_t) { return new STLStringMoveConverter{}; }; - gf["string&&"] = gf["std::string&&"]; - gf["std::string_view"] = (cf_t)+[](cdims_t) { return new STLStringViewConverter{}; }; - gf[STRINGVIEW] = gf["std::string_view"]; - gf["std::string_view&"] = gf["std::string_view"]; - gf["const std::string_view&"] = gf["std::string_view"]; - gf["const " STRINGVIEW "&"] = gf["std::string_view"]; + 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"]; 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["const std::wstring &"] = gf["std::wstring"]; + gf["const " WSTRING1 " &"] = gf["std::wstring"]; + gf["const " WSTRING2 " &"] = gf["std::wstring"]; + // VoidPtrRefConverter should only be used for const references to pointers + gf["const 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**"]; gf["PyObject*"] = (cf_t)+[](cdims_t) { static PyObjectConverter c{}; return &c; }; 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 436d47fafe43f..da6c3ab7e4ff1 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,17 +36,34 @@ typedef unsigned long long PY_ULONG_LONG; typedef long double PY_LONG_DOUBLE; #endif +// 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 { - 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* 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 @@ -53,21 +74,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 +165,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,17 +188,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 IsIntegerType(const std::string &type_name); + bool IsDefaultConstructable(TCppScope_t scope); CPPYY_IMPORT - bool IsDefaultConstructable(TCppType_t type); + bool IsVariable(TCppScope_t scope); CPPYY_IMPORT void GetAllCppNames(TCppScope_t scope, std::set& cppnames); @@ -166,7 +233,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 @@ -184,9 +253,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); @@ -198,7 +267,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 @@ -206,39 +277,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 @@ -254,40 +331,74 @@ namespace Cppyy { // 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 + TCppScope_t WrapLambdaFromVariable(TCppScope_t var); + CPPYY_IMPORT + TCppScope_t AdaptFunctionForLambdaReturn(TCppScope_t fn); CPPYY_IMPORT - intptr_t GetDatamemberOffset(TCppScope_t scope, TCppIndex_t idata); + TCppType_t GetDatamemberType(TCppScope_t var); CPPYY_IMPORT - TCppIndex_t GetDatamemberIndex(TCppScope_t scope, const std::string& name); + 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); + 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); + TCppIndex_t 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 ebb62c8f73d22..a092061255b2d 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareExecutors.h @@ -95,24 +95,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); @@ -147,12 +147,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 { @@ -171,7 +171,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..da1682d2ba9f0 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DispatchPtr.cxx @@ -10,25 +10,25 @@ //----------------------------------------------------------------------------- PyObject* CPyCppyy::DispatchPtr::Get(bool borrowed) const { - PyGILState_STATE state = PyGILState_Ensure(); - PyObject* result = nullptr; + PythonGILRAII python_gil_raii; 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(); + PythonGILRAII python_gil_raii; if (strong) { Py_INCREF(pyobj); fPyHardRef = pyobj; @@ -38,18 +38,16 @@ 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(); + 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); Py_XDECREF(pyobj); - PyGILState_Release(state); } //----------------------------------------------------------------------------- @@ -58,7 +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 - PyGILState_STATE state = PyGILState_Ensure(); + PythonGILRAII python_gil_raii; if (fPyWeakRef) { PyObject* pyobj = CPyCppyy_GetWeakRef(fPyWeakRef); if (pyobj && ((CPPScope*)Py_TYPE(pyobj))->fFlags & CPPScope::kIsPython) @@ -69,13 +67,12 @@ 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(); + PythonGILRAII python_gil_raii; if (this != &other) { Py_XDECREF(fPyWeakRef); fPyWeakRef = nullptr; Py_XDECREF(fPyHardRef); @@ -84,13 +81,13 @@ CPyCppyy::DispatchPtr& CPyCppyy::DispatchPtr::assign(const DispatchPtr& other, v if (fPyHardRef) ((CPPInstance*)fPyHardRef)->SetDispatchPtr(this); Py_XDECREF(pyobj); } - PyGILState_Release(state); return *this; } //----------------------------------------------------------------------------- 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); @@ -101,11 +98,10 @@ void CPyCppyy::DispatchPtr::PythonOwns() //----------------------------------------------------------------------------- void CPyCppyy::DispatchPtr::CppOwns() { + PythonGILRAII python_gil_raii; // 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..b4c67542e6e68 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; } @@ -39,11 +39,11 @@ 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" - " PyGILState_STATE state = PyGILState_Ensure();\n" - " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on deleted python-side proxy\");\n" - " PyGILState_Release(state);\n" + " PyErr_Warn(PyExc_RuntimeWarning, (char*)\"Call attempted on " + "deleted python-side proxy\");\n" " return"; if (retType != "void") { if (retType.back() != '*') @@ -52,13 +52,12 @@ static inline void InjectMethod(Cppyy::TCppMethod_t method, const std::string& m 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 +119,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 +132,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 +148,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 +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" - "PyGILState_STATE state = PyGILState_Ensure();\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 @@ -255,7 +254,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 +281,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,18 +313,21 @@ 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 "; 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"; } @@ -343,9 +344,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 +366,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 +418,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 +441,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 +459,9 @@ 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" + " CPyCppyy::PythonGILRAII python_gil_raii;\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 +474,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 +485,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 +494,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 8a3648fad8576..fafc22293c00c 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( @@ -557,18 +567,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); } //---------------------------------------------------------------------------- @@ -576,7 +584,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'; @@ -598,7 +606,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 */ @@ -629,7 +637,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 @@ -749,7 +757,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); } //---------------------------------------------------------------------------- @@ -791,13 +799,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) { @@ -841,7 +852,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); @@ -884,6 +895,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.size() >= 2 && fullType.compare(fullType.size() - 2, 2, " &") == 0) + 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) @@ -1024,8 +1160,6 @@ struct InitExecFactories_t { gf["const unsigned char ptr"] = gf["unsigned char ptr"]; gf["std::byte ptr"] = (ef_t)+[](cdims_t d) { return new ByteArrayExecutor{d}; }; gf["const std::byte ptr"] = gf["std::byte ptr"]; - gf["byte ptr"] = gf["std::byte ptr"]; - gf["const byte ptr"] = gf["std::byte ptr"]; gf["int8_t ptr"] = (ef_t)+[](cdims_t d) { return new Int8ArrayExecutor{d}; }; gf["uint8_t ptr"] = (ef_t)+[](cdims_t d) { return new UInt8ArrayExecutor{d}; }; gf["short ptr"] = (ef_t)+[](cdims_t d) { return new ShortArrayExecutor{d}; }; @@ -1049,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"]; @@ -1097,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 8e302f74154b8..aa612edc8c29b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx @@ -1192,43 +1192,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/ProxyWrappers.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx index 6aad4bf43a22e..2db3d816f64da 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/ProxyWrappers.cxx @@ -90,16 +90,24 @@ 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); + 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::IsStaticData(scope, idata)) + if (Cppyy::IsStaticDatamember(data) || Cppyy::IsEnumConstant(data)) PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pname, (PyObject*)property); // cleanup @@ -159,7 +167,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 +182,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 +197,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 +219,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 +235,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 +251,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 +302,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 +328,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 +378,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 +400,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 +419,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 +444,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 +470,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 +514,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 +554,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 +566,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 +675,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 +697,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 +719,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 +743,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 +765,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 +786,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 +821,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) { @@ -860,7 +860,7 @@ PyObject* CPyCppyy::BindCppObjectNoCast(Cppyy::TCppObject_t address, if (deref) { Cppyy::TCppType_t clActual = Cppyy::GetActualClass(underlying, deref); if (clActual && clActual != underlying && - Cppyy::IsSubtype(clActual, underlying) && + Cppyy::IsSubclass(clActual, underlying) && Cppyy::GetBaseOffset(clActual, underlying, deref, -1 /* down-cast */) == 0) underlying = clActual; } @@ -932,7 +932,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) @@ -951,24 +951,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; } } @@ -978,7 +973,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/PyException.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/PyException.cxx index f478ab33f98f4..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,7 +26,7 @@ CPyCppyy::PyException::PyException() { #ifdef WITH_THREAD - PyGILState_STATE state = PyGILState_Ensure(); + PythonGILRAII python_gil_raii; #endif #if PY_VERSION_HEX >= 0x030c0000 @@ -115,10 +116,6 @@ CPyCppyy::PyException::PyException() fMsg += ")"; } - -#ifdef WITH_THREAD - PyGILState_Release(state); -#endif } CPyCppyy::PyException::~PyException() noexcept @@ -136,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/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 a0494ee3ad7f2..77be37b87484a 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" @@ -52,19 +53,6 @@ bool HasAttrDirect(PyObject* pyclass, PyObject* pyname, bool mustBeCPyCppyy = fa return false; } -bool HasAttrInMRO(PyObject *pyclass, PyObject *pyname) -{ - // Check base classes in the MRO (skipping the class itself) for a CPyCppyy overload. - PyObject *mro = ((PyTypeObject *)pyclass)->tp_mro; - if (mro && PyTuple_Check(mro)) { - for (Py_ssize_t i = 1; i < PyTuple_GET_SIZE(mro); ++i) { - if (HasAttrDirect(PyTuple_GET_ITEM(mro, i), pyname, /*mustBeCPyCppyy=*/true)) - return true; - } - } - return false; -} - PyObject* GetAttrDirect(PyObject* pyclass, PyObject* pyname) { // get an attribute without causing getattr lookups PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict); @@ -80,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* @@ -200,7 +188,6 @@ PyObject* DeRefGetAttr(PyObject* self, PyObject* name) return nullptr; } - return smart_follow(self, name, PyStrings::gDeref); } @@ -327,111 +314,6 @@ static ItemGetter* GetGetter(PyObject* args) return getter; } -namespace { - -void compileSpanHelpers() -{ - static bool compiled = false; - - if (compiled) - return; - - compiled = true; - - auto code = R"( -namespace __cppyy_internal { - -template -struct ptr_iterator { - T *cur; - T *end; - - ptr_iterator(T *c, T *e) : cur(c), end(e) {} - - T &operator*() const { return *cur; } - ptr_iterator &operator++() - { - ++cur; - return *this; - } - bool operator==(const ptr_iterator &other) const { return cur == other.cur; } - bool operator!=(const ptr_iterator &other) const { return !(*this == other); } -}; - -template -ptr_iterator make_iter(T *begin, T *end) -{ - return {begin, end}; -} - -} // namespace __cppyy_internal - -// Note: for const span, T is const-qualified here -template -auto __cppyy_internal_begin(T &s) noexcept -{ - return __cppyy_internal::make_iter(s.data(), s.data() + s.size()); -} - -// Note: for const span, T is const-qualified here -template -auto __cppyy_internal_end(T &s) noexcept -{ - // end iterator = begin iterator with cur == end - return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size()); -} - )"; - Cppyy::Compile(code, /*silent*/ true); -} - -PyObject *spanBegin() -{ - static PyObject *pyFunc = nullptr; - if (!pyFunc) { - compileSpanHelpers(); - PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::gGlobalScope); - pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_begin"); - if (!pyFunc) { - PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper " - "'__cppyy_internal_begin' for std::span pythonization"); - } - } - return pyFunc; -} - -PyObject *spanEnd() -{ - static PyObject *pyFunc = nullptr; - if (!pyFunc) { - compileSpanHelpers(); - PyObject *py_ns = CPyCppyy::GetScopeProxy(Cppyy::gGlobalScope); - pyFunc = PyObject_GetAttrString(py_ns, "__cppyy_internal_end"); - if (!pyFunc) { - PyErr_Format(PyExc_RuntimeError, "cppyy internal error: failed to locate helper " - "'__cppyy_internal_end' for std::span pythonization"); - } - } - return pyFunc; -} - -} // namespace - -static PyObject *SpanBegin(PyObject *self, PyObject *) -{ - auto begin = spanBegin(); - if (!begin) - return nullptr; - return PyObject_CallOneArg(begin, self); -} - -static PyObject *SpanEnd(PyObject *self, PyObject *) -{ - auto end = spanEnd(); - if (!end) - return nullptr; - return PyObject_CallOneArg(end, self); -} - static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter) { Py_ssize_t sz = getter->size(); @@ -454,11 +336,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(); @@ -563,20 +445,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; } } } @@ -653,13 +524,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) { @@ -670,13 +534,14 @@ 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 @@ -690,7 +555,7 @@ static PyObject* vector_iter(PyObject* v) { 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) { @@ -701,29 +566,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; @@ -1482,7 +1350,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 @@ -1504,7 +1371,6 @@ PyObject* UTF8Str(PyObject* self) Py_DECREF(res); return str_res; } -#endif Py_hash_t STLStringHash(PyObject* self) { @@ -1738,8 +1604,10 @@ bool run_pythonizors(PyObject* pyclass, PyObject* pyname, const std::vector. Skip if size() has multiple overloads, as that - // indicates it is not the simple container-style size() one would map to __len__. - if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/true) || HasAttrInMRO(pyclass, PyStrings::gSize)) { - bool sizeIsInteger = false; - PyObject *pySizeMethod = PyObject_GetAttr(pyclass, PyStrings::gSize); - if (pySizeMethod && CPPOverload_Check(pySizeMethod)) { - auto *ol = (CPPOverload *)pySizeMethod; - if (ol->HasMethods() && ol->fMethodInfo->fMethods.size() == 1) { - PyObject *pyrestype = - ol->fMethodInfo->fMethods[0]->Reflex(Cppyy::Reflex::RETURN_TYPE, Cppyy::Reflex::AS_STRING); - if (pyrestype) { - sizeIsInteger = Cppyy::IsIntegerType(CPyCppyy_PyText_AsString(pyrestype)); - Py_DECREF(pyrestype); - } - } - } - Py_XDECREF(pySizeMethod); - - if (sizeIsInteger) { - bool hasIterators = (HasAttrDirect(pyclass, PyStrings::gBegin) || HasAttrInMRO(pyclass, PyStrings::gBegin)) && - (HasAttrDirect(pyclass, PyStrings::gEnd) || HasAttrInMRO(pyclass, PyStrings::gEnd)); - bool hasSubscript = HasAttrDirect(pyclass, PyStrings::gGetItem) || HasAttrInMRO(pyclass, PyStrings::gGetItem); - if (hasIterators || hasSubscript) { - Utility::AddToClass(pyclass, "__len__", "size"); - } - } +// for STL containers, and user classes modeled after them +// the attribute must be a CPyCppyy overload, otherwise the check gives false +// positives in the case where the class has a non-function attribute that is +// called "size". + if (HasAttrDirect(pyclass, PyStrings::gSize, /*mustBeCPyCppyy=*/ true)) { + Utility::AddToClass(pyclass, "__len__", "size"); } if (HasAttrDirect(pyclass, PyStrings::gContains)) { @@ -1808,12 +1652,12 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) !((PyTypeObject*)pyclass)->tp_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) @@ -1851,7 +1695,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; @@ -1868,7 +1712,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; @@ -1883,7 +1727,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__"); @@ -1895,14 +1738,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); @@ -1911,23 +1753,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 { @@ -1955,10 +1804,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(); } } @@ -1969,21 +1818,6 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) //- class name based pythonization ------------------------------------------- - if (IsTemplatedSTLClass(name, "span")) { - // libstdc++ (GCC >= 15) implements std::span::iterator using a private - // nested tag type, which makes the iterator non-instantiable by - // CallFunc-generated wrappers (the return type cannot be named without - // violating access rules). - // - // To preserve correct Python iteration semantics, we replace begin()/end() - // for std::span to return a custom pointer-based iterator instead. This - // avoids relying on std::span::iterator while still providing a real C++ - // iterator object that CPyCppyy can also wrap and expose via - // __iter__/__next__. - Utility::AddToClass(pyclass, "begin", (PyCFunction)SpanBegin, METH_NOARGS); - Utility::AddToClass(pyclass, "end", (PyCFunction)SpanEnd, METH_NOARGS); - } - if (IsTemplatedSTLClass(name, "vector")) { // std::vector is a special case in C++ @@ -1998,18 +1832,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)) { @@ -2024,14 +1850,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); @@ -2060,7 +1890,6 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) // 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); @@ -2085,7 +1914,9 @@ 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" || + name == "std::__1::basic_string" || // libc++ inline namespace + name == "std::string") { // typedef preserved by GetScopedFinalName on libc++ Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLStringRepr, METH_NOARGS); Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLStringStr, METH_NOARGS); Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLStringBytes, METH_NOARGS); @@ -2093,7 +1924,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) 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); @@ -2109,7 +1940,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" || + name == "std::__1::basic_string_view" || // libc++ inline namespace + name == "std::string_view") { // typedef preserved by GetScopedFinalName on libc++ Utility::AddToClass(pyclass, "__real_init", "__init__"); Utility::AddToClass(pyclass, "__init__", (PyCFunction)StringViewInit, METH_VARARGS | METH_KEYWORDS); Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLViewStringBytes, METH_NOARGS); @@ -2120,14 +1953,9 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, const std::string& name) Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, METH_NOARGS); } -// The first condition was already present in upstream CPyCppyy. The other two -// are special to ROOT, because its reflection layer gives us the types without -// the "std::" namespace. On some platforms, that applies only to the template -// arguments, and on others also to the "basic_string". - else if (name == "std::basic_string,std::allocator >" - || name == "basic_string,allocator >" - || name == "std::basic_string,allocator >" - ) { + else if (name == "std::basic_string,std::allocator >" || + name == "std::__1::basic_string,std::__1::allocator >" || + name == "std::wstring") { Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, METH_NOARGS); Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLWStringStr, METH_NOARGS); Utility::AddToClass(pyclass, "__bytes__", (PyCFunction)STLWStringBytes, METH_NOARGS); 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 dc6453d1529d8..bbf5e83832ebc 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) @@ -76,7 +82,7 @@ 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 || @@ -95,8 +101,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; @@ -142,12 +154,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(); @@ -282,9 +288,8 @@ PyObject* TemplateProxy::Instantiate(const std::string& fname, Py_DECREF(pyol); pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership } - // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy - // we do not cache it - // as this might be a pythonization (monkey-patched func/method) + // Case 6: pre-existing object is not a CPPOverload nor TemplateProxy + // we do not cache it, as this might be a pythonization (monkey-patched func/method) else { Py_DECREF(pyol); pyol = (PyObject*)CPPOverload_New(fname, meth); @@ -435,12 +440,6 @@ static int tpp_doc_set(TemplateProxy* pytmpl, PyObject *val, void *) { 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 @@ -604,7 +603,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; @@ -627,7 +626,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; @@ -664,7 +663,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); @@ -748,21 +747,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 +757,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 +788,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 +818,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; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx index 67361fa69fc8b..9df8785ec6dd7 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TupleOfInstances.cxx @@ -123,7 +123,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 */) { @@ -141,8 +141,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/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 c878e050c01bc..4dbfd4833ad06 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" @@ -200,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); @@ -265,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); @@ -331,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 @@ -354,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__ @@ -493,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; @@ -559,7 +563,8 @@ static bool AddTypeName(std::string& tmpl_name, PyObject* tn, PyObject* arg, // ctypes function pointer PyObject* argtypes = nullptr; PyObject* ret = nullptr; - if ((argtypes = PyObject_GetAttrString(arg, "argtypes")) && (ret = PyObject_GetAttrString(arg, "restype"))) { + 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) @@ -658,6 +663,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 ""; } } @@ -674,6 +681,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 @@ -717,7 +968,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(\"" @@ -755,9 +1006,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"; @@ -771,7 +1019,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"; } } @@ -779,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) @@ -802,9 +1050,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"); } @@ -934,7 +1181,12 @@ 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(); + 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)); if (PyObject_GetBuffer(pyobject, &bufinfo, PyBUF_FORMAT) == 0) { @@ -1055,13 +1307,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; @@ -1171,9 +1417,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(); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h index dfa2e5478f6a0..0640963437394 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Utility.h @@ -39,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) { From 9642b196cebadcdc76473321e0a70f2721e7897b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:52:19 +0200 Subject: [PATCH 15/45] [CPyCppyy] Refactor CallContext policy system, add Py 3.13 guard [upstream] Source: ROOT master - Move to using SetGlobalPolicy(ECallFlags, bool) + GlobalPolicyFlags() - Drop the CallContext* argument from UseStrictOwnership() - Guard PyMapping_GetOptionalItemString for Python>3.13 in Dispatcher.cxx --- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 3 +- .../pyroot/cppyy/CPyCppyy/src/CallContext.cxx | 40 ++++++------------- .../pyroot/cppyy/CPyCppyy/src/CallContext.h | 22 ++++------ .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 14 +++---- .../pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx | 5 +++ 5 files changed, 33 insertions(+), 51 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index d0d5d53e2902f..9f62a3da05fb3 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx @@ -1016,8 +1016,7 @@ PyObject* CPyCppyy::CPPMethod::Execute(void* self, ptrdiff_t offset, CallContext // call the interface method PyObject* result = 0; - if (CallContext::sSignalPolicy != CallContext::kProtected && \ - !(ctxt->fFlags & CallContext::kProtected)) { + if (!(CallContext::GlobalPolicyFlags() & CallContext::kProtected) && !(ctxt->fFlags & CallContext::kProtected)) { // bypasses try block (i.e. segfaults will abort) result = ExecuteFast(self, offset, ctxt); } else { diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx index 759765b242750..416741d0caab1 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.cxx @@ -2,15 +2,12 @@ #include "CPyCppyy.h" #include "CallContext.h" - -//- 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 +//----------------------------------------------------------------------------- +uint32_t &CPyCppyy::CallContext::GlobalPolicyFlags() +{ + static uint32_t flags = 0; + return flags; +} //----------------------------------------------------------------------------- void CPyCppyy::CallContext::AddTemporary(PyObject* pyobj) { @@ -38,24 +35,13 @@ void CPyCppyy::CallContext::Cleanup() { } //----------------------------------------------------------------------------- -bool CPyCppyy::CallContext::SetMemoryPolicy(ECallFlags e) +bool CPyCppyy::CallContext::SetGlobalPolicy(ECallFlags toggleFlag, bool enabled) { -// 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; + auto &flags = GlobalPolicyFlags(); + bool old = flags & toggleFlag; + if (enabled) + flags |= toggleFlag; + else + flags &= ~toggleFlag; return old; } - diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h index 98cb15622598a..35c5a93888d33 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h @@ -72,20 +72,16 @@ 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 - static ECallFlags sMemoryPolicy; - static bool SetMemoryPolicy(ECallFlags e); +// Policies about memory handling and signal safety + static bool SetGlobalPolicy(ECallFlags e, bool enabled); + + static uint32_t& GlobalPolicyFlags(); 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; @@ -146,13 +142,9 @@ inline bool ReleasesGIL(CallContext* ctxt) { return ctxt ? (ctxt->fFlags & CallContext::kReleaseGIL) : false; } -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; +inline bool UseStrictOwnership() { + using CC = CPyCppyy::CallContext; + return !(CC::GlobalPolicyFlags() & CC::kUseHeuristics); } template diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index 094b74b85bf26..cb7552836e493 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -1569,7 +1569,7 @@ bool CPyCppyy::VoidArrayConverter::SetArg( para.fValue.fVoidp = nullptr; if (pyobj) { // depending on memory policy, some objects are no longer owned when passed to C++ - if (!fKeepControl && !UseStrictOwnership(ctxt)) + if (!fKeepControl && !UseStrictOwnership()) pyobj->CppOwns(); // set pointer (may be null) and declare success @@ -1634,7 +1634,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 && CallContext::sMemoryPolicy != CallContext::kUseStrict) + if (!fKeepControl && !UseStrictOwnership()) pyobj->CppOwns(); // set pointer (may be null) and declare success @@ -2233,7 +2233,7 @@ bool CPyCppyy::InstancePtrConverter::SetArg( Cppyy::TCppType_t oisa = pyobj->ObjectIsA(); if (oisa && (oisa == fClass || Cppyy::IsSubclass(oisa, fClass))) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership(ctxt)) + if (!KeepControl() && !UseStrictOwnership()) pyobj->CppOwns(); // calculate offset between formal and actual arguments @@ -2280,7 +2280,7 @@ bool CPyCppyy::InstancePtrConverter::ToMemory(PyObject* value, void* ad if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) + if (!KeepControl() && !UseStrictOwnership()) ((CPPInstance*)value)->CppOwns(); *(void**)address = pyobj->GetObject(); @@ -2464,7 +2464,7 @@ bool CPyCppyy::InstancePtrPtrConverter::SetArg( if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && !UseStrictOwnership(ctxt)) + if (!KeepControl() && !UseStrictOwnership()) pyobj->CppOwns(); // set pointer (may be null) and declare success @@ -2505,7 +2505,7 @@ bool CPyCppyy::InstancePtrPtrConverter::ToMemory( if (Cppyy::IsSubclass(pyobj->ObjectIsA(), fClass)) { // depending on memory policy, some objects need releasing when passed into functions - if (!KeepControl() && CallContext::sMemoryPolicy != CallContext::kUseStrict) + if (!KeepControl() && !UseStrictOwnership()) pyobj->CppOwns(); // register the value for potential recycling @@ -3010,7 +3010,7 @@ bool CPyCppyy::SmartPtrConverter::SetArg( if (Cppyy::TCppType_t tsmart = pyobj->GetSmartIsA()) { if (Cppyy::IsSubclass(tsmart, fSmartPtrType)) { // depending on memory policy, some objects need releasing when passed into functions - if (!fKeepControl && !UseStrictOwnership(ctxt)) + if (!fKeepControl && !UseStrictOwnership()) ((CPPInstance*)pyobject)->CppOwns(); // calculate offset between formal and actual arguments diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx index b4c67542e6e68..3829ec0c0ea03 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Dispatcher.cxx @@ -494,7 +494,12 @@ 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); From 26f566284ec23c5c5441ac05199aa8ad811bea15 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:37:48 +0200 Subject: [PATCH 16/45] [CPyCppyy] Add converters/low-level views for fixed width integers [upstream] --- .../pyroot/cppyy/CPyCppyy/src/CallContext.h | 4 ++ .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 46 ++++++++++++++++++- .../cppyy/CPyCppyy/src/DeclareConverters.h | 12 +++++ .../cppyy/CPyCppyy/src/LowLevelViews.cxx | 40 ++++++++++++++++ .../pyroot/cppyy/CPyCppyy/src/LowLevelViews.h | 9 ++++ 5 files changed, 109 insertions(+), 2 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h index 35c5a93888d33..4b88bfdd3a6fb 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CallContext.h @@ -22,7 +22,11 @@ struct Parameter { union Value { bool fBool; int8_t fInt8; + int16_t fInt16; + int32_t fInt32; uint8_t fUInt8; + uint16_t fUInt16; + uint32_t fUInt32; short fShort; unsigned short fUShort; int fInt; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index cb7552836e493..686953dec08f1 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -147,14 +147,16 @@ 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 NTYPES 25 +#define ct_c_int16 25 +#define ct_c_int32 26 +#define NTYPES 27 static std::array gCTypesNames = { "c_bool", "c_char", "c_wchar", "c_byte", "c_ubyte", "c_short", "c_ushort", "c_uint16", "c_int", "c_uint", "c_uint32", "c_long", "c_ulong", "c_longlong", "c_ulonglong", "c_float", "c_double", "c_longdouble", "c_char_p", "c_wchar_p", "c_void_p", "c_fcomplex", "c_complex", - "_Pointer", "_CFuncPtr" }; + "_Pointer", "_CFuncPtr", "c_int16", "c_int32" }; static std::array gCTypesTypes; static std::array gCTypesPtrTypes; @@ -412,6 +414,10 @@ 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) @@ -820,6 +826,10 @@ 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) @@ -895,6 +905,10 @@ 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); @@ -1053,6 +1067,14 @@ 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( @@ -1865,7 +1887,11 @@ 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', ) @@ -3861,6 +3887,18 @@ static struct InitConvFactories_t { 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["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["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; }; @@ -3911,7 +3949,11 @@ 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}; }; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h index 302027ff652f0..34ea62c7714dd 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h @@ -90,7 +90,11 @@ 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); @@ -110,7 +114,11 @@ 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); @@ -220,7 +228,11 @@ 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); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx index aa612edc8c29b..8e302f74154b8 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.cxx @@ -1192,3 +1192,43 @@ 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 0edb32954a60b..b3b6c8daa16ca 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/LowLevelViews.h @@ -52,6 +52,15 @@ 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); From ac97fc9ad6df72cd5ce644874e016449209ab996 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 16:16:12 +0200 Subject: [PATCH 17/45] [CPyCppyy] Fix typos, call GetActualClass in instance cast_actual [upstream] Source: master --- bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx | 6 +++--- bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx index 2d384d1c65027..d5c7a2b8110b0 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.cxx @@ -285,7 +285,7 @@ static PyObject* op_destruct(CPPInstance* self) } //= CPyCppyy object dispatch support ========================================= -static PyObject* op_dispatch(PyObject* self, PyObject* args, PyObject* /* kdws */) +static PyObject* op_dispatch(PyObject* self, PyObject* args, PyObject* /* kwds */) { // User-side __dispatch__ method to allow selection of a specific overloaded // method. The actual selection is in the __overload__() method of CPPOverload. @@ -548,7 +548,7 @@ static inline void* cast_actual(void* obj) { return address; Cppyy::TCppScope_t klass = ((CPPClass*)Py_TYPE((PyObject*)obj))->fCppType; - Cppyy::TCppScope_t clActual = klass /* XXX: Cppyy::GetActualClass(klass, address) */; + Cppyy::TCppScope_t clActual = Cppyy::GetActualClass(klass, address); if (clActual && clActual != klass) { intptr_t offset = Cppyy::GetBaseOffset( clActual, klass, address, -1 /* down-cast */, true /* report errors */); @@ -805,7 +805,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/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index 9f62a3da05fb3..be58e8d30970b 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx @@ -283,7 +283,7 @@ void CPyCppyy::CPPMethod::SetPyError_(PyObject* msg) // C++ method to give some context. // 2. A C++ exception has occured: // Augment the exception message with the docstring of this method -// 3. A Python exception has occured with a traceback: +// 3. A Python exception has occurred 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. From 2948b611b93495e9faad6def6b933ca01bdbdf7f Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 16:28:28 +0200 Subject: [PATCH 18/45] [CPyCppyy] Use TCppType_t for fCppType and fUnderlyingType [upstream] Neither on ROOT-master, nor compres-forks These fields hold C++ type handles, so semantically we should enforce this type --- bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h index 370224ab69de4..919fb54f23423 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPScope.h @@ -52,7 +52,7 @@ class CPPScope { public: PyHeapTypeObject fType; - Cppyy::TCppScope_t fCppType; + Cppyy::TCppType_t fCppType; uint32_t fFlags; union { CppToPyMap_t* fCppObjects; // classes only @@ -69,7 +69,7 @@ typedef CPPScope CPPClass; class CPPSmartClass : public CPPClass { public: - Cppyy::TCppScope_t fUnderlyingType; + Cppyy::TCppType_t fUnderlyingType; Cppyy::TCppMethod_t fDereferencer; }; From c3c63dea5640ca043670325e114ebfad5206260a Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 16:35:34 +0200 Subject: [PATCH 19/45] [CPyCppyy] Apply memory policy refactor to CPPOverload.cxx [upstream] Source: master - Deprecate __mempolicy__ getter/setter with a clear error pointing users to the SetOwnership() pythonization - Drop the per-method memory policy from mp_call (referenced the removed sMemoryPolicy static) - Use GlobalPolicyFlags() for the Clone heuristic - Match the method name prefix before '<' only, so template arguments don't affect Clone detection --- .../pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx index 2aa50d1a63b43..6d2a2edfb5aee 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPOverload.cxx @@ -549,40 +549,25 @@ 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* pymeth, void*) +static PyObject* mp_getmempolicy(CPPOverload*, void*) { -// 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); + PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message); + return nullptr; } //---------------------------------------------------------------------------- -static int mp_setmempolicy(CPPOverload* pymeth, PyObject* value, void*) +static int mp_setmempolicy(CPPOverload*, PyObject*, void*) { -// 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; + PyErr_SetString(PyExc_RuntimeError, mempolicy_error_message); + return -1; } - //---------------------------------------------------------------------------- #define CPPYY_BOOLEAN_PROPERTY(name, flag, label) \ static PyObject* mp_get##name(CPPOverload* pymeth, void*) { \ @@ -643,8 +628,8 @@ static PyGetSetDef mp_getset[] = { // 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*)"For argument ownership rules: like global, either heuristic or strict", nullptr}, + {(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy, + (char*)"Unused", 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, @@ -686,8 +671,6 @@ 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; @@ -1127,10 +1110,19 @@ 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 - if (CallContext::sMemoryPolicy == CallContext::kUseHeuristics && \ - name.find("Clone") != std::string::npos) - fMethodInfo->fFlags |= CallContext::kIsCreator; +// 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; + } + } #if PY_VERSION_HEX >= 0x03080000 fVectorCall = (vectorcallfunc)mp_vectorcall; From 36f8fa421554c2d40e63e5a89c4d725c85ed16c4 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:07:38 +0200 Subject: [PATCH 20/45] [CPyCppyy] Add CStringArrayConverter::ToMemory and buffer helper [upstream] Source: ROOT-master 760b6cc5c7c - ToMemory override that lets the array converter copy Python strings (not only buffers) into C++ const char* arrays - ToArrayFromBuffer<> template helper that copies a buffer to an array-converter memory address, lifetime management via SetLifeLine --- .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 47 +++++++++++++++++++ .../cppyy/CPyCppyy/src/DeclareConverters.h | 1 + 2 files changed, 48 insertions(+) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index 686953dec08f1..ccd2858b480fb 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -1753,6 +1753,42 @@ bool CPyCppyy::StdSpanConverter::SetArg(PyObject *pyobject, Parameter ¶, Cal #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) \ @@ -1970,6 +2006,17 @@ 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) { diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h index 34ea62c7714dd..84572104e8929 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h @@ -255,6 +255,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: From c76b440ff5606435e44c142a61116e736aa4f1c8 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 16:39:02 +0200 Subject: [PATCH 21/45] [CPyCppyy] Add missing override on HasState in CPPYY_DECLARE_ARRAY_CONVERTER [upstream] Source: master Adds the 'override' keyword on HasState() in the macro so the derived-class definition matches the base-class virtual declaration. --- bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h index 84572104e8929..505a01e852532 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h @@ -63,7 +63,7 @@ public: \ bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; \ PyObject* FromMemory(void*) override; \ bool ToMemory(PyObject*, void*, PyObject* = nullptr) override; \ - bool HasState() { return true; } \ + bool HasState() override { return true; } \ virtual std::string GetFailureMsg() { return "[" #name "ArrayConverter]"; }\ protected: \ dims_t fShape; \ From 1e4e692d8398bda2798adb27479d1600636f45cf Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:46:35 +0200 Subject: [PATCH 22/45] [CPyCppyy] Collapse CPyCppyyModule policy setters into a macro [upstream] Source: ROOT-master - DEFINE_CALL_POLICY_TOGGLE macro generates SetHeuristicMemoryPolicy, SetImplicitSmartPointerConversion, and SetGlobalSignalPolicy used by CallContext::SetGlobalPolicy() - Drop the obsolete kMemoryHeuristics/kMemoryStrict module-level labels (no longer referenced the removed CallContext::kUseStrict) - Add missing guard for Py_INCREF(gThisModule) --- .../cppyy/CPyCppyy/src/CPyCppyyModule.cxx | 67 +++++++------------ 1 file changed, 24 insertions(+), 43 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx index e224ec3fd6f0c..3ab4f8b47e7e1 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx @@ -920,41 +920,23 @@ static PyObject* AddTypeReducer(PyObject*, PyObject* args) Py_RETURN_NONE; } -//---------------------------------------------------------------------------- -static PyObject* SetMemoryPolicy(PyObject*, PyObject* args) -{ -// Set the global memory policy, which affects object ownership when objects -// are passed as function arguments. - PyObject* policy = nullptr; - if (!PyArg_ParseTuple(args, const_cast("O!"), &PyInt_Type, &policy)) - return nullptr; - - long old = (long)CallContext::sMemoryPolicy; - - long l = PyInt_AS_LONG(policy); - if (CallContext::SetMemoryPolicy((CallContext::ECallFlags)l)) { - return PyInt_FromLong(old); - } - - PyErr_Format(PyExc_ValueError, "Unknown policy %ld", l); - return nullptr; +#define DEFINE_CALL_POLICY_TOGGLE(name, flagname) \ +static PyObject* name(PyObject*, PyObject* args) \ +{ \ + PyObject* enabled = 0; \ + if (!PyArg_ParseTuple(args, const_cast("O"), &enabled)) \ + return nullptr; \ + \ + if (CallContext::SetGlobalPolicy(CallContext::flagname, PyObject_IsTrue(enabled))) { \ + Py_RETURN_TRUE; \ + } \ + \ + Py_RETURN_FALSE; \ } -//---------------------------------------------------------------------------- -static PyObject* SetGlobalSignalPolicy(PyObject*, PyObject* args) -{ -// Set the global signal policy, which determines whether a jmp address -// should be saved to return to after a C++ segfault. - PyObject* setProtected = 0; - if (!PyArg_ParseTuple(args, const_cast("O"), &setProtected)) - return nullptr; - - if (CallContext::SetGlobalSignalPolicy(PyObject_IsTrue(setProtected))) { - Py_RETURN_TRUE; - } - - Py_RETURN_FALSE; -} +DEFINE_CALL_POLICY_TOGGLE(SetHeuristicMemoryPolicy, kUseHeuristics); +DEFINE_CALL_POLICY_TOGGLE(SetImplicitSmartPointerConversion, kImplicitSmartPtrConversion); +DEFINE_CALL_POLICY_TOGGLE(SetGlobalSignalPolicy, kProtected); //---------------------------------------------------------------------------- static PyObject* SetOwnership(PyObject*, PyObject* args) @@ -1041,10 +1023,13 @@ static PyMethodDef gCPyCppyyMethods[] = { METH_O, (char*)"Install a type pinning."}, {(char*) "_add_type_reducer", (PyCFunction)AddTypeReducer, METH_VARARGS, (char*)"Add a type reducer."}, - {(char*) "SetMemoryPolicy", (PyCFunction)SetMemoryPolicy, - METH_VARARGS, (char*)"Determines object ownership model."}, - {(char*) "SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, - METH_VARARGS, (char*)"Trap signals in safe mode to prevent interpreter abort."}, + {(char*) "SetHeuristicMemoryPolicy", (PyCFunction)SetHeuristicMemoryPolicy, + METH_VARARGS, (char*)"Set the global memory policy, which affects object ownership when objects are passed as function arguments."}, + {(char*) "SetImplicitSmartPointerConversion", (PyCFunction)SetImplicitSmartPointerConversion, + METH_VARARGS, (char*)"Enable or disable the implicit conversion to smart pointers in function calls (on by default)."}, + {(char *)"SetGlobalSignalPolicy", (PyCFunction)SetGlobalSignalPolicy, METH_VARARGS, + (char *)"Set the global signal policy, which determines whether a jmp address should be saved to return to after a " + "C++ segfault. In practical terms: trap signals in safe mode to prevent interpreter abort."}, {(char*) "SetOwnership", (PyCFunction)SetOwnership, METH_VARARGS, (char*)"Modify held C++ object ownership."}, {(char*) "AddSmartPtrType", (PyCFunction)AddSmartPtrType, @@ -1218,18 +1203,14 @@ extern "C" PyObject* PyInit_libcppyy() 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 static MemoryRegulator s_memory_regulator; +#if PY_VERSION_HEX >= 0x03000000 Py_INCREF(gThisModule); +#endif return gThisModule; } From bb1cfbbfb389bb1e4ddcbc79673e6dab0d46cb46 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 16:49:13 +0200 Subject: [PATCH 23/45] [CPyCppyy] Use CallContext flag for implicit conversion check [upstream] --- bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index ccd2858b480fb..b8f5cd6e5ecb1 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -3114,7 +3114,7 @@ bool CPyCppyy::SmartPtrConverter::SetArg( } // for the case where we have an ordinary object to convert - if (!pyobj->IsSmart() && Cppyy::IsSubclass(oisa, fUnderlyingType)) { + if ((ctxt->fFlags & CallContext::kImplicitSmartPtrConversion) && !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)) { From c9d7ff70450e6edf48c7a31fae9389889f09bbd9 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:41:27 +0200 Subject: [PATCH 24/45] [CPyCppyy] Pythonize: fix __iadd__ return and disable buggy __array__ [upstream] Source: ROOT-master - VectorIAdd returns self after the insert. Python += reassigns the lhs to the returned value, so returning the inserted-iterator result silently breaks the idiomatic += pattern on std::vector. Disable by wrapping VectorArray in #if 0 and removes its Utility::AddToClass call (to be fixed at a later stage) - Drop Cppyy::gGlobalScope extern, every caller uses Cppyy::GetGlobalScope() --- bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 3 --- .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 23 +++++++++++++++---- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h index da6c3ab7e4ff1..057223b70c876 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h @@ -126,9 +126,6 @@ namespace Cppyy { CPPYY_IMPORT bool IsPointerType(TCppType_t type); - CPPYY_IMPORT - TCppScope_t gGlobalScope; // for fast access - // memory management --------------------------------------------------------- CPPYY_IMPORT TCppObject_t Allocate(TCppType_t type); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index 77be37b87484a..b6988641cd883 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -445,9 +445,20 @@ 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) { - PyObject* result = PyObject_CallMethodObjArgs(self, PyStrings::gInsert, vend, fi, nullptr); + // 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); Py_DECREF(vend); - return result; + + if (!it) + return nullptr; + + Py_DECREF(it); + // Assign the result of the __iadd__ override to the std::vector + Py_INCREF(self); + return self; } } } @@ -524,6 +535,9 @@ 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) +#if 0 //--------------------------------------------------------------------------- PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) { @@ -534,6 +548,7 @@ PyObject* VectorArray(PyObject* self, PyObject* args, PyObject* kwargs) Py_DECREF(pydata); return newarr; } +#endif //----------------------------------------------------------------------------- @@ -1834,8 +1849,8 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) Utility::AddToClass(pyclass, "__real_data", "data"); Utility::AddToClass(pyclass, "data", (PyCFunction)VectorData); - // numpy array conversion - Utility::AddToClass(pyclass, "__array__", (PyCFunction)VectorArray, METH_VARARGS | METH_KEYWORDS /* unused */); + // numpy array conversion (disabled: buggy for multi-dim vectors) + // Utility::AddToClass(pyclass, "__array__", (PyCFunction)VectorArray, METH_VARARGS | METH_KEYWORDS /* unused */); // checked getitem if (HasAttrDirect(pyclass, PyStrings::gLen)) { From fde91d7285c9871bb1cf4c44815a2e366070f6ce Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 30 Apr 2026 13:53:50 +0200 Subject: [PATCH 25/45] [CPyCppyy] add std::span branch in TCppType_t CreateConverter overload [upstream] This std::span branch went only into the string based overload and not the new type based one that the compres forks use. --- .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index b8f5cd6e5ecb1..26dcb282fa419 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -3754,6 +3754,31 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d } } +// FIXME: Taken from ROOT, update this to use CppInterOp for span check and extracting value type +#if __cplusplus >= 202002L +//-- special case: std::span + pos = resolvedTypeStr.find("span<"); + if (pos == 0 /* no std:: */ || pos == 5 /* with std:: */ || + pos == 6 /* const no std:: */ || pos == 11 /* const with std:: */ ) { + + auto pos1 = realTypeStr.find('<'); + auto pos21 = realTypeStr.find(','); // for the case there are more template args + auto pos22 = realTypeStr.find('>'); + auto len = std::min(pos21 - pos1, pos22 - pos1) - 1; + std::string value_type = realTypeStr.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; Cppyy::TCppScope_t klass = Cppyy::GetScopeFromType(realType); From b37acf24aea97e71481c96b3d193ddcc1a17a913 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 30 Apr 2026 13:54:02 +0200 Subject: [PATCH 26/45] [CPyCppyy] Use complete basic_string name with default template args [upstream] GetQualifiedCompleteName returns the canonical class name with default template args spelled out --- bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index b6988641cd883..97598ad76b2b2 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -1929,7 +1929,12 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) Utility::AddToClass(pyclass, "__iter__", (PyCFunction)PyObject_SelfIter, METH_NOARGS); } +// CPyCppyy upstream only checks the typedef-collapsed shape. Master ROOT's TClass +// reflection produced that shape for free; with CppInterOp's GetQualifiedCompleteName +// the canonical class name (with default template args, with spaces) is returned, so +// we additionally recognise that shape here. else if (name == "std::basic_string" || + name == "std::basic_string, std::allocator >" || name == "std::__1::basic_string" || // libc++ inline namespace name == "std::string") { // typedef preserved by GetScopedFinalName on libc++ Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLStringRepr, METH_NOARGS); @@ -1956,6 +1961,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) } else if (name == "std::basic_string_view" || + name == "std::basic_string_view >" || name == "std::__1::basic_string_view" || // libc++ inline namespace name == "std::string_view") { // typedef preserved by GetScopedFinalName on libc++ Utility::AddToClass(pyclass, "__real_init", "__init__"); @@ -1969,6 +1975,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) } else if (name == "std::basic_string,std::allocator >" || + name == "std::basic_string, std::allocator >" || name == "std::__1::basic_string,std::__1::allocator >" || name == "std::wstring") { Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, METH_NOARGS); From 5cc9786b80faafa347dd9e670bacb57c2acb4d6e Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 4 May 2026 13:24:47 +0200 Subject: [PATCH 27/45] [CPyCppyy] Fall through to generic Python callable if overload signature mismatches [upstream] In PyFunction_AsCPointer, the CPPOverload and TemplateProxy branches previously returned null when the requested signature does not match the candidates, preventing the generic-Python-callable fall-through (JIT-ed wrapper path), even though CPPOverload and TemplateProxy are valid PyCallable_Check inputs. As a result, std::function/fn ptr parameters could not accept a cppyy free function or template overload whose signature did not match the target. --- bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index 26dcb282fa419..9f97923444dde 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -2838,8 +2838,8 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, } } // FIXME: maybe we should try BestOverloadFunctionMatch before failing - // FIXME: Should we fall-through, with calling through Python - return nullptr; + // fall-through, call through python + } if (TemplateProxy_Check(pyobject)) { @@ -2854,8 +2854,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, void* fptr = (void*)Cppyy::GetFunctionAddress(cppmeth, false); if (fptr) return fptr; } - // FIXME: Should we fall-through, with calling through Python - return nullptr; + // fall-through, call through Python } if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) { From ebf15623b36f338af48c3577131e4c46a2298407 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 22 May 2026 01:19:27 +0200 Subject: [PATCH 28/45] [CPyCppyy] Match function-pointer overloads via TCppType_t [upstream] --- .../pyroot/cppyy/CPyCppyy/src/CPPMethod.h | 4 + .../pyroot/cppyy/CPyCppyy/src/Converters.cxx | 100 +++++++++--------- bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h | 10 ++ .../cppyy/CPyCppyy/src/DeclareConverters.h | 14 +-- .../pyroot/cppyy/CPyCppyy/src/PyCallable.h | 3 + 5 files changed, 77 insertions(+), 54 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h index e9558f5c6869b..00a0888f3cab3 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.h @@ -73,6 +73,10 @@ class CPPMethod : public PyCallable { int GetArgMatchScore(PyObject* args_tuple) override; + bool IsSimilarFnType(Cppyy::TCppType_t fn_type) override { + return Cppyy::IsSimilarFnTypes(fn_type, Cppyy::GetTypeFromScope(fMethod)); + } + public: PyObject* Call(CPPInstance*& self, CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) override; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index 9f97923444dde..b14283f78850d 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -1,5 +1,6 @@ // Bindings #include "CPyCppyy.h" +#include "Cppyy.h" #include "DeclareConverters.h" #include "CallContext.h" #include "CPPExcInstance.h" @@ -29,6 +30,7 @@ #include #include #include +#include #if __cplusplus >= 202002L #include #endif @@ -59,9 +61,6 @@ namespace CPyCppyy { // special objects extern PyObject* gNullPtrObject; extern PyObject* gDefaultObject; - -// regular expression for matching function pointer - static std::regex s_fnptr("\\((\\w*:*)*\\*&*\\)"); } // Define our own PyUnstable_Object_IsUniqueReferencedTemporary function if the @@ -2779,6 +2778,23 @@ static std::map> sWrapperLookup; static std::map> sWrapperWeakRefs; static std::map sWrapperReference; +static void GetSignatureFromFnType(Cppyy::TCppType_t fn_type, std::string& ret, std::string& sig) { + std::vector types; + Cppyy::GetFnTypeSig(fn_type, types); + assert(types.size() >= 1); + ret = Cppyy::GetTypeAsString(types[0]); + sig = "("; + bool f = false; + for (size_t i = 1; i < types.size(); i++) { + if (f) + sig += ", "; + else + f = true; + sig += Cppyy::GetTypeAsString(types[i]); + } + sig += ")"; +} + static PyObject* WrapperCacheEraser(PyObject*, PyObject* pyref) { auto ipos = sWrapperWeakRefs.find(pyref); @@ -2807,18 +2823,12 @@ static PyMethodDef gWrapperCacheEraserMethodDef = { }; static void* PyFunction_AsCPointer(PyObject* pyobject, - const std::string& rettype, const std::string& signature, bool allowCppInstance) + const Cppyy::TCppType_t fn_type, bool allowCppInstance) { // Convert a bound C++ function pointer or callable python object to a C-style // 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)) { @@ -2828,18 +2838,12 @@ 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 = true_signature == CPyCppyy_PyText_AsString(sig); - Py_DECREF(sig); - if (found) { + if (m->IsSimilarFnType(fn_type)) { void* fptr = (void*)m->GetFunctionAddress(); if (fptr) return fptr; - break; // fall-through, with calling through Python } } - // FIXME: maybe we should try BestOverloadFunctionMatch before failing - // fall-through, call through python - + return nullptr; } if (TemplateProxy_Check(pyobject)) { @@ -2849,12 +2853,15 @@ 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, true_signature); + std::string ret{}, sig{}; + GetSignatureFromFnType(fn_type, ret, sig); + Cppyy::TCppMethod_t cppmeth = + Cppyy::GetMethodTemplate(scope, fullname, sig.substr(1, sig.size() - 2)); if (cppmeth) { void* fptr = (void*)Cppyy::GetFunctionAddress(cppmeth, false); if (fptr) return fptr; } - // fall-through, call through Python + return nullptr; } if (PyObject_IsInstance(pyobject, (PyObject*)GetCTypesType(ct_c_funcptr))) { @@ -2870,8 +2877,12 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, // function pointers, but only to std::function. void* wpraddress = nullptr; + std::string ret{}, sig{}; + GetSignatureFromFnType(fn_type, ret, sig); + sig = "(void)" == sig ? "()" : sig; + // re-use existing wrapper if possible - auto key = rettype+true_signature; + auto key = ret+sig; const auto& lookup = sWrapperLookup.find(key); if (lookup != sWrapperLookup.end()) { const auto& existing = lookup->second.find(pyobject); @@ -2899,7 +2910,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, return nullptr; // extract argument types - const std::vector& argtypes = TypeManip::extract_arg_types(true_signature); + const std::vector& argtypes = TypeManip::extract_arg_types(sig); int nArgs = (int)argtypes.size(); // wrapper name @@ -2909,7 +2920,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, // build wrapper function code std::ostringstream code; code << "namespace __cppyy_internal {\n " - << rettype << " " << wname.str() << "("; + << ret << " " << wname.str() << "("; for (int i = 0; i < nArgs; ++i) { code << argtypes[i] << " arg" << i; if (i != nArgs-1) code << ", "; @@ -2918,7 +2929,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, << " CPyCppyy::PythonGILRAII python_gil_raii;\n"; // start function body - Utility::ConstructCallbackPreamble(rettype, argtypes, code); + Utility::ConstructCallbackPreamble(ret, argtypes, code); // create a referenceable pointer PyObject** ref = new PyObject*{pyobject}; @@ -2933,7 +2944,7 @@ static void* PyFunction_AsCPointer(PyObject* pyobject, " else PyErr_SetString(PyExc_TypeError, \"callable was deleted\");\n"; // close - Utility::ConstructCallbackReturn(rettype, nArgs, code); + Utility::ConstructCallbackReturn(ret, nArgs, code); // end of namespace code << "}"; @@ -2973,7 +2984,7 @@ bool CPyCppyy::FunctionPointerConverter::SetArg( } // normal case, get a function pointer - void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); + void* fptr = PyFunction_AsCPointer(pyobject, fFnType, fAllowCppInstance); if (fptr) { SetLifeLine(ctxt->fPyContext, pyobject, (intptr_t)this); para.fValue.fVoidp = fptr; @@ -2989,8 +3000,11 @@ PyObject* CPyCppyy::FunctionPointerConverter::FromMemory(void* address) // A function pointer in clang is represented by a Type, not a FunctionDecl and it's // not possible to get the latter from the former: the backend will need to support // both. Since that is far in the future, we'll use a std::function instead. - if (address) - return Utility::FuncPtr2StdFunction(fRetType, fSignature, *(void**)address); + if (address) { + std::string ret{}, sig{}; + GetSignatureFromFnType(fFnType, ret, sig); + return Utility::FuncPtr2StdFunction(ret, sig, *(void**)address); + } PyErr_SetString(PyExc_TypeError, "can not convert null function pointer"); return nullptr; } @@ -3005,7 +3019,7 @@ bool CPyCppyy::FunctionPointerConverter::ToMemory( } // normal case, get a function pointer - void* fptr = PyFunction_AsCPointer(pyobject, fRetType, fSignature, fAllowCppInstance); + void* fptr = PyFunction_AsCPointer(pyobject, fFnType, fAllowCppInstance); if (fptr) { SetLifeLine(ctxt, pyobject, (intptr_t)address); *((void**)address) = fptr; @@ -3518,7 +3532,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim auto sz1 = pos1-pos-14; if (resolvedType[pos+14+sz1-1] == ' ') sz1 -= 1; - return new StdFunctionConverter(cnv, + return new StdFunctionConverter(cnv, Cppyy::GetType(fullType), resolvedType.substr(pos+14, sz1), resolvedType.substr(pos1, pos2-pos1+1)); } else if (cnv->HasState()) delete cnv; @@ -3574,13 +3588,10 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(const std::string& fullType, cdim result = selectInstanceCnv(klass, cpd, dims, isConst, control); } } else { - std::smatch sm; - if (std::regex_search(resolvedType, sm, s_fnptr)) { - // this is a function pointer - auto pos1 = sm.position(0); - auto pos2 = resolvedType.rfind(')'); - result = new FunctionPointerConverter( - resolvedType.substr(0, pos1), resolvedType.substr(pos1+sm.length(), pos2-1)); + Cppyy::TCppType_t typ = Cppyy::GetType(fullType); + if (Cppyy::IsFunctionType(typ)) { + // this is a function pointer or reference + result = new FunctionPointerConverter(typ); } } const std::string failure_msg("Failed to convert type: " + fullType + "; resolved: " + resolvedType + "; real: " + realType + "; cpd: " + cpd); @@ -3746,7 +3757,7 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d if (resolvedTypeStr[pos+14+sz1-1] == ' ') sz1 -= 1; const std::string &argsStr = resolvedTypeStr.substr(pos1, pos2-pos1+1).c_str(); - return new StdFunctionConverter(cnv, + return new StdFunctionConverter(cnv, resolvedType, resolvedTypeStr.substr(pos+14, sz1), argsStr == "(void)"? "()" : argsStr); } else if (cnv->HasState()) delete cnv; @@ -3781,16 +3792,9 @@ CPyCppyy::Converter* CPyCppyy::CreateConverter(Cppyy::TCppType_t type, cdims_t d // 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)); + if (Cppyy::IsFunctionType(resolvedType)) { + // this is a function function pointer or reference + result = new FunctionPointerConverter(resolvedType); } 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}; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h index 057223b70c876..55d7a63aab83d 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Cppyy.h @@ -284,6 +284,16 @@ namespace Cppyy { CPPYY_IMPORT std::string GetMethodArgDefault(TCppMethod_t, TCppIndex_t iarg); CPPYY_IMPORT + bool IsFunctionType(TCppType_t typ); + CPPYY_IMPORT + TCppType_t GetFnTypeFromStdFn(TCppType_t fn_type); + CPPYY_IMPORT + void GetFnTypeSig(TCppType_t fn_type, std::vector& arg_types); + CPPYY_IMPORT + bool IsSameType(TCppType_t typ1, TCppType_t typ2); + CPPYY_IMPORT + bool IsSimilarFnTypes(TCppType_t typ1, TCppType_t typ2); + CPPYY_IMPORT std::string GetMethodSignature(TCppMethod_t, bool show_formal_args, TCppIndex_t max_args = (TCppIndex_t)-1); CPPYY_IMPORT std::string GetMethodPrototype(TCppMethod_t, bool show_formal_args); diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h index 505a01e852532..a5f53184eb418 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/DeclareConverters.h @@ -3,6 +3,7 @@ // Bindings #include "Converters.h" +#include "Cppyy.h" #include "Dimensions.h" // Standard @@ -418,8 +419,8 @@ class STLStringMoveConverter : public STLStringConverter { // function pointers class FunctionPointerConverter : public Converter { public: - FunctionPointerConverter(const std::string& ret, const std::string& sig) : - fRetType(ret), fSignature(sig) {} + FunctionPointerConverter(Cppyy::TCppType_t FnType) : + fFnType(FnType) {} public: bool SetArg(PyObject*, Parameter&, CallContext* = nullptr) override; @@ -429,16 +430,15 @@ class FunctionPointerConverter : public Converter { virtual std::string GetFailureMsg() { return "[FunctionPointerConverter]"; }; protected: - std::string fRetType; - std::string fSignature; + Cppyy::TCppType_t fFnType; bool fAllowCppInstance = false; }; // std::function class StdFunctionConverter : public FunctionPointerConverter { public: - StdFunctionConverter(Converter* cnv, const std::string& ret, const std::string& sig) : - FunctionPointerConverter(ret, sig), fConverter(cnv) { + StdFunctionConverter(Converter* cnv, Cppyy::TCppType_t fn, const std::string& ret, const std::string& sig) : + FunctionPointerConverter(Cppyy::GetFnTypeFromStdFn(fn)), fRetType(ret), fSignature(sig), fConverter(cnv) { fAllowCppInstance = true; } StdFunctionConverter(const StdFunctionConverter&) = delete; @@ -452,6 +452,8 @@ class StdFunctionConverter : public FunctionPointerConverter { virtual std::string GetFailureMsg() { return "[StdFunctionConverter]"; }; protected: + std::string fRetType; + std::string fSignature; Converter* fConverter; }; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h b/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h index d2a072c5b389e..21ff9c5d85037 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/PyCallable.h @@ -6,6 +6,7 @@ // Bindings #include "CPyCppyy/Reflex.h" #include "CallContext.h" +#include "Cppyy.h" namespace CPyCppyy { @@ -44,6 +45,8 @@ class PyCallable { virtual int GetArgMatchScore(PyObject* /* args_tuple */) { return INT_MAX; } + virtual bool IsSimilarFnType([[maybe_unused]] Cppyy::TCppType_t fn_type) { return false; } + public: virtual PyObject* Call(CPPInstance*& self, CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, CallContext* ctxt = nullptr) = 0; From 52a9b045735cabaa4d3ad661da40d53da53fbd69 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:54:19 +0200 Subject: [PATCH 29/45] [CPyCppyy] declare gException, drop CPYCPPYY_IMPORT from CPPInstance [ROOT-patch] Source: ROOT - SignalTryCatch.h: gException resolved to definition from libCore.so - CPPInstance.h: replace CPYCPPYY_IMPORT with explicit extern / __declspec(dllimport) pair. Used by libROOTPythonizations which does not include CommonDefs.h for the definition of CPYCPPYY_IMPORT --- bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h | 7 ++++++- bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h | 11 ++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h index 2725116229723..9ac6e3c78ac55 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPInstance.h @@ -130,7 +130,12 @@ inline Cppyy::TCppScope_t CPPInstance::ObjectIsA(bool check_smart) const #endif //- object proxy type and type verification ---------------------------------- -CPYCPPYY_IMPORT PyTypeObject CPPInstance_Type; +// 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 #ifndef Py_LIMITED_API template diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h b/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h index 14742b894099d..453d8eb6ee388 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h +++ b/bindings/pyroot/cppyy/CPyCppyy/src/SignalTryCatch.h @@ -40,10 +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) #else @@ -75,6 +71,11 @@ static CppyyExceptionContext_t *gException = &DummyException; gException = R__old; \ } -CPYCPPYY_IMPORT CppyyExceptionContext_t *gException; +// extern, defined in ROOT Core +#ifdef _MSC_VER +extern __declspec(dllimport) CppyyExceptionContext_t *gException; +#else +extern CppyyExceptionContext_t *gException; +#endif #endif From 84e8467e40a09d80fbe048ea4ee0ceff4a369ca3 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:38:03 +0200 Subject: [PATCH 30/45] [CPyCppyy] Add more converter/executor name aliases [ROOT-patch] --- bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx | 14 ++++++++++++++ bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx | 4 ++++ 2 files changed, 18 insertions(+) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx index b14283f78850d..105f91bb9b6d3 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Converters.cxx @@ -4048,8 +4048,11 @@ 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"]; gf["const std::int8_t&"] = gf["const int8_t&"]; gf["std::int8_t&"] = gf["int8_t&"]; @@ -4121,6 +4124,17 @@ static struct InitConvFactories_t { gf["const char*[]"] = (cf_t)+[](cdims_t d) { return new CStringArrayConverter{d, false}; }; gf["char*[]"] = (cf_t)+[](cdims_t d) { return new NonConstCStringArrayConverter{d, false}; }; gf["char ptr"] = gf["char*[]"]; + gf["std::string"] = (cf_t)+[](cdims_t) { return new STLStringConverter{}; }; + gf["const std::string&"] = gf["std::string"]; + gf["string"] = gf["std::string"]; + gf["const string&"] = gf["std::string"]; + gf["std::string&&"] = (cf_t)+[](cdims_t) { return new STLStringMoveConverter{}; }; + gf["string&&"] = gf["std::string&&"]; + gf["std::string_view"] = (cf_t)+[](cdims_t) { return new STLStringViewConverter{}; }; + gf[STRINGVIEW] = gf["std::string_view"]; + gf["std::string_view&"] = gf["std::string_view"]; + gf["const std::string_view&"] = gf["std::string_view"]; + gf["const " STRINGVIEW "&"] = gf["std::string_view"]; 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{}; }; diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx index fafc22293c00c..a0024ab2d7012 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx @@ -1160,6 +1160,8 @@ struct InitExecFactories_t { gf["const unsigned char ptr"] = gf["unsigned char ptr"]; gf["std::byte ptr"] = (ef_t)+[](cdims_t d) { return new ByteArrayExecutor{d}; }; gf["const std::byte ptr"] = gf["std::byte ptr"]; + gf["byte ptr"] = gf["std::byte ptr"]; + gf["const byte ptr"] = gf["std::byte ptr"]; gf["int8_t ptr"] = (ef_t)+[](cdims_t d) { return new Int8ArrayExecutor{d}; }; gf["uint8_t ptr"] = (ef_t)+[](cdims_t d) { return new UInt8ArrayExecutor{d}; }; gf["short ptr"] = (ef_t)+[](cdims_t d) { return new ShortArrayExecutor{d}; }; @@ -1183,7 +1185,9 @@ 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"]; From caed0a4af19a013bb2aa813ea24d1842318da32c Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:46:53 +0200 Subject: [PATCH 31/45] [CPyCppyy] Rename PyInit_libcppyy to Init for ROOT module split [ROOT-patch] Source: ROOT-master ROOT runs CPyCppyy and libROOTPythonizations as two shared libs that under the same libcppyy Python extension module. The real PyInit_libcppyy lives in libROOTPythonizations and calls CPyCppyy::Init() to start the extension. --- bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx index 3ab4f8b47e7e1..083bff270370e 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPyCppyyModule.cxx @@ -1079,7 +1079,7 @@ static struct PyModuleDef moduledef = { namespace CPyCppyy { //---------------------------------------------------------------------------- -extern "C" PyObject* PyInit_libcppyy() +PyObject* Init() { // Initialization of extension module libcppyy. From 741a8d4e4a9a067f955f667921831e4ab23c6d62 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 24 Apr 2026 16:42:37 +0200 Subject: [PATCH 32/45] [CPyCppyy] Pythonize: add std::span support and no-std wstring aliases [ROOT-patch] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Source: ROOT-master - Register a std::span pythonization that overrides begin() / end() with a JIT-compiled __cppyy_internal::ptr_iterator helper. libstdc++ (GCC >= 15) implements std::span::iterator via a private nested tag type, which CallFunc-generated wrappers cannot name without violating access rules — the pointer-based iterator sidesteps that - Add more std::basic_string name variants to the STLWString pythonization --- .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 123 ++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index 97598ad76b2b2..5f55c96faba29 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -314,6 +314,111 @@ static ItemGetter* GetGetter(PyObject* args) return getter; } +namespace { + +void compileSpanHelpers() +{ + static bool compiled = false; + + if (compiled) + return; + + compiled = true; + + auto code = R"( +namespace __cppyy_internal { + +template +struct ptr_iterator { + T *cur; + T *end; + + ptr_iterator(T *c, T *e) : cur(c), end(e) {} + + T &operator*() const { return *cur; } + ptr_iterator &operator++() + { + ++cur; + return *this; + } + bool operator==(const ptr_iterator &other) const { return cur == other.cur; } + bool operator!=(const ptr_iterator &other) const { return !(*this == other); } +}; + +template +ptr_iterator make_iter(T *begin, T *end) +{ + return {begin, end}; +} + +} // namespace __cppyy_internal + +// Note: for const span, T is const-qualified here +template +auto __cppyy_internal_begin(T &s) noexcept +{ + return __cppyy_internal::make_iter(s.data(), s.data() + s.size()); +} + +// Note: for const span, T is const-qualified here +template +auto __cppyy_internal_end(T &s) noexcept +{ + // end iterator = begin iterator with cur == end + return __cppyy_internal::make_iter(s.data() + s.size(), s.data() + s.size()); +} + )"; + Cppyy::Compile(code, /*silent*/ true); +} + +PyObject *spanBegin() +{ + static PyObject *pyFunc = nullptr; + if (!pyFunc) { + compileSpanHelpers(); + 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 " + "'__cppyy_internal_begin' for std::span pythonization"); + } + } + return pyFunc; +} + +PyObject *spanEnd() +{ + static PyObject *pyFunc = nullptr; + if (!pyFunc) { + compileSpanHelpers(); + 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 " + "'__cppyy_internal_end' for std::span pythonization"); + } + } + return pyFunc; +} + +} // namespace + +static PyObject *SpanBegin(PyObject *self, PyObject *) +{ + auto begin = spanBegin(); + if (!begin) + return nullptr; + return PyObject_CallOneArg(begin, self); +} + +static PyObject *SpanEnd(PyObject *self, PyObject *) +{ + auto end = spanEnd(); + if (!end) + return nullptr; + return PyObject_CallOneArg(end, self); +} + static bool FillVector(PyObject* vecin, PyObject* args, ItemGetter* getter) { Py_ssize_t sz = getter->size(); @@ -1833,6 +1938,18 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) //- class name based pythonization ------------------------------------------- + if (IsTemplatedSTLClass(name, "span")) { + // libstdc++ (GCC >= 15) implements std::span::iterator using a private + // nested tag type, which makes the iterator non-instantiable by + // CallFunc-generated wrappers (the return type cannot be named without + // violating access rules). + // + // To preserve correct Python iteration semantics, we replace begin()/end() + // for std::span to return a custom pointer-based iterator instead. + Utility::AddToClass(pyclass, "begin", (PyCFunction)SpanBegin, METH_NOARGS); + Utility::AddToClass(pyclass, "end", (PyCFunction)SpanEnd, METH_NOARGS); + } + if (IsTemplatedSTLClass(name, "vector")) { // std::vector is a special case in C++ @@ -1974,8 +2091,14 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) Utility::AddToClass(pyclass, "__str__", (PyCFunction)STLViewStringStr, METH_NOARGS); } +// The first condition was already present in upstream CPyCppyy. The others +// are special to ROOT, because its reflection layer gives us the types without +// the "std::" namespace. On some platforms, that applies only to the template +// arguments, and on others also to the "basic_string". else if (name == "std::basic_string,std::allocator >" || name == "std::basic_string, std::allocator >" || + name == "basic_string,allocator >" || + name == "std::basic_string,allocator >" || name == "std::__1::basic_string,std::__1::allocator >" || name == "std::wstring") { Utility::AddToClass(pyclass, "__repr__", (PyCFunction)STLWStringRepr, METH_NOARGS); From 9cc6267b8b506271ca864b656fcbb1fa604013ab Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Thu, 23 Apr 2026 17:09:38 +0200 Subject: [PATCH 33/45] [CPyCppyy] Add __template_args__ and signature-plus-template overload [ROOT-patch] Source: ROOT - __template_args__ read-only property on TemplateProxy for introspection of a method's template arguments - "ss:__overload__" branch in tpp_overload so callers can select an overload by both signature and template arguments Added for ROOT's Numba-introspection support. --- .../cppyy/CPyCppyy/src/TemplateProxy.cxx | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx index bbf5e83832ebc..9cd1adbe1e89a 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/TemplateProxy.cxx @@ -747,6 +747,21 @@ 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 = { @@ -757,7 +772,9 @@ 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*)nullptr, nullptr, nullptr, nullptr, nullptr} + {(char*)"__template_args__", (getter)tpp_gettemplateargs, (setter)tpp_settemplateargs, + (char*)"the template arguments for this method", nullptr}, + {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}, }; @@ -788,6 +805,7 @@ 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; @@ -818,6 +836,11 @@ 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; From cf843bc1d7b25a0ec5ba4571c7ff1eb75186d51b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 1 May 2026 18:02:09 +0200 Subject: [PATCH 34/45] [CPyCppyy] Always convert returned to Python string [ROOT-patch] --- .../pyroot/cppyy/CPyCppyy/src/Executors.cxx | 24 +++++++------------ .../pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 5 +++- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx index a0024ab2d7012..8e41840b08738 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Executors.cxx @@ -391,17 +391,9 @@ 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) { - 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; + return CPyCppyy_PyText_FromStringAndSize(result->c_str(), result->size()); } *result = std::string( @@ -569,14 +561,16 @@ PyObject* CPyCppyy::STLStringExecutor::Execute( // TODO: make use of GILLCallS (?!) static Cppyy::TCppScope_t sSTLStringScope = Cppyy::GetFullScope("std::string"); std::string* result = (std::string*)GILCallO(method, self, ctxt, sSTLStringScope); - if (!result) - result = new std::string{}; - else if (PyErr_Occurred()) { - delete result; - return nullptr; + if (!result) { + Py_INCREF(PyStrings::gEmptyString); + return PyStrings::gEmptyString; } - return BindCppObjectNoCast((void*)result, sSTLStringScope, CPPInstance::kIsOwner); + 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; } //---------------------------------------------------------------------------- diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index 5f55c96faba29..f762950cb7ea5 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -1469,7 +1469,7 @@ PyObject* STLStringGetAttr(CPPInstance* self, PyObject* attr_name) return attr; } - +#if 0 PyObject* UTF8Repr(PyObject* self) { // force C++ string types conversion to Python str per Python __repr__ requirements @@ -1480,6 +1480,7 @@ PyObject* UTF8Repr(PyObject* self) Py_DECREF(res); return str_res; } +#endif PyObject* UTF8Str(PyObject* self) { @@ -1847,6 +1848,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) 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,6 +1860,7 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) 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) { // create a pseudo-constructor to allow initializer-style object creation From 5e0d09004b6908155de81a7201b0a925a22e3b5b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sun, 3 May 2026 10:41:48 +0200 Subject: [PATCH 35/45] [CPyCppyy] Fix false addition of STLSequenceIter for pointer return-type iterators [ROOT-patch] Pythonize.cxx tags begin()-returns as STL iterator types when Cppyy::GetScope succeeds on the return-type string. CppInterOp's GetScope returns the underlying class scopes, leading to raw-pointer iterators (e.g. RVec::iterator = T*) falsely classified as STL iterator types. Restore the earlier behaviour of skipping pointer return values by doing a IsPointerType check on the type-handle. ROOT master's TCling-based GetScope returned null for pointer names and did not hit this false positive. --- bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx index f762950cb7ea5..aee9aa30727c7 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/Pythonize.cxx @@ -1780,7 +1780,16 @@ bool CPyCppyy::Pythonize(PyObject* pyclass, Cppyy::TCppScope_t scope) 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)) { + // skip pointer return values. GetScope template parsing strips a + // trailing '*' and returns the underlying class scope. Cppyy::GetType + // preserves pointer qualifiers, so for raw-pointer begin() returns + // (e.g. RVec::iterator = T*), do not add STLSequenceIter. + // ROOT master's TCling-based GetScope returns null for + // pointer names and never hit this false positive. + Cppyy::TCppType_t restype = !resname.empty() + ? Cppyy::GetType(resname, /*enable_slow_lookup=*/true) : 0; + if (!isIterator && restype && !Cppyy::IsPointerType(restype) + && Cppyy::GetScope(resname)) { if (resname.find("iterator") == std::string::npos) gIteratorTypes.insert(resname); isIterator = true; From 781f80870fd470bb97c2a8fbe2b91eaab05a99bb Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sat, 2 May 2026 11:05:10 +0200 Subject: [PATCH 36/45] [cppyy] Update test std.string type checks to Python str [ROOT-patch] 5cfc2dafe38 changed std::string-returning methods to always produce Python str instead of a CPPInstance proxy (gbl.std.string). Update test18_operator_plus_overloads and test34_cstring_template_argument asserts expecting gbl.std.string --- bindings/pyroot/cppyy/cppyy/test/test_regression.py | 3 ++- bindings/pyroot/cppyy/cppyy/test/test_templates.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_regression.py b/bindings/pyroot/cppyy/cppyy/test/test_regression.py index b006195a9cdd7..a7b2846de8ae8 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_regression.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_regression.py @@ -485,7 +485,8 @@ def test18_operator_plus_overloads(self): assert a == 'a' assert b == 'b' - assert type(a+b) == cppyy.gbl.std.string + # patched to expect str instead of cppyy.gbl.std.string, due to ROOT patch + assert type(a+b) == str assert a+b == 'ab' def test19_std_string_hash(self): diff --git a/bindings/pyroot/cppyy/cppyy/test/test_templates.py b/bindings/pyroot/cppyy/cppyy/test/test_templates.py index 51ca569e1559e..8f42cc2fdd6da 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_templates.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_templates.py @@ -1160,7 +1160,8 @@ def test34_cstring_template_argument(self): ns = cppyy.gbl.CStringTemplateArg - assert type(ns.stringify("Alice")) == cppyy.gbl.std.string + # patched expect str instead of cppyy.gbl.std.string, due to ROOT patch + assert type(ns.stringify("Alice")) == str assert ns.stringify("Alice", "Bob") == "Alice Bob " assert ns.stringify(1, 2, 3) == "1 2 3 " assert ns.stringify["const char*"]("Aap") == "Aap " From 4a302928877fea0117483a4b453b64b43dce92a3 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Sun, 26 Apr 2026 14:21:25 +0200 Subject: [PATCH 37/45] [cppyy] Newly enabled tests in ROOT by CppInterOp [ROOT-patch] --- .../pyroot/cppyy/cppyy/test/test_advancedcpp.py | 3 --- bindings/pyroot/cppyy/cppyy/test/test_concurrent.py | 3 --- .../pyroot/cppyy/cppyy/test/test_cpp11features.py | 3 --- .../cppyy/cppyy/test/test_crossinheritance.py | 3 --- bindings/pyroot/cppyy/cppyy/test/test_datatypes.py | 13 +------------ .../pyroot/cppyy/cppyy/test/test_doc_features.py | 2 -- bindings/pyroot/cppyy/cppyy/test/test_eigen.py | 1 - bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py | 2 -- bindings/pyroot/cppyy/cppyy/test/test_numba.py | 10 +++++----- bindings/pyroot/cppyy/cppyy/test/test_operators.py | 1 - bindings/pyroot/cppyy/cppyy/test/test_regression.py | 6 ------ bindings/pyroot/cppyy/cppyy/test/test_stltypes.py | 6 ++---- bindings/pyroot/cppyy/cppyy/test/test_templates.py | 10 ---------- 13 files changed, 8 insertions(+), 55 deletions(-) diff --git a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py b/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py index 03307937c8ab1..e5d750c31e90d 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_advancedcpp.py @@ -770,7 +770,6 @@ def test23_using(self): assert d2.vcheck() == 'A' assert d2.vcheck(1) == 'B' - @mark.xfail(strict=True) def test24_typedef_to_private_class(self): """Typedefs to private classes should not resolve""" @@ -778,7 +777,6 @@ def test24_typedef_to_private_class(self): assert cppyy.gbl.TypedefToPrivateClass().f().m_val == 42 - @mark.xfail(strict=True) def test25_ostream_printing(self): """Mapping of __str__ through operator<<(ostream&)""" @@ -876,7 +874,6 @@ def test27_shadowed_typedef(self): #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""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py b/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py index 748c8fae4533c..224583156f18b 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_concurrent.py @@ -22,7 +22,6 @@ def setup_class(cls): cppyy.gbl.Workers.calc.__release_gil__ = True - @mark.xfail(run=False, reason="Crashes because TClingCallFunc generates wrong code") def test01_simple_threads(self): """Run basic Python threads""" @@ -41,7 +40,6 @@ def test01_simple_threads(self): for t in threads: t.join() - @mark.xfail(run=False, reason="Crashes because the interpreter emits too many warnings") def test02_futures(self): """Run with Python futures""" @@ -262,7 +260,6 @@ def test(): for t in threads: t.join() - @mark.xfail(run=False, reason="segmentation violation") def test07_overload_reuse_in_threads_wo_gil(self): """Threads reuse overload objects; check for clashes if no GIL""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py b/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py index f659a59437356..52b70ac7f5365 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_cpp11features.py @@ -300,7 +300,6 @@ 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) def test09_lambda_calls(self): """Call (global) lambdas""" @@ -357,7 +356,6 @@ 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) def test12_stdfunction(self): """Use of std::function with arguments in a namespace""" @@ -545,7 +543,6 @@ def test18_unique_ptr_identity(self): p2 = c.pget() assert p1 is p2 - @mark.xfail(strict=True) 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 c9f30c0295924..c4483a6aa68e2 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_crossinheritance.py @@ -476,7 +476,6 @@ class MyPyDerived3(VD.MyClass3): class MyPyDerived4(VD.MyClass4[int]): pass - @mark.xfail(strict=True) def test14_protected_access(self): """Derived classes should have access to protected members""" @@ -1021,7 +1020,6 @@ def return_const(self): assert a.return_const().m_value == "abcdef" assert ns.callit(a).m_value == "abcdef" - @mark.xfail(strict=True) def test24_non_copyable(self): """Inheriting from a non-copyable base class""" @@ -1779,7 +1777,6 @@ def f3(self): assert pysub.f3() == "Python: PySub::f3()" assert ns.call_fs(pysub) == pysub.f1() + pysub.f2() + pysub.f3() - @mark.xfail(strict=True) def test38_protected_data(self): """Multiple cross inheritance with protected data""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py b/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py index a26d5892ae72a..28d6fcc673656 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_datatypes.py @@ -19,7 +19,6 @@ def setup_class(cls): cls.datatypes = cppyy.load_reflection_info(cls.test_dct) cls.N = cppyy.gbl.N - @mark.xfail(strict=True) def test01_instance_data_read_access(self): """Read access to instance public data and verify values""" @@ -190,7 +189,6 @@ def test01_instance_data_read_access(self): c.__destruct__() - @mark.xfail(strict=True) def test02_instance_data_write_access(self): """Test write access to instance public data and verify values""" @@ -727,7 +725,6 @@ def test10_enum(self): assert gbl.EnumSpace.AA == 1 assert gbl.EnumSpace.BB == 2 - @mark.xfail(strict=True) def test11_typed_enums(self): """Determine correct types of enums""" @@ -770,7 +767,6 @@ class Test { assert type(sc.vraioufaux.faux) == bool # no bool as base class assert isinstance(sc.vraioufaux.faux, bool) - @mark.xfail(strict=True) def test12_enum_scopes(self): """Enum accessibility and scopes""" @@ -1096,7 +1092,6 @@ def test21_object_validity(self): assert not d2 - @mark.xfail(strict=True) def test22_buffer_shapes(self): """Correctness of declared buffer shapes""" @@ -1260,7 +1255,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(condition=IS_MAC, 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""" @@ -1539,7 +1534,6 @@ 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) def test31_anonymous_union(self): """Anonymous unions place there fields in the parent scope""" @@ -1633,7 +1627,6 @@ def test31_anonymous_union(self): assert type(p.data_c[0]) == float assert p.intensity == 5. - @mark.xfail(strict=True) def test32_anonymous_struct(self): """Anonymous struct creates an unnamed type""" @@ -1682,7 +1675,6 @@ class Foo2 { assert 'foo' in dir(ns.libuntitled1_ExportedSymbols().kotlin.root.com.justamouse.kmmdemo) - @mark.xfail(strict=True) def test33_pointer_to_array(self): """Usability of pointer to array""" @@ -2043,7 +2035,6 @@ def test40_more_aggregates(self, capfd): output = (captured.out + captured.err).lower() assert "error:" not in output - @mark.xfail(strict=True) def test41_complex_numpy_arrays(self, capfd): """Usage of complex numpy arrays""" @@ -2227,7 +2218,6 @@ def test45_const_ref_data(self): b = ns.B() assert b.body1.name == b.body2.name - @mark.xfail(strict=True) def test46_small_int_enums(self): """Proper typing of small int enums""" @@ -2282,7 +2272,6 @@ def test46_small_int_enums(self): assert ns.func_int8() == -1 assert ns.func_uint8() == 255 - @mark.xfail(strict=True) def test47_hidden_name_enum(self): """Usage of hidden name enum""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py b/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py index e4f6d01bc36b8..0f0cbec19e59f 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_doc_features.py @@ -1123,7 +1123,6 @@ def add(self, i): assert v.back().add(17) == 4+42+2*17 - @mark.xfail(strict=True) def test_fallbacks(self): """Template instantation switches based on value sizes""" @@ -1168,7 +1167,6 @@ 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) def test_templated_callback(self): """Templated callback example""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_eigen.py b/bindings/pyroot/cppyy/cppyy/test/test_eigen.py index 58aecc872f1eb..f9ac74b6a5de9 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_eigen.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_eigen.py @@ -100,7 +100,6 @@ def test02_comma_insertion(self): for i in range(5): assert v(i) == i+1 - @mark.xfail(strict=True) def test03_matrices_and_vectors(self): """Matrices and vectors""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py b/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py index fa07805b29598..359da0d494526 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_lowlevel.py @@ -132,7 +132,6 @@ 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) def test06_ctypes_as_ref_and_ptr(self): """Use ctypes for pass-by-ref/ptr""" @@ -489,7 +488,6 @@ 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) 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..4398fb8bfcbce 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_numba.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_numba.py @@ -126,7 +126,6 @@ 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) def test02_JITed_template_free_func(self): """Numba-JITing of Cling-JITed templated free function""" @@ -281,7 +280,6 @@ def tma(x): assert sum == tma(x) - @mark.xfail(strict=True) def test07_datatype_mapping(self): """Numba-JITing of various data types""" @@ -659,7 +657,8 @@ def np_dot_product(x, y): assert (njit_res == res) assert (time_njit < time_np) - @mark.skipif(eigen_path is None, reason="Eigen not found") + # @mark.skipif(eigen_path is None, reason="Eigen not found") + @mark.xfail(run=False, reason="Crashes with Transaction.cpp:98: void cling::Transaction::addNestedTransaction(cling::Transaction*): Assertion `!m_Unloading && 'Must not nest within unloading transaction' failed") def test14_eigen_numba(self): """Numba-JITing of a function that uses a cppyy declared Eigen Vector""" @@ -743,7 +742,7 @@ def setup_class(cls): import cppyy import cppyy.numba_ext - @mark.xfail(strict=True) + @mark.xfail(strict=True, run=False, reason="Crashes with Transaction.cpp:98: void cling::Transaction::addNestedTransaction(cling::Transaction*): Assertion `!m_Unloading && 'Must not nest within unloading transaction' failed") def test01_templated_freefunction(self): """Numba support documentation example: free templated function""" @@ -772,7 +771,8 @@ 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(strict=True, condition=IS_WINDOWS, reason="Fails on Windows") + @mark.xfail(strict=True, run=False, reason="Crashes with Transaction.cpp:98: void cling::Transaction::addNestedTransaction(cling::Transaction*): Assertion `!m_Unloading && 'Must not nest within unloading transaction' failed") 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..04d7d18bff68d 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_operators.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_operators.py @@ -227,7 +227,6 @@ 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") def test09_templated_operator(self, capfd): """Templated operator<()""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_regression.py b/bindings/pyroot/cppyy/cppyy/test/test_regression.py index a7b2846de8ae8..5b1c9cd952cf1 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_regression.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_regression.py @@ -380,7 +380,6 @@ def test15_vector_vs_initializer_list(self): sizeit = cppyy.gbl.vec_vs_init.sizeit assert sizeit(list(range(10))) == 10 - @mark.xfail(strict=True) 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 +472,6 @@ class Derived2 : public Base { assert a != b # derived class' C++ operator!= called - @mark.xfail(strict=True) def test18_operator_plus_overloads(self): """operator+(string, string) should return a string""" @@ -784,7 +782,6 @@ def test28_exception_as_shared_ptr(self): null = cppyy.gbl.exception_as_shared_ptr.get_shared_null() assert not null - @mark.xfail(strict=True) def test29_callback_pointer_values(self, capfd): """Make sure pointer comparisons in callbacks work as expected""" @@ -894,7 +891,6 @@ def test30_uint64_t(self): assert ns.TTest(True).fT == True assert type(ns.TTest(True).fT) == bool - @mark.xfail(strict=True) def test31_enum_in_dir(self): """Failed to pick up enum data""" @@ -917,7 +913,6 @@ def test31_enum_in_dir(self): required = {'prod', 'a', 'b', 'smth', 'my_enum'} assert all_names.intersection(required) == required - @mark.xfail(strict=True) def test32_typedef_class_enum(self): """Use of class enum with typedef'd type""" @@ -955,7 +950,6 @@ def test32_typedef_class_enum(self): assert o.x == Foo.BAZ assert o.y == 1 - @mark.xfail(strict=True) def test33_explicit_template_in_namespace(self): """Lookup of explicit template in namespace""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py b/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py index eb126ff5cdd43..a9cd50bb994a4 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_stltypes.py @@ -449,6 +449,7 @@ def test10_vector_std_distance(self): assert std.distance(v.begin(), v.end()) == v.size() assert std.distance[type(v).iterator](v.begin(), v.end()) == v.size() + @mark.xfail(run=False, reason="Fatal Python error: Segmentation fault") def test11_vector_of_pair(self): """Use of std::vector""" @@ -615,7 +616,6 @@ def test17_vector_cpp17_style(self): v = cppyy.gbl.std.vector(l) assert list(l) == l - @mark.xfail(strict=True) def test18_array_interface(self): """Test usage of __array__ from numpy""" @@ -1108,7 +1108,6 @@ def EQ(result, init, methodname, *args): assert s.rfind('c') < 0 assert s.rfind('c') == s.npos - @mark.xfail(strict=True) def test10_string_in_repr_and_str_bytes(self): """Special cases for __str__/__repr__""" @@ -1937,7 +1936,6 @@ def test02_tuple_size(self): t = std.make_tuple("aap", 42, 5.) assert std.tuple_size(type(t)).value == 3 - @mark.xfail(strict=True) def test03_tuple_iter(self): """Pack/unpack tuples""" @@ -1952,7 +1950,6 @@ def test03_tuple_iter(self): assert b == '2' assert c == 5. - @mark.xfail(strict=True) def test04_tuple_lifeline(self): """Tuple memory management""" @@ -2012,6 +2009,7 @@ def setup_class(cls): cls.stltypes = cppyy.load_reflection_info(cls.test_dct) cls.N = global_n + @mark.xfail(run=False, reason="Fatal Python error: Segmentation fault") def test01_pair_pack_unpack(self): """Pack/unpack pairs""" diff --git a/bindings/pyroot/cppyy/cppyy/test/test_templates.py b/bindings/pyroot/cppyy/cppyy/test/test_templates.py index 8f42cc2fdd6da..4832f5bf9b45f 100644 --- a/bindings/pyroot/cppyy/cppyy/test/test_templates.py +++ b/bindings/pyroot/cppyy/cppyy/test/test_templates.py @@ -148,7 +148,6 @@ 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") def test06_variadic_sfinae(self, capfd): """Attribute testing through SFINAE""" @@ -327,7 +326,6 @@ def test12_template_aliases(self): assert nsup.Foo assert nsup.Bar.Foo # used to fail - @mark.xfail(strict=True) def test13_using_templated_method(self): """Access to base class templated methods through 'using'""" @@ -351,7 +349,6 @@ def test13_using_templated_method(self): assert type(d.get3()) == int assert d.get3() == 5 - @mark.xfail(strict=True) def test14_templated_return_type(self): """Use of a templated return type""" @@ -592,7 +589,6 @@ def test23_overloaded_setitem(self): v = MyVec["float"](2) v[0] = 1 # used to throw TypeError - @mark.xfail(strict=True) def test24_stdfunction_templated_arguments(self): """Use of std::function with templated arguments""" @@ -619,7 +615,6 @@ def callback(x): assert cppyy.gbl.std.function['double(std::vector)'] - @mark.xfail(strict=True) def test25_stdfunction_ref_and_ptr_args(self): """Use of std::function with reference or pointer args""" @@ -916,7 +911,6 @@ class Templated: public NonTemplated { ns.Templated() # used to crash - @mark.xfail(strict=True) def test31_ltlt_in_template_name(self): """Verify lookup of template names with << in the name""" @@ -982,7 +976,6 @@ 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) def test32_template_of_function_with_templated_args(self): """Lookup of templates of function with templated args used to fail""" @@ -1142,7 +1135,6 @@ def test33_using_template_argument(self): assert ns.testptr assert cppyy.gbl.std.vector[ns.testptr] - @mark.xfail(strict=True) def test34_cstring_template_argument(self): """`const char*` use over std::string""" @@ -1301,7 +1293,6 @@ 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) def test04_type_deduction(self): """Usage of type reducer""" @@ -1380,7 +1371,6 @@ def setup_class(cls): import cppyy cls.templates = cppyy.load_reflection_info(cls.test_dct) - @mark.xfail(strict=True) def test01_reduce_binary(self): """Squash template expressions for binary operations (like in gmpxx)""" From a3e5543c844ab9fd65bb5bcd64fffed89b697fce Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 1 May 2026 12:57:59 +0200 Subject: [PATCH 38/45] [pyroot] Exclude canonical std::string from generic pretty-printer [ROOT-patch] _generic.pythonize_generic skips the pretty-printer for std::string because ToString returns a quoted ""x"" form, and CPyCppyy already pythonizes std::string with the unquoted shape. With CppInterOp, klass.__cpp_name__ is the canonical fully-qualified template form, so the typedef "std::string" no longer matched the exclude check --- .../python/ROOT/_pythonization/_generic.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_generic.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_generic.py index 838e4592bdfb4..ca66e84c7c53c 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_generic.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_generic.py @@ -46,8 +46,14 @@ def pythonize_generic(klass, name): m = getattr(klass, "__str__", None) has_cpp_str = True if m is not None and type(m).__name__ == "CPPOverload" else False - # Exclude std::string with its own pythonization from cppyy - exclude = ["std::string"] + # Exclude std::string with its own pythonization from cppyy. With the + # CppInterOp-based backend the class name is the canonical form rather + # than the "std::string" typedef, so list both shapes. + exclude = [ + "std::string", + "std::basic_string", + "std::basic_string, std::allocator >", + ] if name not in exclude and not has_cpp_str: AddPrettyPrintingPyz(klass) From 0089c66cc1271a33daaa0c2f4450b423a81d7f43 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 22 May 2026 01:19:45 +0200 Subject: [PATCH 39/45] [Python] Pythonize templated classes cached in namespace [ROOT-patch] --- .../pythonizations/python/ROOT/_pythonization/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/__init__.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/__init__.py index b2ec9c4937273..c239956a091e8 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/__init__.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/__init__.py @@ -274,9 +274,8 @@ def get_class_name(instantiation): ns_vars = vars(ns_obj) for var_name, var_value in ns_vars.items(): - if str(var_value).startswith("" pythonize_if_match(instance_name, instance) From af0fbe80dcf1619d4ab80bac8b3a2ac141728b82 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 22 May 2026 01:20:05 +0200 Subject: [PATCH 40/45] [pyroot] Refactor RDF Filter/Define callable dispatch [ROOT-patch] --- .../python/ROOT/_pythonization/_rdf_pyz.py | 153 +++--------------- 1 file changed, 26 insertions(+), 127 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rdf_pyz.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rdf_pyz.py index 4551c6a24f60e..1801474b712ab 100755 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rdf_pyz.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_rdf_pyz.py @@ -231,139 +231,30 @@ def jit_function(self, func, cols_list, extra_args): return self.func_call -def remove_fn_name_from_signature(signature): - """ - Removes the function name from a signature string. - The signature is expected to be in the form of "return_type function_name(type param1, type param2, ...)". - """ - if "(" not in signature or ")" not in signature: - raise ValueError(f"Invalid signature format: {signature}") - - return signature[: signature.index(" ")] + signature[signature.index("(") :] - - -def get_cpp_overload_from_templ_proxy(func, types=None): - """ - Gets the C++ overload from a cppyy TemplateProxy using the input - and template arguments types. - """ - signature = ", ".join(types) if types else "" - template_args = func.__template_args__[1:-1] if func.__template_args__ else "" - return func.__overload__(signature, template_args) - - def get_column_types(rdf, cols): return [rdf.GetColumnType(col) for col in cols] -def get_overload_based_on_args(overload_types, types): - """ - Gets the C++ overloads for a function based on the provided signatures and types. - """ - if not isinstance(overload_types, dict): - raise TypeError("Overload types must be a dictionary.") - - if not isinstance(types, list) or not all(isinstance(t, str) for t in types): - raise TypeError("Types must be a list of strings.") - - if len(overload_types) == 1: - # If there is only one signature, return it directly - return next(iter(overload_types)) - - for full_sig, info in overload_types.items(): - input_types = info.get("input_types", ()) - - if len(input_types) != len(types): - continue - - match = True - for expected, actual in zip(input_types, types): - # Loose match: require actual to appear somewhere in expected (e.g. "int" in "const int&") - if actual not in expected: - match = False - break - - if match: - return full_sig - - raise ValueError( - f"No matching overload found for types: {types} in signatures: {overload_types.keys()}. Please check the function overloads." - ) - - -def _get_cpp_signature(func, rdf, cols): +def _resolve_overload_based_on_cols(func, rdf, cols): """ Gets the C++ signature of a cppyy callable. """ import ROOT - if isinstance(func, ROOT._cppyy.types.Template): - func = get_cpp_overload_from_templ_proxy(func, get_column_types(rdf, cols)) - - if not isinstance(func, ROOT._cppyy.types.Function): + if not isinstance(func, (ROOT._cppyy.types.Function, ROOT._cppyy.types.Template)): raise TypeError(f"Expected a cppyy callable, got {type(func).__name__}") - overload_types = func.func_overloads_types - matched_overload = get_overload_based_on_args(overload_types, get_column_types(rdf, cols)) - return remove_fn_name_from_signature(matched_overload) + if isinstance(func, ROOT._cppyy.types.Template) and func.__template_args__: + template_args = func.__template_args__[1:-1] + else: + template_args = "" - -def _to_std_function(func, rdf, cols): - """ - Converts a cppyy callable to std::function. - """ - import ROOT - - if not isinstance(func, ROOT._cppyy.types.Function) and not isinstance(func, ROOT._cppyy.types.Template): - raise TypeError(f"Expected a cppyy callable, got {type(func).__name__}") - - signature = _get_cpp_signature(func, rdf, cols) - return ROOT.std.function(signature) - - -def _handle_cpp_callables(func, original_template, *args, rdf=None, cols=None): - """ - Checks whether the callable `func` is a cppyy proxy of one of these: - 1. C++ functor - 2. std::function - 3. C++ free function - - Cases 1 and 2 above are supported by cppyy, so we can just invoke the original - cppyy TemplateProxy (Filter or Define) with the callable as argument. - For case 3, we need to convert the callable to a std::function - before invoking the original cppyy TemplateProxy. - - Prior to the invocation of the original cppyy TemplateProxy, though, we - need to explicitly instantiate the template using the type of the `func` - callable. Implicit instantiation won't work since the original - implementation of Filter and Define is replaced by a pythonization. - - Arguments: - func: callable to be checked - original_rdf_method: cppyy proxy for Filter or Define - args: arguments passed by the user to Filter or Define - Returns: - RDataFrame: RDataFrame node if `func` can be classified in one of the - three cases above, None otherwise - """ - import ROOT - - is_cpp_functor = lambda: isinstance(getattr(func, "__call__", None), ROOT._cppyy.types.Function) # noqa E731 - - is_std_function = lambda: isinstance(getattr(func, "target_type", None), ROOT._cppyy.types.Function) # noqa E731 - - # handle free functions - if callable(func) and not is_cpp_functor() and not is_std_function(): - try: - func = _to_std_function(func, rdf, cols) - except TypeError as e: - if "Expected a cppyy callable" in str(e): - pass # this function is not convertible to std::function, move on to the next check - else: - raise - - if is_cpp_functor() or is_std_function(): - return original_template[type(func)](*args) + col_types = get_column_types(rdf, cols) + signature = ", ".join(col_types) if cols else "" + if template_args: + return func.__overload__(signature, template_args) + else: + return func.__overload__(signature) class _WarnOnce: @@ -460,9 +351,13 @@ def x_more_than_y(x): f"Arguments should be ('list', 'str',) not ({type(args[0]).__name__, type(args[1]).__name__}." ) - rdf_node = _handle_cpp_callables(func, rdf._OriginalFilter, func, *args, rdf=rdf, cols=col_list) - if rdf_node is not None: - return rdf_node + import ROOT + + if isinstance(func, (ROOT._cppyy.types.Function, ROOT._cppyy.types.Template)): + return rdf._OriginalFilter(_resolve_overload_based_on_cols(func, rdf, col_list), *args) + + if isinstance(func, ROOT._cppyy.types.Instance): + return rdf._OriginalFilter(func, *args) _WarnOnce.warn() jitter = FunctionJitter(rdf) @@ -520,9 +415,13 @@ def x_scaled(x): raise TypeError(f"Define takes a column list as third arguments but {type(cols).__name__} was given.") func = callable_or_str - rdf_node = _handle_cpp_callables(func, rdf._OriginalDefine, col_name, func, cols, rdf=rdf, cols=cols) - if rdf_node is not None: - return rdf_node + + import ROOT + + if isinstance(func, (ROOT._cppyy.types.Function, ROOT._cppyy.types.Template)): + return rdf._OriginalDefine(col_name, _resolve_overload_based_on_cols(func, rdf, cols), cols) + if isinstance(func, ROOT._cppyy.types.Instance): + return rdf._OriginalDefine(col_name, func, cols) _WarnOnce.warn() jitter = FunctionJitter(rdf) From 7add2f965351c25e5f8cfa75be9595e63bc0d364 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Mon, 4 May 2026 13:56:25 +0200 Subject: [PATCH 41/45] [Python] Inject `gPad` and `gVirtualX` into facade without ROOT meta The `gPad and `gVirtualX` identifiers are injected into ROOT meta via `TGlobalMappedFunction::MakeFunctor()`. However, in the ROOT Python interface we don't want to rely on ROOT meta but use Cling or Clang-Repl directly. Hence, we inject these identifiers manually into the facade. The `TDirectoryPythonAdapter` previously used for `gDirectory` mirrored the live-tracking semantics of the C++ macros: every attribute access re-resolves to the current directory. The same semantics are needed for `gPad` and `gVirtualX`, which on the C++ side are also preprocessor macros that expand to a static accessor call. Therefore, a new `LiveProxy` is introduced for this. --- bindings/pyroot/pythonizations/python/ROOT/_facade.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_facade.py b/bindings/pyroot/pythonizations/python/ROOT/_facade.py index 8006329d904dc..98c164df9a8b2 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_facade.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_facade.py @@ -305,7 +305,6 @@ 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() # Release the GIL on the heavy TInterpreter functions. This lets # background Python threads make progress - in particular, JupyROOT's From 608ac22cf98654ea40a3e94bd84532d67ec36af1 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 4 May 2026 21:39:37 +0200 Subject: [PATCH 42/45] [ROOT] Workaround diagnostics when slow type lookup of literal in TColorNumber ctor param --- core/base/inc/TColor.h | 2 +- core/base/src/TColor.cxx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/base/inc/TColor.h b/core/base/inc/TColor.h index 4c68a6e1959b0..7f41c4437dede 100644 --- a/core/base/inc/TColor.h +++ b/core/base/inc/TColor.h @@ -147,7 +147,7 @@ class TColorNumber { // TColorNumber is designed to be constructed implicitly, and C++ won't do // two user-defined conversions in a single implicit conversion chain. inline TColorNumber(const char *color) : TColorNumber{std::string{color}} {} - TColorNumber(std::array rgb); + TColorNumber(std::vector rgb); Int_t number() const { return fNumber; } private: diff --git a/core/base/src/TColor.cxx b/core/base/src/TColor.cxx index 86dc1d3f58827..43163178207c3 100644 --- a/core/base/src/TColor.cxx +++ b/core/base/src/TColor.cxx @@ -3723,4 +3723,4 @@ TColorNumber::TColorNumber(std::string const &color) //////////////////////////////////////////////////////////////////////////////// /// Instantiate a color from a tuple of RGB values between 0.0 and 1.0. -TColorNumber::TColorNumber(std::array rgb) : fNumber{TColor::GetColor(rgb[0], rgb[1], rgb[2])} {} +TColorNumber::TColorNumber(std::vector rgb) : fNumber{TColor::GetColor(rgb[0], rgb[1], rgb[2])} {} From 605c600e0cca6cde439c09d3a0739d5a22d5f40d Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 11 May 2026 14:24:48 +0200 Subject: [PATCH 43/45] [ROOT] Use value_type instead of string manipulation to pythonize stl vector --- .../python/ROOT/_pythonization/_stl_vector.py | 36 +++++++++++++++++-- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_stl_vector.py b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_stl_vector.py index 8e91fe6eb123a..a094c0bcf53a8 100644 --- a/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_stl_vector.py +++ b/bindings/pyroot/pythonizations/python/ROOT/_pythonization/_stl_vector.py @@ -8,8 +8,10 @@ # For the list of contributors see $ROOTSYS/README/CREDITS. # ################################################################################ +import sys + from . import pythonization -from ._rvec import add_array_interface_property +from ._rvec import _array_interface_dtype_map def _data_vec_char(self): @@ -24,6 +26,35 @@ def _data_vec_char(self): self.pop_back() return d + +def _get_array_interface(self): + import ROOT + + value_type = getattr(type(self), "value_type", None) + dtype_numpy = _array_interface_dtype_map.get(value_type) + if dtype_numpy is not None: + dtype_size = ROOT._cppyy.sizeof(value_type) + endianness = "<" if sys.byteorder == "little" else ">" + size = self.size() + # Numpy breaks for data pointer of 0 even though the array is empty. + # We set the pointer to 1 but the value itself is arbitrary and never accessed. + if self.empty(): + pointer = 1 + else: + pointer = ROOT._cppyy.ll.addressof(self.data()) + return { + "shape": (size,), + "typestr": "{}{}{}".format(endianness, dtype_numpy, dtype_size), + "version": 3, + "data": (pointer, False), + } + + +def _add_array_interface_property(klass): + value_type = getattr(klass, "value_type", None) + if value_type in _array_interface_dtype_map: + klass.__array_interface__ = property(_get_array_interface) + @pythonization("vector<", ns="std", is_prefix=True) def pythonize_stl_vector(klass, name): # Parameters: @@ -31,8 +62,7 @@ def pythonize_stl_vector(klass, name): # name: string containing the name of the class # Add numpy array interface - # NOTE: The pythonization is reused from ROOT::VecOps::RVec - add_array_interface_property(klass, name) + _add_array_interface_property(klass) # Inject custom vector::data() value_type = getattr(klass, 'value_type', None) From c4168fe87ddea365a12fd1e0a508bf4b426d1bd7 Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Mon, 11 May 2026 15:05:58 +0200 Subject: [PATCH 44/45] [ROOT][test] Canonical name includes UL suffix for literal --- bindings/pyroot/pythonizations/test/rdataframe_asnumpy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bindings/pyroot/pythonizations/test/rdataframe_asnumpy.py b/bindings/pyroot/pythonizations/test/rdataframe_asnumpy.py index 2ecdd9dafa262..b89f44d76246a 100644 --- a/bindings/pyroot/pythonizations/test/rdataframe_asnumpy.py +++ b/bindings/pyroot/pythonizations/test/rdataframe_asnumpy.py @@ -120,7 +120,7 @@ def test_read_array(self): npy = df.AsNumpy() self.assertEqual(npy["x"].size, 5) self.assertEqual(list(npy["x"][0]), [0, 0, 0]) - self.assertIn("array", str(type(npy["x"][0]))) + self.assertIn("array", str(type(npy["x"][0]))) def test_read_th1f(self): """ From a4858e36d9d1598d84d47002b27a40526f876756 Mon Sep 17 00:00:00 2001 From: Jonas Rembser Date: Thu, 25 Jun 2026 22:02:38 +0200 Subject: [PATCH 45/45] [CPyCppyy] Fix this-pointer offset for using-declared base methods When a derived class pulls a base method into its own overload set with `using Base::method;`, the method is still declared in the base. If that base is not the first one, its subobject sits at a non-zero offset in the derived object. `CPPMethod::Call` computed the this-pointer offset against `fScope` (the class the method is bound on, i.e. the derived class) rather than the method's declaring base. For a using-declared method these differ, yielding a zero offset. The generated wrapper, however, casts 'this' to the declaring base, so the call wrote through an unadjusted pointer, corrupting memory and crashing on destruction. Compute the offset against the method's declaring scope (`Cppyy::GetParentScope`) instead. This is a no-op for ordinary inherited methods, which are bound on their declaring base already. Surfaced by `roottest-python-cpp-cpp` (`TGraphMultiErrors::SetLineColor`, where `TAttLine` is a secondary base). --- bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx index be58e8d30970b..1caed2c8e0537 100644 --- a/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx +++ b/bindings/pyroot/cppyy/CPyCppyy/src/CPPMethod.cxx @@ -1065,10 +1065,18 @@ PyObject* CPyCppyy::CPPMethod::Call(CPPInstance*& self, // get its class Cppyy::TCppType_t derived = self->ObjectIsA(); -// calculate offset (the method expects 'this' to be an object of fScope) +// calculate offset: the generated wrapper casts 'this' to the method's actual +// declaring class, which may differ from fScope. In particular, a method +// brought into fScope through a using-declaration (e.g. `using Base::meth;`) +// is still declared in the base, so 'this' has to be adjusted to that base's +// subobject. Using fScope here would yield a zero offset and corrupt memory. + Cppyy::TCppScope_t declaring = Cppyy::GetParentScope(fMethod); + if (!declaring) + declaring = fScope; + ptrdiff_t offset = 0; - if (derived && derived != fScope) - offset = Cppyy::GetBaseOffset(derived, fScope, object, 1 /* up-cast */); + if (derived && derived != declaring) + offset = Cppyy::GetBaseOffset(derived, declaring, object, 1 /* up-cast */); // actual call; recycle self instead of returning new object for same address objects CPPInstance* pyobj = (CPPInstance*)Execute(object, offset, ctxt);