Library of Assembled Shared Sources
class_definition.cpp
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2025 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43
44
45#include "python_common.h"
46#include "class_definition.h"
47#include "enum_definition.h"
48#include "overload_link.h"
49#include "pyobject_plus.h"
50#include "_lass_module.h"
51#include <iostream>
52#include <cstring>
53
54#if LASS_PLATFORM_TYPE == LASS_PLATFORM_TYPE_WIN32
55# pragma warning(disable: 4996) // This function or variable may be unsafe ...
56#endif
57
58namespace lass
59{
60namespace python
61{
62namespace impl
63{
64
65
66
67PyMethodDef createPyMethodDef(const char *ml_name, PyCFunction ml_meth, int ml_flags, const char *ml_doc)
68{
69 PyMethodDef temp;
70 temp.ml_name = const_cast<char*>(ml_name);
71 temp.ml_meth = ml_meth;
72 temp.ml_flags = ml_flags;
73 temp.ml_doc = const_cast<char*>(ml_doc);
74 return temp;
75}
76
77
78
79PyGetSetDef createPyGetSetDef( const char* name, getter get, setter set, const char* doc, void* closure )
80{
81 PyGetSetDef temp;
82 temp.name = const_cast<char*>(name);
83 temp.get = get;
84 temp.set = set;
85 temp.doc = const_cast<char*>(doc);
86 temp.closure = closure;
87 return temp;
88}
89
90
91
92void dealloc(PyObject* obj)
93{
94 delete static_cast<PyObjectPlus*>(obj);
95};
96
97
98
99int traverse(PyObject* self, visitproc visit, void* arg)
100{
101 Py_VISIT(Py_TYPE(self));
102 return 0;
103}
104
105
106
108 const char* name, const char* doc, Py_ssize_t typeSize,
109 richcmpfunc richcmp, ClassDefinition* parent, TClassRegisterHook registerHook):
110 slots_({{ 0, nullptr }}),
111 parent_(parent),
112 classRegisterHook_(registerHook),
113 className_(name),
114 doc_(doc),
115 implicitConvertersSlot_(0),
116 isFrozen_(false)
117{
118 PyType_Spec spec = {
119 nullptr, /* name */
120 static_cast<int>(typeSize), /* basicsize */
121 0, /* itemsize */
122 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, /* flags */
123 nullptr, /* slots */
124 };
125 spec_ = spec;
126 setSlot(Py_tp_dealloc, &dealloc);
127 setSlot(Py_tp_traverse, &traverse);
128 if (richcmp)
129 {
130 setSlot(Py_tp_richcompare, richcmp);
131 }
132 methods_.push_back(impl::createPyMethodDef( 0, 0, 0, 0 ));
133 getSetters_.push_back(impl::createPyGetSetDef( 0, 0, 0, 0, 0 ));
134}
135
136
137
141
142
143
144const PyTypeObject* ClassDefinition::type() const
145{
146 LASS_ENFORCE(isFrozen_)(name())(" is not frozen yet");
147 return reinterpret_cast<PyTypeObject*>(type_.get());
148}
149
150
151
153{
154 LASS_ENFORCE(isFrozen_)(name())(" is not frozen yet");
155 return reinterpret_cast<PyTypeObject*>(type_.get());
156}
157
158
159
160const char* ClassDefinition::name() const
161{
162 return className_;
163}
164
165
166
167const char* ClassDefinition::doc() const
168{
169 return doc_;
170}
171
172
173
175{
176 doc_ = doc;
177}
178
179
181{
182 if (doc)
183 {
184 setDoc(doc);
185 }
186}
187
188
190{
191 LASS_ASSERT(!slots_.empty() && slots_.back().slot == 0);
192 LASS_ASSERT(slotId > 0);
193 TSlots::iterator i = std::lower_bound(slots_.begin(), slots_.end() - 1, slotId, [](const PyType_Slot& a, TSlotID b) { return a.slot < b; });
194 if (i != slots_.end() && i->slot == slotId)
195 {
196 return i->pfunc;
197 }
198 return nullptr;
199}
200
201
202void* ClassDefinition::setSlot(TSlotID slotId, void* value)
203{
204 LASS_ASSERT(!isFrozen_);
205 LASS_ASSERT(!slots_.empty() && slots_.back().slot == 0);
206 LASS_ASSERT(slotId > 0);
207 LASS_ASSERT(value);
208 TSlots::iterator i = std::lower_bound(slots_.begin(), slots_.end() - 1, slotId, [](const PyType_Slot& a, TSlotID b) { return a.slot < b; });
209 if (i != slots_.end() && i->slot == slotId)
210 {
211 void* old = i->pfunc;
212 i->pfunc = value;
213 return old;
214 }
215 else
216 {
217 PyType_Slot slot = { slotId, value };
218 slots_.insert(i, slot);
219 return nullptr;
220 }
221}
222
223
224void ClassDefinition::addConstructor(newfunc dispatcher, newfunc& overloadChain)
225{
226 overloadChain = setSlot(Py_tp_new, dispatcher);
227}
228
229
230void ClassDefinition::addMethod(const char* name, const char* doc, PyCFunction dispatcher, OverloadLink& overloadChain)
231{
232 TMethods::iterator i = std::find_if(methods_.begin(), methods_.end(), NamePredicate(name));
233 if (i == methods_.end())
234 {
235 methods_.insert(methods_.begin(), createPyMethodDef(name, dispatcher, METH_VARARGS , doc));
236 overloadChain.setNull();
237 }
238 else
239 {
240 LASS_ASSERT(i->ml_flags == METH_VARARGS);
241 overloadChain.setPyCFunction(i->ml_meth);
242 i->ml_meth = dispatcher;
243 if (i->ml_doc == 0)
244 {
245 i->ml_doc = const_cast<char*>(doc);
246 }
247 };
248}
249
250void ClassDefinition::addMethod(const ComparatorSlot& slot, const char*, PyCFunction dispatcher, OverloadLink&)
251{
252 compareFuncs_.push_back(CompareFunc(dispatcher, slot.slot));
253}
254
255void ClassDefinition::addMethod(const LenSlot& slot, const char*, lenfunc dispatcher, OverloadLink&)
256{
257 setSlot(slot.slot, dispatcher);
258}
259
260void ClassDefinition::addMethod(const UnarySlot& slot, const char*, unaryfunc dispatcher, OverloadLink&)
261{
262 setSlot(slot.slot, dispatcher);
263}
264
265void ClassDefinition::addMethod(const BinarySlot& slot, const char*, binaryfunc dispatcher, OverloadLink& overloadChain)
266{
267 overloadChain.setBinaryfunc(setSlot(slot.slot, dispatcher));
268}
269
270void ClassDefinition::addMethod(const TernarySlot& slot, const char*, ternaryfunc dispatcher, OverloadLink& overloadChain)
271{
272 overloadChain.setTernaryfunc(setSlot(slot.slot, dispatcher));
273}
274
275void ClassDefinition::addMethod(const SsizeArgSlot& slot, const char*, ssizeargfunc dispatcher, OverloadLink& overloadChain)
276{
277 overloadChain.setSsizeArgfunc(setSlot(slot.slot, dispatcher));
278}
279
280void ClassDefinition::addMethod(const SsizeObjArgSlot& slot, const char*, ssizeobjargproc dispatcher, OverloadLink& overloadChain)
281{
282 overloadChain.setSsizeObjArgProcfunc(setSlot(slot.slot, dispatcher));
283}
284
285void ClassDefinition::addMethod(const ObjObjSlot& slot, const char*, objobjproc dispatcher, OverloadLink& overloadChain)
286{
287 overloadChain.setObjObjProcfunc(setSlot(slot.slot, dispatcher));
288}
289
290void ClassDefinition::addMethod(const ObjObjArgSlot& slot, const char*, objobjargproc dispatcher, OverloadLink& overloadChain)
291{
292 overloadChain.setObjObjArgProcfunc(setSlot(slot.slot, dispatcher));
293}
294
295void ClassDefinition::addMethod(const IterSlot& slot, const char*, getiterfunc dispatcher, OverloadLink& overloadChain)
296{
297 overloadChain.setGetIterFunc(setSlot(slot.slot, dispatcher));
298}
299
300void ClassDefinition::addMethod(const IterNextSlot& slot, const char*, iternextfunc dispatcher, OverloadLink& overloadChain)
301{
302 overloadChain.setIterNextFunc(setSlot(slot.slot, dispatcher));
303}
304
305void ClassDefinition::addMethod(const ArgKwSlot& slot, const char*, ternaryfunc dispatcher, OverloadLink& overloadChain)
306{
307 overloadChain.setArgKwfunc(setSlot(slot.slot, dispatcher));
308}
309
310void ClassDefinition::addMethod(const InquirySlot& slot, const char*, inquiry dispatcher, OverloadLink&)
311{
312 setSlot(slot.slot, dispatcher);
313}
314
315void ClassDefinition::addGetSetter(const char* name, const char* doc, getter get, setter set)
316{
317 getSetters_.insert(getSetters_.begin(), impl::createPyGetSetDef(name, get, set, doc, 0));
318}
319
320void ClassDefinition::addStaticMethod(const char* name, const char* doc, PyCFunction dispatcher, PyCFunction& overloadChain)
321{
322 std::vector<PyMethodDef>::iterator i = ::std::find_if(methods_.begin(), methods_.end(), NamePredicate(name));
323 if (i == methods_.end())
324 {
325 methods_.insert(methods_.begin(), createPyMethodDef(name, dispatcher, METH_VARARGS | METH_STATIC, doc));
326 overloadChain = 0;
327 }
328 else
329 {
330 LASS_ASSERT(i->ml_flags == (METH_VARARGS | METH_STATIC));
331 overloadChain = i->ml_meth;
332 i->ml_meth = dispatcher;
333 if (i->ml_doc == 0)
334 {
335 i->ml_doc = const_cast<char*>(doc);
336 }
337 }
338}
339
341{
342 LASS_ASSERT(std::count_if(innerClasses_.begin(), innerClasses_.end(), NamePredicate(innerClass.name())) == 0);
343 innerClasses_.push_back(&innerClass);
344}
345
347{
348 // LASS_ASSERT(std::count_if(innerClasses_.begin(), innerClasses_.end(), NamePredicate(innerClass.name())) == 0);
349 innerEnums_.push_back(enumDefinition);
350}
351
352PyObject* ClassDefinition::freezeDefinition(PyObject* module)
353{
354 return freezeDefinition(module, nullptr);
355}
356
357PyObject* ClassDefinition::freezeDefinition(PyObject* module, const char* scopeName)
358{
359 LASS_ASSERT(!isFrozen_);
360 if (isFrozen_)
361 {
362 return type_.get();
363 }
364
365 if (parent_)
366 {
367 // In the general case, we can't freeze the parent's definition, as we can't be sure of its module or scopeName.
368 // However, in case of PyObjectPlus, nobody else will do it.
369 if (parent_ == &PyObjectPlus::_lassPyClassDef)
370 {
371 if (impl::initLassModule() != 0)
372 {
373 return nullptr;
374 }
375 }
376 if (!parent_->isFrozen_)
377 {
378 PyErr_Format(PyExc_AssertionError, "Parent class %s of %s is not frozen yet", parent_->className_, className_);
379 }
380 parent_->subClasses_.push_back(this);
381 }
382
383 const char* moduleName = nullptr;
384 if (module)
385 {
386 moduleName = PyModule_GetName(module);
387 if (!moduleName)
388 {
389 return nullptr;
390 }
391 LASS_ASSERT(!spec_.name || std::strcmp(spec_.name, moduleName) == 0);
392 if (!spec_.name)
393 {
394 const size_t n = std::strlen(moduleName) + std::strlen(className_) + 2; // one extra for dot, and one extra for null
395 char* buf = static_cast<char*>(std::malloc(n));
396 const int r = ::snprintf(buf, n, "%s.%s", moduleName, className_);
397 LASS_ENFORCE(r > 0 && static_cast<size_t>(r) < n);
398 spec_.name = buf;
399 }
400 }
401 else
402 {
403 LASS_ASSERT(!spec_.name || std::strcmp(spec_.name, className_) == 0);
404 if (!spec_.name)
405 {
406 spec_.name = className_;
407 }
408 }
409
410 if (!type_)
411 {
412 setSlot(Py_tp_base, parent_ ? parent_->type() : &PyBaseObject_Type); // INCREF???
413 setSlot(Py_tp_methods, &methods_[0]);
414 setSlot(Py_tp_getset, &getSetters_[0]);
415 if (doc_) // a nullptr as Py_tp_doc causes access violation in PyType_FromSpec
416 {
417 setSlot(Py_tp_doc, const_cast<char*>(doc_));
418 }
419 LASS_ASSERT(slots_.back().slot == 0);
420 spec_.slots = &slots_[0];
421
422#if PY_VERSION_HEX >= 0x030a0000 // >= 3.10
423 if (getSlot(Py_tp_new) == nullptr)
424 {
425 // We don't have a constructor, so we disallow instantiation.
426 spec_.flags |= Py_TPFLAGS_DISALLOW_INSTANTIATION;
427 }
428#endif
429
430 type_.reset(PyType_FromModuleAndSpec(module, &spec_, nullptr));
431 if (!type_)
432 {
433 return nullptr;
434 }
435 }
436
437 PyObject* type = type_.get();
438
439 const char* qualname = className_;
440 if (scopeName)
441 {
442 const size_t n = std::strlen(scopeName) + std::strlen(className_) + 2; // one extra for dot, and one extra for null
443 char* buf = static_cast<char*>(std::malloc(n));
444 const int r = ::snprintf(buf, n, "%s.%s", scopeName, className_);
445 LASS_ENFORCE(r > 0 && static_cast<size_t>(r) < n);
446 qualname = buf;
447 TPyObjPtr objQualname(pyBuildSimpleObject(qualname));
448 if (!objQualname || PyObject_SetAttrString(type, "__qualname__", objQualname.get()) != 0)
449 {
450 return nullptr;
451 }
452 }
453 for (TStaticMembers::const_iterator i = statics_.begin(); i != statics_.end(); ++i)
454 {
455 TPyObjPtr obj = i->member()->build();
456 if (!obj || PyObject_SetAttrString(type, i->name(), obj.get()) != 0)
457 {
458 return nullptr;
459 }
460 }
461 for (TClassDefs::const_iterator i = innerClasses_.begin(); i != innerClasses_.end(); ++i)
462 {
463 ClassDefinition* innerClass = *i;
464 const char* shortName = innerClass->name();
465 PyObject* innerType = innerClass->freezeDefinition(module, qualname);
466 if (!innerType || PyObject_SetAttrString(type, shortName, innerType) != 0)
467 {
468 return nullptr;
469 }
470 }
471 for (auto def : innerEnums_)
472 {
473 PyObject* enumType = def->freezeDefinition(moduleName, qualname);
474 if (!enumType || PyObject_SetAttrString(type, def->name(), enumType) != 0)
475 {
476 return nullptr;
477 }
478 }
479
480 if (classRegisterHook_)
481 {
482 classRegisterHook_();
483 }
484
485#if PY_VERSION_HEX >= 0x030a0000 // >= 3.10
486 reinterpret_cast<PyTypeObject*>(type)->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
487#endif
488
489 isFrozen_ = true;
490 return type;
491}
492
493PyObject* ClassDefinition::callRichCompare(PyObject* self, PyObject* other, int op)
494{
495 // don't worry about GIL, as we should still have it when we get here ...
496
497 if (other == Py_None)
498 {
499 // we need to treat the None type differently because the pyGet/BuildSimpleObject are able to cast
500 // from None but if you give that thing to a reference, then you are in big trouble
501 switch (op)
502 {
503 case Py_EQ:
504 {
505 if (self == other)
506 Py_RETURN_TRUE;
507 else
508 Py_RETURN_FALSE;
509 }
510 case Py_NE:
511 {
512 if (self != other)
513 Py_RETURN_TRUE;
514 else
515 Py_RETURN_FALSE;
516 }
517 // don't define any order relation on None
518 default:
519 Py_RETURN_FALSE;
520 };
521 }
522
523 TPyObjPtr args(Py_BuildValue("(O)", other));
524 const TCompareFuncs::const_iterator end = compareFuncs_.end();
525 for (TCompareFuncs::const_iterator i = compareFuncs_.begin(); i != end; ++i)
526 {
527 if (i->op == op)
528 {
529 PyObject* result = (i->dispatcher)(self, args.get());\
530 if (result || (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)))
531 {
532 return result;
533 }
534 }
535 }
536
537 if (!parent_)
538 {
539 static const char* symbols[] = { "<", "<=", "==", "!=", ">", ">=" };
540 LASS_ASSERT(op >= 0 && op <= Py_GE);
541 std::ostringstream buffer;
542 buffer << "Comparison operator " << symbols[op] << " not implemented for this type";
543 PyErr_SetString(PyExc_NotImplementedError, buffer.str().c_str());
544 return 0;
545 }
546
547 return parent_->callRichCompare(self, other, op);
548}
549
550}
551}
552}
553
554// EOF
Base class of all enum definitions.
void addGetSetter(const char *name, const char *doc, getter get, setter set)
Add a property with optional getter/setter.
void setDocIfNotNull(const char *doc)
Set the class docstring if non-null (keeps existing one if nullptr).
PyObject * callRichCompare(PyObject *self, PyObject *other, int op)
Dispatch rich-compare for this class (used by operator slots).
void addMethod(const char *name, const char *doc, PyCFunction dispatcher, OverloadLink &overloadChain)
Add a named method.
void * setSlot(TSlotID slotId, void *value)
Set raw pointer for a given slot id.
ClassDefinition(const char *name, const char *doc, Py_ssize_t typeSize, richcmpfunc richcmp, ClassDefinition *parent, TClassRegisterHook registerHook)
Construct a class definition.
void addInnerClass(ClassDefinition &innerClass)
Add a nested class definition (inner class).
int TSlotID
Function to call during registration of the class (optional).
PyTypeObject * type()
Get the Python type object (available after freezeDefinition() has been called).
const char * doc() const
Get the class docstring.
void addStaticMethod(const char *name, const char *doc, PyCFunction dispatcher, PyCFunction &overloadChain)
Add a static method with overload support.
void addInnerEnum(EnumDefinitionBase *enumDefinition)
Add a nested enum definition (inner enum).
void setDoc(const char *doc)
Set the class docstring.
void addConstructor(newfunc dispatcher, newfunc &overloadChain)
Add a constructor overload (__init__ dispatcher).
const char * name() const
Get the class name.
PyObject * freezeDefinition(PyObject *module=nullptr)
Finalize the definition and create the Python type.
void * getSlot(TSlotID slotId)
Get raw pointer from a given slot id.
PyObjectPtr< PyObject >::Type TPyObjPtr
PyObjectPtr to a PyObject.
Comprehensive C++ to Python binding library.
Library for Assembled Shared Sources.
Definition config.h:53