Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
CPPScope.cxx
Go to the documentation of this file.
1// Bindings
2#include "cpyrt.h"
3
4using namespace cppjit;
5#include "CPPDataMember.h"
6#include "CPPEnum.h"
7#include "CPPFunction.h"
8#include "CPPOverload.h"
9#include "CPPScope.h"
10#include "CustomPyTypes.h"
11#include "Dispatcher.h"
12#include "ProxyWrappers.h"
13#include "PyStrings.h"
14#include "TemplateProxy.h"
15#include "TypeManip.h"
16#include "Utility.h"
17#include "cppjit_interop.h"
18
19// Standard
20#include <algorithm> // for for_each
21#include <cctype>
22#include <set>
23#include <string.h>
24#include <string>
25#include <vector>
26
27namespace cppjit::cpyrt {
28
29extern PyTypeObject CPPInstance_Type;
30
31//- helpers ------------------------------------------------------------------
32static inline PyObject*
33add_template(PyObject* pyclass, const std::string& name,
34 std::vector<PyCallable*>* overloads = nullptr) {
35 // If templated, the user-facing function must be the template proxy, but the
36 // specific lookup must be the current overload, if already found.
37
38 const std::string& ncl = TypeManip::clean_type(name);
39 PyObject* pyncl = PyUnicode_FromString(ncl.c_str());
40 TemplateProxy* pytmpl =
41 (TemplateProxy*)PyType_Type.tp_getattro(pyclass, pyncl);
42 if (!pytmpl) {
43 PyErr_Clear();
44 pytmpl = TemplateProxy_New(ncl, ncl, pyclass);
45 // cache the template on its clean name
46 PyType_Type.tp_setattro(pyclass, pyncl, (PyObject*)pytmpl);
47 Py_DECREF(pyncl);
48 } else if (!TemplateProxy_CheckExact((PyObject*)pytmpl)) {
49 Py_DECREF(pytmpl);
50 return nullptr;
51 }
52
53 if (overloads) {
54 // adopt the new overloads
55 if (ncl != name)
56 for (auto clb : *overloads)
57 pytmpl->AdoptTemplate(clb);
58 else
59 for (auto clb : *overloads)
60 pytmpl->AdoptMethod(clb);
61 }
62
63 // the caller expects a method matching the full name, thus is a
64 // specialization was requested, do not return the template yet
65 if (ncl == name)
66 return (PyObject*)pytmpl;
67
68 Py_DECREF(pytmpl);
69 return nullptr; // so that caller caches the method on full name
70}
71
72//= cpyrt type proxy construction/destruction =============================
73static PyObject* meta_alloc(PyTypeObject* meta, Py_ssize_t nitems) {
74 // pure memory allocation; object initialization is in pt_new
75 return PyType_Type.tp_alloc(meta, nitems);
76}
77
78//----------------------------------------------------------------------------
79static void meta_dealloc(CPPScope* scope) {
80 if (scope->fFlags & CPPScope::kIsNamespace) {
81 if (scope->fImp.fUsing) {
82 for (auto pyobj : *scope->fImp.fUsing)
83 Py_DECREF(pyobj);
84 delete scope->fImp.fUsing;
85 scope->fImp.fUsing = nullptr;
86 }
87 } else if (!(scope->fFlags & CPPScope::kIsPython)) {
88 delete scope->fImp.fCppObjects;
89 scope->fImp.fCppObjects = nullptr;
90 }
91 delete scope->fOperators;
92 free(scope->fModuleName);
93 return PyType_Type.tp_dealloc((PyObject*)scope);
94}
95
96//-----------------------------------------------------------------------------
97static PyObject* meta_getcppname(CPPScope* scope, void*) {
98 if ((void*)scope == (void*)&CPPInstance_Type)
99 return PyUnicode_FromString("CPPInstance_Type");
100 return PyUnicode_FromString(
101 interop::GetScopedFinalName(scope->fCppType).c_str());
102}
103
104//-----------------------------------------------------------------------------
105static PyObject* meta_getmodule(CPPScope* scope, void*) {
106 if ((void*)scope == (void*)&CPPInstance_Type)
107 return PyUnicode_FromString("cppjit.gbl");
108
109 if (scope->fModuleName)
110 return PyUnicode_FromString(scope->fModuleName);
111
112 // get C++ representation of outer scope
114 if (parent_scope == interop::GetGlobalScope())
115 return PyUnicode_FromString(const_cast<char*>("cppjit.gbl"));
116
117 // now peel scopes one by one, pulling in the python naming (which will
118 // simply recurse if not overridden in python)
119 PyObject* pymodule = nullptr;
120 PyObject* pyscope = cpyrt::GetScopeProxy(parent_scope);
121 if (pyscope) {
122 // get the module of our module
123 pymodule = PyObject_GetAttr(pyscope, PyStrings::gModule);
124 if (pymodule) {
125 // append name of our module
126 PyObject* pymodname = PyObject_GetAttr(pyscope, PyStrings::gName);
127 if (pymodname) {
128 PyUnicode_AppendAndDel(&pymodule, PyUnicode_FromString("."));
129 PyUnicode_AppendAndDel(&pymodule, pymodname);
130 }
131 }
132 Py_DECREF(pyscope);
133 }
134
135 if (pymodule)
136 return pymodule;
137 PyErr_Clear();
138
139 // lookup through python failed, so simply cook up a '::' -> '.' replacement
140 std::string modname = interop::GetScopedFinalName(parent_scope);
142 return PyUnicode_FromString(("cppjit.gbl." + modname).c_str());
143}
144
145//-----------------------------------------------------------------------------
146static int meta_setmodule(CPPScope* scope, PyObject* value, void*) {
147 if ((void*)scope == (void*)&CPPInstance_Type) {
148 PyErr_SetString(PyExc_AttributeError,
149 "attribute \'__module__\' of 'cppjit.CPPScope\' objects is "
150 "not writable");
151 return -1;
152 }
153
154 const char* newname = PyUnicode_AsUTF8(value);
155 if (!value)
156 return -1;
157
158 free(scope->fModuleName);
159 Py_ssize_t sz = PyUnicode_GET_LENGTH(value);
160 scope->fModuleName = (char*)malloc(sz + 1);
161 memcpy(scope->fModuleName, newname, sz + 1);
162
163 return 0;
164}
165
166//----------------------------------------------------------------------------
167static PyObject* meta_repr(CPPScope* scope) {
168 // Specialized b/c type_repr expects __module__ to live in the dictionary,
169 // whereas it is a property (to save memory).
170 if ((void*)scope == (void*)&CPPInstance_Type)
171 return PyUnicode_FromFormat(
172 const_cast<char*>("<class cppjit.CPPInstance at %p>"), scope);
173
175 // either meta type or Python-side derived class: use default type printing
176 return PyType_Type.tp_repr((PyObject*)scope);
177 }
178
179 // skip in case some Python-side derived meta class
180 if (!CPPScope_Check(scope) || !scope->fCppType)
181 return PyType_Type.tp_repr((PyObject*)scope);
182
183 // printing of C++ classes
184 PyObject* modname = meta_getmodule(scope, nullptr);
185 std::string clName = interop::GetFinalName(scope->fCppType);
186 const char* kind =
187 (scope->fFlags & CPPScope::kIsNamespace) ? "namespace" : "class";
188
189 PyObject* repr =
190 PyUnicode_FromFormat("<%s %s.%s at %p>", kind, PyUnicode_AsUTF8(modname),
191 clName.c_str(), scope);
192
193 Py_DECREF(modname);
194 return repr;
195}
196
197//= cpyrt type metaclass behavior =========================================
198static PyObject* pt_new(PyTypeObject* subtype, PyObject* args, PyObject* kwds) {
199 // Called when CPPScope acts as a metaclass; since type_new always resets
200 // tp_alloc, and since it does not call tp_init on types, the metaclass is
201 // being fixed up here, and the class is initialized here as well.
202
203 // fixup of metaclass (left permanent, and in principle only called once b/c
204 // cppjit caches python classes)
205 subtype->tp_alloc = (allocfunc)meta_alloc;
206 subtype->tp_dealloc = (destructor)meta_dealloc;
207
208 // creation of the python-side class; extend the size if this is a smart ptr
209 //
210 // A namespace can never be a smart pointer, so skip the check for namespaces.
213 if (CPPScope_CheckExact(subtype) &&
214 !interop::IsNamespace(((CPPScope*)subtype)->fCppType)) {
215 if (interop::GetSmartPtrInfo(((CPPScope*)subtype)->fCppType, &raw, &deref))
216 subtype->tp_basicsize = sizeof(CPPSmartClass);
217 }
218
219 CPPScope* result = (CPPScope*)PyType_Type.tp_new(subtype, args, kwds);
220 if (!CPPScope_Check(result)) {
221 // either failed or custom user-side metaclass that can't be handled here
222 return nullptr;
223 }
224
225 result->fFlags = CPPScope::kNone;
226 result->fOperators = nullptr;
227 result->fModuleName = nullptr;
228
229 if (raw && deref) {
230 result->fFlags |= CPPScope::kIsSmart;
231 ((CPPSmartClass*)result)->fUnderlyingType = raw;
232 ((CPPSmartClass*)result)->fDereferencer = deref;
233 }
234
235 // initialization of class (based on metatype)
236 if (!CPPScope_CheckExact(subtype) ||
237 !strstr(subtype->tp_name, "_meta") /* convention */) {
238 // there has been a user meta class override in a derived class, so do
239 // the consistent thing, thus allowing user control over naming
240 result->fCppType =
241 interop::GetScope(PyUnicode_AsUTF8(PyTuple_GET_ITEM(args, 0)));
242 } else {
243 // coming here from cppjit or from sub-classing in python; take the
244 // C++ type from the meta class to make sure that the latter category
245 // has fCppType properly set (it inherits the meta class, but has an
246 // otherwise unknown (or wrong) C++ type)
247 result->fCppType = ((CPPScope*)subtype)->fCppType;
248
249 // the following is not robust, but by design, C++ classes get their
250 // dictionaries filled after creation (chicken & egg problem as they
251 // can return themselves in methods), whereas a derived Python class
252 // with method overrides will have a non-empty dictionary (even if it
253 // has no methods, it will at least have a module name)
254 if (3 <= PyTuple_GET_SIZE(args)) {
255 PyObject* dct = PyTuple_GET_ITEM(args, 2);
256 Py_ssize_t sz = PyDict_Size(dct);
257 if (0 < sz && !interop::IsNamespace(result->fCppType)) {
258 result->fFlags |= CPPScope::kIsPython;
259 if (1 < PyTuple_GET_SIZE(PyTuple_GET_ITEM(args, 1)))
261 std::ostringstream errmsg;
262 if (!InsertDispatcher(result, PyTuple_GET_ITEM(args, 1), dct, errmsg)) {
263 PyErr_Format(PyExc_TypeError,
264 "no python-side overrides supported (%s)",
265 errmsg.str().c_str());
266 return nullptr;
267 } else {
268 // the direct base can be useful for some templates, such as
269 // shared_ptrs, so make it accessible (the __cpp_cross__ data member
270 // also signals that this is a cross-inheritance class)
271 PyObject* bname = PyUnicode_FromString(
272 interop::GetBaseName(result->fCppType, 0).c_str());
273 if (!bname)
274 PyErr_Clear();
275 else {
276 if (PyObject_SetAttrString((PyObject*)result, "__cpp_cross__",
277 bname) == -1)
278 PyErr_Clear();
279 Py_DECREF(bname);
280 }
281 }
282 } else if (sz == (Py_ssize_t)-1)
283 PyErr_Clear();
284 }
285 }
286
287 // if the user messed with the metaclass, then we may not have a C++ type,
288 // simply return here before more damage gets done
289 if (!result->fCppType)
290 return (PyObject*)result;
291
292 // maps for using namespaces and tracking objects
293 if (!interop::IsNamespace(result->fCppType)) {
294 static interop::TCppScope_t exc_type =
295 interop::GetScope("exception", interop::GetScope("std"));
296 if (interop::IsSubclass(result->fCppType, exc_type))
298 if (!(result->fFlags & CPPScope::kIsPython))
299 result->fImp.fCppObjects = new CppToPyMap_t;
300 else {
301 // special case: the C++ objects should be stored with the associated C++,
302 // not Python, type
303 CPPClass* kls = (CPPClass*)GetScopeProxy(result->fCppType);
304 if (kls) {
305 result->fImp.fCppObjects = kls->fImp.fCppObjects;
306 Py_DECREF(kls);
307 } else
308 result->fImp.fCppObjects = nullptr;
309 }
310 } else {
311 result->fImp.fUsing = nullptr;
313 }
314
315 if (PyErr_Occurred()) {
316 Py_DECREF((PyObject*)result);
317 return nullptr;
318 }
319
320 return (PyObject*)result;
321}
322
323//----------------------------------------------------------------------------
324static bool IsPlainIdentifier(const std::string& name) {
325 if (name.empty() || std::isdigit((unsigned char)name[0]))
326 return false;
327 for (char c : name) {
328 if (!std::isalnum((unsigned char)c) && c != '_')
329 return false;
330 }
331 return true;
332}
333
334// Returns weak references to the proxies of the namespaces that the namespace
335// \c pyclass pulls in with "using namespace", refreshing the list as needed.
336static const std::vector<PyObject*>& GetUsingScopes(PyObject* pyclass) {
337 CPPScope* klass = (CPPScope*)pyclass;
338 const std::vector<interop::TCppScope_t>& uv =
340 if (!klass->fImp.fUsing || uv.size() != klass->fImp.fUsing->size()) {
341 if (klass->fImp.fUsing) {
342 for (auto pyref : *klass->fImp.fUsing)
343 Py_DECREF(pyref);
344 klass->fImp.fUsing->clear();
345 } else
346 klass->fImp.fUsing = new std::vector<PyObject*>;
347
348 // reload and reset weak refs
349 for (auto uid : uv) {
350 std::string uname = interop::GetScopedFinalName(uid);
351 PyObject* pyuscope = CreateScopeProxy(uname);
352 if (pyuscope) {
353 klass->fImp.fUsing->push_back(PyWeakref_NewRef(pyuscope, nullptr));
354 // the namespace may not otherwise be held, so tie the lifetimes
355 PyObject* llname =
356 PyUnicode_FromString(("__lifeline_" + uname).c_str());
357 PyType_Type.tp_setattro(pyclass, llname, pyuscope);
358 Py_DECREF(llname);
359 Py_DECREF(pyuscope);
360 } else {
361 PyErr_Clear();
362 }
363 }
364 }
365 return *klass->fImp.fUsing;
366}
367
368// Checks whether \c pyname is set as a Python attribute on any namespace that
369// \c pyclass pulls in with "using namespace", directly or transitively.
370static bool HasPythonAttrInUsing(PyObject* pyclass, PyObject* pyname,
371 std::set<PyObject*>& visited) {
372 for (auto pyref : GetUsingScopes(pyclass)) {
373 PyObject* pyuscope = cpyrt_GetWeakRef(pyref);
374 if (!pyuscope)
375 continue;
376 bool found = false;
377 if (visited.insert(pyuscope).second) {
378 found = _PyType_Lookup((PyTypeObject*)pyuscope, pyname) ||
379 _PyType_Lookup(Py_TYPE(pyuscope), pyname) ||
380 (CPPScope_Check(pyuscope) &&
381 (((CPPScope*)pyuscope)->fFlags & CPPScope::kIsNamespace) &&
382 HasPythonAttrInUsing(pyuscope, pyname, visited));
383 }
384 Py_DECREF(pyuscope);
385 if (found)
386 return true;
387 }
388 return false;
389}
390
391//----------------------------------------------------------------------------
392static PyObject* meta_getattro(PyObject* pyclass, PyObject* pyname) {
393 // normal type-based lookup
394 PyObject* attr = PyType_Type.tp_getattro(pyclass, pyname);
395 if (pyclass == (PyObject*)&CPPInstance_Type)
396 return attr;
397
398 PyObject* possibly_shadowed = nullptr;
399 if (attr) {
400 if (CPPScope_Check(attr) && CPPScope_Check(pyclass) &&
401 !(((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace)) {
402 // TODO: the goal here is to prevent typedefs that are shadowed in
403 // subclasses to be found as from the base class. The better approach
404 // would be to find all typedefs at class creation and insert placeholders
405 // to flag here. The current typedef loop in gInterpreter won't do,
406 // however.
407 PyObject* dct = PyObject_GetAttr(pyclass, PyStrings::gDict);
408 if (dct) {
409 PyObject* attr_from_dict = PyObject_GetItem(dct, pyname);
410 Py_DECREF(dct);
411 if (attr_from_dict) {
412 Py_DECREF(attr);
413 return attr_from_dict;
414 }
415 possibly_shadowed = attr;
416 attr = nullptr;
417 }
418 PyErr_Clear();
419 } else if (CPPScope_Check(attr) && CPPScope_Check(pyclass) &&
421 return CreateExcScopeProxy(attr, pyname, pyclass);
422 } else
423 return attr;
424 }
425
426 if (!PyUnicode_CheckExact(pyname) || !CPPScope_Check(pyclass))
427 return possibly_shadowed;
428
429 // filter for python specials
430 std::string name = PyUnicode_AsUTF8(pyname);
431 if (name.size() >= 5 && name.compare(0, 2, "__") == 0 &&
432 name.compare(name.size() - 2, name.size(), "__") == 0)
433 return possibly_shadowed;
434
435 // more elaborate search in case of failure (eg. for inner classes on demand)
436 std::vector<Utility::PyError_t> errors;
437 Utility::FetchError(errors);
438 attr = CreateScopeProxy(name, pyclass);
439
440 if (CPPScope_Check(attr) &&
442 // Instead of the CPPScope, return a fresh exception class derived from
443 // CPPExcInstance.
444 return CreateExcScopeProxy(attr, pyname, pyclass);
445 }
446
447 bool templated_functions_checked = false;
448 CPPScope* klass = ((CPPScope*)pyclass);
449 if (!attr) {
450 Utility::FetchError(errors);
451 interop::TCppScope_t scope = klass->fCppType;
452
453 // namespaces may have seen updates in their list of global functions, which
454 // are available as "methods" even though they're not really that
455 if ((klass->fFlags & CPPScope::kIsNamespace) ||
456 scope == interop::GetGlobalScope()) {
457 // tickle lazy lookup of functions
458 const std::vector<interop::TCppMethod_t> methods =
460 if (!methods.empty()) {
461 // function exists, now collect overloads
462 std::vector<PyCallable*> overloads;
463 for (auto method : methods) {
464 overloads.push_back(new CPPFunction(scope, method));
465 }
466
467 // Note: can't re-use Utility::AddClass here, as there's the risk of
468 // a recursive call. Simply add method directly, as we're guaranteed
469 // that it doesn't exist yet.
471 attr = add_template(pyclass, name, &overloads);
472 else
473 attr = (PyObject*)CPPOverload_New(name, overloads);
474 templated_functions_checked = true;
475 }
476 }
477
478 interop::TCppScope_t lookup_result = nullptr;
479 if (!attr) {
480 lookup_result = interop::GetNamed(name, scope);
481 if (interop::IsVariable(lookup_result) ||
482 interop::IsEnumConstant(lookup_result)) {
483 attr = (PyObject*)CPPDataMember_New(scope, lookup_result);
484 } else if (interop::IsTypedefed(lookup_result)) {
485 interop::TCppType_t resolved_type =
487 const std::string& cpd =
489 if (cpd == "*") {
492 if (tcl) {
493 typedefpointertoclassobject* tpc = PyObject_New(
495 tpc->fCppType = tcl;
496 attr = (PyObject*)tpc;
497 }
498 }
499 }
500 }
501
502 // function templates that have not been instantiated (namespaces _may_ have
503 // already been taken care of, by their general function lookup above)
504 if (!attr && !templated_functions_checked) {
506 attr = add_template(pyclass, name);
507 else {
508 // for completeness in error reporting
509 PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ template",
510 name.c_str());
511 Utility::FetchError(errors);
512 }
513 }
514
515 // enums types requested as type (rather than the constants)
516 if (!attr) {
517 interop::TCppScope_t enumerator =
518 interop::GetUnderlyingScope(lookup_result);
519 if (interop::IsEnumScope(enumerator)) {
520 // enum types (incl. named and class enums)
521 attr = (PyObject*)CPPEnum_New(name, enumerator);
522 } else {
523 // for completeness in error reporting
524 PyErr_Format(PyExc_TypeError, "\'%s\' is not a known C++ enum",
525 name.c_str());
526 Utility::FetchError(errors);
527 }
528 }
529
530 if (attr) {
531 // cache the result
533 PyType_Type.tp_setattro((PyObject*)Py_TYPE(pyclass), pyname, attr);
534
535 Py_DECREF(attr);
536 // The call below goes through "dm_get"
537 attr = PyType_Type.tp_getattro(pyclass, pyname);
538 if (!attr && PyErr_Occurred())
539 Utility::FetchError(errors);
540 } else
541 PyType_Type.tp_setattro(pyclass, pyname, attr);
542
543 } else {
544 Utility::FetchError(errors);
545 }
546 }
547
548 if (!attr && (klass->fFlags & CPPScope::kIsNamespace)) {
549 const std::vector<PyObject*>& using_scopes = GetUsingScopes(pyclass);
550
551 // Searching the using namespaces repeats the full attribute lookup in each
552 // of them, which is expensive for names that don't exist. Anything C++ it
553 // can find is also found by a single unqualified lookup from within this
554 // namespace, which follows the using-directives. If that finds nothing,
555 // only attributes set on the Python proxies of the using namespaces remain.
556 bool search_using = !using_scopes.empty();
557 if (search_using && IsPlainIdentifier(name) && !interop::IsBuiltin(name) &&
559 std::set<PyObject*> visited{pyclass};
560 search_using = HasPythonAttrInUsing(pyclass, pyname, visited);
561 }
562
563 // try all outstanding using namespaces in turn to find the attribute (will
564 // cache locally later; TODO: doing so may cause pathological cases)
565 for (auto pyref : using_scopes) {
566 if (!search_using)
567 break;
568 PyObject* pyuscope = cpyrt_GetWeakRef(pyref);
569 if (pyuscope) {
570 attr = PyObject_GetAttr(pyuscope, pyname);
571 if (!attr)
572 PyErr_Clear();
573 Py_DECREF(pyuscope);
574 }
575 if (attr)
576 break;
577 }
578 }
579
580 // if the attribute was not found but could possibly have been shadowed,
581 // insert it into the dict now, to short-circuit future lookups (TODO: this is
582 // part of the workaround described above of which the true solution is to
583 // loop over all typedefs at creation time for the class)
584 if (possibly_shadowed) {
585 if (attr) {
586 Py_DECREF(possibly_shadowed);
587 } else {
588 attr = possibly_shadowed;
589 PyType_Type.tp_setattro(pyclass, pyname, attr);
590 }
591 }
592
593 if (attr) {
594 PyErr_Clear();
595 } else {
596 // not found: prepare a full error report
597 PyObject* topmsg = nullptr;
598 PyObject *pytype = 0, *pyvalue = 0, *pytrace = 0;
599 PyErr_Fetch(&pytype, &pyvalue, &pytrace);
600 PyObject* sklass = PyObject_Str(pyclass);
601 PyErr_Restore(pytype, pyvalue, pytrace);
602 if (sklass) {
603 topmsg = PyUnicode_FromFormat(
604 "%s has no attribute \'%s\'. Full details:", PyUnicode_AsUTF8(sklass),
605 PyUnicode_AsUTF8(pyname));
606 Py_DECREF(sklass);
607 } else {
608 topmsg = PyUnicode_FromFormat("no such attribute \'%s\'. Full details:",
609 PyUnicode_AsUTF8(pyname));
610 }
611 SetDetailedException(std::move(errors), topmsg /* steals */,
612 PyExc_AttributeError /* default error */);
613 }
614
615 return attr;
616}
617
618//----------------------------------------------------------------------------
619static int meta_setattro(PyObject* pyclass, PyObject* pyname, PyObject* pyval) {
620 // Global data and static data in namespaces is found lazily, thus if the
621 // first use is setting of the global data by the user, it will not be
622 // reflected on the C++ side, b/c there is no descriptor yet. This triggers
623 // the creation for for such data as necessary. The many checks to narrow down
624 // the specific case are needed to prevent unnecessary lookups and recursion.
625 // skip if the given pyval is a descriptor already, or an unassignable class
626 if (((CPPScope*)pyclass)->fFlags & CPPScope::kIsNamespace &&
628 std::string name = PyUnicode_AsUTF8(pyname);
629 if (interop::GetNamed(name, ((CPPScope*)pyclass)->fCppType)) {
630 PyObject* attr = meta_getattro(pyclass, pyname); // triggers creation
631 if (!attr)
632 PyErr_Clear();
633 else
634 Py_DECREF(attr);
635 }
636 }
637
638 return PyType_Type.tp_setattro(pyclass, pyname, pyval);
639}
640
641//----------------------------------------------------------------------------
642static PyObject* meta_reflex(CPPScope* klass, PyObject* args) {
643 // Provide the requested reflection information.
644 interop::Reflex::RequestId_t request = -1;
646 if (!PyArg_ParseTuple(args, const_cast<char*>("i|i:__cpp_reflex__"), &request,
647 &format))
648 return nullptr;
649
650 switch (request) {
652 if (klass->fFlags & CPPScope::kIsNamespace)
655 break;
657 // this is not the strict C++ definition of aggregates, but is closer to
658 // what is needed for Numba and C calling conventions (TODO: probably have
659 // to check for all public data types, too, and maybe for no padding?)
660 if (interop::IsAggregate(klass->fCppType) ||
664 break;
665 }
666
667 PyErr_Format(PyExc_ValueError, "unsupported reflex request %d or format %d",
668 request, format);
669 return nullptr;
670}
671
672//----------------------------------------------------------------------------
673// p2.7 does not have a __dir__ in object, and object.__dir__ in p3 does not
674// quite what I'd expected of it, so the following pulls in the internal code
675#include "PyObjectDir27.inc"
676
677static PyObject* meta_dir(CPPScope* klass) {
678 // Collect a list of everything (currently) available in the namespace.
679 // The backend can filter by returning empty strings. Special care is
680 // taken for functions, which need not be unique (overloading).
681 using namespace cppjit::interop;
682
683 if ((void*)klass == (void*)&CPPInstance_Type)
684 return PyList_New(0);
685
686 if (!cpyrt::CPPScope_Check((PyObject*)klass)) {
687 PyErr_SetString(PyExc_TypeError, "C++ proxy scope expected");
688 return nullptr;
689 }
690
691 PyObject* dirlist = _generic_dir((PyObject*)klass);
692 if (!(klass->fFlags & CPPScope::kIsNamespace))
693 return dirlist;
694
695 std::set<std::string> cppnames;
696 interop::GetAllCppNames(klass->fCppType, cppnames);
697
698 // cleanup names
699 std::set<std::string> dir_cppnames;
700 for (const std::string& name : cppnames) {
701 if (name.find("__", 0, 2) != std::string::npos ||
702 name.find("<") != std::string::npos ||
703 name.find("operator", 0, 8) != std::string::npos)
704 continue;
705 dir_cppnames.insert(name);
706 }
707
708 // get rid of duplicates
709 for (Py_ssize_t i = 0; i < PyList_GET_SIZE(dirlist); ++i)
710 dir_cppnames.insert(PyUnicode_AsUTF8(PyList_GET_ITEM(dirlist, i)));
711
712 Py_DECREF(dirlist);
713 dirlist = PyList_New(dir_cppnames.size());
714
715 // copy total onto python list
716 Py_ssize_t i = 0;
717 for (const auto& name : dir_cppnames) {
718 PyList_SET_ITEM(dirlist, i++, PyUnicode_FromString(name.c_str()));
719 }
720 return dirlist;
721}
722
723//-----------------------------------------------------------------------------
724static PyMethodDef meta_methods[] = {
725 {(char*)"__cpp_reflex__", (PyCFunction)meta_reflex, METH_VARARGS,
726 (char*)"C++ datamember reflection information"},
727 {(char*)"__dir__", (PyCFunction)meta_dir, METH_NOARGS, nullptr},
728 {(char*)nullptr, nullptr, 0, nullptr}};
729
730//-----------------------------------------------------------------------------
731static PyGetSetDef meta_getset[] = {
732 {(char*)"__cpp_name__", (getter)meta_getcppname, nullptr, nullptr, nullptr},
733 {(char*)"__module__", (getter)meta_getmodule, (setter)meta_setmodule,
734 nullptr, nullptr},
735 {(char*)nullptr, nullptr, nullptr, nullptr, nullptr}};
736
737//= cpyrt object proxy type type ==========================================
738PyTypeObject CPPScope_Type = {
739 PyVarObject_HEAD_INIT(&PyType_Type, 0)(char*) "cppjit.CPPScope", // tp_name
740 sizeof(cpyrt::CPPScope), // tp_basicsize
741 0, // tp_itemsize
742 0, // tp_dealloc
743 0, // tp_vectorcall_offset / tp_print
744 0, // tp_getattr
745 0, // tp_setattr
746 0, // tp_as_async / tp_compare
747 (reprfunc)meta_repr, // tp_repr
748 0, // tp_as_number
749 0, // tp_as_sequence
750 0, // tp_as_mapping
751 0, // tp_hash
752 0, // tp_call
753 0, // tp_str
754 (getattrofunc)meta_getattro, // tp_getattro
755 (setattrofunc)meta_setattro, // tp_setattro
756 0, // tp_as_buffer
757 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
758 Py_TPFLAGS_TYPE_SUBCLASS, // tp_flags
759 (char*)"cpyrt metatype (internal)", // tp_doc
760 0, // tp_traverse
761 0, // tp_clear
762 0, // tp_richcompare
763 0, // tp_weaklistoffset
764 0, // tp_iter
765 0, // tp_iternext
766 meta_methods, // tp_methods
767 0, // tp_members
768 meta_getset, // tp_getset
769 &PyType_Type, // tp_base
770 0, // tp_dict
771 0, // tp_descr_get
772 0, // tp_descr_set
773 0, // tp_dictoffset
774 0, // tp_init
775 0, // tp_alloc
776 (newfunc)pt_new, // tp_new
777 0, // tp_free
778 0, // tp_is_gc
779 0, // tp_bases
780 0, // tp_mro
781 0, // tp_cache
782 0, // tp_subclasses
783 0, // tp_weaklist
784 0, // tp_del
785 0, // tp_version_tag
786 0, // tp_finalize
787 0 // tp_vectorcall
789
790} // namespace cppjit::cpyrt
free(fBuffer)
uint32_t fFlags
_object PyObject
#define c(i)
Definition RSha256.hxx:101
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 nitems
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t attr
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:142
Utility::PyOperators * fOperators
Definition CPPScope.h:68
std::vector< PyObject * > * fUsing
Definition CPPScope.h:66
union cppjit::cpyrt::CPPScope::@183 fImp
CppToPyMap_t * fCppObjects
Definition CPPScope.h:65
interop::TCppScope_t fCppType
Definition CPPScope.h:62
void AdoptTemplate(PyCallable *pc)
void AdoptMethod(PyCallable *pc)
#define Py_RETURN_TRUE
Definition cpyrt.h:73
#define Py_RETURN_FALSE
Definition cpyrt.h:77
#define CPYRT_PYTYPE_TAIL
Definition cpyrt.h:159
static PyObject * cpyrt_GetWeakRef(PyObject *ref)
Definition cpyrt.h:113
std::string clean_type(const std::string &cppname, bool template_strip=true, bool const_strip=true)
std::string compound(const std::string &name)
void cppscope_to_pyscope(std::string &cppscope)
size_t FetchError(std::vector< PyError_t > &, bool is_cpp=false)
Definition Utility.cxx:1627
static int meta_setattro(PyObject *pyclass, PyObject *pyname, PyObject *pyval)
Definition CPPScope.cxx:619
static PyObject * meta_reflex(CPPScope *klass, PyObject *args)
Definition CPPScope.cxx:642
bool InsertDispatcher(CPPScope *klass, PyObject *bases, PyObject *dct, std::ostringstream &err)
TemplateProxy * TemplateProxy_New(const std::string &cppname, const std::string &pyname, PyObject *pyclass)
static const std::vector< PyObject * > & GetUsingScopes(PyObject *pyclass)
Definition CPPScope.cxx:336
PyObject * CreateExcScopeProxy(PyObject *pyscope, PyObject *pyname, PyObject *parent)
static PyObject * meta_alloc(PyTypeObject *meta, Py_ssize_t nitems)
Definition CPPScope.cxx:73
PyTypeObject CPPScope_Type
Definition CPPScope.cxx:738
PyTypeObject TypedefPointerToClass_Type
static PyObject * meta_dir(CPPScope *klass)
Definition CPPScope.cxx:677
PyTypeObject CPPInstance_Type
PyObject * GetScopeProxy(interop::TCppScope_t)
static void meta_dealloc(CPPScope *scope)
Definition CPPScope.cxx:79
static PyObject * meta_getmodule(CPPScope *scope, void *)
Definition CPPScope.cxx:105
static PyObject * pt_new(PyTypeObject *subtype, PyObject *args, PyObject *kwds)
Definition CPPScope.cxx:198
static PyGetSetDef meta_getset[]
Definition CPPScope.cxx:731
bool TemplateProxy_CheckExact(T *object)
static PyObject * meta_repr(CPPScope *scope)
Definition CPPScope.cxx:167
PyObject * CreateScopeProxy(PyObject *, PyObject *args)
bool CPPDataMember_Check(T *object)
bool CPPScope_CheckExact(T *object)
Definition CPPScope.h:93
std::unordered_map< interop::TCppObject_t, PyObject * > CppToPyMap_t
Type object to hold class reference (this is only semantically a presentation of CPPScope instances,...
Definition CPPScope.h:38
static PyObject * add_template(PyObject *pyclass, const std::string &name, std::vector< PyCallable * > *overloads=nullptr)
Definition CPPScope.cxx:33
static PyMethodDef meta_methods[]
Definition CPPScope.cxx:724
bool CPPScope_Check(T *object)
Definition CPPScope.h:86
static bool IsPlainIdentifier(const std::string &name)
Definition CPPScope.cxx:324
CPPEnum * CPPEnum_New(const std::string &name, interop::TCppScope_t scope)
Definition CPPEnum.cxx:156
static int meta_setmodule(CPPScope *scope, PyObject *value, void *)
Definition CPPScope.cxx:146
static bool HasPythonAttrInUsing(PyObject *pyclass, PyObject *pyname, std::set< PyObject * > &visited)
Definition CPPScope.cxx:370
CPPOverload * CPPOverload_New(const std::string &name, std::vector< PyCallable * > &methods)
CPPDataMember * CPPDataMember_New(interop::TCppScope_t scope, interop::TCppScope_t var)
static PyObject * meta_getattro(PyObject *pyclass, PyObject *pyname)
Definition CPPScope.cxx:392
static PyObject * meta_getcppname(CPPScope *scope, void *)
Definition CPPScope.cxx:97
const FormatId_t OPTIMAL
Definition Reflex.h:22
const RequestId_t IS_AGGREGATE
Definition Reflex.h:15
const RequestId_t IS_NAMESPACE
Definition Reflex.h:14
Cpp::TypeRef TCppType_t
Definition API.h:42
TCppType_t GetTypeFromScope(TCppScope_t klass)
std::string GetTypeAsString(TCppType_t type)
bool ExistsMethodTemplate(TCppScope_t scope, const std::string &name)
bool IsBuiltin(const std::string &type_name)
bool HasVirtualDestructor(TCppScope_t type)
TCppScope_t GetParentScope(TCppScope_t scope)
bool IsSubclass(TCppScope_t derived, TCppScope_t base)
TCppScope_t GetNamed(const std::string &scope_name, TCppScope_t parent_scope=TCppScope_t{})
Cpp::FuncRef TCppMethod_t
Definition API.h:44
bool IsTypedefed(TCppScope_t scope)
bool IsEnumScope(TCppScope_t scope)
bool GetSmartPtrInfo(TCppScope_t klass, TCppScope_t *raw, TCppMethod_t *deref)
TCppType_t ResolveType(TCppType_t cppitem_name)
TCppScope_t GetUnderlyingScope(TCppScope_t scope)
TCppScope_t GetGlobalScope()
bool IsVisibleName(const std::string &name, TCppScope_t parent_scope=TCppScope_t{})
std::vector< TCppMethod_t > GetMethodsFromName(TCppScope_t scope, const std::string &name)
bool IsNamespace(TCppScope_t scope)
TCppScope_t GetScopeFromType(TCppType_t type)
std::string GetBaseName(TCppScope_t klass, TCppIndex_t ibase)
TCppScope_t GetScope(const std::string &scope_name, TCppScope_t parent_scope=TCppScope_t{})
bool IsEnumConstant(TCppScope_t scope)
std::string GetFinalName(TCppScope_t type)
bool IsVariable(TCppScope_t scope)
std::vector< TCppScope_t > GetUsingNamespaces(TCppScope_t)
bool IsAggregate(TCppScope_t type)
std::string GetScopedFinalName(TCppScope_t type)
void GetAllCppNames(TCppScope_t scope, std::set< std::string > &cppnames)
Cpp::DeclRef TCppScope_t
Definition API.h:41
Definition API.cxx:34
Custom "builtins," detectable by type, for pass by ref and improved performance.
PyObject_HEAD interop::TCppScope_t fCppType