Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
TemplateProxy.cxx
Go to the documentation of this file.
1// Bindings
2#include "CPyCppyy.h"
3#include "TemplateProxy.h"
4#include "CPPClassMethod.h"
5#include "CPPConstructor.h"
6#include "CPPFunction.h"
7#include "CPPMethod.h"
8#include "CPPOverload.h"
9#include "PyCallable.h"
10#include "PyStrings.h"
11#include "Utility.h"
12
13// Standard
14#include <algorithm>
15
16
17namespace CPyCppyy {
18
19//- helper for ctypes conversions --------------------------------------------
20static PyObject* TC2CppName(PyObject* pytc, const char* cpd, bool allow_voidp)
21{
22 const char* name = nullptr;
23 if (CPyCppyy_PyText_Check(pytc)) {
24 char tc = ((char*)CPyCppyy_PyText_AsString(pytc))[0];
25 switch (tc) {
26 case '?': name = "bool"; break;
27 case 'c': name = "char"; break;
28 case 'b': name = "char"; break;
29 case 'B': name = "unsigned char"; break;
30 case 'h': name = "short"; break;
31 case 'H': name = "unsigned short"; break;
32 case 'i': name = "int"; break;
33 case 'I': name = "unsigned int"; break;
34 case 'l': name = "long"; break;
35 case 'L': name = "unsigned long"; break;
36 case 'q': name = "long long"; break;
37 case 'Q': name = "unsigned long long"; break;
38 case 'f': name = "float"; break;
39 case 'd': name = "double"; break;
40 case 'g': name = "long double"; break;
41 case 'z': // special case for C strings, ignore cpd
42 return CPyCppyy_PyText_FromString(std::string{"const char*"}.c_str());
43 default: name = (allow_voidp ? "void*" : nullptr); break;
44 }
45 }
46
47 if (name)
48 return CPyCppyy_PyText_FromString((std::string{name}+cpd).c_str());
49 return nullptr;
50}
51
52//----------------------------------------------------------------------------
53TemplateInfo::TemplateInfo() : fPyClass(nullptr), fNonTemplated(nullptr),
54 fTemplated(nullptr), fLowPriority(nullptr), fDoc(nullptr)
55{
56 /* empty */
57}
58
59//----------------------------------------------------------------------------
61{
62 Py_XDECREF(fPyClass);
63
64 Py_XDECREF(fDoc);
65 Py_DECREF(fNonTemplated);
66 Py_DECREF(fTemplated);
67 Py_DECREF(fLowPriority);
68
69 for (const auto& p : fDispatchMap) {
70 for (const auto& c : p.second) {
71 Py_DECREF(c.second);
72 }
73 }
74}
75
76
77//----------------------------------------------------------------------------
79// Store overloads of this templated method.
80 bool isGreedy = false;
81 for (auto pc : mp->fMethodInfo->fMethods) {
82 if (pc->IsGreedy()) {
83 isGreedy = true;
84 break;
85 }
86 }
87
88 CPPOverload* cppol = isGreedy ? fTI->fLowPriority : fTI->fNonTemplated;
89 cppol->MergeOverload(mp);
90}
91
93// Store overload of this templated method.
94 CPPOverload* cppol = pc->IsGreedy() ? fTI->fLowPriority : fTI->fNonTemplated;
95 cppol->AdoptMethod(pc);
96}
97
99{
100// Store known template methods.
101 fTI->fTemplated->AdoptMethod(pc);
102}
103
104//----------------------------------------------------------------------------
105PyObject* TemplateProxy::Instantiate(const std::string& fname,
106 CPyCppyy_PyArgs_t args, size_t nargsf, Utility::ArgPreference pref, int* pcnt)
107{
108// Instantiate (and cache) templated methods, return method if any
109 std::string proto = "";
110
111#if PY_VERSION_HEX >= 0x03080000
112 bool isNS = (((CPPScope*)fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
113 if (!isNS && !fSelf && CPyCppyy_PyArgs_GET_SIZE(args, nargsf)) {
114 args += 1;
115 nargsf -= 1;
116 }
117#endif
118
119 Py_ssize_t argc = CPyCppyy_PyArgs_GET_SIZE(args, nargsf);
120 if (argc != 0) {
121 PyObject* tpArgs = PyTuple_New(argc);
122 for (Py_ssize_t i = 0; i < argc; ++i) {
123 PyObject* itemi = CPyCppyy_PyArgs_GET_ITEM(args, i);
124
125 bool bArgSet = false;
126
127 // special case for arrays
128 PyObject* pytc = PyObject_GetAttr(itemi, PyStrings::gTypeCode);
129 if (pytc) {
130 PyObject* pyptrname = TC2CppName(pytc, "*", true);
131 if (pyptrname) {
132 PyTuple_SET_ITEM(tpArgs, i, pyptrname);
133 bArgSet = true;
134 // string added, but not counted towards nStrings
135 }
136 Py_DECREF(pytc); pytc = nullptr;
137 } else
138 PyErr_Clear();
139
140 // if not arg set, try special case for ctypes
141 if (!bArgSet) pytc = PyObject_GetAttr(itemi, PyStrings::gCTypesType);
142
143 if (!bArgSet && pytc) {
144 PyObject* pyactname = TC2CppName(pytc, "&", false);
145 if (!pyactname) {
146 // _type_ of a pointer to c_type is that type, which will have a type
147 PyObject* newpytc = PyObject_GetAttr(pytc, PyStrings::gCTypesType);
148 Py_DECREF(pytc);
149 pytc = newpytc;
150 if (pytc) {
151 pyactname = TC2CppName(pytc, "*", false);
152 } else
153 PyErr_Clear();
154 }
155 Py_XDECREF(pytc); pytc = nullptr;
156 if (pyactname) {
157 PyTuple_SET_ITEM(tpArgs, i, pyactname);
158 bArgSet = true;
159 // string added, but not counted towards nStrings
160 }
161 } else
162 PyErr_Clear();
163
164 if (!bArgSet) {
165 // normal case (may well fail)
166 PyErr_Clear();
167 PyObject* tp = (PyObject*)Py_TYPE(itemi);
168 Py_INCREF(tp);
169 PyTuple_SET_ITEM(tpArgs, i, tp);
170 }
171 }
172
173#if PY_VERSION_HEX >= 0x03080000
174 PyObject* pyargs = PyTuple_New(argc);
175 for (Py_ssize_t i = 0; i < argc; ++i) {
176 PyObject* item = CPyCppyy_PyArgs_GET_ITEM(args, i);
177 Py_INCREF(item);
178 PyTuple_SET_ITEM(pyargs, i, item);
179 }
180#else
181 Py_INCREF(args);
182 PyObject* pyargs = args;
183#endif
184 const std::string& name_v1 = \
185 Utility::ConstructTemplateArgs(nullptr, tpArgs, pyargs, pref, 0, pcnt);
186
187 Py_DECREF(pyargs);
188 Py_DECREF(tpArgs);
189 if (name_v1.size())
190 proto = name_v1.substr(1, name_v1.size()-2);
191 }
192
193// the following causes instantiation as necessary
194 Cppyy::TCppScope_t scope = ((CPPClass*)fTI->fPyClass)->fCppType;
195 Cppyy::TCppMethod_t cppmeth = Cppyy::GetMethodTemplate(scope, fname, proto);
196 if (cppmeth) { // overload stops here
197 // A successful instantiation needs to be cached to pre-empt future instantiations. There
198 // are two names involved, the original asked (which may be partial) and the received.
199 //
200 // Caching scheme: if the match is exact, simply add the overload to the pre-existing
201 // one, or create a new overload for later lookups. If the match is not exact, do the
202 // same, but also create an alias. Only add exact matches to the set of known template
203 // instantiations, to prevent piling on from different partial instantiations.
204 //
205 // TODO: this caches the lookup method before the call, meaning that failing overloads
206 // can add already existing overloads to the set of methods.
207
208 std::string resname = Cppyy::GetMethodFullName(cppmeth);
209
210 // An initializer_list is preferred for the argument types, but should not leak into
211 // the argument types. If it did, replace with vector and lookup anew.
212 if (resname.find("initializer_list") != std::string::npos) {
213 auto pos = proto.find("initializer_list");
214 while (pos != std::string::npos) {
215 proto.replace(pos, 16, "vector");
216 pos = proto.find("initializer_list", pos + 6);
217 }
218
220 if (m2 && m2 != cppmeth) {
221 // replace if the new method with vector was found; otherwise just continue
222 // with the previously found method with initializer_list.
223 cppmeth = m2;
224 resname = Cppyy::GetMethodFullName(cppmeth);
225 }
226 }
227
228 bool bExactMatch = fname == resname;
229
230 // lookup on existing name in case this was an overload, not a caching, failure
231 PyObject* dct = PyObject_GetAttr(fTI->fPyClass, PyStrings::gDict);
232 PyObject* pycachename = CPyCppyy_PyText_InternFromString(fname.c_str());
233 PyObject* pyol = PyObject_GetItem(dct, pycachename);
234 if (!pyol) PyErr_Clear();
235 bool bIsCppOL = CPPOverload_Check(pyol);
236
237 if (pyol && !bIsCppOL && !TemplateProxy_Check(pyol)) {
238 // unknown object ... leave well alone
239 Py_DECREF(pyol);
240 Py_DECREF(pycachename);
241 Py_DECREF(dct);
242 return nullptr;
243 }
244
245 // find the full name if the requested one was partial
246 PyObject* exact = nullptr;
247 PyObject* pyresname = CPyCppyy_PyText_FromString(resname.c_str());
248 if (!bExactMatch) {
249 exact = PyObject_GetItem(dct, pyresname);
250 if (!exact) PyErr_Clear();
251 }
252 Py_DECREF(dct);
253
254 bool bIsConstructor = false, bNeedsRebind = true;
255
256 PyCallable* meth = nullptr;
257 if (Cppyy::IsNamespace(scope)) {
258 meth = new CPPFunction(scope, cppmeth);
259 bNeedsRebind = false;
260 } else if (Cppyy::IsStaticMethod(cppmeth)) {
261 meth = new CPPClassMethod(scope, cppmeth);
262 bNeedsRebind = false;
263 } else if (Cppyy::IsConstructor(cppmeth)) {
264 bIsConstructor = true;
265 meth = new CPPConstructor(scope, cppmeth);
266 } else
267 meth = new CPPMethod(scope, cppmeth);
268
269 // Case 1/2: method simply did not exist before
270 if (!pyol) {
271 // actual overload to use (now owns meth)
272 pyol = (PyObject*)CPPOverload_New(fname, meth);
273 if (bIsConstructor) {
274 // TODO: this is an ugly hack :(
275 ((CPPOverload*)pyol)->fMethodInfo->fFlags |= \
277 }
278
279 // add to class dictionary
280 PyType_Type.tp_setattro(fTI->fPyClass, pycachename, pyol);
281 }
282
283 // Case 3/4: pre-existing method that was either not found b/c the full
284 // templated name was constructed in this call or it failed as overload
285 else if (bIsCppOL) {
286 // TODO: see above, since the call hasn't happened yet, this overload may
287 // already exist and fail again.
288 ((CPPOverload*)pyol)->AdoptMethod(meth); // takes ownership
289 }
290
291 // Case 5: must be a template proxy, meaning that current template name is not
292 // a template overload
293 else {
294 ((TemplateProxy*)pyol)->AdoptTemplate(meth->Clone());
295 Py_DECREF(pyol);
296 pyol = (PyObject*)CPPOverload_New(fname, meth); // takes ownership
297 }
298
299 // Special Case if name was aliased (e.g. typedef in template instantiation)
300 if (!exact && !bExactMatch) {
301 PyType_Type.tp_setattro(fTI->fPyClass, pyresname, pyol);
302 }
303
304 // cleanup
305 Py_DECREF(pyresname);
306 Py_DECREF(pycachename);
307
308 // retrieve fresh (for boundedness) and call
309 PyObject* pymeth =
310 CPPOverload_Type.tp_descr_get(pyol, bNeedsRebind ? fSelf : nullptr, (PyObject*)&CPPOverload_Type);
311 Py_DECREF(pyol);
312 return pymeth;
313 }
314
315 PyErr_Format(PyExc_TypeError, "Failed to instantiate \"%s(%s)\"", fname.c_str(), proto.c_str());
316 return nullptr;
317}
318
319
320//= CPyCppyy template proxy construction/destruction =========================
321static TemplateProxy* tpp_new(PyTypeObject*, PyObject*, PyObject*)
322{
323// Create a new empty template method proxy.
324 TemplateProxy* pytmpl = PyObject_GC_New(TemplateProxy, &TemplateProxy_Type);
325 pytmpl->fSelf = nullptr;
326 pytmpl->fTemplateArgs = nullptr;
327 pytmpl->fWeakrefList = nullptr;
328 new (&pytmpl->fTI) TP_TInfo_t{};
329 pytmpl->fTI = std::make_shared<TemplateInfo>();
330
331 PyObject_GC_Track(pytmpl);
332 return pytmpl;
333}
334
335//----------------------------------------------------------------------------
337{
338 return (Py_hash_t)self;
339}
340
341//----------------------------------------------------------------------------
342static PyObject* tpp_richcompare(TemplateProxy* self, PyObject* other, int op)
343{
344 if (op == Py_EQ || op == Py_NE) {
345 if (!TemplateProxy_CheckExact(other))
347
348 if (self->fTI == ((TemplateProxy*)other)->fTI)
350
352 }
353
354 Py_INCREF(Py_NotImplemented);
355 return Py_NotImplemented;
356}
357
358//----------------------------------------------------------------------------
359static int tpp_clear(TemplateProxy* pytmpl)
360{
361// Garbage collector clear of held python member objects.
362 Py_CLEAR(pytmpl->fSelf);
363 Py_CLEAR(pytmpl->fTemplateArgs);
364
365 return 0;
366}
367
368//----------------------------------------------------------------------------
369static void tpp_dealloc(TemplateProxy* pytmpl)
370{
371// Destroy the given template method proxy.
372 if (pytmpl->fWeakrefList)
373 PyObject_ClearWeakRefs((PyObject*)pytmpl);
374 PyObject_GC_UnTrack(pytmpl);
375 tpp_clear(pytmpl);
376 pytmpl->fTI.~TP_TInfo_t();
377 PyObject_GC_Del(pytmpl);
378}
379
380//----------------------------------------------------------------------------
381static int tpp_traverse(TemplateProxy* pytmpl, visitproc visit, void* arg)
382{
383// Garbage collector traverse of held python member objects.
384 Py_VISIT(pytmpl->fSelf);
385 Py_VISIT(pytmpl->fTemplateArgs);
386
387 return 0;
388}
389
390//----------------------------------------------------------------------------
391static PyObject* tpp_doc(TemplateProxy* pytmpl, void*)
392{
393 if (pytmpl->fTI->fDoc) {
394 Py_INCREF(pytmpl->fTI->fDoc);
395 return pytmpl->fTI->fDoc;
396 }
397
398// Forward to method proxies to doc all overloads
399 PyObject* doc = nullptr;
400 if (pytmpl->fTI->fNonTemplated->HasMethods())
401 doc = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fNonTemplated, "__doc__");
402 if (pytmpl->fTI->fTemplated->HasMethods()) {
403 PyObject* doc2 = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fTemplated, "__doc__");
404 if (doc && doc2) {
407 } else if (!doc && doc2) {
408 doc = doc2;
409 }
410 }
411 if (pytmpl->fTI->fLowPriority->HasMethods()) {
412 PyObject* doc2 = PyObject_GetAttrString((PyObject*)pytmpl->fTI->fLowPriority, "__doc__");
413 if (doc && doc2) {
416 } else if (!doc && doc2) {
417 doc = doc2;
418 }
419 }
420
421 if (doc)
422 return doc;
423
425}
426
427static int tpp_doc_set(TemplateProxy* pytmpl, PyObject *val, void *)
428{
429 Py_XDECREF(pytmpl->fTI->fDoc);
430 Py_INCREF(val);
431 pytmpl->fTI->fDoc = val;
432 return 0;
433}
434
435//----------------------------------------------------------------------------
436
437//= CPyCppyy template proxy callable behavior ================================
438
439#define TPPCALL_RETURN \
440{ if (!errors.empty()) \
441 std::for_each(errors.begin(), errors.end(), Utility::PyError_t::Clear);\
442 return result; }
443
444static inline std::string targs2str(TemplateProxy* pytmpl)
445{
446 if (!pytmpl || !pytmpl->fTemplateArgs) return "";
448}
449
450static inline void UpdateDispatchMap(TemplateProxy* pytmpl, bool use_targs, uint64_t sighash, CPPOverload* pymeth)
451{
452// Memoize a method in the dispatch map after successful call; replace old if need be (may be
453// with the same CPPOverload, just with more methods).
454 bool bInserted = false;
455 auto& v = pytmpl->fTI->fDispatchMap[use_targs ? targs2str(pytmpl) : ""];
456
457 Py_INCREF(pymeth);
458 for (auto& p : v) {
459 if (p.first == sighash) {
460 Py_DECREF(p.second);
461 p.second = pymeth;
462 bInserted = true;
463 }
464 }
465 if (!bInserted) v.push_back(std::make_pair(sighash, pymeth));
466}
467
468static inline PyObject* SelectAndForward(TemplateProxy* pytmpl, CPPOverload* pymeth,
469 CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds,
470 bool implicitOkay, bool use_targs, uint64_t sighash, std::vector<Utility::PyError_t>& errors)
471{
472// Forward a call to known overloads, if any.
473 if (pymeth->HasMethods()) {
474 PyObject* pycall = CPPOverload_Type.tp_descr_get(
475 (PyObject*)pymeth, pytmpl->fSelf, (PyObject*)&CPPOverload_Type);
476
477 if (!implicitOkay)
478 ((CPPOverload*)pycall)->fFlags |= CallContext::kNoImplicit;
479
480 // now call the method with the arguments (loops internally)
481 PyObject* result = CPyCppyy_tp_call(pycall, args, nargsf, kwds);
482 Py_DECREF(pycall);
483 if (result) {
484 UpdateDispatchMap(pytmpl, use_targs, sighash, pymeth);
486 }
487 Utility::FetchError(errors);
488 }
489
490 return nullptr;
491}
492
493static inline PyObject* CallMethodImp(TemplateProxy* pytmpl, PyObject*& pymeth,
494 CPyCppyy_PyArgs_t args, size_t nargsf, PyObject* kwds, bool impOK, uint64_t sighash)
495{
496// Actual call of a given overload: takes care of handlign of "self" and
497// dereferences the overloaded method after use.
498
500 if (!impOK && CPPOverload_Check(pymeth))
501 ((CPPOverload*)pymeth)->fFlags |= CallContext::kNoImplicit;
502 bool isNS = (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
503 if (isNS && pytmpl->fSelf && pytmpl->fSelf != Py_None) {
504 // this is a global method added a posteriori to the class
505 PyCallArgs cargs{(CPPInstance*&)pytmpl->fSelf, args, nargsf, kwds};
506 AdjustSelf(cargs);
507 result = CPyCppyy_tp_call(pymeth, cargs.fArgs, cargs.fNArgsf, cargs.fKwds);
508 } else {
509 if (!pytmpl->fSelf && CPPOverload_Check(pymeth))
510 ((CPPOverload*)pymeth)->fFlags &= ~CallContext::kFromDescr;
511 result = CPyCppyy_tp_call(pymeth, args, nargsf, kwds);
512 }
513
514 if (result) {
515 Py_XDECREF(((CPPOverload*)pymeth)->fSelf); ((CPPOverload*)pymeth)->fSelf = nullptr; // unbind
516 UpdateDispatchMap(pytmpl, true, sighash, (CPPOverload*)pymeth);
517 }
518
519 Py_DECREF(pymeth); pymeth = nullptr;
520 return result;
521}
522
523#if PY_VERSION_HEX >= 0x03080000
524static PyObject* tpp_vectorcall(
525 TemplateProxy* pytmpl, PyObject* const *args, size_t nargsf, PyObject* kwds)
526#else
527static PyObject* tpp_call(TemplateProxy* pytmpl, PyObject* args, PyObject* kwds)
528#endif
529{
530// Dispatcher to the actual member method, several uses possible; in order:
531//
532// case 1: explicit template previously selected through subscript
533//
534// case 2: select known non-template overload
535//
536// obj.method(a0, a1, ...)
537// => obj->method(a0, a1, ...) // non-template
538//
539// case 3: select known template overload
540//
541// obj.method(a0, a1, ...)
542// => obj->method(a0, a1, ...) // all known templates
543//
544// case 4: auto-instantiation from types of arguments
545//
546// obj.method(a0, a1, ...)
547// => obj->method<type(a0), type(a1), ...>(a0, a1, ...)
548//
549// Note: explicit instantiation needs to use [] syntax:
550//
551// obj.method[type<a0>, type<a1>, ...](a0, a1, ...)
552//
553// case 5: low priority methods, such as ones that take void* arguments
554//
555
556// TODO: should previously instantiated templates be considered first?
557
558#if PY_VERSION_HEX < 0x03080000
559 size_t nargsf = PyTuple_GET_SIZE(args);
560#endif
561
562 PyObject *pymeth = nullptr, *result = nullptr;
563
564// short-cut through memoization map
565 Py_ssize_t argc = CPyCppyy_PyArgs_GET_SIZE(args, nargsf);
566 uint64_t sighash = HashSignature(args, argc);
567
568 CPPOverload* ol = nullptr;
569 if (!pytmpl->fTemplateArgs) {
570 // look for known signatures ...
571 auto& v = pytmpl->fTI->fDispatchMap[""];
572 for (const auto& p : v) {
573 if (p.first == sighash) {
574 ol = p.second;
575 break;
576 }
577 }
578
579 if (ol != nullptr) {
580 if (!pytmpl->fSelf || pytmpl->fSelf == Py_None) {
581 result = CPyCppyy_tp_call((PyObject*)ol, args, nargsf, kwds);
582 } else {
583 pymeth = CPPOverload_Type.tp_descr_get(
584 (PyObject*)ol, pytmpl->fSelf, (PyObject*)&CPPOverload_Type);
585 result = CPyCppyy_tp_call(pymeth, args, nargsf, kwds);
586 Py_DECREF(pymeth); pymeth = nullptr;
587 }
588 if (result)
589 return result;
590 }
591 }
592
593// container for collecting errors
594 std::vector<Utility::PyError_t> errors;
595 if (ol) Utility::FetchError(errors);
596
597// case 1: explicit template previously selected through subscript
598 if (pytmpl->fTemplateArgs) {
599 // instantiate explicitly
600 PyObject* pyfullname = CPyCppyy_PyText_FromString(pytmpl->fTI->fCppName.c_str());
601 CPyCppyy_PyText_Append(&pyfullname, pytmpl->fTemplateArgs);
602
603 // first, lookup by full name, if previously stored
604 bool isNS = (((CPPScope*)pytmpl->fTI->fPyClass)->fFlags & CPPScope::kIsNamespace);
605 if (pytmpl->fSelf && pytmpl->fSelf != Py_None && !isNS)
606 pymeth = PyObject_GetAttr(pytmpl->fSelf, pyfullname);
607 else // by-passes custom scope getattr that searches into Cling
608 pymeth = PyType_Type.tp_getattro(pytmpl->fTI->fPyClass, pyfullname);
609
610 // attempt call if found (this may fail if there are specializations)
611 if (CPPOverload_Check(pymeth)) {
612 // since the template args are fully explicit, allow implicit conversion of arguments
613 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash);
614 if (result) {
615 Py_DECREF(pyfullname);
617 }
618 Utility::FetchError(errors);
619 } else if (pymeth && PyCallable_Check(pymeth)) {
620 // something different (user provided?)
621 result = CPyCppyy_PyObject_Call(pymeth, args, nargsf, kwds);
622 Py_DECREF(pymeth);
623 if (result) {
624 Py_DECREF(pyfullname);
626 }
627 Utility::FetchError(errors);
628 } else if (!pymeth)
629 PyErr_Clear();
630
631 // not cached or failed call; try instantiation
632 pymeth = pytmpl->Instantiate(
633 CPyCppyy_PyText_AsString(pyfullname), args, nargsf, Utility::kNone);
634 if (pymeth) {
635 // attempt actual call; same as above, allow implicit conversion of arguments
636 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, true, sighash);
637 if (result) {
638 Py_DECREF(pyfullname);
640 }
641 }
642
643 // no drop through if failed (if implicit was desired, don't provide template args)
644 Utility::FetchError(errors);
646 "Could not find \"%s\" (set cppyy.set_debug() for C++ errors):", CPyCppyy_PyText_AsString(pyfullname));
647 Py_DECREF(pyfullname);
648 Utility::SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */);
649
650 return nullptr;
651 }
652
653// case 2: select known non-template overload
654 result = SelectAndForward(pytmpl, pytmpl->fTI->fNonTemplated, args, nargsf, kwds,
655 true /* implicitOkay */, false /* use_targs */, sighash, errors);
656 if (result)
658
659// case 3: select known template overload
660 result = SelectAndForward(pytmpl, pytmpl->fTI->fTemplated, args, nargsf, kwds,
661 false /* implicitOkay */, true /* use_targs */, sighash, errors);
662 if (result)
664
665// case 4: auto-instantiation from types of arguments
667 // TODO: no need to loop if there are no non-instance arguments; also, should any
668 // failed lookup be removed?
669 int pcnt = 0;
670 pymeth = pytmpl->Instantiate(pytmpl->fTI->fCppName, args, nargsf, pref, &pcnt);
671 if (pymeth) {
672 // attempt actual call; argument based, so do not allow implicit conversions
673 result = CallMethodImp(pytmpl, pymeth, args, nargsf, kwds, false, sighash);
675 }
676 Utility::FetchError(errors);
677 if (!pcnt) break; // preference never used; no point trying others
678 }
679
680// case 5: low priority methods, such as ones that take void* arguments
681 result = SelectAndForward(pytmpl, pytmpl->fTI->fLowPriority, args, nargsf, kwds,
682 false /* implicitOkay */, false /* use_targs */, sighash, errors);
683 if (result)
685
686// error reporting is fraud, given the numerous steps taken, but more details seems better
687 if (!errors.empty()) {
688 PyObject* topmsg = CPyCppyy_PyText_FromString("Template method resolution failed:");
689 Utility::SetDetailedException(errors, topmsg /* steals */, PyExc_TypeError /* default error */);
690 } else {
691 PyErr_Format(PyExc_TypeError, "cannot resolve method template call for \'%s\'",
692 pytmpl->fTI->fCppName.c_str());
693 }
694
695 return nullptr;
696}
697
698//----------------------------------------------------------------------------
700{
701// create and use a new template proxy (language requirement)
703
704// new method is to be bound to current object (may be nullptr)
705 if (pyobj) {
706 Py_INCREF(pyobj);
707 newPyTmpl->fSelf = pyobj;
708 } else {
709 Py_INCREF(Py_None);
710 newPyTmpl->fSelf = Py_None;
711 }
712
713 Py_XINCREF(pytmpl->fTemplateArgs);
714 newPyTmpl->fTemplateArgs = pytmpl->fTemplateArgs;
715
716// copy name, class, etc. pointers
717 new (&newPyTmpl->fTI) std::shared_ptr<TemplateInfo>{pytmpl->fTI};
718
719#if PY_VERSION_HEX >= 0x03080000
720 newPyTmpl->fVectorCall = pytmpl->fVectorCall;
721#endif
722
723 return newPyTmpl;
724}
725
726
727//----------------------------------------------------------------------------
729{
730// Explicit template member lookup/instantiation; works by re-bounding. This method can
731// not cache overloads as instantiations need not be unique for the argument types due
732// to template specializations.
733 TemplateProxy* typeBoundMethod = tpp_descr_get(pytmpl, pytmpl->fSelf, nullptr);
734 Py_XDECREF(typeBoundMethod->fTemplateArgs);
735 typeBoundMethod->fTemplateArgs = CPyCppyy_PyText_FromString(
736 Utility::ConstructTemplateArgs(nullptr, args).c_str());
737 return (PyObject*)typeBoundMethod;
738}
739
740//-----------------------------------------------------------------------------
742{
743 return PyInt_FromLong(0); // dummy (__useffi__ unused)
744}
745
746//-----------------------------------------------------------------------------
747static int tpp_setuseffi(CPPOverload*, PyObject*, void*)
748{
749 return 0; // dummy (__useffi__ unused)
750}
751
752
753//----------------------------------------------------------------------------
754static PyMappingMethods tpp_as_mapping = {
755 nullptr, (binaryfunc)tpp_subscript, nullptr
756};
757
758static PyGetSetDef tpp_getset[] = {
759 {(char*)"__doc__", (getter)tpp_doc, (setter)tpp_doc_set, nullptr, nullptr},
760 {(char*)"__useffi__", (getter)tpp_getuseffi, (setter)tpp_setuseffi,
761 (char*)"unused", nullptr},
762 {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}
763};
764
765
766//----------------------------------------------------------------------------
767void TemplateProxy::Set(const std::string& cppname, const std::string& pyname, PyObject* pyclass)
768{
769// Initialize the proxy for the given 'pyclass.'
770 fSelf = nullptr;
771 fTemplateArgs = nullptr;
772
773 fTI->fCppName = cppname;
774 Py_XINCREF(pyclass);
775 fTI->fPyClass = pyclass;
776
777 std::vector<PyCallable*> dummy;
778 fTI->fNonTemplated = CPPOverload_New(pyname, dummy);
779 fTI->fTemplated = CPPOverload_New(pyname, dummy);
780 fTI->fLowPriority = CPPOverload_New(pyname, dummy);
781
782#if PY_VERSION_HEX >= 0x03080000
783 fVectorCall = (vectorcallfunc)tpp_vectorcall;
784#endif
785}
786
787
788//= CPyCppyy method proxy access to internals ================================
790{
791// Select and call a specific C++ overload, based on its signature.
792 const char* sigarg = nullptr;
793 PyObject* sigarg_tuple = nullptr;
794 int want_const = -1;
795
798 std::string proto;
799
800 if (PyArg_ParseTuple(args, const_cast<char*>("s|i:__overload__"), &sigarg, &want_const)) {
801 want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const;
802
803 // check existing overloads in order
804 PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg, want_const);
805 if (ol) return ol;
806 PyErr_Clear();
807 ol = pytmpl->fTI->fTemplated->FindOverload(sigarg, want_const);
808 if (ol) return ol;
809 PyErr_Clear();
810 ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg, want_const);
811 if (ol) return ol;
812
813 proto = Utility::ConstructTemplateArgs(nullptr, args);
814
815 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
816 cppmeth = Cppyy::GetMethodTemplate(
817 scope, pytmpl->fTI->fCppName, proto.substr(1, proto.size()-2));
818 } else if (PyArg_ParseTuple(args, const_cast<char*>("O|i:__overload__"), &sigarg_tuple, &want_const)) {
819 PyErr_Clear();
820 want_const = PyTuple_GET_SIZE(args) == 1 ? -1 : want_const;
821
822 // check existing overloads in order
823 PyObject* ol = pytmpl->fTI->fNonTemplated->FindOverload(sigarg_tuple, want_const);
824 if (ol) return ol;
825 PyErr_Clear();
826 ol = pytmpl->fTI->fTemplated->FindOverload(sigarg_tuple, want_const);
827 if (ol) return ol;
828 PyErr_Clear();
829 ol = pytmpl->fTI->fLowPriority->FindOverload(sigarg_tuple, want_const);
830 if (ol) return ol;
831
832 proto.reserve(128);
833 proto.push_back('<');
834 Py_ssize_t n = PyTuple_Size(sigarg_tuple);
835 for (int i = 0; i < n; i++) {
836 PyObject *pItem = PyTuple_GetItem(sigarg_tuple, i);
837 if(!CPyCppyy_PyText_Check(pItem)) {
838 PyErr_Format(PyExc_LookupError, "argument types should be in string format");
839 return (PyObject*) nullptr;
840 }
841 proto.append(CPyCppyy_PyText_AsString(pItem));
842 if (i < n - 1)
843 proto.push_back(',');
844 }
845 proto.push_back('>');
846
847 scope = ((CPPClass*)pytmpl->fTI->fPyClass)->fCppType;
848 cppmeth = Cppyy::GetMethodTemplate(
849 scope, pytmpl->fTI->fCppName, proto.substr(1, proto.size()-2));
850 } else {
851 PyErr_Format(PyExc_TypeError, "Unexpected arguments to __overload__");
852 return nullptr;
853 }
854
855// else attempt instantiation
856 PyObject* pytype = 0, *pyvalue = 0, *pytrace = 0;
857 PyErr_Fetch(&pytype, &pyvalue, &pytrace);
858
859 if (!cppmeth) {
860 PyErr_Restore(pytype, pyvalue, pytrace);
861 return nullptr;
862 }
863
864 Py_XDECREF(pytype);
865 Py_XDECREF(pyvalue);
866 Py_XDECREF(pytrace);
867
868 // TODO: the next step should be consolidated with Instantiate()
869 PyCallable* meth = nullptr;
870 if (Cppyy::IsNamespace(scope)) {
871 meth = new CPPFunction(scope, cppmeth);
872 } else if (Cppyy::IsStaticMethod(cppmeth)) {
873 meth = new CPPClassMethod(scope, cppmeth);
874 } else if (Cppyy::IsConstructor(cppmeth)) {
875 meth = new CPPConstructor(scope, cppmeth);
876 } else
877 meth = new CPPMethod(scope, cppmeth);
878
879 return (PyObject*)CPPOverload_New(pytmpl->fTI->fCppName+proto, meth);
880}
881
882static PyMethodDef tpp_methods[] = {
883 {(char*)"__overload__", (PyCFunction)tpp_overload, METH_VARARGS,
884 (char*)"select overload for dispatch" },
885 {(char*)nullptr, nullptr, 0, nullptr }
886};
887
888
889//= CPyCppyy template proxy type =============================================
890PyTypeObject TemplateProxy_Type = {
891 PyVarObject_HEAD_INIT(&PyType_Type, 0)
892 (char*)"cppyy.TemplateProxy", // tp_name
893 sizeof(TemplateProxy), // tp_basicsize
894 0, // tp_itemsize
895 (destructor)tpp_dealloc, // tp_dealloc
896#if PY_VERSION_HEX >= 0x03080000
897 offsetof(TemplateProxy, fVectorCall),
898#else
899 0, // tp_vectorcall_offset / tp_print
900#endif
901 0, // tp_getattr
902 0, // tp_setattr
903 0, // tp_as_async / tp_compare
904 0, // tp_repr
905 0, // tp_as_number
906 0, // tp_as_sequence
907 &tpp_as_mapping, // tp_as_mapping
908 (hashfunc)tpp_hash, // tp_hash
909#if PY_VERSION_HEX >= 0x03080000
910 (ternaryfunc)PyVectorcall_Call, // tp_call
911#else
912 (ternaryfunc)tpp_call, // tp_call
913#endif
914 0, // tp_str
915 0, // tp_getattro
916 0, // tp_setattro
917 0, // tp_as_buffer
918 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC
919#if PY_VERSION_HEX >= 0x03080000
920 | Py_TPFLAGS_HAVE_VECTORCALL | Py_TPFLAGS_METHOD_DESCRIPTOR
921#endif
922#if PY_VERSION_HEX >= 0x03120000
923 | Py_TPFLAGS_MANAGED_WEAKREF
924#endif
925 , // tp_flags
926 (char*)"cppyy template proxy (internal)", // tp_doc
927 (traverseproc)tpp_traverse, // tp_traverse
928 (inquiry)tpp_clear, // tp_clear
929 (richcmpfunc)tpp_richcompare, // tp_richcompare
930 offsetof(TemplateProxy, fWeakrefList), // tp_weaklistoffset
931 0, // tp_iter
932 0, // tp_iternext
933 tpp_methods, // tp_methods
934 0, // tp_members
935 tpp_getset, // tp_getset
936 0, // tp_base
937 0, // tp_dict
938 (descrgetfunc)tpp_descr_get, // tp_descr_get
939 0, // tp_descr_set
940 0, // tp_dictoffset
941 0, // tp_init
942 0, // tp_alloc
943 (newfunc)tpp_new, // tp_new
944 0, // tp_free
945 0, // tp_is_gc
946 0, // tp_bases
947 0, // tp_mro
948 0, // tp_cache
949 0, // tp_subclasses
950 0 // tp_weaklist
951#if PY_VERSION_HEX >= 0x02030000
952 , 0 // tp_del
953#endif
954#if PY_VERSION_HEX >= 0x02060000
955 , 0 // tp_version_tag
956#endif
957#if PY_VERSION_HEX >= 0x03040000
958 , 0 // tp_finalize
959#endif
960#if PY_VERSION_HEX >= 0x03080000
961 , 0 // tp_vectorcall
962#endif
963#if PY_VERSION_HEX >= 0x030c0000
964 , 0 // tp_watched
965#endif
966};
967
968} // namespace CPyCppyy
#define Py_TYPE(ob)
Definition CPyCppyy.h:196
#define Py_RETURN_TRUE
Definition CPyCppyy.h:272
#define CPyCppyy_PyText_InternFromString
Definition CPyCppyy.h:82
#define Py_RETURN_FALSE
Definition CPyCppyy.h:276
int Py_ssize_t
Definition CPyCppyy.h:215
#define CPyCppyy_PyText_Append
Definition CPyCppyy.h:83
#define CPyCppyy_PyText_AsString
Definition CPyCppyy.h:76
static Py_ssize_t CPyCppyy_PyArgs_GET_SIZE(CPyCppyy_PyArgs_t args, size_t)
Definition CPyCppyy.h:337
PyObject * CPyCppyy_PyArgs_t
Definition CPyCppyy.h:330
#define CPyCppyy_PyText_AppendAndDel
Definition CPyCppyy.h:84
long Py_hash_t
Definition CPyCppyy.h:114
PyObject * CPyCppyy_PyObject_Call(PyObject *cb, PyObject *args, size_t, PyObject *kwds)
Definition CPyCppyy.h:346
#define CPyCppyy_PyText_FromFormat
Definition CPyCppyy.h:80
PyObject * CPyCppyy_tp_call(PyObject *cb, PyObject *args, size_t, PyObject *kwds)
Definition CPyCppyy.h:349
#define CPyCppyy_PyText_FromString
Definition CPyCppyy.h:81
static PyObject * CPyCppyy_PyArgs_GET_ITEM(CPyCppyy_PyArgs_t args, Py_ssize_t i)
Definition CPyCppyy.h:331
#define CPyCppyy_PyText_Check
Definition CPyCppyy.h:74
#define PyVarObject_HEAD_INIT(type, size)
Definition CPyCppyy.h:194
_object PyObject
#define c(i)
Definition RSha256.hxx:101
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 result
char name[80]
Definition TGX11.cxx:110
#define TPPCALL_RETURN
const char * proto
Definition civetweb.c:17536
void MergeOverload(CPPOverload *meth)
void AdoptMethod(PyCallable *pc)
MethodInfo_t * fMethodInfo
Definition CPPOverload.h:75
bool HasMethods() const
Definition CPPOverload.h:66
virtual PyCallable * Clone()=0
virtual bool IsGreedy()=0
CPPOverload * fTemplated
CPPOverload * fLowPriority
TP_DispatchMap_t fDispatchMap
CPPOverload * fNonTemplated
PyObject * Instantiate(const std::string &fname, CPyCppyy_PyArgs_t tmplArgs, size_t nargsf, Utility::ArgPreference, int *pcnt=nullptr)
void Set(const std::string &cppname, const std::string &pyname, PyObject *pyclass)
PyObject_HEAD PyObject * fSelf
void AdoptTemplate(PyCallable *pc)
void AdoptMethod(PyCallable *pc)
void MergeOverload(CPPOverload *mp)
const Int_t n
Definition legend1.C:16
PyObject * gCTypesType
Definition PyStrings.cxx:38
PyObject * gTypeCode
Definition PyStrings.cxx:37
std::string ConstructTemplateArgs(PyObject *pyname, PyObject *tpArgs, PyObject *args=nullptr, ArgPreference=kNone, int argoff=0, int *pcnt=nullptr)
Definition Utility.cxx:580
size_t FetchError(std::vector< PyError_t > &, bool is_cpp=false)
Definition Utility.cxx:1071
void SetDetailedException(std::vector< PyError_t > &errors, PyObject *topmsg, PyObject *defexc)
Definition Utility.cxx:1083
CPPOverload * CPPOverload_New(const std::string &name, std::vector< PyCallable * > &methods)
static PyObject * tpp_richcompare(TemplateProxy *self, PyObject *other, int op)
bool AdjustSelf(PyCallArgs &cargs)
static PyObject * tpp_doc(TemplateProxy *pytmpl, void *)
static PyMappingMethods tpp_as_mapping
bool CPPOverload_Check(T *object)
Definition CPPOverload.h:90
static int tpp_clear(TemplateProxy *pytmpl)
static PyObject * tpp_call(TemplateProxy *pytmpl, PyObject *args, PyObject *kwds)
static PyObject * tpp_getuseffi(CPPOverload *, void *)
bool TemplateProxy_CheckExact(T *object)
static void tpp_dealloc(TemplateProxy *pytmpl)
uint64_t HashSignature(CPyCppyy_PyArgs_t args, size_t nargsf)
Definition CPPOverload.h:17
static int tpp_traverse(TemplateProxy *pytmpl, visitproc visit, void *arg)
static PyObject * tpp_overload(TemplateProxy *pytmpl, PyObject *args)
static int tpp_setuseffi(CPPOverload *, PyObject *, void *)
static PyGetSetDef tpp_getset[]
PyTypeObject CPPOverload_Type
PyTypeObject TemplateProxy_Type
static PyObject * tpp_subscript(TemplateProxy *pytmpl, PyObject *args)
static PyObject * CallMethodImp(TemplateProxy *pytmpl, PyObject *&pymeth, CPyCppyy_PyArgs_t args, size_t nargsf, PyObject *kwds, bool impOK, uint64_t sighash)
static void UpdateDispatchMap(TemplateProxy *pytmpl, bool use_targs, uint64_t sighash, CPPOverload *pymeth)
static TemplateProxy * tpp_new(PyTypeObject *, PyObject *, PyObject *)
static PyMethodDef tpp_methods[]
static int tpp_doc_set(TemplateProxy *pytmpl, PyObject *val, void *)
static Py_hash_t tpp_hash(TemplateProxy *self)
static PyObject * SelectAndForward(TemplateProxy *pytmpl, CPPOverload *pymeth, CPyCppyy_PyArgs_t args, size_t nargsf, PyObject *kwds, bool implicitOkay, bool use_targs, uint64_t sighash, std::vector< Utility::PyError_t > &errors)
static std::string targs2str(TemplateProxy *pytmpl)
static TemplateProxy * tpp_descr_get(TemplateProxy *pytmpl, PyObject *pyobj, PyObject *)
bool TemplateProxy_Check(T *object)
std::shared_ptr< TemplateInfo > TP_TInfo_t
static PyObject * TC2CppName(PyObject *pytc, const char *cpd, bool allow_voidp)
intptr_t TCppMethod_t
Definition cpp_cppyy.h:22
RPY_EXPORTED TCppMethod_t GetMethodTemplate(TCppScope_t scope, const std::string &name, const std::string &proto)
RPY_EXPORTED bool IsConstructor(TCppMethod_t method)
RPY_EXPORTED bool IsNamespace(TCppScope_t scope)
RPY_EXPORTED bool IsStaticMethod(TCppMethod_t method)
size_t TCppScope_t
Definition cpp_cppyy.h:18
RPY_EXPORTED std::string GetMethodFullName(TCppMethod_t)
CPPOverload::Methods_t fMethods
Definition CPPOverload.h:49