Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
CPPOverload.cxx
Go to the documentation of this file.
1// Bindings
2#include "CPyCppyy.h"
3#include "structmember.h" // from Python
4#if PY_VERSION_HEX < 0x02050000
5#include "compile.h" // from Python
6#elif PY_VERSION_HEX < 0x030b0000
7#include "code.h" // from Python
8#endif
9#ifndef CO_NOFREE
10// python2.2 does not have CO_NOFREE defined
11#define CO_NOFREE 0x0040
12#endif
13#include "CPPOverload.h"
14#include "CPPInstance.h"
15#include "CallContext.h"
16#include "PyStrings.h"
17#include "Utility.h"
18
19// Standard
20#include <algorithm>
21#include <sstream>
22#include <vector>
23
24
25namespace CPyCppyy {
26
27namespace {
28
29// from CPython's instancemethod: Free list for method objects to safe malloc/free overhead
30// The im_self element is used to chain the elements.
31static CPPOverload* free_list;
32static int numfree = 0;
33#ifndef CPPOverload_MAXFREELIST
34#define CPPOverload_MAXFREELIST 32
35#endif
36
37
38// TODO: only used here, but may be better off integrated with Pythonize.cxx callbacks
39class TPythonCallback : public PyCallable {
40public:
42
43 TPythonCallback(PyObject* callable) : fCallable(nullptr)
44 {
45 if (!PyCallable_Check(callable)) {
46 PyErr_SetString(PyExc_TypeError, "parameter must be callable");
47 return;
48 }
49 Py_INCREF(callable);
50 fCallable = callable;
51 }
52
53 virtual ~TPythonCallback() {
54 Py_DECREF(fCallable);
55 fCallable = nullptr;
56 }
57
58 virtual PyObject* GetSignature(bool /*show_formalargs*/ = true) {
59 return CPyCppyy_PyText_FromString("*args, **kwargs");
60 }
61 virtual PyObject* GetPrototype(bool /*show_formalargs*/ = true) {
62 return CPyCppyy_PyText_FromString("<callback>");
63 }
64 virtual PyObject* GetDocString() {
65 if (PyObject_HasAttrString(fCallable, "__doc__")) {
66 return PyObject_GetAttrString(fCallable, "__doc__");
67 } else {
68 return GetPrototype();
69 }
70 }
71
72 virtual int GetPriority() { return 100; };
73 virtual bool IsGreedy() { return false; };
74
75 virtual int GetMaxArgs() { return 100; };
76 virtual PyObject* GetCoVarNames() { // TODO: pick these up from the callable
78 }
79 virtual PyObject* GetArgDefault(int /* iarg */) { // TODO: pick these up from the callable
81 }
82
83 virtual PyObject* GetScopeProxy() { // should this be the module ??
85 }
86
88 return (Cppyy::TCppFuncAddr_t)nullptr;
89 }
90
91 virtual PyCallable* Clone() { return new TPythonCallback(*this); }
92
93 virtual PyObject* Call(
94 CPPInstance*& self, PyObject* args, PyObject* kwds, CallContext* /* ctxt = 0 */) {
95
96 PyObject* newArgs = nullptr;
97 if (self) {
98 Py_ssize_t nargs = PyTuple_Size(args);
99 newArgs = PyTuple_New(nargs+1);
100 Py_INCREF(self);
101 PyTuple_SET_ITEM(newArgs, 0, (PyObject*)self);
102 for (Py_ssize_t iarg = 0; iarg < nargs; ++iarg) {
103 PyObject* pyarg = PyTuple_GET_ITEM(args, iarg);
104 Py_INCREF(pyarg);
105 PyTuple_SET_ITEM(newArgs, iarg+1, pyarg);
106 }
107 } else {
108 Py_INCREF(args);
109 newArgs = args;
110 }
111 return PyObject_Call(fCallable, newArgs, kwds);
112 }
113};
114
115// helper to test whether a method is used in a pseudo-function modus
116static inline bool IsPseudoFunc(CPPOverload* pymeth)
117{
118 return pymeth->fMethodInfo->fFlags & CallContext::kIsPseudoFunc;
119}
120
121// helper to sort on method priority
122static int PriorityCmp(PyCallable* left, PyCallable* right)
123{
124 return left->GetPriority() > right->GetPriority();
125}
126
127// return helper
128static inline void ResetCallState(CPPInstance*& selfnew, CPPInstance* selfold, bool clear)
129{
130 if (selfnew != selfold) {
131 Py_XDECREF(selfnew);
132 selfnew = selfold;
133 }
134
135 if (clear)
136 PyErr_Clear();
137}
138
139// helper to factor out return logic of mp_call
140static inline PyObject* HandleReturn(
141 CPPOverload* pymeth, CPPInstance* oldSelf, PyObject* result)
142{
143// special case for python exceptions, propagated through C++ layer
144 int ll_action = 0;
145 if (result) {
146
147 // if this method creates new objects, always take ownership
148 if (IsCreator(pymeth->fMethodInfo->fFlags)) {
149
150 // either be a constructor with a fresh object proxy self ...
151 if (IsConstructor(pymeth->fMethodInfo->fFlags)) {
152 if (pymeth->fSelf)
153 pymeth->fSelf->PythonOwns();
154 }
155
156 // ... or be a method with an object proxy return value
157 else if (CPPInstance_Check(result))
158 ((CPPInstance*)result)->PythonOwns();
159 }
160
161 // if this new object falls inside self, make sure its lifetime is proper
162 if (pymeth->fMethodInfo->fFlags & CallContext::kSetLifeLine)
163 ll_action = 1;
164 else if (!(pymeth->fMethodInfo->fFlags & CallContext::kNeverLifeLine) && \
165 CPPInstance_Check(pymeth->fSelf) && CPPInstance_Check(result)) {
166 // if self was a by-value return and result is not, pro-actively protect result;
167 // else if the return value falls within the memory of 'this', force a lifeline
168 CPPInstance* cppself = (CPPInstance*)pymeth->fSelf;
169 CPPInstance* cppres = (CPPInstance*)result;
170 if (!(cppres->fFlags & CPPInstance::kIsValue)) { // no need if the result is a full copy
171 if (cppself->fFlags & CPPInstance::kIsValue)
172 ll_action = 2;
173 else if (cppself->fFlags & CPPInstance::kHasLifeline)
174 ll_action = 3;
175 else {
176 ptrdiff_t offset = (ptrdiff_t)cppres->GetObject() - (ptrdiff_t)cppself->GetObject();
177 if (0 <= offset && offset < (ptrdiff_t)Cppyy::SizeOf(cppself->ObjectIsA()))
178 ll_action = 4;
179 }
180 }
181 if (ll_action) cppres->fFlags |= CPPInstance::kHasLifeline; // for chaining
182 }
183
184 if (!ll_action)
185 pymeth->fMethodInfo->fFlags |= CallContext::kNeverLifeLine; // assume invariant semantics
186 }
187
188 if (ll_action) {
189 if (PyObject_SetAttr(result, PyStrings::gLifeLine, (PyObject*)pymeth->fSelf) == -1)
190 PyErr_Clear(); // ignored
191 if (ll_action == 1 /* directly set */ && CPPInstance_Check(result))
192 ((CPPInstance*)result)->fFlags |= CPPInstance::kHasLifeline; // for chaining
193 else
194 pymeth->fMethodInfo->fFlags |= CallContext::kSetLifeLine; // for next time
195 }
196
197// reset self as necessary to allow re-use of the CPPOverload
198 ResetCallState(pymeth->fSelf, oldSelf, false);
199
200 return result;
201}
202
203
204//= CPyCppyy method proxy object behaviour ===================================
205static PyObject* mp_name(CPPOverload* pymeth, void*)
206{
207 return CPyCppyy_PyText_FromString(pymeth->GetName().c_str());
208}
209
210//----------------------------------------------------------------------------
211static PyObject* mp_module(CPPOverload* /* pymeth */, void*)
212{
213 Py_INCREF(PyStrings::gThisModule);
215}
216
217//----------------------------------------------------------------------------
218static PyObject* mp_doc(CPPOverload* pymeth, void*)
219{
220// Build python document string ('__doc__') from all C++-side overloads.
221 CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods;
222
223// collect doc strings
224 CPPOverload::Methods_t::size_type nMethods = methods.size();
225 if (nMethods == 0) // from template proxy with no instantiations
226 return nullptr;
227 PyObject* doc = methods[0]->GetDocString();
228
229// simple case
230 if (nMethods == 1)
231 return doc;
232
233// overloaded method
234 PyObject* separator = CPyCppyy_PyText_FromString("\n");
235 for (CPPOverload::Methods_t::size_type i = 1; i < nMethods; ++i) {
236 CPyCppyy_PyText_Append(&doc, separator);
237 CPyCppyy_PyText_AppendAndDel(&doc, methods[i]->GetDocString());
238 }
239 Py_DECREF(separator);
240
241 return doc;
242}
243
244//----------------------------------------------------------------------------
245static PyObject* mp_meth_func(CPPOverload* pymeth, void*)
246{
247// Create a new method proxy to be returned.
248 CPPOverload* newPyMeth = (CPPOverload*)CPPOverload_Type.tp_alloc(&CPPOverload_Type, 0);
249
250// method info is shared, as it contains the collected overload knowledge
251 *pymeth->fMethodInfo->fRefCount += 1;
252 newPyMeth->fMethodInfo = pymeth->fMethodInfo;
253
254// new method is unbound, track whether this proxy is used in the capacity of a
255// method or a function (which normally is a CPPFunction)
256 newPyMeth->fMethodInfo->fFlags |= CallContext::kIsPseudoFunc;
257
258 return (PyObject*)newPyMeth;
259}
260
261//----------------------------------------------------------------------------
262static PyObject* mp_meth_self(CPPOverload* pymeth, void*)
263{
264// Return the bound self, if any; in case of pseudo-function role, pretend
265// that the data member im_self does not exist.
266 if (IsPseudoFunc(pymeth)) {
267 PyErr_Format(PyExc_AttributeError,
268 "function %s has no attribute \'im_self\'", pymeth->fMethodInfo->fName.c_str());
269 return nullptr;
270 } else if (pymeth->fSelf != 0) {
271 Py_INCREF((PyObject*)pymeth->fSelf);
272 return (PyObject*)pymeth->fSelf;
273 }
274
276}
277
278//----------------------------------------------------------------------------
279static PyObject* mp_meth_class(CPPOverload* pymeth, void*)
280{
281// Return scoping class; in case of pseudo-function role, pretend that there
282// is no encompassing class (i.e. global scope).
283 if (!IsPseudoFunc(pymeth) && pymeth->fMethodInfo->fMethods.size()) {
284 PyObject* pyclass = pymeth->fMethodInfo->fMethods[0]->GetScopeProxy();
285 if (!pyclass)
286 PyErr_Format(PyExc_AttributeError,
287 "function %s has no attribute \'im_class\'", pymeth->fMethodInfo->fName.c_str());
288 return pyclass;
289 }
290
292}
293
294//----------------------------------------------------------------------------
295static PyObject* mp_func_closure(CPPOverload* /* pymeth */, void*)
296{
297// Stub only, to fill out the python function interface.
299}
300
301//----------------------------------------------------------------------------
302static PyObject* mp_func_code(CPPOverload* pymeth, void*)
303{
304// Code details are used in module inspect to fill out interactive help()
305#if PY_VERSION_HEX < 0x03000000
306 CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods;
307
308// collect arguments only if there is just 1 overload, otherwise put in a
309// fake *args (see below for co_varnames)
310 PyObject* co_varnames = methods.size() == 1 ? methods[0]->GetCoVarNames() : nullptr;
311 if (!co_varnames) {
312 // TODO: static methods need no 'self' (but is harmless otherwise)
313 co_varnames = PyTuple_New(1 /* self */ + 1 /* fake */);
314 PyTuple_SET_ITEM(co_varnames, 0, CPyCppyy_PyText_FromString("self"));
315 PyTuple_SET_ITEM(co_varnames, 1, CPyCppyy_PyText_FromString("*args"));
316 }
317
318 int co_argcount = (int)PyTuple_Size(co_varnames);
319
320// for now, code object representing the statement 'pass'
321 PyObject* co_code = PyString_FromStringAndSize("d\x00\x00S", 4);
322
323// tuples with all the const literals used in the function
324 PyObject* co_consts = PyTuple_New(0);
325 PyObject* co_names = PyTuple_New(0);
326
327// names, freevars, and cellvars go unused
328 PyObject* co_unused = PyTuple_New(0);
329
330// filename is made-up
331 PyObject* co_filename = PyString_FromString("cppyy.py");
332
333// name is the function name, also through __name__ on the function itself
334 PyObject* co_name = PyString_FromString(pymeth->GetName().c_str());
335
336// firstlineno is the line number of first function code in the containing scope
337
338// lnotab is a packed table that maps instruction count and line number
339 PyObject* co_lnotab = PyString_FromString("\x00\x01\x0c\x01");
340
341 PyObject* code = (PyObject*)PyCode_New(
342 co_argcount, // argcount
343 co_argcount+1, // nlocals
344 2, // stacksize
345 CO_OPTIMIZED | CO_NEWLOCALS | CO_NOFREE, // flags
346 co_code, // code
347 co_consts, // consts
348 co_names, // names
349 co_varnames, // varnames
350 co_unused, // freevars
351 co_unused, // cellvars
352 co_filename, // filename
353 co_name, // name
354 1, // firstlineno
355 co_lnotab); // lnotab
356
357 Py_DECREF(co_lnotab);
358 Py_DECREF(co_name);
359 Py_DECREF(co_unused);
360 Py_DECREF(co_filename);
361 Py_DECREF(co_varnames);
362 Py_DECREF(co_names);
363 Py_DECREF(co_consts);
364 Py_DECREF(co_code);
365
366 return code;
367#else
368// not important for functioning of most code, so not implemented for p3 for now (TODO)
369 (void)pymeth;
371#endif
372}
373
374//----------------------------------------------------------------------------
375static PyObject* mp_func_defaults(CPPOverload* pymeth, void*)
376{
377// Create a tuple of default values, if there is only one method (otherwise
378// leave undefined: this is only used by inspect for interactive help())
379 CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods;
380
381 if (methods.size() != 1)
382 return PyTuple_New(0);
383
384 int maxarg = methods[0]->GetMaxArgs();
385
386 PyObject* defaults = PyTuple_New(maxarg);
387
388 int itup = 0;
389 for (int iarg = 0; iarg < maxarg; ++iarg) {
390 PyObject* defvalue = methods[0]->GetArgDefault(iarg);
391 if (defvalue)
392 PyTuple_SET_ITEM(defaults, itup++, defvalue);
393 }
394 _PyTuple_Resize(&defaults, itup);
395
396 return defaults;
397}
398
399//----------------------------------------------------------------------------
400static PyObject* mp_func_globals(CPPOverload* /* pymeth */, void*)
401{
402// Return this function's global dict (hard-wired to be the cppyy module); used
403// for lookup of names from co_code indexing into co_names.
404 PyObject* pyglobal = PyModule_GetDict(PyImport_AddModule((char*)"cppyy"));
405 Py_XINCREF(pyglobal);
406 return pyglobal;
407}
408
409//----------------------------------------------------------------------------
410static inline int set_flag(CPPOverload* pymeth, PyObject* value, CallContext::ECallFlags flag, const char* name)
411{
412// Generic setter of a (boolean) flag.
413 if (!value) { // accept as false (delete)
414 pymeth->fMethodInfo->fFlags &= ~flag;
415 return 0;
416 }
417
418 long istrue = PyLong_AsLong(value);
419 if (istrue == -1 && PyErr_Occurred()) {
420 PyErr_Format(PyExc_ValueError, "a boolean 1 or 0 is required for %s", name);
421 return -1;
422 }
423
424 if (istrue)
425 pymeth->fMethodInfo->fFlags |= flag;
426 else
427 pymeth->fMethodInfo->fFlags &= ~flag;
428
429 return 0;
430}
431
432//----------------------------------------------------------------------------
433static PyObject* mp_getcreates(CPPOverload* pymeth, void*)
434{
435// Get '__creates__' boolean, which determines ownership of return values.
436 return PyInt_FromLong((long)IsCreator(pymeth->fMethodInfo->fFlags));
437}
438
439//----------------------------------------------------------------------------
440static int mp_setcreates(CPPOverload* pymeth, PyObject* value, void*)
441{
442// Set '__creates__' boolean, which determines ownership of return values.
443 return set_flag(pymeth, value, CallContext::kIsCreator, "__creates__");
444}
445
446//----------------------------------------------------------------------------
447static PyObject* mp_getmempolicy(CPPOverload* pymeth, void*)
448{
449// Get '_mempolicy' enum, which determines ownership of call arguments.
450 if (pymeth->fMethodInfo->fFlags & CallContext::kUseHeuristics)
451 return PyInt_FromLong(CallContext::kUseHeuristics);
452
453 if (pymeth->fMethodInfo->fFlags & CallContext::kUseStrict)
454 return PyInt_FromLong(CallContext::kUseStrict);
455
456 return PyInt_FromLong(-1);
457}
458
459//----------------------------------------------------------------------------
460static int mp_setmempolicy(CPPOverload* pymeth, PyObject* value, void*)
461{
462// Set '_mempolicy' enum, which determines ownership of call arguments.
463 long mempolicy = PyLong_AsLong(value);
464 if (mempolicy == CallContext::kUseHeuristics) {
465 pymeth->fMethodInfo->fFlags |= CallContext::kUseHeuristics;
466 pymeth->fMethodInfo->fFlags &= ~CallContext::kUseStrict;
467 } else if (mempolicy == CallContext::kUseStrict) {
468 pymeth->fMethodInfo->fFlags |= CallContext::kUseStrict;
469 pymeth->fMethodInfo->fFlags &= ~CallContext::kUseHeuristics;
470 } else {
471 PyErr_SetString(PyExc_ValueError,
472 "expected kMemoryStrict or kMemoryHeuristics as value for __mempolicy__");
473 return -1;
474 }
475
476 return 0;
477}
478
479
480//----------------------------------------------------------------------------
481#define CPPYY_BOOLEAN_PROPERTY(name, flag, label) \
482static PyObject* mp_get##name(CPPOverload* pymeth, void*) { \
483 if (pymeth->fMethodInfo->fFlags & flag) { \
484 Py_RETURN_TRUE; \
485 } \
486 Py_RETURN_FALSE; \
487} \
488 \
489static int mp_set##name(CPPOverload* pymeth, PyObject* value, void*) { \
490 return set_flag(pymeth, value, flag, label); \
491}
492
493CPPYY_BOOLEAN_PROPERTY(lifeline, CallContext::kSetLifeLine, "__set_lifeline__")
494CPPYY_BOOLEAN_PROPERTY(threaded, CallContext::kReleaseGIL, "__release_gil__")
495CPPYY_BOOLEAN_PROPERTY(useffi, CallContext::kUseFFI, "__useffi__")
496CPPYY_BOOLEAN_PROPERTY(sig2exc, CallContext::kProtected, "__sig2exc__")
497
498//----------------------------------------------------------------------------
499static PyGetSetDef mp_getset[] = {
500 {(char*)"__name__", (getter)mp_name, nullptr, nullptr, nullptr},
501 {(char*)"__module__", (getter)mp_module, nullptr, nullptr, nullptr},
502 {(char*)"__doc__", (getter)mp_doc, nullptr, nullptr, nullptr},
503
504// to be more python-like, where these are duplicated as well; to actually
505// derive from the python method or function type is too memory-expensive,
506// given that most of the members of those types would not be used
507 {(char*)"im_func", (getter)mp_meth_func, nullptr, nullptr, nullptr},
508 {(char*)"im_self", (getter)mp_meth_self, nullptr, nullptr, nullptr},
509 {(char*)"im_class", (getter)mp_meth_class, nullptr, nullptr, nullptr},
510
511 {(char*)"func_closure", (getter)mp_func_closure, nullptr, nullptr, nullptr},
512 {(char*)"func_code", (getter)mp_func_code, nullptr, nullptr, nullptr},
513 {(char*)"func_defaults", (getter)mp_func_defaults, nullptr, nullptr, nullptr},
514 {(char*)"func_globals", (getter)mp_func_globals, nullptr, nullptr, nullptr},
515 {(char*)"func_doc", (getter)mp_doc, nullptr, nullptr, nullptr},
516 {(char*)"func_name", (getter)mp_name, nullptr, nullptr, nullptr},
517
518 {(char*)"__creates__", (getter)mp_getcreates, (setter)mp_setcreates,
519 (char*)"For ownership rules of result: if true, objects are python-owned", nullptr},
520 {(char*)"__mempolicy__", (getter)mp_getmempolicy, (setter)mp_setmempolicy,
521 (char*)"For argument ownership rules: like global, either heuristic or strict", nullptr},
522 {(char*)"__set_lifeline__", (getter)mp_getlifeline, (setter)mp_setlifeline,
523 (char*)"If true, set a lifeline from the return value onto self", nullptr},
524 {(char*)"__release_gil__", (getter)mp_getthreaded, (setter)mp_setthreaded,
525 (char*)"If true, releases GIL on call into C++", nullptr},
526 {(char*)"__useffi__", (getter)mp_getuseffi, (setter)mp_setuseffi,
527 (char*)"not implemented", nullptr},
528 {(char*)"__sig2exc__", (getter)mp_getsig2exc, (setter)mp_setsig2exc,
529 (char*)"If true, turn signals into Python exceptions", nullptr},
530 {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}
531};
532
533//= CPyCppyy method proxy function behavior ==================================
534static PyObject* mp_call(CPPOverload* pymeth, PyObject* args, PyObject* kwds)
535{
536// Call the appropriate overload of this method.
537
538 CPPInstance* oldSelf = pymeth->fSelf;
539
540// get local handles to proxy internals
541 auto& methods = pymeth->fMethodInfo->fMethods;
542
543 CPPOverload::Methods_t::size_type nMethods = methods.size();
544
545 CallContext ctxt{};
546 const auto mflags = pymeth->fMethodInfo->fFlags;
547 const auto mempolicy = (mflags & (CallContext::kUseHeuristics | CallContext::kUseStrict));
548 ctxt.fFlags |= mempolicy ? mempolicy : (uint64_t)CallContext::sMemoryPolicy;
549 ctxt.fFlags |= (mflags & CallContext::kReleaseGIL);
550 ctxt.fFlags |= (mflags & CallContext::kProtected);
551 if (IsConstructor(pymeth->fMethodInfo->fFlags)) ctxt.fFlags |= CallContext::kIsConstructor;
552 ctxt.fPyContext = (PyObject*)pymeth->fSelf; // no Py_INCREF as no ownership
553
554// magic variable to prevent recursion passed by keyword?
555 if (kwds && PyDict_CheckExact(kwds) && PyDict_Size(kwds) != 0) {
556 if (PyDict_DelItem(kwds, PyStrings::gNoImplicit) == 0) {
557 ctxt.fFlags |= CallContext::kNoImplicit;
558 if (!PyDict_Size(kwds)) kwds = nullptr;
559 } else
560 PyErr_Clear();
561 }
562
563// simple case
564 if (nMethods == 1) {
565 if (!NoImplicit(&ctxt)) ctxt.fFlags |= CallContext::kAllowImplicit; // no two rounds needed
566 PyObject* result = methods[0]->Call(pymeth->fSelf, args, kwds, &ctxt);
567 return HandleReturn(pymeth, oldSelf, result);
568 }
569
570// otherwise, handle overloading
571 uint64_t sighash = HashSignature(args);
572
573// look for known signatures ...
574 auto& dispatchMap = pymeth->fMethodInfo->fDispatchMap;
575 PyCallable* memoized_pc = nullptr;
576 for (const auto& p : dispatchMap) {
577 if (p.first == sighash) {
578 memoized_pc = p.second;
579 break;
580 }
581 }
582 if (memoized_pc) {
583 PyObject* result = memoized_pc->Call(pymeth->fSelf, args, kwds, &ctxt);
584 result = HandleReturn(pymeth, oldSelf, result);
585
586 if (result)
587 return result;
588
589 // fall through: python is dynamic, and so, the hashing isn't infallible
590 PyErr_Clear();
591 }
592
593// ... otherwise loop over all methods and find the one that does not fail
594 if (!IsSorted(mflags)) {
595 std::stable_sort(methods.begin(), methods.end(), PriorityCmp);
596 pymeth->fMethodInfo->fFlags |= CallContext::kIsSorted;
597 }
598
599 std::vector<Utility::PyError_t> errors;
600 std::vector<bool> implicit_possible(methods.size());
601 for (int stage = 0; stage < 2; ++stage) {
602 bool bHaveImplicit = false;
603 for (CPPOverload::Methods_t::size_type i = 0; i < nMethods; ++i) {
604 if (stage && !implicit_possible[i])
605 continue; // did not set implicit conversion, so don't try again
606
607 PyObject* result = methods[i]->Call(pymeth->fSelf, args, kwds, &ctxt);
608 if (result != 0) {
609 // success: update the dispatch map for subsequent calls
610 if (!memoized_pc)
611 dispatchMap.push_back(std::make_pair(sighash, methods[i]));
612 else {
613 // debatable: apparently there are two methods that map onto the same sighash
614 // and preferring the latest may result in "ping pong."
615 for (auto& p : dispatchMap) {
616 if (p.first == sighash) {
617 p.second = methods[i];
618 break;
619 }
620 }
621 }
622
623 // clear collected errors
624 if (!errors.empty())
625 std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear);
626 return HandleReturn(pymeth, oldSelf, result);
627 }
628
629 // else failure ..
630 if (stage != 0) {
631 PyErr_Clear(); // first stage errors should be more informative
632 continue;
633 }
634
635 // collect error message/trace (automatically clears exception, too)
636 if (!PyErr_Occurred()) {
637 // this should not happen; set an error to prevent core dump and report
638 PyObject* sig = methods[i]->GetPrototype();
639 PyErr_Format(PyExc_SystemError, "%s =>\n %s",
640 CPyCppyy_PyText_AsString(sig), (char*)"nullptr result without error in mp_call");
641 Py_DECREF(sig);
642 }
643 Utility::FetchError(errors);
644
645 if (HaveImplicit(&ctxt)) {
646 bHaveImplicit = true;
647 implicit_possible[i] = true;
648 ctxt.fFlags &= ~CallContext::kHaveImplicit;
649 } else
650 implicit_possible[i] = false;
651 ResetCallState(pymeth->fSelf, oldSelf, false);
652 }
653
654 // only move forward if implicit conversions are available
655 if (!bHaveImplicit)
656 break;
657
658 ctxt.fFlags |= CallContext::kAllowImplicit;
659 }
660
661// first summarize, then add details
663 "none of the %d overloaded methods succeeded. Full details:", (int)nMethods);
664 SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */);
665
666// report failure
667 return nullptr;
668}
669
670//----------------------------------------------------------------------------
671static PyObject* mp_str(CPPOverload* cppinst)
672{
673// Print a description that includes the C++ name
674 std::ostringstream s;
675 s << "<C++ overload \"" << cppinst->fMethodInfo->fName << "\" at " << (void*)cppinst << ">";
676 return CPyCppyy_PyText_FromString(s.str().c_str());
677}
678
679//----------------------------------------------------------------------------
680static CPPOverload* mp_descrget(CPPOverload* pymeth, CPPInstance* pyobj, PyObject*)
681{
682// Descriptor; create and return a new bound method proxy (language requirement) if self
683 if (!pyobj) {
684 Py_INCREF(pymeth);
685 return pymeth; // unbound, e.g. free functions
686 }
687
688// else: bound
689 CPPOverload* newPyMeth = free_list;
690 if (newPyMeth != NULL) {
691 free_list = (CPPOverload*)(newPyMeth->fSelf);
692 (void)PyObject_INIT(newPyMeth, &CPPOverload_Type);
693 numfree--;
694 }
695 else {
696 newPyMeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type);
697 if (!newPyMeth)
698 return nullptr;
699 }
700
701// method info is shared, as it contains the collected overload knowledge
702 *pymeth->fMethodInfo->fRefCount += 1;
703 newPyMeth->fMethodInfo = pymeth->fMethodInfo;
704
705// new method is to be bound to current object
706 Py_INCREF((PyObject*)pyobj);
707 newPyMeth->fSelf = pyobj;
708
709 PyObject_GC_Track(newPyMeth);
710 return newPyMeth;
711}
712
713
714//= CPyCppyy method proxy construction/destruction ===========================
715static CPPOverload* mp_new(PyTypeObject*, PyObject*, PyObject*)
716{
717// Create a new method proxy object.
718 CPPOverload* pymeth = PyObject_GC_New(CPPOverload, &CPPOverload_Type);
719 pymeth->fSelf = nullptr;
720 pymeth->fMethodInfo = new CPPOverload::MethodInfo_t;
721
722 PyObject_GC_Track(pymeth);
723 return pymeth;
724}
725
726//----------------------------------------------------------------------------
727static void mp_dealloc(CPPOverload* pymeth)
728{
729// Deallocate memory held by method proxy object.
730 PyObject_GC_UnTrack(pymeth);
731
732 Py_CLEAR(pymeth->fSelf);
733
734 if (--(*pymeth->fMethodInfo->fRefCount) <= 0) {
735 delete pymeth->fMethodInfo;
736 }
737
739 pymeth->fSelf = (CPyCppyy::CPPInstance*)free_list;
740 free_list = pymeth;
741 numfree++;
742 }
743 else {
744 PyObject_GC_Del(pymeth);
745 }
746}
747
748//----------------------------------------------------------------------------
749static Py_ssize_t mp_hash(CPPOverload* pymeth)
750{
751// Hash of method proxy object for insertion into dictionaries; with actual
752// method (fMethodInfo) shared, its address is best suited.
753 return _Py_HashPointer(pymeth->fMethodInfo);
754}
755
756//----------------------------------------------------------------------------
757static int mp_traverse(CPPOverload* pymeth, visitproc visit, void* args)
758{
759// Garbage collector traverse of held python member objects.
760 if (pymeth->fSelf)
761 return visit((PyObject*)pymeth->fSelf, args);
762
763 return 0;
764}
765
766//----------------------------------------------------------------------------
767static int mp_clear(CPPOverload* pymeth)
768{
769// Garbage collector clear of held python member objects.
770 Py_CLEAR(pymeth->fSelf);
771
772 return 0;
773}
774
775//----------------------------------------------------------------------------
776static PyObject* mp_richcompare(CPPOverload* self, CPPOverload* other, int op)
777{
778// Rich set of comparison objects; only equals is defined.
779 if (op != Py_EQ)
780 return PyType_Type.tp_richcompare((PyObject*)self, (PyObject*)other, op);
781
782// defined by type + (shared) MethodInfo + bound self, with special case for
783// fSelf (i.e. pseudo-function)
784 if ((Py_TYPE(self) == Py_TYPE(other) && self->fMethodInfo == other->fMethodInfo) && \
785 ((IsPseudoFunc(self) && IsPseudoFunc(other)) || self->fSelf == other->fSelf)) {
787 }
789}
790
791
792//= CPyCppyy method proxy access to internals ================================
793static PyObject* mp_overload(CPPOverload* pymeth, PyObject* sigarg)
794{
795// Select and call a specific C++ overload, based on its signature.
796 if (!CPyCppyy_PyText_Check(sigarg)) {
797 PyErr_Format(PyExc_TypeError, "__overload__() argument 1 must be string, not %.50s",
798 sigarg == Py_None ? "None" : Py_TYPE(sigarg)->tp_name);
799 return nullptr;
800 }
801
802 std::string sig1{"("}; sig1.append(CPyCppyy_PyText_AsString(sigarg)); sig1.append(")");
803 sig1.erase(std::remove(sig1.begin(), sig1.end(), ' '), std::end(sig1));
804
805 CPPOverload::Methods_t& methods = pymeth->fMethodInfo->fMethods;
806 for (auto& meth : methods) {
807
808 bool found = false;
809
810 PyObject* pysig2 = meth->GetSignature(false);
811 std::string sig2(CPyCppyy_PyText_AsString(pysig2));
812 sig2.erase(std::remove(sig2.begin(), sig2.end(), ' '), std::end(sig2));
813 Py_DECREF(pysig2);
814 if (sig1 == sig2) found = true;
815
816 if (!found) {
817 pysig2 = meth->GetSignature(true);
818 std::string sig3(CPyCppyy_PyText_AsString(pysig2));
819 sig3.erase(std::remove(sig3.begin(), sig3.end(), ' '), std::end(sig3));
820 Py_DECREF(pysig2);
821 if (sig1 == sig3) found = true;
822 }
823
824 if (found) {
825 CPPOverload* newmeth = mp_new(nullptr, nullptr, nullptr);
826 CPPOverload::Methods_t vec; vec.push_back(meth->Clone());
827 newmeth->Set(pymeth->fMethodInfo->fName, vec);
828
829 if (pymeth->fSelf) {
830 Py_INCREF(pymeth->fSelf);
831 newmeth->fSelf = pymeth->fSelf;
832 }
833 newmeth->fMethodInfo->fFlags = pymeth->fMethodInfo->fFlags;
834
835 return (PyObject*)newmeth;
836 }
837 }
838
839 PyErr_Format(PyExc_LookupError,
840 "signature \"%s\" not found", CPyCppyy_PyText_AsString(sigarg));
841 return nullptr;
842}
843
844//= CPyCppyy method proxy access to internals ================================
845static PyObject* mp_add_overload(CPPOverload* pymeth, PyObject* new_overload)
846{
847 TPythonCallback* cb = new TPythonCallback(new_overload);
848 pymeth->AdoptMethod(cb);
850}
851
852static PyMethodDef mp_methods[] = {
853 {(char*)"__overload__", (PyCFunction)mp_overload, METH_O,
854 (char*)"select overload for dispatch" },
855 {(char*)"__add_overload__", (PyCFunction)mp_add_overload, METH_O,
856 (char*)"add a new overload" },
857 {(char*)nullptr, nullptr, 0, nullptr }
858};
859
860} // unnamed namespace
861
862
863//= CPyCppyy method proxy type ===============================================
864PyTypeObject CPPOverload_Type = {
865 PyVarObject_HEAD_INIT(&PyType_Type, 0)
866 (char*)"cppyy.CPPOverload", // tp_name
867 sizeof(CPPOverload), // tp_basicsize
868 0, // tp_itemsize
869 (destructor)mp_dealloc, // tp_dealloc
870 0, // tp_print
871 0, // tp_getattr
872 0, // tp_setattr
873 0, // tp_compare
874 0, // tp_repr
875 0, // tp_as_number
876 0, // tp_as_sequence
877 0, // tp_as_mapping
878 (hashfunc)mp_hash, // tp_hash
879 (ternaryfunc)mp_call, // tp_call
880 (reprfunc)mp_str, // tp_str
881 0, // tp_getattro
882 0, // tp_setattro
883 0, // tp_as_buffer
884 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags
885 (char*)"cppyy method proxy (internal)", // tp_doc
886 (traverseproc)mp_traverse, // tp_traverse
887 (inquiry)mp_clear, // tp_clear
888 (richcmpfunc)mp_richcompare, // tp_richcompare
889 0, // tp_weaklistoffset
890 0, // tp_iter
891 0, // tp_iternext
892 mp_methods, // tp_methods
893 0, // tp_members
894 mp_getset, // tp_getset
895 0, // tp_base
896 0, // tp_dict
897 (descrgetfunc)mp_descrget, // tp_descr_get
898 0, // tp_descr_set
899 0, // tp_dictoffset
900 0, // tp_init
901 0, // tp_alloc
902 (newfunc)mp_new, // tp_new
903 0, // tp_free
904 0, // tp_is_gc
905 0, // tp_bases
906 0, // tp_mro
907 0, // tp_cache
908 0, // tp_subclasses
909 0 // tp_weaklist
910#if PY_VERSION_HEX >= 0x02030000
911 , 0 // tp_del
912#endif
913#if PY_VERSION_HEX >= 0x02060000
914 , 0 // tp_version_tag
915#endif
916#if PY_VERSION_HEX >= 0x03040000
917 , 0 // tp_finalize
918#endif
919};
920
921} // namespace CPyCppyy
922
923
924//- public members -----------------------------------------------------------
925void CPyCppyy::CPPOverload::Set(const std::string& name, std::vector<PyCallable*>& methods)
926{
927// Fill in the data of a freshly created method proxy.
929 fMethodInfo->fMethods.swap(methods);
930 fMethodInfo->fFlags &= ~CallContext::kIsSorted;
931
932// special case: all constructors are considered creators by default
933 if (name == "__init__")
935
936// special case, in heuristics mode also tag *Clone* methods as creators
938 name.find("Clone") != std::string::npos)
940}
941
942//----------------------------------------------------------------------------
944{
945// Fill in the data of a freshly created method proxy.
946 fMethodInfo->fMethods.push_back(pc);
947 fMethodInfo->fFlags &= ~CallContext::kIsSorted;
948}
949
950//----------------------------------------------------------------------------
952{
953 if (!HasMethods()) // if fresh method being filled: also copy flags
954 fMethodInfo->fFlags = meth->fMethodInfo->fFlags;
955 fMethodInfo->fMethods.insert(fMethodInfo->fMethods.end(),
956 meth->fMethodInfo->fMethods.begin(), meth->fMethodInfo->fMethods.end());
957 fMethodInfo->fFlags &= ~CallContext::kIsSorted;
958 meth->fMethodInfo->fDispatchMap.clear();
959 meth->fMethodInfo->fMethods.clear();
960}
961
962//----------------------------------------------------------------------------
964{
965// Destructor (this object is reference counted).
966 for (Methods_t::iterator it = fMethods.begin(); it != fMethods.end(); ++it) {
967 delete *it;
968 }
969 fMethods.clear();
970 delete fRefCount;
971}
972
973// TODO: something like PyMethod_Fini to clear up the free_list
#define CPPOverload_MAXFREELIST
#define CO_NOFREE
#define CPPYY_BOOLEAN_PROPERTY(name, flag, label)
PyObject * fCallable
#define Py_TYPE(ob)
Definition CPyCppyy.h:217
#define Py_RETURN_TRUE
Definition CPyCppyy.h:293
#define Py_RETURN_FALSE
Definition CPyCppyy.h:297
int Py_ssize_t
Definition CPyCppyy.h:236
#define CPyCppyy_PyText_Append
Definition CPyCppyy.h:104
#define CPyCppyy_PyText_AsString
Definition CPyCppyy.h:97
#define CPyCppyy_PyText_AppendAndDel
Definition CPyCppyy.h:105
#define CPyCppyy_PyText_FromFormat
Definition CPyCppyy.h:101
#define Py_RETURN_NONE
Definition CPyCppyy.h:289
#define CPyCppyy_PyText_FromString
Definition CPyCppyy.h:102
#define CPyCppyy_PyText_Check
Definition CPyCppyy.h:95
#define PyVarObject_HEAD_INIT(type, size)
Definition CPyCppyy.h:215
unsigned int fFlags
_object PyObject
winID h TVirtualViewer3D TVirtualGLPainter p
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h offset
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t result
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void value
char name[80]
Definition TGX11.cxx:110
void MergeOverload(CPPOverload *meth)
void AdoptMethod(PyCallable *pc)
MethodInfo_t * fMethodInfo
Definition CPPOverload.h:68
std::vector< PyCallable * > Methods_t
Definition CPPOverload.h:39
void Set(const std::string &name, std::vector< PyCallable * > &methods)
PyObject * gLifeLine
Definition PyStrings.cxx:24
PyObject * gThisModule
Definition PyStrings.cxx:54
PyObject * gNoImplicit
Definition PyStrings.cxx:56
size_t FetchError(std::vector< PyError_t > &)
Definition Utility.cxx:881
void SetDetailedException(std::vector< PyError_t > &errors, PyObject *topmsg, PyObject *defexc)
Definition Utility.cxx:893
Set of helper functions that are invoked from the pythonizors, on the Python side.
bool HaveImplicit(CallContext *ctxt)
PyObject * GetScopeProxy(Cppyy::TCppScope_t)
static PyMethodObject * free_list
bool NoImplicit(CallContext *ctxt)
static int numfree
bool IsCreator(uint64_t flags)
bool CPPInstance_Check(T *object)
PyTypeObject CPPOverload_Type
uint64_t HashSignature(PyObject *args)
Definition CPPOverload.h:16
bool IsConstructor(uint64_t flags)
bool IsSorted(uint64_t flags)
RPY_EXPORTED size_t SizeOf(TCppType_t klass)
RPY_EXPORTED TCppFuncAddr_t GetFunctionAddress(TCppMethod_t method, bool check_enabled=true)
void * TCppFuncAddr_t
Definition cpp_cppyy.h:25
void(off) SmallVectorTemplateBase< T
CPPOverload::DispatchMap_t fDispatchMap
Definition CPPOverload.h:46
CPPOverload::Methods_t fMethods
Definition CPPOverload.h:47
static ECallFlags sMemoryPolicy
Definition CallContext.h:71
static void Clear(PyError_t &e)
Definition Utility.h:82