Library of Assembled Shared Sources
 
Loading...
Searching...
No Matches
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-2026 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"
52#include <iostream>
53#include <cstring>
54
55#if LASS_PLATFORM_TYPE == LASS_PLATFORM_TYPE_WIN32
56# pragma warning(disable: 4996) // This function or variable may be unsafe ...
57#endif
58
59namespace lass
60{
61namespace python
62{
63namespace impl
64{
65
66
67
68PyMethodDef createPyMethodDef(const char *ml_name, PyCFunction ml_meth, int ml_flags, const char *ml_doc)
69{
70 PyMethodDef temp;
71 temp.ml_name = const_cast<char*>(ml_name);
72 temp.ml_meth = ml_meth;
73 temp.ml_flags = ml_flags;
74 temp.ml_doc = const_cast<char*>(ml_doc);
75 return temp;
76}
77
78
79
80PyGetSetDef createPyGetSetDef( const char* name, getter get, setter set, const char* doc, void* closure )
81{
82 PyGetSetDef temp;
83 temp.name = const_cast<char*>(name);
84 temp.get = get;
85 temp.set = set;
86 temp.doc = const_cast<char*>(doc);
87 temp.closure = closure;
88 return temp;
89}
90
91
92
93void dealloc(PyObject* obj)
94{
95 delete static_cast<PyObjectPlus*>(obj);
96};
97
98
99
101 const char* name, const char* doc, Py_ssize_t typeSize,
102 richcmpfunc richcmp, ClassDefinition* parent, TClassRegisterHook registerHook):
103 slots_({{ 0, nullptr }}),
104 parent_(parent),
105 classRegisterHook_(registerHook),
106 className_(name),
107 doc_(doc),
108 implicitConvertersSlot_(0),
109 isFrozen_(false)
110{
111 PyType_Spec spec = {
112 nullptr, /* name */
113 static_cast<int>(typeSize), /* basicsize */
114 0, /* itemsize */
115 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* flags */
116 nullptr, /* slots */
117 };
118 spec_ = spec;
119 setSlot(Py_tp_dealloc, &dealloc);
120 if (richcmp)
121 {
122 setSlot(Py_tp_richcompare, richcmp);
123 }
124 methods_.push_back(impl::createPyMethodDef( 0, 0, 0, 0 ));
125 getSetters_.push_back(impl::createPyGetSetDef( 0, 0, 0, 0, 0 ));
126}
127
128
129
133
134
135
136const PyTypeObject* ClassDefinition::type() const
137{
138 LASS_ENFORCE(type_)(name())(" is not frozen yet");
139 return reinterpret_cast<PyTypeObject*>(type_.get());
140}
141
142
143
145{
146 LASS_ENFORCE(type_)(name())(" is not frozen yet");
147 return reinterpret_cast<PyTypeObject*>(type_.get());
148}
149
150
151
152const char* ClassDefinition::name() const
153{
154 return className_;
155}
156
157
158
159const char* ClassDefinition::doc() const
160{
161 return doc_;
162}
163
164
165
167{
168 doc_ = doc;
169}
170
171
173{
174 if (doc)
175 {
176 setDoc(doc);
177 }
178}
179
180
182{
183 LASS_ASSERT(!slots_.empty() && slots_.back().slot == 0);
184 LASS_ASSERT(slotId > 0);
185 TSlots::iterator i = std::lower_bound(slots_.begin(), slots_.end() - 1, slotId, [](const PyType_Slot& a, TSlotID b) { return a.slot < b; });
186 if (i != slots_.end() && i->slot == slotId)
187 {
188 return i->pfunc;
189 }
190 return nullptr;
191}
192
193
194void* ClassDefinition::setSlot(TSlotID slotId, void* value)
195{
196 LASS_ASSERT(!isFrozen_);
197 LASS_ASSERT(!slots_.empty() && slots_.back().slot == 0);
198 LASS_ASSERT(slotId > 0);
199 LASS_ASSERT(value);
200 TSlots::iterator i = std::lower_bound(slots_.begin(), slots_.end() - 1, slotId, [](const PyType_Slot& a, TSlotID b) { return a.slot < b; });
201 if (i != slots_.end() && i->slot == slotId)
202 {
203 void* old = i->pfunc;
204 i->pfunc = value;
205 return old;
206 }
207 else
208 {
209 PyType_Slot slot = { slotId, value };
210 slots_.insert(i, slot);
211 return nullptr;
212 }
213}
214
215
216void ClassDefinition::addConstructor(newfunc dispatcher, newfunc& overloadChain)
217{
218 overloadChain = setSlot(Py_tp_new, dispatcher);
219}
220
221
222void ClassDefinition::addMethod(const char* name, const char* doc, PyCFunction dispatcher, OverloadLink& overloadChain)
223{
224 TMethods::iterator i = std::find_if(methods_.begin(), methods_.end(), NamePredicate(name));
225 if (i == methods_.end())
226 {
227 methods_.insert(methods_.begin(), createPyMethodDef(name, dispatcher, METH_VARARGS , doc));
228 overloadChain.setNull();
229 }
230 else
231 {
232 LASS_ASSERT(i->ml_flags == METH_VARARGS);
233 overloadChain.setPyCFunction(i->ml_meth);
234 i->ml_meth = dispatcher;
235 if (i->ml_doc == 0)
236 {
237 i->ml_doc = const_cast<char*>(doc);
238 }
239 };
240}
241
242void ClassDefinition::addMethod(const ComparatorSlot& slot, const char*, PyCFunction dispatcher, OverloadLink&)
243{
244 compareFuncs_.push_back(CompareFunc(dispatcher, slot.slot));
245}
246
247void ClassDefinition::addMethod(const LenSlot& slot, const char*, lenfunc dispatcher, OverloadLink&)
248{
249 setSlot(slot.slot, dispatcher);
250}
251
252void ClassDefinition::addMethod(const UnarySlot& slot, const char*, unaryfunc dispatcher, OverloadLink&)
253{
254 setSlot(slot.slot, dispatcher);
255}
256
257void ClassDefinition::addMethod(const BinarySlot& slot, const char*, binaryfunc dispatcher, OverloadLink& overloadChain)
258{
259 overloadChain.setBinaryfunc(setSlot(slot.slot, dispatcher));
260}
261
262void ClassDefinition::addMethod(const TernarySlot& slot, const char*, ternaryfunc dispatcher, OverloadLink& overloadChain)
263{
264 overloadChain.setTernaryfunc(setSlot(slot.slot, dispatcher));
265}
266
267void ClassDefinition::addMethod(const SsizeArgSlot& slot, const char*, ssizeargfunc dispatcher, OverloadLink& overloadChain)
268{
269 overloadChain.setSsizeArgfunc(setSlot(slot.slot, dispatcher));
270}
271
272void ClassDefinition::addMethod(const SsizeObjArgSlot& slot, const char*, ssizeobjargproc dispatcher, OverloadLink& overloadChain)
273{
274 overloadChain.setSsizeObjArgProcfunc(setSlot(slot.slot, dispatcher));
275}
276
277void ClassDefinition::addMethod(const ObjObjSlot& slot, const char*, objobjproc dispatcher, OverloadLink& overloadChain)
278{
279 overloadChain.setObjObjProcfunc(setSlot(slot.slot, dispatcher));
280}
281
282void ClassDefinition::addMethod(const ObjObjArgSlot& slot, const char*, objobjargproc dispatcher, OverloadLink& overloadChain)
283{
284 overloadChain.setObjObjArgProcfunc(setSlot(slot.slot, dispatcher));
285}
286
287void ClassDefinition::addMethod(const IterSlot& slot, const char*, getiterfunc dispatcher, OverloadLink& overloadChain)
288{
289 overloadChain.setGetIterFunc(setSlot(slot.slot, dispatcher));
290}
291
292void ClassDefinition::addMethod(const IterNextSlot& slot, const char*, iternextfunc dispatcher, OverloadLink& overloadChain)
293{
294 overloadChain.setIterNextFunc(setSlot(slot.slot, dispatcher));
295}
296
297void ClassDefinition::addMethod(const ArgKwSlot& slot, const char*, ternaryfunc dispatcher, OverloadLink& overloadChain)
298{
299 overloadChain.setArgKwfunc(setSlot(slot.slot, dispatcher));
300}
301
302void ClassDefinition::addMethod(const InquirySlot& slot, const char*, inquiry dispatcher, OverloadLink&)
303{
304 setSlot(slot.slot, dispatcher);
305}
306
307void ClassDefinition::addGetSetter(const char* name, const char* doc, getter get, setter set)
308{
309 getSetters_.insert(getSetters_.begin(), impl::createPyGetSetDef(name, get, set, doc, 0));
310}
311
312void ClassDefinition::addStaticMethod(const char* name, const char* doc, PyCFunction dispatcher, PyCFunction& overloadChain)
313{
314 std::vector<PyMethodDef>::iterator i = ::std::find_if(methods_.begin(), methods_.end(), NamePredicate(name));
315 if (i == methods_.end())
316 {
317 methods_.insert(methods_.begin(), createPyMethodDef(name, dispatcher, METH_VARARGS | METH_STATIC, doc));
318 overloadChain = 0;
319 }
320 else
321 {
322 LASS_ASSERT(i->ml_flags == (METH_VARARGS | METH_STATIC));
323 overloadChain = i->ml_meth;
324 i->ml_meth = dispatcher;
325 if (i->ml_doc == 0)
326 {
327 i->ml_doc = const_cast<char*>(doc);
328 }
329 }
330}
331
333{
334 LASS_ASSERT(std::count_if(innerClasses_.begin(), innerClasses_.end(), NamePredicate(innerClass.name())) == 0);
335 innerClasses_.push_back(&innerClass);
336}
337
339{
340 // LASS_ASSERT(std::count_if(innerClasses_.begin(), innerClasses_.end(), NamePredicate(innerClass.name())) == 0);
341 innerEnums_.push_back(enumDefinition);
342}
343
344PyObject* ClassDefinition::freezeDefinition(PyObject* module)
345{
346 return freezeDefinition(module, nullptr);
347}
348
349PyObject* ClassDefinition::freezeDefinition(PyObject* module, const char* scopeName)
350{
351 LASS_ASSERT(!isFrozen_);
352 if (isFrozen_)
353 {
354 return type_.get();
355 }
356
357 if (parent_)
358 {
359 // In the general case, we can't freeze the parent's definition, as we can't be sure of its module or scopeName.
360 // However, in case of PyObjectPlus, nobody else will do it.
361 if (parent_ == &PyObjectPlus::_lassPyClassDef)
362 {
363 if (impl::initLassModule() != 0)
364 {
365 return nullptr;
366 }
367 }
368 if (!parent_->type_)
369 {
370 PyErr_Format(PyExc_AssertionError, "Parent class %s of %s is not frozen yet", parent_->className_, className_);
371 return nullptr;
372 }
373 parent_->subClasses_.push_back(this);
374 }
375
376 const char* moduleName = nullptr;
377 if (module)
378 {
379 moduleName = PyModule_GetName(module);
380 if (!moduleName)
381 {
382 return nullptr;
383 }
384 LASS_ASSERT(!spec_.name || std::strcmp(spec_.name, moduleName) == 0);
385 if (!spec_.name)
386 {
387 const size_t n = std::strlen(moduleName) + std::strlen(className_) + 2; // one extra for dot, and one extra for null
388 char* buf = static_cast<char*>(std::malloc(n));
389 if (!buf)
390 {
391 PyErr_NoMemory();
392 return nullptr;
393 }
394 const int r = ::snprintf(buf, n, "%s.%s", moduleName, className_);
395 LASS_ENFORCE(r > 0 && static_cast<size_t>(r) < n);
396 spec_.name = buf; // leaked on purpose
397 }
398 }
399 else
400 {
401 LASS_ASSERT(!spec_.name || std::strcmp(spec_.name, className_) == 0);
402 if (!spec_.name)
403 {
404 spec_.name = className_;
405 }
406 }
407
408 if (!type_)
409 {
410 setSlot(Py_tp_base, parent_ ? parent_->type() : &PyBaseObject_Type); // INCREF???
411 setSlot(Py_tp_methods, &methods_[0]);
412 setSlot(Py_tp_getset, &getSetters_[0]);
413 if (doc_) // a nullptr as Py_tp_doc causes access violation in PyType_FromSpec
414 {
415 setSlot(Py_tp_doc, const_cast<char*>(doc_));
416 }
417 LASS_ASSERT(slots_.back().slot == 0);
418 spec_.slots = &slots_[0];
419
420 if (getSlot(Py_tp_new) == nullptr)
421 {
422 // We don't have a constructor, so we disallow instantiation.
423 spec_.flags |= Py_TPFLAGS_DISALLOW_INSTANTIATION;
424 }
425
426 type_.reset(PyType_FromModuleAndSpec(module, &spec_, nullptr));
427 if (!type_)
428 {
429 return nullptr;
430 }
431 }
432
433 PyObject* type = type_.get();
434
435 const char* qualname = className_;
436 std::string scopedQualname;
437 if (scopeName)
438 {
439 scopedQualname = stde::safe_format("%s.%s", scopeName, className_);
440 qualname = scopedQualname.data();
441 TPyObjPtr objQualname(pyBuildSimpleObject(qualname));
442 if (!objQualname || PyObject_SetAttrString(type, "__qualname__", objQualname.get()) != 0)
443 {
444 return nullptr;
445 }
446 }
447 for (TStaticMembers::const_iterator i = statics_.begin(); i != statics_.end(); ++i)
448 {
449 TPyObjPtr obj = i->member()->build();
450 if (!obj || PyObject_SetAttrString(type, i->name(), obj.get()) != 0)
451 {
452 return nullptr;
453 }
454 }
455 for (TClassDefs::const_iterator i = innerClasses_.begin(); i != innerClasses_.end(); ++i)
456 {
457 ClassDefinition* innerClass = *i;
458 const char* shortName = innerClass->name();
459 PyObject* innerType = innerClass->freezeDefinition(module, qualname);
460 if (!innerType || PyObject_SetAttrString(type, shortName, innerType) != 0)
461 {
462 return nullptr;
463 }
464 }
465 for (auto def : innerEnums_)
466 {
467 PyObject* enumType = def->freezeDefinition(moduleName, qualname);
468 if (!enumType || PyObject_SetAttrString(type, def->name(), enumType) != 0)
469 {
470 return nullptr;
471 }
472 }
473
474 if (classRegisterHook_)
475 {
476 classRegisterHook_();
477 }
478
479 if (freezeType() != 0)
480 {
481 return nullptr;
482 }
483
484 isFrozen_ = true;
485 return type;
486}
487
488
489
490int ClassDefinition::freezeType()
491{
492 PyTypeObject* type = reinterpret_cast<PyTypeObject*>(type_.get());
493#if PY_VERSION_HEX >= 0x030e0000 // >= 3.14
494 if (!PyType_HasFeature(type, Py_TPFLAGS_IMMUTABLETYPE))
495 {
496 if (parent_ && !PyType_HasFeature(parent_->type(), Py_TPFLAGS_IMMUTABLETYPE))
497 {
498 // can't freeze type yet when parent's type isn't frozen. Parent will need to take care of us!
499 LASS_ASSERT(std::find(parent_->subClasses_.begin(), parent_->subClasses_.end(), this) != parent_->subClasses_.end());
500 return 0;
501 }
502 if (PyType_Freeze(type) != 0)
503 {
504 return -1;
505 }
506 }
507
508 // freeze types of all subclasses that we've skipped
509 for (auto *subClass : subClasses_)
510 {
511 // their definition should already be frozen if they're in this list
512 LASS_ASSERT(subClass->isFrozen_);
513 if (subClass->freezeType() != 0)
514 {
515 return -1;
516 }
517 LASS_ASSERT(PyType_HasFeature(subClass->type(), Py_TPFLAGS_IMMUTABLETYPE));
518 }
519#else
520 type->tp_flags |= Py_TPFLAGS_IMMUTABLETYPE;
521#endif
522 return 0;
523}
524
525
526
527PyObject* ClassDefinition::callRichCompare(PyObject* self, PyObject* other, int op)
528{
529 // don't worry about GIL, as we should still have it when we get here ...
530
531 if (other == Py_None)
532 {
533 // we need to treat the None type differently because the pyGet/BuildSimpleObject are able to cast
534 // from None but if you give that thing to a reference, then you are in big trouble
535 switch (op)
536 {
537 case Py_EQ:
538 {
539 if (self == other)
540 Py_RETURN_TRUE;
541 else
542 Py_RETURN_FALSE;
543 }
544 case Py_NE:
545 {
546 if (self != other)
547 Py_RETURN_TRUE;
548 else
549 Py_RETURN_FALSE;
550 }
551 // don't define any order relation on None
552 default:
553 Py_RETURN_FALSE;
554 };
555 }
556
557 TPyObjPtr args(Py_BuildValue("(O)", other));
558 const TCompareFuncs::const_iterator end = compareFuncs_.end();
559 for (TCompareFuncs::const_iterator i = compareFuncs_.begin(); i != end; ++i)
560 {
561 if (i->op == op)
562 {
563 PyObject* result = (i->dispatcher)(self, args.get());\
564 if (result || (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_TypeError)))
565 {
566 return result;
567 }
568 }
569 }
570
571 if (!parent_)
572 {
573 static const char* symbols[] = { "<", "<=", "==", "!=", ">", ">=" };
574 LASS_ASSERT(op >= 0 && op <= Py_GE);
575 std::ostringstream buffer;
576 buffer << "Comparison operator " << symbols[op] << " not implemented for this type";
577 PyErr_SetString(PyExc_NotImplementedError, buffer.str().c_str());
578 return 0;
579 }
580
581 return parent_->callRichCompare(self, other, op);
582}
583
584}
585}
586}
587
588// 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