[neon/forks/sip6/Neon/release] /: New upstream version 6.15.3
Dmitry Shachnev
null at kde.org
Mon Jul 27 12:04:48 BST 2026
Git commit 4d53982dabb35a95bfeb43b98179c4ccdc0c7009 by Dmitry Shachnev.
Committed on 24/03/2026 at 12:56.
Pushed by carlosdem into branch 'Neon/release'.
New upstream version 6.15.3
M +3 -3 .git_archival.txt
M +1 -1 docs/conf.py
M +14 -7 docs/directives.rst
M +12 -3 docs/examples.rst
M +22 -0 docs/releases.md
M +1 -0 examples/package/core/core.sip
M +2 -0 examples/standalone/fib.sip
M +6 -8 sipbuild/generator/outputs/code/backends/abstract_backend.py
M +77 -58 sipbuild/generator/outputs/code/backends/v12v13.py
M +23 -65 sipbuild/generator/outputs/code/snippets.py
M +34 -0 sipbuild/generator/outputs/code/utils.py
M +14 -6 sipbuild/generator/parser/parser_manager.py
M +10 -5 sipbuild/generator/resolver/resolver.py
M +7 -1 sipbuild/generator/utils.py
M +12 -1 test/conftest.py
A +26 -0 test/extenders/core_module.sip
A +35 -0 test/extenders/extras1_module.sip
A +28 -0 test/extenders/extras2_module.sip
A +39 -0 test/extenders/test_extenders.py
M +1 -1 test/imported_exceptions/handler_module.sip
M +0 -17 test/imports/core_module.sip
M +0 -22 test/imports/extras_module.sip
M +0 -7 test/imports/test_imports.py
M +6 -6 test/movable/movable_module.sip
M +8 -8 test/template_typedef/template_typedef_module.sip
https://invent.kde.org/neon/forks/sip6/-/commit/4d53982dabb35a95bfeb43b98179c4ccdc0c7009
diff --git a/.git_archival.txt b/.git_archival.txt
index 0bbb749..2b23ab6 100644
--- a/.git_archival.txt
+++ b/.git_archival.txt
@@ -1,3 +1,3 @@
-node: cfbd43a5641b6e0f5c51ae7b51d79449db394127
-node-date: 2026-03-09T11:57:57Z
-describe-name: 6.15.2
+node: b7a14f4ae72d5eab4e4823dbfa2fb63e56910348
+node-date: 2026-03-24T11:51:06Z
+describe-name: 6.15.3
diff --git a/docs/conf.py b/docs/conf.py
index 4f184f5..728dd0d 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.2'
+version = 'v6.15.3'
# -- General configuration ---------------------------------------------------
diff --git a/docs/directives.rst b/docs/directives.rst
index 13390aa..d3c61bb 100644
--- a/docs/directives.rst
+++ b/docs/directives.rst
@@ -1724,15 +1724,22 @@ then the pattern should instead be::
.. versionadded:: 6.10
-This directive is used to specify the minimum version of the ABI required to
-build the project. It normally reflects the calls made to the public API from
-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.
+This directive is used to specify the exact major version number and the
+minimum minor version number of the ABI required to build the project. It
+normally reflects the calls made to the public API from handwritten code, ie.
+if the code makes 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 directive is also specified in an imported module (see
+:directive:`%Import`), then SIP will check that the versions do not conflict.
+
+If the major version is omitted (and not provide by an imported module) then
+the default ABI will be used. The default may change in the future and so this
+directive should always be used in this module or an imported one.
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``.
+version will be used. However it is recommended that a minor number is always
+specified, even if it is ``0``.
For example::
diff --git a/docs/examples.rst b/docs/examples.rst
index fb2b067..322b65f 100644
--- a/docs/examples.rst
+++ b/docs/examples.rst
@@ -51,8 +51,12 @@ standalone projects this would normally be the same as the name defined in the
:file:`pyproject.toml` file. It also specifies that the code being wrapped is
implemented in C (as opposed to C++).
-The next line of interest is the declaration of the :c:func:`fib_n` function to
-be wrapped.
+The next line of interest is the :directive:`MinimumABIVersion` directive.
+This specifies the ABI version being targeted. This example is so simple that
+it will will work with any ABI version but it is good practice to specify a
+particular version.
+
+Next is the declaration of the :c:func:`fib_n` function to be wrapped.
The remainder of the file is the :directive:`%MethodCode` directive attached to
the function declaration. This is used to provide the actual implementation of
@@ -304,6 +308,9 @@ The :directive:`%Module` directive, as well as specifying the full package name
of the :mod:`~examples.core` module, specifies that the bindings will use the
`PEP 384 <https://www.python.org/dev/peps/pep-0384/>`__ stable ABI.
+The :directive:`%MinimumABIVersion` directive specifies the ABI version being
+targeted.
+
The :directive:`%DefaultEncoding` directive specifies that any character
conversions between C/C++ and Python ``str`` objects will default to the ASCII
codec.
@@ -370,6 +377,8 @@ We next look at the :file:`extras.sip` file (downloadable from
This is very similar to the :mod:`~examples.core` module in that it implements
simple platform-specific functions. The key thing to notice is that there is
no need to specify the platform tag as part of the configuration as it is
-obtained automatically from the installed ``examples-core`` project.
+obtained automatically from the installed ``examples-core`` project. Also note
+that the target ABI version has not been specified and the version will be
+taken from the ``examples-core`` project.
The ``examples-extras`` project has no need for a :file:`project.py` file.
diff --git a/docs/releases.md b/docs/releases.md
index 5190958..f27f2ce 100644
--- a/docs/releases.md
+++ b/docs/releases.md
@@ -1,5 +1,27 @@
# Release Notes
+## v6.15.3
+
+### Bug fixes
+
+- Fixed the handling of virtuals that return enums with a non-`int` base type.
+ Resolves [#105](https://github.com/Python-SIP/sip/issues/105)
+- A regression in v6.15.2 that caused a crash if `MinimumABIVersion` was not
+ specified was fixed. Resolves
+ [#106](https://github.com/Python-SIP/sip/issues/106)
+
+### Deprecations
+
+The use of the `%MinimumABIVersion` directive will be required in SIP v7.
+Failing to use it now results in a deprecation warning.
+
+### Testing
+
+- The tests now amend `CXXFLAGS` instead of overwriting them. This fixes
+ `blhc` (build log hardening check) in Debian. Pull request
+ [#104](https://github.com/Python-SIP/sip/pull/104).
+
+
## v6.15.2
### Enhancements
diff --git a/examples/package/core/core.sip b/examples/package/core/core.sip
index 5e7073c..a16c9a5 100644
--- a/examples/package/core/core.sip
+++ b/examples/package/core/core.sip
@@ -2,6 +2,7 @@
%Module(name=examples.core, use_limited_api=True)
+%MinimumABIVersion "13.1"
%DefaultEncoding "ASCII"
%Platforms {Linux macOS Windows}
diff --git a/examples/standalone/fib.sip b/examples/standalone/fib.sip
index 63a8da6..16cedc9 100644
--- a/examples/standalone/fib.sip
+++ b/examples/standalone/fib.sip
@@ -2,6 +2,8 @@
%Module(name=fib, language="C")
+%MinimumABIVersion "13.1"
+
int fib_n(int n);
%MethodCode
if (a0 <= 0)
diff --git a/sipbuild/generator/outputs/code/backends/abstract_backend.py b/sipbuild/generator/outputs/code/backends/abstract_backend.py
index 2219758..9ce45c1 100644
--- a/sipbuild/generator/outputs/code/backends/abstract_backend.py
+++ b/sipbuild/generator/outputs/code/backends/abstract_backend.py
@@ -44,6 +44,12 @@ class AbstractBackend(ABC):
...
+ @abstractmethod
+ def g_class_spec_extern_decl(self, sf, klass):
+ """ Generate the extern declaration of a class specification. """
+
+ ...
+
@abstractmethod
def g_conversion_to_enum(self, sf, enum):
""" Generate the code to convert a Python enum (sipSelf) to a C/C++
@@ -197,14 +203,6 @@ class AbstractBackend(ABC):
# 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
- return the number of entries.
- """
-
- ...
-
@abstractmethod
def g_sip_api(self, sf, module_name, module_closure):
""" Generate the SIP API as seen by generated code. """
diff --git a/sipbuild/generator/outputs/code/backends/v12v13.py b/sipbuild/generator/outputs/code/backends/v12v13.py
index eb54fcf..03b458a 100644
--- a/sipbuild/generator/outputs/code/backends/v12v13.py
+++ b/sipbuild/generator/outputs/code/backends/v12v13.py
@@ -10,17 +10,16 @@ from ....utils import find_method
from ...formatters import fmt_argument_as_cpp_type
-from ..snippets import (g_class_docstring, g_class_method_table,
- 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 ..snippets import (g_class_docstring, 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,
- pyqt5_supported, pyqt6_supported, scoped_class_name,
+ get_function_table, get_mapped_type_flags, get_method_table,
+ 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, pyqt5_supported, pyqt6_supported, scoped_class_name,
scoped_variable_name, type_needs_user_state, variables_in_scope)
from .abstract_backend import AbstractBackend
@@ -112,6 +111,14 @@ f''' if (targetType == {sc_type_ref})
if iface_file.type is IfaceFileType.NAMESPACE:
sf.write('#endif\n')
+ def g_class_spec_extern_decl(self, sf, klass):
+ """ Generate the extern declaration of a class specification. """
+
+ module_name = self.spec.module.py_name
+ klass_name = klass.iface_file.fq_cpp_name.as_word
+
+ sf.write(f'\nextern sipClassTypeDef sipTypeDef_{module_name}_{klass_name};\n')
+
def g_conversion_to_enum(self, sf, enum):
""" Generate the code to convert a Python enum (sipSelf) to a C/C++
enum (sipCpp).
@@ -589,7 +596,7 @@ f'''
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,
+ cod_nrmethods = self._g_py_method_table(sf, bindings, members,
mapped_type)
id_int = 'SIP_NULLPTR'
@@ -852,53 +859,6 @@ f'''
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.
- """
-
- scope_name = scope.iface_file.fq_cpp_name.as_word
-
- no_intro = True
-
- for member_nr, member in enumerate(members):
- # Save the index in the table.
- member.member_nr = member_nr
-
- py_name = member.py_name
- cached_py_name = self.cached_name_ref(py_name)
- comma = '' if member is members[-1] else ','
-
- if member.no_arg_parser or member.allow_keyword_args:
- cast = 'SIP_MLMETH_CAST('
- cast_suffix = ')'
- flags = '|METH_KEYWORDS'
- else:
- cast = ''
- cast_suffix = ''
- flags = ''
-
- if has_method_docstring(bindings, member, scope.overloads):
- docstring = f'doc_{scope_name}_{py_name.name}'
- else:
- docstring = 'SIP_NULLPTR'
-
- if no_intro:
- sf.write(
-f'''
-
-static PyMethodDef methods_{scope_name}[] = {{
-''')
-
- no_intro = False
-
- sf.write(f' {{{cached_py_name}, {cast}meth_{scope_name}_{py_name.name}{cast_suffix}, METH_VARARGS{flags}, {docstring}}}{comma}\n')
-
- if not no_intro:
- sf.write('};\n')
-
- return len(members)
-
def g_sip_api(self, sf, module_name, module_state):
""" Generate the SIP API as seen by generated code. """
@@ -1321,7 +1281,7 @@ static sipPySlotDef slots_{klass_name}[] = {{
sf.write(' {0, (sipPySlotType)0}\n};\n')
# The attributes tables.
- nr_methods = g_class_method_table(self, sf, bindings, klass)
+ nr_methods = self._g_class_method_table(sf, bindings, klass)
nr_enum_members, _ = self.g_enums_specifications(sf, bindings,
scope=klass)
@@ -1798,6 +1758,18 @@ f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *
return docstring_ref
+ def _g_class_method_table(self, 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)
+ else:
+ members = get_method_table(klass)
+
+ return self._g_py_method_table(sf, bindings, members, klass)
+
def _g_enums_defs(self, sf, needed_enums):
""" Generate the definitions for all wrapped enums. """
@@ -2234,6 +2206,53 @@ f'''
return True
+ 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.
+ """
+
+ scope_name = scope.iface_file.fq_cpp_name.as_word
+
+ no_intro = True
+
+ for member_nr, member in enumerate(members):
+ # Save the index in the table.
+ member.member_nr = member_nr
+
+ py_name = member.py_name
+ cached_py_name = self.cached_name_ref(py_name)
+ comma = '' if member is members[-1] else ','
+
+ if member.no_arg_parser or member.allow_keyword_args:
+ cast = 'SIP_MLMETH_CAST('
+ cast_suffix = ')'
+ flags = '|METH_KEYWORDS'
+ else:
+ cast = ''
+ cast_suffix = ''
+ flags = ''
+
+ if has_method_docstring(bindings, member, scope.overloads):
+ docstring = f'doc_{scope_name}_{py_name.name}'
+ else:
+ docstring = 'SIP_NULLPTR'
+
+ if no_intro:
+ sf.write(
+f'''
+
+static PyMethodDef methods_{scope_name}[] = {{
+''')
+
+ no_intro = False
+
+ sf.write(f' {{{cached_py_name}, {cast}meth_{scope_name}_{py_name.name}{cast_suffix}, METH_VARARGS{flags}, {docstring}}}{comma}\n')
+
+ if not no_intro:
+ sf.write('};\n')
+
+ return len(members)
+
# The types that are implemented as PyObject*.
_PY_OBJECT_TYPES = (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE,
ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE,
diff --git a/sipbuild/generator/outputs/code/snippets.py b/sipbuild/generator/outputs/code/snippets.py
index 8c0f619..ecc9775 100644
--- a/sipbuild/generator/outputs/code/snippets.py
+++ b/sipbuild/generator/outputs/code/snippets.py
@@ -23,28 +23,15 @@ from ..formatters import (fmt_argument_as_cpp_type, fmt_argument_as_name,
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_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,
- need_error_flag, py_scope, pyqt5_supported, pyqt6_supported,
- release_gil, scoped_class_name, skip_overload, type_needs_user_state,
+ get_enum_class_scope, 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, need_error_flag,
+ py_scope, pyqt5_supported, pyqt6_supported, release_gil,
+ scoped_class_name, skip_overload, type_needs_user_state,
variables_in_scope)
-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)
- else:
- members = _get_method_table(klass)
-
- return backend.g_py_method_table(sf, bindings, members, klass)
-
-
def g_composite_module_code(backend, sf, py_debug):
""" Generate the code for a composite module. """
@@ -721,7 +708,7 @@ def _arg_is_v13_typed_enum(spec, arg):
# 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
+ # types larger than an int and doesn't need any casting. ABI 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
@@ -781,7 +768,7 @@ def _arg_parser(backend, sf, scope, py_signature, signature_nr, ctor=None,
single_arg = False
if spec.target_abi >= (14, 0):
- args.append('sipModule')
+ args.append('sipMS')
if overload is not None:
member_name = overload.common.py_name.name
@@ -1493,9 +1480,7 @@ def _class_api(backend, sf, 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
- spec_suffix = backend.get_spec_suffix()
- sf.write(f'\nextern sipClassType{spec_suffix} sipType{spec_suffix}_{module_name}_{klass_name};\n')
+ backend.g_class_spec_extern_decl(sf, klass)
def g_class_docstring(sf, spec, bindings, klass):
@@ -1852,40 +1837,6 @@ def _gc_ellipsis(sf, signature):
sf.write(f'\n Py_DECREF(a{last});\n')
-def _get_method_table(klass):
- """ Return a sorted list of relevant methods (either lazy or non-lazy) for
- a class.
- """
-
- # Only provide an entry point if there is at least one overload that is
- # defined in this class and is a non-abstract function or slot. We allow
- # private (even though we don't actually generate code) because we need to
- # intercept the name before it reaches a more public version further up the
- # class hierarchy. We add the ctor and any variable handlers as special
- # entries.
-
- members = []
-
- for visible_member in klass.visible_members:
- if visible_member.member.py_slot is not None:
- continue
-
- need_member = False
-
- for overload in visible_member.scope.overloads:
- # Skip protected methods if we don't have the means to handle them.
- if overload.access_specifier is AccessSpecifier.PROTECTED and not klass.has_shadow:
- continue
-
- if not skip_overload(overload, visible_member.member, klass, visible_member.scope):
- need_member = True
-
- if need_member:
- members.append(visible_member.member)
-
- return get_function_table(members)
-
-
def _get_subformat_char(arg):
""" Return the sub-format character for an argument. """
@@ -2717,7 +2668,7 @@ f''' if (!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject(sip{prefix}_{fq_c
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 ''
+ sip_module = 'sipMS, ' if spec.target_abi >= (14, 0) else ''
sf.write(
f''' {cpp_name} *sipCpp = reinterpret_cast<{cpp_name} *>(sipGetCppPtr({sip_module}{backend.get_wrapper_type_cast()}sipSelf, {type_ref}));
@@ -2780,7 +2731,7 @@ f'''
if is_number_slot(member.py_slot) or is_rich_compare_slot(member.py_slot):
if spec.target_abi >= (14, 0):
- extend_context = 'sipModule'
+ extend_context = 'sipMS'
else:
extend_context = f'&sipModuleAPI_{spec.module.py_name}'
@@ -3893,7 +3844,14 @@ f'''
sf.write(')\n{\n')
if result_is_returned:
- result_plain_decl = fmt_argument_as_cpp_type(spec, result, plain=True)
+ decl = fmt_argument_as_cpp_type(spec, result, plain=True)
+
+ if _arg_is_v13_typed_enum(spec, result):
+ result_plain_decl = 'int'
+ result_cast = '(' + decl + ')'
+ else:
+ result_plain_decl = decl
+ result_cast = ''
if result_instance_code is not None:
sf.write(
@@ -3995,8 +3953,8 @@ f'''
if result_is_returned:
sf.write(
-'''
- return sipRes;
+f'''
+ return {result_cast}sipRes;
''')
sf.write('}\n')
@@ -4097,7 +4055,7 @@ f' PyObject *sipResObj = sipCallMethod({context}SIP_NULLPTR, sipMethod, ')
sf.write(
f'''
- return {result_ref}sipRes;
+ return {result_cast}{result_ref}sipRes;
''')
sf.write('}\n')
@@ -4955,7 +4913,7 @@ f''' Py_INCREF(Py_None);
build_result_args = []
if spec.target_abi >= (14, 0):
- build_result_args.append('sipModule')
+ build_result_args.append('sipMS')
build_result_args.append('0')
diff --git a/sipbuild/generator/outputs/code/utils.py b/sipbuild/generator/outputs/code/utils.py
index ce75466..668c362 100644
--- a/sipbuild/generator/outputs/code/utils.py
+++ b/sipbuild/generator/outputs/code/utils.py
@@ -184,6 +184,40 @@ def get_mapped_type_flags(mapped_type):
return '|'.join(flags)
+def get_method_table(klass):
+ """ Return a sorted list of relevant methods (either lazy or non-lazy) for
+ a class.
+ """
+
+ # Only provide an entry point if there is at least one overload that is
+ # defined in this class and is a non-abstract function or slot. We allow
+ # private (even though we don't actually generate code) because we need to
+ # intercept the name before it reaches a more public version further up the
+ # class hierarchy. We add the ctor and any variable handlers as special
+ # entries.
+
+ members = []
+
+ for visible_member in klass.visible_members:
+ if visible_member.member.py_slot is not None:
+ continue
+
+ need_member = False
+
+ for overload in visible_member.scope.overloads:
+ # Skip protected methods if we don't have the means to handle them.
+ if overload.access_specifier is AccessSpecifier.PROTECTED and not klass.has_shadow:
+ continue
+
+ if not skip_overload(overload, visible_member.member, klass, visible_member.scope):
+ need_member = True
+
+ if need_member:
+ members.append(visible_member.member)
+
+ return get_function_table(members)
+
+
def get_named_value_decl(spec, scope, type, name):
""" Return the declaration of a named variable to hold a C++ value. """
diff --git a/sipbuild/generator/parser/parser_manager.py b/sipbuild/generator/parser/parser_manager.py
index e857bfb..0731065 100644
--- a/sipbuild/generator/parser/parser_manager.py
+++ b/sipbuild/generator/parser/parser_manager.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 functools import partial
@@ -605,7 +605,7 @@ class ParserManager:
if base_type_s is not None:
# The minor version of the target ABI may not be known yet (and we
# don't need it) so just test the major version.
- if self.spec.target_abi is not None and self.spec.target_abi[0] < 13:
+ if self.target_major_abi < 13:
self.parser_error(p, symbol,
"/BaseType/ is only supported for ABI v13.0 and later")
@@ -658,7 +658,7 @@ class ParserManager:
# 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):
+ if cpp_name is None or (self.target_major_abi == 12 and not is_scoped):
self.check_attributes(p, symbol, m_py_name.name,
"an enum member")
@@ -1669,6 +1669,14 @@ class ParserManager:
return value
+ @property
+ def target_major_abi(self):
+ """ The major version of the currently specified target ABI. """
+
+ target_abi = self.spec.target_abi
+
+ return DEFAULT_ABI_MAJOR if target_abi is None else target_abi[0]
+
def validate_function(self, p, symbol, overload):
""" Validate a completed function. """
@@ -1736,9 +1744,7 @@ class ParserManager:
def validate_mapped_type(self, p, symbol, mapped_type):
""" Validate a completed mapped type. """
- # The minor version of the target ABI may not be known yet (and we
- # don't need it) so just test the major version.
- if self.spec.target_abi is None or self.spec.target_abi[0] >= 13:
+ if self.target_major_abi >= 13:
convert_to_us = mapped_type.convert_to_type_code is not None and 'sipUserState' in mapped_type.convert_to_type_code.text
release_us = mapped_type.release_code is not None and 'sipUserState' in mapped_type.release_code.text
@@ -1900,6 +1906,8 @@ class ParserManager:
target_abi = self.spec.target_abi
if target_abi is None:
+ deprecated("Not specifying %MinimumABIVersion")
+
major_version = DEFAULT_ABI_MAJOR
minor_version = None
else:
diff --git a/sipbuild/generator/resolver/resolver.py b/sipbuild/generator/resolver/resolver.py
index b8deb24..7b3aab0 100644
--- a/sipbuild/generator/resolver/resolver.py
+++ b/sipbuild/generator/resolver/resolver.py
@@ -20,8 +20,8 @@ from ..specification import (AccessSpecifier, Argument, ArgumentType,
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,
- fast_contains, find_iface_file, find_method, same_argument_type,
- same_base_type, same_signature, search_typedefs)
+ fast_contains, find_iface_file, find_method, is_namespace_extender,
+ same_argument_type, same_base_type, same_signature, search_typedefs)
def resolve(spec, modules):
@@ -2179,9 +2179,14 @@ def _create_sorted_numbered_types(spec, mod, error_log):
continue
if mod is spec.module or klass.iface_file.needed:
- if not klass.is_hidden_namespace:
- mod.needed_types.append(Argument(ArgumentType.CLASS,
- definition=klass, name=klass.iface_file.cpp_name))
+ if klass.is_hidden_namespace:
+ continue
+
+ if spec.target_abi >= (14, 0) and is_namespace_extender(klass):
+ continue
+
+ mod.needed_types.append(Argument(ArgumentType.CLASS,
+ definition=klass, name=klass.iface_file.cpp_name))
for mapped_type in spec.mapped_types:
if mapped_type.iface_file.module is not mod:
diff --git a/sipbuild/generator/utils.py b/sipbuild/generator/utils.py
index 5147ebf..76244fe 100644
--- a/sipbuild/generator/utils.py
+++ b/sipbuild/generator/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 .scoped_name import ScopedName
@@ -233,6 +233,12 @@ def find_method(klass, name):
return None
+def is_namespace_extender(klass):
+ """ Return True if a class is a namespace extender. """
+
+ return klass.iface_file.type is IfaceFileType.NAMESPACE and klass.real_class is not None
+
+
def normalised_scoped_name(scoped_name, scope):
""" Convert a scoped name to a fully qualified name. """
diff --git a/test/conftest.py b/test/conftest.py
index 749ad67..c6e9de8 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -28,6 +28,15 @@ def pytest_addoption(parser):
choices=ABI_VERSIONS, default=ABI_VERSIONS[-1])
+ at pytest.fixture
+def abi_package_version(package):
+ """ This fixture extracts the ABI version used to build a test package.
+ The value is the ABI major version.
+ """
+
+ return package.sip.SIP_ABI_VERSION >> 16
+
+
@pytest.fixture
def abi_version(module):
""" This fixture extracts the ABI version used to build a test module. The
@@ -292,7 +301,9 @@ def _build_test_module(sip_file, test_dir, abi_version, package, exceptions,
f.write(f'tags = [{tags_s}]\n')
# Configure the C++11 support.
- os.environ['CXXFLAGS'] = '-std=c++11'
+ cxxflags = os.environ.get('CXXFLAGS', '')
+ if '-std=c++11' not in cxxflags:
+ os.environ['CXXFLAGS'] = f'{cxxflags} -std=c++11'
# Build and move the test module.
_build_module(module_name, package,
diff --git a/test/extenders/core_module.sip b/test/extenders/core_module.sip
new file mode 100644
index 0000000..c492933
--- /dev/null
+++ b/test/extenders/core_module.sip
@@ -0,0 +1,26 @@
+// The SIP implementation of the core_module test module.
+
+
+%Module(name=extenders.core_module)
+
+
+%ModuleHeaderCode
+namespace NameSpace
+{
+ int twice(int n);
+};
+%End
+
+
+%ModuleCode
+int NameSpace::twice(int n)
+{
+ return 2 * n;
+}
+%End
+
+
+namespace NameSpace
+{
+ int twice(int n /Constrained/);
+};
diff --git a/test/extenders/extras1_module.sip b/test/extenders/extras1_module.sip
new file mode 100644
index 0000000..4ddbcaf
--- /dev/null
+++ b/test/extenders/extras1_module.sip
@@ -0,0 +1,35 @@
+// The SIP implementation of the extras1_module test module.
+
+
+%Module(name=extenders.extras1_module)
+
+%Import core_module.sip
+
+
+%ModuleHeaderCode
+namespace NameSpace
+{
+ float twice(float n);
+ float thrice(float n);
+};
+%End
+
+
+%ModuleCode
+float NameSpace::twice(float n)
+{
+ return 2 * n;
+}
+
+float NameSpace::thrice(float n)
+{
+ return 3 * n;
+}
+%End
+
+
+namespace NameSpace
+{
+ float twice(float n);
+ float thrice(float n);
+};
diff --git a/test/extenders/extras2_module.sip b/test/extenders/extras2_module.sip
new file mode 100644
index 0000000..5eb7d7a
--- /dev/null
+++ b/test/extenders/extras2_module.sip
@@ -0,0 +1,28 @@
+// The SIP implementation of the extras2_module test module.
+
+
+%Module(name=extenders.extras2_module)
+
+%Import core_module.sip
+
+
+%ModuleHeaderCode
+namespace NameSpace
+{
+ int thrice(int n);
+};
+%End
+
+
+%ModuleCode
+int NameSpace::thrice(int n)
+{
+ return 3 * n;
+}
+%End
+
+
+namespace NameSpace
+{
+ int thrice(int n /Constrained/);
+};
diff --git a/test/extenders/test_extenders.py b/test/extenders/test_extenders.py
new file mode 100644
index 0000000..d87cdf2
--- /dev/null
+++ b/test/extenders/test_extenders.py
@@ -0,0 +1,39 @@
+# SPDX-License-Identifier: BSD-2-Clause
+
+# Copyright (c) 2026 Phil Thompson <phil at riverbankcomputing.com>
+
+
+import pytest
+
+
+cfg_package = 'extenders'
+
+
+def test_core_callable(package, abi_package_version):
+ # Older versions of the ABI do not support extending existing callables
+ # with new overloads.
+ if abi_package_version >= 14:
+ twice_i = package.core_module.NameSpace.twice(5)
+
+ assert isinstance(twice_i, int)
+ assert twice_i == 10
+
+def test_extended_overload(package):
+ twice_f = package.core_module.NameSpace.twice(5.0)
+
+ assert isinstance(twice_f, float)
+ assert twice_f == 10.0
+
+def test_extended_callable_or_overload(package, abi_package_version):
+ # Older versions of the ABI do not support extending existing callables
+ # with new overloads.
+ if abi_package_version >= 14:
+ thrice_i = package.core_module.NameSpace.thrice(5)
+
+ assert isinstance(thrice_i, int)
+ assert thrice_i == 15
+
+ thrice_f = package.core_module.NameSpace.thrice(5.0)
+
+ assert isinstance(thrice_f, float)
+ assert thrice_f == 15.0
diff --git a/test/imported_exceptions/handler_module.sip b/test/imported_exceptions/handler_module.sip
index 7d5e948..89baf1b 100644
--- a/test/imported_exceptions/handler_module.sip
+++ b/test/imported_exceptions/handler_module.sip
@@ -16,7 +16,7 @@
SIP_BLOCK_THREADS
#if SIP_ABI_MAJOR_VERSION >= 14
- PyErr_SetString((PyObject *)sipGetPyType(sipModule, sipTypeID_std_exception), detail);
+ PyErr_SetString((PyObject *)sipGetPyType(sipMS, sipTypeID_std_exception), detail);
#else
PyErr_SetString(sipException_std_exception, detail);
#endif
diff --git a/test/imports/core_module.sip b/test/imports/core_module.sip
index f482b9a..dc2854f 100644
--- a/test/imports/core_module.sip
+++ b/test/imports/core_module.sip
@@ -13,20 +13,3 @@ class BaseClass
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
index 4569618..455ea0c 100644
--- a/test/imports/extras_module.sip
+++ b/test/imports/extras_module.sip
@@ -14,11 +14,6 @@ class Subclass : public BaseClass
public:
Subclass() {}
};
-
-namespace NameSpace
-{
- int get_extras_value();
-};
%End
@@ -27,20 +22,3 @@ 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
index 1f930c8..53807ee 100644
--- a/test/imports/test_imports.py
+++ b/test/imports/test_imports.py
@@ -9,13 +9,6 @@ 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/movable/movable_module.sip b/test/movable/movable_module.sip
index 2265cfb..413df85 100644
--- a/test/movable/movable_module.sip
+++ b/test/movable/movable_module.sip
@@ -14,9 +14,9 @@ template <TYPE>
%ConvertFromTypeCode
#if SIP_ABI_MAJOR_VERSION >= 14
- sipTypeID type_id = sipFindTypeID(sipModule, "TYPE*");
+ sipTypeID type_id = sipFindTypeID(sipMS, "TYPE*");
- return sipConvertFromType(sipModule, sipCpp->release(), type_id, Py_None);
+ return sipConvertFromType(sipMS, sipCpp->release(), type_id, Py_None);
#else
const sipTypeDef *td = sipFindType("TYPE*");
@@ -26,18 +26,18 @@ template <TYPE>
%ConvertToTypeCode
#if SIP_ABI_MAJOR_VERSION >= 14
- sipTypeID type_id = sipFindTypeID(sipModule, "TYPE");
+ sipTypeID type_id = sipFindTypeID(sipMS, "TYPE");
if (sipIsErr == NULL)
- return sipCanConvertToType(sipModule, sipPy, type_id, 0);
+ return sipCanConvertToType(sipMS, 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));
+ TYPE *t = reinterpret_cast<TYPE *>(sipConvertToType(sipMS, sipPy, type_id, sipTransferObj, 0, &state, sipIsErr));
- sipReleaseType(sipModule, t, type_id, state);
+ sipReleaseType(sipMS, t, type_id, state);
if (*sipIsErr)
return 0;
diff --git a/test/template_typedef/template_typedef_module.sip b/test/template_typedef/template_typedef_module.sip
index d7b480c..adf9c32 100644
--- a/test/template_typedef/template_typedef_module.sip
+++ b/test/template_typedef/template_typedef_module.sip
@@ -22,8 +22,8 @@ template<TYPE>
%ConvertFromTypeCode
#if SIP_ABI_MAJOR_VERSION >= 14
- //sipTypeID kp_type_id = sipFindTypeID(sipModule, "TYPE");
- sipTypeID kp_type_id = getSipType(sipModule, "TYPE");
+ //sipTypeID kp_type_id = sipFindTypeID(sipMS, "TYPE");
+ sipTypeID kp_type_id = getSipType(sipMS, "TYPE");
if (!kp_type_id)
return NULL;
@@ -48,7 +48,7 @@ template<TYPE>
TYPE *cpp = new TYPE(sipCpp->at(i));
#if SIP_ABI_MAJOR_VERSION >= 14
- PyObject *pobj = sipConvertFromNewType(sipModule, cpp, kp_type_id,
+ PyObject *pobj = sipConvertFromNewType(sipMS, cpp, kp_type_id,
sipTransferObj);
#else
PyObject *pobj = sipConvertFromNewType(cpp, kpTypeDef, sipTransferObj);
@@ -70,8 +70,8 @@ template<TYPE>
%ConvertToTypeCode
#if SIP_ABI_MAJOR_VERSION >= 14
- //sipTypeID kp_type_id = sipFindTypeID(sipModule, "TYPE");
- sipTypeID kp_type_id = getSipType(sipModule, "TYPE");
+ //sipTypeID kp_type_id = sipFindTypeID(sipMS, "TYPE");
+ sipTypeID kp_type_id = getSipType(sipMS, "TYPE");
if (!kp_type_id)
return 0;
@@ -96,7 +96,7 @@ template<TYPE>
int state;
#if SIP_ABI_MAJOR_VERSION >= 14
- TYPE *p = static_cast<TYPE *>(sipConvertToType(sipModule,
+ TYPE *p = static_cast<TYPE *>(sipConvertToType(sipMS,
PyList_GET_ITEM(sipPy, i), kp_type_id, sipTransferObj,
SIP_NOT_NONE, &state, sipIsErr));
#else
@@ -107,7 +107,7 @@ template<TYPE>
if (*sipIsErr) {
#if SIP_ABI_MAJOR_VERSION >= 14
- sipReleaseType(sipModule, p, kp_type_id, state);
+ sipReleaseType(sipMS, p, kp_type_id, state);
#else
sipReleaseType(p, kpTypeDef, state);
#endif
@@ -118,7 +118,7 @@ template<TYPE>
v->push_back(*p);
#if SIP_ABI_MAJOR_VERSION >= 14
- sipReleaseType(sipModule, p, kp_type_id, state);
+ sipReleaseType(sipMS, p, kp_type_id, state);
#else
sipReleaseType(p, kpTypeDef, state);
#endif
More information about the Neon-commits
mailing list