Logo ROOT   6.30.04
Reference Guide
 All Namespaces Files Pages
TemplateProxy.cxx
Go to the documentation of this file.
1 // Author: Wim Lavrijsen, Jan 2005
2 
3 // Bindings
4 #include "PyROOT.h"
5 #include "TemplateProxy.h"
6 #include "MethodProxy.h"
7 #include "TFunctionHolder.h"
8 #include "TMethodHolder.h"
9 #include "PyCallable.h"
10 #include "PyStrings.h"
11 #include "Utility.h"
12 #include "PyRootType.h"
13 
14 // ROOT
15 #include "TClass.h"
16 #include "TMethod.h"
17 
18 
19 namespace PyROOT {
20 
21 ////////////////////////////////////////////////////////////////////////////////
22 /// Initialize the proxy for the given 'pyclass.'
23 
24 void TemplateProxy::Set( const std::string& name, PyObject* pyclass )
25 {
26  fPyName = PyROOT_PyUnicode_FromString( const_cast< char* >( name.c_str() ) );
27  Py_XINCREF( pyclass );
28  fPyClass = pyclass;
29  fSelf = NULL;
30  std::vector< PyCallable* > dummy;
31  fNonTemplated = MethodProxy_New( name, dummy );
32  fTemplated = MethodProxy_New( name, dummy );
33 }
34 
35 ////////////////////////////////////////////////////////////////////////////////
36 /// Store overloads of this templated method.
37 
38 void TemplateProxy::AddOverload( MethodProxy* mp ) {
39  fNonTemplated->AddMethod( mp );
40 }
41 
42 void TemplateProxy::AddOverload( PyCallable* pc ) {
43 // Store overload of this templated method.
44  fNonTemplated->AddMethod( pc );
45 }
46 
47 void TemplateProxy::AddTemplate( PyCallable* pc )
48 {
49 // Store know template methods.
50  fTemplated->AddMethod( pc );
51 }
52 
53 
54 namespace {
55 
56 //= PyROOT template proxy construction/destruction ===========================
57  TemplateProxy* tpp_new( PyTypeObject*, PyObject*, PyObject* )
58  {
59  // Create a new empty template method proxy.
60  TemplateProxy* pytmpl = PyObject_GC_New( TemplateProxy, &TemplateProxy_Type );
61  pytmpl->fPyName = NULL;
62  pytmpl->fPyClass = NULL;
63  pytmpl->fSelf = NULL;
64  pytmpl->fNonTemplated = NULL;
65  pytmpl->fTemplated = NULL;
66 
67  PyObject_GC_Track( pytmpl );
68  return pytmpl;
69  }
70 
71 ////////////////////////////////////////////////////////////////////////////////
72 /// Garbage collector clear of held python member objects.
73 
74  int tpp_clear( TemplateProxy* pytmpl )
75  {
76  Py_CLEAR( pytmpl->fPyName );
77  Py_CLEAR( pytmpl->fPyClass );
78  Py_CLEAR( pytmpl->fSelf );
79  Py_CLEAR( pytmpl->fNonTemplated );
80  Py_CLEAR( pytmpl->fTemplated );
81 
82  return 0;
83  }
84 
85 ////////////////////////////////////////////////////////////////////////////////
86 /// Destroy the given template method proxy.
87 
88  void tpp_dealloc( TemplateProxy* pytmpl )
89  {
90  PyObject_GC_UnTrack( pytmpl );
91  tpp_clear( pytmpl );
92  PyObject_GC_Del( pytmpl );
93  }
94 
95 ////////////////////////////////////////////////////////////////////////////////
96 /// Forward to method proxies to doc all overloads
97 
98  PyObject* tpp_doc( TemplateProxy* pytmpl, void* )
99  {
100  PyObject* doc = nullptr;
101  if ( pytmpl->fNonTemplated )
102  doc = PyObject_GetAttrString( (PyObject*)pytmpl->fNonTemplated, "__doc__" );
103  if ( pytmpl->fTemplated ) {
104  PyObject* doc2 = PyObject_GetAttrString( (PyObject*)pytmpl->fTemplated, "__doc__" );
105  if ( doc && doc2 ) {
106  PyROOT_PyUnicode_AppendAndDel( &doc, PyROOT_PyUnicode_FromString( "\n" ));
107  PyROOT_PyUnicode_AppendAndDel( &doc, doc2 );
108  } else if ( !doc && doc2 ) {
109  doc = doc2;
110  }
111  }
112 
113  if ( doc )
114  return doc;
115 
116  return PyROOT_PyUnicode_FromString( TemplateProxy_Type.tp_doc );
117  }
118 
119 ////////////////////////////////////////////////////////////////////////////////
120 /// Garbage collector traverse of held python member objects.
121 
122  int tpp_traverse( TemplateProxy* pytmpl, visitproc visit, void* arg )
123  {
124  Py_VISIT( pytmpl->fPyName );
125  Py_VISIT( pytmpl->fPyClass );
126  Py_VISIT( pytmpl->fSelf );
127  Py_VISIT( pytmpl->fNonTemplated );
128  Py_VISIT( pytmpl->fTemplated );
129 
130  return 0;
131  }
132 
133 //= PyROOT template proxy callable behavior ==================================
134  PyObject* tpp_call( TemplateProxy* pytmpl, PyObject* args, PyObject* kwds )
135  {
136  // Dispatcher to the actual member method, several uses possible; in order:
137  //
138  // case 1:
139  //
140  // obj.method( a0, a1, ... ) # non-template
141  // => obj->method( a0, a1, ... ) // non-template
142  //
143  // case 2:
144  //
145  // obj.method( t0, t1, ... )( a0, a1, ... )
146  // -> getattr( obj, 'method< t0, t1, ... >' )( a0, a1, ... )
147  // => obj->method< t0, t1, ... >( a0, a1, ... )
148  //
149  // case 3:
150  //
151  // obj.method( a0, a1, ... ) # all known templates
152  // => obj->method( a0, a1, ... ) // all known templates
153  //
154  // case 4:
155  //
156  // collect types of arguments unless they are a type themselves (the idea
157  // here is it is more likely for e.g. (1, 3.14) to be real arguments and
158  // e.g. (int, 'double') to be template arguments:
159  // a) if ! is_type(arg)
160  // template<> method< T0, T1, ... >( type(a0), type(a1), ... )
161  // b) else
162  // drop to case 5
163  // TODO: there is ambiguity in the above if the arguments are strings
164  //
165  // case 5:
166  //
167  // instantiate template<> method< t0, t1, ... >
168 
169  // case 1: obj->method( a0, a1, ... )
170 
171  // simply forward the call: all non-templated methods are defined on class definition
172  // and thus already available
173  PyObject* pymeth = MethodProxy_Type.tp_descr_get(
174  (PyObject*)pytmpl->fNonTemplated, pytmpl->fSelf, (PyObject*)&MethodProxy_Type );
175  if ( MethodProxy_Check( pymeth ) ) {
176  // now call the method with the arguments
177  PyObject* result = MethodProxy_Type.tp_call( pymeth, args, kwds );
178  Py_DECREF( pymeth ); pymeth = 0;
179  if ( result )
180  return result;
181  // TODO: collect error here, as the failure may be either an overload
182  // failure after which we should continue; or a real failure, which should
183  // be reported.
184  }
185  Py_XDECREF( pymeth ); pymeth = 0;
186  PyErr_Clear();
187 
188  // error check on method() which can not be derived if non-templated case fails
189  Py_ssize_t nArgs = PyTuple_GET_SIZE( args );
190  if ( nArgs == 0 ) {
191  PyErr_Format( PyExc_TypeError, "template method \'%s\' with no arguments must be explicit",
192  PyROOT_PyUnicode_AsString( pytmpl->fPyName ) );
193  return 0;
194  }
195 
196  // case 2: non-instantiating obj->method< t0, t1, ... >( a0, a1, ... )
197 
198  Bool_t isType = kFALSE;
199  Int_t nStrings = 0;
200  PyObject* tpArgs = PyTuple_New( nArgs );
201  for ( Int_t i = 0; i < nArgs; ++i ) {
202  PyObject* itemi = PyTuple_GET_ITEM( args, i );
203  if ( PyType_Check( itemi ) ) isType = kTRUE;
204 #if PY_VERSION_HEX >= 0x03000000
205  else if ( ! isType && PyUnicode_Check( itemi ) ) nStrings += 1;
206 #else
207  else if ( ! isType && PyBytes_Check( itemi ) ) nStrings += 1;
208 #endif
209  // special case for arrays
210  PyObject* pytc = PyObject_GetAttr( itemi, PyStrings::gTypeCode );
211  if ( ! ( pytc && PyROOT_PyUnicode_Check( pytc ) ) ) {
212  // normal case (not an array)
213  PyErr_Clear();
214  PyObject* tp = (PyObject*)Py_TYPE( itemi );
215  Py_INCREF( tp );
216  PyTuple_SET_ITEM( tpArgs, i, tp );
217  } else {
218  // array, build up a pointer type
219  char tc = ((char*)PyROOT_PyUnicode_AsString( pytc ))[0];
220  const char* ptrname = 0;
221  switch ( tc ) {
222  case 'b': ptrname = "char*"; break;
223  case 'h': ptrname = "short*"; break;
224  case 'H': ptrname = "unsigned short*"; break;
225  case 'i': ptrname = "int*"; break;
226  case 'I': ptrname = "unsigned int*"; break;
227  case 'l': ptrname = "long*"; break;
228  case 'L': ptrname = "unsigned long*"; break;
229  case 'f': ptrname = "float*"; break;
230  case 'd': ptrname = "double*"; break;
231  default: ptrname = "void*"; // TODO: verify if this is right
232  }
233  if ( ptrname ) {
234  //PyObject* pyptrname = PyBytes_FromString( ptrname );
235  PyObject *pyptrname = PyROOT_PyUnicode_FromString(ptrname);
236  PyTuple_SET_ITEM( tpArgs, i, pyptrname );
237  // string added, but not counted towards nStrings
238  } else {
239  // this will cleanly fail instantiation
240  Py_INCREF( pytc );
241  PyTuple_SET_ITEM( tpArgs, i, pytc );
242  }
243  }
244  Py_XDECREF( pytc );
245  }
246 
247  // build "< type, type, ... >" part of method name
248  PyObject* pyname_v1 = Utility::BuildTemplateName( pytmpl->fPyName, args, 0 );
249  if ((isType || nStrings == nArgs) && pyname_v1) { // types in args or all strings
250  // lookup method on self (to make sure it propagates), which is readily callable
251  pymeth = PyObject_GetAttr( pytmpl->fSelf ? pytmpl->fSelf : pytmpl->fPyClass, pyname_v1 );
252  if ( pymeth ) { // overloads stop here, as this is an explicit match
253  Py_DECREF( pyname_v1 );
254  if (PyErr_WarnEx(PyExc_FutureWarning,
255  "Instantiating a function template with parentheses ( f(type1, ..., typeN) ) "
256  "is deprecated and will not be supported in a future version of ROOT. "
257  "Instead, use square brackets: f[type1, ..., typeN]", 1) < 0) {
258  return nullptr;
259  }
260  return pymeth; // callable method, next step is by user
261  }
262  }
263  PyErr_Clear();
264 
265  // case 3: loop over all previously instantiated templates
266  pymeth = MethodProxy_Type.tp_descr_get(
267  (PyObject*)pytmpl->fTemplated, pytmpl->fSelf, (PyObject*)&MethodProxy_Type );
268  if ( MethodProxy_Check( pymeth ) ) {
269  // now call the method with the arguments
270  PyObject* result = MethodProxy_Type.tp_call( pymeth, args, kwds );
271  Py_DECREF( pymeth ); pymeth = 0;
272  if ( result ) {
273  Py_XDECREF( pyname_v1 );
274  return result;
275  }
276  // TODO: collect error here, as the failure may be either an overload
277  // failure after which we should continue; or a real failure, which should
278  // be reported.
279  }
280  Py_XDECREF( pymeth ); pymeth = 0;
281  PyErr_Clear();
282 
283  // still here? try instantiating methods
284 
285  PyObject* clName = PyObject_GetAttr( pytmpl->fPyClass, PyStrings::gCppName );
286  if (!clName) {
287  PyErr_Clear();
288  clName = PyObject_GetAttr(pytmpl->fPyClass, PyStrings::gName);
289  }
290  auto clNameStr = std::string(PyROOT_PyUnicode_AsString(clName));
291  if (clNameStr == "_global_cpp")
292  clNameStr = ""; // global namespace
293  TClass* klass = TClass::GetClass(clNameStr.c_str());
294  Py_DECREF( clName );
295  const std::string& tmplname = pytmpl->fNonTemplated->fMethodInfo->fName;
296 
297  // case 4a: instantiating obj->method< T0, T1, ... >( type(a0), type(a1), ... )( a0, a1, ... )
298  if ( ! isType && ! ( nStrings == nArgs ) ) { // no types among args and not all strings
299  for (auto pref : {Utility::kReference, Utility::kPointer, Utility::kValue}) {
300  int pcnt = 0;
301  PyObject* pyname_v2 = Utility::BuildTemplateName( NULL, tpArgs, 0, args, pref, &pcnt, true );
302  if ( pyname_v2 ) {
303  std::string mname = PyROOT_PyUnicode_AsString( pyname_v2 );
304  Py_DECREF( pyname_v2 );
305  std::string proto = mname.substr( 1, mname.size() - 2 );
306  // the following causes instantiation as necessary
307  auto scope = Cppyy::GetScope(clNameStr);
308  auto cppmeth = Cppyy::GetMethodTemplate(scope, tmplname, proto);
309  if ( cppmeth ) { // overload stops here
310  Py_XDECREF( pyname_v1 );
311  if (Cppyy::IsNamespace(scope) || Cppyy::IsStaticMethod(cppmeth)) {
312  pytmpl->fTemplated->AddMethod( new TFunctionHolder( scope, cppmeth ) );
313  pymeth = (PyObject*)MethodProxy_New(
314  Cppyy::GetMethodName(cppmeth).c_str(), new TFunctionHolder( scope, cppmeth ) );
315  } else {
316  pytmpl->fTemplated->AddMethod( new TMethodHolder( scope, cppmeth ) );
317  pymeth = (PyObject*)MethodProxy_New(
318  Cppyy::GetMethodName(cppmeth).c_str(), new TMethodHolder( scope, cppmeth ) );
319  }
320  PyObject_SetAttrString( pytmpl->fPyClass, (char*)Cppyy::GetMethodName(cppmeth).c_str(), (PyObject*)pymeth );
321  Py_DECREF( pymeth );
322  pymeth = PyObject_GetAttrString(
323  pytmpl->fSelf ? pytmpl->fSelf : pytmpl->fPyClass, (char*)Cppyy::GetMethodName(cppmeth).c_str() );
324  PyObject* result = MethodProxy_Type.tp_call( pymeth, args, kwds );
325  Py_DECREF( pymeth );
326  return result;
327  }
328  }
329  if (!pcnt) break; // preference never used; no point trying others
330  }
331  }
332 
333  // case 4b/5: instantiating obj->method< t0, t1, ... >( a0, a1, ... )
334  if ( pyname_v1 ) {
335  std::string mname = PyROOT_PyUnicode_AsString( pyname_v1 );
336  // the following causes instantiation as necessary
337  TMethod* cppmeth = klass ? klass->GetMethodAny( mname.c_str() ) : 0;
338  if ( cppmeth ) { // overload stops here
339  pymeth = (PyObject*)MethodProxy_New(
340  mname, new TMethodHolder( Cppyy::GetScope( klass->GetName() ), (Cppyy::TCppMethod_t)cppmeth ) );
341  PyObject_SetAttr( pytmpl->fPyClass, pyname_v1, (PyObject*)pymeth );
342  if ( mname != cppmeth->GetName() ) // happens with typedefs and template default arguments
343  PyObject_SetAttrString( pytmpl->fPyClass, (char*)mname.c_str(), (PyObject*)pymeth );
344  Py_DECREF( pymeth );
345  pymeth = PyObject_GetAttr( pytmpl->fSelf ? pytmpl->fSelf : pytmpl->fPyClass, pyname_v1 );
346  Py_DECREF( pyname_v1 );
347  if (PyErr_WarnEx(PyExc_FutureWarning,
348  "Instantiating a function template with parentheses ( f(type1, ..., typeN) ) "
349  "is deprecated and will not be supported in a future version of ROOT. "
350  "Instead, use square brackets: f[type1, ..., typeN]", 1) < 0) {
351  return nullptr;
352  }
353  return pymeth; // callable method, next step is by user
354  }
355  Py_DECREF( pyname_v1 );
356  }
357 
358  // moderately generic error message, but should be clear enough
359  PyErr_Format( PyExc_TypeError, "can not resolve method template call for \'%s\'",
360  PyROOT_PyUnicode_AsString( pytmpl->fPyName ) );
361  return 0;
362  }
363 
364 ////////////////////////////////////////////////////////////////////////////////
365 /// create and use a new template proxy (language requirement)
366 
367  TemplateProxy* tpp_descrget( TemplateProxy* pytmpl, PyObject* pyobj, PyObject* )
368  {
369  TemplateProxy* newPyTmpl = (TemplateProxy*)TemplateProxy_Type.tp_alloc( &TemplateProxy_Type, 0 );
370 
371  // copy name and class pointers
372  Py_INCREF( pytmpl->fPyName );
373  newPyTmpl->fPyName = pytmpl->fPyName;
374 
375  Py_XINCREF( pytmpl->fPyClass );
376  newPyTmpl->fPyClass = pytmpl->fPyClass;
377 
378  // copy non-templated method proxy pointer
379  Py_INCREF( pytmpl->fNonTemplated );
380  newPyTmpl->fNonTemplated = pytmpl->fNonTemplated;
381 
382  // copy templated method proxy pointer
383  Py_INCREF( pytmpl->fTemplated );
384  newPyTmpl->fTemplated = pytmpl->fTemplated;
385 
386  // new method is to be bound to current object (may be NULL)
387  Py_XINCREF( pyobj );
388  newPyTmpl->fSelf = pyobj;
389 
390  return newPyTmpl;
391  }
392 
393 ////////////////////////////////////////////////////////////////////////////////
394 /// Explicit instantiation with square bracket syntax.
395 /// Implementation is equivalent to case 2&4b in tpp_call (parenthesis syntax)
396 
397  PyObject *tpp_subscript(TemplateProxy *pytmpl, PyObject *args)
398  {
399  bool justOne = !PyTuple_CheckExact(args);
400  Py_ssize_t nArgs;
401  if (justOne) {
402  nArgs = 1;
403  auto item = args;
404  args = PyTuple_New(nArgs);
405  Py_INCREF(item);
406  PyTuple_SET_ITEM(args, 0, item);
407  } else {
408  nArgs = PyTuple_GET_SIZE(args);
409  }
410 
411  Bool_t isType = false;
412  Int_t nStrings = 0;
413  for (int i = 0; i < nArgs; ++i) {
414  PyObject* itemi = PyTuple_GET_ITEM(args, i);
415  if (PyType_Check(itemi)) isType = kTRUE;
416 #if PY_VERSION_HEX >= 0x03000000
417  else if (! isType && PyUnicode_Check(itemi)) nStrings += 1;
418 #else
419  else if (! isType && PyBytes_Check(itemi)) nStrings += 1;
420 #endif
421  }
422 
423  // Build "< type, type, ... >" part of method name
424  PyObject* pyname = Utility::BuildTemplateName(pytmpl->fPyName, args, 0);
425  if (justOne) Py_DECREF(args);
426 
427  // Non-instantiating obj->method< t0, t1, ... >( a0, a1, ... )
428  if ((isType || nStrings == nArgs) && pyname) { // types in args or all strings
429  // Lookup method on self (to make sure it propagates), which is readily callable
430  PyObject* pymeth = PyObject_GetAttr(pytmpl->fSelf ? pytmpl->fSelf : pytmpl->fPyClass, pyname);
431  if (pymeth) { // Overloads stop here, as this is an explicit match
432  Py_DECREF(pyname);
433  return pymeth; // Callable method, next step is by user
434  }
435  PyErr_Clear();
436  }
437 
438  // Still here? try instantiating methods
439  PyObject* clName = PyObject_GetAttr(pytmpl->fPyClass, PyStrings::gCppName);
440  if (!clName) {
441  PyErr_Clear();
442  clName = PyObject_GetAttr(pytmpl->fPyClass, PyStrings::gName);
443  }
444  auto clNameStr = std::string(PyROOT_PyUnicode_AsString(clName));
445  if (clNameStr == "_global_cpp")
446  clNameStr = ""; // global namespace
447  auto klass = TClass::GetClass(clNameStr.c_str());
448  Py_DECREF(clName);
449 
450  // Instantiating obj->method< t0, t1, ... >( a0, a1, ... )
451  if (pyname) {
452  std::string mname = PyROOT_PyUnicode_AsString(pyname);
453  // The following causes instantiation as necessary
454  TMethod *cppmeth = klass ? klass->GetMethodAny(mname.c_str()) : nullptr;
455  if (cppmeth) { // overload stops here
456  PyObject *pymeth = (PyObject*)MethodProxy_New(
457  mname, new TMethodHolder(Cppyy::GetScope(klass->GetName()),(Cppyy::TCppMethod_t)cppmeth));
458  PyObject_SetAttr(pytmpl->fPyClass, pyname, (PyObject*)pymeth);
459  if (mname != cppmeth->GetName()) // happens with typedefs and template default arguments
460  PyObject_SetAttrString(pytmpl->fPyClass, (char*)mname.c_str(), (PyObject*)pymeth);
461  Py_DECREF(pymeth);
462  pymeth = PyObject_GetAttr(pytmpl->fSelf ? pytmpl->fSelf : pytmpl->fPyClass, pyname);
463  Py_DECREF(pyname);
464  return pymeth; // callable method, next step is by user
465  }
466  Py_DECREF(pyname);
467  }
468 
469  PyErr_Format(PyExc_TypeError, "cannot resolve method template instantiation for \'%s\'",
470  PyROOT_PyUnicode_AsString(pytmpl->fPyName));
471  return nullptr;
472  }
473 
474 ////////////////////////////////////////////////////////////////////////////////
475 
476  static PyMappingMethods tpp_as_mapping = {
477  nullptr, (binaryfunc)tpp_subscript, nullptr
478  };
479 
480  PyGetSetDef tpp_getset[] = {
481  { (char*)"__doc__", (getter)tpp_doc, NULL, NULL, NULL },
482  { (char*)NULL, NULL, NULL, NULL, NULL }
483  };
484 
485 } // unnamed namespace
486 
487 
488 //= PyROOT template proxy type ===============================================
489 PyTypeObject TemplateProxy_Type = {
490  PyVarObject_HEAD_INIT( &PyType_Type, 0 )
491  (char*)"ROOT.TemplateProxy", // tp_name
492  sizeof(TemplateProxy), // tp_basicsize
493  0, // tp_itemsize
494  (destructor)tpp_dealloc, // tp_dealloc
495  0, // tp_print
496  0, // tp_getattr
497  0, // tp_setattr
498  0, // tp_compare
499  0, // tp_repr
500  0, // tp_as_number
501  0, // tp_as_sequence
502  &tpp_as_mapping, // tp_as_mapping
503  0, // tp_hash
504  (ternaryfunc)tpp_call, // tp_call
505  0, // tp_str
506  0, // tp_getattro
507  0, // tp_setattro
508  0, // tp_as_buffer
509  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, // tp_flags
510  (char*)"PyROOT template proxy (internal)", // tp_doc
511  (traverseproc)tpp_traverse,// tp_traverse
512  (inquiry)tpp_clear, // tp_clear
513  0, // tp_richcompare
514  0, // tp_weaklistoffset
515  0, // tp_iter
516  0, // tp_iternext
517  0, // tp_methods
518  0, // tp_members
519  tpp_getset, // tp_getset
520  0, // tp_base
521  0, // tp_dict
522  (descrgetfunc)tpp_descrget,// tp_descr_get
523  0, // tp_descr_set
524  0, // tp_dictoffset
525  0, // tp_init
526  0, // tp_alloc
527  (newfunc)tpp_new, // tp_new
528  0, // tp_free
529  0, // tp_is_gc
530  0, // tp_bases
531  0, // tp_mro
532  0, // tp_cache
533  0, // tp_subclasses
534  0 // tp_weaklist
535 #if PY_VERSION_HEX >= 0x02030000
536  , 0 // tp_del
537 #endif
538 #if PY_VERSION_HEX >= 0x02060000
539  , 0 // tp_version_tag
540 #endif
541 #if PY_VERSION_HEX >= 0x03040000
542  , 0 // tp_finalize
543 #endif
544 };
545 
546 } // namespace PyROOT