[neon/forks/sip6/Neon/release] /: New upstream version 6.15.2
Dmitry Shachnev
null at kde.org
Mon Jul 27 12:04:48 BST 2026
Git commit c087549993bbac63693442d046dc6252f432cc73 by Dmitry Shachnev.
Committed on 09/03/2026 at 14:07.
Pushed by carlosdem into branch 'Neon/release'.
New upstream version 6.15.2
M +3 -3 .git_archival.txt
M +0 -1 MANIFEST.in
M +2 -1 docs/abi_12.rst
M +2 -1 docs/abi_13.rst
M +1 -1 docs/conf.py
M +4 -0 docs/directives.rst
M +1 -1 docs/module_v12_releases.md
M +6 -1 docs/module_v13_releases.md
M +7 -2 docs/pyproject_toml.rst
M +38 -7 docs/releases.md
M +27 -22 docs/specification_files.rst
M +8 -56 sipbuild/generator/indexed_lists.py
M +241 -24 sipbuild/generator/outputs/code/backends/abstract_backend.py
M +961 -73 sipbuild/generator/outputs/code/backends/v12v13.py
M +3 -0 sipbuild/generator/outputs/code/code.py
M +307 -1024 sipbuild/generator/outputs/code/snippets.py
M +35 -8 sipbuild/generator/outputs/code/utils.py
M +52 -69 sipbuild/generator/parser/parser_manager.py
M +40 -39 sipbuild/generator/parser/python_exceptions.py
M +2 -2 sipbuild/generator/parser/rules.py
M +62 -29 sipbuild/generator/resolver/resolver.py
M +16 -3 sipbuild/generator/specification.py
M +1 -1 sipbuild/module/source/13/11/sip.h.in
M +13 -1 sipbuild/module/source/13/11/sip_core.c
M +4 -1 test/class_attrs/test_class_attrs.py
M +1 -5 test/class_callables/test_class_callables.py
M +15 -5 test/conftest.py
M +8 -1 test/imported_exceptions/handler_module.sip
M +2 -5 test/imported_exceptions/test_imported_exceptions.py
M +1 -1 test/imported_exceptions/thrower_module.sip
A +20 -0 test/imports/base_class.h *
A +32 -0 test/imports/core_module.sip
A +46 -0 test/imports/extras_module.sip
A +21 -0 test/imports/test_imports.py
M +0 -4 test/int_convertors/test_int_convertors.py
A +68 -0 test/mapped_type_attrs/mapped_type_attrs_module.sip
A +21 -0 test/mapped_type_attrs/test_mapped_type_attrs.py
M +8 -9 test/mixins/mixins_module.sip
M +0 -2 test/module_attrs/module_attrs_module.sip
M +1 -21 test/module_attrs/test_module_attrs.py
M +29 -1 test/movable/movable_module.sip
M +10 -13 test/movable/test_movable.py
M +1 -5 test/py_properties/test_py_properties.py
M +47 -9 test/template_typedef/template_typedef_module.sip
M +1 -5 test/template_typedef/test_template_typedef.py
M +1 -5 test/templates/test_templates.py
C +24 -56 test/v12_enums/test_v12_enums.py [from: test/py_enums/test_py_enums.py - 062% similarity]
C +16 -36 test/v12_enums/v12_enums_module.sip [from: test/py_enums/py_enums_module.sip - 079% similarity]
R +51 -4 test/v13_enums/test_v13_enums.py [from: test/py_enums/test_py_enums.py - 073% similarity]
R +40 -2 test/v13_enums/v13_enums_module.sip [from: test/py_enums/py_enums_module.sip - 071% similarity]
A +38 -0 test/virtuals/test_virtuals.py
A +39 -0 test/virtuals/virtuals_module.sip
The files marked with a * at the end have a non valid license. Please read: https://community.kde.org/Policies/Licensing_Policy and use the headers which are listed at that page.
https://invent.kde.org/neon/forks/sip6/-/commit/c087549993bbac63693442d046dc6252f432cc73
diff --git a/.git_archival.txt b/.git_archival.txt
index 85a7bbb..0bbb749 100644
--- a/.git_archival.txt
+++ b/.git_archival.txt
@@ -1,3 +1,3 @@
-node: 4884efc25d98c9b2cf734adf43695ab3192f8dcc
-node-date: 2025-12-20T13:46:07Z
-describe-name: 6.15.1
+node: cfbd43a5641b6e0f5c51ae7b51d79449db394127
+node-date: 2026-03-09T11:57:57Z
+describe-name: 6.15.2
diff --git a/MANIFEST.in b/MANIFEST.in
index 376d67a..322fcdb 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,4 +2,3 @@ exclude .git*
exclude .readthedocs.yaml
prune docs
prune examples
-prune test
diff --git a/docs/abi_12.rst b/docs/abi_12.rst
index 17d4482..f696b61 100644
--- a/docs/abi_12.rst
+++ b/docs/abi_12.rst
@@ -459,7 +459,8 @@ module, that can be used by handwritten code in specification files.
.. c:function:: PyObject *sipConvertFromNewType(void *cpp, const sipTypeDef *td, PyObject *transferObj)
This converts a new C structure or a C++ class instance to an instance of
- the corresponding generated Python type.
+ the corresponding generated Python type. Note that care should be taken to
+ ensure that the instance really is new.
:param cpp:
the C/C++ instance.
diff --git a/docs/abi_13.rst b/docs/abi_13.rst
index 32c2378..fbead95 100644
--- a/docs/abi_13.rst
+++ b/docs/abi_13.rst
@@ -437,7 +437,8 @@ module, that can be used by handwritten code in specification files.
.. c:function:: PyObject *sipConvertFromNewType(void *cpp, const sipTypeDef *td, PyObject *transferObj)
This converts a new C structure or a C++ class instance to an instance of
- the corresponding generated Python type.
+ the corresponding generated Python type. Note that care should be taken to
+ ensure that the instance really is new.
:param cpp:
the C/C++ instance.
diff --git a/docs/conf.py b/docs/conf.py
index 032cbee..4f184f5 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -13,7 +13,7 @@ project = 'sip'
copyright = '{0} Phil Thompson <phil at riverbankcomputing.com>'.format(
date.today().year)
author = 'Phil Thompson'
-version = 'v6.15.1'
+version = 'v6.15.2'
# -- General configuration ---------------------------------------------------
diff --git a/docs/directives.rst b/docs/directives.rst
index 8f94930..13390aa 100644
--- a/docs/directives.rst
+++ b/docs/directives.rst
@@ -1730,6 +1730,10 @@ handwritten code, ie. if the code make a call to a function that was added to a
particular ABI version then that version should be specified as the minimum
required.
+If the minor version is omitted then the latest minor version of the major
+version will be used. However this isn't particularly useful and it is
+recommended that a minor number is always specified, even if it is ``0``.
+
For example::
%MinimumABIVersion "13.5"
diff --git a/docs/module_v12_releases.md b/docs/module_v12_releases.md
index fb0a7bb..4a6abbb 100644
--- a/docs/module_v12_releases.md
+++ b/docs/module_v12_releases.md
@@ -1,7 +1,7 @@
# `sip` Module v12 History
-## v12.18
+## v12.18.0
- Added support for Python v3.14.
- Removed support for Python v3.9.
diff --git a/docs/module_v13_releases.md b/docs/module_v13_releases.md
index 61ad339..a441f8c 100644
--- a/docs/module_v13_releases.md
+++ b/docs/module_v13_releases.md
@@ -1,7 +1,12 @@
# `sip` Module v13 History
-## v13.11
+## v13.11.1
+
+- Fixed a bug with operators that take an enum as their first argument.
+
+
+## v13.11.0
- Added support for Python v3.14.
- Removed support for Python v3.9.
diff --git a/docs/pyproject_toml.rst b/docs/pyproject_toml.rst
index 9a957f1..6f37688 100644
--- a/docs/pyproject_toml.rst
+++ b/docs/pyproject_toml.rst
@@ -97,9 +97,14 @@ list options may contain environment markers as defined in `PEP 508
**abi-version**
The version number of the ABI of the :mod:`sip` module to generate code
- for. By default the latest major version is used. If the minor version is
+ for. If it is not specified then the version defined by any
+ :directive:`%MinimumABIVerson` directive is used. If that is also not
+ specified then the latest major version is used. If the minor version is
not specified then the latest minor version of the major version is used.
- There is also a corresponding command line option.
+ There is also a corresponding command line option. It is recommended that
+ the :directive:`%MinimumABIVerson` directive is used in preference to
+ using this key/value and the corresponding command line option used to
+ temporarily override the directive if required.
**api-dir**
The value is the name of a the directory in which a QScintilla :file:`.api`
diff --git a/docs/releases.md b/docs/releases.md
index fad429b..5190958 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -1,5 +1,36 @@
# Release Notes
+## v6.15.2
+
+### Enhancements
+
+- The way in which the parser checks for duplicate attributes in a scope is now
+ quick and complete.
+
+### Bug fixes
+
+- Operators that take an enum as their first argument is now fixed in ABI v13.
+ The latest ABI v13 version is v13.11.1.
+- The handling of the wrapped instance result of a call to a method with the
+ `/Factory/` annotation has been fixed in the case where result was created by
+ a Python reimplementation of a C++ virtual.
+- Fixed a warning about an unclosed file when generating code.
+- Removed support for all remaining Python v2 exceptions.
+
+### Testing
+
+- The tests are now included in the sdist. Resolves [#103](https://github.com/Python-SIP/sip/issues/103)
+- The mixins test was fixed so that it will run on Python v3.12 and earlier.
+ Resolves [#101](https://github.com/Python-SIP/sip/issues/101)
+- Added tests including those related to enums and importing wrapped modules.
+- Tests may now use separate `.h` files rather then code embedded in the `.sip`
+ files.
+
+### Documentation
+
+- Added recommendations regarding the ways to specify the ABI version to use.
+
+
## v6.15.1
### Bug fixes
@@ -407,15 +438,15 @@ Resolves [#43](https://github.com/Python-SIP/sip/issues/43)
### Bug fixes
-- The handling of the SIP versions timeline was fixed. (Resolves
- [#47](https://github.com/Python-SIP/sip/issues/47))
+- The handling of the SIP versions timeline was fixed. Resolves
+ [#47](https://github.com/Python-SIP/sip/issues/47)
- Annotations are now only validated if they are known to be needed.
-- The buffer protocol support for byte objects was fixed. (Resolves
- [#36](https://github.com/Python-SIP/sip/issues/36))
+- The buffer protocol support for byte objects was fixed. Resolves
+ [#36](https://github.com/Python-SIP/sip/issues/36)
- All outstanding compiler warnings were eliminated when building the `sip`
- module. (Resolves [#32](https://github.com/Python-SIP/sip/issues/32))
-- Redundant `%` in trace output of `this` pointer in were removed. (Pull
- request [#33](https://github.com/Python-SIP/sip/pull/33))
+ module. Resolves [#32](https://github.com/Python-SIP/sip/issues/32)
+- Redundant `%` in trace output of `this` pointer in were removed. Pull
+ request [#33](https://github.com/Python-SIP/sip/pull/33)
## v6.8.6
diff --git a/docs/specification_files.rst b/docs/specification_files.rst
index 0443428..65e1ee6 100644
--- a/docs/specification_files.rst
+++ b/docs/specification_files.rst
@@ -244,36 +244,41 @@ file.
*exception-base* ::= **(** [*exception-name* | *python-exception*] **)**
- *python-exception* ::= [**SIP_ArithmeticError** | **SIP_AssertionError** |
- **SIP_AttributeError** | **SIP_BaseException** |
- **SIP_BlockingIOError** | **SIP_BrokenPipeError** |
- **SIP_BufferError** | **SIP_ChildProcessError** |
- **SIP_ConnectionAbortedError** | **SIP_ConnectionError** |
- **SIP_ConnectionRefusedError** | **SIP_ConnectionResetError** |
- **SIP_EnvironmentError** | **SIP_EOFError** | **SIP_Exception** |
+ *python-exception* ::= [
+ **SIP_BaseException** | **SIP_BaseExceptionGroup** |
+ **SIP_Exception** |
+ **SIP_ArithmeticError** | **SIP_AssertionError** |
+ **SIP_AttributeError** | **SIP_BlockingIOError** |
+ **SIP_BrokenPipeError** | **SIP_BufferError** |
+ **SIP_ChildProcessError** | **SIP_ConnectionAbortedError** |
+ **SIP_ConnectionError** | **SIP_ConnectionRefusedError** |
+ **SIP_ConnectionResetError** | **SIP_EOFError** |
**SIP_FileExistsError** | **SIP_FileNotFoundError** |
**SIP_FloatingPointError** | **SIP_GeneratorExit** |
- **SIP_ImportError** | **SIP_IndentationError** |
- **SIP_IndexError** | **SIP_InterruptedError** | **SIP_IOError** |
- **SIP_IsADirectoryError** | **SIP_KeyboardInterrupt** |
- **SIP_KeyError** | **SIP_LookupError** | **SIP_MemoryError** |
- **SIP_NameError** | **SIP_NotADirectoryError** |
- **SIP_NotImplementedError** | **SIP_OSError** |
- **SIP_OverflowError** | **SIP_PermissionError** |
- **SIP_ProcessLookupError** | **SIP_ReferenceError** |
- **SIP_RuntimeError** | **SIP_StandardError** |
+ **SIP_ImportCycleError** | **SIP_ImportError** |
+ **SIP_IndentationError** | **SIP_IndexError** |
+ **SIP_InterruptedError** | **SIP_IsADirectoryError** |
+ **SIP_KeyError** | **SIP_KeyboardInterrupt** |
+ **SIP_LookupError** | **SIP_MemoryError** |
+ **SIP_ModuleNotFoundError** | **SIP_NameError** |
+ **SIP_NotADirectoryError** | **SIP_NotImplementedError** |
+ **SIP_OSError** | **SIP_OverflowError** | **SIP_PermissionError** |
+ **SIP_ProcessLookupError** | **SIP_PythonFinalizationError ** |
+ **SIP_RecursionError** | **SIP_ReferenceError** |
+ **SIP_RuntimeError** | **SIP_StopAsyncIteration** |
**SIP_StopIteration** | **SIP_SyntaxError** | **SIP_SystemError** |
**SIP_SystemExit** | **SIP_TabError** | **SIP_TimeoutError** |
**SIP_TypeError** | **SIP_UnboundLocalError** |
**SIP_UnicodeDecodeError** | **SIP_UnicodeEncodeError** |
**SIP_UnicodeError** | **SIP_UnicodeTranslateError** |
- **SIP_ValueError** | **SIP_VMSError** | **SIP_WindowsError** |
- **SIP_ZeroDivisionError** | **SIP_Warning** |
+ **SIP_ValueError** | **SIP_ZeroDivisionError** |
+ **SIP_Warning** |
**SIP_BytesWarning** | **SIP_DeprecationWarning** |
- **SIP_FutureWarning** | **SIP_ImportWarning** |
- **SIP_PendingDeprecationWarning** | **SIP_ResourceWarning** |
- **SIP_RuntimeWarning** | **SIP_SyntaxWarning** |
- **SIP_UnicodeWarning** | **SIP_UserWarning**]
+ **SIP_EncodingWarning** | **SIP_FutureWarning** |
+ **SIP_ImportWarning** | **SIP_PendingDeprecationWarning** |
+ **SIP_ResourceWarning** | **SIP_RuntimeWarning** |
+ **SIP_SyntaxWarning** | **SIP_UnicodeWarning** |
+ **SIP_UserWarning**]
*argument-list* ::= *argument* [**,** *argument-list*] [**,** **...**]
diff --git a/sipbuild/generator/indexed_lists.py b/sipbuild/generator/indexed_lists.py
index 5505f56..be917a8 100644
--- a/sipbuild/generator/indexed_lists.py
+++ b/sipbuild/generator/indexed_lists.py
@@ -42,13 +42,6 @@ class IndexedList(list[_T]):
"""Called at initialisation and then whenever the list is cleared."""
...
- def _transform_inserted(self, item):
- """
- Called on each item to be inserted; this function's return value is
- then inserted instead of that item.
- """
- return item
-
# Redefinitions of list methods to keep index up-to-date
def __init__(self):
@@ -58,13 +51,11 @@ class IndexedList(list[_T]):
def append(self, v):
"""Add v to the end of list, updating indices in the process."""
- v = self._transform_inserted(v)
super().append(v)
self._index_add(v)
def insert(self, i, v):
"""Add v to the list at index i, updating indices in the process."""
- v = self._transform_inserted(v)
super().insert(i, v)
self._index_add(v)
@@ -93,7 +84,6 @@ class IndexedList(list[_T]):
def __setitem__(self, i, v):
"""Set element at index i to v, updating indices in the process."""
- v = self._transform_inserted(v)
self._index_remove(self[i])
super().__setitem__(i, v)
self._index_add(v)
@@ -135,67 +125,29 @@ class IndexedEnumList(IndexedList['WrappedEnum']):
"""
A list of WrappedEnums keeping the following indices:
- enums by fq_cpp_name
- - enums by scope and py_name
- - unscoped enums' members by outer scope and py_name
"""
def _index_clear(self):
- """Set up indices. See IndexedList._index_clear()."""
+ """ Initialise the indices. """
+
self._by_cppname = {}
- self._by_scope_pyname = {}
- self._unscoped_by_scope_member = {}
def _index_add(self, enum):
- """Add WrappedEnum to indices. See IndexedList._index_add()."""
+ """ Add a WrappedEnum to the indices. """
+
if enum.fq_cpp_name:
- assert enum.fq_cpp_name not in self._by_cppname, f"Duplicate enum: {enum.fq_cpp_name}"
self._by_cppname[enum.fq_cpp_name] = enum
- if enum.py_name:
- assert (enum.scope, str(enum.py_name)) not in self._by_scope_pyname
- self._by_scope_pyname[enum.scope, str(enum.py_name)] = enum
-
- if not enum.is_scoped:
- for member in enum.members:
- assert (enum.scope, str(member.py_name)) not in self._unscoped_by_scope_member, f"Duplicate enum member: {member.py_name.name}"
- self._unscoped_by_scope_member[enum.scope, str(member.py_name)] = enum
-
def _index_remove(self, enum):
- """Remove WrappedEnum from indices. See IndexedList._index_add()."""
+ """ Remove a WrappedEnum from the indices. """
+
if enum.fq_cpp_name:
del self._by_cppname[enum.fq_cpp_name]
- if enum.py_name:
- del self._by_scope_pyname[enum.scope, enum.py_name.name]
-
- if not enum.is_scoped:
- for member in enum.members:
- del self._unscoped_by_scope_member[(enum.scope, str(member.py_name))]
-
- def _transform_inserted(self, enum):
- """
- Transform element before it is inserted.
- See IndexedList._transform_inserted().
- """
- # Make sure nobody changes the member list after it's inserted, since
- # it wouldn't be reindexed.
- enum.member = tuple(enum.members)
- return enum
-
def by_fq_cpp_name(self, name):
- """Find WrappedEnum by its .fq_cpp_name."""
- return self._by_cppname.get(name)
+ """ Find a WrappedEnum by its .fq_cpp_name. """
- def by_scope_and_py_name(self, scope, name):
- """Find WrappedEnum by its .scope and .py_name."""
- return self._by_scope_pyname.get((scope, str(name)))
-
- def by_scope_and_unscoped_member_py_name(self, scope, name):
- """
- Find member of unscoped WrappedEnum by the enum's .scope and the
- member's .py_name.
- """
- return self._unscoped_by_scope_member.get((scope, str(name)))
+ return self._by_cppname.get(name)
class IndexedCachedNameList(IndexedList['CachedName']):
diff --git a/sipbuild/generator/outputs/code/backends/abstract_backend.py b/sipbuild/generator/outputs/code/backends/abstract_backend.py
index 03ee23a..2219758 100644
--- a/sipbuild/generator/outputs/code/backends/abstract_backend.py
+++ b/sipbuild/generator/outputs/code/backends/abstract_backend.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from abc import ABC, abstractmethod
@@ -25,34 +25,104 @@ class AbstractBackend(ABC):
return backend(spec)
+ @abstractmethod
+ def g_cast_function(self, sf, klass):
+ """ Generate the function that casts a C++ pointer to a target type.
+ """
+
+ ...
+
+ @abstractmethod
+ def g_catch_body(self, sf):
+ """ Generate the body of a catch clause. """
+
+ ...
+
+ @abstractmethod
+ def g_class_api(self, sf, klass):
+ """ Generate the API details for a class. """
+
+ ...
+
+ @abstractmethod
+ def g_conversion_to_enum(self, sf, enum):
+ """ Generate the code to convert a Python enum (sipSelf) to a C/C++
+ enum (sipCpp).
+ """
+
+ ...
+
+ @abstractmethod
+ def g_cpp_dtor(self, sf):
+ """ Generate the body of the dtor of a generated shadow class. """
+
+ ...
+
@abstractmethod
def g_create_wrapped_module(self, sf, bindings,
# TODO These will probably be generated here at some point.
- has_sip_strings,
+ name_cache_closure,
has_external,
- nr_enum_members,
+ enums_closure,
has_virtual_error_handlers,
nr_subclass_convertors,
inst_state,
slot_extenders,
init_extenders
):
- """ Generate the code to create a wrapped module. """
+ """ Generate the code to create a wrapped module and return an
+ ABI-specific object that will be passed back to the backend at some
+ point. """
+
+ ...
+
+ @abstractmethod
+ def g_enums_specifications(self, sf, scope=None):
+ """ Generate the specifications for the wrapped enums in a scope and
+ return an ABI-specific object which will be passed back to the backend
+ at some point.
+ """
...
+ def g_exceptions_specifications(self, sf):
+ """ Generate the specifications for any exceptions. """
+
+ # This default implementation does nothing.
+ pass
+
@abstractmethod
- def g_enum_macros(self, sf, scope=None, imported_module=None):
- """ Generate the type macros for enums. """
+ def g_exceptions_decls(self, sf):
+ """ Generate the declarations of all exceptions. """
...
- def g_function_support_vars(self, sf):
- """ Generate the variables needed by a function. """
+ def g_exceptions_defn(self, sf):
+ """ Generate the definition of the exceptions data structure. """
# This default implementation does nothing.
pass
+ @abstractmethod
+ def g_get_py_reimpl(self, sf, klass, overload, virt_nr):
+ """ Generate the code to get the Python reimplementation of a C++
+ virtual.
+ """
+
+ ...
+
+ @abstractmethod
+ def g_import_tables(self, sf):
+ """ Generated the tables related to imported modules. """
+
+ ...
+
+ @abstractmethod
+ def g_imported_module_decls(self, sf, imported_module):
+ """ Generate any declarations related to an imported module. """
+
+ ...
+
@abstractmethod
def g_init_mixin_impl_body(self, sf, klass):
""" Generate the body of the implementation of a mixin initialisation
@@ -67,6 +137,14 @@ class AbstractBackend(ABC):
...
+ @abstractmethod
+ def g_mapped_type_definition(self, sf, bindings, mapped_type):
+ """ Generate the type structure that contains all the information
+ needed by a mapped type.
+ """
+
+ ...
+
def g_mapped_type_int_instances(self, sf, mapped_type):
""" Generate the code to add a set of ints to a mapped type. Return
True if there was at least one.
@@ -75,26 +153,50 @@ class AbstractBackend(ABC):
# This default implementation does nothing.
return False
- def g_method_support_vars(self, sf):
- """ Generate the variables needed by a method. """
-
- # This default implementation does nothing.
- pass
-
@abstractmethod
def g_module_definition(self, sf, has_module_functions=False):
""" Generate the module definition structure. """
...
+ def g_name_cache(self, sf):
+ """ Generate the name cache definition and return an ABI-specific
+ object which will be passed back to the backend at some point.
+ """
+
+ # This default implementation returns None.
+ return None
+
+ @staticmethod
+ @abstractmethod
+ def g_not_implemented(sf):
+ """ Generate the code to clear any exception and return
+ Py_NotImplemented.
+ """
+
+ ...
+
+ @abstractmethod
+ def g_py_method_end(self, sf, state, nr_signatures):
+ """ Generate the end of a method implementation. """
+
+ ...
+
@abstractmethod
- def g_module_functions_table(self, sf, bindings, module):
- """ Generate the table of module functions and return True if anything
- was actually generated.
+ def g_py_method_start(self, sf, bindings, klass, member, original_klass,
+ need_args, need_self):
+ """ Generate the start of a method implementation and return an
+ ABI-specific object which will be passed to g_py_method_end().
"""
...
+ def g_py_method_support_vars(self, sf, scope):
+ """ Generate the variables needed by a method implementation. """
+
+ # This default implementation does nothing.
+ pass
+
@abstractmethod
def g_py_method_table(self, sf, bindings, members, scope):
""" Generate a Python method table for a class or mapped type and
@@ -104,17 +206,45 @@ class AbstractBackend(ABC):
...
@abstractmethod
- def g_sip_api(self, sf, module_name):
+ def g_sip_api(self, sf, module_name, module_closure):
""" Generate the SIP API as seen by generated code. """
...
- def g_slot_support_vars(self, sf):
+ @abstractmethod
+ def g_slot_implementations(self, sf, bindings, scope, members):
+ """ Generate the slot implementations for a scope. """
+
+ ...
+
+ def g_slot_support_vars(self, sf, scope, member):
""" Generate the variables needed by a slot function. """
# This default implementation does nothing.
pass
+ @abstractmethod
+ def g_static_function_end(self, sf, state, nr_signatures):
+ """ Generate the end of a static function implementation. """
+
+ ...
+
+ @abstractmethod
+ def g_static_function_start(self, sf, bindings, scope_py, member,
+ overloads):
+ """ Generate the start of a static function implementation and return
+ an ABI-specific object which will be passed to g_static_function_end().
+ """
+
+ ...
+
+ def g_static_function_support_vars(self, sf, scope):
+ """ Generate the variables needed by a static function implementation.
+ """
+
+ # This default implementation does nothing.
+ pass
+
@abstractmethod
def g_static_variables_table(self, sf, scope=None):
""" Generate the tables of static variables for a scope and return a
@@ -173,11 +303,35 @@ class AbstractBackend(ABC):
...
@abstractmethod
- def get_class_ref_value(self, klass):
- """ Return the value of a class's reference. """
+ def get_enum_to_py_conversion(self, enum, value_name):
+ """ Return the code to convert a C/C++ enum to a Python object. """
+
+ ...
+
+ @abstractmethod
+ def get_enum_ref_value(self, enum):
+ """ Return the value of an enum's reference. """
...
+ @staticmethod
+ def get_module_context():
+ """ Return the value of a module context passed as the first argument
+ to many ABI calls.
+ """
+
+ # This default implementation returns nothing.
+ return ''
+
+ @staticmethod
+ def get_module_context_decl():
+ """ Return the declaration of the value of a module context passed as
+ the first argument to many ABI calls.
+ """
+
+ # This default implementation returns nothing.
+ return ''
+
@abstractmethod
def get_py_method_args(self, *, is_impl, self_is_type, need_self=False,
need_args=True):
@@ -187,6 +341,29 @@ class AbstractBackend(ABC):
...
+ @staticmethod
+ @abstractmethod
+ def get_raise_unknown_exception():
+ """ Return the call to raise an exception about an unknown exception.
+ """
+
+ ...
+
+ @staticmethod
+ @abstractmethod
+ def get_result_parser():
+ """ Return the name of the Python reimplementation result parser. """
+
+ ...
+
+ @abstractmethod
+ def get_sipself_test(self, klass):
+ """ Return the code that checks if 'sipSelf' was bound or passed as an
+ argument.
+ """
+
+ ...
+
@staticmethod
@abstractmethod
def get_slot_ref(slot_type):
@@ -194,6 +371,37 @@ class AbstractBackend(ABC):
...
+ @abstractmethod
+ def get_spec_for_class(self, klass):
+ """ Return the name of the data structure specifying a class. """
+
+ ...
+
+ @abstractmethod
+ def get_spec_for_enum(self, enum, enums_closure):
+ """ Return the name of the data structure specifying an enum. """
+
+ ...
+
+ def get_spec_for_exception(self, exception):
+ """ Return the name of the data structure specifying an exception. """
+
+ # This default implementation does nothing.
+ pass
+
+ @abstractmethod
+ def get_spec_for_mapped_type(self, mapped_type):
+ """ Return the name of the data structure specifying a mapped type. """
+
+ ...
+
+ @staticmethod
+ @abstractmethod
+ def get_spec_suffix():
+ """ Return the suffix used for immutable specifications. """
+
+ ...
+
@staticmethod
@abstractmethod
def get_type_ref(wrapped_object):
@@ -203,18 +411,27 @@ class AbstractBackend(ABC):
@staticmethod
@abstractmethod
- def get_types_table_prefix():
- """ Return the prefix in the name of the wrapped types table. """
+ def get_types_table_decl(module):
+ """ Return the declaration of a module's wrapped types table. """
...
@staticmethod
@abstractmethod
- def get_wrapped_type_type():
+ def get_wrapper_type():
""" Return the type of the C representation of a wrapped object. """
...
+ @staticmethod
+ @abstractmethod
+ def get_wrapper_type_cast():
+ """ Return the cast from a PyObject* of the C representation of a
+ wrapped object.
+ """
+
+ ...
+
def legacy_qt_support(self):
""" Return True if the module implements legacy Qt support. """
diff --git a/sipbuild/generator/outputs/code/backends/v12v13.py b/sipbuild/generator/outputs/code/backends/v12v13.py
index eccbae4..eb54fcf 100644
--- a/sipbuild/generator/outputs/code/backends/v12v13.py
+++ b/sipbuild/generator/outputs/code/backends/v12v13.py
@@ -1,20 +1,22 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from ....scoped_name import STRIP_GLOBAL
-from ....specification import (ArgumentType, IfaceFileType, WrappedClass,
- WrappedEnum)
+from ....specification import (AccessSpecifier, ArgumentType, IfaceFileType,
+ MappedType, WrappedClass, WrappedEnum)
from ....utils import find_method
from ...formatters import fmt_argument_as_cpp_type
from ..snippets import (g_class_docstring, g_class_method_table,
- g_enum_member_table, g_module_docstring, g_type_init_body,
- g_pyqt_class_plugin, g_pyqt_helper_defns, g_pyqt_helper_init)
-from ..utils import (get_class_flags, get_const_cast, get_docstring_text,
- get_encoded_type, get_enum_member, get_named_value_decl,
+ g_method_docstring, g_module_docstring, g_type_init_body, g_py_slot,
+ g_pyqt_class_plugin, g_pyqt_helper_defns, g_pyqt_helper_init,
+ g_static_function)
+from ..utils import (get_class_flags, get_class_from_void, get_const_cast,
+ get_docstring_text, get_encoded_type, get_enum_member,
+ get_function_table, get_mapped_type_flags, get_named_value_decl,
get_normalised_cached_name, get_optional_ptr, get_use_in_code,
get_user_state_suffix, get_void_ptr_cast, has_method_docstring,
is_used_in_code, keep_py_reference, need_dealloc, py_scope,
@@ -27,18 +29,122 @@ from .abstract_backend import AbstractBackend
class v12v13Backend(AbstractBackend):
""" The backend code generator for v12 and v13 of the ABI. """
+ def g_cast_function(self, sf, klass):
+ """ Generate the function that casts a C++ pointer to a target type.
+ """
+
+ spec = self.spec
+ as_word = klass.iface_file.fq_cpp_name.as_word
+
+ sf.write(
+f'''
+
+/* Cast a pointer to a type somewhere in its inheritance hierarchy. */
+extern "C" {{static void *cast_{as_word}(void *, const sipTypeDef *);}}
+static void *cast_{as_word}(void *sipCppV, const sipTypeDef *targetType)
+{{
+ {get_class_from_void(spec, klass)};
+
+ if (targetType == {self.get_type_ref(klass)})
+ return sipCppV;
+
+''')
+
+ for superclass in klass.superclasses:
+ sc_fq_cpp_name = superclass.iface_file.fq_cpp_name
+ sc_scope_s = scoped_class_name(spec, superclass)
+ sc_type_ref = self.get_type_ref(superclass)
+
+ if len(superclass.superclasses) != 0:
+ # Delegate to the super-class's cast function. This will
+ # handle virtual and non-virtual diamonds.
+ sf.write(
+f''' sipCppV = ((const sipClassTypeDef *){sc_type_ref})->ctd_cast(static_cast<{sc_scope_s} *>(sipCpp), targetType);
+ if (sipCppV)
+ return sipCppV;
+
+''')
+ else:
+ # The super-class is a base class and so doesn't have a cast
+ # function. It also means that a simple check will do instead.
+ sf.write(
+f''' if (targetType == {sc_type_ref})
+ return static_cast<{sc_scope_s} *>(sipCpp);
+
+''')
+
+ sf.write(
+''' return SIP_NULLPTR;
+}
+''')
+
+ def g_catch_body(self, sf):
+ """ Generate the body of a catch clause. """
+
+ sf.write(
+''' void *sipExcState = SIP_NULLPTR;
+ sipExceptionHandler sipExcHandler;
+ std::exception_ptr sipExcPtr = std::current_exception();
+
+ while ((sipExcHandler = sipNextExceptionHandler(&sipExcState)) != SIP_NULLPTR)
+ if (sipExcHandler(sipExcPtr))
+ return SIP_NULLPTR;
+
+''')
+
+ def g_class_api(self, sf, klass):
+ """ Generate the API details for a class. """
+
+ module = self.spec.module
+ module_name = module.py_name
+ iface_file = klass.iface_file
+
+ if iface_file.module is module:
+ sf.write(f'#define {self.get_type_ref(klass)} sipExportedTypes_{module_name}[{iface_file.type_nr}]\n')
+ else:
+ type_ref = self.get_type_ref(klass)
+
+ if iface_file.type is IfaceFileType.NAMESPACE:
+ sf.write(f'\n#if !defined({type_ref})')
+
+ sf.write(f'\n#define {type_ref} sipImportedTypes_{module_name}_{iface_file.module.py_name}[{iface_file.type_nr}].it_td\n')
+
+ if iface_file.type is IfaceFileType.NAMESPACE:
+ sf.write('#endif\n')
+
+ def g_conversion_to_enum(self, sf, enum):
+ """ Generate the code to convert a Python enum (sipSelf) to a C/C++
+ enum (sipCpp).
+ """
+
+ type_ref = self.get_type_ref(enum)
+ cpp_name = enum.fq_cpp_name.as_cpp
+
+ sf.write(
+f''' {cpp_name} sipCpp = static_cast<{cpp_name}>(sipConvertToEnum(sipSelf, {type_ref}));
+
+ if (PyErr_Occurred())
+''')
+
+ def g_cpp_dtor(self, sf):
+ """ Generate the body of the dtor of a generated shadow class. """
+
+ sf.write(' sipInstanceDestroyedEx(&sipPySelf);\n')
+
def g_create_wrapped_module(self, sf, bindings,
# TODO These will probably be generated here at some point.
- has_sip_strings,
+ name_cache_state,
has_external,
- nr_enum_members,
+ enums_state,
has_virtual_error_handlers,
nr_subclass_convertors,
inst_state,
slot_extenders,
init_extenders
):
- """ Generate the code to create a wrapped module. """
+ """ Generate the code to create a wrapped module and return the name
+ cache state.
+ """
spec = self.spec
target_abi = spec.target_abi
@@ -76,6 +182,7 @@ f''' {len(module.needed_types)},
''')
if self.custom_enums_supported():
+ nr_enum_members, _ = enums_state
enum_members = get_optional_ptr(nr_enum_members > 0,
'enummembers')
sf.write(
@@ -159,37 +266,286 @@ const sipAPIDef *sipAPI_{module_name};
g_pyqt_helper_defns(sf, spec)
self.g_module_init_start(sf)
- has_module_functions = self.g_module_functions_table(sf, bindings,
+ has_module_functions = self._g_module_functions_table(sf, bindings,
module)
self.g_module_definition(sf, has_module_functions=has_module_functions)
self._g_module_init_body(sf)
- def g_enum_macros(self, sf, scope=None, imported_module=None):
- """ Generate the type macros for enums. """
+ return name_cache_state
+
+ def g_enums_specifications(self, sf, bindings, scope=None):
+ """ Generate the specifications for the wrapped enums in a scope and
+ return the total number of enum members.
+ """
spec = self.spec
+ needed_enums = []
+
+ # We generate the enum definitions for all scopes in the same place.
+ if scope is None:
+ # Generate any enum slot tables.
+ for enum in spec.enums:
+ if enum.module is not spec.module or enum.fq_cpp_name is None:
+ continue
+
+ if len(enum.slots) == 0:
+ continue
+
+ for member in enum.slots:
+ g_py_slot(self, sf, bindings, member, scope=enum)
+
+ enum_name = enum.fq_cpp_name.as_word
+
+ sf.write(
+f'''
+static sipPySlotDef slots_{enum_name}[] = {{
+''')
+
+ for member in enum.slots:
+ if member.py_slot is not None:
+ slot_ref = self.get_slot_ref(member.py_slot)
+ sf.write(f' {{(void *)slot_{enum_name}_{member.py_name}, {slot_ref}}},\n')
+
+ sf.write(
+''' {SIP_NULLPTR, (sipPySlotType)0}
+};
+
+''')
+
+ self._g_enums_defs(sf, needed_enums)
+
+ if not self.custom_enums_supported():
+ return -1, needed_enums
+
+ enum_members = []
for enum in spec.enums:
- if enum.fq_cpp_name is None:
+ if enum.module is not spec.module:
continue
- # Continue unless the scopes match.
- if scope is not None:
- if enum.scope is not scope:
+ enum_py_scope = py_scope(enum.scope)
+
+ if isinstance(scope, WrappedClass):
+ # The scope is a class.
+ if enum_py_scope is not scope or (enum.is_protected and not scope.has_shadow):
continue
- elif enum.scope is not None:
+
+ elif scope is not None:
+ # The scope is a mapped type.
+ if enum.scope != scope:
+ continue
+
+ elif enum_py_scope is not None or isinstance(enum.scope, MappedType) or enum.fq_cpp_name is None:
continue
- value = None
+ enum_members.extend(enum.members)
+
+ nr_members = len(enum_members)
+ if nr_members == 0:
+ return 0, needed_enums
+
+ enum_members.sort(key=lambda v: v.scope.type_nr)
+ enum_members.sort(key=lambda v: v.py_name.name)
+
+ if py_scope(scope) is None:
+ sf.write(
+'''
+/* These are the enum members of all global enums. */
+static sipEnumMemberDef enummembers[] = {
+''')
+ else:
+ sf.write(
+f'''
+static sipEnumMemberDef enummembers_{scope.iface_file.fq_cpp_name.as_word}[] = {{
+''')
+
+ for enum_member in enum_members:
+ sf.write(f' {{{self.cached_name_ref(enum_member.py_name)}, ')
+ sf.write(self._get_cast_enum_member(enum_member))
+ sf.write(f', {enum_member.scope.type_nr}}},\n')
+
+ sf.write('};\n')
+
+ return len(enum_members), needed_enums
+
+ def g_exceptions_decls(self, sf):
+ """ Generate the declarations of all exceptions. """
+
+ spec = self.spec
+ module = spec.module
+ module_name = module.py_name
+ no_exceptions = True
+
+ for exception in spec.exceptions:
+ if exception.iface_file.module is module and exception.exception_nr >= 0:
+ if no_exceptions:
+ sf.write(
+f'''
+/* The exceptions defined in this module. */
+extern PyObject *sipExportedExceptions_{module_name}[];
+
+''')
+
+ no_exceptions = False
+
+ sf.write(f'#define sipException_{exception.iface_file.fq_cpp_name.as_word} sipExportedExceptions_{module_name}[{exception.exception_nr}]\n')
+
+ def g_exceptions_defn(self, sf):
+ """ Generate the definition of the exceptions data structure. """
+
+ module = self.spec.module
+
+ sf.write(
+f'''
+
+PyObject *sipExportedExceptions_{module.py_name}[{module.nr_exceptions + 1}];
+''')
+
+ def g_get_py_reimpl(self, sf, klass, overload, virt_nr):
+ """ Generate the code to get the Python reimplementation of a C++
+ virtual.
+ """
+
+ if overload.is_const:
+ const_cast_char = 'const_cast<char *>('
+ const_cast_sw = 'const_cast<sipSimpleWrapper **>('
+ const_cast_tail = ')'
+ else:
+ const_cast_char = ''
+ const_cast_sw = ''
+ const_cast_tail = ''
+
+ abi_12_8_arg = f'{const_cast_sw}&sipPySelf{const_cast_tail}, ' if self.spec.target_abi >= (12, 8) else ''
+
+ klass_py_name_ref = self.cached_name_ref(klass.py_name) if overload.is_abstract else 'SIP_NULLPTR'
+ member_py_name_ref = self.cached_name_ref(overload.common.py_name)
+
+ sf.write(f'\n sipMeth = sipIsPyMethod(&sipGILState, {const_cast_char}&sipPyMethods[{virt_nr}]{const_cast_tail}, {abi_12_8_arg}{klass_py_name_ref}, {member_py_name_ref});\n')
- if imported_module is None:
- if enum.module is spec.module:
- value = f'sipExportedTypes_{spec.module.py_name}[{enum.type_nr}]'
- elif enum.module is imported_module and enum.needed:
- value = f'sipImportedTypes_{spec.module.py_name}_{enum.module.py_name}[{enum.type_nr}].it_td'
+ def g_import_tables(self, sf):
+ """ Generated the tables related to imported modules. """
- if value is not None:
- sf.write(f'\n#define {self.get_type_ref(enum)} {value}\n')
+ spec = self.spec
+ module = spec.module
+ module_name = module.py_name
+
+ for imported_module in module.all_imports:
+ imported_module_name = imported_module.py_name
+
+ if len(imported_module.needed_types) != 0:
+ sf.write(
+f'''
+
+/* This defines the types that this module needs to import from {imported_module_name}. */
+sipImportedTypeDef sipImportedTypes_{module_name}_{imported_module_name}[] = {{
+''')
+
+ for needed_type in imported_module.needed_types:
+ if needed_type.type is ArgumentType.MAPPED:
+ type_name = needed_type.definition.cpp_name
+ else:
+ if needed_type.type is ArgumentType.CLASS:
+ scoped_name = needed_type.definition.iface_file.fq_cpp_name
+ else:
+ scoped_name = needed_type.definition.fq_cpp_name
+
+ type_name = scoped_name.cpp_stripped(STRIP_GLOBAL)
+
+ sf.write(f' {{"{type_name}"}},\n')
+
+ sf.write(
+''' {SIP_NULLPTR}
+};
+''')
+
+ if imported_module.nr_virtual_error_handlers > 0:
+ sf.write(
+f'''
+
+/*
+ * This defines the virtual error handlers that this module needs to import
+ * from {imported_module_name}.
+ */
+sipImportedVirtErrorHandlerDef sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}[] = {{
+''')
+
+ # The handlers are unordered so search for each in turn. There
+ # will probably be only one so speed isn't an issue.
+ for i in range(imported_module.nr_virtual_error_handlers):
+ for handler in spec.virtual_error_handlers:
+ if handler.module is imported_module and handler.handler_nr == i:
+ sf.write(f' {{"{handler.name}"}},\n')
+
+ sf.write(
+''' {SIP_NULLPTR}
+};
+''')
+
+ if imported_module.nr_exceptions > 0:
+ sf.write(
+f'''
+
+/*
+ * This defines the exception objects that this module needs to import from
+ * {imported_module_name}.
+ */
+sipImportedExceptionDef sipImportedExceptions_{module_name}_{imported_module_name}[] = {{
+''')
+
+ # The exceptions are unordered so search for each in turn.
+ # There will probably be very few so speed isn't an issue.
+ for i in range(imported_module.nr_exceptions):
+ for exception in spec.exceptions:
+ if exception.iface_file.module is imported_module and exception.exception_nr == i:
+ sf.write(f' {{"{exception.py_name}"}},\n')
+
+ sf.write(
+''' {SIP_NULLPTR}
+};
+''')
+
+ sf.write(
+'''
+
+/* This defines the modules that this module needs to import. */
+static sipImportedModuleDef importsTable[] = {
+''')
+
+ for imported_module in module.all_imports:
+ imported_module_name = imported_module.py_name
+
+ types = handlers = exceptions = 'SIP_NULLPTR'
+
+ if len(imported_module.needed_types) != 0:
+ types = f'sipImportedTypes_{module_name}_{imported_module_name}'
+
+ if imported_module.nr_virtual_error_handlers != 0:
+ handlers = f'sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}'
+
+ if imported_module.nr_exceptions != 0:
+ exceptions = f'sipImportedExceptions_{module_name}_{imported_module_name}'
+
+ sf.write(f' {{"{imported_module.fq_py_name}", {types}, {handlers}, {exceptions}}},\n')
+
+ sf.write(
+''' {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR}
+};
+''')
+
+ def g_imported_module_decls(self, sf, imported_module):
+ """ Generate any declarations related to an imported module. """
+
+ module_name = self.spec.module.py_name
+ imported_module_name = imported_module.py_name
+
+ if len(imported_module.needed_types) != 0:
+ sf.write(f'extern sipImportedTypeDef sipImportedTypes_{module_name}_{imported_module_name}[];\n')
+
+ if imported_module.nr_virtual_error_handlers != 0:
+ sf.write(f'extern sipImportedVirtErrorHandlerDef sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}[];\n')
+
+ if imported_module.nr_exceptions != 0:
+ sf.write(f'extern sipImportedExceptionDef sipImportedExceptions_{module_name}_{imported_module_name}[];\n')
def g_init_mixin_impl_body(self, sf, klass):
""" Generate the body of the implementation of a mixin initialisation
@@ -205,19 +561,118 @@ const sipAPIDef *sipAPI_{module_name};
""" Generate the API details for a mapped type. """
spec = self.spec
+ module = spec.module
iface_file = mapped_type.iface_file
- module_name = spec.module.py_name
+ module_name = module.py_name
mapped_type_name = iface_file.fq_cpp_name.as_word
- sf.write(
+ if iface_file.module is spec.module:
+ sf.write(
f'''
#define {self.get_type_ref(mapped_type)} sipExportedTypes_{module_name}[{iface_file.type_nr}]
extern sipMappedTypeDef sipTypeDef_{module_name}_{mapped_type_name};
+''')
+ else:
+ sf.write(
+f'''
+#define {self.get_type_ref(mapped_type)} sipImportedTypes_{module_name}_{iface_file.module.py_name}[{iface_file.type_nr}].it_td
+''')
+
+ def g_mapped_type_definition(self, sf, bindings, mapped_type):
+ """ Generate the type structure that contains all the information
+ needed by a mapped type.
+ """
+
+ spec = self.spec
+ mapped_type_name = mapped_type.iface_file.fq_cpp_name.as_word
+
+ members = get_function_table(mapped_type.members)
+ cod_nrmethods = self.g_py_method_table(sf, bindings, members,
+ mapped_type)
+
+ id_int = 'SIP_NULLPTR'
+
+ if self.custom_enums_supported():
+ cod_nrenummembers, _ = self.g_enums_specifications(sf, bindings,
+ scope=mapped_type)
+ has_ints = False
+ needs_namespace = (cod_nrenummembers > 0)
+ else:
+ if self.g_mapped_type_int_instances(sf, mapped_type):
+ id_int = 'intInstances_' + mapped_type_name
+ needs_namespace = True
+ else:
+ needs_namespace = False
+
+ if cod_nrmethods > 0:
+ needs_namespace = True
+
+ if pyqt6_supported(spec) and mapped_type.pyqt_flags != 0:
+ sf.write(f'\n\nstatic pyqt6MappedTypePluginDef plugin_{mapped_type_name} = {{{mapped_type.pyqt_flags}}};\n')
+
+ td_plugin_data = '&plugin_' + mapped_type_name
+ else:
+ td_plugin_data = 'SIP_NULLPTR'
+
+ sf.write(
+f'''
+
+sipMappedTypeDef sipTypeDef_{mapped_type.iface_file.module.py_name}_{mapped_type_name} = {{
+ {{
+''')
+
+ if spec.target_abi < (13, 0):
+ sf.write(
+''' -1,
+ SIP_NULLPTR,
''')
- self.g_enum_macros(sf, scope=mapped_type)
+ td_flags = get_mapped_type_flags(mapped_type)
+ td_cname = self.cached_name_ref(mapped_type.cpp_name, as_nr=True)
+ cod_name = self.cached_name_ref(mapped_type.py_name, as_nr=True) if needs_namespace else '-1'
+ cod_methods = 'SIP_NULLPTR' if cod_nrmethods == 0 else 'methods_' + mapped_type_name
+
+ sf.write(
+f''' SIP_NULLPTR,
+ {td_flags},
+ {td_cname},
+ SIP_NULLPTR,
+ {td_plugin_data},
+ }},
+ {{
+ {cod_name},
+ {{0, 0, 1}},
+ {cod_nrmethods}, {cod_methods},
+''')
+
+ if self.custom_enums_supported():
+ cod_enummembers = 'SIP_NULLPTR' if cod_nrenummembers == 0 else 'enummembers_' + mapped_type_name
+
+ sf.write(
+f''' {cod_nrenummembers}, {cod_enummembers},
+''')
+
+ mtd_assign = 'SIP_NULLPTR' if mapped_type.no_assignment_operator else 'assign_' + mapped_type_name
+ mtd_array = 'SIP_NULLPTR' if mapped_type.no_default_ctor else 'array_' + mapped_type_name
+ mtd_copy = 'SIP_NULLPTR' if mapped_type.no_copy_ctor else 'copy_' + mapped_type_name
+ mtd_release = 'SIP_NULLPTR' if mapped_type.no_release else 'release_' + mapped_type_name
+ mtd_cto = 'SIP_NULLPTR' if mapped_type.convert_to_type_code is None else 'convertTo_' + mapped_type_name
+ mtd_cfrom = 'SIP_NULLPTR' if mapped_type.convert_from_type_code is None else 'convertFrom_' + mapped_type_name
+
+ sf.write(
+f''' 0, SIP_NULLPTR,
+ {{SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, {id_int}, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR}}
+ }},
+ {mtd_assign},
+ {mtd_array},
+ {mtd_copy},
+ {mtd_release},
+ {mtd_cto},
+ {mtd_cfrom}
+}};
+''')
def g_mapped_type_int_instances(self, sf, mapped_type):
""" Generate the code to add a set of ints to a mapped type. Return
@@ -249,30 +704,6 @@ f''' static PyModuleDef sip_module_def = {{
}};
''')
- def g_module_functions_table(self, sf, bindings, module):
- """ Generate the table of module functions and return True if anything
- was actually generated.
- """
-
- # We always generate a table.
- sf.write(' static PyMethodDef sip_methods[] = {\n')
-
- self._g_module_function_table_entries(sf, bindings, module,
- module.global_functions)
-
- # Generate the global functions for any hidden namespaces.
- for klass in self.spec.classes:
- if klass.iface_file.module is module and klass.is_hidden_namespace:
- self._g_module_function_table_entries(sf, bindings, module,
- klass.members)
-
- sf.write(
-''' {SIP_NULLPTR, SIP_NULLPTR, 0, SIP_NULLPTR}
- };
-''')
-
- return True
-
def g_module_init_start(self, sf):
""" Generate the start of the Python module initialisation function.
"""
@@ -300,6 +731,127 @@ PyMODINIT_FUNC PyInit_{module_name}({arg_type})
{{
''')
+ def g_name_cache(self, sf):
+ """ Generate the name cache definition and return the transformed name
+ cache.
+ """
+
+ spec = self.spec
+ module = spec.module
+
+ # Make sure the module name is cached.
+ module.fq_py_name.used = True
+
+ # Transform the name cache.
+ name_cache_list = _name_cache_as_list(spec.name_cache)
+
+ # Generate the names.
+ has_sip_strings = False
+
+ for name in name_cache_list:
+ if not name.used or name.is_substring:
+ continue
+
+ if not has_sip_strings:
+ has_sip_strings = True
+
+ sf.write(
+f'''
+/* Define the strings used by this module. */
+const char sipStrings_{module.py_name}[] = {{
+''')
+
+ sf.write(' ')
+
+ for ch in name.name:
+ sf.write(f"'{ch}', ")
+
+ sf.write('0,\n')
+
+ if has_sip_strings:
+ sf.write('};\n')
+
+ return name_cache_list
+
+ @staticmethod
+ def g_not_implemented(sf):
+ """ Generate the code to clear any exception and return
+ Py_NotImplemented.
+ """
+
+ sf.write(
+'''
+ PyErr_Clear();
+
+ Py_INCREF(Py_NotImplemented);
+ return Py_NotImplemented;
+''')
+
+ def g_py_method_end(self, sf, state, nr_signatures):
+ """ Generate the end of a method implementation. """
+
+ klass, member, has_auto_docstring, need_args = state
+
+ if not member.no_arg_parser:
+ klass_name = klass.iface_file.fq_cpp_name.as_word
+ member_py_name = member.py_name.name
+
+ sip_parse_err = 'sipParseErr' if need_args else 'SIP_NULLPTR'
+ klass_py_name_ref = self.cached_name_ref(klass.py_name)
+ member_py_name_ref = self.cached_name_ref(member.py_name)
+ docstring_ref = f'doc_{klass_name}_{member_py_name}' if has_auto_docstring else 'SIP_NULLPTR'
+
+ sf.write(
+f'''
+ sipNoMethod({sip_parse_err}, {klass_py_name_ref}, {member_py_name_ref}, {docstring_ref});
+
+ return SIP_NULLPTR;
+''')
+
+ sf.write('}\n')
+
+ def g_py_method_start(self, sf, bindings, klass, member, original_klass,
+ need_args, need_self):
+ """ Generate the start of a method implementation and return a 4-tuple
+ the class, member, whether it has an automatically generated docstring
+ and whether an argument parser will be needed.
+ """
+
+ spec = self.spec
+ klass_name = klass.iface_file.fq_cpp_name.as_word
+ member_py_name = member.py_name.name
+
+ # Generate the docstrings.
+ if has_method_docstring(bindings, member, original_klass.overloads):
+ sf.write(f'PyDoc_STRVAR(doc_{klass_name}_{member_py_name}, "')
+
+ has_auto_docstring = g_method_docstring(sf, spec, bindings, member,
+ original_klass.overloads,
+ is_method=not klass.is_hidden_namespace)
+
+ sf.write('");\n\n')
+ else:
+ has_auto_docstring = False
+
+ if member.no_arg_parser or member.allow_keyword_args:
+ kw_fw_decl = ', PyObject *'
+ kw_decl = ', PyObject *sipKwds'
+ else:
+ kw_fw_decl = kw_decl = ''
+
+ if not spec.c_bindings:
+ sf.write(f'extern "C" {{static PyObject *meth_{klass_name}_{member_py_name}({self.get_py_method_args(is_impl=False)}{kw_fw_decl});}}\n')
+
+ sf.write(f'static PyObject *meth_{klass_name}_{member_py_name}({self.get_py_method_args(is_impl=True, need_self=need_self, need_args=need_args)}{kw_decl})\n{{\n')
+
+ return klass, member, has_auto_docstring, need_args
+
+ def g_py_method_support_vars(self, sf, need_args):
+ """ Generate the variables needed by a method implementation. """
+
+ if need_args:
+ sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n');
+
def g_py_method_table(self, sf, bindings, members, scope):
""" Generate a Python method table for a class or mapped type and
return the number of entries.
@@ -347,11 +899,27 @@ static PyMethodDef methods_{scope_name}[] = {{
return len(members)
- def g_sip_api(self, sf, module_name):
+ def g_sip_api(self, sf, module_name, module_state):
""" Generate the SIP API as seen by generated code. """
spec = self.spec
+ # Generate the references to (potentially) shared strings.
+ sf.write(
+'''
+/*
+ * Convenient names to refer to various strings defined in this module.
+ * Only the class names are part of the public API.
+ */
+''')
+
+ for cached_name in module_state:
+ if cached_name.used:
+ sf.write(
+f'''#define {self.cached_name_ref(cached_name, as_nr=True)} {cached_name.offset}
+#define {self.cached_name_ref(cached_name)} &sipStrings_{module_name}[{cached_name.offset}]
+''')
+
sf.write(
f'''
#define sipMalloc sipAPI_{module_name}->api_malloc
@@ -568,6 +1136,99 @@ f'''#define sipIsPyMethod sipAPI_{module_name}->api_is_py_method_1
f'''#define sipIsPyMethod sipAPI_{module_name}->api_is_py_method
''')
+ # Generate the name strings.
+ sf.write(
+f'''
+/* The strings used by this module. */
+extern const char sipStrings_{module_name}[];
+''')
+
+ def g_slot_implementations(self, sf, bindings, scope, members):
+ """ Generate the slot implementations for a scope. """
+
+ is_ns = isinstance(scope, WrappedClass) and scope.iface_file.type is IfaceFileType.NAMESPACE
+
+ for member in members:
+ if is_ns:
+ g_static_function(self, sf, bindings, member, scope=scope)
+ elif member.py_slot is not None:
+ g_py_slot(self, sf, bindings, member, scope=scope)
+
+ def g_static_function_end(self, sf, state, nr_signatures):
+ """ Generate the end of a static function implementation. """
+
+ if nr_signatures != 0:
+ member, member_ref, has_auto_docstring = state
+
+ sf.write(
+f'''
+ /* Raise an exception if the arguments couldn't be parsed. */
+ sipNoFunction(sipParseErr, {self.cached_name_ref(member.py_name)}, ''')
+
+ if has_auto_docstring:
+ sf.write(f'doc_{member_ref}')
+ else:
+ sf.write('SIP_NULLPTR')
+
+ sf.write(');\n\n return SIP_NULLPTR;\n')
+
+ sf.write('}\n')
+
+ def g_static_function_start(self, sf, bindings, scope_py, member,
+ overloads):
+ """ Generate the start of a static function implementation and return
+ a 3-tuple of the member, the member reference and whether it has an
+ automatically generated docstring.
+ """
+
+ spec = self.spec
+ member_name = member.py_name.name
+
+ if scope_py is not None:
+ member_name = scope_py.iface_file.fq_cpp_name.as_word + '_' + member_name
+
+ # Generate the docstrings.
+ if has_method_docstring(bindings, member, overloads):
+ sf.write(f'PyDoc_STRVAR(doc_{member_name}, "')
+ has_auto_docstring = g_method_docstring(sf, spec, bindings, member,
+ overloads)
+ sf.write('");\n\n')
+ else:
+ has_auto_docstring = False
+
+ if member.no_arg_parser or member.allow_keyword_args:
+ kw_fw_decl = ', PyObject *'
+ kw_decl = ', PyObject *sipKwds'
+ else:
+ kw_fw_decl = kw_decl = ''
+
+ if scope_py is None:
+ if not spec.c_bindings:
+ sf.write(f'extern "C" {{static PyObject *func_{member_name}({self.get_py_method_args(is_impl=False)}{kw_fw_decl});}}\n')
+
+ sf.write(f'static PyObject *func_{member_name}({self.get_py_method_args(is_impl=True)}{kw_decl})\n')
+ else:
+ # This can only happen with C++ bindings.
+ sf.write(f'extern "C" {{static PyObject *meth_{member_name}({self.get_py_method_args(is_impl=False)}{kw_fw_decl});}}\n')
+
+ sf.write(f'static PyObject *meth_{member_name}({self.get_py_method_args(is_impl=True)}{kw_decl})\n')
+
+ sf.write('{\n')
+
+ return member, member_name, has_auto_docstring
+
+ def g_static_function_support_vars(self, sf, scope):
+ """ Generate the variables needed by a static function implementation.
+ """
+
+ sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n')
+
+ if scope is None and self.spec.c_bindings:
+ sf.write(
+'''
+ (void)sipSelf;
+''')
+
def g_static_variables_table(self, sf, scope=None):
""" Generate the tables of static variables for a scope and return a
set of strings corresponding to the tables actually generated.
@@ -661,11 +1322,8 @@ static sipPySlotDef slots_{klass_name}[] = {{
# The attributes tables.
nr_methods = g_class_method_table(self, sf, bindings, klass)
-
- if self.custom_enums_supported():
- nr_enum_members = g_enum_member_table(self, sf, scope=klass)
- else:
- nr_enum_members = -1
+ nr_enum_members, _ = self.g_enums_specifications(sf, bindings,
+ scope=klass)
# The property and variable handlers.
nr_variables = 0
@@ -744,7 +1402,7 @@ static sipPySlotDef slots_{klass_name}[] = {{
sv_state = self.g_static_variables_table(sf, scope=klass)
# Generate the docstring.
- docstring_ref = g_class_docstring(sf, spec, bindings, klass)
+ docstring_ref = self._g_class_docstring(sf, bindings, klass)
# Generate any plugin-specific data structures.
plugin_ref = 'SIP_NULLPTR'
@@ -982,16 +1640,28 @@ f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *
return self.spec.target_abi[0] < 13
- def get_class_ref_value(self, klass):
- """ Return the value of a class's reference. """
+ def get_enum_to_py_conversion(self, enum, value_name):
+ """ Return the code to convert a C/C++ enum to a Python object. """
- module_name = self.spec.module.py_name
- iface_file = klass.iface_file
+ if enum.fq_cpp_name is None:
+ return f'PyLong_FromLong({value_name})'
+
+ if not self.spec.c_bindings:
+ value_name = f'static_cast<int>({value_name})'
+
+ return f'sipConvertFromEnum({value_name}, {self.get_type_ref(enum)})'
- return f'sipExportedTypes_{module_name}[{iface_file.type_nr}]'
+ def get_enum_ref_value(self, enum):
+ """ Return the value of an enum's reference. """
- def get_py_method_args(self, *, is_impl, is_module_fn, need_self=False,
- need_args=True):
+ spec = self.spec
+
+ if enum.module is spec.module:
+ return f'sipExportedTypes_{spec.module.py_name}[{enum.type_nr}]'
+
+ return f'sipImportedTypes_{spec.module.py_name}_{enum.module.py_name}[{enum.type_nr}].it_td'
+
+ def get_py_method_args(self, *, is_impl, need_self=False, need_args=True):
""" Return the part of a Python method signature that are ABI
dependent.
"""
@@ -1001,12 +1671,61 @@ f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *
return f'PyObject *{self_name}, PyObject *{args_name}'
+ @staticmethod
+ def get_raise_unknown_exception():
+ """ Return the call to raise an exception about an unknown exception.
+ """
+
+ return 'sipRaiseUnknownException()'
+
+ @staticmethod
+ def get_result_parser():
+ """ Return the name of the Python reimplementation result parser. """
+
+ return 'sipParseResultEx'
+
+ def get_sipself_test(self, klass):
+ """ Return the code that checks if 'sipSelf' was bound or passed as an
+ argument.
+ """
+
+ if self.spec.target_abi >= (13, 0):
+ sipself_test = f'!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject({self.get_type_ref(klass)}))'
+ else:
+ sipself_test = '!sipSelf'
+
+ return f'({sipself_test} || sipIsDerivedClass((sipSimpleWrapper *)sipSelf))'
+
@staticmethod
def get_slot_ref(slot_type):
""" Return a reference to a slot. """
return slot_type.name.lower() + '_slot'
+ def get_spec_for_class(self, klass):
+ """ Return the name of the data structure specifying a class. """
+
+ return f'sipTypeDef_{self.spec.module.py_name}_{klass.iface_file.fq_cpp_name.as_word}.ctd_base'
+
+ def get_spec_for_mapped_type(self, mapped_type):
+ """ Return the name of the data structure specifying a mapped type. """
+
+ return f'sipTypeDef_{self.spec.module.py_name}_{mapped_type.iface_file.fq_cpp_name.as_word}.mtd_base'
+
+ def get_spec_for_enum(self, enum, enums_state):
+ """ Return the name of the data structure specifying an enum. """
+
+ _, needed_enums = enums_state
+ enum_nr = needed_enums.index(enum)
+
+ return f'enumTypes[{enum_nr}].etd_base'
+
+ @staticmethod
+ def get_spec_suffix():
+ """ Return the suffix used for immutable specifications. """
+
+ return 'Def'
+
@staticmethod
def get_type_ref(wrapped_object):
""" Return the reference to the type of a wrapped object. """
@@ -1016,17 +1735,25 @@ f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *
return 'sipType_' + fq_cpp_name.as_word
@staticmethod
- def get_types_table_prefix():
- """ Return the prefix in the name of the wrapped types table. """
+ def get_types_table_decl(module):
+ """ Return the declaration of a module's wrapped types table. """
- return 'sipTypeDef *sipExportedTypes'
+ return f'sipTypeDef *sipExportedTypes_{module.py_name}'
@staticmethod
- def get_wrapped_type_type():
+ def get_wrapper_type():
""" Return the type of the C representation of a wrapped object. """
return 'sipSimpleWrapper *'
+ @staticmethod
+ def get_wrapper_type_cast():
+ """ Return the cast from a PyObject* of the C representation of a
+ wrapped object.
+ """
+
+ return '(sipSimpleWrapper *)'
+
def legacy_qt_support(self):
""" Return True if the module implements legacy Qt support. """
@@ -1055,6 +1782,70 @@ f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *
return target_abi >= min_13 or (min_12 <= target_abi < (13, 0))
+ def _g_class_docstring(self, sf, bindings, klass):
+ """ Generate any docstring for a class and return an appropriate
+ reference to it.
+ """
+
+ if self._has_class_docstring(bindings, klass):
+ docstring_ref = 'doc_' + klass.iface_file.fq_cpp_name.as_word
+
+ sf.write(f'\nPyDoc_STRVAR({docstring_ref}, "')
+ g_class_docstring(sf, self.spec, bindings, klass)
+ sf.write('");\n')
+ else:
+ docstring_ref = 'SIP_NULLPTR'
+
+ return docstring_ref
+
+ def _g_enums_defs(self, sf, needed_enums):
+ """ Generate the definitions for all wrapped enums. """
+
+ spec = self.spec
+ module = spec.module
+
+ # Note that we go through the sorted table of needed types rather than
+ # the unsorted list of all enums.
+ for needed_type in module.needed_types:
+ if needed_type.type is not ArgumentType.ENUM:
+ continue
+
+ enum = needed_type.definition
+
+ scope_type_nr = -1 if enum.scope is None else enum.scope.iface_file.type_nr
+
+ if len(needed_enums) == 0:
+ sf.write('static sipEnumTypeDef enumTypes[] = {\n')
+
+ cpp_name = get_normalised_cached_name(enum.cached_fq_cpp_name)
+ py_name = get_normalised_cached_name(enum.py_name)
+
+ if self.py_enums_supported():
+ base_type = 'SIP_ENUM_' + enum.base_type.name
+ nr_members = len(enum.members)
+
+ sf.write(
+f' {{{{SIP_NULLPTR, SIP_TYPE_ENUM, sipNameNr_{cpp_name}, SIP_NULLPTR, 0}}, {base_type}, sipNameNr_{py_name}, {scope_type_nr}, {nr_members}')
+ else:
+ sip_type = 'SIP_TYPE_SCOPED_ENUM' if enum.is_scoped else 'SIP_TYPE_ENUM'
+
+ v12_fields = '-1, SIP_NULLPTR, ' if spec.target_abi < (13, 0) else ''
+
+ sf.write(
+f' {{{{{v12_fields}SIP_NULLPTR, {sip_type}, sipNameNr_{cpp_name}, SIP_NULLPTR, 0}}, sipNameNr_{py_name}, {scope_type_nr}')
+
+ if len(enum.slots) == 0:
+ sf.write(', SIP_NULLPTR')
+ else:
+ sf.write(', slots_' + enum.fq_cpp_name.as_word)
+
+ sf.write('},\n')
+
+ needed_enums.append(enum)
+
+ if len(needed_enums) != 0:
+ sf.write('};\n')
+
def _g_instances_char(self, sf, scope):
""" Generate the code to add a set of characters to a dictionary.
Return True if there was at least one.
@@ -1070,6 +1861,9 @@ f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *
ci_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)
ci_encoding = _get_encoding(variable.type)
+ if variable.type.type is ArgumentType.USTRING:
+ ci_val = '(char)' + ci_val;
+
instances.append((ci_name, ci_val, ci_encoding))
return _write_instances_table(sf, scope, instances,
@@ -1162,7 +1956,7 @@ static sipDoubleInstanceDef doubleInstances{suffix}[]''')
for enum_member in enum.members:
ii_name = self.cached_name_ref(enum_member.py_name)
- ii_val = get_enum_member(spec, enum_member)
+ ii_val = self._get_cast_enum_member(enum_member)
instances.append((ii_name, ii_val))
# Handle int variables.
@@ -1189,7 +1983,7 @@ static sipDoubleInstanceDef doubleInstances{suffix}[]''')
for enum_member in enum.members:
ii_name = self.cached_name_ref(enum_member.py_name)
- ii_val = get_enum_member(spec, enum_member)
+ ii_val = self._get_cast_enum_member(enum_member)
instances.append((ii_name, ii_val))
return _write_instances_table(sf, scope, instances,
@@ -1416,6 +2210,30 @@ f'''
'doc_' + member.py_name.name)
sf.write(f', {docstring_ref}}},\n')
+ def _g_module_functions_table(self, sf, bindings, module):
+ """ Generate the table of module functions and return True if anything
+ was actually generated.
+ """
+
+ # We always generate a table.
+ sf.write(' static PyMethodDef sip_methods[] = {\n')
+
+ self._g_module_function_table_entries(sf, bindings, module,
+ module.global_functions)
+
+ # Generate the global functions for any hidden namespaces.
+ for klass in self.spec.classes:
+ if klass.iface_file.module is module and klass.is_hidden_namespace:
+ self._g_module_function_table_entries(sf, bindings, module,
+ klass.members)
+
+ sf.write(
+''' {SIP_NULLPTR, SIP_NULLPTR, 0, SIP_NULLPTR}
+ };
+''')
+
+ return True
+
# The types that are implemented as PyObject*.
_PY_OBJECT_TYPES = (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE,
ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE,
@@ -1993,6 +2811,11 @@ f'''
}
''')
+ def _get_cast_enum_member(self, enum_member):
+ """ Return the appropriately cast enum member. """
+
+ return f'static_cast<int>({get_enum_member(self.spec, enum_member)})'
+
def _get_variable_member(self, variable):
""" Return the member variable of a class. """
@@ -2153,6 +2976,32 @@ f'''
return statement
+ @staticmethod
+ def _has_class_docstring(bindings, klass):
+ """ Return True if a class has a docstring. """
+
+ auto_docstring = False
+
+ # Check for any explicit docstrings and remember if there were any that
+ # could be automatically generated.
+ if klass.docstring is not None:
+ return True
+
+ for ctor in klass.ctors:
+ if ctor.access_specifier is AccessSpecifier.PRIVATE:
+ continue
+
+ if ctor.docstring is not None:
+ return True
+
+ if bindings.docstrings:
+ auto_docstring = True
+
+ if not klass.can_create:
+ return False
+
+ return auto_docstring
+
def _write_int_instances(self, sf, scope, target_type, type_name):
""" Generate the code to add a set of a particular type to a
dictionary. Return True if there was at least one.
@@ -2224,6 +3073,45 @@ def _get_encoding(type):
return encoding
+def _name_cache_as_list(name_cache):
+ """ Return a name cache as a correctly ordered list of CachedName objects.
+ """
+
+ name_cache_list = []
+
+ # Create the list sorted first by descending name length and then
+ # alphabetical order.
+ for k in sorted(name_cache.keys(), reverse=True):
+ name_cache_list.extend(sorted(name_cache[k], key=lambda k: k.name))
+
+ # Set the offset into the string pool for every used name.
+ offset = 0
+
+ # Map of suffix to previously processed name
+ suffixes = {}
+
+ for cached_name in name_cache_list:
+ if not cached_name.used:
+ continue
+
+ name_len = len(cached_name.name)
+
+ # See if the tail of a previous used name could be used instead.
+ prev_name = suffixes.get(cached_name.name)
+ if prev_name:
+ cached_name.is_substring = True
+ cached_name.offset = prev_name.offset + len(prev_name.name) - name_len
+
+ if not cached_name.is_substring:
+ cached_name.offset = offset
+ offset += name_len + 1
+
+ for i in range(len(cached_name.name)):
+ suffixes.setdefault(cached_name.name[i:], cached_name)
+
+ return name_cache_list
+
+
def _write_instances_table(sf, scope, instances, declaration_template):
""" Write a table of instances. Return True if there was a table written.
"""
diff --git a/sipbuild/generator/outputs/code/code.py b/sipbuild/generator/outputs/code/code.py
index c466d4e..acf339c 100644
--- a/sipbuild/generator/outputs/code/code.py
+++ b/sipbuild/generator/outputs/code/code.py
@@ -143,6 +143,9 @@ def _module_code(backend, bindings, project, py_debug, buildable):
g_iface_file_code(backend, iface_sf, bindings, project,
buildable, py_debug, iface_file, need_postinc)
+ if use_sf is None:
+ iface_sf.close()
+
sf.close()
header_name = os.path.join(buildable.build_dir, f'sipAPI{module_name}.h')
diff --git a/sipbuild/generator/outputs/code/snippets.py b/sipbuild/generator/outputs/code/snippets.py
index f469950..8c0f619 100644
--- a/sipbuild/generator/outputs/code/snippets.py
+++ b/sipbuild/generator/outputs/code/snippets.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from ....exceptions import UserException
@@ -21,9 +21,9 @@ from ..formatters import (fmt_argument_as_cpp_type, fmt_argument_as_name,
fmt_signature_as_cpp_declaration, fmt_signature_as_cpp_definition,
fmt_signature_as_type_hint, fmt_value_list_as_cpp_expression)
-from .utils import (arg_is_small_enum, callable_overloads, get_const_cast,
+from .utils import (callable_overloads, get_class_from_void, get_const_cast,
get_convert_to_type_code, get_docstring_text, get_encoded_type,
- get_enum_class_scope, get_enum_member, get_named_value_decl,
+ get_enum_class_scope, get_function_table, get_named_value_decl,
get_normalised_cached_name, get_optional_ptr, get_type_from_void,
get_use_in_code, get_user_state_suffix, get_void_ptr_cast,
has_method_docstring, is_used_in_code, keep_py_reference, need_dealloc,
@@ -32,30 +32,13 @@ from .utils import (arg_is_small_enum, callable_overloads, get_const_cast,
variables_in_scope)
-def g_class_docstring(sf, spec, bindings, klass):
- """ Generate any docstring for a class and return an appropriate reference
- to it.
- """
-
- if _has_class_docstring(bindings, klass):
- docstring_ref = 'doc_' + klass.iface_file.fq_cpp_name.as_word
-
- sf.write(f'\nPyDoc_STRVAR({docstring_ref}, "')
- _class_docstring(sf, spec, bindings, klass)
- sf.write('");\n')
- else:
- docstring_ref = 'SIP_NULLPTR'
-
- return docstring_ref
-
-
def g_class_method_table(backend, sf, bindings, klass):
""" Generate the sorted table of methods for a class and return the number
of entries.
"""
if klass.iface_file.type is IfaceFileType.NAMESPACE:
- members = _get_function_table(klass.members)
+ members = get_function_table(klass.members)
else:
members = _get_method_table(klass)
@@ -123,64 +106,6 @@ f' sip_import_component_module(sipModuleDict, "{mod.fq_py_name}");\n')
''')
-def g_enum_member_table(backend, sf, scope=None):
- """ Generate the table of enum members for a scope. Return the number of
- them.
- """
-
- spec = backend.spec
- enum_members = []
-
- for enum in spec.enums:
- if enum.module is not spec.module:
- continue
-
- enum_py_scope = py_scope(enum.scope)
-
- if isinstance(scope, WrappedClass):
- # The scope is a class.
- if enum_py_scope is not scope or (enum.is_protected and not scope.has_shadow):
- continue
-
- elif scope is not None:
- # The scope is a mapped type.
- if enum.scope != scope:
- continue
-
- elif enum_py_scope is not None or isinstance(enum.scope, MappedType) or enum.fq_cpp_name is None:
- continue
-
- enum_members.extend(enum.members)
-
- nr_members = len(enum_members)
- if nr_members == 0:
- return 0
-
- enum_members.sort(key=lambda v: v.scope.type_nr)
- enum_members.sort(key=lambda v: v.py_name.name)
-
- if py_scope(scope) is None:
- sf.write(
-'''
-/* These are the enum members of all global enums. */
-static sipEnumMemberDef enummembers[] = {
-''')
- else:
- sf.write(
-f'''
-static sipEnumMemberDef enummembers_{scope.iface_file.fq_cpp_name.as_word}[] = {{
-''')
-
- for enum_member in enum_members:
- sf.write(f' {{{backend.cached_name_ref(enum_member.py_name)}, ')
- sf.write(get_enum_member(spec, enum_member))
- sf.write(f', {enum_member.scope.type_nr}}},\n')
-
- sf.write('};\n')
-
- return nr_members
-
-
def g_iface_file_code(backend, sf, bindings, project, buildable, py_debug,
iface_file, need_postinc):
""" Generate the code for an interface. """
@@ -219,7 +144,7 @@ def g_iface_file_code(backend, sf, bindings, project, buildable, py_debug,
def g_module_code(backend, sf, bindings, project, py_debug, buildable):
""" Generate the code for a module excluding the code specific to an
- interface file. It returns a closure that will be passed to
+ interface file. It returns a ABI-specific state that will be passed to
g_module_header_file().
"""
@@ -246,18 +171,8 @@ def g_module_code(backend, sf, bindings, project, py_debug, buildable):
#define sipQtDisconnectPySignal 0
''')
- # Only the legacy ABIs use a cached version of the module name.
- if spec.target_abi < (14, 0):
- module.fq_py_name.used = True
-
- # Transform the name cache.
- name_cache_list = _name_cache_as_list(spec.name_cache)
-
- # Define the names.
- has_sip_strings = _name_cache(sf, spec, name_cache_list)
- else:
- name_cache_list = None
- has_sip_strings = False
+ # Generate the name cache.
+ name_cache_state = backend.g_name_cache(sf)
# Generate the C++ code blocks.
sf.write_code(module.module_code)
@@ -267,6 +182,8 @@ def g_module_code(backend, sf, bindings, project, py_debug, buildable):
_virtual_handler(backend, sf, handler)
# Generate any virtual error handlers.
+ wrapper_type = backend.get_wrapper_type()
+
for virtual_error_handler in spec.virtual_error_handlers:
if virtual_error_handler.module is module:
self_name = get_use_in_code(virtual_error_handler.code,
@@ -277,7 +194,7 @@ def g_module_code(backend, sf, bindings, project, py_debug, buildable):
sf.write(
f'''
-void sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *{self_name}, sip_gilstate_t {state_name})
+void sipVEH_{module_name}_{virtual_error_handler.name}({wrapper_type}{self_name}, sip_gilstate_t {state_name})
{{
''')
@@ -290,13 +207,13 @@ void sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *{self_n
for member in module.global_functions:
if member.py_slot is None:
- _static_function(backend, sf, bindings, member)
+ g_static_function(backend, sf, bindings, member)
else:
# Make sure that there is still an overload and we haven't moved
# them all to classes.
for overload in module.overloads:
if overload.common is member:
- _py_slot(backend, sf, bindings, member)
+ g_py_slot(backend, sf, bindings, member)
slot_extenders = True
break
@@ -305,7 +222,7 @@ void sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *{self_n
if klass.iface_file.module is module and klass.is_hidden_namespace:
for member in klass.members:
if member.py_slot is None:
- _static_function(backend, sf, bindings, member,
+ g_static_function(backend, sf, bindings, member,
scope=klass)
# Generate any class specific __init__ or slot extenders.
@@ -317,7 +234,7 @@ void sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *{self_n
init_extenders = True
for member in klass.members:
- _py_slot(backend, sf, bindings, member, scope=klass)
+ g_py_slot(backend, sf, bindings, member, scope=klass)
slot_extenders = True
# Generate any __init__ extender table.
@@ -410,99 +327,25 @@ static sipExternalTypeDef externalTypesTable[] = {
};
''')
- # Generate any enum slot tables.
- for enum in spec.enums:
- if enum.module is not module or enum.fq_cpp_name is None:
- continue
-
- if len(enum.slots) == 0:
- continue
-
- for member in enum.slots:
- _py_slot(backend, sf, bindings, member, scope=enum)
-
- enum_name = enum.fq_cpp_name.as_word
-
- sf.write(
-f'''
-static sipPySlotDef slots_{enum_name}[] = {{
-''')
-
- for member in enum.slots:
- if member.py_slot is not None:
- slot_ref = backend.get_slot_ref(member.py_slot)
- sf.write(f' {{(void *)slot_{enum_name}_{member.py_name}, {slot_ref}}},\n')
-
- sf.write(
-''' {SIP_NULLPTR, (sipPySlotType)0}
-};
-
-''')
-
- # Generate the enum type structures while recording the order in which they
- # are generated. Note that we go through the sorted table of needed types
- # rather than the unsorted list of all enums.
- needed_enums = []
-
- # TODO ABI v14 only wants these for the local module. Other ABIs as well?
- for needed_type in module.needed_types:
- if needed_type.type is not ArgumentType.ENUM:
- continue
-
- enum = needed_type.definition
-
- scope_type_nr = -1 if enum.scope is None else enum.scope.iface_file.type_nr
-
- if len(needed_enums) == 0:
- sf.write('static sipEnumTypeDef enumTypes[] = {\n')
-
- cpp_name = get_normalised_cached_name(enum.cached_fq_cpp_name)
- py_name = get_normalised_cached_name(enum.py_name)
-
- if backend.py_enums_supported():
- base_type = 'SIP_ENUM_' + enum.base_type.name
- nr_members = len(enum.members)
-
- sf.write(
-f' {{{{SIP_NULLPTR, SIP_TYPE_ENUM, sipNameNr_{cpp_name}, SIP_NULLPTR, 0}}, {base_type}, sipNameNr_{py_name}, {scope_type_nr}, {nr_members}')
- else:
- sip_type = 'SIP_TYPE_SCOPED_ENUM' if enum.is_scoped else 'SIP_TYPE_ENUM'
-
- v12_fields = '-1, SIP_NULLPTR, ' if spec.target_abi < (13, 0) else ''
-
- sf.write(
-f' {{{{{v12_fields}SIP_NULLPTR, {sip_type}, sipNameNr_{cpp_name}, SIP_NULLPTR, 0}}, sipNameNr_{py_name}, {scope_type_nr}')
-
- if len(enum.slots) == 0:
- sf.write(', SIP_NULLPTR')
- else:
- sf.write(', slots_' + enum.fq_cpp_name.as_word)
-
- sf.write('},\n')
-
- needed_enums.append(enum)
+ # Generate the wrapped enum specifications.
+ enums_state = backend.g_enums_specifications(sf, bindings)
- if len(needed_enums) != 0:
- sf.write('};\n')
-
- if backend.custom_enums_supported():
- nr_enum_members = g_enum_member_table(backend, sf)
- else:
- nr_enum_members = -1
+ # Generate the exception specifications.
+ backend.g_exceptions_specifications(sf)
# Generate the types table.
if len(module.needed_types) != 0:
- _types_table(backend, sf, module, needed_enums)
+ _types_table(backend, sf, module, enums_state)
# Generate the typedefs table.
if module.nr_typedefs > 0:
sf.write(
-'''
+f'''
/*
* These define each typedef in this module.
*/
-static sipTypedefDef typedefsTable[] = {
+static sipTypedef{backend.get_spec_suffix()} typedefsTable[] = {{
''')
for typedef in spec.typedefs:
@@ -557,109 +400,8 @@ static sipVirtErrorHandlerDef virtErrorHandlersTable[] = {
''')
# Generate the tables for things we are importing.
- for imported_module in module.all_imports:
- imported_module_name = imported_module.py_name
-
- if len(imported_module.needed_types) != 0:
- sf.write(
-f'''
-
-/* This defines the types that this module needs to import from {imported_module_name}. */
-sipImportedTypeDef sipImportedTypes_{module_name}_{imported_module_name}[] = {{
-''')
-
- for needed_type in imported_module.needed_types:
- if needed_type.type is ArgumentType.MAPPED:
- type_name = needed_type.definition.cpp_name
- else:
- if needed_type.type is ArgumentType.CLASS:
- scoped_name = needed_type.definition.iface_file.fq_cpp_name
- else:
- scoped_name = needed_type.definition.fq_cpp_name
-
- type_name = scoped_name.cpp_stripped(STRIP_GLOBAL)
-
- sf.write(f' {{"{type_name}"}},\n')
-
- sf.write(
-''' {SIP_NULLPTR}
-};
-''')
-
- if imported_module.nr_virtual_error_handlers > 0:
- sf.write(
-f'''
-
-/*
- * This defines the virtual error handlers that this module needs to import
- * from {imported_module_name}.
- */
-sipImportedVirtErrorHandlerDef sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}[] = {{
-''')
-
- # The handlers are unordered so search for each in turn. There
- # will probably be only one so speed isn't an issue.
- for i in range(imported_module.nr_virtual_error_handlers):
- for handler in spec.virtual_error_handlers:
- if handler.module is imported_module and handler.handler_nr == i:
- sf.write(f' {{"{handler.name}"}},\n')
-
- sf.write(
-''' {SIP_NULLPTR}
-};
-''')
-
- if imported_module.nr_exceptions > 0:
- sf.write(
-f'''
-
-/*
- * This defines the exception objects that this module needs to import from
- * {imported_module_name}.
- */
-sipImportedExceptionDef sipImportedExceptions_{module_name}_{imported_module_name}[] = {{
-''')
-
- # The exceptions are unordered so search for each in turn. There
- # will probably be very few so speed isn't an issue.
- for i in range(imported_module.nr_exceptions):
- for exception in spec.exceptions:
- if exception.iface_file.module is imported_module and exception.exception_nr == i:
- sf.write(f' {{"{exception.py_name}"}},\n')
-
- sf.write(
-''' {SIP_NULLPTR}
-};
-''')
-
if len(module.all_imports) != 0:
- sf.write(
-'''
-
-/* This defines the modules that this module needs to import. */
-static sipImportedModuleDef importsTable[] = {
-''')
-
- for imported_module in module.all_imports:
- imported_module_name = imported_module.py_name
-
- types = handlers = exceptions = 'SIP_NULLPTR'
-
- if len(imported_module.needed_types) != 0:
- types = f'sipImportedTypes_{module_name}_{imported_module_name}'
-
- if imported_module.nr_virtual_error_handlers != 0:
- handlers = f'sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}'
-
- if imported_module.nr_exceptions != 0:
- exceptions = f'sipImportedExceptions_{module_name}_{imported_module_name}'
-
- sf.write(f' {{"{imported_module.fq_py_name}", {types}, {handlers}, {exceptions}}},\n')
-
- sf.write(
-''' {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR}
-};
-''')
+ backend.g_import_tables(sf)
# Generate the table of sub-class convertors
if nr_subclass_convertors > 0:
@@ -712,11 +454,7 @@ static sipLicenseDef module_license = {{
# Generate any exceptions support.
if bindings.exceptions:
if module.nr_exceptions > 0:
- sf.write(
-f'''
-
-PyObject *sipExportedExceptions_{module_name}[{module.nr_exceptions + 1}];
-''')
+ backend.g_exceptions_defn(sf)
if backend.abi_has_next_exception_handler():
_exception_handler(backend, sf)
@@ -748,10 +486,10 @@ static sipQtAPI qtAPI = {{
sf.write('\n\n')
# Generate the code to create the wrapped module
- backend.g_create_wrapped_module(sf, bindings,
- has_sip_strings,
+ return backend.g_create_wrapped_module(sf, bindings,
+ name_cache_state,
has_external,
- nr_enum_members,
+ enums_state,
has_virtual_error_handlers,
nr_subclass_convertors,
static_variables_state,
@@ -759,8 +497,6 @@ static sipQtAPI qtAPI = {{
init_extenders
)
- return name_cache_list
-
def g_module_docstring(sf, module):
""" Generate the definition of the module's optional docstring. """
@@ -772,7 +508,7 @@ PyDoc_STRVAR(doc_mod_{module.py_name}, "{get_docstring_text(module.docstring)}")
''')
-def g_module_header_file(backend, sf, bindings, py_debug, closure):
+def g_module_header_file(backend, sf, bindings, py_debug, state):
""" Generate the internal module API header. """
spec = backend.spec
@@ -811,33 +547,8 @@ f'''#ifndef _{module_name}API_H
sf.write('\n')
- # Generate references to (potentially) shared strings.
- if closure is not None:
- sf.write(
-'''
-/*
- * Convenient names to refer to various strings defined in this module.
- * Only the class names are part of the public API.
- */
-''')
-
- for cached_name in closure:
- if cached_name.used:
- sf.write(
-f'''#define {backend.cached_name_ref(cached_name, as_nr=True)} {cached_name.offset}
-#define {backend.cached_name_ref(cached_name)} &sipStrings_{module_name}[{cached_name.offset}]
-''')
-
# Generate the SIP API.
- backend.g_sip_api(sf, module_name)
-
- # The name strings.
- if closure is not None:
- sf.write(
-f'''
-/* The strings used by this module. */
-extern const char sipStrings_{module_name}[];
-''')
+ backend.g_sip_api(sf, module_name, state)
_module_api(backend, sf, bindings)
@@ -854,29 +565,20 @@ extern sipExportedModuleDef sipModuleAPI_{module_name};
sf.write(f'extern sipTypeDef *sipExportedTypes_{module_name}[];\n')
for imported_module in module.all_imports:
- imported_module_name = imported_module.py_name
-
_imported_module_api(backend, sf, imported_module)
- if len(imported_module.needed_types) != 0:
- sf.write(f'extern sipImportedTypeDef sipImportedTypes_{module_name}_{imported_module_name}[];\n')
-
- if imported_module.nr_virtual_error_handlers != 0:
- sf.write(f'extern sipImportedVirtErrorHandlerDef sipImportedVirtErrorHandlers_{module_name}_{imported_module_name}[];\n')
-
- if imported_module.nr_exceptions != 0:
- sf.write(f'extern sipImportedExceptionDef sipImportedExceptions_{module_name}_{imported_module_name}[];\n')
-
if pyqt5_supported(spec) or pyqt6_supported(spec):
+ wrapper_type = backend.get_wrapper_type()
+
sf.write(
f'''
-typedef const QMetaObject *(*sip_qt_metaobject_func)(sipSimpleWrapper *, sipTypeDef *);
+typedef const QMetaObject *(*sip_qt_metaobject_func)({wrapper_type}, sipTypeDef *);
extern sip_qt_metaobject_func sip_{module_name}_qt_metaobject;
-typedef int (*sip_qt_metacall_func)(sipSimpleWrapper *, sipTypeDef *, QMetaObject::Call, int, void **);
+typedef int (*sip_qt_metacall_func)({wrapper_type}, sipTypeDef *, QMetaObject::Call, int, void **);
extern sip_qt_metacall_func sip_{module_name}_qt_metacall;
-typedef bool (*sip_qt_metacast_func)(sipSimpleWrapper *, const sipTypeDef *, const char *, void **);
+typedef bool (*sip_qt_metacast_func)({wrapper_type}, const sipTypeDef *, const char *, void **);
extern sip_qt_metacast_func sip_{module_name}_qt_metacast;
''')
@@ -906,6 +608,8 @@ def g_type_init_body(backend, sf, bindings, klass):
# Generate the code that parses the Python arguments and calls the correct
# constructor.
+ signature_nr = 0
+
for ctor in klass.ctors:
if ctor.access_specifier is AccessSpecifier.PRIVATE:
continue
@@ -919,11 +623,13 @@ def g_type_init_body(backend, sf, bindings, klass):
else:
error_flag = old_error_flag = False
- _arg_parser(backend, sf, klass, ctor.py_signature, ctor=ctor)
+ _arg_parser(backend, sf, klass, ctor.py_signature, signature_nr,
+ ctor=ctor)
_ctor_call(backend, sf, bindings, klass, ctor, error_flag,
old_error_flag)
sf.write(' }\n')
+ signature_nr += 1
sf.write(
'''
@@ -1007,16 +713,28 @@ f'''
''')
-def _arg_parser(backend, sf, scope, py_signature, ctor=None, is_method=False,
- overload=None):
- """ Generate the argument variables for a member
- function/constructor/operator.
- """
+def _arg_is_v13_typed_enum(spec, arg):
+ """ Return True if an argument refers to an ABI v13 typed C++11 enum. """
+
+ # Support for small (ie. smaller than an int) typed enums was added to ABI
+ # v13 by adding appropriate casting to an int whenever an enum value is
+ # passed to or from the ABI. The returned value is used to determine if
+ # the casting is necessary. ABI v14 instead passes a pointer to the enum
+ # value (rather than the value itself) which means that it can support enum
+ # types larger than an int and doesn't need any casting. ABU v12 does not
+ # support typed enums.
+
+ return spec.target_abi[0] == 13 and arg.type is ArgumentType.ENUM and arg.definition.enum_base_type is not None
+
+
+def _arg_parser(backend, sf, scope, py_signature, signature_nr, ctor=None,
+ is_method=False, overload=None):
+ """ Generate the argument variables for a callable. """
spec = backend.spec
- # If the scope is just a namespace, then ignore it.
- if isinstance(scope, WrappedClass) and scope.iface_file.type is IfaceFileType.NAMESPACE:
+ # If the scope is a mapped type or a namespace, then ignore it.
+ if isinstance(scope, MappedType) or (isinstance(scope, WrappedClass) and scope.iface_file.type is IfaceFileType.NAMESPACE):
scope = None
# For ABI v13 and later static methods use self for the type object.
@@ -1065,9 +783,36 @@ def _arg_parser(backend, sf, scope, py_signature, ctor=None, is_method=False,
if spec.target_abi >= (14, 0):
args.append('sipModule')
+ if overload is not None:
+ member_name = overload.common.py_name.name
+
+ if scope is None:
+ callable_name = member_name
+ else:
+ if isinstance(scope, WrappedEnum):
+ scope_name = scope.fq_cpp_name
+ else:
+ scope_name = scope.iface_file.fq_cpp_name
+
+ callable_name = scope_name.as_word + '_' + member_name
+ else:
+ assert ctor is not None
+ callable_name = scope.iface_file.fq_cpp_name.as_word
+
+ if overload is not None and overload.common.py_slot is not None:
+ # TODO Add type hints for slots.
+ args.append('SIP_NULLPTR')
+ args.append('&sipPState')
+ else:
+ args.append(f'sipTypeHints_{callable_name}[{signature_nr}]')
+ args.append('sipPStateP')
+
if overload is not None and is_number_slot(overload.common.py_slot):
parser_function = 'sipParsePair'
- args.append('&sipParseErr')
+
+ if spec.target_abi < (14, 0):
+ args.append('&sipParseErr')
+
args.append('sipArg0')
args.append('sipArg1')
@@ -1083,7 +828,10 @@ def _arg_parser(backend, sf, scope, py_signature, ctor=None, is_method=False,
sip_value = 'sipValue'
parser_function = f'sipValue {operator} SIP_NULLPTR && sipParsePair'
- args.append('&sipParseErr')
+
+ if spec.target_abi < (14, 0):
+ args.append('&sipParseErr')
+
args.append('sipName')
args.append(sip_value)
@@ -1125,7 +873,9 @@ def _arg_parser(backend, sf, scope, py_signature, ctor=None, is_method=False,
if is_ka_list:
sf.write(' };\n\n')
- args.append('sipParseErr' if ctor is not None else '&sipParseErr')
+ if spec.target_abi < (14, 0):
+ args.append('sipParseErr' if ctor is not None else '&sipParseErr')
+
args.append('sipArgs')
if spec.target_abi >= (14, 0):
@@ -1133,19 +883,18 @@ def _arg_parser(backend, sf, scope, py_signature, ctor=None, is_method=False,
# The call slot has a traditional signature.
parser_function = 'sipParseKwdArgs'
else:
- parser_function = 'sipParseVectorcallKwdArgs'
+ parser_function = 'sipParseVcKwdArgs'
args.append('sipNrArgs')
else:
parser_function = 'sipParseKwdArgs'
- args.append('sipKwds')
+ args.append('sipKwdNames' if spec.target_abi >= (14, 0) else 'sipKwds')
args.append('sipKwdList' if is_ka_list else 'SIP_NULLPTR')
args.append('sipUnused' if ctor is not None else 'SIP_NULLPTR')
else:
single_arg = not (overload is None or overload.common.py_slot is None or is_multi_arg_slot(overload.common.py_slot))
- args.append('&sipParseErr')
if spec.target_abi >= (14, 0):
if overload is not None and overload.common.py_slot is PySlot.CALL:
@@ -1164,12 +913,15 @@ def _arg_parser(backend, sf, scope, py_signature, ctor=None, is_method=False,
args.append('sipArg')
args.append('SIP_NULLPTR')
else:
- parser_function = 'sipParseVectorcallArgs'
+ parser_function = 'sipParseVcKwdArgs'
args.append('sipArgs')
args.append('sipNrArgs')
- args.append('sipKwds' if is_method else 'SIP_NULLPTR')
+ args.append('sipKwdNames')
+ args.append('SIP_NULLPTR')
+ args.append('SIP_NULLPTR')
else:
parser_function = 'sipParseArgs'
+ args.append('&sipParseErr')
args.append('sipArg' + ('' if single_arg else 's'))
# Generate the format string.
@@ -1491,7 +1243,7 @@ def _argument_variable(backend, sf, scope, arg, arg_nr):
else:
arg.derefs = []
- if arg_is_small_enum(arg):
+ if _arg_is_v13_typed_enum(spec, arg):
arg.type = ArgumentType.INT
use_typename = False
@@ -1522,12 +1274,12 @@ def _argument_variable(backend, sf, scope, arg, arg_nr):
if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and (nr_derefs == 0 or arg.is_reference):
sf.write(f'&{arg_name}def')
else:
- if arg_is_small_enum(arg):
+ if _arg_is_v13_typed_enum(spec, arg):
sf.write('static_cast<int>(')
sf.write(fmt_value_list_as_cpp_expression(spec, arg.default_value))
- if arg_is_small_enum(arg):
+ if _arg_is_v13_typed_enum(spec, arg):
sf.write(')')
sf.write(';\n')
@@ -1598,7 +1350,7 @@ def _call_args(sf, spec, cpp_signature, py_signature):
if nr_derefs == 1:
indirection = '&'
- if arg_is_small_enum(arg):
+ if _arg_is_v13_typed_enum(spec, arg):
prefix = 'static_cast<' + fmt_enum_as_cpp_type(arg.definition) + '>('
suffix = ')'
@@ -1666,21 +1418,12 @@ def _catch(backend, sf, bindings, py_signature, throw_args, release_gil):
_delete_temporaries(backend, sf, py_signature)
if use_handler:
- sf.write(
-''' void *sipExcState = SIP_NULLPTR;
- sipExceptionHandler sipExcHandler;
- std::exception_ptr sipExcPtr = std::current_exception();
-
- while ((sipExcHandler = sipNextExceptionHandler(&sipExcState)) != SIP_NULLPTR)
- if (sipExcHandler(sipExcPtr))
- return SIP_NULLPTR;
-
-''')
+ backend.g_catch_body(sf)
sf.write(
-''' sipRaiseUnknownException();
+f''' {backend.get_raise_unknown_exception()};
return SIP_NULLPTR;
- }
+ }}
''')
@@ -1745,16 +1488,17 @@ def _class_api(backend, sf, klass):
sf.write('\n')
if klass.real_class is None and not klass.is_hidden_namespace:
- sf.write(f'#define {backend.get_type_ref(klass)} {backend.get_class_ref_value(klass)}\n')
+ backend.g_class_api(sf, klass)
- backend.g_enum_macros(sf, scope=klass)
+ _enum_macros(backend, sf, scope=klass)
if not klass.external and not klass.is_hidden_namespace:
klass_name = iface_file.fq_cpp_name.as_word
- sf.write(f'\nextern sipClassTypeDef sipTypeDef_{module_name}_{klass_name};\n')
+ spec_suffix = backend.get_spec_suffix()
+ sf.write(f'\nextern sipClassType{spec_suffix} sipType{spec_suffix}_{module_name}_{klass_name};\n')
-def _class_docstring(sf, spec, bindings, klass):
+def g_class_docstring(sf, spec, bindings, klass):
""" Generate the docstring for a class. """
NEWLINE = '\\n"\n"'
@@ -1792,8 +1536,8 @@ def _class_docstring(sf, spec, bindings, klass):
sf.write(NEWLINE)
# Insert a blank line if any explicit docstring wants to
- # include a signature. This maintains compatibility with
- # previous versions.
+ # include a signature. This maintains compatibility with
+ # previous versions.
if any_implied:
sf.write(NEWLINE)
@@ -1824,10 +1568,16 @@ def _ctor_auto_docstring(sf, spec, bindings, klass, ctor):
""" Generate the automatic docstring for a ctor. """
if bindings.docstrings:
- py_name = fmt_scoped_py_name(klass.scope, klass.py_name.name)
- signature = fmt_signature_as_type_hint(spec, ctor.py_signature,
- need_self=False, exclude_result=True)
- sf.write(py_name + signature)
+ g_ctor_type_hint(sf, spec, bindings, klass, ctor)
+
+
+def g_ctor_type_hint(sf, spec, bindings, klass, ctor):
+ """ Generate the type hint for a ctor. """
+
+ py_name = fmt_scoped_py_name(klass.scope, klass.py_name.name)
+ signature = fmt_signature_as_type_hint(spec, ctor.py_signature,
+ need_self=False, exclude_result=True)
+ sf.write(py_name + signature)
def _ctor_call(backend, sf, bindings, klass, ctor, error_flag, old_error_flag):
@@ -2048,7 +1798,7 @@ def _delete_temporaries(backend, sf, py_signature):
if arg.type is ArgumentType.MAPPED and arg.definition.no_release:
continue
- sf.write(f' sipReleaseType{get_user_state_suffix(spec, arg)}(')
+ sf.write(f' sipReleaseType{get_user_state_suffix(spec, arg)}({backend.get_module_context()}')
if spec.c_bindings or not arg.is_const:
sf.write(arg_name)
@@ -2065,6 +1815,34 @@ def _delete_temporaries(backend, sf, py_signature):
sf.write(');\n')
+def _enum_macros(backend, sf, scope=None, imported_module=None):
+ """ Generate the type macros for enums. """
+
+ spec = backend.spec
+
+ for enum in spec.enums:
+ if enum.fq_cpp_name is None:
+ continue
+
+ # Continue unless the scopes match.
+ if scope is not None:
+ if enum.scope is not scope:
+ continue
+ elif enum.scope is not None:
+ continue
+
+ value = None
+
+ if imported_module is None:
+ if enum.module is spec.module:
+ value = backend.get_enum_ref_value(enum)
+ elif enum.module is imported_module and enum.needed:
+ value = backend.get_enum_ref_value(enum)
+
+ if value is not None:
+ sf.write(f'\n#define {backend.get_type_ref(enum)} {value}\n')
+
+
def _gc_ellipsis(sf, signature):
""" Generate the code to garbage collect any ellipsis argument. """
@@ -2074,12 +1852,6 @@ def _gc_ellipsis(sf, signature):
sf.write(f'\n Py_DECREF(a{last});\n')
-def _get_function_table(members):
- """ Return a sorted list of relevant functions for a namespace. """
-
- return sorted(members, key=lambda m: m.py_name.name)
-
-
def _get_method_table(klass):
""" Return a sorted list of relevant methods (either lazy or non-lazy) for
a class.
@@ -2111,7 +1883,7 @@ def _get_method_table(klass):
if need_member:
members.append(visible_member.member)
- return _get_function_table(members)
+ return get_function_table(members)
def _get_subformat_char(arg):
@@ -2146,32 +1918,6 @@ def _handling_exceptions(bindings, throw_args):
return bindings.exceptions and (throw_args is None or throw_args.arguments is not None)
-def _has_class_docstring(bindings, klass):
- """ Return True if a class has a docstring. """
-
- auto_docstring = False
-
- # Check for any explicit docstrings and remember if there were any that
- # could be automatically generated.
- if klass.docstring is not None:
- return True
-
- for ctor in klass.ctors:
- if ctor.access_specifier is AccessSpecifier.PRIVATE:
- continue
-
- if ctor.docstring is not None:
- return True
-
- if bindings.docstrings:
- auto_docstring = True
-
- if not klass.can_create:
- return False
-
- return auto_docstring
-
-
def _has_optional_args(overload):
""" Return True if an overload has optional arguments. """
@@ -2180,24 +1926,14 @@ def _has_optional_args(overload):
return len(args) != 0 and args[-1].default_value is not None
-def _mapped_type_method_table(backend, sf, bindings, mapped_type):
- """ Generate the sorted table of static methods for a mapped type and
- return the number of entries.
- """
-
- members = _get_function_table(mapped_type.members)
-
- return backend.g_py_method_table(sf, bindings, members, mapped_type)
-
-
def _method_auto_docstring(sf, spec, bindings, overload, is_method):
""" Generate the automatic docstring for a function/method. """
if bindings.docstrings:
- _overload_auto_docstring(sf, spec, overload, is_method=is_method)
+ g_overload_type_hint(sf, spec, overload, is_method=is_method)
-def _method_docstring(sf, spec, bindings, member, overloads, is_method=False):
+def g_method_docstring(sf, spec, bindings, member, overloads, is_method=False):
""" Generate the docstring for all overloads of a function/method. Return
True if the docstring was entirely automatically generated.
"""
@@ -2250,8 +1986,8 @@ def _method_docstring(sf, spec, bindings, member, overloads, is_method=False):
return auto_docstring
-def _overload_auto_docstring(sf, spec, overload, is_method=True):
- """ Generate the docstring for a single API overload. """
+def g_overload_type_hint(sf, spec, overload, is_method=True):
+ """ Generate the type hint for a single API overload. """
need_self = is_method and not overload.is_static
signature = fmt_signature_as_type_hint(spec, overload.py_signature,
@@ -2269,6 +2005,7 @@ def _pyqt_emitters(backend, sf, klass):
for member in klass.members:
in_emitter = False
+ signature_nr = 0
for overload in klass.overloads:
if not (overload.common is member and overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL and _has_optional_args(overload)):
@@ -2293,7 +2030,9 @@ f'''static int emit_{klass_name}_{overload.cpp_name}(void *sipCppV, PyObject *si
# overloaded signal.
sf.write('\n {\n')
- _arg_parser(backend, sf, klass, overload.py_signature)
+ _arg_parser(backend, sf, klass, overload.py_signature,
+ signature_nr)
+ signature_nr += 1
sf.write(
f''' {{
@@ -2388,17 +2127,17 @@ def _pyqt_signal_table_entry(sf, spec, bindings, klass, signal, member_nr):
if signal.docstring is not None:
if signal.docstring.signature is DocstringSignature.PREPENDED:
- _overload_auto_docstring(sf, spec, signal)
+ g_overload_type_hint(sf, spec, signal)
sf.write('\\n')
sf.write(get_docstring_text(signal.docstring))
if signal.docstring.signature is DocstringSignature.APPENDED:
sf.write('\\n')
- _overload_auto_docstring(sf, spec, signal)
+ g_overload_type_hint(sf, spec, signal)
else:
sf.write('\\1')
- _overload_auto_docstring(sf, spec, signal)
+ g_overload_type_hint(sf, spec, signal)
sf.write('", ')
else:
@@ -2478,18 +2217,16 @@ def _try(sf, bindings, throw_args):
''')
-def _types_table(backend, sf, module, needed_enums):
+def _types_table(backend, sf, module, enums_state):
""" Generate the types table for a module. """
- module_name = module.py_name
-
sf.write(
f'''
/*
* This defines each type in this module.
*/
-{backend.get_types_table_prefix()}_{module_name}[] = {{
+{backend.get_types_table_decl(module)}[] = {{
''')
# TODO Does this exclude types defined in another module?
@@ -2500,92 +2237,24 @@ f'''
if klass.external:
sf.write(' 0,\n')
elif not klass.is_hidden_namespace:
- sf.write(f' &sipTypeDef_{module_name}_{klass.iface_file.fq_cpp_name.as_word}.ctd_base,\n')
+ sf.write(f' &{backend.get_spec_for_class(klass)},\n')
elif needed_type.type is ArgumentType.MAPPED:
mapped_type = needed_type.definition
- sf.write(f' &sipTypeDef_{module_name}_{mapped_type.iface_file.fq_cpp_name.as_word}.mtd_base,\n')
+ sf.write(f' &{backend.get_spec_for_mapped_type(mapped_type)},\n')
elif needed_type.type is ArgumentType.ENUM:
enum = needed_type.definition
- enum_index = needed_enums.index(enum)
-
- sf.write(f' &enumTypes[{enum_index}].etd_base,\n')
-
- sf.write('};\n')
-
-
-def _name_cache(sf, spec, name_cache_list):
- """ Generate the name cache definition. Return True if something was
- actually generated.
- """
-
- has_sip_strings = False
-
- for name in name_cache_list:
- if not name.used or name.is_substring:
- continue
-
- if not has_sip_strings:
- has_sip_strings = True
-
- sf.write(
-f'''
-/* Define the strings used by this module. */
-const char sipStrings_{spec.module.py_name}[] = {{
-''')
-
- sf.write(' ')
-
- for ch in name.name:
- sf.write(f"'{ch}', ")
-
- sf.write('0,\n')
- if has_sip_strings:
- sf.write('};\n')
-
- return has_sip_strings
-
-
-def _name_cache_as_list(name_cache):
- """ Return a name cache as a correctly ordered list of CachedName objects.
- """
-
- name_cache_list = []
-
- # Create the list sorted first by descending name length and then
- # alphabetical order.
- for k in sorted(name_cache.keys(), reverse=True):
- name_cache_list.extend(sorted(name_cache[k], key=lambda k: k.name))
-
- # Set the offset into the string pool for every used name.
- offset = 0
+ sf.write(f' &{backend.get_spec_for_enum(enum, enums_state)},\n')
- # Map of suffix to previously processed name
- suffixes = {}
+ elif needed_type.type is ArgumentType.EXCEPTION:
+ exception = needed_type.definition
- for cached_name in name_cache_list:
- if not cached_name.used:
- continue
-
- name_len = len(cached_name.name)
-
- # See if the tail of a previous used name could be used instead.
- prev_name = suffixes.get(cached_name.name)
- if prev_name:
- cached_name.is_substring = True
- cached_name.offset = prev_name.offset + len(prev_name.name) - name_len
-
- if not cached_name.is_substring:
- cached_name.offset = offset
- offset += name_len + 1
+ sf.write(f' &{backend.get_spec_for_exception(exception)},\n')
- for i in range(len(cached_name.name)):
- suffixes.setdefault(cached_name.name[i:], cached_name)
-
- return name_cache_list
+ sf.write('};\n')
def _subclass_convertors(sf, spec, module):
@@ -2644,11 +2313,10 @@ f'''
return nr_subclass_convertors
-def _static_function(backend, sf, bindings, member, scope=None):
+def g_static_function(backend, sf, bindings, member, scope=None):
""" Generate a static function. """
spec = backend.spec
- member_name = member.py_name.name
if scope is None:
overloads = spec.module.overloads
@@ -2657,40 +2325,12 @@ def _static_function(backend, sf, bindings, member, scope=None):
overloads = scope.overloads
scope_py = py_scope(scope)
- if scope_py is not None:
- member_name = scope_py.iface_file.fq_cpp_name.as_word + '_' + member_name
-
sf.write('\n\n')
- # Generate the docstrings.
- if has_method_docstring(bindings, member, overloads):
- sf.write(f'PyDoc_STRVAR(doc_{member_name}, "')
- has_auto_docstring = _method_docstring(sf, spec, bindings, member,
- overloads)
- sf.write('");\n\n')
- else:
- has_auto_docstring = False
-
- if member.no_arg_parser or member.allow_keyword_args:
- kw_fw_decl = ', PyObject *'
- kw_decl = ', PyObject *sipKwds'
- else:
- kw_fw_decl = kw_decl = ''
-
- if scope_py is None:
- if not spec.c_bindings:
- sf.write(f'extern "C" {{static PyObject *func_{member_name}({backend.get_py_method_args(is_impl=False, is_module_fn=True)}{kw_fw_decl});}}\n')
-
- sf.write(f'static PyObject *func_{member_name}({backend.get_py_method_args(is_impl=True, is_module_fn=True)}{kw_decl})\n')
- else:
- # This can only happen with C++ bindings.
- sf.write(f'extern "C" {{static PyObject *meth_{member_name}({backend.get_py_method_args(is_impl=False, is_module_fn=False)}{kw_fw_decl});}}\n')
-
- sf.write(f'static PyObject *meth_{member_name}({backend.get_py_method_args(is_impl=True, is_module_fn=False)}{kw_decl})\n')
-
- sf.write('{\n')
+ state = backend.g_static_function_start(sf, bindings, scope_py, member,
+ overloads)
- need_intro = True
+ signature_nr = 0
for overload in overloads:
if overload.common is not member:
@@ -2700,41 +2340,13 @@ def _static_function(backend, sf, bindings, member, scope=None):
sf.write_code(overload.method_code)
break
- if need_intro:
- if scope is None:
- backend.g_function_support_vars(sf)
- else:
- backend.g_method_support_vars(sf)
-
- sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n')
-
- if scope is None and spec.c_bindings:
- sf.write(
-'''
- (void)sipSelf;
-''')
-
- need_intro = False
+ if signature_nr == 0:
+ backend.g_static_function_support_vars(sf, scope)
- _function_body(backend, sf, bindings, scope, overload)
+ _function_body(backend, sf, bindings, scope, overload, signature_nr)
+ signature_nr += 1
- if not need_intro:
- sf.write(
-f'''
- /* Raise an exception if the arguments couldn't be parsed. */
- sipNoFunction(sipParseErr, {backend.cached_name_ref(member.py_name)}, ''')
-
- if has_auto_docstring:
- sf.write(f'doc_{member_name}')
- else:
- sf.write('SIP_NULLPTR')
-
- sf.write(''');
-
- return SIP_NULLPTR;
-''')
-
- sf.write('}\n')
+ backend.g_static_function_end(sf, state, signature_nr)
def _access_functions(spec, sf, scope=None):
@@ -2859,20 +2471,22 @@ f''' {mapped_type_type} *sipPtr = sipMalloc(sizeof ({mapped_type_type}));
sf.write('}\n\n')
- _convert_to_definitions(sf, spec, mapped_type)
+ _convert_to_definitions(backend, sf, mapped_type)
# Generate the from type convertor.
if mapped_type.convert_from_type_code is not None:
+ context = backend.get_module_context_decl()
+
sf.write('\n\n')
if not spec.c_bindings:
- sf.write(f'extern "C" {{static PyObject *convertFrom_{mapped_type_name}(void *, PyObject *);}}\n')
+ sf.write(f'extern "C" {{static PyObject *convertFrom_{mapped_type_name}({context}void *, PyObject *);}}\n')
xfer = get_use_in_code(mapped_type.convert_from_type_code,
'sipTransferObj', spec=spec)
sf.write(
-f'''static PyObject *convertFrom_{mapped_type_name}(void *sipCppV, PyObject *{xfer})
+f'''static PyObject *convertFrom_{mapped_type_name}({context}void *sipCppV, PyObject *{xfer})
{{
{_mapped_type_from_void(spec, mapped_type_type)};
@@ -2884,115 +2498,23 @@ f'''static PyObject *convertFrom_{mapped_type_name}(void *sipCppV, PyObject *{xf
# Generate the static methods.
for member in mapped_type.members:
- _static_function(backend, sf, bindings, member, scope=mapped_type)
-
- cod_nrmethods = _mapped_type_method_table(backend, sf, bindings,
- mapped_type)
-
- id_int = 'SIP_NULLPTR'
-
- if backend.custom_enums_supported():
- cod_nrenummembers = g_enum_member_table(backend, sf, scope=mapped_type)
- has_ints = False
- needs_namespace = (cod_nrenummembers > 0)
- else:
- if backend.g_mapped_type_int_instances(sf, mapped_type):
- id_int = 'intInstances_' + mapped_type_name
-
- needs_namespace = False
-
- if cod_nrmethods > 0:
- needs_namespace = True
-
- if pyqt6_supported(spec) and mapped_type.pyqt_flags != 0:
- sf.write(f'\n\nstatic pyqt6MappedTypePluginDef plugin_{mapped_type_name} = {{{mapped_type.pyqt_flags}}};\n')
-
- td_plugin_data = '&plugin_' + mapped_type_name
- else:
- td_plugin_data = 'SIP_NULLPTR'
-
- sf.write(
-f'''
-
-sipMappedTypeDef sipTypeDef_{mapped_type.iface_file.module.py_name}_{mapped_type_name} = {{
- {{
-''')
-
- if spec.target_abi < (13, 0):
- sf.write(
-''' -1,
- SIP_NULLPTR,
-''')
-
- flags = []
-
- if mapped_type.handles_none:
- flags.append('SIP_TYPE_ALLOW_NONE')
+ g_static_function(backend, sf, bindings, member, scope=mapped_type)
- if mapped_type.needs_user_state:
- flags.append('SIP_TYPE_USER_STATE')
-
- flags.append('SIP_TYPE_MAPPED')
-
- td_flags = '|'.join(flags)
-
- td_cname = backend.cached_name_ref(mapped_type.cpp_name, as_nr=True)
-
- cod_name = backend.cached_name_ref(mapped_type.py_name, as_nr=True) if needs_namespace else '-1'
- cod_methods = 'SIP_NULLPTR' if cod_nrmethods == 0 else 'methods_' + mapped_type_name
-
- sf.write(
-f''' SIP_NULLPTR,
- {td_flags},
- {td_cname},
- SIP_NULLPTR,
- {td_plugin_data},
- }},
- {{
- {cod_name},
- {{0, 0, 1}},
- {cod_nrmethods}, {cod_methods},
-''')
-
- if backend.custom_enums_supported():
- cod_enummembers = 'SIP_NULLPTR' if cod_nrenummembers == 0 else 'enummembers_' + mapped_type_name
-
- sf.write(
-f''' {cod_nrenummembers}, {cod_enummembers},
-''')
-
- mtd_assign = 'SIP_NULLPTR' if mapped_type.no_assignment_operator else 'assign_' + mapped_type_name
- mtd_array = 'SIP_NULLPTR' if mapped_type.no_default_ctor else 'array_' + mapped_type_name
- mtd_copy = 'SIP_NULLPTR' if mapped_type.no_copy_ctor else 'copy_' + mapped_type_name
- mtd_release = 'SIP_NULLPTR' if mapped_type.no_release else 'release_' + mapped_type_name
- mtd_cto = 'SIP_NULLPTR' if mapped_type.convert_to_type_code is None else 'convertTo_' + mapped_type_name
- mtd_cfrom = 'SIP_NULLPTR' if mapped_type.convert_from_type_code is None else 'convertFrom_' + mapped_type_name
-
- sf.write(
-f''' 0, SIP_NULLPTR,
- {{SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, {id_int}, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR}}
- }},
- {mtd_assign},
- {mtd_array},
- {mtd_copy},
- {mtd_release},
- {mtd_cto},
- {mtd_cfrom}
-}};
-''')
+ backend.g_mapped_type_definition(sf, bindings, mapped_type)
def _class_cpp(backend, sf, bindings, klass, py_debug):
""" Generate the C++ code for a class. """
spec = backend.spec
+ context = backend.get_module_context_decl()
sf.write_code(klass.type_code)
_class_functions(backend, sf, bindings, klass, py_debug)
_access_functions(spec, sf, scope=klass)
if klass.iface_file.type is not IfaceFileType.NAMESPACE:
- _convert_to_definitions(sf, spec, klass)
+ _convert_to_definitions(backend, sf, klass)
# Generate the optional from type convertor.
if klass.convert_from_type_code is not None:
@@ -3003,12 +2525,12 @@ def _class_cpp(backend, sf, bindings, klass, py_debug):
'sipTransferObj', spec=spec)
if not spec.c_bindings:
- sf.write(f'extern "C" {{static PyObject *convertFrom_{name}(void *, PyObject *);}}\n')
+ sf.write(f'extern "C" {{static PyObject *convertFrom_{name}({context}void *, PyObject *);}}\n')
sf.write(
-f'''static PyObject *convertFrom_{name}(void *sipCppV, PyObject *{xfer})
+f'''static PyObject *convertFrom_{name}({context}void *sipCppV, PyObject *{xfer})
{{
- {_class_from_void(spec, klass)};
+ {get_class_from_void(spec, klass)};
''')
@@ -3019,7 +2541,7 @@ f'''static PyObject *convertFrom_{name}(void *sipCppV, PyObject *{xfer})
backend.g_type_definition(sf, bindings, klass, py_debug)
-def _convert_to_definitions(sf, spec, scope):
+def _convert_to_definitions(backend, sf, scope):
""" Generate the "to type" convertor definitions. """
convert_to_type_code = scope.convert_to_type_code
@@ -3027,6 +2549,8 @@ def _convert_to_definitions(sf, spec, scope):
if convert_to_type_code is None:
return
+ spec = backend.spec
+
scope_type = Argument(
ArgumentType.CLASS if isinstance(scope, WrappedClass) else ArgumentType.MAPPED,
definition=scope)
@@ -3050,8 +2574,10 @@ def _convert_to_definitions(sf, spec, scope):
sf.write('\n\n')
+ context = backend.get_module_context_decl()
+
if not spec.c_bindings:
- sf.write(f'extern "C" {{static int convertTo_{scope_name}(PyObject *, void **, int *, PyObject *')
+ sf.write(f'extern "C" {{static int convertTo_{scope_name}({context}PyObject *, void **, int *, PyObject *')
if need_us_arg:
sf.write(', void **')
@@ -3062,7 +2588,7 @@ def _convert_to_definitions(sf, spec, scope):
if sip_cpp_ptr_v != '':
sip_cpp_ptr_v += 'V'
- sf.write(f'static int convertTo_{scope_name}(PyObject *{sip_py}, void **{sip_cpp_ptr_v}, int *{sip_is_err}, PyObject *{xfer}')
+ sf.write(f'static int convertTo_{scope_name}({context}PyObject *{sip_py}, void **{sip_cpp_ptr_v}, int *{sip_is_err}, PyObject *{xfer}')
if need_us_arg:
sf.write(', void **')
@@ -3084,7 +2610,7 @@ def _convert_to_definitions(sf, spec, scope):
sf.write('}\n')
-def _py_slot(backend, sf, bindings, member, scope=None):
+def g_py_slot(backend, sf, bindings, member, scope=None):
""" Generate a Python slot handler for either a class, an enum or an
extender.
"""
@@ -3169,7 +2695,7 @@ def _py_slot(backend, sf, bindings, member, scope=None):
sf.write(f'{slot_decl}{member.py_name.name}({arg_str})\n{{\n')
- backend.g_slot_support_vars(sf)
+ backend.g_slot_support_vars(sf, scope, member)
if member.py_slot is PySlot.CALL and member.no_arg_parser:
for overload in overloads:
@@ -3177,6 +2703,7 @@ def _py_slot(backend, sf, bindings, member, scope=None):
sf.write_code(overload.method_code)
else:
if is_inplace_number_slot(member.py_slot):
+ # TODO Fix for v14.
sf.write(
f''' if (!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject(sip{prefix}_{fq_cpp_name.as_word})))
{{
@@ -3187,29 +2714,25 @@ f''' if (!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject(sip{prefix}_{fq_c
''')
if not is_number_slot(member.py_slot):
- type_ref = backend.get_type_ref(scope)
-
if isinstance(scope, WrappedClass):
cpp_name = scoped_class_name(spec, scope)
+ type_ref = backend.get_type_ref(scope)
sip_module = 'sipModule, ' if spec.target_abi >= (14, 0) else ''
sf.write(
-f''' {cpp_name} *sipCpp = reinterpret_cast<{cpp_name} *>(sipGetCppPtr({sip_module}(sipSimpleWrapper *)sipSelf, {type_ref}));
+f''' {cpp_name} *sipCpp = reinterpret_cast<{cpp_name} *>(sipGetCppPtr({sip_module}{backend.get_wrapper_type_cast()}sipSelf, {type_ref}));
if (!sipCpp)
''')
else:
- cpp_name = fq_cpp_name.as_cpp
- sf.write(
-f''' {cpp_name} sipCpp = static_cast<{cpp_name}>(sipConvertToEnum(sipSelf, {type_ref}));
-
- if (PyErr_Occurred())
-''')
+ backend.g_conversion_to_enum(sf, scope)
sf.write(f' return {ret_value};\n\n')
+ p_state = 'sipPState' if spec.target_abi >= (14, 0) else 'sipParseErr'
+
if has_args:
- sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n')
+ sf.write(f' PyObject *{p_state} = SIP_NULLPTR;\n')
for overload in overloads:
if overload.common is member and overload.is_abstract:
@@ -3217,13 +2740,15 @@ f''' {cpp_name} sipCpp = static_cast<{cpp_name}>(sipConvertToEnum(sipSelf, {t
break
scope_not_enum = not isinstance(scope, WrappedEnum)
+ signature_nr = 0
for overload in overloads:
if overload.common is member:
dereferenced = scope_not_enum and not overload.dont_deref_self
_function_body(backend, sf, bindings, scope, overload,
- dereferenced=dereferenced)
+ signature_nr, dereferenced=dereferenced)
+ signature_nr += 1
if has_args:
if member.py_slot in (PySlot.CONCAT, PySlot.ICONCAT, PySlot.REPEAT, PySlot.IREPEAT):
@@ -3239,15 +2764,17 @@ f'''
else:
if is_rich_compare_slot(member.py_slot):
sf.write(
-'''
- Py_XDECREF(sipParseErr);
+f'''
+ Py_XDECREF({p_state});
''')
elif is_number_slot(member.py_slot) or is_inplace_number_slot(member.py_slot):
+ # TODO Fix this for v14 at least (don't test the value
+ # after the XDECREF).
sf.write(
-'''
- Py_XDECREF(sipParseErr);
+f'''
+ Py_XDECREF({p_state});
- if (sipParseErr == Py_None)
+ if ({p_state} == Py_None)
return SIP_NULLPTR;
''')
@@ -3258,15 +2785,7 @@ f'''
extend_context = f'&sipModuleAPI_{spec.module.py_name}'
# We can only extend class slots. */
- if not isinstance(scope, WrappedClass):
- sf.write(
-'''
- PyErr_Clear();
-
- Py_INCREF(Py_NotImplemented);
- return Py_NotImplemented;
-''')
- else:
+ if isinstance(scope, WrappedClass):
slot_ref = backend.get_slot_ref(member.py_slot)
if is_number_slot(member.py_slot):
@@ -3279,18 +2798,22 @@ f'''
f'''
return sipPySlotExtend({extend_context}, {slot_ref}, {backend.get_type_ref(scope)}, sipSelf, sipArg);
''')
+ else:
+ backend.g_not_implemented(sf)
elif is_inplace_number_slot(member.py_slot):
- sf.write(
-'''
- PyErr_Clear();
-
- Py_INCREF(Py_NotImplemented);
- return Py_NotImplemented;
-''')
+ backend.g_not_implemented(sf)
else:
member_name = '(sipValue != SIP_NULLPTR ? sipName___setattr__ : sipName___delattr__)' if member.py_slot is PySlot.SETATTR else backend.cached_name_ref(member.py_name)
- sf.write(
+ if spec.target_abi >= (14, 0):
+ sf.write(
+f'''
+ sipNoCallable(sipPState, {backend.cached_name_ref(py_name)}, {member_name});
+
+ return {ret_value};
+''')
+ else:
+ sf.write(
f'''
sipNoMethod(sipParseErr, {backend.cached_name_ref(py_name)}, {member_name}, SIP_NULLPTR);
@@ -3326,166 +2849,11 @@ def _class_functions(backend, sf, bindings, klass, py_debug):
visible_member.member, visible_member.scope)
# The slot functions.
- has_getitem_slot = has_setitem_slot = has_delitem_slot = False
- rich_comparison_members = []
-
- for member in klass.members:
- if klass.iface_file.type is IfaceFileType.NAMESPACE:
- _static_function(backend, sf, bindings, member, scope=klass)
- elif member.py_slot is not None:
- _py_slot(backend, sf, bindings, member, scope=klass)
-
- if member.py_slot is PySlot.GETITEM:
- has_getitem_slot = True
-
- if member.py_slot is PySlot.SETITEM:
- has_setitem_slot = True
-
- if member.py_slot is PySlot.DELITEM:
- has_delitem_slot = True
-
- if is_rich_compare_slot(member.py_slot):
- rich_comparison_members.append(member)
-
- if spec.target_abi >= (14, 0):
- # Generate item dispatchers if required.
- if has_getitem_slot:
- sf.write(
-f'''
-
-extern "C" {{static PyObject *slot_{as_word}___sq_item__(PyObject *, Py_ssize_t);}}
-static PyObject *slot_{as_word}___sq_item__(PyObject *self, Py_ssize_t n)
-{{
- PyObject *arg = PyLong_FromSsize_t(n);
- if (arg == NULL)
- return NULL;
-
- PyObject *res = slot_{as_word}___getitem__(self, arg);
- Py_DECREF(arg);
-
- return res;
-}}
-''')
-
- if has_setitem_slot or has_delitem_slot:
- sf.write(
-f'''
-
-extern "C" {{static int slot_{as_word}___mp_ass_subscript__(PyObject *, PyObject *, PyObject *);}}
-static int slot_{as_word}___mp_ass_subscript__(PyObject *self, PyObject *key, PyObject *value)
-{{
- if (value != NULL)
-''')
-
- if has_setitem_slot:
- sf.write(
-f''' return slot_{as_word}___setitem__(self, key, value);
-''')
- else:
- sf.write(
-''' {
- PyErr_SetNone(PyExc_NotImplementedError);
- return -1;
- }
-''')
-
- sf.write('\n');
-
- if has_delitem_slot:
- sf.write(
-f''' return slot_{as_word}___delitem__(self, key);
-''')
- else:
- sf.write(
-''' PyErr_SetNone(PyExc_NotImplementedError);
- return -1;
-''')
-
- sf.write('}\n');
-
- sf.write(
-f'''
-
-extern "C" {{static int slot_{as_word}___sq_ass_item__(PyObject *, Py_ssize_t, PyObject *);}}
-static int slot_{as_word}___sq_ass_item__(PyObject *self, Py_ssize_t index, PyObject *value)
-{{
- PyObject *key = PyLong_FromSsize_t(index);
- if (key == NULL)
- return -1;
-
- int res = slot_{as_word}___mp_ass_subscript__(self, key, value);
-
- Py_DECREF(key);
-
- return res;
-}}
-''');
-
- # Generate a rich comparision dispatcher if required.
- if rich_comparison_members:
- sf.write(
-f'''
-
-extern "C" {{static PyObject *slot_{as_word}___richcompare__(PyObject *, PyObject *, int);}}
-static PyObject *slot_{as_word}___richcompare__(PyObject *self, PyObject *arg, int op)
-{{
- switch (op)
- {{
-''')
-
- for rc_member in rich_comparison_members:
- sf.write(f' case Py_{rc_member.py_slot.name}: return slot_{as_word}_{rc_member.py_name}(self, arg);\n')
-
- sf.write(
-f''' }}
-
- return Py_NewRef(Py_NotImplemented);
-}}
-''')
+ backend.g_slot_implementations(sf, bindings, klass, klass.members)
# The cast function.
if len(klass.superclasses) != 0:
- sf.write(
-f'''
-
-/* Cast a pointer to a type somewhere in its inheritance hierarchy. */
-extern "C" {{static void *cast_{as_word}(void *, const sipTypeDef *);}}
-static void *cast_{as_word}(void *sipCppV, const sipTypeDef *targetType)
-{{
- {_class_from_void(spec, klass)};
-
- if (targetType == {backend.get_type_ref(klass)})
- return sipCppV;
-
-''')
-
- for superclass in klass.superclasses:
- sc_fq_cpp_name = superclass.iface_file.fq_cpp_name
- sc_scope_s = scoped_class_name(spec, superclass)
- sc_type_ref = backend.get_type_ref(superclass)
-
- if len(superclass.superclasses) != 0:
- # Delegate to the super-class's cast function. This will
- # handle virtual and non-virtual diamonds.
- sf.write(
-f''' sipCppV = ((const sipClassTypeDef *){sc_type_ref})->ctd_cast(static_cast<{sc_scope_s} *>(sipCpp), targetType);
- if (sipCppV)
- return sipCppV;
-
-''')
- else:
- # The super-class is a base class and so doesn't have a cast
- # function. It also means that a simple check will do instead.
- sf.write(
-f''' if (targetType == {sc_type_ref})
- return static_cast<{sc_scope_s} *>(sipCpp);
-
-''')
-
- sf.write(
-''' return SIP_NULLPTR;
-}
-''')
+ backend.g_cast_function(sf, klass)
if klass.iface_file.type is not IfaceFileType.NAMESPACE and not spec.c_bindings:
# Generate the release function without compiler warnings.
@@ -3514,7 +2882,7 @@ f''' if (targetType == {sc_type_ref})
sf.write(f'static void release_{as_word}(void *{sip_cpp_v}, int{sip_state})\n{{\n')
if need_cast_ptr:
- sf.write(f' {_class_from_void(spec, klass)};\n\n')
+ sf.write(f' {get_class_from_void(spec, klass)};\n\n')
if len(klass.dealloc_code) != 0:
sf.write_code(klass.dealloc_code)
@@ -3570,7 +2938,7 @@ f''' delete reinterpret_cast<{scoped_class_name(spec, klass)} *>(sipCppV);
sf.write(
f'''static int traverse_{as_word}(void *sipCppV, visitproc sipVisit, void *sipArg)
{{
- {_class_from_void(spec, klass)};
+ {get_class_from_void(spec, klass)};
int sipRes;
''')
@@ -3593,7 +2961,7 @@ f'''static int traverse_{as_word}(void *sipCppV, visitproc sipVisit, void *sipAr
sf.write(
f'''static int clear_{as_word}(void *sipCppV)
{{
- {_class_from_void(spec, klass)};
+ {get_class_from_void(spec, klass)};
int sipRes;
''')
@@ -3632,7 +3000,7 @@ f'''static int clear_{as_word}(void *sipCppV)
sf.write('{\n')
if need_cpp:
- sf.write(f' {_class_from_void(spec, klass)};\n')
+ sf.write(f' {get_class_from_void(spec, klass)};\n')
sf.write(' int sipRes;\n\n')
sf.write_code(code)
@@ -3663,7 +3031,7 @@ f'''static int clear_{as_word}(void *sipCppV)
sf.write('{\n')
if need_cpp:
- sf.write(f' {_class_from_void(spec, klass)};\n')
+ sf.write(f' {get_class_from_void(spec, klass)};\n')
sf.write_code(code)
sf.write('}\n')
@@ -3678,7 +3046,7 @@ f'''static int clear_{as_word}(void *sipCppV)
sf.write(
f'''static PyObject *pickle_{as_word}(void *sipCppV)
{{
- {_class_from_void(spec, klass)};
+ {get_class_from_void(spec, klass)};
PyObject *sipRes;
''')
@@ -3708,7 +3076,7 @@ f'''static int final_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, PyObject
''')
if need_cpp:
- sf.write(f' {_class_from_void(spec, klass)};\n\n')
+ sf.write(f' {get_class_from_void(spec, klass)};\n\n')
sf.write_code(code)
@@ -3810,14 +3178,12 @@ f''' return new {scope_s}(reinterpret_cast<const {scope_s} *>(sipSrc)[sipSrcI
if need_dealloc(spec, bindings, klass):
sf.write('\n\n')
- wrapped_type_type = backend.get_wrapped_type_type()
+ wrapper_type = backend.get_wrapper_type()
if not spec.c_bindings:
- sf.write(f'extern "C" {{static void dealloc_{as_word}({wrapped_type_type});}}\n')
+ sf.write(f'extern "C" {{static void dealloc_{as_word}({wrapper_type});}}\n')
- sf.write(f'static void dealloc_{as_word}({wrapped_type_type}sipSelf)\n{{\n')
-
- backend.g_slot_support_vars(sf)
+ sf.write(f'static void dealloc_{as_word}({wrapper_type}sipSelf)\n{{\n')
if bindings.tracing:
sf.write(f' sipTrace(SIP_TRACE_DEALLOCS, "dealloc_{as_word}()\\n");\n\n')
@@ -3894,7 +3260,9 @@ def _shadow_code(backend, sf, bindings, klass):
if klass.dtor_virtual_catcher_code is not None:
sf.write_code(klass.dtor_virtual_catcher_code)
- sf.write(' sipInstanceDestroyedEx(&sipPySelf);\n}\n')
+ backend.g_cpp_dtor(sf)
+
+ sf.write('}\n')
# The meta methods if required.
if (pyqt5_supported(spec) or pyqt6_supported(spec)) and klass.is_qobject:
@@ -4011,32 +3379,15 @@ def _virtual_catcher(backend, sf, bindings, klass, virtual_overload, virt_nr):
_restore_protections(protection_state)
+ if result is not None and result.type is ArgumentType.VOID and len(result.derefs) == 0:
+ result = None
+
sf.write(
''' sip_gilstate_t sipGILState;
PyObject *sipMeth;
-
''')
- if overload.is_const:
- const_cast_char = 'const_cast<char *>('
- const_cast_sw = 'const_cast<sipSimpleWrapper **>('
- const_cast_tail = ')'
- else:
- const_cast_char = ''
- const_cast_sw = ''
- const_cast_tail = ''
-
- abi_12_8_arg = f'{const_cast_sw}&sipPySelf{const_cast_tail}, ' if spec.target_abi >= (12, 8) else ''
-
- klass_py_name_ref = backend.cached_name_ref(klass.py_name) if overload.is_abstract else 'SIP_NULLPTR'
- member_py_name_ref = backend.cached_name_ref(overload.common.py_name)
-
- sf.write(f' sipMeth = sipIsPyMethod(&sipGILState, {const_cast_char}&sipPyMethods[{virt_nr}]{const_cast_tail}, {abi_12_8_arg}{klass_py_name_ref}, {member_py_name_ref});\n')
-
- # The rest of the common code.
-
- if result is not None and result.type is ArgumentType.VOID and len(result.derefs) == 0:
- result = None
+ backend.g_get_py_reimpl(sf, klass, overload, virt_nr)
sf.write('\n if (!sipMeth)\n')
@@ -4105,7 +3456,7 @@ def _virtual_handler_call(backend, sf, klass, virtual_overload, result):
result_type = fmt_argument_as_cpp_type(spec, overload.cpp_signature.result,
scope=klass.iface_file)
- sf.write(f' extern {result_type} sipVH_{module_name}_{handler.handler_nr}(sip_gilstate_t, sipVirtErrorHandlerFunc, sipSimpleWrapper *, PyObject *')
+ sf.write(f' extern {result_type} sipVH_{module_name}_{handler.handler_nr}({backend.get_module_context_decl()}sip_gilstate_t, sipVirtErrorHandlerFunc, {backend.get_wrapper_type()}, PyObject *')
if len(handler.cpp_signature.args) > 0:
sf.write(', ' + fmt_signature_as_cpp_declaration(spec,
@@ -4162,7 +3513,7 @@ def _virtual_handler_call(backend, sf, klass, virtual_overload, result):
# the handler.
error_handler_ref = f'sipImportedVirtErrorHandlers_{module_name}_{error_handler.module.py_name}[{error_handler.handler_nr}].iveh_handler'
- sf.write(f'sipVH_{module_name}_{handler.handler_nr}(sipGILState, {error_handler_ref}, sipPySelf, sipMeth')
+ sf.write(f'sipVH_{module_name}_{handler.handler_nr}({backend.get_module_context()}sipGILState, {error_handler_ref}, sipPySelf, sipMeth')
for arg_nr, arg in enumerate(overload.cpp_signature.args):
prefix = ''
@@ -4521,7 +3872,7 @@ def _virtual_handler(backend, sf, handler):
sf.write(
f'''
-{result_decl} sipVH_{module.py_name}_{handler.handler_nr}(sip_gilstate_t sipGILState, sipVirtErrorHandlerFunc sipErrorHandler, sipSimpleWrapper *sipPySelf, PyObject *sipMethod''')
+{result_decl} sipVH_{module.py_name}_{handler.handler_nr}({backend.get_module_context_decl()}sip_gilstate_t sipGILState, sipVirtErrorHandlerFunc sipErrorHandler, {backend.get_wrapper_type()}sipPySelf, PyObject *sipMethod''')
if len(handler.cpp_signature.args) > 0:
sf.write(', ' + fmt_signature_as_cpp_definition(spec,
@@ -4660,12 +4011,14 @@ f'''
nr_values += 1
# Call the method.
+ context = backend.get_module_context()
+
if nr_values == 0:
sf.write(
-' sipCallProcedureMethod(sipGILState, sipErrorHandler, sipPySelf, sipMethod, ')
+f' sipCallProcedureMethod({context}sipGILState, sipErrorHandler, sipPySelf, sipMethod, ')
else:
sf.write(
-' PyObject *sipResObj = sipCallMethod(SIP_NULLPTR, sipMethod, ')
+f' PyObject *sipResObj = sipCallMethod({context}SIP_NULLPTR, sipMethod, ')
sf.write(_tuple_builder(backend, handler.py_signature))
@@ -4725,7 +4078,7 @@ f'''
sf.write(f''');
- {return_code}sipParseResultEx({params});
+ {return_code}{backend.get_result_parser()}({backend.get_module_context()}{params});
''')
if result_is_returned:
@@ -5134,27 +4487,14 @@ def _module_api(backend, sf, bindings):
if mapped_type.iface_file.module is module:
backend.g_mapped_type_api(sf, mapped_type)
- no_exceptions = True
-
- for exception in spec.exceptions:
- if exception.iface_file.module is module and exception.exception_nr >= 0:
- if no_exceptions:
- sf.write(
-f'''
-/* The exceptions defined in this module. */
-extern PyObject *sipExportedExceptions_{module_name}[];
-
-''')
-
- no_exceptions = False
-
- sf.write(f'#define sipException_{exception.iface_file.fq_cpp_name.as_word} sipExportedExceptions_{module_name}[{exception.exception_nr}]\n')
+ backend.g_exceptions_decls(sf)
+ _enum_macros(backend, sf)
- backend.g_enum_macros(sf)
+ wrapper_type = backend.get_wrapper_type()
for virtual_error_handler in spec.virtual_error_handlers:
if virtual_error_handler.module is module:
- sf.write(f'\nvoid sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *, sip_gilstate_t);\n')
+ sf.write(f'\nvoid sipVEH_{module_name}_{virtual_error_handler.name}({wrapper_type}, sip_gilstate_t);\n')
def _imported_module_api(backend, sf, imported_module):
@@ -5168,17 +4508,9 @@ def _imported_module_api(backend, sf, imported_module):
if iface_file.module is imported_module:
if iface_file.needed:
- type_ref = backend.get_type_ref(klass)
-
- if iface_file.type is IfaceFileType.NAMESPACE:
- sf.write(f'\n#if !defined({type_ref})')
-
- sf.write(f'\n#define {type_ref} sipImportedTypes_{module_name}_{iface_file.module.py_name}[{iface_file.type_nr}].it_td\n')
+ backend.g_class_api(sf, klass)
- if iface_file.type is IfaceFileType.NAMESPACE:
- sf.write('#endif\n')
-
- backend.g_enum_macros(sf, scope=klass,
+ _enum_macros(backend, sf, scope=klass,
imported_module=imported_module)
for mapped_type in spec.mapped_types:
@@ -5186,9 +4518,9 @@ def _imported_module_api(backend, sf, imported_module):
if iface_file.module is imported_module:
if iface_file.needed:
- sf.write(f'\n#define {backend.get_type_ref(mapped_type)} sipImportedTypes_{module_name}_{iface_file.module.py_name}[{iface_file.type_nr}].it_td\n')
+ backend.g_mapped_type_api(sf, mapped_type)
- backend.g_enum_macros(sf, scope=mapped_type,
+ _enum_macros(backend, sf, scope=mapped_type,
imported_module=imported_module)
for exception in spec.exceptions:
@@ -5196,11 +4528,13 @@ def _imported_module_api(backend, sf, imported_module):
if iface_file.module is imported_module and exception.exception_nr >= 0:
# TODO ABI v14 will get the exception directly from the imported
- # module's state (possibly via an API call) rather than taking a
- # copy of the Python object.
+ # module's state (possibly via an API call) rather than keeping a
+ # reference to the Python object.
sf.write(f'\n#define sipException_{iface_file.fq_cpp_name.as_word} sipImportedExceptions_{module_name}_{iface_file.module.py_name}[{exception.exception_nr}].iexc_object\n')
- backend.g_enum_macros(sf, imported_module=imported_module)
+ _enum_macros(backend, sf, imported_module=imported_module)
+
+ backend.g_imported_module_decls(sf, imported_module)
def _shadow_class_declaration(backend, sf, bindings, klass):
@@ -5291,9 +4625,9 @@ protected:
sf.write(';\n')
sf.write(
-'''
+f'''
public:
- sipSimpleWrapper *sipPySelf;
+ {backend.get_wrapper_type()}sipPySelf;
''')
# The private declarations.
@@ -5424,37 +4758,14 @@ def _member_function(backend, sf, bindings, klass, member, original_klass):
sf.write('\n\n')
- # Generate the docstrings.
- if has_method_docstring(bindings, member, original_klass.overloads):
- sf.write(f'PyDoc_STRVAR(doc_{klass_name}_{member_py_name}, "')
-
- has_auto_docstring = _method_docstring(sf, spec, bindings, member,
- original_klass.overloads,
- is_method=not klass.is_hidden_namespace)
-
- sf.write('");\n\n')
- else:
- has_auto_docstring = False
-
- if spec.target_abi >= (14, 0) or member.no_arg_parser or member.allow_keyword_args:
- kw_fw_decl = ', PyObject *'
- kw_decl = ', PyObject *sipKwds'
- else:
- kw_fw_decl = kw_decl = ''
-
- if not spec.c_bindings:
- sf.write(f'extern "C" {{static PyObject *meth_{klass_name}_{member_py_name}({backend.get_py_method_args(is_impl=False, is_module_fn=False)}{kw_fw_decl});}}\n')
-
- sf.write(f'static PyObject *meth_{klass_name}_{member_py_name}({backend.get_py_method_args(is_impl=True, is_module_fn=False, need_self=need_self, need_args=need_args)}{kw_decl})\n{{\n')
+ state = backend.g_py_method_start(sf, bindings, klass, member,
+ original_klass, need_args, need_self)
if bindings.tracing:
sf.write(f' sipTrace(SIP_TRACE_METHODS, "meth_{klass_name}_{member_py_name}()\\n");\n\n')
if not member.no_arg_parser:
- backend.g_method_support_vars(sf)
-
- if need_args:
- sf.write(' PyObject *sipParseErr = SIP_NULLPTR;\n')
+ backend.g_py_method_support_vars(sf, need_args)
if need_selfarg:
# This determines if we call the explicitly scoped version or the
@@ -5473,15 +4784,7 @@ def _member_function(backend, sf, bindings, klass, member, original_klass):
# In addition, if the type is a derived class then we know that
# there can't be a C++ sub-class that we don't know about so we can
# avoid the vtable.
- #
- # Note that we would like to rename 'sipSelfWasArg' to
- # 'sipExplicitScope' but it is part of the public API.
- if spec.target_abi >= (13, 0):
- sipself_test = f'!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject({backend.get_type_ref(klass)}))'
- else:
- sipself_test = '!sipSelf'
-
- sf.write(f' bool sipSelfWasArg = ({sipself_test} || sipIsDerivedClass((sipSimpleWrapper *)sipSelf));\n')
+ sf.write(f' bool sipSelfWasArg = {backend.get_sipself_test(klass)};\n')
if need_orig_self:
# This is similar to the above but for abstract methods. We allow
@@ -5489,6 +4792,8 @@ def _member_function(backend, sf, bindings, klass, member, original_klass):
# implementation can be put in a mixin and it will all work.
sf.write(' PyObject *sipOrigSelf = sipSelf;\n')
+ signature_nr = 0
+
for overload in original_klass.overloads:
# If we are handling one variant then we must handle them all.
if skip_overload(overload, member, klass, original_klass, want_local=False):
@@ -5501,27 +4806,15 @@ def _member_function(backend, sf, bindings, klass, member, original_klass):
sf.write_code(overload.method_code)
break
- _function_body(backend, sf, bindings, klass, overload, is_method=True,
- original_klass=original_klass)
+ _function_body(backend, sf, bindings, klass, overload, signature_nr,
+ is_method=True, original_klass=original_klass)
+ signature_nr += 1
- if not member.no_arg_parser:
- sip_parse_err = 'sipParseErr' if need_args else 'SIP_NULLPTR'
- klass_py_name_ref = backend.cached_name_ref(klass.py_name)
- member_py_name_ref = backend.cached_name_ref(member.py_name)
- docstring_ref = f'doc_{klass_name}_{member_py_name}' if has_auto_docstring else 'SIP_NULLPTR'
+ backend.g_py_method_end(sf, state, signature_nr)
- sf.write(
-f'''
- sipNoMethod({sip_parse_err}, {klass_py_name_ref}, {member_py_name_ref}, {docstring_ref});
- return SIP_NULLPTR;
-''')
-
- sf.write('}\n')
-
-
-def _function_body(backend, sf, bindings, scope, overload, is_method=False,
- original_klass=None, dereferenced=True):
+def _function_body(backend, sf, bindings, scope, overload, signature_nr,
+ is_method=False, original_klass=None, dereferenced=True):
""" Generate the function calls for a particular overload. """
spec = backend.spec
@@ -5557,11 +4850,11 @@ def _function_body(backend, sf, bindings, scope, overload, is_method=False,
py_signature_adjusted = True
- _arg_parser(backend, sf, scope, py_signature, is_method=is_method,
- overload=overload)
+ _arg_parser(backend, sf, scope, py_signature, signature_nr,
+ is_method=is_method, overload=overload)
elif not is_int_arg_slot(overload.common.py_slot) and not is_zero_arg_slot(overload.common.py_slot):
- _arg_parser(backend, sf, scope, py_signature, is_method=is_method,
- overload=overload)
+ _arg_parser(backend, sf, scope, py_signature, signature_nr,
+ is_method=is_method, overload=overload)
_function_call(backend, sf, bindings, scope, overload, dereferenced,
original_scope)
@@ -5613,7 +4906,7 @@ f''' Py_INCREF(Py_None);
if result is not None and result.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
result_type_ref = backend.get_type_ref(result.definition)
- if overload.transfer is Transfer.TRANSFER_BACK:
+ if overload.transfer is Transfer.TRANSFER_BACK or overload.factory:
result_owner = 'Py_None'
elif overload.transfer is Transfer.TRANSFER:
result_owner = 'sipSelf'
@@ -5621,12 +4914,18 @@ f''' Py_INCREF(Py_None);
result_owner = 'SIP_NULLPTR'
sip_res = get_const_cast(spec, result, 'sipRes')
-
- if is_new_instance or overload.factory:
+ context = backend.get_module_context()
+
+ # Note that this used to test for /Factory/ as well but such a method
+ # can still return a previously wrapped instance if ends up calling a
+ # Python reimplementation of a virtual. In such a case the previous
+ #Â wrapping must be returned as it may include additional
+ # Python-specific behaviour.
+ if is_new_instance:
this_action = action if nr_return_values == 1 else 'PyObject *sipResObj ='
owner = '(PyObject *)sipOwner' if has_owner and overload.factory else result_owner
- sf.write(f' {this_action} sipConvertFromNewType({sip_res}, {result_type_ref}, {owner});\n')
+ sf.write(f' {this_action} sipConvertFromNewType({context}{sip_res}, {result_type_ref}, {owner});\n')
# Shortcut if this is the only value returned.
if nr_return_values == 1:
@@ -5637,7 +4936,7 @@ f''' Py_INCREF(Py_None);
this_action = 'PyObject *sipResObj =' if nr_return_values > 1 or need_xfer else action
owner = 'SIP_NULLPTR' if need_xfer else result_owner
- sf.write(f' {this_action} sipConvertFromType({sip_res}, {result_type_ref}, {owner});\n')
+ sf.write(f' {this_action} sipConvertFromType({context}{sip_res}, {result_type_ref}, {owner});\n')
# Transferring the result of a static overload needs an explicit
# call to sipTransferTo().
@@ -5714,19 +5013,14 @@ f''' Py_INCREF(Py_None);
need_new_instance = _need_new_instance(value)
convertor = 'sipConvertFromNewType' if need_new_instance else 'sipConvertFromType'
+ context = backend.get_module_context()
value_name = get_const_cast(spec, value, value_name)
transfer = 'Py_None' if not need_new_instance and value.transfer is Transfer.TRANSFER_BACK else 'SIP_NULLPTR'
- sf.write(f' {action} {convertor}({value_name}, {backend.get_type_ref(value.definition)}, {transfer});\n')
+ sf.write(f' {action} {convertor}({context}{value_name}, {backend.get_type_ref(value.definition)}, {transfer});\n')
elif value.type is ArgumentType.ENUM:
- if value.definition.fq_cpp_name is not None:
- if not spec.c_bindings:
- value_name = f'static_cast<int>({value_name})'
-
- sf.write(f' {action} sipConvertFromEnum({value_name}, {backend.get_type_ref(value.definition)});\n')
- else:
- sf.write(f' {action} PyLong_FromLong({value_name});\n')
+ sf.write(f' {action} {backend.get_enum_to_py_conversion(value.definition, value_name)};\n')
elif value.type is ArgumentType.ASCII_STRING:
if len(value.derefs) == 0:
@@ -6494,7 +5788,7 @@ def _get_slot_arg(spec, overload, arg_nr):
if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
if len(arg.derefs) == 0:
prefix = '*'
- elif arg_is_small_enum(arg):
+ elif _arg_is_v13_typed_enum(spec, arg):
prefix = 'static_cast<' + fmt_enum_as_cpp_type(arg.definition) + '>('
suffix = ')'
@@ -6648,17 +5942,6 @@ def _arg_name(spec, name, code):
return ''
-def _class_from_void(spec, klass):
- """ Return an assignment statement from a void * variable to a class
- instance variable.
- """
-
- klass_type = scoped_class_name(spec, klass)
- cast = get_type_from_void(spec, klass_type, 'sipCppV')
-
- return f'{klass_type} *sipCpp = {cast}'
-
-
def _mapped_type_from_void(spec, mapped_type_type):
""" Return an assignment statement from a void * variable to a mapped type
variable.
@@ -6712,7 +5995,7 @@ def _exception_handler(backend, sf):
f'''
/* Handle the exceptions defined in this module. */
-bool sipExceptionHandler_{spec.module.py_name}(std::exception_ptr sipExcPtr)
+bool sipExceptionHandler_{spec.module.py_name}({backend.get_module_context_decl()}std::exception_ptr sipExcPtr)
{{
try {{
std::rethrow_exception(sipExcPtr);
diff --git a/sipbuild/generator/outputs/code/utils.py b/sipbuild/generator/outputs/code/utils.py
index 5853871..ce75466 100644
--- a/sipbuild/generator/outputs/code/utils.py
+++ b/sipbuild/generator/outputs/code/utils.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from ...specification import (AccessSpecifier, ArgumentType, CodeBlock,
@@ -10,12 +10,6 @@ from ...specification import (AccessSpecifier, ArgumentType, CodeBlock,
from ..formatters import fmt_argument_as_cpp_type, fmt_class_as_scoped_name
-def arg_is_small_enum(arg):
- """ Return True if an argument refers to a small C++11 enum. """
-
- return arg.type is ArgumentType.ENUM and arg.definition.enum_base_type is not None
-
-
def callable_overloads(member, overloads):
""" An iterator over the non-private and non-signal overloads. """
@@ -53,6 +47,17 @@ def get_class_flags(spec, klass, py_debug):
return '|'.join(flags)
+def get_class_from_void(spec, klass):
+ """ Return an assignment statement from a void * variable to a class
+ instance variable.
+ """
+
+ klass_type = scoped_class_name(spec, klass)
+ cast = get_type_from_void(spec, klass_type, 'sipCppV')
+
+ return f'{klass_type} *sipCpp = {cast}'
+
+
def get_const_cast(spec, type, value):
""" Return a value with an appropriate const_cast to a type. """
@@ -154,7 +159,29 @@ def get_enum_member(spec, enum_member):
scope_s += '::'
- return f'static_cast<int>({scope_s}{enum_member.cpp_name})'
+ return scope_s + enum_member.cpp_name
+
+
+def get_function_table(members):
+ """ Return a sorted list of relevant functions for a namespace. """
+
+ return sorted(members, key=lambda m: m.py_name.name)
+
+
+def get_mapped_type_flags(mapped_type):
+ """ Return the flags for a mapped type. """
+
+ flags = []
+
+ if mapped_type.handles_none:
+ flags.append('SIP_TYPE_ALLOW_NONE')
+
+ if mapped_type.needs_user_state:
+ flags.append('SIP_TYPE_USER_STATE')
+
+ flags.append('SIP_TYPE_MAPPED')
+
+ return '|'.join(flags)
def get_named_value_decl(spec, scope, type, name):
diff --git a/sipbuild/generator/parser/parser_manager.py b/sipbuild/generator/parser/parser_manager.py
index 36c11b1..e857bfb 100644
--- a/sipbuild/generator/parser/parser_manager.py
+++ b/sipbuild/generator/parser/parser_manager.py
@@ -91,6 +91,24 @@ class ParserManager:
self._sip_file = None
self._sip_files = []
+ @property
+ def attributes(self):
+ """ The dict of attribute types, keyed by attribute name, defined in
+ the current scope/module.
+ """
+
+ try:
+ scope_state = self._scope_stack[-1]
+ except IndexError:
+ scope_state = None
+
+ # Return the module attributes if there is no current scope or the
+ # current scope is a hidden namespace.
+ if scope_state is None or (isinstance(scope_state.scope, WrappedClass) and scope_state.scope.is_hidden_namespace):
+ return self.module_state.attributes
+
+ return scope_state.attributes
+
def complete_class(self, p, symbol, annotations, has_body):
""" Complete the definition of the class that is the current scope, pop
it and return it.
@@ -240,7 +258,8 @@ class ParserManager:
self.pop_scope()
# Check the name in the current scope (ie. the class's parent scope).
- self.check_attributes(p, symbol, py_name, ignore=klass)
+ self.check_attributes(p, symbol, py_name, "a class or namespace",
+ ignore=klass)
# Check that external classes have only been declared at the global
# scope.
@@ -615,7 +634,7 @@ class ParserManager:
py_name = cached_name(self.spec,
self.get_py_name(cpp_name, annotations))
- self.check_attributes(p, symbol, py_name)
+ self.check_attributes(p, symbol, py_name, "an enum")
if self.in_main_module:
cached_fq_cpp_name.used = True
@@ -636,8 +655,12 @@ class ParserManager:
# Create the members.
for m_cpp_name, m_py_name, m_no_type_hint in members:
- if not is_scoped:
- self.check_attributes(p, symbol, m_py_name.name)
+ # Check the member name if it is going to be visible in the current
+ # scope.
+ # TODO Also check for ABI v14 and custom enums.
+ if cpp_name is None or (self.spec.target_abi[0] == 12 and not is_scoped):
+ self.check_attributes(p, symbol, m_py_name.name,
+ "an enum member")
w_enum.members.append(
WrappedEnumMember(m_cpp_name, m_py_name, w_enum,
@@ -1004,8 +1027,8 @@ class ParserManager:
"{0} is not a valid {1} annotation".format(name,
context))
- def check_attributes(self, p, symbol, py_name, is_function=False,
- ignore=None):
+ def check_attributes(self, p, symbol, py_name, description,
+ is_function=False, ignore=None):
""" Check that a Python name will not clash with another object in the
same Python scope.
"""
@@ -1014,75 +1037,31 @@ class ParserManager:
if not self.spec.is_strict:
return
- # Report a name clash with something.
- def clash(thing):
- self.parser_error(p, symbol,
- "there is already {0} in scope called '{1}'".format(thing,
- py_name))
-
- # Check the enums.
- if self.spec.enums.by_scope_and_py_name(self.scope, py_name):
- clash("an enum")
-
- if self.spec.enums.by_scope_and_unscoped_member_py_name(self.scope, py_name):
- clash("an enum member")
-
- # Only check the members if this attribute isn't a member because we
- # can handle members with the same name in the same scope.
- if not is_function:
- if self.scope is None:
- members = self.module_state.module.global_functions
- thing = "a function"
- else:
- members = self.scope.members
- thing = "a method"
-
- for md in members:
- if md.py_name.name == py_name:
- clash(thing)
- break
+ # See if there is already an attribute with the same name.
+ attributes = self.attributes
+ detail = attributes.get(str(py_name))
- # There is nothing more to check in mapped types.
- if isinstance(self.scope, MappedType):
+ if detail is None:
+ # Remember the attribute.
+ attributes[str(py_name)] = (description, ignore)
return
- # Check the variables.
- for vd in self.spec.variables:
- if vd.scope is not self.scope:
- continue
+ thing, ignored = detail
- if vd.py_name.name == py_name:
- clash("a variable")
- break
-
- # Check the classes.
- for cd in self.spec.classes:
- if cd.scope is not self.scope:
- continue
+ # Some attributes of the same type need extra handling.
+ if thing == description:
+ # Callables don't clash with other callables.
+ if is_function:
+ return
# A class will have already been added to the scope and this will
# tell us to ignore it.
- if cd is ignore:
- continue
-
- if cd.external:
- continue
-
- if cd.py_name.name == py_name:
- clash("a class or namespace")
+ if ignored is ignore:
+ return
- if self.scope is None:
- # Check the exceptions.
- for xd in self.spec.exceptions:
- if xd.py_name is not None and xd.py_name == py_name:
- clash("an exception")
- break
- else:
- # Check the properties.
- for pd in self.scope.properties:
- if pd.name.name == py_name:
- clash("a property")
- break
+ # Report the name clash.
+ self.parser_error(p, symbol,
+ f"there is already {thing} in scope called '{py_name}'")
def cpp_only(self, p, symbol, feature):
""" Check that a C++ feature isn't being used in a C module. """
@@ -1805,7 +1784,7 @@ class ParserManager:
if self.scope is not None and self.scope.iface_file.type is IfaceFileType.NAMESPACE:
variable.is_static = True
- self.check_attributes(p, symbol, variable.py_name)
+ self.check_attributes(p, symbol, variable.py_name, "a variable")
def _add_auto_slot(self, p, symbol, annotations, py_name, py_signature,
cpp_signature, method_code):
@@ -1989,7 +1968,8 @@ class ParserManager:
py_name = '__setattr__'
# Check for name clashes.
- self.check_attributes(p, symbol, py_name, is_function=True)
+ self.check_attributes(p, symbol, py_name, "a callable",
+ is_function=True)
# Create a new member if necessary.
no_arg_parser = annotations.get('NoArgParser', False)
@@ -2201,6 +2181,7 @@ class ModuleState:
self.module = module
self.sip_file = sip_file
+ self.attributes = {}
self.all_raise_py_exception = False
self.auto_py_name_rules = []
self.call_super_init = None
@@ -2217,6 +2198,8 @@ class ScopeState:
self.scope = scope
self.access_specifier = access_specifier
+
+ self.attributes = {}
self.pyqt_method_specifier = None
diff --git a/sipbuild/generator/parser/python_exceptions.py b/sipbuild/generator/parser/python_exceptions.py
index acc1dd0..178560b 100644
--- a/sipbuild/generator/parser/python_exceptions.py
+++ b/sipbuild/generator/parser/python_exceptions.py
@@ -1,77 +1,78 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2024 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
# The builtin Python exceptions across all supported versions of Python.
PYTHON_EXCEPTIONS = (
'BaseException',
+ 'BaseExceptionGroup',
'Exception',
- 'StopIteration',
- 'GeneratorExit',
- 'ArithmeticError',
- 'LookupError',
- 'StandardError', # Python v2.
+ 'ArithmeticError',
'AssertionError',
'AttributeError',
+ 'BlockingIOError',
+ 'BrokenPipeError',
'BufferError',
+ 'ChildProcessError',
+ 'ConnectionAbortedError',
+ 'ConnectionError',
+ 'ConnectionRefusedError',
+ 'ConnectionResetError',
'EOFError',
+ 'FileExistsError',
+ 'FileNotFoundError',
'FloatingPointError',
- 'OSError',
+ 'GeneratorExit',
+ 'ImportCycleError',
'ImportError',
+ 'IndentationError',
'IndexError',
+ 'InterruptedError',
+ 'IsADirectoryError',
'KeyError',
'KeyboardInterrupt',
+ 'LookupError',
'MemoryError',
+ 'ModuleNotFoundError',
'NameError',
+ 'NotADirectoryError',
+ 'NotImplementedError',
+ 'OSError',
'OverflowError',
+ 'PermissionError',
+ 'ProcessLookupError',
+ 'PythonFinalizationError',
+ 'RecursionError',
+ 'ReferenceError',
'RuntimeError',
- 'NotImplementedError',
+ 'StopAsyncIteration',
+ 'StopIteration',
'SyntaxError',
- 'IndentationError',
- 'TabError',
- 'ReferenceError',
'SystemError',
'SystemExit',
+ 'TabError',
+ 'TimeoutError',
'TypeError',
'UnboundLocalError',
- 'UnicodeError',
- 'UnicodeEncodeError',
'UnicodeDecodeError',
+ 'UnicodeEncodeError',
+ 'UnicodeError',
'UnicodeTranslateError',
'ValueError',
'ZeroDivisionError',
- 'EnvironmentError', # Python v2.
- 'IOError', # Python v2.
- 'WindowsError', # Python v2.
- 'VMSError', # Python v2.
-
- 'BlockingIOError',
- 'BrokenPipeError',
- 'ChildProcessError',
- 'ConnectionError',
- 'ConnectionAbortedError',
- 'ConnectionRefusedError',
- 'ConnectionResetError',
- 'FileExistsError',
- 'FileNotFoundError',
- 'InterruptedError',
- 'IsADirectoryError',
- 'NotADirectoryError',
- 'PermissionError',
- 'ProcessLookupError',
- 'TimeoutError',
'Warning',
- 'UserWarning',
+ 'BytesWarning',
'DeprecationWarning',
- 'PendingDeprecationWarning',
- 'SyntaxWarning',
- 'RuntimeWarning',
+ 'EncodingWarning',
'FutureWarning',
'ImportWarning',
- 'UnicodeWarning',
- 'BytesWarning',
+ 'PendingDeprecationWarning',
'ResourceWarning',
+ 'RuntimeWarning',
+ 'SyntaxWarning',
+ 'UnicodeWarning',
+ 'UserWarning',
)
diff --git a/sipbuild/generator/parser/rules.py b/sipbuild/generator/parser/rules.py
index 7f3993d..9d69969 100644
--- a/sipbuild/generator/parser/rules.py
+++ b/sipbuild/generator/parser/rules.py
@@ -1338,7 +1338,7 @@ def p_property(p):
"a name must be specified for %Property")
return
- pm.check_attributes(p, 4, name)
+ pm.check_attributes(p, 4, name, "a property")
name = cached_name(pm.spec, name)
if pm.in_main_module:
@@ -2415,7 +2415,7 @@ def p_exception(p):
cpp_name = p[2]
py_name = pm.get_py_name(cpp_name.base_name, p[4])
- pm.check_attributes(p, 2, py_name)
+ pm.check_attributes(p, 2, py_name, "an exception")
builtin_base, defined_base = p[3]
raise_code = p[6].get('%RaiseCode')
diff --git a/sipbuild/generator/resolver/resolver.py b/sipbuild/generator/resolver/resolver.py
index c226801..b8deb24 100644
--- a/sipbuild/generator/resolver/resolver.py
+++ b/sipbuild/generator/resolver/resolver.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
@@ -13,9 +13,10 @@ from ..python_slots import (is_hash_return_slot, is_int_return_slot,
is_void_return_slot, is_zero_arg_slot)
from ..scoped_name import ScopedName
from ..specification import (AccessSpecifier, Argument, ArgumentType,
- ArrayArgument, ClassKey, Constructor, IfaceFileType, IndexedClassList, MappedType,
- Member, PyQtMethodSpecifier, PySlot, Signature, Transfer, ValueType,
- VirtualHandler, VirtualOverload, VisibleMember, WrappedClass)
+ ArrayArgument, ClassKey, Constructor, EnumBaseType, IfaceFileType,
+ IndexedClassList, MappedType, Member, PyQtMethodSpecifier, PySlot,
+ Signature, Transfer, ValueType, VirtualHandler, VirtualOverload,
+ VisibleMember, WrappedClass)
from ..templates import (encoded_template_name, same_template_signature,
template_code, template_code_blocks, template_expansions)
from ..utils import (append_iface_file, argument_as_str, cached_name,
@@ -159,15 +160,7 @@ def resolve(spec, modules):
if exception_mod is spec.module:
append_iface_file(spec.module.used, exception.iface_file)
- # Skip those that don't require a Python exception object to be
- # created.
- if exception.iface_file.type is not IfaceFileType.EXCEPTION:
- continue
-
- if exception.builtin_base_exception is None and exception.defined_base_exception is None:
- continue
-
- if exception_mod is spec.module or exception.needed:
+ if _exception_needed(spec, exception):
exception.exception_nr = exception_mod.nr_exceptions
exception_mod.nr_exceptions += 1
@@ -365,6 +358,10 @@ def _set_all_imports(mod, error_log, seen=None):
seen.remove(mod)
+ # Set the module's number, ie. it's index in the all_imports list.
+ for nr, imported_mod in enumerate(mod.all_imports):
+ imported_mod.module_nr = nr
+
def _move_main_module_casts_slots(spec, error_log):
""" Move the casts and slots to the correct place for a main module (ie.
@@ -951,6 +948,14 @@ _ENUM_BASE_TYPES = (
ArgumentType.BOOL,
)
+# ABI v14 also supports integer base types larger than int.
+_ENUM_BASE_TYPES_V14 = (
+ ArgumentType.LONG,
+ ArgumentType.ULONG,
+ ArgumentType.LONGLONG,
+ ArgumentType.ULONGLONG,
+)
+
def _resolve_enums(spec, error_log):
""" Resolve the base types for all the enums. """
@@ -958,13 +963,26 @@ def _resolve_enums(spec, error_log):
base_type = enum.enum_base_type
if base_type is None:
+ # There is no explicit C++ base type so if the Python base type
+ # implies it has unsigned value then default the C++ base type to
+ # unsigned.
+ if enum.base_type is EnumBaseType.UINT_ENUM:
+ enum.enum_base_type = Argument(ArgumentType.UINT)
+
continue
_resolve_type(spec, enum.module, enum.scope, base_type, error_log)
- # The current ABI implementations only support enums no larger than an
- # int.
- if base_type.type not in _ENUM_BASE_TYPES or len(base_type.derefs) != 0:
+ if len(base_type.derefs) != 0:
+ bad_base_type = True
+ elif base_type.type in _ENUM_BASE_TYPES:
+ bad_base_type = False
+ elif spec.target_abi >= (14, 0) and base_type.type in _ENUM_BASE_TYPES_V14:
+ bad_base_type = False
+ else:
+ bad_base_type = True
+
+ if bad_base_type:
error_log.log(f"unsupported enum base type",
source_location=base_type.source_location)
@@ -2162,13 +2180,8 @@ def _create_sorted_numbered_types(spec, mod, error_log):
if mod is spec.module or klass.iface_file.needed:
if not klass.is_hidden_namespace:
- # For ABI v14 and later the sip module searches this table
- # using the Python name (as part of attribute lookup). For
- # earlier versions it uses the C/C++ name (for sipFindType()).
- key_name = klass.py_name if spec.target_abi >= (14, 0) else klass.iface_file.cpp_name
-
mod.needed_types.append(Argument(ArgumentType.CLASS,
- definition=klass, name=key_name))
+ definition=klass, name=klass.iface_file.cpp_name))
for mapped_type in spec.mapped_types:
if mapped_type.iface_file.module is not mod:
@@ -2189,14 +2202,20 @@ def _create_sorted_numbered_types(spec, mod, error_log):
mod.needed_types.append(Argument(ArgumentType.ENUM,
definition=enum, name=enum.cached_fq_cpp_name))
+ # ABI v14 includes exceptions in the list of needed types.
+ if spec.target_abi >= (14, 0):
+ for exception in spec.exceptions:
+ if _exception_needed(spec, exception):
+ mod.needed_types.append(Argument(ArgumentType.EXCEPTION,
+ definition=exception,
+ name=exception.iface_file.cpp_name))
+
# Sort the list and assign type numbers.
mod.needed_types.sort(key=lambda t: t.name.name)
- needed_type_nr = 0
-
- for needed_type in mod.needed_types:
+ for type_nr, needed_type in enumerate(mod.needed_types):
if needed_type.type is ArgumentType.CLASS:
- needed_type.definition.iface_file.type_nr = needed_type_nr
+ needed_type.definition.iface_file.type_nr = type_nr
# If we find a class called QObject, assume it's Qt.
if needed_type.name.name == 'QObject':
@@ -2207,12 +2226,26 @@ def _create_sorted_numbered_types(spec, mod, error_log):
spec.pyqt_qobject = needed_type.definition
elif needed_type.type is ArgumentType.MAPPED:
- needed_type.definition.iface_file.type_nr = needed_type_nr
+ needed_type.definition.iface_file.type_nr = type_nr
elif needed_type.type is ArgumentType.ENUM:
- needed_type.definition.type_nr = needed_type_nr
+ needed_type.definition.type_nr = type_nr
+
+ elif needed_type.type is ArgumentType.EXCEPTION:
+ needed_type.definition.iface_file.type_nr = type_nr
+
+
+def _exception_needed(spec, exception):
+ """ Return True if an exception is needed by the module. """
+
+ # Skip those that don't require a Python exception object to be created.
+ if exception.iface_file.type is not IfaceFileType.EXCEPTION:
+ return False
+
+ if exception.builtin_base_exception is None and exception.defined_base_exception is None:
+ return False
- needed_type_nr += 1
+ return exception.iface_file.module is spec.module or exception.needed
def _check_properties(klass, error_log):
diff --git a/sipbuild/generator/specification.py b/sipbuild/generator/specification.py
index c81bc76..0843c99 100644
--- a/sipbuild/generator/specification.py
+++ b/sipbuild/generator/specification.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from dataclasses import dataclass, field
@@ -207,6 +207,10 @@ class ArgumentType(Enum):
# A Py_hash_t.
HASH = 57
+ # An exception. This is only used internally (exceptions can't be passed
+ # as arguments).
+ EXCEPTION = 58
+
class ArrayArgument(Enum):
""" The array support provided by an argument. """
@@ -262,7 +266,7 @@ class DocstringSignature(Enum):
class EnumBaseType(Enum):
- """ The different base types fo an enum. """
+ """ The different base types of an enum. """
# enum.Enum
ENUM = auto()
@@ -915,6 +919,11 @@ class MappedType:
# The type hints.
type_hints: 'TypeHints|None' = None
+ def __hash__(self):
+ """ Reimplemented so a MappedType object can be used as a dict key. """
+
+ return id(self)
+
@dataclass
class MappedTypeTemplate:
@@ -1025,6 +1034,9 @@ class Module:
# The %ModuleHeaderCode.
module_header_code: list[CodeBlock] = field(default_factory=list)
+ # The number of the module. (resolver)
+ module_nr: int = -1
+
# The module's support for multiple interpreters.
multi_interpreter_support: MultiInterpreterSupport = MultiInterpreterSupport.NOT_SUPPORTED
@@ -1698,7 +1710,8 @@ class WrappedClass:
visible_members: list[VisibleMember] = field(default_factory=list)
def __hash__(self):
- """ Reimplemented so an Argument object can be used as a dict key. """
+ """ Reimplemented so a WrappedClass object can be used as a dict key.
+ """
return id(self)
diff --git a/sipbuild/module/source/13/11/sip.h.in b/sipbuild/module/source/13/11/sip.h.in
index fe8a89b..ac8e115 100644
--- a/sipbuild/module/source/13/11/sip.h.in
+++ b/sipbuild/module/source/13/11/sip.h.in
@@ -39,7 +39,7 @@ extern "C" {
/* The version of the ABI. */
#define SIP_ABI_MAJOR_VERSION 13
#define SIP_ABI_MINOR_VERSION 11
-#define SIP_MODULE_PATCH_VERSION 0
+#define SIP_MODULE_PATCH_VERSION 1
/*
diff --git a/sipbuild/module/source/13/11/sip_core.c b/sipbuild/module/source/13/11/sip_core.c
index 80d0c6d..69162a7 100644
--- a/sipbuild/module/source/13/11/sip_core.c
+++ b/sipbuild/module/source/13/11/sip_core.c
@@ -8432,7 +8432,6 @@ static void *findSlot(PyObject *self, sipPySlotType st)
PyTypeObject *py_type = Py_TYPE(self);
/* See if it is a wrapper. */
- /* TODO: will this always be TRUE? */
if (PyObject_TypeCheck((PyObject *)py_type, &sipWrapperType_Type))
{
const sipClassTypeDef *ctd;
@@ -8441,6 +8440,19 @@ static void *findSlot(PyObject *self, sipPySlotType st)
slot = findSlotInClass(ctd, st);
}
+ else
+ {
+ sipEnumTypeDef *etd;
+
+ /* If it is not a wrapper then it must be an enum. */
+ etd = (sipEnumTypeDef *)sip_enum_get_generated_type(
+ (PyObject *)py_type);
+
+ assert(etd != NULL);
+ assert(etd->etd_pyslots != NULL);
+
+ slot = findSlotInSlotList(etd->etd_pyslots, st);
+ }
return slot;
}
diff --git a/test/class_attrs/test_class_attrs.py b/test/class_attrs/test_class_attrs.py
index 4193175..4be6c92 100644
--- a/test/class_attrs/test_class_attrs.py
+++ b/test/class_attrs/test_class_attrs.py
@@ -1,11 +1,14 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
import pytest
+# TODO Expand the tests to include attributes that are instances of Python
+# sub-classes of wrapped types. Specifically that the objects don't get
+# garbage collected prematurely.
@pytest.fixture
def klass(module):
""" This is a fixture that returns an instance of Klass. """
diff --git a/test/class_callables/test_class_callables.py b/test/class_callables/test_class_callables.py
index d7cfe7b..771687d 100644
--- a/test/class_callables/test_class_callables.py
+++ b/test/class_callables/test_class_callables.py
@@ -1,15 +1,11 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
import pytest
-# TODO: Needs in-development v14 support.
-cfg_disabled_for = [14]
-
-
@pytest.fixture
def klass(module):
""" This is a fixture that returns an instance of Klass. """
diff --git a/test/conftest.py b/test/conftest.py
index 30d6407..749ad67 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
import glob
@@ -102,6 +102,9 @@ def package(request):
module_names = [package]
+ # Remove any previous package directory.
+ shutil.rmtree(os.path.join(test_dir, package), ignore_errors=True)
+
# Build each module in the package.
for sip_file in _get_sip_files(test_dir):
module_name = _build_test_module(sip_file, test_dir, abi_version,
@@ -184,9 +187,7 @@ minimum-macos-version = "10.9"
sip-files-dir = ".."
"""
-_SIP_MODULE = """
-sip-module = "{sip_module}"
-"""
+_SIP_MODULE = 'sip-module = "{sip_module}"\n'
def _build_module(module_name, package, build_args, src_dir, test_dir,
@@ -261,6 +262,9 @@ def _build_test_module(sip_file, test_dir, abi_version, package, exceptions,
os.mkdir(build_dir)
+ # See if there are any .h files.
+ has_h_files = (len(glob.glob(os.path.join(test_dir, '*.h'))) > 0)
+
# Create a pyproject.toml file.
pyproject_toml = os.path.join(build_dir, 'pyproject.toml')
@@ -274,16 +278,22 @@ def _build_test_module(sip_file, test_dir, abi_version, package, exceptions,
_SIP_MODULE.format(
sip_module=_get_full_module_name(package)))
- if exceptions or tags is not None:
+ if exceptions or has_h_files or tags is not None:
f.write(f'\n[tool.sip.bindings.{module_name}]\n')
if exceptions:
f.write('exceptions = true\n')
+ if has_h_files:
+ f.write('include-dirs = [".."]\n')
+
if tags is not None:
tags_s = ', '.join([f'"{t}"' for t in tags])
f.write(f'tags = [{tags_s}]\n')
+ # Configure the C++11 support.
+ os.environ['CXXFLAGS'] = '-std=c++11'
+
# Build and move the test module.
_build_module(module_name, package,
['-m', 'sipbuild.tools.build', '--verbose'], build_dir, test_dir,
diff --git a/test/imported_exceptions/handler_module.sip b/test/imported_exceptions/handler_module.sip
index 21b9a48..7d5e948 100644
--- a/test/imported_exceptions/handler_module.sip
+++ b/test/imported_exceptions/handler_module.sip
@@ -1,7 +1,7 @@
// The SIP implementation of the handler_module test module.
-%Module(name=pkg.handler_module)
+%Module(name=imported_exceptions.handler_module)
%Exception std::exception(SIP_Exception) /PyName=StdException, Default/
@@ -12,8 +12,15 @@
%RaiseCode
const char *detail = sipExceptionRef.what();
+
SIP_BLOCK_THREADS
+
+#if SIP_ABI_MAJOR_VERSION >= 14
+ PyErr_SetString((PyObject *)sipGetPyType(sipModule, sipTypeID_std_exception), detail);
+#else
PyErr_SetString(sipException_std_exception, detail);
+#endif
+
SIP_UNBLOCK_THREADS
%End
};
diff --git a/test/imported_exceptions/test_imported_exceptions.py b/test/imported_exceptions/test_imported_exceptions.py
index 62687e8..1f457a1 100644
--- a/test/imported_exceptions/test_imported_exceptions.py
+++ b/test/imported_exceptions/test_imported_exceptions.py
@@ -1,16 +1,13 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
import pytest
-# TODO
-cfg_disabled_for = [14]
-
cfg_exceptions = True
-cfg_package = 'pkg'
+cfg_package = 'imported_exceptions'
def test_imported_exceptions(package):
diff --git a/test/imported_exceptions/thrower_module.sip b/test/imported_exceptions/thrower_module.sip
index 7fb3242..5706abf 100644
--- a/test/imported_exceptions/thrower_module.sip
+++ b/test/imported_exceptions/thrower_module.sip
@@ -1,7 +1,7 @@
// The SIP implementation of the thrower_module test module.
-%Module(name=pkg.thrower_module)
+%Module(name=imported_exceptions.thrower_module)
%Import handler_module.sip
diff --git a/test/imports/base_class.h b/test/imports/base_class.h
new file mode 100644
index 0000000..26584fd
--- /dev/null
+++ b/test/imports/base_class.h
@@ -0,0 +1,20 @@
+// The declaration of the BaseClass test class defined in the core_module test
+// module.
+
+
+#if !defined(_BASE_CLASS_H)
+#define _BASE_CLASS_H
+
+class BaseClass
+{
+public:
+ BaseClass() {}
+};
+
+
+namespace NameSpace
+{
+ int get_core_value();
+};
+
+#endif
diff --git a/test/imports/core_module.sip b/test/imports/core_module.sip
new file mode 100644
index 0000000..f482b9a
--- /dev/null
+++ b/test/imports/core_module.sip
@@ -0,0 +1,32 @@
+// The SIP implementation of the core_module test module.
+
+
+%Module(name=imports.core_module)
+
+
+class BaseClass
+{
+%TypeHeaderCode
+#include "base_class.h"
+%End
+
+public:
+ BaseClass();
+};
+
+
+%ModuleCode
+int NameSpace::get_core_value()
+{
+ return 10;
+}
+%End
+
+namespace NameSpace
+{
+%TypeHeaderCode
+#include "base_class.h"
+%End
+
+ int get_core_value();
+};
diff --git a/test/imports/extras_module.sip b/test/imports/extras_module.sip
new file mode 100644
index 0000000..4569618
--- /dev/null
+++ b/test/imports/extras_module.sip
@@ -0,0 +1,46 @@
+// The SIP implementation of the extras_module test module.
+
+
+%Module(name=imports.extras_module)
+
+%Import core_module.sip
+
+
+%ModuleHeaderCode
+#include "base_class.h"
+
+class Subclass : public BaseClass
+{
+public:
+ Subclass() {}
+};
+
+namespace NameSpace
+{
+ int get_extras_value();
+};
+%End
+
+
+class Subclass : public BaseClass
+{
+public:
+ Subclass();
+};
+
+
+%ModuleCode
+int NameSpace::get_extras_value()
+{
+ return 20;
+}
+%End
+
+namespace NameSpace
+{
+%TypeHeaderCode
+#include "base_class.h"
+%End
+
+ int get_extras_value();
+};
diff --git a/test/imports/test_imports.py b/test/imports/test_imports.py
new file mode 100644
index 0000000..1f930c8
--- /dev/null
+++ b/test/imports/test_imports.py
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
+
+
+import pytest
+
+
+cfg_package = 'imports'
+
+
+def test_namespace_extenders(package):
+ assert package.core_module.NameSpace.get_core_value() == 10
+ assert package.core_module.NameSpace.get_extras_value() == 20
+
+ with pytest.raises(AttributeError):
+ package.extras_module.NameSpace
+
+def test_superclasses(package):
+ assert issubclass(package.extras_module.Subclass,
+ package.core_module.BaseClass)
diff --git a/test/int_convertors/test_int_convertors.py b/test/int_convertors/test_int_convertors.py
index 874fa37..3cc1f76 100644
--- a/test/int_convertors/test_int_convertors.py
+++ b/test/int_convertors/test_int_convertors.py
@@ -6,10 +6,6 @@
import pytest
-# TODO Needs support for virtuals.
-cfg_disabled_for = [14]
-
-
@pytest.fixture(scope='module')
def bool_value(module):
""" A fixture for creating a test sub-class of IntConvertors that
diff --git a/test/mapped_type_attrs/mapped_type_attrs_module.sip b/test/mapped_type_attrs/mapped_type_attrs_module.sip
new file mode 100644
index 0000000..6c50ba4
--- /dev/null
+++ b/test/mapped_type_attrs/mapped_type_attrs_module.sip
@@ -0,0 +1,68 @@
+// The SIP implementation of the mapped_type_attrs_module test module.
+
+
+%Module(name=mapped_type_attrs_module)
+
+
+%MappedType EnumAttrs
+{
+ enum MTEnum {
+ MTEnumMember
+ };
+
+%ConvertFromTypeCode
+ (void)sipCpp;
+
+ Py_INCREF(Py_None);
+
+ return Py_None;
+%End
+
+%ConvertToTypeCode
+ if (sipIsErr == NULL)
+ return 1;
+
+ return sipGetState(sipTransferObj);
+%End
+};
+
+
+%ModuleHeaderCode
+class EnumAttrs
+{
+public:
+ enum MTEnum {
+ MTEnumMember = 10
+ };
+};
+%End
+
+
+%MappedType Callables
+{
+ static int callable();
+
+%ConvertFromTypeCode
+ (void)sipCpp;
+
+ Py_INCREF(Py_None);
+
+ return Py_None;
+%End
+
+%ConvertToTypeCode
+ if (sipIsErr == NULL)
+ return 1;
+
+ return sipGetState(sipTransferObj);
+%End
+};
+
+
+%ModuleHeaderCode
+class Callables
+{
+public:
+ static int callable() {return 42;}
+};
+%End
diff --git a/test/mapped_type_attrs/test_mapped_type_attrs.py b/test/mapped_type_attrs/test_mapped_type_attrs.py
new file mode 100644
index 0000000..489b571
--- /dev/null
+++ b/test/mapped_type_attrs/test_mapped_type_attrs.py
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
+
+
+from sys import getrefcount
+
+import pytest
+
+
+def test_enum_attrs(module, abi_version):
+ """ Test a mapped typed containing an enum. """
+
+ member = module.EnumAttrs.MTEnum.MTEnumMember
+ value = member.value if abi_version >= 13 else member
+ assert value == 10
+
+def test_callables(module):
+ """ Test a mapped typed containing a callable. """
+
+ assert module.Callables.callable() == 42
diff --git a/test/mixins/mixins_module.sip b/test/mixins/mixins_module.sip
index de0e650..1d65d89 100644
--- a/test/mixins/mixins_module.sip
+++ b/test/mixins/mixins_module.sip
@@ -63,19 +63,18 @@ public:
*sipUnused = dict;
}
- PyObject *value;
- int rc = PyDict_PopString(dict, "extra_kwarg", &value);
+ PyObject *value = PyDict_GetItemString(dict, "extra_kwarg");
- if (rc < 0)
- return -1;
-
- if (rc == 1)
+ if (value != NULL)
{
- rc = PyObject_SetAttrString(sipSelf, "extra_kwarg", value);
- Py_DECREF(value);
+ if (PyObject_SetAttrString(sipSelf, "extra_kwarg", value) < 0)
+ return -1;
+
+ if (PyDict_DelItemString(dict, "extra_kwarg") < 0)
+ return -1;
}
- return rc;
+ return 0;
%End
int get_attr() const;
diff --git a/test/module_attrs/module_attrs_module.sip b/test/module_attrs/module_attrs_module.sip
index b0fb42f..06164c4 100644
--- a/test/module_attrs/module_attrs_module.sip
+++ b/test/module_attrs/module_attrs_module.sip
@@ -7,7 +7,6 @@
%ModuleHeaderCode
// POD types.
bool bool_attr = 1;
-_Bool _Bool_attr = 1;
char byte_attr = 10;
signed char sbyte_attr = -10;
unsigned char ubyte_attr = 10;
@@ -74,7 +73,6 @@ int int_attr_bad_setter = 10;
// POD types.
bool bool_attr;
-_Bool _Bool_attr;
char byte_attr /PyInt/;
signed char sbyte_attr /PyInt/;
unsigned char ubyte_attr /PyInt/;
diff --git a/test/module_attrs/test_module_attrs.py b/test/module_attrs/test_module_attrs.py
index f304679..f12e4f8 100644
--- a/test/module_attrs/test_module_attrs.py
+++ b/test/module_attrs/test_module_attrs.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from sys import getrefcount
@@ -28,26 +28,6 @@ def test_attrs_bool(module, abi_version):
module.bool_attr = 0
assert not module.bool_attr
-def test_attrs__Bool(module, abi_version):
- if abi_version >= 14:
- # Values are mapped to True and False.
- assert module._Bool_attr is True
- module._Bool_attr = False
- assert module._Bool_attr is False
- module._Bool_attr = 10
- assert module._Bool_attr is True
- module._Bool_attr = 0
- assert module._Bool_attr is False
- else:
- # Values are left as integers.
- assert module._Bool_attr
- module._Bool_attr = False
- assert not module._Bool_attr
- module._Bool_attr = 10
- assert module._Bool_attr
- module._Bool_attr = 0
- assert not module._Bool_attr
-
def test_attrs_byte(module):
assert module.byte_attr == 10
module.byte_attr = 20
diff --git a/test/movable/movable_module.sip b/test/movable/movable_module.sip
index 14ea49f..2265cfb 100644
--- a/test/movable/movable_module.sip
+++ b/test/movable/movable_module.sip
@@ -13,12 +13,39 @@ template <TYPE>
%End
%ConvertFromTypeCode
+#if SIP_ABI_MAJOR_VERSION >= 14
+ sipTypeID type_id = sipFindTypeID(sipModule, "TYPE*");
+
+ return sipConvertFromType(sipModule, sipCpp->release(), type_id, Py_None);
+#else
const sipTypeDef *td = sipFindType("TYPE*");
- return sipConvertFromNewType(sipCpp->release(), td, NULL);
+ return sipConvertFromType(sipCpp->release(), td, Py_None);
+#endif
%End
%ConvertToTypeCode
+#if SIP_ABI_MAJOR_VERSION >= 14
+ sipTypeID type_id = sipFindTypeID(sipModule, "TYPE");
+
+ if (sipIsErr == NULL)
+ return sipCanConvertToType(sipModule, sipPy, type_id, 0);
+
+ if (sipPy == Py_None)
+ return 1;
+
+ int state;
+ TYPE *t = reinterpret_cast<TYPE *>(sipConvertToType(sipModule, sipPy, type_id, sipTransferObj, 0, &state, sipIsErr));
+
+ sipReleaseType(sipModule, t, type_id, state);
+
+ if (*sipIsErr)
+ return 0;
+
+ *sipCppPtr = new std::unique_ptr<TYPE>(t);
+
+ return sipGetState(sipTransferObj);
+#else
const sipTypeDef *td = sipFindType("TYPE");
if (sipIsErr == NULL)
@@ -38,6 +65,7 @@ template <TYPE>
*sipCppPtr = new std::unique_ptr<TYPE>(t);
return sipGetState(sipTransferObj);
+#endif
%End
};
diff --git a/test/movable/test_movable.py b/test/movable/test_movable.py
index d2e06c6..feb77de 100644
--- a/test/movable/test_movable.py
+++ b/test/movable/test_movable.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from sys import getrefcount
@@ -8,10 +8,6 @@ from sys import getrefcount
import pytest
-# TODO
-cfg_disabled_for = [14]
-
-
def test_movable(module):
""" Test the support for the /Movable/ mapped type annotation. (See
issue/60.) It also verifies the test implementation of the
@@ -33,20 +29,21 @@ def test_movable(module):
# Test an empty wrapper.
assert ow.getObjectValue() == -1000
- # Test an non-empty wrapper.
+ # Test a non-empty wrapper.
ow.setObject(ao)
- assert getrefcount(ao) == ao_base_refcount + 1
assert ow.getObjectValue() == 4
+ # Test the changed state of the object.
+ assert getrefcount(ao) == ao_base_refcount + 1
+ assert module.ispyowned(ao) is False
+
# Unwrap the object and test the wrapper.
- ao2 = ow.takeObject()
+ assert ow.takeObject() is ao
assert ow.getObjectValue() == -1000
# Re-test the value of the object.
- assert ao2.getValue() == 4
+ assert ao.getValue() == 4
- # Check that the original Python object no longer wraps the C++ object.
+ # Re-test the state of the object.
assert getrefcount(ao) == ao_base_refcount
-
- with pytest.raises(RuntimeError):
- ao.getValue()
+ assert module.ispyowned(ao) is True
diff --git a/test/py_properties/test_py_properties.py b/test/py_properties/test_py_properties.py
index a321677..46b5c2f 100644
--- a/test/py_properties/test_py_properties.py
+++ b/test/py_properties/test_py_properties.py
@@ -1,10 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
-
-
-# TODO
-cfg_disabled_for = [14]
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
def test_property_getter(module):
diff --git a/test/template_typedef/template_typedef_module.sip b/test/template_typedef/template_typedef_module.sip
index 666fb2e..d7b480c 100644
--- a/test/template_typedef/template_typedef_module.sip
+++ b/test/template_typedef/template_typedef_module.sip
@@ -6,7 +6,11 @@
%ExportedHeaderCode
// The need for this, ie. to resolve the type as a typedef, is a SIP bug.
-#define getSipType(sipTypename) (sipResolveTypedef(sipTypename) ? sipFindType(sipResolveTypedef(sipTypename)) : sipFindType(sipTypename))
+#if SIP_ABI_MAJOR_VERSION >= 14
+#define getSipType(mod, name) sipFindTypeID(mod, sipResolveTypedef(mod, name))
+#else
+#define getSipType(name) (sipResolveTypedef(name) ? sipFindType(sipResolveTypedef(name)) : sipFindType(name))
+#endif
%End
@@ -17,12 +21,19 @@ template<TYPE>
%End
%ConvertFromTypeCode
+#if SIP_ABI_MAJOR_VERSION >= 14
+ //sipTypeID kp_type_id = sipFindTypeID(sipModule, "TYPE");
+ sipTypeID kp_type_id = getSipType(sipModule, "TYPE");
+
+ if (!kp_type_id)
+ return NULL;
+#else
//const sipTypeDef *kpTypeDef = sipFindType("TYPE");
const sipTypeDef *kpTypeDef = getSipType("TYPE");
- if (!kpTypeDef) {
+ if (!kpTypeDef)
return NULL;
- }
+#endif
// Create the Python list of the correct length.
PyObject *l;
@@ -35,11 +46,15 @@ template<TYPE>
// wrapper.
for (size_t i = 0; i < sipCpp->size(); ++i) {
TYPE *cpp = new TYPE(sipCpp->at(i));
- PyObject *pobj;
- // Get the Python wrapper for the Type instance, creating a new one if
- // necessary, and handle any ownership transfer.
- if ((pobj = sipConvertFromNewType(cpp, kpTypeDef, sipTransferObj)) == NULL) {
+#if SIP_ABI_MAJOR_VERSION >= 14
+ PyObject *pobj = sipConvertFromNewType(sipModule, cpp, kp_type_id,
+ sipTransferObj);
+#else
+ PyObject *pobj = sipConvertFromNewType(cpp, kpTypeDef, sipTransferObj);
+#endif
+
+ if (pobj == NULL) {
// There was an error so garbage collect the Python list.
Py_XDECREF(l);
return NULL;
@@ -54,12 +69,19 @@ template<TYPE>
%End
%ConvertToTypeCode
+#if SIP_ABI_MAJOR_VERSION >= 14
+ //sipTypeID kp_type_id = sipFindTypeID(sipModule, "TYPE");
+ sipTypeID kp_type_id = getSipType(sipModule, "TYPE");
+
+ if (!kp_type_id)
+ return 0;
+#else
//const sipTypeDef *kpTypeDef = sipFindType("TYPE");
const sipTypeDef *kpTypeDef = getSipType("TYPE");
- if (!kpTypeDef) {
+ if (!kpTypeDef)
return 0;
- }
+#endif
// Check if type is compatible
if (sipIsErr == NULL) {
@@ -72,18 +94,34 @@ template<TYPE>
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(sipPy); ++i) {
int state;
+
+#if SIP_ABI_MAJOR_VERSION >= 14
+ TYPE *p = static_cast<TYPE *>(sipConvertToType(sipModule,
+ PyList_GET_ITEM(sipPy, i), kp_type_id, sipTransferObj,
+ SIP_NOT_NONE, &state, sipIsErr));
+#else
TYPE *p = static_cast<TYPE *>(sipConvertToType(
PyList_GET_ITEM(sipPy, i), kpTypeDef, sipTransferObj,
SIP_NOT_NONE, &state, sipIsErr));
+#endif
if (*sipIsErr) {
+#if SIP_ABI_MAJOR_VERSION >= 14
+ sipReleaseType(sipModule, p, kp_type_id, state);
+#else
sipReleaseType(p, kpTypeDef, state);
+#endif
delete v;
return 0;
}
v->push_back(*p);
+
+#if SIP_ABI_MAJOR_VERSION >= 14
+ sipReleaseType(sipModule, p, kp_type_id, state);
+#else
sipReleaseType(p, kpTypeDef, state);
+#endif
}
*sipCppPtr = v;
diff --git a/test/template_typedef/test_template_typedef.py b/test/template_typedef/test_template_typedef.py
index 37ad078..2a0dd88 100644
--- a/test/template_typedef/test_template_typedef.py
+++ b/test/template_typedef/test_template_typedef.py
@@ -1,10 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
-
-
-# TODO
-cfg_disabled_for = [14]
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
def test_mapped_type_typedef(module):
diff --git a/test/templates/test_templates.py b/test/templates/test_templates.py
index bb836c7..f8c047f 100644
--- a/test/templates/test_templates.py
+++ b/test/templates/test_templates.py
@@ -1,10 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
-
-
-# TODO
-cfg_disabled_for = [14]
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
def test_simple_template_superclasses(module):
diff --git a/test/py_enums/test_py_enums.py b/test/v12_enums/test_v12_enums.py
similarity index 62%
copy from test/py_enums/test_py_enums.py
copy to test/v12_enums/test_v12_enums.py
index fe1c79b..4cef91a 100644
--- a/test/py_enums/test_py_enums.py
+++ b/test/v12_enums/test_v12_enums.py
@@ -1,16 +1,14 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
-from enum import Enum, Flag, IntEnum, IntFlag
+from enum import Enum
import pytest
-# TODO
-#cfg_enabled_for = [13, 14]
-cfg_enabled_for = [13]
+cfg_enabled_for = [12, 14]
@pytest.fixture(scope='module')
@@ -31,15 +29,6 @@ def named_enum_class(module):
return NamedEnumClass
- at pytest.fixture
-def members_invalid(named_enum_class):
- """ A fixture that creates the instance of a test class with invalid enum
- members.
- """
-
- return named_enum_class(0)
-
-
@pytest.fixture
def members_valid(module, named_enum_class):
""" A fixture that creates the instance of a test class with valid enum
@@ -77,42 +66,35 @@ def test_class_anon_value(module):
assert module.EnumClass.ClassAnonMember == 40
-# The following test the /BaseType/ annotation.
-
-def test_Enum_BaseType(module):
- assert issubclass(module.EnumBase, Enum)
- assert not issubclass(module.EnumBase, Flag)
- assert not issubclass(module.EnumBase, IntEnum)
- assert not issubclass(module.EnumBase, IntFlag)
-
-def test_Flag_BaseType(module):
- assert issubclass(module.FlagBase, Flag)
- assert not issubclass(module.FlagBase, IntEnum)
- assert not issubclass(module.FlagBase, IntFlag)
-
-def test_IntEnum_BaseType(module):
- assert not issubclass(module.IntEnumBase, Flag)
- assert issubclass(module.IntEnumBase, IntEnum)
- assert not issubclass(module.IntEnumBase, IntFlag)
+# The following test named enums.
-def test_IntFlag_BaseType(module):
- assert not issubclass(module.IntFlagBase, IntEnum)
- assert issubclass(module.IntFlagBase, IntFlag)
+def test_module_named_enum_attrs(module):
+ assert module.NamedEnum.__module__ == 'v12_enums_module'
+ assert module.NamedEnum.__name__ == 'NamedEnum'
+ assert module.NamedEnum.__qualname__ == 'NamedEnum'
+def test_class_named_enum_attrs(module):
+ assert module.EnumClass.ClassNamedEnum.__module__ == 'v12_enums_module'
+ assert module.EnumClass.ClassNamedEnum.__name__ == 'ClassNamedEnum'
+ assert module.EnumClass.ClassNamedEnum.__qualname__ == 'EnumClass.ClassNamedEnum'
-# The following test named enums.
+def test_module_named_value(module):
+ assert module.NamedEnum.NamedMember == 20
-def test_module_named_type(module):
- assert issubclass(module.NamedEnum, Enum)
+def test_module_named_value_legacy(module):
+ assert module.NamedMember == 20
-def test_module_named_value(module):
- assert module.NamedEnum.NamedMember.value == 20
+def test_enum_operator_add(module):
+ assert module.NamedEnum.NamedMember + 10 == 30
-def test_class_named_type(module):
- assert issubclass(module.EnumClass.ClassNamedEnum, Enum)
+def test_enum_operator_eq(module):
+ assert module.NamedEnum.NamedMember == 20
def test_class_named_value(module):
- assert module.EnumClass.ClassNamedEnum.ClassNamedMember.value == 50
+ assert module.EnumClass.ClassNamedEnum.ClassNamedMember == 50
+
+def test_class_named_value_legacy(module):
+ assert module.EnumClass.ClassNamedMember == 50
def test_named_get_member(module, members_valid, virtual_hook):
assert members_valid.named_get() == module.EnumClass.ClassNamedEnum.ClassNamedMember
@@ -129,20 +111,6 @@ def test_named_overload_set(module, members_valid):
module.EnumClass.ClassNamedEnum.ClassNamedMember)
assert members_valid.named_overload
-def test_named_get_invalid(members_invalid, virtual_hook):
- members_invalid.named_get()
-
- with pytest.raises(TypeError):
- virtual_hook.reraise()
-
-def test_named_set_invalid(members_invalid):
- with pytest.raises(TypeError):
- members_invalid.named_set(50)
-
-def test_named_var_invalid(members_invalid):
- with pytest.raises(TypeError):
- members_invalid.named_var = 50
-
# The following test scoped enums.
diff --git a/test/py_enums/py_enums_module.sip b/test/v12_enums/v12_enums_module.sip
similarity index 79%
copy from test/py_enums/py_enums_module.sip
copy to test/v12_enums/v12_enums_module.sip
index 99ae9d3..8d26b16 100644
--- a/test/py_enums/py_enums_module.sip
+++ b/test/v12_enums/v12_enums_module.sip
@@ -1,7 +1,9 @@
-// The SIP implementation of the py_enums_module test module.
+// The SIP implementation of the v12_enums_module test module.
-%Module(name=py_enums_module)
+%Module(name=v12_enums_module)
+
+%SipModuleConfiguration {CustomEnums}
%ModuleHeaderCode
@@ -14,28 +16,14 @@ enum NamedEnum {
NamedMember = 20
};
+int operator+(NamedEnum e, int incr);
+bool operator==(NamedEnum e, int v);
+
enum class ScopedEnum {
ScopedMember = 30
};
-enum EnumBase {
- EnumBaseMember = 100
-};
-
-enum FlagBase {
- FlagBaseMember = 110
-};
-
-enum IntEnumBase {
- IntEnumBaseMember = 120
-};
-
-enum IntFlagBase {
- IntFlagBaseMember = 130
-};
-
-
class EnumClass {
public:
EnumClass() : named_overload(false) {}
@@ -83,6 +71,12 @@ public:
%End
+%ModuleCode
+int operator+(NamedEnum e, int incr) {return (int)e + incr;}
+bool operator==(NamedEnum e, int v) {return (int)e == v;}
+%End
+
+
enum {
AnonMember
};
@@ -91,28 +85,14 @@ enum NamedEnum {
NamedMember
};
+int operator+(NamedEnum e, int incr);
+bool operator==(NamedEnum e, int v);
+
enum class ScopedEnum {
ScopedMember
};
-enum EnumBase /BaseType=Enum/ {
- EnumBaseMember
-};
-
-enum FlagBase /BaseType=Flag/ {
- FlagBaseMember
-};
-
-enum IntEnumBase /BaseType=IntEnum/ {
- IntEnumBaseMember
-};
-
-enum IntFlagBase /BaseType=IntFlag/ {
- IntFlagBaseMember
-};
-
-
class EnumClass {
public:
EnumClass();
diff --git a/test/py_enums/test_py_enums.py b/test/v13_enums/test_v13_enums.py
similarity index 73%
rename from test/py_enums/test_py_enums.py
rename to test/v13_enums/test_v13_enums.py
index fe1c79b..0008def 100644
--- a/test/py_enums/test_py_enums.py
+++ b/test/v13_enums/test_v13_enums.py
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: BSD-2-Clause
-# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
from enum import Enum, Flag, IntEnum, IntFlag
@@ -8,9 +8,7 @@ from enum import Enum, Flag, IntEnum, IntFlag
import pytest
-# TODO
-#cfg_enabled_for = [13, 14]
-cfg_enabled_for = [13]
+cfg_enabled_for = [13, 14]
@pytest.fixture(scope='module')
@@ -62,6 +60,19 @@ def scoped_members(module):
return ScopedEnumClass()
+ at pytest.fixture
+def typed_members(module):
+ """ A fixture that creates the instance of a test class with valid typed
+ enum members.
+ """
+
+ class TypedEnumClass(module.EnumClass):
+ def typed_virt(self):
+ return module.EnumClass.ClassTypedEnum.ClassTypedMember
+
+ return TypedEnumClass()
+
+
# The following test anonymous enums.
def test_module_anon_type(module):
@@ -102,12 +113,28 @@ def test_IntFlag_BaseType(module):
# The following test named enums.
+def test_module_named_enum_attrs(module):
+ assert module.NamedEnum.__module__ == 'v13_enums_module'
+ assert module.NamedEnum.__name__ == 'NamedEnum'
+ assert module.NamedEnum.__qualname__ == 'NamedEnum'
+
+def test_class_named_enum_attrs(module):
+ assert module.EnumClass.ClassNamedEnum.__module__ == 'v13_enums_module'
+ assert module.EnumClass.ClassNamedEnum.__name__ == 'ClassNamedEnum'
+ assert module.EnumClass.ClassNamedEnum.__qualname__ == 'EnumClass.ClassNamedEnum'
+
def test_module_named_type(module):
assert issubclass(module.NamedEnum, Enum)
def test_module_named_value(module):
assert module.NamedEnum.NamedMember.value == 20
+def test_enum_operator_add(module):
+ assert module.NamedEnum.NamedMember + 10 == 30
+
+def test_enum_operator_eq(module):
+ assert module.NamedEnum.NamedMember == 20
+
def test_class_named_type(module):
assert issubclass(module.EnumClass.ClassNamedEnum, Enum)
@@ -168,3 +195,23 @@ def test_scoped_set_member(module, scoped_members):
def test_scoped_var_member(module, scoped_members):
scoped_members.scoped_var = module.EnumClass.ClassScopedEnum.ClassScopedMember
+
+
+# The following test typed enums.
+
+def test_class_typed_type(module):
+ assert issubclass(module.EnumClass.ClassTypedEnum, Enum)
+
+def test_class_typed_value(module):
+ assert module.EnumClass.ClassTypedEnum.ClassTypedMember.value == 0xff
+
+def test_typed_get_member(module, typed_members, virtual_hook):
+ assert typed_members.typed_get() is module.EnumClass.ClassTypedEnum.ClassTypedMember
+ virtual_hook.reraise()
+
+def test_typed_set_member(module, typed_members):
+ typed_members.typed_set(
+ module.EnumClass.ClassTypedEnum.ClassTypedMember)
+
+def test_typed_var_member(module, typed_members):
+ typed_members.typed_var = module.EnumClass.ClassTypedEnum.ClassTypedMember
diff --git a/test/py_enums/py_enums_module.sip b/test/v13_enums/v13_enums_module.sip
similarity index 71%
rename from test/py_enums/py_enums_module.sip
rename to test/v13_enums/v13_enums_module.sip
index 99ae9d3..bb78577 100644
--- a/test/py_enums/py_enums_module.sip
+++ b/test/v13_enums/v13_enums_module.sip
@@ -1,7 +1,9 @@
-// The SIP implementation of the py_enums_module test module.
+// The SIP implementation of the v13_enums_module test module.
-%Module(name=py_enums_module)
+%Module(name=v13_enums_module)
+
+%SipModuleConfiguration {PyEnums}
%ModuleHeaderCode
@@ -14,6 +16,9 @@ enum NamedEnum {
NamedMember = 20
};
+int operator+(NamedEnum e, int incr);
+bool operator==(NamedEnum e, int v);
+
enum class ScopedEnum {
ScopedMember = 30
};
@@ -78,11 +83,32 @@ public:
}
static void scoped_set(ClassScopedEnum) {}
ClassScopedEnum scoped_var;
+
+ enum ClassTypedEnum : unsigned char {
+ ClassTypedMember = 0xff
+ };
+
+ ClassTypedEnum typed_get() {
+ return typed_virt();
+ }
+
+ virtual ClassTypedEnum typed_virt() {
+ return ClassTypedEnum::ClassTypedMember;
+ }
+
+ static void typed_set(ClassTypedEnum) {}
+ ClassTypedEnum typed_var;
};
%End
+%ModuleCode
+int operator+(NamedEnum e, int incr) {return (int)e + incr;}
+bool operator==(NamedEnum e, int v) {return (int)e == v;}
+%End
+
+
enum {
AnonMember
};
@@ -91,6 +117,9 @@ enum NamedEnum {
NamedMember
};
+int operator+(NamedEnum e, int incr);
+bool operator==(NamedEnum e, int v);
+
enum class ScopedEnum {
ScopedMember
};
@@ -147,4 +176,13 @@ public:
virtual ClassScopedEnum scoped_virt();
static void scoped_set(ClassScopedEnum);
ClassScopedEnum scoped_var;
+
+ enum ClassTypedEnum : unsigned char {
+ ClassTypedMember
+ };
+
+ ClassTypedEnum typed_get();
+ virtual ClassTypedEnum typed_virt();
+ static void typed_set(ClassTypedEnum);
+ ClassTypedEnum typed_var;
};
diff --git a/test/virtuals/test_virtuals.py b/test/virtuals/test_virtuals.py
new file mode 100644
index 0000000..d3ab8d7
--- /dev/null
+++ b/test/virtuals/test_virtuals.py
@@ -0,0 +1,38 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+# Copyright (c) 2025 Phil Thompson <phil at riverbankcomputing.com>
+
+
+def test_base_implementation_int(module):
+ class Derived(module.Base):
+ def default_value_int(self):
+ return 2 * super().default_value_int()
+
+ klass = Derived()
+ value = module.Base.default_value_int(klass)
+
+ assert value == 10
+
+
+def test_py_reimplementation_class(module):
+ class Derived(module.Base):
+ def default_value_class(self):
+ return Derived(b'Derived')
+
+ value = Derived().get_value_class()
+
+ assert module.ispyowned(value)
+
+ # Note that we don't test the type as it will be Base (not Derived) as we
+ # haven't implemented %ConvertToSubClassCode.
+ assert value.get_class_name() == b'Derived'
+
+
+def test_py_reimplementation_int(module):
+ class Derived(module.Base):
+ def default_value_int(self):
+ return 2 * super().default_value_int()
+
+ value = Derived().get_value_int()
+
+ assert value == 20
diff --git a/test/virtuals/virtuals_module.sip b/test/virtuals/virtuals_module.sip
new file mode 100644
index 0000000..8dd88df
--- /dev/null
+++ b/test/virtuals/virtuals_module.sip
@@ -0,0 +1,39 @@
+// The SIP implementation of the virtuals_module test module.
+
+
+%Module(name=virtuals_module)
+
+
+%ModuleHeaderCode
+class Base
+{
+public:
+ Base(const char *class_name = nullptr) : m_class_name(class_name) {}
+ virtual ~Base() {}
+
+ virtual int default_value_int() const {return 10;}
+ int get_value_int() const {return default_value_int();}
+
+ virtual Base *default_value_class() const {return new Base("Base");}
+ Base *get_value_class() const {return default_value_class();}
+ const char *get_class_name() const {return m_class_name;}
+
+private:
+ const char *m_class_name;
+};
+%End
+
+
+class Base
+{
+public:
+ Base(const char *class_name = 0);
+ virtual ~Base();
+
+ virtual int default_value_int() const;
+ int get_value_int() const;
+
+ virtual Base *default_value_class() const /Factory/;
+ Base *get_value_class() const /Factory/;
+ const char *get_class_name() const;
+};
More information about the Neon-commits
mailing list