[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