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