# SPDX-License-Identifier: BSD-2-Clause

# Copyright (c) 2024 Phil Thompson <phil@riverbankcomputing.com>


import os

from ...exceptions import UserException
from ...version import SIP_VERSION_STR

from ..python_slots import (is_hash_return_slot, is_inplace_number_slot,
        is_inplace_sequence_slot, is_int_arg_slot, is_int_return_slot,
        is_multi_arg_slot, is_number_slot, is_rich_compare_slot,
        is_ssize_return_slot, is_void_return_slot, is_zero_arg_slot)
from ..scoped_name import STRIP_GLOBAL, STRIP_NONE
from ..specification import (AccessSpecifier, Argument, ArgumentType,
        ArrayArgument, CodeBlock, DocstringSignature, GILAction, IfaceFileType,
        KwArgs, MappedType, PyQtMethodSpecifier, PySlot, QualifierType,
        Transfer, ValueType, WrappedClass, WrappedEnum)
from ..utils import find_method, py_as_int, same_signature

from .formatters import (fmt_argument_as_cpp_type, fmt_argument_as_name,
        fmt_class_as_scoped_name, fmt_copying, fmt_enum_as_cpp_type,
        fmt_scoped_py_name, fmt_signature_as_cpp_declaration,
        fmt_signature_as_cpp_definition, fmt_signature_as_type_hint,
        fmt_value_list_as_cpp_expression)


def output_code(spec, bindings, project, buildable):
    """ Output the C/C++ code and add it to the given buildable. """

    module = spec.module
    py_debug = project.py_debug

    if spec.is_composite:
        source_name = os.path.join(buildable.build_dir,
                'sip' + module.py_name + 'cmodule.c')

        with CompilationUnit(source_name, "Composite module code.", module, project, buildable, sip_api_file=False) as sf:
            _composite_module_code(sf, spec, py_debug)
    else:
        _module_code(spec, bindings, project, py_debug, buildable)


def _internal_api_header(sf, spec, bindings, py_debug, name_cache_list):
    """ Generate the C++ internal module API header file and return its path
    name.
    """

    module = spec.module
    module_name = spec.module.py_name

    # The include files.
    sf.write(
f'''#ifndef _{module_name}API_H
#define _{module_name}API_H
''')

    _declare_limited_api(sf, py_debug, module=module)
    _include_sip_h(sf, module)

    if _pyqt5(spec) or _pyqt6(spec):
        sf.write(
'''
#include <QMetaType>
#include <QThread>
''')

    # Define the qualifiers.
    qualifier_defines = []

    _append_qualifier_defines(module, bindings, qualifier_defines)

    for imported_module in module.all_imports:
        _append_qualifier_defines(imported_module, bindings, qualifier_defines)

    if len(qualifier_defines) != 0:
        sf.write('\n/* These are the qualifiers that are enabled. */\n')

        for qualifier_define in qualifier_defines:
            sf.write(qualifier_define + '\n')

        sf.write('\n')

    # Generate references to (potentially) shared strings.
    no_intro = True

    for cached_name in name_cache_list:
        if not cached_name.used:
            continue

        if no_intro:
            sf.write(
'''
/*
 * Convenient names to refer to various strings defined in this module.
 * Only the class names are part of the public API.
 */
''')

            no_intro = False

        sf.write(
f'''#define {_cached_name_ref(cached_name, as_nr=True)} {cached_name.offset}
#define {_cached_name_ref(cached_name)} &sipStrings_{module_name}[{cached_name.offset}]
''')

    # These are common to all ABI versions.
    sf.write(
f'''
#define sipMalloc                   sipAPI_{module_name}->api_malloc
#define sipFree                     sipAPI_{module_name}->api_free
#define sipBuildResult              sipAPI_{module_name}->api_build_result
#define sipCallMethod               sipAPI_{module_name}->api_call_method
#define sipCallProcedureMethod      sipAPI_{module_name}->api_call_procedure_method
#define sipCallErrorHandler         sipAPI_{module_name}->api_call_error_handler
#define sipParseResultEx            sipAPI_{module_name}->api_parse_result_ex
#define sipParseResult              sipAPI_{module_name}->api_parse_result
#define sipParseArgs                sipAPI_{module_name}->api_parse_args
#define sipParseKwdArgs             sipAPI_{module_name}->api_parse_kwd_args
#define sipParsePair                sipAPI_{module_name}->api_parse_pair
#define sipInstanceDestroyed        sipAPI_{module_name}->api_instance_destroyed
#define sipInstanceDestroyedEx      sipAPI_{module_name}->api_instance_destroyed_ex
#define sipConvertFromSequenceIndex sipAPI_{module_name}->api_convert_from_sequence_index
#define sipConvertFromSliceObject   sipAPI_{module_name}->api_convert_from_slice_object
#define sipConvertFromVoidPtr       sipAPI_{module_name}->api_convert_from_void_ptr
#define sipConvertToVoidPtr         sipAPI_{module_name}->api_convert_to_void_ptr
#define sipAddException             sipAPI_{module_name}->api_add_exception
#define sipNoFunction               sipAPI_{module_name}->api_no_function
#define sipNoMethod                 sipAPI_{module_name}->api_no_method
#define sipAbstractMethod           sipAPI_{module_name}->api_abstract_method
#define sipBadClass                 sipAPI_{module_name}->api_bad_class
#define sipBadCatcherResult         sipAPI_{module_name}->api_bad_catcher_result
#define sipBadCallableArg           sipAPI_{module_name}->api_bad_callable_arg
#define sipBadOperatorArg           sipAPI_{module_name}->api_bad_operator_arg
#define sipTrace                    sipAPI_{module_name}->api_trace
#define sipTransferBack             sipAPI_{module_name}->api_transfer_back
#define sipTransferTo               sipAPI_{module_name}->api_transfer_to
#define sipSimpleWrapper_Type       sipAPI_{module_name}->api_simplewrapper_type
#define sipWrapper_Type             sipAPI_{module_name}->api_wrapper_type
#define sipWrapperType_Type         sipAPI_{module_name}->api_wrappertype_type
#define sipVoidPtr_Type             sipAPI_{module_name}->api_voidptr_type
#define sipGetPyObject              sipAPI_{module_name}->api_get_pyobject
#define sipGetAddress               sipAPI_{module_name}->api_get_address
#define sipGetMixinAddress          sipAPI_{module_name}->api_get_mixin_address
#define sipGetCppPtr                sipAPI_{module_name}->api_get_cpp_ptr
#define sipGetComplexCppPtr         sipAPI_{module_name}->api_get_complex_cpp_ptr
#define sipCallHook                 sipAPI_{module_name}->api_call_hook
#define sipEndThread                sipAPI_{module_name}->api_end_thread
#define sipRaiseUnknownException    sipAPI_{module_name}->api_raise_unknown_exception
#define sipRaiseTypeException       sipAPI_{module_name}->api_raise_type_exception
#define sipBadLengthForSlice        sipAPI_{module_name}->api_bad_length_for_slice
#define sipAddTypeInstance          sipAPI_{module_name}->api_add_type_instance
#define sipPySlotExtend             sipAPI_{module_name}->api_pyslot_extend
#define sipAddDelayedDtor           sipAPI_{module_name}->api_add_delayed_dtor
#define sipCanConvertToType         sipAPI_{module_name}->api_can_convert_to_type
#define sipConvertToType            sipAPI_{module_name}->api_convert_to_type
#define sipForceConvertToType       sipAPI_{module_name}->api_force_convert_to_type
#define sipConvertToEnum            sipAPI_{module_name}->api_convert_to_enum
#define sipConvertToBool            sipAPI_{module_name}->api_convert_to_bool
#define sipReleaseType              sipAPI_{module_name}->api_release_type
#define sipConvertFromType          sipAPI_{module_name}->api_convert_from_type
#define sipConvertFromNewType       sipAPI_{module_name}->api_convert_from_new_type
#define sipConvertFromNewPyType     sipAPI_{module_name}->api_convert_from_new_pytype
#define sipConvertFromEnum          sipAPI_{module_name}->api_convert_from_enum
#define sipGetState                 sipAPI_{module_name}->api_get_state
#define sipExportSymbol             sipAPI_{module_name}->api_export_symbol
#define sipImportSymbol             sipAPI_{module_name}->api_import_symbol
#define sipFindType                 sipAPI_{module_name}->api_find_type
#define sipBytes_AsChar             sipAPI_{module_name}->api_bytes_as_char
#define sipBytes_AsString           sipAPI_{module_name}->api_bytes_as_string
#define sipString_AsASCIIChar       sipAPI_{module_name}->api_string_as_ascii_char
#define sipString_AsASCIIString     sipAPI_{module_name}->api_string_as_ascii_string
#define sipString_AsLatin1Char      sipAPI_{module_name}->api_string_as_latin1_char
#define sipString_AsLatin1String    sipAPI_{module_name}->api_string_as_latin1_string
#define sipString_AsUTF8Char        sipAPI_{module_name}->api_string_as_utf8_char
#define sipString_AsUTF8String      sipAPI_{module_name}->api_string_as_utf8_string
#define sipUnicode_AsWChar          sipAPI_{module_name}->api_unicode_as_wchar
#define sipUnicode_AsWString        sipAPI_{module_name}->api_unicode_as_wstring
#define sipConvertFromConstVoidPtr  sipAPI_{module_name}->api_convert_from_const_void_ptr
#define sipConvertFromVoidPtrAndSize    sipAPI_{module_name}->api_convert_from_void_ptr_and_size
#define sipConvertFromConstVoidPtrAndSize   sipAPI_{module_name}->api_convert_from_const_void_ptr_and_size
#define sipWrappedTypeName(wt)      ((wt)->wt_td->td_cname)
#define sipDeprecated               sipAPI_{module_name}->api_deprecated
#define sipGetReference             sipAPI_{module_name}->api_get_reference
#define sipKeepReference            sipAPI_{module_name}->api_keep_reference
#define sipRegisterProxyResolver    sipAPI_{module_name}->api_register_proxy_resolver
#define sipRegisterPyType           sipAPI_{module_name}->api_register_py_type
#define sipTypeFromPyTypeObject     sipAPI_{module_name}->api_type_from_py_type_object
#define sipTypeScope                sipAPI_{module_name}->api_type_scope
#define sipResolveTypedef           sipAPI_{module_name}->api_resolve_typedef
#define sipRegisterAttributeGetter  sipAPI_{module_name}->api_register_attribute_getter
#define sipEnableAutoconversion     sipAPI_{module_name}->api_enable_autoconversion
#define sipInitMixin                sipAPI_{module_name}->api_init_mixin
#define sipExportModule             sipAPI_{module_name}->api_export_module
#define sipInitModule               sipAPI_{module_name}->api_init_module
#define sipGetInterpreter           sipAPI_{module_name}->api_get_interpreter
#define sipSetTypeUserData          sipAPI_{module_name}->api_set_type_user_data
#define sipGetTypeUserData          sipAPI_{module_name}->api_get_type_user_data
#define sipPyTypeDict               sipAPI_{module_name}->api_py_type_dict
#define sipPyTypeName               sipAPI_{module_name}->api_py_type_name
#define sipGetCFunction             sipAPI_{module_name}->api_get_c_function
#define sipGetMethod                sipAPI_{module_name}->api_get_method
#define sipFromMethod               sipAPI_{module_name}->api_from_method
#define sipGetDate                  sipAPI_{module_name}->api_get_date
#define sipFromDate                 sipAPI_{module_name}->api_from_date
#define sipGetDateTime              sipAPI_{module_name}->api_get_datetime
#define sipFromDateTime             sipAPI_{module_name}->api_from_datetime
#define sipGetTime                  sipAPI_{module_name}->api_get_time
#define sipFromTime                 sipAPI_{module_name}->api_from_time
#define sipIsUserType               sipAPI_{module_name}->api_is_user_type
#define sipCheckPluginForType       sipAPI_{module_name}->api_check_plugin_for_type
#define sipUnicodeNew               sipAPI_{module_name}->api_unicode_new
#define sipUnicodeWrite             sipAPI_{module_name}->api_unicode_write
#define sipUnicodeData              sipAPI_{module_name}->api_unicode_data
#define sipGetBufferInfo            sipAPI_{module_name}->api_get_buffer_info
#define sipReleaseBufferInfo        sipAPI_{module_name}->api_release_buffer_info
#define sipIsOwnedByPython          sipAPI_{module_name}->api_is_owned_by_python
#define sipIsDerivedClass           sipAPI_{module_name}->api_is_derived_class
#define sipGetUserObject            sipAPI_{module_name}->api_get_user_object
#define sipSetUserObject            sipAPI_{module_name}->api_set_user_object
#define sipRegisterEventHandler     sipAPI_{module_name}->api_register_event_handler
#define sipConvertToArray           sipAPI_{module_name}->api_convert_to_array
#define sipConvertToTypedArray      sipAPI_{module_name}->api_convert_to_typed_array
#define sipEnableGC                 sipAPI_{module_name}->api_enable_gc
#define sipPrintObject              sipAPI_{module_name}->api_print_object
#define sipLong_AsChar              sipAPI_{module_name}->api_long_as_char
#define sipLong_AsSignedChar        sipAPI_{module_name}->api_long_as_signed_char
#define sipLong_AsUnsignedChar      sipAPI_{module_name}->api_long_as_unsigned_char
#define sipLong_AsShort             sipAPI_{module_name}->api_long_as_short
#define sipLong_AsUnsignedShort     sipAPI_{module_name}->api_long_as_unsigned_short
#define sipLong_AsInt               sipAPI_{module_name}->api_long_as_int
#define sipLong_AsUnsignedInt       sipAPI_{module_name}->api_long_as_unsigned_int
#define sipLong_AsLong              sipAPI_{module_name}->api_long_as_long
#define sipLong_AsUnsignedLong      sipAPI_{module_name}->api_long_as_unsigned_long
#define sipLong_AsLongLong          sipAPI_{module_name}->api_long_as_long_long
#define sipLong_AsUnsignedLongLong  sipAPI_{module_name}->api_long_as_unsigned_long_long
#define sipLong_AsSizeT             sipAPI_{module_name}->api_long_as_size_t
#define sipVisitWrappers            sipAPI_{module_name}->api_visit_wrappers
#define sipRegisterExitNotifier     sipAPI_{module_name}->api_register_exit_notifier
''')

    # These are dependent on the specific ABI version.
    if spec.abi_version >= (13, 0):
        # ABI v13.6 and later.
        if spec.abi_version >= (13, 6):
            sf.write(
f'''#define sipPyTypeDictRef            sipAPI_{module_name}->api_py_type_dict_ref
''')

        # ABI v13.1 and later.
        if spec.abi_version >= (13, 1):
            sf.write(
f'''#define sipNextExceptionHandler     sipAPI_{module_name}->api_next_exception_handler
''')

        # ABI v13.0 and later. */
        sf.write(
f'''#define sipIsEnumFlag               sipAPI_{module_name}->api_is_enum_flag
#define sipConvertToTypeUS          sipAPI_{module_name}->api_convert_to_type_us
#define sipForceConvertToTypeUS     sipAPI_{module_name}->api_force_convert_to_type_us
#define sipReleaseTypeUS            sipAPI_{module_name}->api_release_type_us
''')
    else:
        # ABI v12.13 and later.
        if spec.abi_version >= (12, 13):
            sf.write(
f'''#define sipPyTypeDictRef            sipAPI_{module_name}->api_py_type_dict_ref
''')

        # ABI v12.9 and later.
        if spec.abi_version >= (12, 9):
            sf.write(
f'''#define sipNextExceptionHandler     sipAPI_{module_name}->api_next_exception_handler
''')

        # ABI v12.8 and earlier.
        sf.write(
f'''#define sipSetNewUserTypeHandler    sipAPI_{module_name}->api_set_new_user_type_handler
#define sipGetFrame                 sipAPI_{module_name}->api_get_frame
#define sipSetDestroyOnExit         sipAPI_{module_name}->api_set_destroy_on_exit
#define sipEnableOverflowChecking   sipAPI_{module_name}->api_enable_overflow_checking
#define sipIsAPIEnabled             sipAPI_{module_name}->api_is_api_enabled
#define sipClearAnySlotReference    sipAPI_{module_name}->api_clear_any_slot_reference
#define sipConnectRx                sipAPI_{module_name}->api_connect_rx
#define sipConvertRx                sipAPI_{module_name}->api_convert_rx
#define sipDisconnectRx             sipAPI_{module_name}->api_disconnect_rx
#define sipFreeSipslot              sipAPI_{module_name}->api_free_sipslot
#define sipInvokeSlot               sipAPI_{module_name}->api_invoke_slot
#define sipInvokeSlotEx             sipAPI_{module_name}->api_invoke_slot_ex
#define sipSameSlot                 sipAPI_{module_name}->api_same_slot
#define sipSaveSlot                 sipAPI_{module_name}->api_save_slot
#define sipVisitSlot                sipAPI_{module_name}->api_visit_slot
''')

    if spec.abi_version >= (12, 8):
        # ABI v12.8 and later.
        sf.write(
f'''#define sipIsPyMethod               sipAPI_{module_name}->api_is_py_method_12_8
''')
    else:
        # ABI v12.7 and earlier.
        sf.write(
f'''#define sipIsPyMethod               sipAPI_{module_name}->api_is_py_method
''')

    # The name strings.
    sf.write(
f'''
/* The strings used by this module. */
extern const char sipStrings_{module_name}[];
''')

    _module_api(sf, spec, bindings)

    sf.write(
f'''
/* The SIP API, this module's API and the APIs of any imported modules. */
extern const sipAPIDef *sipAPI_{module_name};
extern sipExportedModuleDef sipModuleAPI_{module_name};
''')

    if len(module.needed_types) != 0:
        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(sf, spec, 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(spec) or _pyqt6(spec):
        sf.write(
f'''
typedef const QMetaObject *(*sip_qt_metaobject_func)(sipSimpleWrapper *, sipTypeDef *);
extern sip_qt_metaobject_func sip_{module_name}_qt_metaobject;

typedef int (*sip_qt_metacall_func)(sipSimpleWrapper *, 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 **);
extern sip_qt_metacast_func sip_{module_name}_qt_metacast;
''')

    # Handwritten code.
    sf.write_code(spec.exported_header_code)
    sf.write_code(module.module_header_code)

    # Make sure any header code needed by the default exception is included.
    if module.default_exception is not None:
        sf.write_code(module.default_exception.iface_file.type_header_code)

    # Note that we don't forward declare the virtual handlers.  This is because
    # we would need to #include everything needed for their argument types.
    sf.write('\n#endif\n')


def _make_part_name(buildable, module_name, part_nr, source_suffix):
    """ Return the filename of a source code part on the heap. """

    return os.path.join(buildable.build_dir,
            f'sip{module_name}part{part_nr}{source_suffix}')


def _composite_module_code(sf, spec, py_debug):
    """ Output the code for a composite module. """

    module = spec.module

    _declare_limited_api(sf, py_debug)
    _include_sip_h(sf, module)

    sf.write(
'''

static void sip_import_component_module(PyObject *d, const char *name)
{
    PyObject *mod;

    PyErr_Clear();

    mod = PyImport_ImportModule(name);

    /*
     * Note that we don't complain if the module can't be imported.  This
     * is a favour to Linux distro packagers who like to split PyQt into
     * different sub-packages.
     */
    if (mod)
    {
        PyDict_Merge(d, PyModule_GetDict(mod), 0);
        Py_DECREF(mod);
    }
}
''')

    _module_docstring(sf, module)
    _module_init_start(sf, spec)
    _module_definition(sf, module)

    sf.write(
'''
    PyObject *sipModule, *sipModuleDict;

    if ((sipModule = PyModule_Create(&sip_module_def)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    sipModuleDict = PyModule_GetDict(sipModule);

''')

    for mod in module.all_imports:
        sf.write(
f'    sip_import_component_module(sipModuleDict, "{mod.fq_py_name}");\n')

    sf.write(
'''
    PyErr_Clear();

    return sipModule;
}
''')


def _module_code(spec, bindings, project, py_debug, buildable):
    """ Generate the C/C++ code for a module. """

    module = spec.module
    module_name = module.py_name
    parts = bindings.concatenate

    source_suffix = bindings.source_suffix
    if source_suffix is None:
        source_suffix = '.c' if spec.c_bindings else '.cpp'

    # Calculate the number of files in each part.
    if parts:
        nr_files = 1

        for iface_file in spec.iface_files:
            if iface_file.module is module and iface_file.type is not IfaceFileType.EXCEPTION:
                nr_files += 1

        max_per_part = (nr_files + parts - 1) // parts
        files_in_part = 1
        this_part = 0

        source_name = _make_part_name(buildable, module_name, 0, source_suffix)
    else:
        source_name = os.path.join(buildable.build_dir,
                'sip' + module_name + 'cmodule' + source_suffix)

    sf = CompilationUnit(source_name, "Module code.", module, project,
            buildable)

    # Include the library headers for types used by virtual handlers, module
    # level functions, module level variables, exceptions and Qt meta types.
    _used_includes(sf, module.used)

    sf.write_code(module.unit_postinclude_code)

    # If there should be a Qt support API then generate stubs values for the
    # optional parts.  These should be undefined in %ModuleCode if a C++
    # implementation is provided.
    if spec.abi_version < (13, 0) and _module_supports_qt(spec):
        sf.write(
'''
#define sipQtCreateUniversalSignal          0
#define sipQtFindUniversalSignal            0
#define sipQtEmitSignal                     0
#define sipQtConnectPySignal                0
#define sipQtDisconnectPySignal             0
''')

    # Transform the name cache.
    name_cache_list = _name_cache_as_list(spec.name_cache)

    # Define the names.
    _name_cache(sf, spec, name_cache_list)

    # Generate the C++ code blocks.
    sf.write_code(module.module_code)

    # Generate any virtual handlers.
    for handler in spec.virtual_handlers:
        _virtual_handler(sf, spec, handler)

    # Generate any virtual error handlers.
    for virtual_error_handler in spec.virtual_error_handlers:
        if virtual_error_handler.module is module:
            self_name = _use_in_code(virtual_error_handler.code, 'sipPySelf')
            state_name = _use_in_code(virtual_error_handler.code,
                    'sipGILState')

            sf.write(
f'''

void sipVEH_{module_name}_{virtual_error_handler.name}(sipSimpleWrapper *{self_name}, sip_gilstate_t {state_name})
{{
''')

            sf.write_code(virtual_error_handler.code)

            sf.write('}\n')

    # Generate the global functions.
    slot_extenders = False

    for member in module.global_functions:
        if member.py_slot is None:
            _ordinary_function(sf, spec, 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(sf, spec, bindings, member)
                    slot_extenders = True
                    break

    # Generate the global functions for any hidden namespaces.
    for klass in spec.classes:
        if klass.iface_file.module is module and klass.is_hidden_namespace:
            for member in klass.members:
                if member.py_slot is None:
                    _ordinary_function(sf, spec, bindings, member, scope=klass)

    # Generate any class specific __init__ or slot extenders.
    init_extenders = False

    for klass in module.proxies:
        if len(klass.ctors) != 0:
            _type_init(sf, spec, bindings, klass)
            init_extenders = True

        for member in klass.members:
            _py_slot(sf, spec, bindings, member, scope=klass)
            slot_extenders = True

    # Generate any __init__ extender table.
    if init_extenders:
        sf.write(
'''
static sipInitExtenderDef initExtenders[] = {
''')

        first_field = '-1, ' if spec.abi_version < (13, 0) else ''

        for klass in module.proxies:
            if len(klass.ctors) != 0:
                klass_name = klass.iface_file.fq_cpp_name.as_word
                encoded_type = _encoded_type(module, klass)

                sf.write(f'    {{{first_field}init_type_{klass_name}, {encoded_type}, SIP_NULLPTR}},\n')

        sf.write(
f'''    {{{first_field}SIP_NULLPTR, {{0, 0, 0}}, SIP_NULLPTR}}
}};
''')

    # Generate any slot extender table.
    if slot_extenders:
        sf.write(
'''
static sipPySlotExtenderDef slotExtenders[] = {\n''')

        for member in module.global_functions:
            if member.py_slot is None:
                continue

            for overload in module.overloads:
                if overload.common is member:
                    member_name = member.py_name
                    slot_name = _get_slot_name(member.py_slot)

                    sf.write(
f'    {{(void *)slot_{member_name}, {slot_name}, {{0, 0, 0}}}},\n')

                    break

        for klass in module.proxies:
            for member in klass.members:
                klass_name = klass.iface_file.fq_cpp_name.as_word
                member_name = member.py_name
                slot_name = _get_slot_name(member.py_slot)
                encoded_type = _encoded_type(module, klass)

                sf.write(f'    {{(void *)slot_{klass_name}_{member_name}, {slot_name}, {encoded_type}}},\n')

        sf.write(
'''    {SIP_NULLPTR, (sipPySlotType)0, {0, 0, 0}}
};
''')

    # Generate the global access functions.
    _access_functions(sf, spec)

    # Generate any sub-class convertors.
    nr_subclass_convertors = _subclass_convertors(sf, spec, module)

    # Generate the external classes table if needed.
    has_external = False

    for klass in spec.classes:
        if not klass.external:
            continue

        if klass.iface_file.module is not module:
            continue

        if not has_external:
            sf.write(
'''

/* This defines each external type declared in this module, */
static sipExternalTypeDef externalTypesTable[] = {
''')

            has_external = True

        type_nr = klass.iface_file.type_nr
        klass_py = klass.iface_file.fq_cpp_name.as_py

        sf.write(f'    {{{type_nr}, "{klass_py}"}},\n')

    if has_external:
        sf.write(
'''    {-1, SIP_NULLPTR}
};
''')

    # 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(sf, spec, 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:
                member_name = member.py_name
                slot_name = _get_slot_name(member.py_slot)

                sf.write(f'    {{(void *)slot_{enum_name}_{member_name}, {slot_name}}},\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 = []

    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 spec.abi_version >= (13, 0):
            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'

            sf.write(
f'    {{{{-1, SIP_NULLPTR, 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')

    if spec.abi_version >= (13, 0):
        nr_enum_members = -1
    else:
        nr_enum_members = _enum_member_table(sf, spec)

    # Generate the types table.
    if len(module.needed_types) != 0:
        _types_table(sf, module, needed_enums)

    # Generate the typedefs table.
    if module.nr_typedefs > 0:
        sf.write(
'''

/*
 * These define each typedef in this module.
 */
static sipTypedefDef typedefsTable[] = {
''')

        for typedef in spec.typedefs:
            if typedef.module is not module:
                continue

            cpp_name = typedef.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)

            sf.write(f'    {{"{cpp_name}", "')

            # The default behaviour isn't right in a couple of cases.
            # TODO: is this still true?
            if typedef.type.type is ArgumentType.LONGLONG:
                sf.write('long long')
            elif typedef.type.type is ArgumentType.ULONGLONG:
                sf.write('unsigned long long')
            else:
                sf.write(
                        fmt_argument_as_cpp_type(spec, typedef.type,
                                strip=STRIP_GLOBAL, use_typename=False))

            sf.write('"},\n')

        sf.write('};\n')

    # Generate the error handlers table.
    has_virtual_error_handlers = False

    for handler in spec.virtual_error_handlers:
        if handler.module is not module:
            continue

        if not has_virtual_error_handlers:
            has_virtual_error_handlers = True

            sf.write(
'''

/*
 * This defines the virtual error handlers that this module implements and
 * can be used by other modules.
 */
static sipVirtErrorHandlerDef virtErrorHandlersTable[] = {
''')

        sf.write(f'    {{"{handler.name}", sipVEH_{module_name}_{handler.name}}},\n')

    if has_virtual_error_handlers:
        sf.write(
'''    {SIP_NULLPTR, SIP_NULLPTR}
};
''')

    # 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}
};
''')

    # Generate the table of sub-class convertors
    if nr_subclass_convertors > 0:
        sf.write(
'''

/* This defines the class sub-convertors that this module defines. */
static sipSubClassConvertorDef convertorsTable[] = {
''')

        for klass in spec.classes:
            if klass.iface_file.module is not module:
                continue

            if klass.convert_to_subclass_code is None:
                continue

            klass_name = klass.iface_file.fq_cpp_name.as_word
            encoded_type = _encoded_type(module, klass.subclass_base)

            sf.write(f'    {{sipSubClass_{klass_name}, {encoded_type}, SIP_NULLPTR}},\n')

        sf.write(
'''    {SIP_NULLPTR, {0, 0, 0}, SIP_NULLPTR}
};
''')

    # Generate any license information.
    if module.license is not None:
        license = module.license
        licensee = 'SIP_NULLPTR' if license.licensee is None else '"' + license.licensee + '"'
        timestamp = 'SIP_NULLPTR' if license.timestamp is None else '"' + license.timestamp + '"'
        signature = 'SIP_NULLPTR' if license.signature is None else '"' + license.signature + '"'

        sf.write(
f'''

/* Define the module's license. */
static sipLicenseDef module_license = {{
    "{license.type}",
    {licensee},
    {timestamp},
    {signature}
}};
''')

    # Generate each instance table.
    is_inst_class = _class_instances(sf, spec)
    is_inst_voidp = _void_pointer_instances(sf, spec)
    is_inst_char = _char_instances(sf, spec)
    is_inst_string = _string_instances(sf, spec)
    is_inst_int = _int_instances(sf, spec)
    is_inst_long = _long_instances(sf, spec)
    is_inst_ulong = _unsigned_long_instances(sf, spec)
    is_inst_longlong = _long_long_instances(sf, spec)
    is_inst_ulonglong = _unsigned_long_long_instances(sf, spec)
    is_inst_double = _double_instances(sf, spec)

    # Generate any exceptions support.
    if bindings.exceptions:
        if module.nr_exceptions > 0:
            sf.write(
f'''

PyObject *sipExportedExceptions_{module_name}[{module.nr_exceptions + 1}];
''')

        if _abi_has_next_exception_handler(spec):
            _exception_handler(sf, spec)

    # Generate any Qt support API.
    if spec.abi_version < (13, 0) and _module_supports_qt(spec):
        sf.write(
f'''

/* This defines the Qt support API. */

static sipQtAPI qtAPI = {{
    &sipExportedTypes_{module_name}[{spec.pyqt_qobject.iface_file.type_nr}],
    sipQtCreateUniversalSignal,
    sipQtFindUniversalSignal,
    sipQtCreateUniversalSlot,
    sipQtDestroyUniversalSlot,
    sipQtFindSlot,
    sipQtConnect,
    sipQtDisconnect,
    sipQtSameSignalSlotName,
    sipQtFindSipslot,
    sipQtEmitSignal,
    sipQtConnectPySignal,
    sipQtDisconnectPySignal
}};
''')

    imports_table = _optional_ptr(len(module.all_imports) != 0, 'importsTable')
    exported_types = _optional_ptr(len(module.needed_types) != 0,
            'sipExportedTypes_' + module_name)
    external_types = _optional_ptr(has_external, 'externalTypesTable')
    typedefs_table = _optional_ptr(module.nr_typedefs != 0, 'typedefsTable')

    sf.write(
f'''

/* This defines this module. */
sipExportedModuleDef sipModuleAPI_{module_name} = {{
    SIP_NULLPTR,
    {spec.abi_version[1]},
    sipNameNr_{_get_normalised_cached_name(module.fq_py_name)},
    0,
    sipStrings_{module_name},
    {imports_table},
''')

    if spec.abi_version < (13, 0):
        qt_api = _optional_ptr(_module_supports_qt(spec), '&qtAPI')
        sf.write(f'    {qt_api},\n')

    sf.write(
f'''    {len(module.needed_types)},
    {exported_types},
    {external_types},
''')

    if nr_enum_members >= 0:
        enum_members = _optional_ptr(nr_enum_members > 0, 'enummembers')
        sf.write(
f'''    {nr_enum_members},
    {enum_members},
''')

    veh_table = _optional_ptr(has_virtual_error_handlers,
            'virtErrorHandlersTable')
    convertors = _optional_ptr(nr_subclass_convertors > 0, 'convertorsTable')
    type_instances = _optional_ptr(is_inst_class, 'typeInstances')
    void_ptr_instances = _optional_ptr(is_inst_voidp, 'voidPtrInstances')
    char_instances = _optional_ptr(is_inst_char, 'charInstances')
    string_instances = _optional_ptr(is_inst_string, 'stringInstances')
    int_instances = _optional_ptr(is_inst_int, 'intInstances')
    long_instances = _optional_ptr(is_inst_long, 'longInstances')
    unsigned_long_instances = _optional_ptr(is_inst_ulong,
            'unsignedLongInstances')
    long_long_instances = _optional_ptr(is_inst_longlong, 'longLongInstances')
    unsigned_long_long_instances = _optional_ptr(is_inst_ulonglong,
            'unsignedLongLongInstances')
    double_instances = _optional_ptr(is_inst_double, 'doubleInstances')
    module_license = _optional_ptr(module.license is not None,
            '&module_license')
    exported_exceptions = _optional_ptr(module.nr_exceptions > 0,
            'sipExportedExceptions_' + module_name)
    slot_extender_table = _optional_ptr(slot_extenders, 'slotExtenders')
    init_extender_table = _optional_ptr(init_extenders, 'initExtenders')
    delayed_dtors = _optional_ptr(module.has_delayed_dtors, 'sipDelayedDtors')

    sf.write(
f'''    {module.nr_typedefs},
    {typedefs_table},
    {veh_table},
    {convertors},
    {{{type_instances}, {void_ptr_instances}, {char_instances}, {string_instances}, {int_instances}, {long_instances}, {unsigned_long_instances}, {long_long_instances}, {unsigned_long_long_instances}, {double_instances}}},
    {module_license},
    {exported_exceptions},
    {slot_extender_table},
    {init_extender_table},
    {delayed_dtors},
    SIP_NULLPTR,
''')

    if spec.abi_version < (13, 0):
        # The unused version support.
        sf.write(
'''    SIP_NULLPTR,
    SIP_NULLPTR,
''')

    exception_handler = _optional_ptr(
            (_abi_has_next_exception_handler(spec) and bindings.exceptions and module.nr_exceptions > 0),
            'sipExceptionHandler_' + module_name)

    sf.write(
f'''    {exception_handler},
}};
''')

    _module_docstring(sf, module)

    # Generate the storage for the external API pointers.
    sf.write(
f'''

/* The SIP API and the APIs of any imported modules. */
const sipAPIDef *sipAPI_{module_name};
''')

    if _pyqt5(spec) or _pyqt6(spec):
        sf.write(
f'''
sip_qt_metaobject_func sip_{module_name}_qt_metaobject;
sip_qt_metacall_func sip_{module_name}_qt_metacall;
sip_qt_metacast_func sip_{module_name}_qt_metacast;
''')

    # Generate the Python module initialisation function.
    _module_init_start(sf, spec)

    # Generate the global functions.
    sf.write('    static PyMethodDef sip_methods[] = {\n')

    _global_function_table_entries(sf, spec, bindings, module.global_functions)

    # Generate the global functions for any hidden namespaces.
    for klass in spec.classes:
        if klass.iface_file.module is module and klass.is_hidden_namespace:
            _global_function_table_entries(sf, spec, bindings, klass.members)

    sf.write(
'''        {SIP_NULLPTR, SIP_NULLPTR, 0, SIP_NULLPTR}
    };
''')

    _module_definition(sf, module, method_table='sip_methods')

    sf.write('\n    PyObject *sipModule, *sipModuleDict;\n')

    if spec.sip_module:
        sf.write('    PyObject *sip_sipmod, *sip_capiobj;\n\n')

    # Generate any pre-initialisation code.
    sf.write_code(module.preinitialisation_code)

    sf.write(
'''    /* Initialise the module and get it's dictionary. */
    if ((sipModule = PyModule_Create(&sip_module_def)) == SIP_NULLPTR)
        return SIP_NULLPTR;

    sipModuleDict = PyModule_GetDict(sipModule);

''')

    _sip_api(sf, spec)

    # Generate any initialisation code.
    sf.write_code(module.initialisation_code)

    abi_major, abi_minor = spec.abi_version

    sf.write(
f'''    /* Export the module and publish it's API. */
    if (sipExportModule(&sipModuleAPI_{module_name}, {abi_major}, {abi_minor}, 0) < 0)
    {{
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }}
''')

    if _pyqt5(spec) or _pyqt6(spec):
        # Import the helpers.
        sf.write(
f'''
    sip_{module_name}_qt_metaobject = (sip_qt_metaobject_func)sipImportSymbol("qtcore_qt_metaobject");
    sip_{module_name}_qt_metacall = (sip_qt_metacall_func)sipImportSymbol("qtcore_qt_metacall");
    sip_{module_name}_qt_metacast = (sip_qt_metacast_func)sipImportSymbol("qtcore_qt_metacast");

    if (!sip_{module_name}_qt_metacast)
        Py_FatalError("Unable to import qtcore_qt_metacast");

''')

    sf.write(
f'''    /* Initialise the module now all its dependencies have been set up. */
    if (sipInitModule(&sipModuleAPI_{module_name}, sipModuleDict) < 0)
    {{
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }}
''')

    _types_inline(sf, spec)
    _py_objects(sf, spec)

    # Create any exception objects.
    for exception in spec.exceptions:
        if exception.iface_file.module is not module:
            continue

        if exception.exception_nr < 0:
            continue

        if exception.builtin_base_exception is not None:
            exception_type = 'PyExc_' + exception.builtin_base_exception
        else:
            exception_type = 'sipException_' + exception.defined_base_exception.iface_file.fq_cpp_name.as_word

        sf.write(
f'''
    if ((sipExportedExceptions_{module_name}[{exception.exception_nr}] = PyErr_NewException(
            "{module_name}.{exception.py_name}",
            {exception_type}, SIP_NULLPTR)) == SIP_NULLPTR || PyDict_SetItemString(sipModuleDict, "{exception.py_name}", sipExportedExceptions_{module_name}[{exception.exception_nr}]) < 0)
    {{
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }}
''')

    if module.nr_exceptions > 0:
        sf.write(
f'''
    sipExportedExceptions_{module_name}[{module.nr_exceptions}] = SIP_NULLPTR;
''')

    # Generate the enum meta-type registrations for PyQt6 so that they can be
    # used in queued connections.
    if _pyqt6(spec):
        for enum in spec.enums:
            if enum.module is not module or enum.fq_cpp_name is None:
                continue

            if enum.is_protected:
                continue

            if isinstance(enum.scope, WrappedClass) and enum.scope.pyqt_no_qmetaobject:
                continue

            sf.write(f'    qMetaTypeId<{enum.fq_cpp_name.as_cpp}>();\n')

    # Generate any post-initialisation code. */
    sf.write_code(module.postinitialisation_code)

    sf.write(
'''
    return sipModule;
}
''')

    # Generate the interface source files.
    for iface_file in spec.iface_files:
        if iface_file.module is module and iface_file.type is not IfaceFileType.EXCEPTION:
            need_postinc = False
            use_sf = None

            if parts:
                if files_in_part == max_per_part:
                    # Close the old part.
                    sf.close()

                    # Create a new one.
                    files_in_part = 1
                    this_part += 1

                    source_name = _make_part_name(buildable, module_name,
                            this_part, source_suffix)
                    sf = CompilationUnit(source_name, "Module code.", module,
                            project, buildable)

                    need_postinc = True
                else:
                    files_in_part += 1

                if iface_file.file_extension is None:
                    # The interface file should use this source file rather
                    # than create one of its own.
                    use_sf = sf

            _iface_file_cpp(spec, bindings, project, buildable, py_debug,
                    iface_file, need_postinc, source_suffix, use_sf)

    sf.close()

    header_name = os.path.join(buildable.build_dir, f'sipAPI{module_name}.h')

    with SourceFile(header_name, "Internal module API header file.", module, project, buildable.headers) as sf:
        _internal_api_header(sf, spec, bindings, py_debug, name_cache_list)


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

    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.
        for prev_name in name_cache_list:
            prev_name_len = len(prev_name.name)

            if prev_name_len <= name_len:
                break

            if not prev_name.used or prev_name.is_substring:
                continue

            if prev_name.name.endswith(cached_name.name):
                cached_name.is_substring = True
                cached_name.offset = prev_name.offset + prev_name_len - name_len;
                break

        if not cached_name.is_substring:
            cached_name.offset = offset
            offset += name_len + 1

    return name_cache_list


def _name_cache(sf, spec, name_cache_list):
    """ Generate the name cache definition. """

    sf.write(
f'''
/* Define the strings used by this module. */
const char sipStrings_{spec.module.py_name}[] = {{
''')

    for name in name_cache_list:
        if not name.used or name.is_substring:
            continue

        sf.write('    ')

        for ch in name.name:
            sf.write(f"'{ch}', ")

        sf.write('0,\n')

    sf.write('};\n')


def _types_table(sf, module, needed_enums):
    """ Generate the types table for a module. """

    module_name = module.py_name

    sf.write(
f'''

/*
 * This defines each type in this module.
 */
sipTypeDef *sipExportedTypes_{module_name}[] = {{
''')

    for needed_type in module.needed_types:
        if needed_type.type is ArgumentType.CLASS:
            klass = needed_type.definition

            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')

        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')

        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 _sip_api(sf, spec):
    """ Generate the code to get the sip API. """

    sip_module_name = spec.sip_module
    module_name = spec.module.py_name

    if sip_module_name:
        # Note that we don't use PyCapsule_Import() because it doesn't handle
        # package.module.attribute.

        sf.write(
f'''    /* Get the SIP module's API. */
    if ((sip_sipmod = PyImport_ImportModule("{sip_module_name}")) == SIP_NULLPTR)
    {{
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }}

    sip_capiobj = PyDict_GetItemString(PyModule_GetDict(sip_sipmod), "_C_API");
    Py_DECREF(sip_sipmod);

    if (sip_capiobj == SIP_NULLPTR || !PyCapsule_CheckExact(sip_capiobj))
    {{
        PyErr_SetString(PyExc_AttributeError, "{sip_module_name}._C_API is missing or has the wrong type");
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }}

''')

        if spec.c_bindings:
            c_api = f'(const sipAPIDef *)PyCapsule_GetPointer(sip_capiobj, "{sip_module_name}._C_API")'
        else:
            c_api = f'reinterpret_cast<const sipAPIDef *>(PyCapsule_GetPointer(sip_capiobj, "{sip_module_name}._C_API"))'

        sf.write(
f'''    sipAPI_{module_name} = {c_api};

    if (sipAPI_{module_name} == SIP_NULLPTR)
    {{
        Py_DECREF(sipModule);
        return SIP_NULLPTR;
    }}

''')
    else:
        # If there is no sip module name then we are getting the API from a
        # non-shared sip module.
        sf.write(
f'''    if ((sipAPI_{module_name} = sip_init_library(sipModuleDict)) == SIP_NULLPTR)
        return SIP_NULLPTR;

''')


def _module_init_start(sf, spec, generate_c=True):
    """ Generate the start of the Python module initialisation function. """

    if spec.is_composite or spec.c_bindings:
        extern_c = ''
        arg_type = 'void'
    else:
        extern_c = 'extern "C" '
        arg_type = ''

    module_name = spec.module.py_name

    sf.write(
f'''

/* The Python module initialisation function. */
#if defined(SIP_STATIC_MODULE)
{extern_c}PyObject *PyInit_{module_name}({arg_type})
#else
PyMODINIT_FUNC PyInit_{module_name}({arg_type})
#endif
{{
''')


def _module_definition(sf, module, method_table='SIP_NULLPTR'):
    """ Generate the module definition structure. """

    if module.docstring is None:
        docstring_ref = 'SIP_NULLPTR'
    else:
        docstring_ref = f'doc_mod_{module.py_name}'

    sf.write(
f'''    static PyModuleDef sip_module_def = {{
        PyModuleDef_HEAD_INIT,
        "{module.fq_py_name}",
        {docstring_ref},
        -1,
        {method_table},
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR,
        SIP_NULLPTR
    }};
''')


def _subclass_convertors(sf, spec, module):
    """ Generate all the sub-class convertors for a module and return the
    number of them.
    """

    nr_subclass_convertors = 0

    for klass in spec.classes:
        if klass.iface_file.module is not module:
            continue

        if klass.convert_to_subclass_code is None:
            continue

        sf.write(
'''

/* Convert to a sub-class if possible. */
''')

        klass_name = klass.iface_file.fq_cpp_name.as_word
        base_cpp = klass.subclass_base.iface_file.fq_cpp_name.as_cpp

        if not spec.c_bindings:
            sf.write(
f'extern "C" {{static const sipTypeDef *sipSubClass_{klass_name}(void **);}}\n')

        # Allow the deprecated use of sipClass rather than sipType.
        if _is_used_in_code(klass.convert_to_subclass_code, 'sipClass'):
            decl = 'sipWrapperType *sipClass'
            result = '(sipClass ? sipClass->wt_td : 0)'
        else:
            decl = 'const sipTypeDef *sipType'
            result = 'sipType'

        sf.write(
f'''static const sipTypeDef *sipSubClass_{klass_name}(void **sipCppRet)
{{
    {base_cpp} *sipCpp = reinterpret_cast<{base_cpp} *>(*sipCppRet);
    {decl};

''')

        sf.write_code(klass.convert_to_subclass_code)

        sf.write(
f'''
    return {result};
}}
''')

        nr_subclass_convertors += 1

    return nr_subclass_convertors


def _encoded_type(module, klass, last=False):
    """ Return the structure representing an encoded type. """

    klass_module = klass.iface_file.module

    fields = [str(klass.iface_file.type_nr)]

    if klass_module is module:
        fields.append('255')
    else:
        for module_nr, imported_module in enumerate(module.all_imports):
            if imported_module is klass_module:
                fields.append(str(module_nr))
                break

    fields.append(str(int(last)))

    return '{' + ', '.join(fields) + '}'


def _ordinary_function(sf, spec, bindings, member, scope=None):
    """ Generate an ordinary function. """

    member_name = member.py_name.name

    if scope is None:
        overloads = spec.module.overloads
        py_scope = None
        py_scope_prefix = ''
    else:
        overloads = scope.overloads
        py_scope = _py_scope(scope)
        py_scope_prefix = '' if py_scope is None else py_scope.iface_file.fq_cpp_name.as_word + '_'

    sf.write('\n\n')

    # Generate the docstrings.
    if _has_member_docstring(bindings, member, overloads):
        sf.write(f'PyDoc_STRVAR(doc_{py_scope_prefix}{member_name}, "')
        has_auto_docstring = _member_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 = ''

    sip_self_unused = False

    if py_scope is None:
        if not spec.c_bindings:
            sf.write(f'extern "C" {{static PyObject *func_{member_name}(PyObject *, PyObject *{kw_fw_decl});}}\n')
            sip_self = ''
        else:
            sip_self = 'sipSelf'
            sip_self_unused = True;

        sf.write(f'static PyObject *func_{member_name}(PyObject *{sip_self}, PyObject *sipArgs{kw_decl})\n')
    else:
        if not spec.c_bindings:
            sf.write(f'extern "C" {{static PyObject *meth_{py_scope_prefix}{member_name}(PyObject *, PyObject *{kw_fw_decl});}}\n')

        sf.write(f'static PyObject *meth_{py_scope_prefix}{member_name}(PyObject *, PyObject *sipArgs{kw_decl})\n')

    sf.write('{\n')

    need_intro = True

    for overload in overloads:
        if overload.common is not member:
            continue

        if member.no_arg_parser:
            sf.write_code(overload.method_code)
            break

        if need_intro:
            sf.write('    PyObject *sipParseErr = SIP_NULLPTR;\n')

            if sip_self_unused:
                sf.write(
'''
    (void)sipSelf;
''')

            need_intro = False

        _function_body(sf, spec, bindings, scope, overload)

    if not need_intro:
        sf.write(
f'''
    /* Raise an exception if the arguments couldn't be parsed. */
    sipNoFunction(sipParseErr, {_cached_name_ref(member.py_name)}, ''')

        if has_auto_docstring:
            sf.write(f'doc_{py_scope_prefix}{member_name}')
        else:
            sf.write('SIP_NULLPTR')

        sf.write(''');

    return SIP_NULLPTR;
''')

    sf.write('}\n')


def _enum_member_table(sf, spec, scope=None):
    """ Generate the table of enum members for a scope.  Return the number of
    them.
    """

    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'    {{{_cached_name_ref(enum_member.py_name)}, ')
        sf.write(_enum_member(spec, enum_member))
        sf.write(f', {enum_member.scope.type_nr}}},\n')

    sf.write('};\n')

    return nr_members


def _access_functions(sf, spec, scope=None):
    """ Generate the access functions for the variables. """

    for variable in _variables_in_scope(spec, scope, check_handler=False):
        if variable.access_code is None:
            continue

        cpp_name = variable.fq_cpp_name.as_word

        sf.write('\n\n/* Access function. */\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static void *access_{cpp_name}();}}\n')

        sf.write(f'static void *access_{cpp_name}()\n{{\n')
        sf.write_code(variable.access_code)
        sf.write('}\n')


# The types that are implemented as PyObject*.
_PY_OBJECT_TYPES = (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE,
    ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE,
    ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER,
    ArgumentType.PYENUM)

def _py_objects(sf, spec):
    """ Generate the inline code to add a set of Python objects to a module
    dictionary.
    """

    # Note that we should add these via a table (like int, etc) but that will
    # require a major API version change so this will do for now.

    no_intro = True

    for variable in spec.variables:
        if variable.module is not spec.module:
            continue

        if variable.type.type not in _PY_OBJECT_TYPES:
            continue

        if variable.needs_handler:
            continue

        if no_intro:
            sf.write('\n    /* Define the Python objects wrapped as such. */\n')
            no_intro = False

        py_name = _cached_name_ref(variable.py_name)
        cpp_name = variable.fq_cpp_name.as_cpp

        sf.write(f'    PyDict_SetItemString(sipModuleDict, {py_name}, {cpp_name});\n')


def _types_inline(sf, spec):
    """ Generate the inline code to add a set of generated type instances to a
    dictionary.
    """

    no_intro = True

    for variable in spec.variables:
        if variable.module is not spec.module:
            continue

        if variable.type.type not in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.ENUM):
            continue

        if variable.needs_handler:
            continue

        # Skip classes that don't need inline code.
        if spec.c_bindings or variable.access_code is not None or len(variable.type.derefs) != 0:
            continue

        if no_intro:
            sf.write(
'''
    /*
     * Define the class, mapped type and enum instances that have to be
     * added inline.
     */
''')

            no_intro = False

        if _py_scope(variable.scope) is None:
            dict_name = 'sipModuleDict'
        else:
            dict_name = f'(PyObject *)sipTypeAsPyTypeObject({_gto_name(variable.scope)})'

        py_name = _cached_name_ref(variable.py_name)

        if variable.type.is_const:
            type_name = fmt_argument_as_cpp_type(spec, variable.type,
                    plain=True, no_derefs=True)
            ptr = f'const_cast<{type_name} *>(&{variable.fq_cpp_name.as_cpp})'
        else:
            ptr = '&' + variable.fq_cpp_name.as_cpp

        sf.write(f'    sipAddTypeInstance({dict_name}, {py_name}, {ptr}, {_gto_name(variable.type.definition)});\n')


def _class_instances(sf, spec, scope=None):
    """ Generate the code to add a set of class instances to a dictionary.
    Return True if there was at least one.
    """

    instances = []

    for variable in _variables_in_scope(spec, scope):
        if variable.type.type is not ArgumentType.CLASS and (variable.type.type is not ArgumentType.ENUM or variable.type.definition.fq_cpp_name is None):
            continue

        # Skip ordinary C++ class instances which need to be done with inline
        # code rather than through a static table.  This is because C++ does
        # not guarantee the order in which the table and the instance will be
        # created.  So far this has only been seen to be a problem when
        # statically linking SIP generated modules on Windows.
        if not spec.c_bindings and variable.access_code is None and len(variable.type.derefs) == 0:
            continue

        ti_name = _cached_name_ref(variable.py_name)
        ti_ptr = '&' + variable.fq_cpp_name.as_cpp
        ti_type = '&' + _gto_name(variable.type.definition)
        ti_flags = '0'

        if variable.type.type is ArgumentType.CLASS:
            if variable.access_code is not None:
                ti_ptr = '(void *)access_' + variable.fq_cpp_name.as_word
                ti_flags = 'SIP_ACCFUNC|SIP_NOT_IN_MAP'
            elif len(variable.type.derefs) != 0:
                # This may be a bit heavy handed.
                if variable.type.is_const:
                    ti_ptr = '(void *)' + ti_ptr

                ti_flags = 'SIP_INDIRECT'
            else:
                ti_ptr = _const_cast(spec, variable.type, ti_ptr)

        instances.append((ti_name, ti_ptr, ti_type, ti_flags))

    return _write_instances_table(sf, scope, instances,
'''/* Define the class and enum instances to be added to this {dict_type} dictionary. */
static sipTypeInstanceDef typeInstances{suffix}[]''')


def _void_pointer_instances(sf, spec, scope=None):
    """ Generate the code to add a set of void pointers to a dictionary.
    Return True if there was at least one.
    """

    instances = []

    for variable in _variables_in_scope(spec, scope):
        if variable.type.type not in (ArgumentType.VOID, ArgumentType.STRUCT, ArgumentType.UNION):
            continue

        vi_name = _cached_name_ref(variable.py_name)
        vi_val = _const_cast(spec, variable.type,
                variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL))
        instances.append((vi_name, vi_val))

    return _write_instances_table(sf, scope, instances,
'''/* Define the void pointers to be added to this {dict_type} dictionary. */
"static sipVoidPtrInstanceDef voidPtrInstances{suffix}[]''')


def _char_instances(sf, spec, scope=None):
    """ Generate the code to add a set of characters to a dictionary.  Return
    True if there was at least one.
    """

    instances = []

    for variable in _variables_in_scope(spec, scope):
        if variable.type.type not in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING) or len(variable.type.derefs) != 0:
            continue

        ci_name = _cached_name_ref(variable.py_name)
        ci_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)
        ci_encoding = "'" + _get_encoding(variable.type) + "'"

        instances.append((ci_name, ci_val, ci_encoding))

    return _write_instances_table(sf, scope, instances,
'''/* Define the chars to be added to this {dict_type} dictionary. */
static sipCharInstanceDef charInstances{suffix}[]''')


def _string_instances(sf, spec, scope=None):
    """ Generate the code to add a set of strings to a dictionary.  Return True
    if there is at least one.
    """

    instances = []

    for variable in _variables_in_scope(spec, scope):
        if (variable.type.type not in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING) or len(variable.type.derefs) == 0) and variable.type.type is not ArgumentType.WSTRING:
            continue

        si_name = _cached_name_ref(variable.py_name)
        si_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)

        # This is the hack for handling wchar_t and wchar_t*.
        encoding = _get_encoding(variable.type)

        if encoding == 'w':
            si_val = '(const char *)&' + si_val
        elif encoding == 'W':
            si_val = '(const char *)' + si_val

        si_encoding = "'" + encoding + "'"

        instances.append((si_name, si_val, si_encoding))

    return _write_instances_table(sf, scope, instances,
'''/* Define the strings to be added to this {dict_type} dictionary. */
static sipStringInstanceDef stringInstances{suffix}[]''')


def _int_instances(sf, spec, scope=None):
    """ Generate the code to add a set of ints.  Return True if there was at
    least one.
    """

    instances = []

    if spec.abi_version >= (13, 0):
        # Named enum members are handled as int variables but must be placed at
        # the start of the table.  Note we use the sorted table of needed types
        # rather than the unsorted table of all enums.
        for type in spec.module.needed_types:
            if type.type is not ArgumentType.ENUM:
                continue

            enum = type.definition

            if _py_scope(enum.scope) is not scope or enum.module is not spec.module:
                continue

            for enum_member in enum.members:
                ii_name = _cached_name_ref(enum_member.py_name)
                ii_val = _enum_member(spec, enum_member)
                instances.append((ii_name, ii_val))

    # Handle int variables.
    for variable in _variables_in_scope(spec, scope):
        if variable.type.type not in (ArgumentType.ENUM, ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.USHORT, ArgumentType.SHORT, ArgumentType.CINT, ArgumentType.INT, ArgumentType.BOOL, ArgumentType.CBOOL):
            continue

        # Named enums are handled elsewhere.
        if variable.type.type is ArgumentType.ENUM and variable.type.definition.fq_cpp_name is not None:
            continue

        ii_name = _cached_name_ref(variable.py_name)
        ii_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)
        instances.append((ii_name, ii_val))

    # Anonymous enum members are handled as int variables.
    if spec.abi_version >= (13, 0) or scope is None:
        for enum in spec.enums:
            if _py_scope(enum.scope) is not scope or enum.module is not spec.module:
                continue

            if enum.fq_cpp_name is not None:
                continue

            for enum_member in enum.members:
                ii_name = _cached_name_ref(enum_member.py_name)
                ii_val = _enum_member(spec, enum_member)
                instances.append((ii_name, ii_val))

    return _write_instances_table(sf, scope, instances,
'''/* Define the enum members and ints to be added to this {dict_type}. */
static sipIntInstanceDef intInstances{suffix}[]''')


def _long_instances(sf, spec, scope=None):
    """ Generate the code to add a set of longs to a dictionary.  Return True
    if there was at least one.
    """

    return _write_int_instances(sf, spec, scope, ArgumentType.LONG, 'long')


def _unsigned_long_instances(sf, spec, scope=None):
    """ Generate the code to add a set of unsigned longs to a dictionary.
    Return True if there was at least one.
    """

    return _write_int_instances(sf, spec, scope, ArgumentType.ULONG,
            'unsigned long')


def _long_long_instances(sf, spec, scope=None):
    """ Generate the code to add a set of long longs to a dictionary.  Return
    True if there was at least one.
    """

    return _write_int_instances(sf, spec, scope, ArgumentType.LONGLONG,
            'long long')


def _unsigned_long_long_instances(sf, spec, scope=None):
    """ Generate the code to add a set of unsigned long longs to a dictionary.
    Return True if there was at least one.
    """

    return _write_int_instances(sf, spec, scope, ArgumentType.ULONGLONG,
            'unsigned long long')


def _write_int_instances(sf, spec, 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.
    """

    instances = []

    for variable in _variables_in_scope(spec, scope):
        variable_type = variable.type.type

        # We treat unsigned and size_t as unsigned long as we don't (currently
        # anyway) generate a separate table for them.
        if variable_type in (ArgumentType.UINT, ArgumentType.SIZE) and target_type is ArgumentType.ULONG:
            variable_type = ArgumentType.ULONG

        if variable_type is not target_type:
            continue

        ii_name = _cached_name_ref(variable.py_name)
        ii_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)
        instances.append((ii_name, ii_val))

    table_type_name = type_name.title().replace(' ', '')
    table_name = table_type_name[0].lower() + table_type_name[1:]

    declaration_template = f'''/* Define the {type_name}s to be added to this {{dict_type}} dictionary. */
static sip{table_type_name}InstanceDef {table_name}Instances{{suffix}}[]'''

    return _write_instances_table(sf, scope, instances, declaration_template)


def _double_instances(sf, spec, scope=None):
    """ Generate the code to add a set of doubles to a dictionary.  Return True
    if there was at least one.
    """

    instances = []

    for variable in _variables_in_scope(spec, scope):
        if variable.type.type not in (ArgumentType.FLOAT, ArgumentType.CFLOAT, ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
            continue

        di_name = _cached_name_ref(variable.py_name)
        di_val = variable.fq_cpp_name.cpp_stripped(STRIP_GLOBAL)
        instances.append((di_name, di_val))

    return _write_instances_table(sf, scope, instances,
'''/* Define the doubles to be added to this {dict_type} dictionary. */
static sipDoubleInstanceDef doubleInstances{suffix}[]''')


def _empty_iface_file(spec, iface_file):
    """ See if an interface file has any content. """

    for klass in spec.classes:
        if klass.iface_file is iface_file and not klass.is_hidden_namespace and not klass.is_protected and not klass.external:
            return False

    for mapped_type in spec.mapped_types:
        if mapped_type.iface_file is iface_file:
            return False

    return True


def _iface_file_cpp(spec, bindings, project, buildable, py_debug, iface_file,
        need_postinc, source_suffix, sf):
    """ Generate the C/C++ code for an interface. """

    # Check that there will be something in the file so that we don't get
    # warning messages from ranlib.
    if _empty_iface_file(spec, iface_file):
        return

    if sf is None:
        source_name = os.path.join(buildable.build_dir,
                'sip' + iface_file.module.py_name)

        for part in iface_file.fq_cpp_name:
            source_name += part

        if iface_file.file_extension is not None:
            source_suffix = iface_file.file_extension

        source_name += source_suffix

        sf = CompilationUnit(source_name, "Interface wrapper code.",
                iface_file.module, project, buildable)

        need_postinc = True

    sf.write('\n')

    sf.write_code(iface_file.type_header_code)
    _used_includes(sf, iface_file.used)

    if need_postinc:
        sf.write_code(iface_file.module.unit_postinclude_code)

    for klass in spec.classes:
        # Protected classes must be generated in the interface file of the
        # enclosing scope.
        if klass.is_protected:
            continue

        if klass.external:
            continue

        if klass.iface_file is iface_file:
            _class_cpp(sf, spec, bindings, klass, py_debug)

            # Generate any enclosed protected classes.
            for proto_klass in spec.classes:
                if proto_klass.is_protected and proto_klass.scope is klass:
                    _class_cpp(sf, spec, bindings, proto_klass, py_debug)

    for mapped_type in spec.mapped_types:
        if mapped_type.iface_file is iface_file:
            _mapped_type_cpp(sf, spec, bindings, mapped_type)


def _mapped_type_cpp(sf, spec, bindings, mapped_type):
    """ Generate the C++ code for a mapped type version. """

    mapped_type_name = mapped_type.iface_file.fq_cpp_name.as_word
    mapped_type_type = fmt_argument_as_cpp_type(spec, mapped_type.type,
            plain=True, no_derefs=True)

    sf.write_code(mapped_type.type_code)

    if not mapped_type.no_release:
        # Generate the assignment helper.  Note that the source pointer is not
        # const.  This is to allow the source instance to be modified as a
        # consequence of the assignment, eg. if it is implementing some sort of
        # reference counting scheme.
        if not mapped_type.no_assignment_operator:
            sf.write('\n\n')

            if not spec.c_bindings:
                sf.write(f'extern "C" {{static void assign_{mapped_type_name}(void *, Py_ssize_t, void *);}}\n')

            sf.write(f'static void assign_{mapped_type_name}(void *sipDst, Py_ssize_t sipDstIdx, void *sipSrc)\n{{\n')

            if spec.c_bindings:
                sf.write(f'    (({mapped_type_type} *)sipDst)[sipDstIdx] = *(({mapped_type_type} *)sipSrc);\n')
            else:
                sf.write(f'    reinterpret_cast<{mapped_type_type} *>(sipDst)[sipDstIdx] = *reinterpret_cast<{mapped_type_type} *>(sipSrc);\n')

            sf.write('}\n')

        # Generate the array allocation helper.
        if not mapped_type.no_default_ctor:
            sf.write('\n\n')

            if not spec.c_bindings:
                sf.write(f'extern "C" {{static void *array_{mapped_type_name}(Py_ssize_t);}}\n')

            sf.write(f'static void *array_{mapped_type_name}(Py_ssize_t sipNrElem)\n{{\n')

            if spec.c_bindings:
                sf.write(f'    return sipMalloc(sizeof ({mapped_type_type}) * sipNrElem);\n')
            else:
                sf.write(f'    return new {mapped_type_type}[sipNrElem];\n')

            sf.write('}\n')

        # Generate the copy helper.
        if not mapped_type.no_copy_ctor:
            sf.write('\n\n')

            if not spec.c_bindings:
                sf.write(f'extern "C" {{static void *copy_{mapped_type_name}(const void *, Py_ssize_t);}}\n')

            sf.write(f'static void *copy_{mapped_type_name}(const void *sipSrc, Py_ssize_t sipSrcIdx)\n{{\n')

            if spec.c_bindings:
                sf.write(
f'''    {mapped_type_type} *sipPtr = sipMalloc(sizeof ({mapped_type_type}));
    *sipPtr = ((const {mapped_type_type} *)sipSrc)[sipSrcIdx];

    return sipPtr;
''')
            else:
                sf.write(f'    return new {mapped_type_type}(reinterpret_cast<const {mapped_type_type} *>(sipSrc)[sipSrcIdx]);\n')

            sf.write('}\n')

        sf.write('\n\n/* Call the mapped type\'s destructor. */\n')

        need_state = _is_used_in_code(mapped_type.release_code, 'sipState')

        if not spec.c_bindings:
            arg_3 = ', void *' if spec.abi_version >= (13, 0) else ''
            sf.write(f'extern "C" {{static void release_{mapped_type_name}(void *, int{arg_3});}}\n')

        arg_2 = ' sipState' if spec.c_bindings or need_state else ''
        sf.write(f'static void release_{mapped_type_name}(void *sipCppV, int{arg_2}')

        if spec.abi_version >= (13, 0):
            user_state = _use_in_code(mapped_type.release_code, 'sipUserState')
            sf.write(', void *' + user_state)

        sf.write(f')\n{{\n    {_mapped_type_from_void(spec, mapped_type_type)};\n')

        if bindings.release_gil:
            sf.write('    Py_BEGIN_ALLOW_THREADS\n')

        if mapped_type.release_code is not None:
            sf.write_code(mapped_type.release_code)
        elif spec.c_bindings:
            sf.write('    sipFree(sipCpp);\n')
        else:
            sf.write('    delete sipCpp;\n')

        if bindings.release_gil:
            sf.write('    Py_END_ALLOW_THREADS\n')

        sf.write('}\n\n')

    _convert_to_definitions(sf, spec, mapped_type)

    # Generate the from type convertor.
    if mapped_type.convert_from_type_code is not None:
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static PyObject *convertFrom_{mapped_type_name}(void *, PyObject *);}}\n')

        xfer = _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})
{{
    {_mapped_type_from_void(spec, mapped_type_type)};

''')

        sf.write_code(mapped_type.convert_from_type_code)

        sf.write('}\n')

    # Generate the static methods.
    for member in mapped_type.members:
        _ordinary_function(sf, spec, bindings, member, scope=mapped_type)

    cod_nrmethods = _mapped_type_method_table(sf, bindings, spec, mapped_type)

    id_int = 'SIP_NULLPTR'

    if spec.abi_version >= (13, 0):
        if _int_instances(sf, spec, scope=mapped_type):
            id_int = 'intInstances_' + mapped_type_name

        needs_namespace = False
    else:
        cod_nrenummembers = _enum_member_table(sf, spec, scope=mapped_type)
        has_ints = False
        needs_namespace = (cod_nrenummembers > 0)

    if cod_nrmethods > 0:
        needs_namespace = True

    if _pyqt6(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.abi_version < (13, 0):
        sf.write(
'''        -1,
        SIP_NULLPTR,
''')

    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')

    td_flags = '|'.join(flags)

    td_cname = _cached_name_ref(mapped_type.cpp_name, as_nr=True)

    cod_name = _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 spec.abi_version < (13, 0):
        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 _class_cpp(sf, spec, bindings, klass, py_debug):
    """ Generate the C++ code for a class. """

    sf.write_code(klass.type_code)
    _class_functions(sf, spec, bindings, klass, py_debug)
    _access_functions(sf, spec, scope=klass)

    if klass.iface_file.type is not IfaceFileType.NAMESPACE:
        _convert_to_definitions(sf, spec, klass)

        # Generate the optional from type convertor.
        if klass.convert_from_type_code is not None:
            sf.write('\n\n')

            name = klass.iface_file.fq_cpp_name.as_word
            xfer = _use_in_code(klass.convert_from_type_code, 'sipTransferObj',
                    spec=spec)

            if not spec.c_bindings:
                sf.write(f'extern "C" {{static PyObject *convertFrom_{name}(void *, PyObject *);}}\n')

            sf.write(
f'''static PyObject *convertFrom_{name}(void *sipCppV, PyObject *{xfer})
{{
    {_class_from_void(spec, klass)};

''')

            sf.write_code(klass.convert_from_type_code)

            sf.write('}\n')

    _type_definition(sf, spec, bindings, klass, py_debug)


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.
    """

    # Only provide an entry point if there is at least one overload that is
    # defined in this class and is a non-abstract function or slot.  We allow
    # private (even though we don't actually generate code) because we need to
    # intercept the name before it reaches a more public version further up the
    # class hierarchy.  We add the ctor and any variable handlers as special
    # entries.

    members = []

    for visible_member in klass.visible_members:
        if visible_member.member.py_slot is not None:
            continue

        need_member = False

        for overload in visible_member.scope.overloads:
            # Skip protected methods if we don't have the means to handle them.
            if overload.access_specifier is AccessSpecifier.PROTECTED and not klass.has_shadow:
                continue

            if not _skip_overload(overload, visible_member.member, klass, visible_member.scope):
                need_member = True

        if need_member:
            members.append(visible_member.member)

    return _get_function_table(members)


def _mapped_type_method_table(sf, spec, 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 _py_method_table(sf, spec, bindings, members, mapped_type)


def _class_method_table(sf, spec, bindings, klass):
    """ Generate the sorted table of methods for a class and return the number
    of entries.
    """

    if klass.iface_file.type is IfaceFileType.NAMESPACE:
        members = _get_function_table(klass.members)
    else:
        members = _get_method_table(klass)

    return _py_method_table(sf, spec, bindings, members, klass)


def _py_method_table(sf, spec, bindings, members, scope):
    """ Generate a Python method table for a class or mapped type and return
    the number of entries.
    """

    scope_name = scope.iface_file.fq_cpp_name.as_word

    no_intro = True

    for member_nr, member in enumerate(members):
        # Save the index in the table.
        member.member_nr = member_nr

        py_name = member.py_name
        cached_py_name = _cached_name_ref(py_name)
        comma = '' if member is members[-1] else ','

        if member.no_arg_parser or member.allow_keyword_args:
            cast = 'SIP_MLMETH_CAST('
            cast_suffix = ')'
            flags = '|METH_KEYWORDS'
        else:
            cast = ''
            cast_suffix = ''
            flags = ''

        if _has_member_docstring(bindings, member, scope.overloads):
            docstring = f'doc_{scope_name}_{py_name.name}'
        else:
            docstring = 'SIP_NULLPTR'

        if no_intro:
            sf.write(
f'''

static PyMethodDef methods_{scope_name}[] = {{
''')

            no_intro = False

        sf.write(f'    {{{cached_py_name}, {cast}meth_{scope_name}_{py_name.name}{cast_suffix}, METH_VARARGS{flags}, {docstring}}}{comma}\n')

    if not no_intro:
        sf.write('};\n')

    return len(members)


def _convert_to_definitions(sf, spec, scope):
    """ Generate the "to type" convertor definitions. """

    convert_to_type_code = scope.convert_to_type_code

    if convert_to_type_code is None:
        return

    scope_type = Argument(
            ArgumentType.CLASS if isinstance(scope, WrappedClass) else ArgumentType.MAPPED,
            definition=scope)

    # Sometimes type convertors are just stubs that set the error flag, so
    # check if we actually need everything so that we can avoid compiler
    # warnings.
    sip_py = _use_in_code(convert_to_type_code, 'sipPy', spec=spec)
    sip_cpp_ptr = _use_in_code(convert_to_type_code, 'sipCppPtr', spec=spec)
    sip_is_err = _use_in_code(convert_to_type_code, 'sipIsErr', spec=spec)
    xfer = _use_in_code(convert_to_type_code, 'sipTransferObj', spec=spec)

    if spec.abi_version >= (13, 0):
        need_us_arg = True
        need_us_val = (spec.c_bindings or _type_needs_user_state(scope_type))
    else:
        need_us_arg = False
        need_us_val = False

    scope_name = scope.iface_file.fq_cpp_name.as_word

    sf.write('\n\n')

    if not spec.c_bindings:
        sf.write(f'extern "C" {{static int convertTo_{scope_name}(PyObject *, void **, int *, PyObject *')

        if need_us_arg:
            sf.write(', void **')

        sf.write(');}\n')

    sip_cpp_ptr_v = sip_cpp_ptr
    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}')

    if need_us_arg:
        sf.write(', void **')

        if need_us_val:
            sf.write('sipUserStatePtr')

    sf.write(')\n{\n')

    if sip_cpp_ptr != '':
        type_s = fmt_argument_as_cpp_type(spec, scope_type, plain=True,
                no_derefs=True)

        if spec.c_bindings:
            cast_value = f'({type_s} **)sipCppPtrV'
        else:
            cast_value = f'reinterpret_cast<{type_s} **>(sipCppPtrV)'

        sf.write(f'    {type_s} **sipCppPtr = {cast_value};\n\n')

    sf.write_code(convert_to_type_code)

    sf.write('}\n')


def _variable_getter(sf, spec, variable):
    """ Generate a variable getter. """

    variable_type = variable.type.type
    first_arg = 'sipSelf' if spec.c_bindings or not variable.is_static else ''
    last_arg = _use_in_code(variable.get_code, 'sipPyType', spec=spec)

    needs_new = (variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(variable.type.derefs) == 0 and variable.type.is_const)

    # If the variable is itself a non-const instance of a wrapped class then
    # two things must happen.  Firstly, the getter must return the same Python
    # object each time - it must not re-wrap the the instance.  This is because
    # the Python object can contain important state information that must not
    # be lost (specifically references to other Python objects that it owns).
    # Therefore the Python object wrapping the containing class must cache a
    # reference to the Python object wrapping the variable.  Secondly, the
    # Python object wrapping the containing class must not be garbage collected
    # before the Python object wrapping the variable is (because the latter
    # references memory, ie. the variable itself, that is managed by the
    # former).  Therefore the Python object wrapping the variable must keep a
    # reference to the Python object wrapping the containing class (but only if
    # the latter is non-static).
    var_key = self_key = 0

    if variable_type is ArgumentType.CLASS and len(variable.type.derefs) == 0 and not variable.type.is_const:
        var_key = variable.type.definition.iface_file.module.next_key
        variable.type.definition.iface_file.module.next_key -= 1

        if not variable.is_static:
            self_key = variable.module.next_key
            variable.module.next_key -= 1

    second_arg = 'sipPySelf' if spec.c_bindings or var_key < 0 else ''
    variable_as_word = variable.fq_cpp_name.as_word

    sf.write('\n\n')

    if not spec.c_bindings:
        sf.write(f'extern "C" {{static PyObject *varget_{variable_as_word}(void *, PyObject *, PyObject *);}}\n')

    sf.write(
f'''static PyObject *varget_{variable_as_word}(void *{first_arg}, PyObject *{second_arg}, PyObject *{last_arg})
{{
''')

    if variable.get_code is not None:
        sip_py_decl = 'PyObject *sipPy'
    elif var_key < 0:
        if variable.is_static:
            sip_py_decl = 'static PyObject *sipPy = SIP_NULLPTR'
        else:
            sip_py_decl = 'PyObject *sipPy'
    else:
        sip_py_decl = None

    if sip_py_decl is not None:
        sf.write('    ' + sip_py_decl + ';\n')

    if variable.get_code is None:
        value_decl = _get_named_value_decl(spec, variable.scope, variable.type,
                'sipVal')
        sf.write(f'    {value_decl};\n')

    if not variable.is_static:
        scope_s = _scoped_class_name(spec, variable.scope)

        if spec.c_bindings:
            sip_self = f'({scope_s} *)sipSelf'
        else:
            sip_self = f'reinterpret_cast<{scope_s} *>(sipSelf)'

        sf.write(f'    {scope_s} *sipCpp = {sip_self};\n')

    sf.write('\n')

    # Handle any handwritten getter.
    if variable.get_code is not None:
        sf.write_code(variable.get_code)

        sf.write(
'''
    return sipPy;
}
''')

        return

    # Get any previously wrapped cached object.
    if var_key < 0:
        if variable.is_static:
            sf.write(
'''    if (sipPy)
    {
        Py_INCREF(sipPy);
        return sipPy;
    }

''')
        else:
            sf.write(
f'''    sipPy = sipGetReference(sipPySelf, {self_key});

    if (sipPy)
        return sipPy;

''')

    variable_type_s = fmt_argument_as_cpp_type(spec, variable.type, plain=True,
            no_derefs=True)

    if needs_new:
        if spec.c_bindings:
            sf.write('    *sipVal = ')
        else:
            sf.write(f'    sipVal = new {variable_type_s}(')
    else:
        sf.write('    sipVal = ')

        if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(variable.type.derefs) == 0:
            sf.write('&')

    sf.write(_variable_member(variable))

    if needs_new and not spec.c_bindings:
        sf.write(')')

    sf.write(';\n\n')

    if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        prefix_s = 'sipPy =' if var_key < 0 else 'return'
        new_s = 'New' if needs_new else ''
        sip_val_s = _const_cast(spec, variable.type, 'sipVal')

        sf.write(f'    {prefix_s} sipConvertFrom{new_s}Type({sip_val_s}, {_gto_name(variable.type.definition)}, SIP_NULLPTR);\n')

        if var_key < 0:
            if variable.is_static:
                ref_code = 'Py_INCREF(sipPy)'
            else:
                ref_code = f'sipKeepReference(sipPySelf, {self_key}, sipPy)'

            sf.write(
f'''
    if (sipPy)
    {{
        sipKeepReference(sipPy, {var_key}, sipPySelf);
        {ref_code};
    }}

    return sipPy;
''')

    elif variable_type in (ArgumentType.BOOL, ArgumentType.CBOOL):
        sf.write('    return PyBool_FromLong(sipVal);\n')

    elif variable_type is ArgumentType.ASCII_STRING:
        if len(variable.type.derefs) == 0:
            sf.write('    return PyUnicode_DecodeASCII(&sipVal, 1, SIP_NULLPTR);\n')
        else:
            sf.write(
'''    if (sipVal == SIP_NULLPTR)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    return PyUnicode_DecodeASCII(sipVal, strlen(sipVal), SIP_NULLPTR);
''')

    elif variable_type is ArgumentType.LATIN1_STRING:
        if len(variable.type.derefs) == 0:
            sf.write('    return PyUnicode_DecodeLatin1(&sipVal, 1, SIP_NULLPTR);\n')
        else:
            sf.write(
'''    if (sipVal == SIP_NULLPTR)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    return PyUnicode_DecodeLatin1(sipVal, strlen(sipVal), SIP_NULLPTR);
''')

    elif variable_type is ArgumentType.UTF8_STRING:
        if len(variable.type.derefs) == 0:
            sf.write('    return PyUnicode_FromStringAndSize(&sipVal, 1);\n')
        else:
            sf.write(
'''    if (sipVal == SIP_NULLPTR)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    return PyUnicode_FromString(sipVal);
''')

    elif variable_type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING):
        cast_s = '' if variable_type is ArgumentType.STRING else '(char *)'

        if len(variable.type.derefs) == 0:
            sf.write(f'    return PyBytes_FromStringAndSize({cast_s}&sipVal, 1);\n')
        else:
            sf.write(
f'''    if (sipVal == SIP_NULLPTR)
    {{
        Py_INCREF(Py_None);
        return Py_None;
    }}

    return PyBytes_FromString({cast_s}sipVal);
''')

    elif variable_type is ArgumentType.WSTRING:
        if len(variable.type.derefs) == 0:
            sf.write('    return PyUnicode_FromWideChar(&sipVal, 1);\n')
        else:
            sf.write(
'''    if (sipVal == SIP_NULLPTR)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    return PyUnicode_FromWideChar(sipVal, (Py_ssize_t)wcslen(sipVal));
''')

    elif variable_type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
        sf.write('    return PyFloat_FromDouble((double)sipVal);\n')

    elif variable_type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
        sf.write('    return PyFloat_FromDouble(sipVal);\n')

    elif variable_type is ArgumentType.ENUM:
        if variable.type.definition.fq_cpp_name is None:
            sf.write('    return PyLong_FromLong(sipVal);\n')
        else:
            sip_val_s = 'sipVal' if spec.c_bindings else 'static_cast<int>(sipVal)'
            sf.write(f'    return sipConvertFromEnum({sip_val_s}, {_gto_name(variable.type.definition)});\n')

    elif variable_type in (ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.SHORT, ArgumentType.INT, ArgumentType.CINT):
        sf.write('    return PyLong_FromLong(sipVal);\n')

    elif variable_type is ArgumentType.LONG:
        sf.write('    return PyLong_FromLong(sipVal);\n')

    elif variable_type in (ArgumentType.UBYTE, ArgumentType.USHORT):
        sf.write('    return PyLong_FromUnsignedLong(sipVal);\n')

    elif variable_type in (ArgumentType.UINT, ArgumentType.ULONG, ArgumentType.SIZE):
        sf.write('    return PyLong_FromUnsignedLong(sipVal);\n')

    elif variable_type is ArgumentType.LONGLONG:
        sf.write('    return PyLong_FromLongLong(sipVal);\n')

    elif variable_type is ArgumentType.ULONGLONG:
        sf.write('    return PyLong_FromUnsignedLongLong(sipVal);\n')

    elif variable_type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
        const_s = 'Const' if variable.type.is_const else ''
        cast_s = _get_void_ptr_cast(variable.type)

        sf.write(f'    return sipConvertFrom{const_s}VoidPtr({cast_s}sipVal);\n')

    elif variable_type is ArgumentType.CAPSULE:
        cast_s = _get_void_ptr_cast(variable.type)

        sf.write(f'    return PyCapsule_New({cast_s}sipVal, "{variable.type.definition.as_cpp}", SIP_NULLPTR);\n')

    elif variable_type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM):
        sf.write(
'''    Py_XINCREF(sipVal);
    return sipVal;
''')

    sf.write('}\n')


def _variable_setter(sf, spec, variable):
    """ Generate a variable setter. """

    variable_type = variable.type.type

    # We need to keep a reference to the original Python object if it is
    # providing the memory that the C/C++ variable is pointing to.
    keep = _keep_py_reference(variable.type)

    need_sip_cpp = (spec.c_bindings or variable.set_code is None or _is_used_in_code(variable.set_code, 'sipCpp'))

    first_arg = 'sipSelf' if spec.c_bindings or not variable.is_static else ''
    if not need_sip_cpp:
        first_arg = ''

    last_arg = 'sipPySelf' if spec.c_bindings or (not variable.is_static and keep) else ''

    sip_py = 'sipPy' if spec.c_bindings or variable.set_code is None or _is_used_in_code(variable.set_code, 'sipPy') else ''
    variable_as_word = variable.fq_cpp_name.as_word

    sf.write('\n\n')

    if not spec.c_bindings:
        sf.write(f'extern "C" {{static int varset_{variable_as_word}(void *, PyObject *, PyObject *);}}\n')

    sf.write(
f'''static int varset_{variable_as_word}(void *{first_arg}, PyObject *{sip_py}, PyObject *{last_arg})
{{
''')

    if variable.set_code is None:
        if variable_type is ArgumentType.BOOL:
            value_decl = 'int sipVal'
        else:
            value_decl = _get_named_value_decl(spec, variable.scope,
                    variable.type, 'sipVal')

        sf.write(f'    {value_decl};\n')

    if not variable.is_static and need_sip_cpp:
        scope_s = _scoped_class_name(spec, variable.scope)

        if spec.c_bindings:
            statement = f'({scope_s} *)sipSelf'
        else:
            statement = f'reinterpret_cast<{scope_s} *>(sipSelf)'

        sf.write(f'    {scope_s} *sipCpp = {statement};\n\n')

    # Handle any handwritten setter.
    if variable.set_code is not None:
        sf.write('   int sipErr = 0;\n\n')
        sf.write_code(variable.set_code)
        sf.write(
'''
    return (sipErr ? -1 : 0);
}
''')

        return

    has_state = False

    if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        sf.write('    int sipIsErr = 0;\n')

        if len(variable.type.derefs) == 0:
            convert_to_type_code = variable.type.definition.convert_to_type_code

            if variable_type is ArgumentType.MAPPED and variable.type.definition.no_release:
                convert_to_type_code = None

            if convert_to_type_code is not None:
                has_state = True

                sf.write('    int sipValState;\n')

                if _type_needs_user_state(variable.type):
                    sf.write('    void *sipValUserState;\n')

    sf.write(f'    sipVal = {_variable_to_cpp(spec, variable, has_state)};\n')

    deref = ''

    if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        if len(variable.type.derefs) == 0:
            deref = '*'

        error_test = 'sipIsErr'
    elif variable_type is ArgumentType.BOOL:
        error_test = 'sipVal < 0'
    else:
        error_test = 'PyErr_Occurred() != SIP_NULLPTR'

    sf.write(
f'''
    if ({error_test})
        return -1;

''')

    member = _variable_member(variable)

    if variable_type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM):
        sf.write(
f'''    Py_XDECREF({member});
    Py_INCREF(sipVal);

''')

    value = deref + 'sipVal'

    if variable_type is ArgumentType.BOOL:
        if spec.c_bindings:
            value = '(bool)' + value
        else:
            value = f'static_cast<bool>({value})'

    sf.write(f'    {member} = {value};\n')

    # Note that wchar_t * leaks here.

    if has_state:
        suffix = _user_state_suffix(spec, variable.type)

        sf.write(
f'''
    sipReleaseType{suffix}(sipVal, {_gto_name(variable.type.definition)}, sipValState''')

        if _type_needs_user_state(variable.type):
            sf.write(', sipValUserState')

        sf.write(');\n')

    # Generate the code to keep the object alive while we use its data.
    if keep:
        if variable.is_static:
            sf.write(
'''
    static PyObject *sipKeep = SIP_NULLPTR;

    Py_XDECREF(sipKeep);
    sipKeep = sipPy;
    Py_INCREF(sipKeep);
''')
        else:
            key = variable.module.next_key
            variable.module.next_key -= 1

            sf.write(
f'''
    sipKeepReference(sipPySelf, {key}, sipPy);
''')

    sf.write(
'''
    return 0;
}
''')


def _variable_member(variable):
    """ Return the member variable of a class. """

    if variable.is_static:
        scope = variable.scope.iface_file.fq_cpp_name.as_cpp + '::'
    else:
        scope = 'sipCpp->'

    return scope + variable.fq_cpp_name.base_name


def _variable_to_cpp(spec, variable, has_state):
    """ Return the statement to convert a Python variable to C/C++. """

    type_s = fmt_argument_as_cpp_type(spec, variable.type, plain=True,
            no_derefs=True)

    variable_type = variable.type.type

    if variable_type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        if spec.c_bindings:
            statement = f'({type_s} *)'
            cast_tail = ''
        else:
            statement = f'reinterpret_cast<{type_s} *>('
            cast_tail = ')'

        # Note that we don't support /Transfer/ but could do.  We could also
        # support /Constrained/ (so long as we also supported it for all
        # types).

        suffix = _user_state_suffix(spec, variable.type)
        flags = '0' if len(variable.type.derefs) != 0 else 'SIP_NOT_NONE'
        state_ptr = '&sipValState' if has_state else 'SIP_NULLPTR'

        statement += f'sipForceConvertToType{suffix}(sipPy, {_gto_name(variable.type.definition)}, SIP_NULLPTR, {flags}, {state_ptr}'

        if _type_needs_user_state(variable.type):
            statement += ', &sipValUserState'

        statement += ', &sipIsErr)' + cast_tail

    elif variable_type is ArgumentType.ENUM:
        statement = f'({type_s})sipConvertToEnum(sipPy, {_gto_name(variable.type.definition)})'

    elif variable_type is ArgumentType.SSTRING:
        if len(variable.type.derefs) == 0:
            statement = '(signed char)sipBytes_AsChar(sipPy)'
        elif variable.type.is_const:
            statement = '(const signed char *)sipBytes_AsString(sipPy)'
        else:
            statement = '(signed char *)sipBytes_AsString(sipPy)'

    elif variable_type is ArgumentType.USTRING:
        if len(variable.type.derefs) == 0:
            statement = '(unsigned char)sipBytes_AsChar(sipPy)'
        elif variable.type.is_const:
            statement = '(const unsigned char *)sipBytes_AsString(sipPy)'
        else:
            statement = '(unsigned char *)sipBytes_AsString(sipPy)'

    elif variable_type is ArgumentType.ASCII_STRING:
        if len(variable.type.derefs) == 0:
            statement = 'sipString_AsASCIIChar(sipPy)'
        elif variable.type.is_const:
            statement = 'sipString_AsASCIIString(&sipPy)'
        else:
            statement = '(char *)sipString_AsASCIIString(&sipPy)'

    elif variable_type is ArgumentType.LATIN1_STRING:
        if len(variable.type.derefs) == 0:
            statement = 'sipString_AsLatin1Char(sipPy)'
        elif variable.type.is_const:
            statement = 'sipString_AsLatin1String(&sipPy)'
        else:
            statement = '(char *)sipString_AsLatin1String(&sipPy)'

    elif variable_type is ArgumentType.UTF8_STRING:
        if len(variable.type.derefs) == 0:
            statement = 'sipString_AsUTF8Char(sipPy)'
        elif variable.type.is_const:
            statement = 'sipString_AsUTF8String(&sipPy)'
        else:
            statement = '(char *)sipString_AsUTF8String(&sipPy)'

    elif variable_type is ArgumentType.STRING:
        if len(variable.type.derefs) == 0:
            statement = 'sipBytes_AsChar(sipPy)'
        elif variable.type.is_const:
            statement = 'sipBytes_AsString(sipPy)'
        else:
            statement = '(char *)sipBytes_AsString(sipPy)'

    elif variable_type is ArgumentType.WSTRING:
        if len(variable.type.derefs) == 0:
            statement = 'sipUnicode_AsWChar(sipPy)'
        else:
            statement = 'sipUnicode_AsWString(sipPy)'

    elif variable_type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
        statement = '(float)PyFloat_AsDouble(sipPy)'

    elif variable_type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
        statement = 'PyFloat_AsDouble(sipPy)'

    elif variable_type in (ArgumentType.BOOL, ArgumentType.CBOOL):
        statement = 'sipConvertToBool(sipPy)'

    elif variable_type is ArgumentType.BYTE:
        statement = 'sipLong_AsChar(sipPy)'

    elif variable_type is ArgumentType.SBYTE:
        statement = 'sipLong_AsSignedChar(sipPy)'

    elif variable_type is ArgumentType.UBYTE:
        statement = 'sipLong_AsUnsignedChar(sipPy)'

    elif variable_type is ArgumentType.USHORT:
        statement = 'sipLong_AsUnsignedShort(sipPy)'

    elif variable_type is ArgumentType.SHORT:
        statement = 'sipLong_AsShort(sipPy)'

    elif variable_type is ArgumentType.UINT:
        statement = 'sipLong_AsUnsignedInt(sipPy)'

    elif variable_type is ArgumentType.SIZE:
        statement = 'sipLong_AsSizeT(sipPy)'

    elif variable_type in (ArgumentType.INT, ArgumentType.CINT):
        statement = 'sipLong_AsInt(sipPy)'

    elif variable_type is ArgumentType.ULONG:
        statement = 'sipLong_AsUnsignedLong(sipPy)'

    elif variable_type is ArgumentType.LONG:
        statement = 'sipLong_AsLong(sipPy)'

    elif variable_type is ArgumentType.ULONGLONG:
        statement = 'sipLong_AsUnsignedLongLong(sipPy)'

    elif variable_type is ArgumentType.LONGLONG:
        statement = 'sipLong_AsLongLong(sipPy)'

    elif variable_type in (ArgumentType.STRUCT, ArgumentType.UNION):
        statement = f'({type_s} *)sipConvertToVoidPtr(sipPy)'

    elif variable_type is ArgumentType.VOID:
        statement = 'sipConvertToVoidPtr(sipPy)'

    elif variable_type is ArgumentType.CAPSULE:
        statement = f'PyCapsule_GetPointer(sipPy, "{variable.type.definition.as_cpp}")'

    else:
        # These are just the PyObject types.
        statement = 'sipPy'

    return statement


def _py_slot(sf, spec, bindings, member, scope=None):
    """ Generate a Python slot handler for either a class, an enum or an
    extender.
    """

    if scope is None:
        prefix = ''
        py_name = None
        fq_cpp_name = None
        overloads = spec.module.overloads
    elif isinstance(scope, WrappedEnum):
        prefix = 'Type'
        py_name = scope.py_name
        fq_cpp_name = scope.fq_cpp_name
        overloads = scope.overloads
    else:
        prefix = 'Type'
        py_name = scope.py_name
        fq_cpp_name = scope.iface_file.fq_cpp_name
        overloads = scope.overloads

    if is_void_return_slot(member.py_slot) or is_int_return_slot(member.py_slot):
        ret_type = 'int '
        ret_value = '-1'
    elif is_ssize_return_slot(member.py_slot):
        ret_type = 'Py_ssize_t '
        ret_value = '0'
    elif is_hash_return_slot(member.py_slot):
        if spec.abi_version >= (13, 0):
            ret_type = 'Py_hash_t '
            ret_value = '0'
        else:
            ret_type = 'long '
            ret_value = '0L'
    else:
        ret_type = 'PyObject *'
        ret_value = 'SIP_NULLPTR'

    has_args = True

    if is_int_arg_slot(member.py_slot):
        has_args = False
        arg_str = 'PyObject *sipSelf, int a0'
        decl_arg_str = 'PyObject *, int'
    elif member.py_slot is PySlot.CALL:
        if spec.c_bindings or member.allow_keyword_args or member.no_arg_parser:
            arg_str = 'PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds'
        else:
            arg_str = 'PyObject *sipSelf, PyObject *sipArgs, PyObject *'

        decl_arg_str = 'PyObject *, PyObject *, PyObject *'
    elif is_multi_arg_slot(member.py_slot):
        arg_str = 'PyObject *sipSelf, PyObject *sipArgs'
        decl_arg_str = 'PyObject *, PyObject *'
    elif is_zero_arg_slot(member.py_slot):
        has_args = False
        arg_str = 'PyObject *sipSelf'
        decl_arg_str = 'PyObject *'
    elif is_number_slot(member.py_slot):
        arg_str = 'PyObject *sipArg0, PyObject *sipArg1'
        decl_arg_str = 'PyObject *, PyObject *'
    elif member.py_slot is PySlot.SETATTR:
        arg_str = 'PyObject *sipSelf, PyObject *sipName, PyObject *sipValue'
        decl_arg_str = 'PyObject *, PyObject *, PyObject *'
    else:
        arg_str = 'PyObject *sipSelf, PyObject *sipArg'
        decl_arg_str = 'PyObject *, PyObject *'

    sf.write('\n\n')

    slot_decl = f'static {ret_type}slot_'

    if fq_cpp_name is not None:
        slot_decl += fq_cpp_name.as_word + '_'

    if not spec.c_bindings:
        sf.write(f'extern "C" {{{slot_decl}{member.py_name.name}({decl_arg_str});}}\n')

    sf.write(f'{slot_decl}{member.py_name.name}({arg_str})\n{{\n')

    if member.py_slot is PySlot.CALL and member.no_arg_parser:
        for overload in overloads:
            if overload.common is member:
                sf.write_code(overload.method_code)
    else:
        if is_inplace_number_slot(member.py_slot):
            sf.write(
f'''    if (!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject(sip{prefix}_{fq_cpp_name.as_word})))
    {{
        Py_INCREF(Py_NotImplemented);
        return Py_NotImplemented;
    }}

''')

        if not is_number_slot(member.py_slot):
            as_cpp = fq_cpp_name.as_cpp
            gto_name = _gto_name(scope)

            if isinstance(scope, WrappedClass):
                sf.write(
f'''    {as_cpp} *sipCpp = reinterpret_cast<{as_cpp} *>(sipGetCppPtr((sipSimpleWrapper *)sipSelf, {gto_name}));

    if (!sipCpp)
''')
            else:
                sf.write(
f'''    {as_cpp} sipCpp = static_cast<{as_cpp}>(sipConvertToEnum(sipSelf, {gto_name}));

    if (PyErr_Occurred())
''')

            sf.write(f'        return {ret_value};\n\n')

        if has_args:
            sf.write('    PyObject *sipParseErr = SIP_NULLPTR;\n')

        for overload in overloads:
            if overload.common is member and overload.is_abstract:
                sf.write('    PyObject *sipOrigSelf = sipSelf;\n')
                break

        scope_not_enum = not isinstance(scope, WrappedEnum)

        for overload in overloads:
            if overload.common is member:
                dereferenced = scope_not_enum and not overload.dont_deref_self

                _function_body(sf, spec, bindings, scope, overload,
                        dereferenced=dereferenced)

        if has_args:
            if member.py_slot in (PySlot.CONCAT, PySlot.ICONCAT, PySlot.REPEAT, PySlot.IREPEAT):
                sf.write(
f'''
    /* Raise an exception if the argument couldn't be parsed. */
    sipBadOperatorArg(sipSelf, sipArg, {_get_slot_name(member.py_slot)});

    return SIP_NULLPTR;
''')

            else:
                if is_rich_compare_slot(member.py_slot):
                    sf.write(
'''
    Py_XDECREF(sipParseErr);
''')
                elif is_number_slot(member.py_slot) or is_inplace_number_slot(member.py_slot):
                    sf.write(
'''
    Py_XDECREF(sipParseErr);

    if (sipParseErr == Py_None)
        return SIP_NULLPTR;
''')

                if is_number_slot(member.py_slot) or is_rich_compare_slot(member.py_slot):
                    # We can only extend class slots. */
                    if not isinstance(scope, WrappedClass):
                        sf.write(
'''
    PyErr_Clear();

    Py_INCREF(Py_NotImplemented);
    return Py_NotImplemented;
''')
                    elif is_number_slot(member.py_slot):
                        sf.write(
f'''
    return sipPySlotExtend(&sipModuleAPI_{spec.module.py_name}, {_get_slot_name(member.py_slot)}, SIP_NULLPTR, sipArg0, sipArg1);
''')
                    else:
                        sf.write(
f'''
    return sipPySlotExtend(&sipModuleAPI_{spec.module.py_name}, {_get_slot_name(member.py_slot)}, {_gto_name(scope)}, sipSelf, sipArg);
''')
                elif is_inplace_number_slot(member.py_slot):
                    sf.write(
'''
    PyErr_Clear();

    Py_INCREF(Py_NotImplemented);
    return Py_NotImplemented;
''')
                else:
                    member_name = '(sipValue != SIP_NULLPTR ? sipName___setattr__ : sipName___delattr__)' if member.py_slot is PySlot.SETATTR else _cached_name_ref(member.py_name)

                    sf.write(
f'''
    sipNoMethod(sipParseErr, {_cached_name_ref(py_name)}, {member_name}, SIP_NULLPTR);

    return {ret_value};
''')
        else:
            sf.write(
'''
    return 0;
''')

    sf.write('}\n')


def _class_functions(sf, spec, bindings, klass, py_debug):
    """ Generate the member functions for a class. """

    as_word = klass.iface_file.fq_cpp_name.as_word
    scope_s = _scoped_class_name(spec, klass)

    # Any shadow code.
    if klass.has_shadow:
        if not klass.export_derived:
            _shadow_class_declaration(sf, spec, bindings, klass)

        _shadow_code(sf, spec, bindings, klass)

    # The member functions.
    for visible_member in klass.visible_members:
        if visible_member.member.py_slot is None:
            _member_function(sf, spec, bindings, klass, visible_member.member,
                    visible_member.scope)

    # The slot functions.
    for member in klass.members:
        if klass.iface_file.type is IfaceFileType.NAMESPACE:
            _ordinary_function(sf, spec, bindings, member, scope=klass)
        elif member.py_slot is not None:
            _py_slot(sf, spec, bindings, member, scope=klass)

    # 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 == {_gto_name(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_gto_name = _gto_name(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_gto_name})->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_gto_name})
        return static_cast<{sc_scope_s} *>(sipCpp);

''')

        sf.write(
'''    return SIP_NULLPTR;
}
''')

    if klass.iface_file.type is not IfaceFileType.NAMESPACE and not spec.c_bindings:
        # Generate the release function without compiler warnings.
        need_state = False
        need_ptr = need_cast_ptr = _is_used_in_code(klass.dealloc_code,
                'sipCpp')

        public_dtor = klass.dtor is AccessSpecifier.PUBLIC

        if klass.can_create or public_dtor:
            if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject and public_dtor:
                need_ptr = need_cast_ptr = True
            elif klass.has_shadow:
                need_ptr = need_state = True
            elif public_dtor:
                need_ptr = True

        sf.write('\n\n/* Call the instance\'s destructor. */\n')

        sip_cpp_v = 'sipCppV' if spec.c_bindings or need_ptr else ''
        sip_state = ' sipState' if spec.c_bindings or need_state else ''

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static void release_{as_word}(void *, int);}}\n')

        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')

        if len(klass.dealloc_code) != 0:
            sf.write_code(klass.dealloc_code)
            sf.write('\n')

        if klass.can_create or public_dtor:
            release_gil = _release_gil(klass.dtor_gil_action, bindings)

            # If there is an explicit public dtor then assume there is some way
            # to call it which we haven't worked out (because we don't fully
            # understand C++).

            if release_gil:
                sf.write('    Py_BEGIN_ALLOW_THREADS\n\n')

            if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject and public_dtor:
                # QObjects should only be deleted in the threads that they
                # belong to.
                sf.write(
'''    if (QThread::currentThread() == sipCpp->thread())
        delete sipCpp;
    else
        sipCpp->deleteLater();
''')
            elif klass.has_shadow:
                sf.write(
f'''    if (sipState & SIP_DERIVED_CLASS)
        delete reinterpret_cast<sip{as_word} *>(sipCppV);
''')

                if public_dtor:
                    sf.write(
f'''    else
        delete reinterpret_cast<{_scoped_class_name(spec, klass)} *>(sipCppV);
''')
            elif public_dtor:
                sf.write(
f'''    delete reinterpret_cast<{_scoped_class_name(spec, klass)} *>(sipCppV);
''')

            if release_gil:
                sf.write('\n    Py_END_ALLOW_THREADS\n')

        sf.write('}\n')

    # The traverse function.
    if klass.gc_traverse_code is not None:
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static int traverse_{as_word}(void *, visitproc, void *);}}\n')

        sf.write(
f'''static int traverse_{as_word}(void *sipCppV, visitproc sipVisit, void *sipArg)
{{
    {_class_from_void(spec, klass)};
    int sipRes;

''')

        sf.write_code(klass.gc_traverse_code)

        sf.write(
'''
    return sipRes;
}
''')

    # The clear function.
    if klass.gc_clear_code is not None:
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static int clear_{as_word}(void *);}}\n')

        sf.write(
f'''static int clear_{as_word}(void *sipCppV)
{{
    {_class_from_void(spec, klass)};
    int sipRes;

''')

        sf.write_code(klass.gc_clear_code)

        sf.write(
'''
    return sipRes;
}
''')

    # The buffer interface functions.
    if klass.bi_get_buffer_code is not None:
        code = klass.bi_get_buffer_code

        need_cpp = _is_used_in_code(code, 'sipCpp')
        sip_self = _arg_name(spec, 'sipSelf', code)
        sip_cpp_v = 'sipCppV' if spec.c_bindings or need_cpp else ''

        sf.write('\n\n')

        if not py_debug and spec.module.use_limited_api:
            if not spec.c_bindings:
                sf.write(f'extern "C" {{static int getbuffer_{as_word}(PyObject *, void *, sipBufferDef *);}}\n')

            sf.write(f'static int getbuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, sipBufferDef *sipBuffer)\n')
        else:
            if not spec.c_bindings:
                sf.write(f'extern "C" {{static int getbuffer_{as_word}(PyObject *, void *, Py_buffer *, int);}}\n')

            sip_flags = _arg_name(spec, 'sipFlags', code)

            sf.write(f'static int getbuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, Py_buffer *sipBuffer, int {sip_flags})\n')

        sf.write('{\n')

        if need_cpp:
            sf.write(f'    {_class_from_void(spec, klass)};\n')

        sf.write('    int sipRes;\n\n')
        sf.write_code(code)
        sf.write('\n    return sipRes;\n}\n')

    if klass.bi_release_buffer_code is not None:
        code = klass.bi_release_buffer_code

        need_cpp = _is_used_in_code(code, 'sipCpp')
        sip_self = _arg_name(spec, 'sipSelf', code)
        sip_cpp_v = 'sipCppV' if spec.c_bindings or need_cpp else ''

        sf.write('\n\n')

        if not py_debug and spec.module.use_limited_api:
            if not spec.c_bindings:
                sf.write(f'extern "C" {{static void releasebuffer_{as_word}(PyObject *, void *);}}\n')

            sf.write(f'static void releasebuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v})\n')
        else:
            if not spec.c_bindings:
                sf.write(f'extern "C" {{static void releasebuffer_{as_word}(PyObject *, void *, Py_buffer *);}}\n')

            sip_buffer = _arg_name(spec, 'sipBuffer', code)

            sf.write(f'static void releasebuffer_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, Py_buffer *{sip_buffer})\n')

        sf.write('{\n')

        if need_cpp:
            sf.write(f'    {_class_from_void(spec, klass)};\n')

        sf.write_code(code)
        sf.write('}\n')

    # The pickle function.
    if klass.pickle_code is not None:
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static PyObject *pickle_{as_word}(void *);}}\n')

        sf.write(
f'''static PyObject *pickle_{as_word}(void *sipCppV)
{{
    {_class_from_void(spec, klass)};
    PyObject *sipRes;

''')

        sf.write_code(klass.pickle_code)

        sf.write('\n    return sipRes;\n}\n')

    # The finalisation function.
    if klass.finalisation_code is not None:
        code = klass.finalisation_code

        need_cpp = _is_used_in_code(code, 'sipCpp')
        sip_self = _arg_name(spec, 'sipSelf', code)
        sip_cpp_v = 'sipCppV' if spec.c_bindings or need_cpp else ''
        sip_kwds = _arg_name(spec, 'sipKwds', code)
        sip_unused = _arg_name(spec, 'sipUnused', code)

        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static int final_{as_word}(PyObject *, void *, PyObject *, PyObject **);}}\n')

        sf.write(
f'''static int final_{as_word}(PyObject *{sip_self}, void *{sip_cpp_v}, PyObject *{sip_kwds}, PyObject **{sip_unused})
{{
''')

        if need_cpp:
            sf.write(f'    {_class_from_void(spec, klass)};\n\n')

        sf.write_code(code)

        sf.write('}\n')

    # The mixin initialisation function.
    if klass.mixin:
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static int mixin_{as_word}(PyObject *, PyObject *, PyObject *);}}\n')

        sf.write(
f'''static int mixin_{as_word}(PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds)
{{
    return sipInitMixin(sipSelf, sipArgs, sipKwds, (sipClassTypeDef *)&sipTypeDef_{spec.module.py_name}_{as_word});
}}
''')

    # The array allocation helpers.
    if spec.c_bindings or klass.needs_array_helper:
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static void *array_{as_word}(Py_ssize_t);}}\n')

        sf.write(f'static void *array_{as_word}(Py_ssize_t sipNrElem)\n{{\n')

        if spec.c_bindings:
            sf.write(f'    return sipMalloc(sizeof ({scope_s}) * sipNrElem);\n')
        else:
            sf.write(f'    return new {scope_s}[sipNrElem];\n')

        sf.write('}\n')

        if _abi_supports_array(spec):
            sf.write('\n\n')

            if not spec.c_bindings:
                sf.write(f'extern "C" {{static void array_delete_{as_word}(void *);}}\n')

            sf.write(f'static void array_delete_{as_word}(void *sipCpp)\n{{\n')

            if spec.c_bindings:
                sf.write('    sipFree(sipCpp);\n')
            else:
                sf.write(f'    delete[] reinterpret_cast<{scope_s} *>(sipCpp);\n')

            sf.write('}\n')

    # The copy and assignment helpers.
    if spec.c_bindings or klass.needs_copy_helper:
        # The assignment helper.  We assume that there will be a valid
        # assigment operator if there is a a copy ctor.  Note that the source
        # pointer is not const.  This is to allow the source instance to be
        # modified as a consequence of the assignment, eg. if it is
        # implementing some sort of reference counting scheme.

        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static void assign_{as_word}(void *, Py_ssize_t, void *);}}\n')

        sf.write(f'static void assign_{as_word}(void *sipDst, Py_ssize_t sipDstIdx, void *sipSrc)\n{{\n')

        if spec.c_bindings:
            sf.write(f'    (({scope_s} *)sipDst)[sipDstIdx] = *(({scope_s} *)sipSrc);\n')
        else:
            sf.write(f'    reinterpret_cast<{scope_s} *>(sipDst)[sipDstIdx] = *reinterpret_cast<{scope_s} *>(sipSrc);\n')

        sf.write('}\n')

        # The copy helper.
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static void *copy_{as_word}(const void *, Py_ssize_t);}}\n')

        sf.write(f'static void *copy_{as_word}(const void *sipSrc, Py_ssize_t sipSrcIdx)\n{{\n')

        if spec.c_bindings:
            sf.write(
f'''    {scope_s} *sipPtr = sipMalloc(sizeof ({scope_s}));
    *sipPtr = ((const {scope_s} *)sipSrc)[sipSrcIdx];

    return sipPtr;
''')
        else:
            sf.write(
f'''    return new {scope_s}(reinterpret_cast<const {scope_s} *>(sipSrc)[sipSrcIdx]);
''')

        sf.write('}\n')

    # The dealloc function.
    if _need_dealloc(spec, bindings, klass):
        sf.write('\n\n')

        if not spec.c_bindings:
            sf.write(f'extern "C" {{static void dealloc_{as_word}(sipSimpleWrapper *);}}\n')

        sf.write(f'static void dealloc_{as_word}(sipSimpleWrapper *sipSelf)\n{{\n')

        if bindings.tracing:
            sf.write(f'    sipTrace(SIP_TRACE_DEALLOCS, "dealloc_{as_word}()\\n");\n\n')

        # Disable the virtual handlers.
        if klass.has_shadow:
            sf.write(
f'''    if (sipIsDerivedClass(sipSelf))
        reinterpret_cast<sip{as_word} *>(sipGetAddress(sipSelf))->sipPySelf = SIP_NULLPTR;

''')

        if spec.c_bindings or klass.dtor is AccessSpecifier.PUBLIC or (klass.has_shadow and klass.dtor is AccessSpecifier.PROTECTED):
            sf.write('    if (sipIsOwnedByPython(sipSelf))\n    {\n')

            if klass.delay_dtor:
                sf.write('        sipAddDelayedDtor(sipSelf);\n')
            elif spec.c_bindings:
                if klass.dealloc_code:
                    sf.write(klass.dealloc_code)

                sf.write('        sipFree(sipGetAddress(sipSelf));\n')
            else:
                flag = 'sipIsDerivedClass(sipSelf)' if klass.has_shadow else '0'
                sf.write(f'        release_{as_word}(sipGetAddress(sipSelf), {flag});\n')

            sf.write('    }\n')

        sf.write('}\n')

    # The type initialisation function.
    if klass.can_create:
        _type_init(sf, spec, bindings, klass)


def _shadow_code(sf, spec, bindings, klass):
    """ Generate the shadow (derived) class code. """

    klass_name = klass.iface_file.fq_cpp_name.as_word
    klass_cpp_name = klass.iface_file.fq_cpp_name.as_cpp

    # Generate the wrapper class constructors.
    nr_virtuals = _count_virtual_overloads(spec, klass)

    for ctor in _unique_class_ctors(spec, klass):
        throw_specifier = _throw_specifier(bindings, ctor.throw_args)
        protected_call_args = _protected_call_args(spec, ctor.cpp_signature)
        args = fmt_signature_as_cpp_definition(spec, ctor.cpp_signature,
                scope=klass.iface_file)

        sf.write(f'\nsip{klass_name}::sip{klass_name}({args}){throw_specifier}: {_scoped_class_name(spec, klass)}({protected_call_args}), sipPySelf(SIP_NULLPTR)\n{{\n')

        if bindings.tracing:
            args = fmt_signature_as_cpp_declaration(spec, ctor.cpp_signature,
                    scope=klass.iface_file)

            sf.write(f'    sipTrace(SIP_TRACE_CTORS, "sip{klass_name}::sip{klass_name}({args}){throw_specifier} (this=0x%%08x)\\n", this);\n\n')

        if nr_virtuals > 0:
            sf.write('    memset(sipPyMethods, 0, sizeof (sipPyMethods));\n')

        sf.write('}\n')

    # The destructor.
    if klass.dtor is not AccessSpecifier.PRIVATE:
        throw_specifier = _throw_specifier(bindings, klass.dtor_throw_args)

        sf.write(f'\nsip{klass_name}::~sip{klass_name}(){throw_specifier}\n{{\n')

        if bindings.tracing:
            sf.write(f'    sipTrace(SIP_TRACE_DTORS, "sip{klass_name}::~sip{klass_name}(){throw_specifier} (this=0x%%08x)\\n", this);\n\n')

        if klass.dtor_virtual_catcher_code is not None:
            sf.write_code(klass.dtor_virtual_catcher_code)

        sf.write('    sipInstanceDestroyedEx(&sipPySelf);\n}\n')

    # The meta methods if required.
    if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject:
        module_name = spec.module.py_name
        gto_name = _gto_name(klass)

        if not klass.pyqt_no_qmetaobject:
            sf.write(
f'''
const QMetaObject *sip{klass_name}::metaObject() const
{{
    if (sipGetInterpreter())
        return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : sip_{module_name}_qt_metaobject(sipPySelf, {gto_name});

    return {klass_cpp_name}::metaObject();
}}
''')

        sf.write(
f'''
int sip{klass_name}::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{{
    _id = {klass_cpp_name}::qt_metacall(_c, _id, _a);

    if (_id >= 0)
    {{
        SIP_BLOCK_THREADS
        _id = sip_{module_name}_qt_metacall(sipPySelf, {gto_name}, _c, _id, _a);
        SIP_UNBLOCK_THREADS
    }}

    return _id;
}}

void *sip{klass_name}::qt_metacast(const char *_clname)
{{
    void *sipCpp;

    return (sip_{module_name}_qt_metacast(sipPySelf, {gto_name}, _clname, &sipCpp) ? sipCpp : {klass_cpp_name}::qt_metacast(_clname));
}}
''')

    # Generate the virtual catchers.
    for virt_nr, virtual_overload in enumerate(_unique_class_virtual_overloads(spec, klass)):
        _virtual_catcher(sf, spec, bindings, klass, virtual_overload, virt_nr)

    # Generate the wrapper around each protected member function.
    _protected_definitions(sf, spec, klass)


def _protected_enums(sf, spec, klass):
    """ Generate the protected enums for a class. """

    for enum in spec.enums:
        if not enum.is_protected:
            continue

        # See if the class defining the enum is in our class hierachy.
        for mro_klass in klass.mro:
            if mro_klass is enum.scope:
                break
        else:
            continue

        sf.write(
'''
    /* Expose this protected enum. */
    enum''')

        if enum.fq_cpp_name is not None:
            sf.write(' sip' + enum.fq_cpp_name.base_name)

        sf.write(' {')

        eol = '\n'
        scope_cpp_name = enum.scope.iface_file.fq_cpp_name.as_cpp

        for member in enum.members:
            member_cpp_name = member.cpp_name

            sf.write(f'{eol}        {member_cpp_name} = {scope_cpp_name}::{member_cpp_name}')

            eol = ',\n'

        sf.write('\n    };\n')


def _virtual_catcher(sf, spec, bindings, klass, virtual_overload, virt_nr):
    """ Generate the catcher for a virtual function. """

    overload = virtual_overload.overload
    result = overload.cpp_signature.result

    result_type = fmt_argument_as_cpp_type(spec, result,
            scope=klass.iface_file, make_public=True)
    klass_name = klass.iface_file.fq_cpp_name.as_word
    overload_cpp_name = _overload_cpp_name(overload)
    throw_specifier = _throw_specifier(bindings, overload.throw_args)
    const = ' const' if overload.is_const else ''

    protection_state = set()
    _remove_protections(overload.cpp_signature, protection_state)

    args = fmt_signature_as_cpp_definition(spec, overload.cpp_signature,
            scope=klass.iface_file)
    sf.write(f'\n{result_type} sip{klass_name}::{overload_cpp_name}({args}){const}{throw_specifier}\n{{\n')

    if bindings.tracing:
        args = fmt_signature_as_cpp_declaration(spec, overload.cpp_signature,
                scope=klass.iface_file)
        sf.write(f'    sipTrace(SIP_TRACE_CATCHERS, "{result_type} sip{klass_name}::{overload_cpp_name}({args}){const}{throw_specifier} (this=0x%%08x)\\n", this);\n\n')

    _restore_protections(protection_state)

    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.abi_version >= (12, 8) else ''

    klass_py_name_ref = _cached_name_ref(klass.py_name) if overload.is_abstract else 'SIP_NULLPTR'
    member_py_name_ref = _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

    sf.write('\n    if (!sipMeth)\n')

    if overload.virtual_call_code is not None:
        sf.write('    {\n')

        if result is not None:
            sip_res = fmt_argument_as_cpp_type(spec, result, name='sipRes',
                    scope=klass.iface_file)
            sf.write(f'        {sip_res};\n')

        sf.write('\n')

        sf.write_code(overload.virtual_call_code)

        sip_res = ' sipRes' if result is not None else ''
        sf.write(
f'''
        return{sip_res};
    }}
''')

    elif overload.is_abstract:
        _default_instance_return(sf, spec, result)

    else:
        if result is None:
            sf.write('    {\n        ')
        else:
            sf.write('        return ')

        args = []
        for arg_nr, arg in enumerate(overload.cpp_signature.args):
            args.append(fmt_argument_as_name(spec, arg, arg_nr))
        args = ', '.join(args)
 
        sf.write(f'{klass.iface_file.fq_cpp_name.as_cpp}::{overload_cpp_name}({args});\n')
 
        if result is None:
            # Note that we should also generate this if the function returns a
            # value, but we are lazy and this is all that is needed by PyQt.
            if overload.new_thread:
                sf.write('        sipEndThread();\n')

            sf.write('        return;\n    }\n')

    sf.write('\n')

    _virtual_handler_call(sf, spec, klass, virtual_overload, result)

    sf.write('}\n')


def _virtual_handler_call(sf, spec, klass, virtual_overload, result):
    """ Generate a call to a single virtual handler. """

    module = spec.module
    overload = virtual_overload.overload
    handler = virtual_overload.handler

    module_name = module.py_name

    protection_state = _fake_protected_args(handler.cpp_signature)

    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 *')

    if len(handler.cpp_signature.args) > 0:
        sf.write(', ' + fmt_signature_as_cpp_declaration(spec,
                handler.cpp_signature, scope=klass.iface_file))

    _restore_protected_args(protection_state)

    # Add extra arguments for all the references we need to keep.
    args_keep = False
    result_keep = False
    saved_keys = {}

    if result is not None and _keep_py_reference(result):
        result_keep = True
        saved_keys[result] = result.key
        result.key = module.next_key
        module.next_key -= 1
        sf.write(', int')

    for arg in overload.cpp_signature.args:
        if arg.is_out and _keep_py_reference(arg):
            args_keep = True
            saved_keys[arg] = arg.key
            arg.key = module.next_key
            module.next_key -= 1
            sf.write(', int')

    sf.write(');\n\n    ')

    trailing = ''

    if not overload.new_thread and result is not None:
        sf.write('return ')

        if result.type is ArgumentType.ENUM and result.definition.is_protected:
            protection_state = set()
            _remove_protection(result, protection_state)

            enum_type = fmt_enum_as_cpp_type(result.definition)
            sf.write(f'static_cast<{enum_type}>(')
            trailing = ')'

            _restore_protections(protection_state)

    error_handler = handler.virtual_error_handler

    if error_handler is None:
        error_handler_ref = '0'
    elif error_handler.module is module:
        error_handler_ref = f'sipVEH_{module_name}_{error_handler.name}'
    else:
        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')

    for arg_nr, arg in enumerate(overload.cpp_signature.args):
        prefix = ''

        if arg.type is ArgumentType.CLASS and arg.definition.is_protected:
            if arg.is_reference or len(arg.derefs) == 0:
                prefix = '&'
        elif arg.type is ArgumentType.ENUM and arg.definition.is_protected:
            prefix = '(' + fmt_enum_as_cpp_type(arg.definition) + ')'

        arg_name = fmt_argument_as_name(spec, arg, arg_nr)

        sf.write(f', {prefix}{arg_name}')

    # Pass the keys to maintain the kept references.
    if result_keep:
        sf.write(', ' + str(result.key))

    if args_keep:
        for arg in overload.cpp_signature.args:
            if arg.is_out and _keep_py_reference(arg):
                sf.write(', ' + str(arg.key))

    for type, key in saved_keys.items():
        type.key = key

    sf.write(f'){trailing};\n')

    if overload.new_thread:
        sf.write('\n    sipEndThread();\n')


def _cast_zero(spec, arg):
    """ Return a cast to zero. """

    if arg.type is ArgumentType.ENUM:
        enum = arg.definition
        enum_type = fmt_enum_as_cpp_type(enum)

        if len(enum.members) == 0:
            return f'({enum_type})0'

        if enum.is_scoped:
            scope = enum_type
        elif enum.scope is not None:
            scope = _enum_class_scope(spec, enum)
        else:
            scope = ''

        return scope + '::' + enum.members[0].cpp_name

    if arg.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM, ArgumentType.ELLIPSIS):
        return 'SIP_NULLPTR'

    return '0'


def _default_instance_return(sf, spec, result):
    """ Generate a statement to return the default instance of a type typically
    on error (ie. when there is nothing sensible to return).
    """

    # Handle the trivial case.
    if result is None:
        sf.write('        return;\n')
        return

    result_type_plain = fmt_argument_as_cpp_type(spec, result, plain=True,
            no_derefs=True)

    # Handle any %InstanceCode.
    if len(result.derefs) == 0 and result.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        instance_code = result.definition.instance_code
    else:
        instance_code = None

    if instance_code is not None:
        sf.write(
f'''    {{
        static {result_type_plain} *sipCpp = SIP_NULLPTR;

        if (!sipCpp)
        {{
''')

        sf.write_code(instance_code)

        sf.write(
'''        }

        return *sipCpp;
    }
''')

        return

    sf.write('        return ')

    if result.type is ArgumentType.MAPPED and len(result.derefs) == 0:
        # We don't know anything about the mapped type so we just hope is has a
        # default ctor.

        if result.is_reference:
            sf.write('*new ')

        sf.write(result_type_plain + '()')

    elif result.type is ArgumentType.CLASS and len(result.derefs) == 0:
        # If we don't have a suitable ctor then the generated code will issue
        # an error message.

        ctor = result.definition.default_ctor

        if ctor is not None and ctor.access_specifier is AccessSpecifier.PUBLIC and ctor.cpp_signature is not None:
            # If this is a badly designed class.  We can only generate correct
            # code by leaking memory.
            if result.is_reference:
                sf.write('*new ')

            sf.write(result_type_plain)
            sf.write(_call_default_ctor(spec, ctor))
        else:
            raise UserException(
                    result.definition.iface_file.fq_cpp_name.as_cpp + " must have a default constructor")

    else:
        sf.write(_cast_zero(spec, result))

    sf.write(';\n')


def _call_default_ctor(spec, ctor):
    """ Return the call to a default ctor. """

    args = []

    for arg in ctor.cpp_signature.args:
        if arg.default_value is not None:
            break

        # Do what we can to provide type information to the compiler.
        if arg.type is ArgumentType.CLASS and len(arg.derefs) > 0 and not arg.is_reference:
            class_type = fmt_argument_as_cpp_type(spec, arg)
            arg_s = f'static_cast<{class_type}>(0)'
        elif arg.type is ArgumentType.ENUM:
            enum_type = fmt_enum_as_cpp_type(arg.definition)
            arg_s = f'static_cast<{enum_type}>(0)'
        elif arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
            arg_s = '0.0F'
        elif arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
            arg_s = '0.0'
        elif arg.type in (ArgumentType.UINT, ArgumentType.SIZE):
            arg_s = '0U'
        elif arg.type in (ArgumentType.LONG, ArgumentType.LONGLONG):
            arg_s = '0L'
        elif arg.type in (ArgumentType.ULONG, ArgumentType.ULONGLONG):
            arg_s = '0UL'
        elif arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.USTRING, ArgumentType.SSTRING, ArgumentType.STRING) and len(arg.derefs) == 0:
            arg_s = "'\\0'"
        elif arg.type is ArgumentType.WSTRING and len(arg.derefs) == 0:
            arg_s = "L'\\0'"
        else:
            arg_s = '0'

        args.append(arg_s)

    return '(' + ', '.join(args) + ')'


def _protected_declarations(sf, spec, klass):
    """ Generate the declarations of the protected wrapper functions for a
    class.
    """

    no_intro = True

    for visible_member in klass.visible_members:
        if visible_member.member.py_slot is not None:
            continue

        for overload in visible_member.scope.overloads:
            if overload.common is not visible_member.member or overload.access_specifier is not AccessSpecifier.PROTECTED:
                continue

            # Check we haven't already handled this signature (eg. if we have
            # specified the same method with different Python names.
            if _is_duplicate_protected(spec, klass, overload):
                continue

            if no_intro:
                sf.write(
'''
    /*
     * There is a public method for every protected method visible from
     * this class.
     */
''')

                no_intro = False

            sf.write('    ')

            if overload.is_static:
                sf.write('static ')

            result_type = fmt_argument_as_cpp_type(spec,
                    overload.cpp_signature.result, scope=klass.iface_file)

            if not overload.is_static and not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation):
                sf.write(f'{result_type} sipProtectVirt_{overload.cpp_name}(bool')

                if len(overload.cpp_signature.args) > 0:
                    sf.write(', ')
            else:
                sf.write(f'{result_type} sipProtect_{overload.cpp_name}(')

            args = fmt_signature_as_cpp_declaration(spec,
                    overload.cpp_signature, scope=klass.iface_file)
            const_s = ' const' if overload.is_const else ''

            sf.write(f'{args}){const_s};\n')


def _protected_definitions(sf, spec, klass):
    """ Generate the definitions of the protected wrapper functions for a
    class.
    """

    klass_name = klass.iface_file.fq_cpp_name.as_word

    for visible_member in klass.visible_members:
        if visible_member.member.py_slot is not None:
            continue

        for overload in visible_member.scope.overloads:
            if overload.common is not visible_member.member or overload.access_specifier is not AccessSpecifier.PROTECTED:
                continue

            # Check we haven't already handled this signature (eg. if we have
            # specified the same method with different Python names.
            if _is_duplicate_protected(spec, klass, overload):
                continue

            overload_name = overload.cpp_name
            result = overload.cpp_signature.result
            result_type = fmt_argument_as_cpp_type(spec, result,
                    scope=klass.iface_file)

            sf.write('\n')

            if not overload.is_static and not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation):
                sf.write(f'{result_type} sip{klass_name}::sipProtectVirt_{overload_name}(bool sipSelfWasArg')

                if len(overload.cpp_signature.args) > 0:
                    sf.write(', ')
            else:
                sf.write(f'{result_type} sip{klass_name}::sipProtect_{overload_name}(')

            args = fmt_signature_as_cpp_definition(spec,
                    overload.cpp_signature, scope=klass.iface_file)
            const_s = ' const' if overload.is_const else ''

            sf.write(f'{args}){const_s}\n{{\n')

            closing_parens = ')'

            if result.type is ArgumentType.VOID and len(result.derefs) == 0:
                sf.write('    ')
            else:
                sf.write('    return ')

                if result.type is ArgumentType.CLASS and result.definition.is_protected:
                    scope_s = _scoped_class_name(spec, klass)
                    sf.write(f'static_cast<{scope_s} *>(')
                    closing_parens += ')'
                elif result.type is ArgumentType.ENUM and result.definition.is_protected:
                    # One or two older compilers can't handle a static_cast
                    # here so we revert to a C-style cast.
                    sf.write('(' + fmt_enum_as_cpp_type(result.definition) + ')')

            protected_call_args = _protected_call_args(spec,
                    overload.cpp_signature)

            if not overload.is_abstract:
                visible_scope_s = _scoped_class_name(spec, visible_member.scope)

                if overload.is_virtual or overload.is_virtual_reimplementation:
                    sf.write(f'(sipSelfWasArg ? {visible_scope_s}::{overload_name}({protected_call_args}) : ')
                    closing_parens += ')'
                else:
                    sf.write(visible_scope_s + '::')

            sf.write(f'{overload_name}({protected_call_args}{closing_parens};\n}}\n')


def _is_duplicate_protected(spec, klass, target_overload):
    """ Return True if a protected method is a duplicate. """

    for visible_member in klass.visible_members:
        if visible_member.member.py_slot is not None:
            continue

        for overload in visible_member.scope.overloads:
            if overload.common is not visible_member.member or overload.access_specifier is not AccessSpecifier.PROTECTED:
                continue

            if overload is target_overload:
                return False

            if overload.cpp_name == target_overload.cpp_name and same_signature(spec, overload.cpp_signature, target_overload.cpp_signature):
                return True

    # We should never get here.
    return False


def _protected_call_args(spec, signature):
    """ Return the arguments for a call to a protected method. """

    args = []

    for arg_nr, arg in enumerate(signature.args):
        if arg.type is ArgumentType.ENUM and arg.definition.is_protected:
            cast_s = '(' + arg.definition.fq_cpp_name.as_cpp + ')'
        else:
            cast_s = ''

        args.append(cast_s + fmt_argument_as_name(spec, arg, arg_nr))

    return ', '.join(args)


def _virtual_handler(sf, spec, handler):
    """ Generate the function that does most of the work to handle a particular
    virtual function.
    """

    module = spec.module
    result = handler.cpp_signature.result

    result_decl = fmt_argument_as_cpp_type(spec, result)

    result_is_returned = (result.type is not ArgumentType.VOID or len(result.derefs) != 0)
    result_is_reference = False
    result_instance_code = None

    if result_is_returned:
        # If we are returning a reference to an instance then we take care to
        # handle Python errors but still return a valid C++ instance.
        if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(result.derefs) == 0:
            if result.is_reference:
                result_is_reference = True
            else:
                result_instance_code = result.definition.instance_code

    sf.write(
f'''
{result_decl} sipVH_{module.py_name}_{handler.handler_nr}(sip_gilstate_t sipGILState, sipVirtErrorHandlerFunc sipErrorHandler, sipSimpleWrapper *sipPySelf, PyObject *sipMethod''')

    if len(handler.cpp_signature.args) > 0:
        sf.write(', ' + fmt_signature_as_cpp_definition(spec,
                handler.cpp_signature))

    # Define the extra arguments for kept references.
    if result_is_returned and _keep_py_reference(result):
        sf.write(', int')

        if handler.virtual_catcher_code is None or _is_used_in_code(handler.virtual_catcher_code, 'sipResKey'):
            sf.write(' sipResKey')

    for arg_nr, arg in enumerate(handler.cpp_signature.args):
        if arg.is_out and _keep_py_reference(arg):
            arg_name = fmt_argument_as_name(spec, arg, arg_nr)
            sf.write(f', int {arg_name}Key')

    sf.write(')\n{\n')

    if result_is_returned:
        result_plain_decl = fmt_argument_as_cpp_type(spec, result, plain=True)

        if result_instance_code is not None:
            sf.write(
f'''    static {result_plain_decl} *sipCpp = SIP_NULLPTR;

    if (!sipCpp)
    {{
''')

            sf.write_code(result_instance_code)

            sf.write('    }\n\n')

        sf.write('    ')

        # wchar_t * return values are always on the heap.  To reduce memory
        # leaks we keep the last result around until we have a new one.  This
        # means that ownership of the return value stays with the function
        # returning it - which is consistent with how other types work, even
        # though it may not be what's required in all cases.  Note that we
        # should do this in the code that calls the handler instead of here (as
        # we do with strings) so that it doesn't get shared between all
        # callers.
        if result.type is ArgumentType.WSTRING and len(result.derefs) == 1:
            sf.write('static ')

        sf.write(result_plain_decl)

        sf.write(' {}sipRes'.format('*' if result_is_reference else ''))

        sipres_value = ''

        if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.TEMPLATE) and len(result.derefs) == 0:
            if result_instance_code is not None:
                sipres_value = ' = *sipCpp'
            elif result.type is ArgumentType.CLASS:
                ctor = result.definition.default_ctor

                if ctor is not None and ctor.access_specifier is AccessSpecifier.PUBLIC and ctor.cpp_signature is not None and len(ctor.cpp_signature.args) > 0 and ctor.cpp_signature.args[0].default_value is None:
                    sipres_value = _call_default_ctor(spec, ctor)
        elif result.type is ArgumentType.ENUM and result.definition.is_protected:
            # Currently SIP generates the virtual handlers before any shadow
            # classes which means that the compiler doesn't know about the
            # handling of protected enums.  Therefore we can only initialise to
            # 0.
            sipres_value = ' = 0'
        else:
            # We initialise the result to try and suppress a compiler warning.
            sipres_value = ' = ' + _cast_zero(spec, result)

        sf.write(sipres_value + ';\n')

        if result.type is ArgumentType.WSTRING and len(result.derefs) == 1:
            free_arg = 'const_cast<wchar_t *>(sipRes)' if result.is_const else 'sipRes'

            sf.write(
f'''
    if (sipRes)
    {{
        // Return any previous result to the heap.
        sipFree({free_arg});
        sipRes = SIP_NULLPTR;
    }}

''')

    if handler.virtual_catcher_code is not None:
        error_flag = _need_error_flag(handler.virtual_catcher_code)
        old_error_flag = _need_old_error_flag(handler.virtual_catcher_code)

        if error_flag:
            sf.write('    sipErrorState sipError = sipErrorNone;\n')
        elif old_error_flag:
            sf.write('    int sipIsErr = 0;\n')

        sf.write('\n')

        sf.write_code(handler.virtual_catcher_code)

        sf.write(
'''
    Py_DECREF(sipMethod);
''')

        if error_flag or old_error_flag:
            error_test = 'sipError != sipErrorNone' if error_flag else 'sipIsErr'

            sf.write(
f'''
    if ({error_test})
        sipCallErrorHandler(sipErrorHandler, sipPySelf, sipGILState);
''')

        sf.write(
'''
    SIP_RELEASE_GIL(sipGILState)
''')

        if result_is_returned:
            sf.write(
'''
    return sipRes;
''')

        sf.write('}\n')

        return

    # See how many values we expect.
    nr_values = 1 if result_is_returned else 0

    for arg in handler.py_signature.args:
        if arg.is_out:
            nr_values += 1

    # Call the method.
    if nr_values == 0:
        sf.write(
'    sipCallProcedureMethod(sipGILState, sipErrorHandler, sipPySelf, sipMethod, ')
    else:
        sf.write(
'    PyObject *sipResObj = sipCallMethod(SIP_NULLPTR, sipMethod, ')

    sf.write(_tuple_builder(spec, handler.py_signature))

    if nr_values == 0:
        sf.write(''');
}
''')

        return

    # Generate the call to sipParseResultEx().
    params = ['sipGILState', 'sipErrorHandler', 'sipPySelf', 'sipMethod',
            'sipResObj']

    # Build the format string.
    fmt = '"'

    if nr_values == 0:
        fmt += 'Z'
    else:
        if nr_values > 1:
            fmt += '('

        if result_is_returned:
            fmt += _get_parse_result_format(result, spec,
                    result_is_reference=result_is_reference,
                    transfer_result=handler.transfer_result)

        for arg in handler.py_signature.args:
            if arg.is_out:
                fmt += _get_parse_result_format(arg, spec)

        if nr_values > 1:
            fmt += ')'

    fmt += '"'

    params.append(fmt)

    # Add the destination pointers.
    if result_is_returned:
        _add_parse_result_extra_params(params, spec, module, result)
        params.append('&sipRes')

    for arg_nr, arg in enumerate(handler.py_signature.args):
        if arg.is_out:
            _add_parse_result_extra_params(params, spec, module, arg, arg_nr)

            arg_ref = '&' if arg.is_reference else ''
            arg_name = fmt_argument_as_name(spec, arg, arg_nr)
            params.append(arg_ref + arg_name)

    params = ', '.join(params)

    return_code = 'int sipRc = ' if result_is_reference or handler.abort_on_exception else ''

    sf.write(f''');

    {return_code}sipParseResultEx({params});
''')

    if result_is_returned:
        if result_is_reference or handler.abort_on_exception:
            sf.write(
'''
    if (sipRc < 0)
''')

            if handler.abort_on_exception:
                sf.write('        abort();\n')
            else:
                _default_instance_return(sf, spec, result)

        result_ref = '*' if result_is_reference else ''

        sf.write(
f'''
    return {result_ref}sipRes;
''')

    sf.write('}\n')


def _add_parse_result_extra_params(params, spec, module, arg, arg_nr=-1):
    """ Add any extra parameters needed by sipParseResultEx() for a particular
    type to a list.
    """

    if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.ENUM):
        params.append(_gto_name(arg.definition))
    elif arg.type is ArgumentType.PYTUPLE:
        params.append('&PyTuple_Type')
    elif arg.type is ArgumentType.PYLIST:
        params.append('&PyList_Type')
    elif arg.type is ArgumentType.PYDICT:
        params.append('&PyDict_Type')
    elif arg.type is ArgumentType.PYSLICE:
        params.append('&PySlice_Type')
    elif arg.type is ArgumentType.PYTYPE:
        params.append('&PyType_Type')
    elif arg.type is ArgumentType.CAPSULE:
        params.append('"' + arg.definition.as_cpp + '"')
    elif _keep_py_reference(arg):
        if arg_nr < 0:
            params.append('sipResKey')
        else:
            params.append(fmt_argument_as_name(spec, arg, arg_nr) + 'Key')


def _get_parse_result_format(arg, spec, result_is_reference=False,
        transfer_result=False):
    """ Return the format characters used by sipParseResultEx() for a
    particular type.
    """

    nr_derefs = len(arg.derefs)
    no_derefs = (nr_derefs == 0)

    if arg.type in (ArgumentType.MAPPED, ArgumentType.FAKE_VOID, ArgumentType.CLASS):
        f = 0x00

        if nr_derefs == 0:
            f |= 0x01

            if not result_is_reference:
                f |= 0x04
        elif nr_derefs == 1:
            if arg.is_out:
                f |= 0x04
            elif arg.disallow_none:
                f |= 0x01

        if transfer_result:
            f |= 0x02

        return 'H' + str(f)

    if arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL):
        return 'b'

    if arg.type is ArgumentType.ASCII_STRING:
        return 'aA' if no_derefs else 'AA'

    if arg.type is ArgumentType.LATIN1_STRING:
        return 'aL' if no_derefs else 'AL'

    if arg.type is ArgumentType.UTF8_STRING:
        return 'a8' if no_derefs else 'A8'

    if arg.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING):
        return 'c' if no_derefs else 'B'

    if arg.type is ArgumentType.WSTRING:
        return 'w' if no_derefs else 'x'

    if arg.type is ArgumentType.ENUM:
        return 'F' if arg.definition.fq_cpp_name is not None else 'e'

    if arg.type is ArgumentType.BYTE:
        return 'I' if _abi_has_working_char_conversion(spec) else 'L'

    if arg.type is ArgumentType.SBYTE:
        return 'L'

    if arg.type is ArgumentType.UBYTE:
        return 'M'

    if arg.type is ArgumentType.USHORT:
        return 't'

    if arg.type is ArgumentType.SHORT:
        return 'h'

    if arg.type in (ArgumentType.INT, ArgumentType.CINT):
        return 'i'

    if arg.type is ArgumentType.UINT:
        return 'u'

    if arg.type is ArgumentType.SIZE:
        return '='

    if arg.type is ArgumentType.LONG:
        return 'l'

    if arg.type is ArgumentType.ULONG:
        return 'm'

    if arg.type is ArgumentType.LONGLONG:
        return 'n'

    if arg.type is ArgumentType.ULONGLONG:
        return 'o'

    if arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
        return 'V'

    if arg.type is ArgumentType.CAPSULE:
        return 'z'

    if arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
        return 'f'

    if arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
        return 'd'

    if arg.type is ArgumentType.PYOBJECT:
        return 'O'

    if arg.type in (ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.SLICE, ArgumentType.PYTYPE):
        return 'N' if arg.allow_none else 'T'

    if arg.type is ArgumentType.PYBUFFER:
        return '$' if arg.allow_none else '!'

    if arg.type is ArgumentType.PYENUM:
        return '^' if arg.allow_none else '&'

    # We should never get here.
    return ' '


def _tuple_builder(spec, signature):
    """ Return the code to build a tuple of Python arguments. """

    array_len_arg_nr = -1
    format_s = '"'

    for arg_nr, arg in enumerate(signature.args):
        if not arg.is_in:
            continue

        format_ch = ''
        nr_derefs = len(arg.derefs)
        not_a_pointer = (nr_derefs == 0 or (nr_derefs == 1 and arg.is_out))

        if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING):
            format_ch = 'a' if not_a_pointer else 'A'

        elif arg.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING):
            if not_a_pointer:
                format_ch = 'c'
            elif arg.array is ArrayArgument.ARRAY:
                format_ch = 'g'
            else:
                format_ch = 's'

        elif arg.type is ArgumentType.WSTRING:
            if not_a_pointer:
                format_ch = 'w'
            elif arg.array is ArrayArgument.ARRAY:
                format_ch = 'G'
            else:
                format_ch = 'x'

        elif arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL):
            format_ch = 'b'

        elif arg.type is ArgumentType.ENUM:
            format_ch = 'e' if arg.definition.fq_cpp_name is None else 'F'

        elif arg.type is ArgumentType.CINT:
            format_ch = 'i'

        elif arg.type is ArgumentType.UINT:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'u'

        elif arg.type is ArgumentType.INT:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'i'

        elif arg.type is ArgumentType.SIZE:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = '='

        elif arg.type in (ArgumentType.BYTE, ArgumentType.SBYTE):
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                # Note that this is the correct thing to do even if char is
                # unsigned.
                format_ch = 'L'

        elif arg.type is ArgumentType.UBYTE:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'M'

        elif arg.type is ArgumentType.USHORT:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 't'

        elif arg.type is ArgumentType.SHORT:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'h'

        elif arg.type is ArgumentType.LONG:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'l'

        elif arg.type is ArgumentType.ULONG:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'm'

        elif arg.type is ArgumentType.LONGLONG:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'n'

        elif arg.type is ArgumentType.ULONGLONG:
            if arg.array is ArrayArgument.ARRAY_SIZE:
                array_len_arg_nr = arg_nr
            else:
                format_ch = 'o'

        elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
            format_ch = 'V'

        elif arg.type is ArgumentType.CAPSULE:
            format_ch = 'z'

        elif arg.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
            format_ch = 'f'

        elif arg.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
            format_ch = 'd'

        elif arg.type in (ArgumentType.MAPPED, ArgumentType.CLASS):
            if arg.array is ArrayArgument.ARRAY:
                format_ch = 'r'
            else:
                format_ch = 'N' if _needs_heap_copy(arg) else 'D'

        elif arg.type is ArgumentType.FAKE_VOID:
            format_ch = 'D'

        elif arg.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM):
            format_ch = 'S'

        format_s += format_ch

    format_s += '"'

    args = [format_s]

    for arg_nr, arg in enumerate(signature.args):
        if not arg.is_in:
            continue

        nr_derefs = len(arg.derefs)

        if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING, ArgumentType.WSTRING):
            if not (nr_derefs == 0 or (nr_derefs == 1 and arg.is_out)):
                nr_derefs -= 1

        elif arg.type in (ArgumentType.MAPPED, ArgumentType.CLASS, ArgumentType.FAKE_VOID):
            if nr_derefs > 0:
                nr_derefs -= 1

        elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
            nr_derefs -= 1

        if arg.type in (ArgumentType.MAPPED, ArgumentType.CLASS, ArgumentType.FAKE_VOID):
            prefix = ''
            ref = ''

            needs_copy = _needs_heap_copy(arg)

            if needs_copy:
                prefix = 'new ' + fmt_argument_as_cpp_type(spec, arg, plain=True, no_derefs=True) + '('
            else:
                if arg.is_const:
                    prefix = 'const_cast<' + fmt_argument_as_cpp_type(spec, arg, plain=True, no_derefs=True, use_typename=False) + ' *>('

                if len(arg.derefs) == 0:
                    ref = '&'
                else:
                    ref = '*' * nr_derefs

            suffix = '' if prefix == '' else ')'

            arg_ref = prefix + ref + fmt_argument_as_name(spec, arg, arg_nr) + suffix

            args.append(arg_ref)

            if arg.array is ArrayArgument.ARRAY:
                array_len_arg_name = fmt_argument_as_name(spec,
                        signature.args[array_len_arg_nr], array_len_arg_nr)
                args.append('(Py_ssize_t)' + array_len_arg_name)

            args.append(_gto_name(arg.definition))

            if arg.array is not ArrayArgument.ARRAY:
                args.append('SIP_NULLPTR')

        elif arg.type is ArgumentType.CAPSULE:
            args.append('"' + arg.definition.as_cpp + '"')

        else:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                args.append(
                        '*' * nr_derefs + fmt_argument_as_name(spec, arg, arg_nr))

            if arg.array is ArrayArgument.ARRAY:
                array_len_arg_name = fmt_argument_as_name(spec,
                        signature.args[array_len_arg_nr], array_len_arg_nr)
                args.append('(Py_ssize_t)' + array_len_arg_name)
            elif arg.type is ArgumentType.ENUM and arg.definition.fq_cpp_name is not None:
                args.append(_gto_name(arg.definition))

    return ', '.join(args)


def _used_includes(sf, used):
    """ Generate the library header #include directives required by either a
    class or a module.
    """

    sf.write('\n')

    for iface_file in used:
        sf.write_code(iface_file.type_header_code)


def _module_api(sf, spec, bindings):
    """ Generate the API details for a module. """

    module = spec.module
    module_name = module.py_name

    for klass in spec.classes:
        if klass.iface_file.module is module:
            _class_api(sf, spec, klass)

        if klass.export_derived:
            sf.write_code(klass.iface_file.type_header_code)
            _shadow_class_declaration(sf, spec, bindings, klass)

    for mapped_type in spec.mapped_types:
        if mapped_type.iface_file.module is module:
            _mapped_type_api(sf, spec, 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')

    _enum_macros(sf, spec)

    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')


def _imported_module_api(sf, spec, imported_module):
    """ Generate the API details for an imported module. """

    module_name = spec.module.py_name

    for klass in spec.classes:
        iface_file = klass.iface_file

        if iface_file.module is imported_module:
            if iface_file.needed:
                gto_name = _gto_name(klass)

                if iface_file.type is IfaceFileType.NAMESPACE:
                    sf.write(f'\n#if !defined({gto_name})')

                sf.write(f'\n#define {gto_name} 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')

            _enum_macros(sf, spec, scope=klass,
                    imported_module=imported_module)

    for mapped_type in spec.mapped_types:
        iface_file = mapped_type.iface_file

        if iface_file.module is imported_module:
            if iface_file.needed:
                sf.write(f'\n#define {_gto_name(mapped_type)} sipImportedTypes_{module_name}_{iface_file.module.py_name}[{iface_file.type_nr}].it_td\n')

            _enum_macros(sf, spec, scope=mapped_type,
                    imported_module=imported_module)

    for exception in spec.exceptions:
        iface_file = exception.iface_file

        if iface_file.module is imported_module and exception.exception_nr >= 0:
            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')

    _enum_macros(sf, spec, imported_module=imported_module)


def _mapped_type_api(sf, spec, mapped_type):
    """ Generate the API details for a mapped type. """

    iface_file = mapped_type.iface_file

    module_name = spec.module.py_name
    mapped_type_name = iface_file.fq_cpp_name.as_word

    sf.write(
f'''
#define {_gto_name(mapped_type)} sipExportedTypes_{module_name}[{iface_file.type_nr}]

extern sipMappedTypeDef sipTypeDef_{module_name}_{mapped_type_name};
''')

    _enum_macros(sf, spec, scope=mapped_type)


def _class_api(sf, spec, klass):
    """ Generate the C++ API for a class. """

    iface_file = klass.iface_file

    module_name = spec.module.py_name

    sf.write('\n')

    if klass.real_class is None and not klass.is_hidden_namespace:
        sf.write(f'#define {_gto_name(klass)} sipExportedTypes_{module_name}[{iface_file.type_nr}]\n')

    _enum_macros(sf, spec, 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')


def _enum_macros(sf, spec, scope=None, imported_module=None):
    """ Generate the type macros for enums. """

    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 = 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'

        if value is not None:
            sf.write(f'\n#define {_gto_name(enum)} {value}\n')


def _shadow_class_declaration(sf, spec, bindings, klass):
    """ Generate the shadow class declaration. """

    klass_name = klass.iface_file.fq_cpp_name.as_word

    sf.write(
f'''

class sip{klass_name} : public {_scoped_class_name(spec, klass)}
{{
public:
''')

    # Define a shadow class for any protected classes we have.
    for protected_klass in spec.classes:
        if not protected_klass.is_protected:
            continue

        # See if the class defining the class is in our class hierachy.
        for mro in klass.mro:
            if mro is protected_klass.scope:
                break
        else:
            continue

        protected_klass_base_name = protected_klass.iface_file.fq_cpp_name.base_name

        sf.write(
f'''    class sip{protected_klass_base_name} : public {protected_klass_base_name} {{
    public:
''')

        _protected_enums(sf, spec, protected_klass)

        sf.write('    };\n\n')

    # The constructor declarations.
    for ctor in _unique_class_ctors(spec, klass):
        args = fmt_signature_as_cpp_declaration(spec, ctor.cpp_signature,
                scope=klass.iface_file)
        throw_specifier = _throw_specifier(bindings, ctor.throw_args)

        sf.write(f'    sip{klass_name}({args}){throw_specifier};\n')

    # The destructor.
    if klass.dtor is not AccessSpecifier.PRIVATE:
        virtual_s = 'virtual ' if len(klass.virtual_overloads) != 0 else ''
        throw_specifier = _throw_specifier(bindings, klass.dtor_throw_args)

        sf.write(f'    {virtual_s}~sip{klass_name}(){throw_specifier};\n')

    # The metacall methods if required.
    if (_pyqt5(spec) or _pyqt6(spec)) and klass.is_qobject:
        sf.write(
'''
    int qt_metacall(QMetaObject::Call, int, void **) SIP_OVERRIDE;
    void *qt_metacast(const char *) SIP_OVERRIDE;
''')

        if not klass.pyqt_no_qmetaobject:
            sf.write('    const QMetaObject *metaObject() const SIP_OVERRIDE;\n')

    # The exposure of protected enums.
    _protected_enums(sf, spec, klass)

    # The wrapper around each protected member function.
    _protected_declarations(sf, spec, klass)

    # The catcher around each virtual function in the hierarchy.
    for virt_nr, virtual_overload in enumerate(_unique_class_virtual_overloads(spec, klass)):
        if virt_nr == 0:
            sf.write(
'''
    /*
     * There is a protected method for every virtual method visible from
     * this class.
     */
protected:
''')

        sf.write('    ')
        _overload_decl(sf, spec, bindings, klass, virtual_overload.overload)
        sf.write(';\n')

    sf.write(
'''
public:
    sipSimpleWrapper *sipPySelf;
''')

    # The private declarations.
    sf.write(
f'''
private:
    sip{klass_name}(const sip{klass_name} &);
    sip{klass_name} &operator = (const sip{klass_name} &);
''')

    nr_virtual_overloads = _count_virtual_overloads(spec, klass)
    if nr_virtual_overloads > 0:
        sf.write(f'\n    char sipPyMethods[{nr_virtual_overloads}];\n')

    sf.write('};\n')


def _overload_decl(sf, spec, bindings, klass, overload):
    """ Generate the C++ declaration for an overload. """

    cpp_signature = overload.cpp_signature

    # Counter the handling of protected enums by the argument formatter.
    protection_state = set()
    _remove_protections(cpp_signature, protection_state)

    result_type = fmt_argument_as_cpp_type(spec, cpp_signature.result,
            scope=klass.iface_file)
 
    args = []
    for arg in cpp_signature.args:
        args.append(
                fmt_argument_as_cpp_type(spec, arg, scope=klass.iface_file))

    args = ', '.join(args)
 
    const_s = ' const' if overload.is_const else ''
    throw_specifier = _throw_specifier(bindings, overload.throw_args)

    sf.write(f'{result_type} {_overload_cpp_name(overload)}({args}){const_s}{throw_specifier} SIP_OVERRIDE')

    _restore_protections(protection_state)


def _call_args(sf, spec, cpp_signature, py_signature):
    """ Generate typed arguments for a call. """

    for arg_nr, arg in enumerate(cpp_signature.args):
        if arg_nr > 0:
            sf.write(', ')

        # See if the argument needs dereferencing or it's address taking.
        indirection = ''
        nr_derefs = len(arg.derefs)

        if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING, ArgumentType.WSTRING):
            if nr_derefs > (0 if arg.is_out else 1) and not arg.is_reference:
                indirection = '&'

        elif arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
            if nr_derefs == 2:
                indirection = '&'
            elif nr_derefs == 0:
                indirection = '*'

        elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
            if nr_derefs == 2:
                indirection = '&'

        else:
            if nr_derefs == 1:
                indirection = '&'

        # See if we need to cast a Python void * to the correct C/C++ pointer
        # type.  Note that we assume that the arguments correspond and are just
        # different types.
        need_cast = False

        if py_signature is not cpp_signature and len(py_signature.args) == len(cpp_signature.args):
            py_arg = py_signature.args[arg_nr]

            VOID_TYPES = (ArgumentType.VOID, ArgumentType.CAPSULE)

            if py_arg.type in VOID_TYPES and arg.type not in VOID_TYPES and len(py_arg.derefs) == nr_derefs:
                need_cast = True

        arg_name = fmt_argument_as_name(spec, arg, arg_nr)
        arg_cpp_type_name = fmt_argument_as_cpp_type(spec, arg, plain=True,
                no_derefs=True)

        if need_cast:
            if spec.c_bindings:
                sf.write(f'({arg_cpp_type_name} *){arg_name}')
            else:
                sf.write(f'reinterpret_cast<{arg_cpp_type_name} *>({arg_name})')
        else:
            sf.write(indirection)

            if arg.array is ArrayArgument.ARRAY_SIZE:
                sf.write(f'({arg_cpp_type_name})')

            sf.write(arg_name)


def _get_named_value_decl(spec, scope, type, name):
    """ Return the declaration of a named variable to hold a C++ value. """

    saved_derefs = type.derefs
    saved_is_const = type.is_const
    saved_is_reference = type.is_reference

    if len(type.derefs) == 0:
        if type.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
            type.derefs = [False]
        else:
            type.is_const = False

    type.is_reference = False

    named_value_decl = fmt_argument_as_cpp_type(spec, type, name=name,
            scope=scope.iface_file if isinstance(scope, (WrappedClass, MappedType)) else None)

    type.derefs = saved_derefs
    type.is_const = saved_is_const
    type.is_reference = saved_is_reference

    return named_value_decl


def _argument_variable(sf, spec, scope, arg, arg_nr):
    """ Generate the definition of an argument variable and any supporting
    variables.
    """

    scope_iface_file = scope.iface_file if isinstance(scope, (WrappedClass, MappedType)) else None
    arg_name = fmt_argument_as_name(spec, arg, arg_nr)
    supporting_default_value = ' = 0' if arg.default_value is not None else ''
    nr_derefs = len(arg.derefs)

    if arg.is_in and arg.default_value is not None and arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and (nr_derefs == 0 or arg.is_reference):
        arg_cpp_type = fmt_argument_as_cpp_type(spec, arg,
                scope=scope_iface_file)

        # Generate something to hold the default value as it cannot be assigned
        # straight away.
        expression = fmt_value_list_as_cpp_expression(spec, arg.default_value)
        sf.write(f'        {arg_cpp_type} {arg_name}def = {expression};\n')

    # Adjust the type so we have the type that will really handle it.
    saved_derefs = arg.derefs
    saved_type = arg.type
    saved_is_reference = arg.is_reference
    saved_is_const = arg.is_const

    if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING, ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING, ArgumentType.WSTRING):
        if not arg.is_reference:
            if nr_derefs == 2:
                arg.derefs = arg.derefs[0:1]
            elif nr_derefs == 1 and arg.is_out:
                arg.derefs = []

    elif arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED, ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
        arg.derefs = [arg.derefs[0] if len(arg.derefs) != 0 else False]

    else:
        arg.derefs = []

    # Array sizes are always Py_ssize_t.
    if arg.array is ArrayArgument.ARRAY_SIZE:
        arg.type = ArgumentType.SSIZE

    arg.is_reference = False

    if len(arg.derefs) == 0:
        arg.is_const = False

    modified_arg_cpp_type = fmt_argument_as_cpp_type(spec, arg,
            scope=scope_iface_file)

    sf.write(f'        {modified_arg_cpp_type} {arg_name}')

    # Restore the argument to its original state.
    arg.derefs = saved_derefs
    arg.type = saved_type
    arg.is_reference = saved_is_reference
    arg.is_const = saved_is_const

    # Generate any default value.
    if arg.is_in and arg.default_value is not None:
        sf.write(' = ')

        if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and (nr_derefs == 0 or arg.is_reference):
            sf.write(f'&{arg_name}def')
        else:
            sf.write(fmt_value_list_as_cpp_expression(spec, arg.default_value))

    sf.write(';\n')

    # Some types have supporting variables.
    if arg.is_in:
        if arg.get_wrapper:
            sf.write(f'        PyObject *{arg_name}Wrapper{supporting_default_value};\n')
        elif arg.key is not None:
            sf.write(f'        PyObject *{arg_name}Keep{supporting_default_value};\n')

        if arg.type is ArgumentType.CLASS:
            if arg.array is ArrayArgument.ARRAY and _abi_supports_array(spec):
                if _abi_supports_array(spec):
                    sf.write(f'        int {arg_name}IsTemp = 0;\n')
            else:
                if arg.definition.convert_to_type_code is not None and not arg.is_constrained:
                    sf.write(f'        int {arg_name}State = 0;\n')

                    if _type_needs_user_state(arg):
                        sf.write(f'        void *{arg_name}UserState = SIP_NULLPTR;\n')

        elif arg.type is ArgumentType.MAPPED:
            if not arg.definition.no_release and not arg.is_constrained:
                sf.write(f'        int {arg_name}State = 0;\n')

                if _type_needs_user_state(arg):
                    sf.write(f'        void *{arg_name}UserState = SIP_NULLPTR;\n')

        elif arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING):
            if arg.key is None and nr_derefs == 1:
                sf.write(f'        PyObject *{arg_name}Keep{supporting_default_value};\n')


def _type_definition(sf, spec, bindings, klass, py_debug):
    """ Generate the type structure that contains all the information needed by
    the meta-type.  A sub-set of this is used to extend namespaces.
    """

    module = spec.module
    klass_name = klass.iface_file.fq_cpp_name.as_word

    # The super-types table.
    if len(klass.superclasses) != 0:
        encoded_types = []

        for superclass in klass.superclasses:
            last = superclass is klass.superclasses[-1]
            encoded_types.append(_encoded_type(module, superclass, last=last))

        encoded_types = ', '.join(encoded_types)

        sf.write(
f'''

/* Define this type's super-types. */
static sipEncodedTypeDef supers_{klass_name}[] = {{{encoded_types}}};
''')

    # The slots table.
    is_slots = False

    for member in klass.members:
        if member.py_slot is None:
            continue

        if not is_slots:
            sf.write(
f'''

/* Define this type's Python slots. */
static sipPySlotDef slots_{klass_name}[] = {{
''')

            is_slots = True

        slot_name = _get_slot_name(member.py_slot)
        member_name = member.py_name
        sf.write(f'    {{(void *)slot_{klass_name}_{member_name}, {slot_name}}},\n')

    if is_slots:
        sf.write('    {0, (sipPySlotType)0}\n};\n')

    # The attributes tables.
    nr_methods = _class_method_table(sf, spec, bindings, klass)

    if spec.abi_version >= (13, 0):
        nr_enum_members = -1
    else:
        nr_enum_members = _enum_member_table(sf, spec, scope=klass)

    # The property and variable handlers.
    nr_variables = 0

    if klass.has_variable_handlers:
        for variable in spec.variables:
            if variable.scope is klass and variable.needs_handler:
                nr_variables += 1

                _variable_getter(sf, spec, variable)

                if _can_set_variable(variable):
                    _variable_setter(sf, spec, variable)

    # Generate any property docstrings.
    for prop in klass.properties:
        nr_variables += 1

        if prop.docstring is not None:
            docstring = _docstring_text(prop.docstring)
            sf.write(f'\nPyDoc_STRVAR(doc_{klass_name}_{prop.name}, "{docstring}");\n')

    # The variables table.
    if nr_variables != 0:
        sf.write(f'\nsipVariableDef variables_{klass_name}[] = {{\n')

    for prop in klass.properties:
        fields = ['PropertyVariable', _cached_name_ref(prop.name)]

        getter_nr = find_method(klass, prop.getter).member_nr
        fields.append(f'&methods_{klass_name}[{getter_nr}]')

        if prop.setter is None:
            fields.append('SIP_NULLPTR')
        else:
            setter_nr = find_method(klass, prop.setter).member_nr
            fields.append(f'&methods_{klass_name}[{setter_nr}]')

        # We don't support a deleter yet.
        fields.append('SIP_NULLPTR')

        if prop.docstring is None:
            fields.append('SIP_NULLPTR')
        else:
            fields.append(f'doc_{klass_name}_{prop.name}')

        fields = ', '.join(fields)
        sf.write(f'    {{{fields}}},\n')

    if klass.has_variable_handlers:
        for variable in spec.variables:
            if variable.scope is klass and variable.needs_handler:
                variable_name = variable.fq_cpp_name.as_word

                fields = []

                fields.append('ClassVariable' if variable.is_static else 'InstanceVariable')
                fields.append(_cached_name_ref(variable.py_name))
                fields.append('(PyMethodDef *)varget_' + variable_name)

                if _can_set_variable(variable):
                    fields.append('(PyMethodDef *)varset_' + variable_name)
                else:
                    fields.append('SIP_NULLPTR')

                fields.append('SIP_NULLPTR')
                fields.append('SIP_NULLPTR')

                fields = ', '.join(fields)
                sf.write(f'    {{{fields}}},\n')

    if nr_variables != 0:
        sf.write('};\n')

    # Generate each instance table.
    is_inst_class = _class_instances(sf, spec, scope=klass)
    is_inst_voidp = _void_pointer_instances(sf, spec, scope=klass)
    is_inst_char = _char_instances(sf, spec, scope=klass)
    is_inst_string = _string_instances(sf, spec, scope=klass)
    is_inst_int = _int_instances(sf, spec, scope=klass)
    is_inst_long = _long_instances(sf, spec, scope=klass)
    is_inst_ulong = _unsigned_long_instances(sf, spec, scope=klass)
    is_inst_longlong = _long_long_instances(sf, spec, scope=klass)
    is_inst_ulonglong = _unsigned_long_long_instances(sf, spec, scope=klass)
    is_inst_double = _double_instances(sf, spec, scope=klass)

    # Generate the docstrings.
    if _has_class_docstring(bindings, klass):
        docstring_ref = 'doc_' + klass_name

        sf.write(f'\nPyDoc_STRVAR({docstring_ref}, "')
        _class_docstring(sf, spec, bindings, klass)
        sf.write('");\n')
    else:
        docstring_ref = 'SIP_NULLPTR'

    # Generate any plugin-specific data structures.
    plugin_ref = 'SIP_NULLPTR'

    if _pyqt5(spec) or _pyqt6(spec):
        if _pyqt_class_plugin(sf, spec, bindings, klass):
            plugin_ref = '&plugin_' + klass_name

    # The type definition structure itself.
    base_fields = []
    container_fields = []
    class_fields = []

    if spec.abi_version < (13, 0):
        base_fields.append('-1')
        base_fields.append('SIP_NULLPTR')

    base_fields.append('SIP_NULLPTR')

    flags = []

    if klass.is_abstract:
        flags.append('SIP_TYPE_ABSTRACT')

    if klass.subclass_base is not None:
        flags.append('SIP_TYPE_SCC')

    if klass.handles_none:
        flags.append('SIP_TYPE_ALLOW_NONE')

    if klass.has_nonlazy_method:
        flags.append('SIP_TYPE_NONLAZY')

    if module.call_super_init:
        flags.append('SIP_TYPE_SUPER_INIT')

    if not py_debug and module.use_limited_api:
        flags.append('SIP_TYPE_LIMITED_API')

    flags.append('SIP_TYPE_NAMESPACE' if klass.iface_file.type is IfaceFileType.NAMESPACE else 'SIP_TYPE_CLASS')

    if len(flags) == 0:
        flags.append('0')

    base_fields.append('|'.join(flags))

    base_fields.append(_cached_name_ref(klass.iface_file.cpp_name, as_nr=True))
    base_fields.append('SIP_NULLPTR')
    base_fields.append(plugin_ref)

    container_fields.append(_cached_name_ref(klass.py_name, as_nr=True) if klass.real_class is None else '-1')

    if klass.real_class is not None:
        encoded_type = _encoded_type(module, klass.real_class)
    elif _py_scope(klass.scope) is not None:
        encoded_type = _encoded_type(module, klass.scope)
    else:
        encoded_type = '{0, 0, 1}'

    container_fields.append(encoded_type)

    if nr_methods == 0:
        container_fields.append('0, SIP_NULLPTR')
    else:
        container_fields.append(str(nr_methods) + ', methods_' + klass_name)

    if nr_enum_members == 0:
        container_fields.append('0, SIP_NULLPTR')
    elif nr_enum_members > 0:
        container_fields.append(str(nr_enum_members) + ', enummembers_' + klass_name)

    if nr_variables == 0:
        container_fields.append('0, SIP_NULLPTR')
    else:
        container_fields.append(str(nr_variables) + ', variables_' + klass_name)

    instances = []

    instances.append(
            _class_object_ref(is_inst_class, 'typeInstances', klass_name))
    instances.append(
            _class_object_ref(is_inst_voidp, 'voidPtrInstances', klass_name))
    instances.append(
            _class_object_ref(is_inst_char, 'charInstances', klass_name))
    instances.append(
            _class_object_ref(is_inst_string, 'stringInstances', klass_name))
    instances.append(
            _class_object_ref(is_inst_int, 'intInstances', klass_name))
    instances.append(
            _class_object_ref(is_inst_long, 'longInstances', klass_name))
    instances.append(
            _class_object_ref(is_inst_ulong, 'unsignedLongInstances',
                    klass_name))
    instances.append(
            _class_object_ref(is_inst_longlong, 'longLongInstances',
                    klass_name))
    instances.append(
            _class_object_ref(is_inst_ulonglong, 'unsignedLongLongInstances',
                    klass_name))
    instances.append(
            _class_object_ref(is_inst_double, 'doubleInstances', klass_name))

    container_fields.append('{' + ', '.join(instances) + '}')

    class_fields.append(docstring_ref)
    class_fields.append(_cached_name_ref(klass.metatype, as_nr=True) if klass.metatype is not None else '-1')
    class_fields.append(_cached_name_ref(klass.supertype, as_nr=True) if klass.supertype is not None else '-1')
    class_fields.append(
            _class_object_ref((len(klass.superclasses) != 0), 'supers',
                    klass_name))
    class_fields.append(_class_object_ref(is_slots, 'slots', klass_name))
    class_fields.append(
                _class_object_ref(klass.can_create, 'init_type', klass_name))
    class_fields.append(
                _class_object_ref((klass.gc_traverse_code is not None),
                        'traverse', klass_name))
    class_fields.append(
                _class_object_ref((klass.gc_clear_code is not None), 'clear',
                        klass_name))
    class_fields.append(
                _class_object_ref((klass.bi_get_buffer_code is not None),
                        'getbuffer', klass_name))
    class_fields.append(
                _class_object_ref((klass.bi_release_buffer_code is not None),
                        'releasebuffer', klass_name))
    class_fields.append(
                _class_object_ref(_need_dealloc(spec, bindings, klass),
                        'dealloc', klass_name))
    class_fields.append(
                _class_object_ref((spec.c_bindings or klass.needs_copy_helper),
                        'assign', klass_name))
    class_fields.append(
                _class_object_ref((spec.c_bindings or klass.needs_array_helper),
                        'array', klass_name))
    class_fields.append(
                _class_object_ref((spec.c_bindings or klass.needs_copy_helper),
                        'copy', klass_name))
    class_fields.append(
                _class_object_ref((not spec.c_bindings and klass.iface_file.type is not IfaceFileType.NAMESPACE),
                        'release', klass_name))
    class_fields.append(
            _class_object_ref((len(klass.superclasses) != 0), 'cast',
                    klass_name))
    class_fields.append(
                _class_object_ref((klass.convert_to_type_code is not None and klass.iface_file.type is not IfaceFileType.NAMESPACE),
                        'convertTo', klass_name))
    class_fields.append(
                _class_object_ref((klass.convert_from_type_code is not None and klass.iface_file.type is not IfaceFileType.NAMESPACE),
                        'convertFrom', klass_name))
    class_fields.append('SIP_NULLPTR')
    class_fields.append(
                _class_object_ref((klass.pickle_code is not None), 'pickle',
                        klass_name))
    class_fields.append(
                _class_object_ref((klass.finalisation_code is not None),
                        'final', klass_name))
    class_fields.append(_class_object_ref(klass.mixin, 'mixin', klass_name))

    if _abi_supports_array(spec):
        class_fields.append(
                    _class_object_ref((spec.c_bindings or klass.needs_array_helper),
                            'array_delete', klass_name))

        if klass.can_create:
            class_fields.append(f'sizeof ({_scoped_class_name(spec, klass)})')
        else:
            class_fields.append('0')

    base_fields = ',\n        '.join(base_fields)
    container_fields = ',\n        '.join(container_fields)
    class_fields = ',\n    '.join(class_fields)

    sf.write(
f'''

sipClassTypeDef sipTypeDef_{module.py_name}_{klass_name} = {{
    {{
        {base_fields},
    }},
    {{
        {container_fields},
    }},
    {class_fields},
}};
''')


def _class_object_ref(test, object_name, klass_name):
    """ Return an appropriate reference to a class-specific object. """

    return object_name + '_' + klass_name if test else 'SIP_NULLPTR'


def _can_set_variable(variable):
    """ Return True if a variable can be set. """

    if variable.no_setter:
        return False

    if len(variable.type.derefs) == 0 and variable.type.is_const:
        return False

    return True


def _pyqt_emitters(sf, spec, klass):
    """ Generate the PyQt emitters for a class. """

    klass_name = klass.iface_file.fq_cpp_name.as_word
    scope_s = _scoped_class_name(spec, klass)
    klass_name_ref = _cached_name_ref(klass.py_name)

    for member in klass.members:
        in_emitter = False

        for overload in klass.overloads:
            if not (overload.common is member and overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL and _has_optional_args(overload)):
                continue

            if not in_emitter:
                in_emitter = True

                sf.write('\n\n')

                if not spec.c_bindings:
                    sf.write(f'extern "C" {{static int emit_{klass_name}_{overload.cpp_name}(void *, PyObject *);}}\n\n')

                sf.write(
f'''static int emit_{klass_name}_{overload.cpp_name}(void *sipCppV, PyObject *sipArgs)
{{
    PyObject *sipParseErr = SIP_NULLPTR;
    {scope_s} *sipCpp = reinterpret_cast<{scope_s} *>(sipCppV);
''')

            # Generate the code that parses the args and emits the appropriate
            # overloaded signal.
            sf.write('\n    {\n')

            _arg_parser(sf, spec, klass, overload.py_signature)

            sf.write(
f'''        {{
            Py_BEGIN_ALLOW_THREADS
            sipCpp->{overload.cpp_name}(''')

            _call_args(sf, spec, overload.cpp_signature, overload.py_signature)

            sf.write(''');
            Py_END_ALLOW_THREADS

''')

            _delete_temporaries(sf, spec, overload.py_signature)

            sf.write(
'''
            return 0;
        }
    }
''')

        if in_emitter:
            member_name_ref = _cached_name_ref(member.py_name)

            sf.write(
f'''
    sipNoMethod(sipParseErr, {klass_name_ref}, {member_name_ref}, SIP_NULLPTR);

    return -1;
}}
''')


def _pyqt_signal_table_entry(sf, spec, bindings, klass, signal, member_nr):
    """ Generate an entry in the PyQt signal table. """

    klass_name = klass.iface_file.fq_cpp_name.as_word

    stripped = False
    signature_state = {}

    args = []

    for arg in signal.cpp_signature.args:
        # Do some signal argument normalisation so that Qt doesn't have to.
        if arg.is_const and (arg.is_reference or len(arg.derefs) == 0):
            signature_state[arg] = arg.is_reference

            arg.is_const = False
            arg.is_reference = False

        if arg.scopes_stripped != 0:
            strip = arg.scopes_stripped
            stripped = True
        else:
            strip = STRIP_GLOBAL

        args.append(
                fmt_argument_as_cpp_type(spec, arg, scope=klass.iface_file,
                        strip=strip))

    # Note the lack of a separating space.
    args = ','.join(args)

    sf.write(f'    {{"{signal.cpp_name}({args})')

    # If a scope was stripped then append an unstripped version which can be
    # parsed by PyQt.
    if stripped:
        args = []

        for arg in signal.cpp_signature.args:
            args.append(
                    fmt_argument_as_cpp_type(spec, arg, scope=klass.iface_file,
                            strip=STRIP_GLOBAL))

        # Note the lack of a separating space.
        args = ','.join(args)

        sf.write(f'|({args})')

    sf.write('", ')

    # Restore the signature state.
    for arg, is_reference in signature_state.items():
        arg.is_const = True
        arg.is_reference = is_reference

    if bindings.docstrings:
        sf.write('"')

        if signal.docstring is not None:
            if signal.docstring.signature is DocstringSignature.PREPENDED:
                _overload_auto_docstring(sf, spec, signal)
                sf.write('\\n')

            sf.write(_docstring_text(signal.docstring))

            if signal.docstring.signature is DocstringSignature.APPENDED:
                sf.write('\\n')
                _overload_auto_docstring(sf, spec, signal)
        else:
            sf.write('\\1')
            _overload_auto_docstring(sf, spec, signal)

        sf.write('", ')
    else:
        sf.write('SIP_NULLPTR, ')

    sf.write(f'&methods_{klass_name}[{member_nr}], ' if member_nr >= 0 else 'SIP_NULLPTR, ')

    sf.write(f'emit_{klass_name}_{signal.cpp_name}' if _has_optional_args(signal) else 'SIP_NULLPTR')

    sf.write('},\n')


def _get_slot_name(slot_type):
    """ Return the sip module's string equivalent of a slot. """

    return slot_type.name.lower() + '_slot'


def _type_init(sf, spec, bindings, klass):
    """ Generate the initialisation function for the type. """

    klass_name = klass.iface_file.fq_cpp_name.as_word

    # See if we need to name the self and owner arguments so that we can avoid
    # a compiler warning about an unused argument.
    need_self = (spec.c_bindings or klass.has_shadow)
    need_owner = spec.c_bindings

    for ctor in klass.ctors:
        if _is_used_in_code(ctor.method_code, 'sipSelf'):
            need_self = True

        if ctor.transfer is Transfer.TRANSFER:
            need_owner = True
        else:
            for arg in ctor.py_signature.args:
                if not arg.is_in:
                    continue

                if arg.key is not None:
                    need_self = True

                if arg.transfer is Transfer.TRANSFER:
                    need_self = True

                if arg.transfer is Transfer.TRANSFER_THIS:
                    need_owner = True

    sf.write('\n\n')

    if not spec.c_bindings:
        sf.write(f'extern "C" {{static void *init_type_{klass_name}(sipSimpleWrapper *, PyObject *, PyObject *, PyObject **, PyObject **, PyObject **);}}\n')

    sip_self = 'sipSelf' if need_self else ''
    sip_owner = 'sipOwner' if need_owner else ''
    sip_cpp_type = 'sip' + klass_name if klass.has_shadow else _scoped_class_name(spec, klass)

    sf.write(
f'''static void *init_type_{klass_name}(sipSimpleWrapper *{sip_self}, PyObject *sipArgs, PyObject *sipKwds, PyObject **sipUnused, PyObject **{sip_owner}, PyObject **sipParseErr)
{{
    {sip_cpp_type} *sipCpp = SIP_NULLPTR;
''')

    if bindings.tracing:
        sf.write(f'\n    sipTrace(SIP_TRACE_INITS, "init_type_{klass_name}()\\n");\n')

    # Generate the code that parses the Python arguments and calls the correct
    # constructor.
    for ctor in klass.ctors:
        if ctor.access_specifier is AccessSpecifier.PRIVATE:
            continue

        sf.write('\n    {\n')

        if ctor.method_code is not None:
            error_flag = _need_error_flag(ctor.method_code)
            old_error_flag = _need_old_error_flag(ctor.method_code)
        else:
            error_flag = old_error_flag = False

        _arg_parser(sf, spec, klass, ctor.py_signature, ctor=ctor)
        _constructor_call(sf, spec, bindings, klass, ctor, error_flag,
                old_error_flag)

        sf.write('    }\n')

    sf.write(
'''
    return SIP_NULLPTR;
}
''')


def _count_virtual_overloads(spec, klass):
    """ Return the number of virtual members in a class. """

    return len(list(_unique_class_virtual_overloads(spec, klass)))

 
def _handling_exceptions(bindings, throw_args):
    """ Return True if exceptions from a callable are being handled. """

    # Handle any exceptions if there was no throw specifier, or a non-empty
    # throw specifier.
    return bindings.exceptions and (throw_args is None or throw_args.arguments is not None)


def _try(sf, bindings, throw_args):
    """ Generate the try block for a call. """

    # Generate the block if there was no throw specifier, or a non-empty throw
    # specifier.
    if _handling_exceptions(bindings, throw_args):
        sf.write(
'''            try
            {
''')


def _catch(sf, spec, bindings, py_signature, throw_args, release_gil):
    """ Generate the catch blocks for a call. """

    if _handling_exceptions(bindings, throw_args):
        use_handler = _abi_has_next_exception_handler(spec)

        sf.write('            }\n')

        if not use_handler:
            if throw_args is not None:
                for exception in throw_args.arguments:
                    _catch_block(sf, spec, exception,
                            py_signature=py_signature, release_gil=release_gil)
            elif spec.module.default_exception is not None:
                _catch_block(sf, spec, spec.module.default_exception,
                        py_signature=py_signature, release_gil=release_gil)

        sf.write(
'''            catch (...)
            {
''')

        if release_gil:
            sf.write(
'''                Py_BLOCK_THREADS

''')

        _delete_outs(sf, spec, py_signature)
        _delete_temporaries(sf, spec, 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;

''')

        sf.write(
'''                sipRaiseUnknownException();
                return SIP_NULLPTR;
            }
''')


def _catch_block(sf, spec, exception, py_signature=None, release_gil=False):
    """ Generate a single catch block. """

    exception_fq_cpp_name = exception.iface_file.fq_cpp_name

    # The global scope is stripped from the exception name to be consistent
    # with older versions of SIP.
    exception_cpp_stripped = exception_fq_cpp_name.cpp_stripped(STRIP_GLOBAL)

    sip_exception_ref = 'sipExceptionRef' if exception.class_exception is not None or _is_used_in_code(exception.raise_code, 'sipExceptionRef') else ''

    sf.write(
f'''            catch ({exception_cpp_stripped} &{sip_exception_ref})
            {{
''')

    if release_gil:
        sf.write(
'''
                Py_BLOCK_THREADS
''')

    if py_signature is not None:
        _delete_outs(sf, spec, py_signature)
        _delete_temporaries(sf, spec, py_signature)
        result = 'SIP_NULLPTR'
    else:
        result = 'true'

    # See if the exception is a wrapped class.
    if exception.class_exception is not None:
        exception_cpp = exception_fq_cpp_name.as_cpp

        sf.write(
f'''                /* Hope that there is a valid copy ctor. */
                {exception_cpp} *sipExceptionCopy = new {exception_cpp}(sipExceptionRef);

                sipRaiseTypeException({_gto_name(exception)}, sipExceptionCopy);
''')
    else:
        sf.write_code(exception.raise_code)

    sf.write(
f'''
                return {result};
            }}
''')


def _throw_specifier(bindings, throw_args):
    """ Return a throw specifier. """

    return ' noexcept' if bindings.exceptions and throw_args is not None and throw_args.arguments is None else ''


def _constructor_call(sf, spec, bindings, klass, ctor, error_flag,
        old_error_flag):
    """ Generate a single constructor call. """

    klass_name = klass.iface_file.fq_cpp_name.as_word
    scope_s = _scoped_class_name(spec, klass)

    sf.write('        {\n')

    if ctor.premethod_code is not None:
        sf.write('\n')
        sf.write_code(ctor.premethod_code)
        sf.write('\n')

    if error_flag:
        sf.write('            sipErrorState sipError = sipErrorNone;\n\n')
    elif old_error_flag:
        sf.write('            int sipIsErr = 0;\n\n')

    if ctor.deprecated:
        # Note that any temporaries will leak if an exception is raised.
        sf.write(
f'''            if (sipDeprecated({_cached_name_ref(klass.py_name)}, SIP_NULLPTR) < 0)
                return SIP_NULLPTR;

''')

    # Call any pre-hook.
    if ctor.prehook is not None:
        sf.write(f'            sipCallHook("{ctor.prehook}");\n\n')

    if ctor.method_code is not None:
        sf.write_code(ctor.method_code)
    elif spec.c_bindings:
        sf.write(f'            sipCpp = sipMalloc(sizeof ({scope_s}));\n')
    else:
        release_gil = _release_gil(ctor.gil_action, bindings)

        if ctor.raises_py_exception:
            sf.write('            PyErr_Clear();\n\n')

        if release_gil:
            sf.write('            Py_BEGIN_ALLOW_THREADS\n')

        _try(sf, bindings, ctor.throw_args)

        klass_type = 'sip' + klass_name if klass.has_shadow else scope_s
        sf.write(f'            sipCpp = new {klass_type}(')

        if ctor.is_cast:
            # We have to fiddle the type to generate the correct code.
            arg0 = ctor.py_signature.args[0]
            saved_definition = arg0.definition
            arg0.definition = klass
            cast_call = fmt_argument_as_cpp_type(spec, arg0)
            arg0.definition = saved_definition

            sf.write(f'a0->operator {cast_call}()')
        else:
            _call_args(sf, spec, ctor.cpp_signature, ctor.py_signature)

        sf.write(');\n')

        _catch(sf, spec, bindings, ctor.py_signature, ctor.throw_args,
                release_gil)

        if release_gil:
            sf.write('            Py_END_ALLOW_THREADS\n')

        # This is a bit of a hack to say we want the result transferred.  We
        # don't simply call sipTransferTo() because the wrapper object hasn't
        # been fully initialised yet.
        if ctor.transfer is Transfer.TRANSFER:
            sf.write('\n            *sipOwner = Py_None;\n')

    # Handle any /KeepReference/ arguments.
    for arg_nr, arg in enumerate(ctor.py_signature.args):
        if not arg.is_in:
            continue

        if arg.key is not None:
            arg_name = fmt_argument_as_name(spec, arg, arg_nr)
            suffix = 'Keep' if (arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) and len(arg.derefs) == 1) or not arg.get_wrapper else 'Wrapper'

            sf.write(f'\n            sipKeepReference((PyObject *)sipSelf, {arg.key}, {arg_name}{suffix});\n')

    _gc_ellipsis(sf, ctor.py_signature)
    _delete_temporaries(sf, spec, ctor.py_signature)

    sf.write('\n')

    if ctor.raises_py_exception:
        sf.write(
'''            if (PyErr_Occurred())
            {
                delete sipCpp;
                return SIP_NULLPTR;
            }

''')

    if error_flag:
        sf.write('            if (sipError == sipErrorNone)\n')

        if klass.has_shadow or ctor.posthook is not None:
            sf.write('            {\n')

        if klass.has_shadow:
            sf.write('                sipCpp->sipPySelf = sipSelf;\n\n')

        # Call any post-hook.
        if ctor.posthook is not None:
            sf.write(f'            sipCallHook("{ctor.posthook}");\n\n')

        sf.write('                return sipCpp;\n')

        if klass.has_shadow or ctor.posthook is not None:
            sf.write('            }\n')

        sf.write(
'''
            if (sipUnused)
            {
                Py_XDECREF(*sipUnused);
            }

            sipAddException(sipError, sipParseErr);

            if (sipError == sipErrorFail)
                return SIP_NULLPTR;
''')
    else:
        if old_error_flag:
            sf.write(
'''            if (sipIsErr)
            {
                if (sipUnused)
                {
                    Py_XDECREF(*sipUnused);
                }

                sipAddException(sipErrorFail, sipParseErr);
                return SIP_NULLPTR;
            }

''')

        if klass.has_shadow:
            sf.write('            sipCpp->sipPySelf = sipSelf;\n\n')

        # Call any post-hook.
        if ctor.posthook is not None:
            sf.write(f'            sipCallHook("{ctor.posthook}");\n\n')

        sf.write('            return sipCpp;\n')

    sf.write('        }\n')


def _skip_overload(overload, member, klass, scope, want_local=True):
    """ See if a member overload should be skipped. """

    # Skip if it's not the right name.
    if overload.common is not member:
        return True

    # Skip if it's a signal.
    if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL:
        return True

    # Skip if it's a private abstract.
    if overload.is_abstract and overload.access_specifier is AccessSpecifier.PRIVATE:
        return True

    # If we are disallowing them, skip if it's not in the current class unless
    # it is protected.
    if want_local and overload.access_specifier is not AccessSpecifier.PROTECTED and klass is not scope:
        return True

    return False


def _member_function(sf, spec, bindings, klass, member, original_klass):
    """ Generate a class member function. """

    # Check that there is at least one overload that needs to be handled.  See
    # if we can avoid naming the "self" argument (and suppress a compiler
    # warning).  See if we need to remember if "self" was explicitly passed as
    # an argument.  See if we need to handle keyword arguments.
    need_method = need_self = need_args = need_selfarg = need_orig_self = False

    for overload in original_klass.overloads:
        # Skip protected methods if we don't have the means to handle them.
        if overload.access_specifier is AccessSpecifier.PROTECTED and not klass.has_shadow:
            continue

        if not _skip_overload(overload, member, klass, original_klass):
            need_method = True

            if overload.access_specifier is not AccessSpecifier.PRIVATE:
                need_args = True

                if spec.abi_version >= (13, 0) or not overload.is_static:
                    need_self = True

                    if overload.is_abstract:
                        need_orig_self = True
                    elif overload.is_virtual or overload.is_virtual_reimplementation or _is_used_in_code(overload.method_code, 'sipSelfWasArg'):
                        need_selfarg = True

    # Handle the trivial case.
    if not need_method:
        return

    klass_name = klass.iface_file.fq_cpp_name.as_word
    member_py_name = member.py_name.name

    sf.write('\n\n')

    # Generate the docstrings.
    if _has_member_docstring(bindings, member, original_klass.overloads):
        sf.write(f'PyDoc_STRVAR(doc_{klass_name}_{member_py_name}, "')

        has_auto_docstring = _member_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:
        arg3_type = ', PyObject *'
        arg3_decl = ', PyObject *sipKwds'
    else:
        arg3_type = ''
        arg3_decl = ''

    sip_self = 'sipSelf' if need_self else ''
    sip_args = 'sipArgs' if need_args else ''

    if not spec.c_bindings:
        sf.write(f'extern "C" {{static PyObject *meth_{klass_name}_{member_py_name}(PyObject *, PyObject *{arg3_type});}}\n')

    sf.write(f'static PyObject *meth_{klass_name}_{member_py_name}(PyObject *{sip_self}, PyObject *{sip_args}{arg3_decl})\n{{\n')

    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:
        if need_args:
            sf.write('    PyObject *sipParseErr = SIP_NULLPTR;\n')

        if need_selfarg:
            # This determines if we call the explicitly scoped version or the
            # unscoped version (which will then go via the vtable).
            #
            # - If the call was unbound and self was passed as the first
            #   argument (ie. Foo.meth(self)) then we always want to call the
            #   explicitly scoped version.
            #
            # - If the call was bound then we only call the unscoped version in
            #   case there is a C++ sub-class reimplementation that Python
            #   knows nothing about.  Otherwise, if the call was invoked by
            #   super() within a Python reimplementation then the Python
            #   reimplementation would be called recursively.
            #
            # 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.abi_version >= (13, 0):
                sipself_test = f'!PyObject_TypeCheck(sipSelf, sipTypeAsPyTypeObject({_gto_name(klass)}))'
            else:
                sipself_test = '!sipSelf'

            sf.write(f'    bool sipSelfWasArg = ({sipself_test} || sipIsDerivedClass((sipSimpleWrapper *)sipSelf));\n')

        if need_orig_self:
            # This is similar to the above but for abstract methods.  We allow
            # the (potential) recursion because it means that the concrete
            # implementation can be put in a mixin and it will all work.
            sf.write('    PyObject *sipOrigSelf = sipSelf;\n')

    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):
            continue

        if overload.access_specifier is AccessSpecifier.PRIVATE:
            continue

        if member.no_arg_parser:
            sf.write_code(overload.method_code)
            break

        _function_body(sf, spec, bindings, klass, overload,
                original_klass=original_klass)

    if not member.no_arg_parser:
        sip_parse_err = 'sipParseErr' if need_args else 'SIP_NULLPTR'
        klass_py_name_ref = _cached_name_ref(klass.py_name)
        member_py_name_ref = _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 _function_body(sf, spec, bindings, scope, overload, original_klass=None,
        dereferenced=True):
    """ Generate the function calls for a particular overload. """

    if scope is None:
        original_scope = None
    elif isinstance(scope, WrappedClass):
        # If there was no original class (ie. where a virtual was first
        # defined) then use this class,
        if original_klass is None:
            original_klass = scope

        original_scope = original_klass
    else:
        original_scope = scope

    py_signature = overload.py_signature

    sf.write('\n    {\n')

    # In case we have to fiddle with it.
    py_signature_adjusted = False

    if is_number_slot(overload.common.py_slot):
        # Number slots must have two arguments because we parse them slightly
        # differently.
        if len(py_signature.args) == 1:
            py_signature.args.append(py_signature.args[0])

            # Insert self in the right place.
            py_signature.args[0] = Argument(ArgumentType.CLASS, is_in=True,
                    is_reference=True, definition=original_klass)

            py_signature_adjusted = True

        _arg_parser(sf, spec, scope, py_signature, overload=overload)
    elif not is_int_arg_slot(overload.common.py_slot) and not is_zero_arg_slot(overload.common.py_slot):
        _arg_parser(sf, spec, scope, py_signature, overload=overload)

    _function_call(sf, spec, bindings, scope, overload, dereferenced,
            original_scope)

    sf.write('    }\n')

    if py_signature_adjusted:
        del overload.py_signature.args[0]


def _handle_result(sf, spec, overload, is_new_instance, result_size_arg_nr,
        action):
    """ Generate the code to handle the result of a call to a member function.
    """

    result = overload.py_signature.result

    if result.type is ArgumentType.VOID and len(result.derefs) == 0:
        result = None

    # See if we are returning 0, 1 or more values.
    nr_return_values = 0

    if result is not None:
        only_out_arg_nr = -1
        nr_return_values += 1

    has_owner = False

    for arg_nr, arg in enumerate(overload.py_signature.args):
        if arg.is_out:
            only_out_arg_nr = arg_nr
            nr_return_values += 1

        if arg.transfer is Transfer.TRANSFER_THIS:
            has_owner = True

    # Handle the trivial case.
    if nr_return_values == 0:
        sf.write(
f'''            Py_INCREF(Py_None);
            {action} Py_None;
''')

        return

    # Handle results that are classes or mapped types separately.
    if result is not None and result.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        result_gto_name = _gto_name(result.definition)

        if overload.transfer is Transfer.TRANSFER_BACK:
            result_owner = 'Py_None'
        elif overload.transfer is Transfer.TRANSFER:
            result_owner = 'sipSelf'
        else:
            result_owner = 'SIP_NULLPTR'

        sip_res = _const_cast(spec, result, 'sipRes')

        if is_new_instance or overload.factory:
            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_gto_name}, {owner});\n')

            # Shortcut if this is the only value returned.
            if nr_return_values == 1:
                return
        else:
            need_xfer = overload.transfer is Transfer.TRANSFER and overload.is_static

            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_gto_name}, {owner});\n')

            # Transferring the result of a static overload needs an explicit
            # call to sipTransferTo().
            if need_xfer:
                sf.write('\n           sipTransferTo(sipResObj, Py_None);\n')

            # Shortcut if this is the only value returned.
            if nr_return_values == 1:
                if need_xfer:
                    sf.write('\n           return sipResObj;\n')

                return

    # If there are multiple values then build a tuple.
    if nr_return_values > 1:
        build_result_args = ['0']

        # Build the format string.
        format_s = ''

        if result is not None:
            format_s += 'R' if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) else _get_build_result_format(result)

        for arg in overload.py_signature.args:
            if arg.is_out:
                format_s += _get_build_result_format(arg)

        build_result_args.append('"(' + format_s + ')"')

        # Pass the values for conversion.
        if result is not None:
            build_result_args.append('sipResObj' if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) else 'sipRes')

            if result.type is ArgumentType.ENUM and result.definition.fq_cpp_name is not None:
                build_result_args.append(_gto_name(result.definition))

        for arg_nr, arg in enumerate(overload.py_signature.args):
            if arg.is_out:
                build_result_args.append(fmt_argument_as_name(spec, arg,
                        arg_nr))

                if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
                    build_result_args.append(_gto_name(arg.definition))

                    transfer = 'Py_None' if arg.transfer is Transfer.TRANSFER_BACK else 'SIP_NULLPTR'
                    build_result_args.append(transfer)
                elif arg.type is ArgumentType.ENUM and arg.definition.fq_cpp_name is not None:
                    build_result_args.append(_gto_name(arg.definition))

        build_result_args = ', '.join(build_result_args)

        sf.write(f'            {action} sipBuildResult({build_result_args});\n')

        # All done for multiple values.
        return

    # Deal with the only returned value.
    if only_out_arg_nr < 0:
        value = result
        value_name = 'sipRes'
    else:
        value = overload.py_signature.args[only_out_arg_nr]
        value_name = fmt_argument_as_name(spec, value, only_out_arg_nr)

    if value.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        need_new_instance = _need_new_instance(value)

        convertor = 'sipConvertFromNewType' if need_new_instance else 'sipConvertFromType'
        value_name = _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}, {_gto_name(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}, {_gto_name(value.definition)});\n')
        else:
            sf.write(f'            {action} PyLong_FromLong({value_name});\n')

    elif value.type is ArgumentType.ASCII_STRING:
        if len(value.derefs) == 0:
            sf.write(f'            {action} PyUnicode_DecodeASCII(&{value_name}, 1, SIP_NULLPTR);\n')
        else:
            sf.write(
f'''            if ({value_name} == SIP_NULLPTR)
            {{
                Py_INCREF(Py_None);
                return Py_None;
            }}

            {action} PyUnicode_DecodeASCII({value_name}, strlen({value_name}), SIP_NULLPTR);
''')

    elif value.type is ArgumentType.LATIN1_STRING:
        if len(value.derefs) == 0:
            sf.write(f'            {action} PyUnicode_DecodeLatin1(&{value_name}, 1, SIP_NULLPTR);\n')
        else:
            sf.write(
f'''            if ({value_name} == SIP_NULLPTR)
            {{
                Py_INCREF(Py_None);
                return Py_None;
            }}

            {action} PyUnicode_DecodeLatin1({value_name}, strlen({value_name}), SIP_NULLPTR);
''')

    elif value.type is ArgumentType.UTF8_STRING:
        if len(value.derefs) == 0:
            sf.write(f'            {action} PyUnicode_FromStringAndSize(&{value_name}, 1);\n')
        else:
            sf.write(
f'''            if ({value_name} == SIP_NULLPTR)
            {{
                Py_INCREF(Py_None);
                return Py_None;
            }}

            {action} PyUnicode_FromString({value_name});
''')

    elif value.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING):
        cast = '' if value.type is ArgumentType.STRING else '(char *)'

        if len(value.derefs) == 0:
            sf.write(f'            {action} PyBytes_FromStringAndSize({cast}&{value_name}, 1);\n')
        else:
            sf.write(
f'''            if ({value_name} == SIP_NULLPTR)
            {{
                Py_INCREF(Py_None);
                return Py_None;
            }}

            {action} PyBytes_FromString({cast}{value_name});
''')

    elif value.type is ArgumentType.WSTRING:
        if len(value.derefs) == 0:
            sf.write(f'            {action} PyUnicode_FromWideChar(&{value_name}, 1);\n')
        else:
            sf.write(
f'''            if ({value_name} == SIP_NULLPTR)
            {{
                Py_INCREF(Py_None);
                return Py_None;
            }}

            {action} PyUnicode_FromWideChar({value_name}, (Py_ssize_t)wcslen({value_name}));
''')

    elif value.type in (ArgumentType.BOOL, ArgumentType.CBOOL):
        sf.write(f'            {action} PyBool_FromLong({value_name});\n')

    elif value.type in (ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.SHORT, ArgumentType.INT, ArgumentType.CINT, ArgumentType.LONG):
        sf.write(f'            {action} PyLong_FromLong({value_name});\n')

    elif value.type in (ArgumentType.UBYTE, ArgumentType.USHORT, ArgumentType.UINT, ArgumentType.ULONG, ArgumentType.SIZE):
        sf.write(f'            {action} PyLong_FromUnsignedLong({value_name});\n')

    elif value.type is ArgumentType.LONGLONG:
        sf.write(f'            {action} PyLong_FromLongLong({value_name});\n')

    elif value.type is ArgumentType.ULONGLONG:
        sf.write(f'            {action} PyLong_FromUnsignedLongLong({value_name});\n')

    elif value.type is ArgumentType.SSIZE:
        sf.write(f'            {action} PyLong_FromSsize_t({value_name});\n')

    elif value.type is ArgumentType.VOID:
        convertor = 'sipConvertFromConstVoidPtr' if value.is_const else 'sipConvertFromVoidPtr'
        if result_size_arg_nr >= 0:
            convertor += 'AndSize'

        sf.write(f'            {action} {convertor}({_get_void_ptr_cast(value)}{value_name}')

        if result_size_arg_nr >= 0:
            sf.write(', ' + fmt_argument_as_name(spec, overload.py_signature.args[result_size_arg_nr], result_size_arg_nr))

        sf.write(');\n')

    elif value.type is ArgumentType.CAPSULE:
        sf.write(f'            {action} PyCapsule_New({value_name}, "{value.definition.as_cpp}", SIP_NULLPTR);\n')

    elif value.type in (ArgumentType.STRUCT, ArgumentType.UNION):
        convertor = 'sipConvertFromConstVoidPtr' if value.is_const else 'sipConvertFromVoidPtr'

        sf.write(f'            {action} {convertor}({value_name});\n')

    elif value.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
        sf.write(f'            {action} PyFloat_FromDouble((double){value_name});\n')

    elif value.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
        sf.write(f'            {action} PyFloat_FromDouble({value_name});\n')

    elif value.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM):
        sf.write(f'            {action} {value_name};\n')


def _get_build_result_format(type):
    """ Return the format string used by sipBuildResult() for a particular
    type.
    """

    if type.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        return 'N' if _need_new_instance(type) else 'D'

    if type.type is ArgumentType.FAKE_VOID:
        return 'D'

    if type.type in (ArgumentType.BOOL, ArgumentType.CBOOL):
        return 'b'

    if type.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING):
        return 'A' if _is_string(type) else 'a'

    if type.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING):
        return 's' if _is_string(type) else 'c'

    if type.type is ArgumentType.WSTRING:
        return 'x' if _is_string(type) else 'w'

    if type.type is ArgumentType.ENUM:
        return 'F' if type.definition.fq_cpp_name is not None else 'e'

    if type.type in (ArgumentType.BYTE, ArgumentType.SBYTE):
        # Note that this is the correct thing to do even if char is unsigned.
        return 'L'

    if type.type is ArgumentType.UBYTE:
        return 'M'

    if type.type is ArgumentType.SHORT:
        return 'h'

    if type.type is ArgumentType.USHORT:
        return 't'

    if type.type in (ArgumentType.INT, ArgumentType.CINT):
        return 'i'

    if type.type is ArgumentType.UINT:
        return 'u'

    if type.type is ArgumentType.SIZE:
        return '='

    if type.type is ArgumentType.LONG:
        return 'l'

    if type.type is ArgumentType.ULONG:
        return 'm'

    if type.type is ArgumentType.LONGLONG:
        return 'n'

    if type.type is ArgumentType.ULONGLONG:
        return 'o'

    if type.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
        return 'V'

    if type.type is ArgumentType.CAPSULE:
        return 'z'

    if type.type in (ArgumentType.FLOAT, ArgumentType.CFLOAT):
        return 'f'

    if type.type in (ArgumentType.DOUBLE, ArgumentType.CDOUBLE):
        return 'd'

    if type.type in (ArgumentType.PYOBJECT, ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYCALLABLE, ArgumentType.PYSLICE, ArgumentType.PYTYPE, ArgumentType.PYBUFFER, ArgumentType.PYENUM):
        return 'R'

    # We should never get here.
    return ''


def _is_string(type):
    """ Check if a type is a string rather than a char type. """

    nr_derefs = len(type.derefs)

    if type.is_out and not type.is_reference:
        nr_derefs -= 1

    return nr_derefs > 0


def _needs_heap_copy(arg, using_copy_ctor=True):
    """ Return True if an argument (or result) needs to be copied to the heap.
    """

    # The type is a class or mapped type and not a pointer.
    if not arg.no_copy and arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(arg.derefs) == 0:
        # We need a copy unless it is a non-const reference.
        if not arg.is_reference or arg.is_const:
            # We assume we can copy a mapped type.
            if arg.type is ArgumentType.MAPPED:
                return True

            klass = arg.definition

            # We can't copy an abstract class.
            if klass.is_abstract:
                return False

            # We can copy if we have a public copy ctor.
            if not klass.cannot_copy:
                return True

            # We can't copy if we must use a copy ctor.
            if using_copy_ctor:
                return False

            # We can copy if we have a public assignment operator.
            return not klass.cannot_assign

    return False


def _function_call(sf, spec, bindings, scope, overload, dereferenced,
        original_scope):
    """ Generate a function call. """

    py_slot = overload.common.py_slot
    result = overload.py_signature.result
    result_cpp_type = fmt_argument_as_cpp_type(spec, result, plain=True,
            no_derefs=True)
    static_factory = (scope is None or overload.is_static) and overload.factory

    sf.write('        {\n')

    # If there is no shadow class then protected methods can never be called.
    if overload.access_specifier is AccessSpecifier.PROTECTED and not scope.has_shadow:
        sf.write(
'''            /* Never reached. */
        }
''')

        return

    # Save the full result type as we may want to fiddle with it.
    saved_result_is_const = result.is_const

    # See if we need to make a copy of the result on the heap.
    is_new_instance = _needs_heap_copy(result, using_copy_ctor=False)

    if is_new_instance:
        result.is_const = False

    result_decl = _get_result_decl(spec, scope, overload, result)
    if result_decl is not None:
        sf.write('            ' + result_decl + ';\n')
        separating_newline = True
    else:
        separating_newline = False

    # See if we want to keep a reference to the result.
    post_process = result.key is not None

    delete_temporaries = True
    result_size_arg_nr = -1

    for arg_nr, arg in enumerate(overload.py_signature.args):
        if arg.result_size:
            result_size_arg_nr = arg_nr

        if static_factory and arg.key is not None:
            post_process = True

        # If we have an In,Out argument that has conversion code then we delay
        # the destruction of any temporary variables until after we have
        # converted the outputs.
        if arg.is_in and arg.is_out and _get_convert_to_type_code(arg) is not None:
            delete_temporaries = False
            post_process = True

        # If we are returning a class via an output only reference or pointer
        # then we need an instance on the heap.
        if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and _need_new_instance(arg):
            arg_name = fmt_argument_as_name(spec, arg, arg_nr)
            arg_cpp_type = fmt_argument_as_cpp_type(spec, arg, plain=True,
                    no_derefs=True)
            sf.write(f'            {arg_name} = new {arg_cpp_type}();\n')
            separating_newline = True

    if post_process:
        sf.write('            PyObject *sipResObj;\n')
        separating_newline = True

    if overload.premethod_code is not None:
        sf.write('\n')
        sf.write_code(overload.premethod_code)

    error_flag = old_error_flag = False

    if overload.method_code is not None:
        # See if the handwritten code seems to be using the error flag.
        if _need_error_flag(overload.method_code):
            sf.write('            sipErrorState sipError = sipErrorNone;\n')
            error_flag = True
            separating_newline = True
        elif _need_old_error_flag(overload.method_code):
            sf.write('            int sipIsErr = 0;\n')
            old_error_flag = True
            separating_newline = True

    if separating_newline:
        sf.write('\n')

    # If it is abstract make sure that self was bound.
    if overload.is_abstract:
        sf.write(
f'''            if (!sipOrigSelf)
            {{
                sipAbstractMethod({_cached_name_ref(scope.py_name)}, {_cached_name_ref(overload.common.py_name)});
                return SIP_NULLPTR;
            }}

''')

    if overload.deprecated:
        scope_py_name_ref = _cached_name_ref(scope.py_name) if scope is not None and scope.py_name is not None else 'SIP_NULLPTR'
        error_return = '-1' if is_void_return_slot(py_slot) or is_int_return_slot(py_slot) or is_ssize_return_slot(py_slot) or is_hash_return_slot(py_slot) else 'SIP_NULLPTR'

        # Note that any temporaries will leak if an exception is raised.
        sf.write(
f'''            if (sipDeprecated({scope_py_name_ref}, {_cached_name_ref(overload.common.py_name)}) < 0)
                return {error_return};

''')

    # Call any pre-hook.
    if overload.prehook is not None:
        sf.write(f'            sipCallHook("{overload.prehook}");\n\n')

    if overload.method_code is not None:
        sf.write_code(overload.method_code)
    else:
        release_gil = _release_gil(overload.gil_action, bindings)
        needs_closing_paren = False

        if is_new_instance and spec.c_bindings:
            sf.write(
f'''            if ((sipRes = ({result_cpp_type} *)sipMalloc(sizeof ({result_cpp_type}))) == SIP_NULLPTR)
        {{
''')

            _gc_ellipsis(sf, overload.py_signature)

            sf.write(
'''                return SIP_NULLPTR;
            }

''')

        if overload.raises_py_exception:
            sf.write('            PyErr_Clear();\n\n')

        if isinstance(scope, WrappedClass) and scope.len_cpp_name is not None:
            _sequence_support(sf, spec, scope, overload)

        if release_gil:
            sf.write('            Py_BEGIN_ALLOW_THREADS\n')

        _try(sf, bindings, overload.throw_args)

        sf.write('            ')

        if result_decl is not None:
            # Construct a copy on the heap if needed.
            if is_new_instance:
                if spec.c_bindings:
                    sf.write('*sipRes = ')
                elif result.type is ArgumentType.CLASS and result.definition.cannot_copy:
                    sf.write(f'sipRes = reinterpret_cast<{result_cpp_type} *>(::operator new(sizeof ({result_cpp_type})));\n            *sipRes = ')
                else:
                    sf.write(f'sipRes = new {result_cpp_type}(')
                    needs_closing_paren = True
            else:
                sf.write('sipRes = ')

                # See if we need the address of the result.  Any reference will
                # be non-const.
                if result.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and (len(result.derefs) == 0 or result.is_reference):
                    sf.write('&')

        if py_slot is None:
            _cpp_function_call(sf, spec, scope, overload, original_scope)
        elif py_slot is PySlot.CALL:
            sf.write('(*sipCpp)(')
            _call_args(sf, spec, overload.cpp_signature, overload.py_signature)
            sf.write(')')
        else:
            sf.write(_get_slot_call(spec, scope, overload, dereferenced))

        if needs_closing_paren:
            sf.write(')')

        sf.write(';\n')

        _catch(sf, spec, bindings, overload.py_signature, overload.throw_args,
                release_gil)

        if release_gil:
            sf.write('            Py_END_ALLOW_THREADS\n')

    for arg_nr, arg in enumerate(overload.py_signature.args):
        if not arg.is_in:
            continue

        # Handle any /KeepReference/ arguments except for static factories.
        if not static_factory and arg.key is not None:
            sip_self = 'SIP_NULLPTR' if scope is None or overload.is_static else 'sipSelf'
            keep_reference_call = _get_keep_reference_call(spec, arg, arg_nr,
                    sip_self)

            sf.write(f'\n            {keep_reference_call};\n')

        # Handle /TransferThis/ for non-factory methods.
        if not overload.factory and arg.transfer is Transfer.TRANSFER_THIS:
            sf.write(
'''
            if (sipOwner)
                sipTransferTo(sipSelf, (PyObject *)sipOwner);
            else
                sipTransferBack(sipSelf);
''')

    if overload.transfer is Transfer.TRANSFER_THIS:
        sf.write('\n            sipTransferTo(sipSelf, SIP_NULLPTR);\n')

    _gc_ellipsis(sf, overload.py_signature)

    if delete_temporaries and not is_zero_arg_slot(py_slot):
        _delete_temporaries(sf, spec, overload.py_signature)

    sf.write('\n')

    # Handle the error flag if it was used.
    error_value = '-1' if is_void_return_slot(py_slot) or is_int_return_slot(py_slot) or is_ssize_return_slot(py_slot) or is_hash_return_slot(py_slot) else '0'

    if overload.raises_py_exception:
        sf.write(
f'''            if (PyErr_Occurred())
                return {error_value};

''')
    elif error_flag:
        if not is_zero_arg_slot(py_slot):
            sf.write(
f'''            if (sipError == sipErrorFail)
                return {error_value};

''')

        sf.write(
'''            if (sipError == sipErrorNone)
            {
''')
    elif old_error_flag:
        sf.write(
f'''            if (sipIsErr)
                return {error_value};

''')

    # Call any post-hook.
    if overload.posthook is not None:
        sf.write(f'\n            sipCallHook("{overload.posthook}");\n')

    if is_void_return_slot(py_slot):
        sf.write(
'''            return 0;
''')
    elif is_inplace_number_slot(py_slot) or is_inplace_sequence_slot(py_slot):
        sf.write(
'''            Py_INCREF(sipSelf);
            return sipSelf;
''')
    elif is_int_return_slot(py_slot) or is_ssize_return_slot(py_slot) or is_hash_return_slot(py_slot):
        sf.write(
'''            return sipRes;
''')
    else:
        action = 'sipResObj =' if post_process else 'return'
        _handle_result(sf, spec, overload, is_new_instance, result_size_arg_nr,
                action)

        # Delete the temporaries now if we haven't already done so.
        if not delete_temporaries:
            _delete_temporaries(sf, spec, overload.py_signature)

        # Keep a reference to a pointer to a class if it isn't owned by Python.
        if result.key is not None:
            sip_self = 'SIP_NULLPTR' if overload.is_static else 'sipSelf'
            sf.write(f'\n            sipKeepReference({sip_self}, {result.key}, sipResObj);\n')

        # Keep a reference to any argument with the result if the function is a
        # static factory.
        if static_factory:
            for arg_nr, arg in enumerate(overload.py_signature.args):
                if not arg.is_in:
                    continue

                if arg.key != None:
                    keep_reference_call = _get_keep_reference_call(spec, arg,
                            arg_nr, 'sipResObj')
                    sf.write(f'\n            {keep_reference_call};\n')

        if post_process:
            sf.write('\n            return sipResObj;\n')

    if error_flag:
        sf.write('            }\n')

        if not is_zero_arg_slot(py_slot):
            sf.write('\n            sipAddException(sipError, &sipParseErr);\n')

    sf.write('        }\n')

    # Restore the full state of the result.
    result.is_const = saved_result_is_const


def _get_keep_reference_call(spec, arg, arg_nr, object_name):
    """ Return a call to sipKeepReference() for an argument. """

    arg_name = fmt_argument_as_name(spec, arg, arg_nr)
    suffix = 'Wrapper' if arg.get_wrapper and (arg.type not in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) or len(arg.derefs) != 1) else 'Keep'

    return f'sipKeepReference({object_name}, {arg.key}, {arg_name}{suffix})'


def _get_result_decl(spec, scope, overload, result):
    """ Return the declaration of a variable to hold the result of a function
    call if one is needed.
    """

    # See if sipRes is needed.
    no_result = (is_inplace_number_slot(overload.common.py_slot) or
             is_inplace_sequence_slot(overload.common.py_slot) or
             (result.type is ArgumentType.VOID and len(result.derefs) == 0))

    if no_result:
        return None

    result_decl = _get_named_value_decl(spec, scope, result, 'sipRes')

    # The typical %MethodCode usually causes a compiler warning, so we
    # initialise the result in that case to try and suppress it.
    initial_value = ' = ' + _cast_zero(spec, result) if overload.method_code is not None else ''

    return result_decl + initial_value


def _get_slot_call(spec, scope, overload, dereferenced):
    """ Return the call to a Python slot (except for PySlot.CALL which is
    handled separately).
    """

    py_slot = overload.common.py_slot

    if py_slot is PySlot.GETITEM:
        return f'(*sipCpp)[{_get_slot_arg(spec, overload, 0)}]'

    if py_slot in (PySlot.INT, PySlot.FLOAT):
        return '*sipCpp'

    if py_slot is PySlot.ADD:
        return _get_number_slot_call(spec, overload, '+')

    if py_slot is PySlot.CONCAT:
        return _get_binary_slot_call(spec, scope, overload, '+', dereferenced)

    if py_slot is PySlot.SUB:
        return _get_number_slot_call(spec, overload, '-')

    if py_slot in (PySlot.MUL, PySlot.MATMUL):
        return _get_number_slot_call(spec, overload, '*')

    if py_slot is PySlot.REPEAT:
        return _get_binary_slot_call(spec, scope, overload, '*', dereferenced)

    if py_slot is PySlot.TRUEDIV:
        return _get_number_slot_call(spec, overload, '/')

    if py_slot is PySlot.MOD:
        return _get_number_slot_call(spec, overload, '%')

    if py_slot is PySlot.AND:
        return _get_number_slot_call(spec, overload, '&')

    if py_slot is PySlot.OR:
        return _get_number_slot_call(spec, overload, '|')

    if py_slot is PySlot.XOR:
        return _get_number_slot_call(spec, overload, '^')

    if py_slot is PySlot.LSHIFT:
        return _get_number_slot_call(spec, overload, '<<')

    if py_slot is PySlot.RSHIFT:
        return _get_number_slot_call(spec, overload, '>>')

    if py_slot in (PySlot.IADD, PySlot.ICONCAT):
        return _get_binary_slot_call(spec, scope, overload, '+=', dereferenced)

    if py_slot is PySlot.ISUB:
        return _get_binary_slot_call(spec, scope, overload, '-=', dereferenced)

    if py_slot in (PySlot.IMUL, PySlot.IREPEAT, PySlot.IMATMUL):
        return _get_binary_slot_call(spec, scope, overload, '*=', dereferenced)

    if py_slot is PySlot.ITRUEDIV:
        return _get_binary_slot_call(spec, scope, overload, '/=', dereferenced)

    if py_slot is PySlot.IMOD:
        return _get_binary_slot_call(spec, scope, overload, '%=', dereferenced)

    if py_slot is PySlot.IAND:
        return _get_binary_slot_call(spec, scope, overload, '&=', dereferenced)

    if py_slot is PySlot.IOR:
        return _get_binary_slot_call(spec, scope, overload, '|=', dereferenced)

    if py_slot is PySlot.IXOR:
        return _get_binary_slot_call(spec, scope, overload, '^=', dereferenced)

    if py_slot is PySlot.ILSHIFT:
        return _get_binary_slot_call(spec, scope, overload, '<<=', dereferenced)

    if py_slot is PySlot.IRSHIFT:
        return _get_binary_slot_call(spec, scope, overload, '>>=', dereferenced)

    if py_slot is PySlot.INVERT:
        return '~(*sipCpp)'

    if py_slot is PySlot.LT:
        return _get_binary_slot_call(spec, scope, overload, '<', dereferenced)

    if py_slot is PySlot.LE:
        return _get_binary_slot_call(spec, scope, overload, '<=', dereferenced)

    if py_slot is PySlot.EQ:
        return _get_binary_slot_call(spec, scope, overload, '==', dereferenced)

    if py_slot is PySlot.NE:
        return _get_binary_slot_call(spec, scope, overload, '!=', dereferenced)

    if py_slot is PySlot.GT:
        return _get_binary_slot_call(spec, scope, overload, '>', dereferenced)

    if py_slot is PySlot.GE:
        return _get_binary_slot_call(spec, scope, overload, '>=', dereferenced)

    if py_slot is PySlot.NEG:
        return '-(*sipCpp)'

    if py_slot is PySlot.POS:
        return '+(*sipCpp)'

    # We should never get here.
    return ''


def _cpp_function_call(sf, spec, scope, overload, original_scope):
    """ Generate a call to a C++ function. """

    cpp_name = overload.cpp_name

    # If the function is protected then call the public wrapper.  If it is
    # virtual then call the explicit scoped function if "self" was passed as
    # the first argument.

    nr_parens = 1

    if scope is None:
        sf.write(cpp_name + '(')
    elif scope.iface_file.type is IfaceFileType.NAMESPACE:
        sf.write(f'{scope.iface_file.fq_cpp_name.as_cpp}::{cpp_name}(')
    elif overload.is_static:
        if overload.access_specifier is AccessSpecifier.PROTECTED:
            sf.write(f'sip{scope.iface_file.fq_cpp_name.as_word}::sipProtect_{cpp_name}(')
        else:
            sf.write(f'{original_scope.iface_file.fq_cpp_name.as_cpp}::{cpp_name}(')
    elif overload.access_specifier is AccessSpecifier.PROTECTED:
        if not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation):
            sf.write(f'sipCpp->sipProtectVirt_{cpp_name}(sipSelfWasArg')

            if len(overload.cpp_signature.args) != 0:
                sf.write(', ')
        else:
            sf.write(f'sipCpp->sipProtect_{cpp_name}(')
    elif not overload.is_abstract and (overload.is_virtual or overload.is_virtual_reimplementation):
        sf.write(f'(sipSelfWasArg ? sipCpp->{original_scope.iface_file.fq_cpp_name.as_cpp}::{cpp_name}(')
        _call_args(sf, spec, overload.cpp_signature, overload.py_signature)
        sf.write(f') : sipCpp->{cpp_name}(')
        nr_parens += 1
    else:
        sf.write(f'sipCpp->{cpp_name}(')

    _call_args(sf, spec, overload.cpp_signature, overload.py_signature)

    sf.write(')' * nr_parens)


def _get_slot_arg(spec, overload, arg_nr):
    """ Return an argument to a slot call. """

    arg = overload.py_signature.args[arg_nr]

    dereference = '*' if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and len(arg.derefs) == 0 else ''

    return dereference + fmt_argument_as_name(spec, arg, arg_nr)


# A map of operators and their complements.
_OPERATOR_COMPLEMENTS = {
    '<': '>=',
    '<=': '>',
    '==': '!=',
    '!=': '==',
    '>': '<=',
    '>=': '<',
}

def _get_binary_slot_call(spec, scope, overload, operator, dereferenced):
    """ Return the call to a binary (non-number) slot method. """

    slot_call = ''

    if overload.is_complementary:
        operator = _OPERATOR_COMPLEMENTS[operator]
        slot_call += '!'

    if overload.is_global:
        # If it has been moved from a namespace then get the C++ scope.
        if overload.common.namespace_iface_file is not None:
            slot_call += overload.common.namespace_iface_file.fq_cpp_name.as_cpp + '::'

        if dereferenced:
            slot_call += f'operator{operator}((*sipCpp), '
        else:
            slot_call += f'operator{operator}(sipCpp, '
    else:
        dereference = '->' if dereferenced else '.'

        if overload.is_abstract:
            slot_call += f'sipCpp{dereference}operator{operator}('
        else:
            slot_call += f'sipCpp{dereference}{scope.iface_file.fq_cpp_name.as_cpp}::operator{operator}('

    slot_call += _get_slot_arg(spec, overload, 0)
    slot_call += ')'

    return slot_call


def _get_number_slot_call(spec, overload, operator):
    """ Return the call to a binary number slot method. """

    arg0 = _get_slot_arg(spec, overload, 0)
    arg1 = _get_slot_arg(spec, overload, 1)

    return f'({arg0} {operator} {arg1})'


def _arg_parser(sf, spec, scope, py_signature, ctor=None, overload=None):
    """ Generate the argument variables for a member
    function/constructor/operator.
    """

    # If the scope is just a namespace, then ignore it.
    if 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.
    if spec.abi_version >= (13, 0):
        handle_self = (scope is not None and overload is not None and overload.common.py_slot is None)
    else:
        handle_self = (scope is not None and overload is not None and overload.common.py_slot is None and not overload.is_static)

    # Generate the local variables that will hold the parsed arguments and
    # values returned via arguments.
    array_len_arg_nr = -1
    need_owner = False
    ctor_needs_self = False

    for arg_nr, arg in enumerate(py_signature.args):
        if arg.array is ArrayArgument.ARRAY_SIZE:
            array_len_arg_nr = arg_nr

        _argument_variable(sf, spec, scope, arg, arg_nr)

        if arg.transfer is Transfer.TRANSFER_THIS:
            need_owner = True

        if ctor is not None and arg.transfer is Transfer.TRANSFER:
            ctor_needs_self = True

    if overload is not None and need_owner:
        sf.write('        sipWrapper *sipOwner = SIP_NULLPTR;\n')

    if handle_self and not overload.is_static:
        cpp_type = 'const ' if overload.is_const else ''

        if overload.access_specifier is AccessSpecifier.PROTECTED and scope.has_shadow:
            cpp_type += 'sip' + scope.iface_file.fq_cpp_name.as_word
        else:
            cpp_type += _scoped_class_name(spec, scope)

        sf.write(f'        {cpp_type} *sipCpp;\n\n')
    elif len(py_signature.args) != 0:
        sf.write('\n')

    # Generate the call to the parser function.
    args = []
    single_arg = False

    if overload is not None and is_number_slot(overload.common.py_slot):
        parser_function = 'sipParsePair'
        args.append('&sipParseErr')
        args.append('sipArg0')
        args.append('sipArg1')

    elif overload is not None and overload.common.py_slot is PySlot.SETATTR:
        # We don't even try to invoke the parser if there is a value and there
        # shouldn't be (or vice versa) so that the list of errors doesn't get
        # polluted with signatures that can never apply.
        if overload.is_delattr:
            operator = '=='
            sip_value = 'SIP_NULLPTR'
        else:
            operator = '!='
            sip_value = 'sipValue'

        parser_function = f'sipValue {operator} SIP_NULLPTR && sipParsePair'
        args.append('&sipParseErr')
        args.append('sipName')
        args.append(sip_value)

    elif (overload is not None and overload.common.allow_keyword_args) or ctor is not None:
        # We handle keywords if we might have been passed some (because one of
        # the overloads uses them or we are a ctor).  However this particular
        # overload might not have any.
        if overload is not None:
            kw_args = overload.kw_args
        elif ctor is not None:
            kw_args = ctor.kw_args
        else:
            kw_args = KwArgs.NONE

        # The above test isn't good enough because when the flags were set in
        # the parser we couldn't know for sure if an argument was an output
        # pointer.  Therefore we check here.  The drawback is that we may
        # generate the name string for the argument but never use it, or we
        # might have an empty keyword name array or one that contains only
        # NULLs.
        is_ka_list = False

        if kw_args is not KwArgs.NONE:
            for arg in py_signature.args:
                if not arg.is_in:
                    continue

                if not is_ka_list:
                    sf.write('        static const char *sipKwdList[] = {\n')
                    is_ka_list = True

                if arg.name is not None and (kw_args is KwArgs.ALL or arg.default_value is not None):
                    arg_name_ref = _cached_name_ref(arg.name)
                else:
                    arg_name_ref = 'SIP_NULLPTR'

                sf.write(f'            {arg_name_ref},\n')

            if is_ka_list:
                sf.write('        };\n\n')

        parser_function = 'sipParseKwdArgs'
        args.append('sipParseErr' if ctor is not None else '&sipParseErr')
        args.append('sipArgs')
        args.append('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))
        plural = '' if single_arg else 's'

        parser_function = 'sipParseArgs'
        args.append('&sipParseErr')
        args.append('sipArg' + plural)

    # Generate the format string.
    format_s = '"'
    optional_args = False

    if single_arg:
        format_s += '1'

    if ctor_needs_self:
        format_s += '#'
    elif handle_self:
        if overload.is_static:
            format_s += 'C'
        elif overload.access_is_really_protected:
            format_s += 'p'
        else:
            format_s += 'B'

    for arg in py_signature.args:
        if not arg.is_in:
            continue

        if arg.default_value is not None and not optional_args:
            format_s += '|'
            optional_args = True

        # Get the wrapper if explicitly asked for or we are going to keep a
        # reference to.  However if it is an encoded string then we will get
        # the actual wrapper from the format character.
        if arg.get_wrapper:
            format_s += '@'
        elif arg.key is not None:
            if not (arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) and len(arg.derefs) == 1):
                format_s += '@'

        if arg.type is ArgumentType.ASCII_STRING:
            format_s += 'AA' if _is_string(arg) else 'aA'

        elif arg.type is ArgumentType.LATIN1_STRING:
            format_s += 'AL' if _is_string(arg) else 'aL'

        elif arg.type is ArgumentType.UTF8_STRING:
            format_s += 'A8' if _is_string(arg) else 'a8'

        elif arg.type in (ArgumentType.SSTRING, ArgumentType.USTRING, ArgumentType.STRING):
            if arg.array is ArrayArgument.ARRAY:
                format_s += 'k'
            elif _is_string(arg):
                format_s += 's'
            else:
                format_s += 'c'

        elif arg.type is ArgumentType.WSTRING:
            if arg.array is ArrayArgument.ARRAY:
                format_s += 'K'
            elif _is_string(arg):
                format_s += 'x'
            else:
                format_s += 'w'

        elif arg.type is ArgumentType.ENUM:
            if arg.definition.fq_cpp_name is None:
                format_s += 'e'
            elif arg.is_constrained:
                format_s += 'XE'
            else:
                format_s += 'E'

        elif arg.type is ArgumentType.BOOL:
            format_s += 'b'

        elif arg.type is ArgumentType.CBOOL:
            format_s += 'Xb'

        elif arg.type is ArgumentType.INT:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'i'

        elif arg.type is ArgumentType.UINT:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'u'

        elif arg.type is ArgumentType.SIZE:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += '='

        elif arg.type is ArgumentType.CINT:
            format_s += 'Xi'

        elif arg.type is ArgumentType.BYTE:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'I' if _abi_has_working_char_conversion(spec) else 'L'

        elif arg.type is ArgumentType.SBYTE:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'L'

        elif arg.type is ArgumentType.UBYTE:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'M'

        elif arg.type is ArgumentType.SHORT:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'h'

        elif arg.type is ArgumentType.USHORT:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 't'

        elif arg.type is ArgumentType.LONG:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'l'

        elif arg.type is ArgumentType.ULONG:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'm'

        elif arg.type is ArgumentType.LONGLONG:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'n'

        elif arg.type is ArgumentType.ULONGLONG:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                format_s += 'o'

        elif arg.type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
            format_s += 'v'

        elif arg.type is ArgumentType.CAPSULE:
            format_s += 'z'

        elif arg.type is ArgumentType.FLOAT:
            format_s += 'f'

        elif arg.type is ArgumentType.CFLOAT:
            format_s += 'Xf'

        elif arg.type is ArgumentType.DOUBLE:
            format_s += 'd'

        elif arg.type is ArgumentType.CDOUBLE:
            format_s += 'Xd'

        elif arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
            if arg.array is ArrayArgument.ARRAY:
                format_s += '>' if arg.type is ArgumentType.CLASS and _abi_supports_array(spec) else 'r'
            else:
                format_s += 'J' + _get_subformat_char(arg)

        elif arg.type is ArgumentType.PYOBJECT:
            format_s += 'P' + _get_subformat_char(arg)

        elif arg.type in (ArgumentType.PYTUPLE, ArgumentType.PYLIST, ArgumentType.PYDICT, ArgumentType.PYSLICE, ArgumentType.PYTYPE):
            format_s += 'N' if arg.allow_none else 'T'

        elif arg.type is ArgumentType.PYCALLABLE:
            format_s += 'H' if arg.allow_none else 'F'

        elif arg.type is ArgumentType.PYBUFFER:
            format_s += '$' if arg.allow_none else '!'

        elif arg.type is ArgumentType.PYENUM:
            format_s += '^' if arg.allow_none else '&'

        elif arg.type is ArgumentType.ELLIPSIS:
            format_s += 'W'

    format_s += '"'
    args.append(format_s)

    # Generate the parameters corresponding to the format string.
    if ctor_needs_self:
        args.append('sipSelf')
    elif handle_self:
        args.append('&sipSelf')

        if not overload.is_static:
            args.append(_gto_name(scope))
            args.append('&sipCpp')

    for arg_nr, arg in enumerate(py_signature.args):
        if not arg.is_in:
            continue

        arg_name = fmt_argument_as_name(spec, arg, arg_nr)
        arg_name_ref = '&' + arg_name

        # Use the wrapper name if it was explicitly asked for.
        if arg.get_wrapper:
            args.append(f'&{arg_name}Wrapper')
        elif arg.key is not None:
            args.append(f'&{arg_name}Keep')

        if arg.type is ArgumentType.MAPPED:
            mapped_type = arg.definition

            args.append(_gto_name(mapped_type))
            args.append(arg_name_ref)

            if arg.array is ArrayArgument.ARRAY:
                array_len_arg_name = fmt_argument_as_name(spec,
                        py_signature.args[array_len_arg_nr], array_len_arg_nr)
                args.append('&' + array_len_arg_name)
            elif mapped_type.convert_to_type_code is not None and not arg.is_constrained:
                args.append('SIP_NULLPTR' if mapped_type.no_release else f'&{arg_name}State')

                if mapped_type.needs_user_state:
                    args.append(f'&{arg_name}UserState')

        elif arg.type is ArgumentType.CLASS:
            klass = arg.definition

            args.append(_gto_name(klass))
            args.append(arg_name_ref)

            if arg.array is ArrayArgument.ARRAY:
                array_len_arg_name = fmt_argument_as_name(spec,
                        py_signature.args[array_len_arg_nr], array_len_arg_nr)
                args.append('&' + array_len_arg_name)

                if _abi_supports_array(spec):
                    args.append(f'&{arg_name}IsTemp')
            else:
                if arg.transfer is Transfer.TRANSFER_THIS:
                    args.append('sipOwner' if ctor is not None else '&sipOwner')

                if klass.convert_to_type_code is not None and not arg.is_constrained:
                    args.append(f'&{arg_name}State')

        elif arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING):
            if arg.key is None and len(arg.derefs) == 1:
                args.append(f'&{arg_name}Keep')

            args.append(arg_name_ref)

        elif arg.type is ArgumentType.PYTUPLE:
            args.append('&PyTuple_Type')
            args.append(arg_name_ref)

        elif arg.type is ArgumentType.PYLIST:
            args.append('&PyList_Type')
            args.append(arg_name_ref)

        elif arg.type is ArgumentType.PYDICT:
            args.append('&PyDict_Type')
            args.append(arg_name_ref)

        elif arg.type is ArgumentType.PYSLICE:
            args.append('&PySlice_Type')
            args.append(arg_name_ref)

        elif arg.type is ArgumentType.PYTYPE:
            args.append('&PyType_Type')
            args.append(arg_name_ref)

        elif arg.type is ArgumentType.ENUM:
            if arg.definition.fq_cpp_name is not None:
                args.append(_gto_name(arg.definition))

            args.append(arg_name_ref)

        elif arg.type is ArgumentType.CAPSULE:
            args.append('"' + arg.definition.as_cpp + '"')
            args.append(arg_name_ref)

        else:
            if arg.array is not ArrayArgument.ARRAY_SIZE:
                args.append(arg_name_ref)

            if arg.array is ArrayArgument.ARRAY:
                array_len_arg_name = fmt_argument_as_name(spec,
                        py_signature.args[array_len_arg_nr], array_len_arg_nr)
                args.append('&' + array_len_arg_name)

    args = ', '.join(args)

    sf.write(f'        if ({parser_function}({args}))\n')


def _get_subformat_char(arg):
    """ Return the sub-format character for an argument. """

    flags = 0

    if arg.transfer is Transfer.TRANSFER:
        flags |= 0x02

    if arg.transfer is Transfer.TRANSFER_BACK:
        flags |= 0x04

    if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
        if len(arg.derefs) == 0 or arg.disallow_none:
            flags |= 0x01

        if arg.transfer is Transfer.TRANSFER_THIS:
            flags |= 0x10

        if arg.is_constrained or (arg.type is ArgumentType.CLASS and arg.definition.convert_to_type_code is None):
            flags |= 0x08

    return chr(ord('0') + flags)


def _get_convert_to_type_code(type):
    """ Return a type's %ConvertToTypeCode. """

    if type.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and not type.is_constrained:
        return type.definition.convert_to_type_code

    return None


def _gc_ellipsis(sf, signature):
    """ Generate the code to garbage collect any ellipsis argument. """

    last = len(signature.args) - 1

    if last >= 0 and signature.args[last].type is ArgumentType.ELLIPSIS:
        sf.write(f'\n            Py_DECREF(a{last});\n')


def _delete_outs(sf, spec, py_signature):
    """ Generate the code to delete any instances created to hold /Out/
    arguments.
    """

    for arg_nr, arg in enumerate(py_signature.args):
        if arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED) and _need_new_instance(arg):
            sf.write(f'                delete {fmt_argument_as_name(spec, arg, arg_nr)};\n')



def _delete_temporaries(sf, spec, py_signature):
    """ Generate the code to delete any temporary variables on the heap created
    by type convertors.
    """

    for arg_nr, arg in enumerate(py_signature.args):
        arg_name = fmt_argument_as_name(spec, arg, arg_nr)

        if arg.array is ArrayArgument.ARRAY and arg.type in (ArgumentType.CLASS, ArgumentType.MAPPED):
            if arg.transfer is not Transfer.TRANSFER:
                extra_indent = ''

                if arg.type is ArgumentType.CLASS and _abi_supports_array(spec):
                    sf.write(f'            if ({arg_name}IsTemp)\n')
                    extra_indent = '    '

                if spec.c_bindings:
                    sf.write(f'            {extra_indent}sipFree({arg_name});\n')
                else:
                    sf.write(f'            {extra_indent}delete[] {arg_name};\n')

            continue

        if not arg.is_in:
            continue

        if arg.type in (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING) and len(arg.derefs) == 1:
            decref = 'Py_XDECREF' if arg.default_value is not None else 'Py_DECREF'

            sf.write(f'            {decref}({arg_name}Keep);\n')

        elif arg.type is ArgumentType.WSTRING and len(arg.derefs) == 1:
            if spec.c_bindings or not arg.is_const:
                sf.write(f'            sipFree({arg_name});\n')
            else:
                sf.write(f'            sipFree(const_cast<wchar_t *>({arg_name}));\n')

        else:
            convert_to_type_code = _get_convert_to_type_code(arg)

            if convert_to_type_code is not None:
                if arg.type is ArgumentType.MAPPED and arg.definition.no_release:
                    continue

                sf.write(f'            sipReleaseType{_user_state_suffix(spec, arg)}(')

                if spec.c_bindings or not arg.is_const:
                    sf.write(arg_name)
                else:
                    arg_cpp_plain = fmt_argument_as_cpp_type(spec, arg,
                            plain=True, no_derefs=True)
                    sf.write(f'const_cast<{arg_cpp_plain} *>({arg_name})')

                sf.write(f', {_gto_name(arg.definition)}, {arg_name}State')

                if _type_needs_user_state(arg):
                    sf.write(f', {arg_name}UserState')

                sf.write(');\n')


def _get_normalised_cached_name(cached_name):
    """ Return the normalised form of a cached name. """

    # If the name seems to be a template then just use the offset to ensure
    # that it is unique.
    if '<' in cached_name.name:
        return str(cached_name.offset)

    # Handle C++ and Python scopes.
    return cached_name.name.replace(':', '_').replace('.', '_')


def _scoped_class_name(spec, klass):
    """ Return a scoped class name as a string.  Protected classes have to be
    explicitly scoped.
    """

    return fmt_class_as_scoped_name(spec, klass, scope=klass.iface_file)


def _need_error_flag(code):
    """ Return True if handwritten code uses the error flag. """

    return _is_used_in_code(code, 'sipError')


def _need_old_error_flag(code):
    """ Return True if handwritten code uses the deprecated error flag. """

    return _is_used_in_code(code, 'sipIsErr')


def _need_new_instance(arg):
    """ Return True if the argument type means an instance needs to be created
    on the heap to pass back to Python.
    """

    if not arg.is_in and arg.is_out:
        if arg.is_reference and len(arg.derefs) == 0:
            return True

        if not arg.is_reference and len(arg.derefs) == 1:
            return True

    return False


def _fake_protected_args(signature):
    """ Convert any protected arguments (ie. those whose type is unavailable
    outside of a shadow class) to a fundamental type to be used instead (with
    suitable casts).
    """

    protection_state = []

    for arg in signature.args:
        if arg.type is ArgumentType.ENUM and arg.definition.is_protected:
            protection_state.append(arg)
            arg.type = ArgumentType.INT
        elif arg.type is ArgumentType.CLASS and arg.definition.is_protected:
            protection_state.append((arg, arg.derefs, arg.is_reference))
            arg.type = ArgumentType.FAKE_VOID
            arg.derefs = [False]
            arg.is_reference = False

    return protection_state


def _restore_protected_args(protection_state):
    """ Restore any protected arguments faked by _fake_protected_args(). """

    for protected in protection_state:
        if isinstance(protected, Argument):
            protected.type = ArgumentType.ENUM
        else:
            protected, derefs, is_reference = protected
            protected.type = ArgumentType.CLASS
            protected.derefs = derefs
            protected.is_reference = is_reference


def _remove_protection(arg, protection_state):
    """ Reset and save any protections so that the argument will be rendered
    exactly as defined in C++.
    """

    if arg.type in (ArgumentType.CLASS, ArgumentType.ENUM) and arg.definition.is_protected:
        arg.definition.is_protected = False
        protection_state.add(arg.definition)


def _remove_protections(signature, protection_state):
    """ Reset and save any protections so that the signature will be rendered
    exactly as defined in C++.
    """

    _remove_protection(signature.result, protection_state)

    for arg in signature.args:
        _remove_protection(arg, protection_state)


def _restore_protections(protection_state):
    """ Restore any protections removed by _remove_protection(). """

    for protected in protection_state:
        protected.is_protected = True


def _need_dealloc(spec, bindings, klass):
    """ Return True if a dealloc function is needed for a class. """

    if klass.iface_file.type is IfaceFileType.NAMESPACE:
        return False

    # Each of these conditions cause some code to be generated.

    if bindings.tracing:
        return True

    if spec.c_bindings:
        return True

    if len(klass.dealloc_code) != 0:
        return True

    if klass.dtor is AccessSpecifier.PUBLIC:
        return True

    if klass.has_shadow:
        return True

    return False


def _arg_name(spec, name, code):
    """ Return the argument name to use in a function definition for
    handwritten code.
    """

    # Always use the name in C code.
    if spec.c_bindings:
        return name

    # Use the name if it is used in the handwritten code.
    if _is_used_in_code(code, name):
        return name

    # Don't use the name.
    return ''


def _use_in_code(code, s, spec=None):
    """ Return the string to use depending on whether it is used in some code
    and optionally if the bindings are for C.
    """

    # Always use the string for C bindings.
    if spec is not None and spec.c_bindings:
        return s

    return s if _is_used_in_code(code, s) else ''


def _is_used_in_code(code, s):
    """ Return True if a string is used in code. """

    # The code may be a list of code blocks or an optional code block.
    if code is None:
        return False

    if isinstance(code, CodeBlock):
        code = [code]

    for cb in code:
        if s in cb.text:
            return True

    return False


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)

    if spec.c_bindings:
        return f'{klass_type} *sipCpp = ({klass_type} *)sipCppV'

    return f'{klass_type} *sipCpp = reinterpret_cast<{klass_type} *>(sipCppV)'


def _mapped_type_from_void(spec, mapped_type_type):
    """ Return an assignment statement from a void * variable to a mapped type
    variable.
    """

    if spec.c_bindings:
        return f'{mapped_type_type} *sipCpp = ({mapped_type_type} *)sipCppV'

    return f'{mapped_type_type} *sipCpp = reinterpret_cast<{mapped_type_type} *>(sipCppV)'


# The types that need a Python reference.
_PY_REF_TYPES = (ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING,
    ArgumentType.UTF8_STRING, ArgumentType.USTRING, ArgumentType.SSTRING,
    ArgumentType.STRING)

def _keep_py_reference(arg):
    """ Return True if the argument has a type that requires an extra reference
    to the originating object to be kept.
    """

    return (arg.type in _PY_REF_TYPES and not arg.is_reference and len(arg.derefs) > 0)


def _get_encoding(type):
    """ Return the encoding character for the given type. """

    if type.type is ArgumentType.ASCII_STRING:
        encoding = 'A'
    elif type.type is ArgumentType.LATIN1_STRING:
        encoding = 'L'
    elif type.type is ArgumentType.UTF8_STRING:
        encoding = '8'
    elif type.type is ArgumentType.WSTRING:
        encoding = 'w' if len(type.derefs) == 0 else 'W'
    else:
        encoding = 'N'

    return encoding


def _has_member_docstring(bindings, member, overloads):
    """ Return True if a function/method has a docstring. """

    auto_docstring = False

    # Check for any explicit docstrings and remember if there were any that
    # could be automatically generated.
    for overload in _callable_overloads(member, overloads):
        if overload.docstring is not None:
            return True

        if bindings.docstrings:
            auto_docstring = True

    if member.no_arg_parser:
        return False

    return auto_docstring


def _member_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.
    """

    NEWLINE = '\\n"\n"'

    auto_docstring = True

    # See if all the docstrings are automatically generated.
    all_auto = True
    any_implied = False

    for overload in _callable_overloads(member, overloads):
        if overload.docstring is not None:
            all_auto = False

            if overload.docstring.signature is not DocstringSignature.DISCARDED:
                any_implied = True

    # Generate the docstring.
    is_first = True

    for overload in _callable_overloads(member, overloads):
        if not is_first:
            sf.write(NEWLINE)

            # Insert a blank line if any explicit docstring wants to include a
            # signature.  This maintains compatibility with previous versions.
            if any_implied:
                sf.write(NEWLINE)

        if overload.docstring is not None:
            if overload.docstring.signature is DocstringSignature.PREPENDED:
                _member_auto_docstring(sf, spec, bindings, overload, is_method)
                sf.write(NEWLINE)

            sf.write(_docstring_text(overload.docstring))

            if overload.docstring.signature is DocstringSignature.APPENDED:
                sf.write(NEWLINE)
                _member_auto_docstring(sf, spec, bindings, overload, is_method)

            auto_docstring = False
        elif all_auto or any_implied:
            _member_auto_docstring(sf, spec, bindings, overload, is_method)

        is_first = False

    return auto_docstring


def _member_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)


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 _class_docstring(sf, spec, bindings, klass):
    """ Generate the docstring for a class. """

    NEWLINE = '\\n"\n"'

    # See if all the docstrings are automatically generated.
    all_auto = (klass.docstring is None)
    any_implied = False

    for ctor in klass.ctors:
        if ctor.access_specifier is AccessSpecifier.PRIVATE:
            continue

        if ctor.docstring is not None:
            all_auto = False

            if ctor.docstring.signature is not DocstringSignature.DISCARDED:
                any_implied = True

    # Generate the docstring.
    if all_auto:
        sf.write('\\1')

    if klass.docstring is not None and klass.docstring.signature is not DocstringSignature.PREPENDED:
        sf.write(_docstring_text(klass.docstring))
        is_first = False
    else:
        is_first = True

    if klass.docstring is None or klass.docstring.signature is not DocstringSignature.DISCARDED:
        for ctor in klass.ctors:
            if ctor.access_specifier is AccessSpecifier.PRIVATE:
                continue

            if not is_first:
                sf.write(NEWLINE)

                # Insert a blank line if any explicit docstring wants to
                # include a signature.  This maintains compatibility with
                # previous versions.
                if any_implied:
                    sf.write(NEWLINE)

            if ctor.docstring is not None:
                if ctor.docstring.signature is DocstringSignature.PREPENDED:
                    _ctor_auto_docstring(sf, spec, bindings, klass, ctor)
                    sf.write(NEWLINE)

                sf.write(_docstring_text(ctor.docstring))

                if ctor.docstring.signature is DocstringSignature.APPENDED:
                    sf.write(NEWLINE)
                    _ctor_auto_docstring(sf, spec, bindings, klass, ctor)
            elif all_auto or any_implied:
                _ctor_auto_docstring(sf, spec, bindings, klass, ctor)

            is_first = False

    if klass.docstring is not None and klass.docstring.signature is DocstringSignature.PREPENDED:
        if not is_first:
            sf.write(NEWLINE)
            sf.write(NEWLINE)

        sf.write(_docstring_text(klass.docstring))


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)


def _docstring_text(docstring):
    """ Return the text of a docstring. """

    text = docstring.text

    # Remove any single trailing newline.
    if text.endswith('\n'):
        text = text[:-1]

    s = ''

    for ch in text:
        if ch == '\n':
            # Let the compiler concatanate lines.
            s += '\\n"\n"'
        elif ch in r'\"':
            s += '\\'
            s += ch
        elif ch.isprintable():
            s += ch
        else:
            s += f'\\{ord(ch):03o}'

    return s


def _module_docstring(sf, module):
    """ Generate the definition of a module's optional docstring. """

    if module.docstring is not None:
        sf.write(
f'''
"PyDoc_STRVAR(doc_mod_{module.py_name}, "{_docstring_text(module.docstring)}");
''')


def _get_void_ptr_cast(type):
    """ Return a void* cast for an argument if needed. """

    # Generate a cast if the argument's type was a typedef.  This allows us to
    # use typedef's to void* to hide something more complex that we don't
    # handle.
    if type.original_typedef is None:
        return ''

    return '(const void *)' if type.is_const else '(void *)'


def _declare_limited_api(sf, py_debug, module=None):
    """ Declare the use of the limited API. """

    if py_debug:
        return

    if module is None or module.use_limited_api:
        sf.write(
'''
#if !defined(Py_LIMITED_API)
#define Py_LIMITED_API
#endif
''')


def _plugin_signals_table(sf, spec, bindings, klass):
    """ Generate the PyQt signals table and return True if anything was
    generated.
    """

    # Handle the trivial case.
    if not klass.is_qobject:
        return False

    is_signals = False

    # The signals must be grouped by name.
    for member in klass.members:
        member_nr = member.member_nr

        for overload in klass.overloads:
            if overload.common is not member or overload.pyqt_method_specifier is not PyQtMethodSpecifier.SIGNAL:
                continue

            if member_nr >= 0:
                # See if there is a non-signal overload.
                for non_sig in klass.overloads:
                    if non_sig is not overload and non_sig.common is member and non_sig.pyqt_method_specifier is not PyQtMethodSpecifier.SIGNAL:
                        break
                else:
                    member_nr = -1

            if not is_signals:
                is_signals = True

                _pyqt_emitters(sf, spec, klass)

                pyqt_version = '5' if _pyqt5(spec) else '6'
                sf.write(
f'''

/* Define this type's signals. */
static const pyqt{pyqt_version}QtSignal signals_{klass.iface_file.fq_cpp_name.as_word}[] = {{
''')

            # We enable a hack that supplies any missing optional arguments.
            # We only include the version with all arguments and provide an
            # emitter function which handles the optional arguments.
            _pyqt_signal_table_entry(sf, spec, bindings, klass, overload,
                    member_nr)

            member_nr = -1

    if is_signals:
        sf.write('    {SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR, SIP_NULLPTR}\n};\n')

    return is_signals


def _has_optional_args(overload):
    """ Return True if an overload has optional arguments. """

    args = overload.cpp_signature.args

    return len(args) != 0 and args[-1].default_value is not None


def _pyqt_class_plugin(sf, spec, bindings, klass):
    """ Generate any extended class definition data for PyQt.  Return True if
    anything was generated.
    """

    is_signals = _plugin_signals_table(sf, spec, bindings, klass)

    # The PyQt6 support code doesn't assume the structure is generated.
    if _pyqt6(spec):
        generated = is_signals

        if klass.is_qobject and not klass.pyqt_no_qmetaobject:
            generated = True

        if klass.pyqt_interface is not None:
            generated = True

        if not generated:
            return False

    klass_name = klass.iface_file.fq_cpp_name.as_word

    pyqt_version = '5' if _pyqt5(spec) else '6'
    sf.write(f'\n\nstatic pyqt{pyqt_version}ClassPluginDef plugin_{klass_name} = {{\n')

    mo_ref = f'&{_scoped_class_name(spec, klass)}::staticMetaObject' if klass.is_qobject and not klass.pyqt_no_qmetaobject else 'SIP_NULLPTR'
    sf.write(f'    {mo_ref},\n')

    if _pyqt5(spec):
        sf.write(f'    {klass.pyqt_flags},\n')

    signals_ref = f'signals_{klass_name}' if is_signals else 'SIP_NULLPTR'
    sf.write(f'    {signals_ref},\n')

    interface_ref = f'"{klass.pyqt_interface}"' if klass.pyqt_interface is not None else 'SIP_NULLPTR'
    sf.write(f'    {interface_ref}\n')

    sf.write('};\n')

    return True


def _global_function_table_entries(sf, spec, bindings, members):
    """ Generate the entries in a table of PyMethodDef for global functions.
    """

    for member in members:
        if member.py_slot is None:
            py_name = _get_normalised_cached_name(member.py_name)
            sf.write(f'        {{sipName_{py_name}, ')

            if member.no_arg_parser or member.allow_keyword_args:
                sf.write(f'SIP_MLMETH_CAST(func_{member.py_name.name}), METH_VARARGS|METH_KEYWORDS')
            else:
                sf.write(f'func_{member.py_name.name}, METH_VARARGS')

            docstring = _optional_ptr(
                    _has_member_docstring(bindings, member,
                            spec.module.overloads),
                    'doc_' + member.py_name.name)
            sf.write(f', {docstring}}},\n')


def _enum_class_scope(spec, enum):
    """ Return the scope of an unscoped enum as a string. """

    if enum.is_protected:
        scope_s = 'sip' + enum.scope.iface_file.fq_cpp_name.as_word
    elif enum.scope.is_protected:
        scope_s = _scoped_class_name(spec, enum.scope)
    else:
        scope_s = enum.scope.iface_file.fq_cpp_name.as_cpp

    return scope_s


def _include_sip_h(sf, module):
    """ Generate the inclusion of sip.h. """

    if module.py_ssize_t_clean:
        sf.write(
'''
#define PY_SSIZE_T_CLEAN
''')

    sf.write(
'''
#include "sip.h"
''')


def _enum_member(spec, enum_member):
    """ Return an enum member as a string. """

    if spec.c_bindings:
        return enum_member.cpp_name

    enum = enum_member.scope

    if enum.no_scope:
        scope_s = ''
    else:
        if enum.is_scoped:
            scope_s = '::' + enum.cached_fq_cpp_name.name
        elif isinstance(enum.scope, WrappedClass):
            scope_s = _enum_class_scope(spec, enum)
        elif isinstance(enum.scope, MappedType):
            scope_s = enum.scope.iface_file.fq_cpp_name.as_cpp
        else:
            # This can't happen.
            scope_s = ''

        scope_s += '::'

    return f'static_cast<int>({scope_s}{enum_member.cpp_name})'


def _type_needs_user_state(type):
    """ Return True if a type needs user state to be provided. """

    return type.type is ArgumentType.MAPPED and type.definition.needs_user_state


def _user_state_suffix(spec, type):
    """ Return the suffix for functions that have a variant that supports a
    user state.
    """

    return 'US' if spec.abi_version >= (13, 0) and _type_needs_user_state(type) else ''


def _exception_handler(sf, spec):
    """ Generate the exception handler for a module. """

    need_decl = True

    for exception in spec.exceptions:
        if exception.iface_file.module is spec.module:
            if need_decl:
                sf.write(
f'''

/* Handle the exceptions defined in this module. */
bool sipExceptionHandler_{spec.module.py_name}(std::exception_ptr sipExcPtr)
{{
    try {{
        std::rethrow_exception(sipExcPtr);
    }}
''')

                need_decl = False

            _catch_block(sf, spec, exception)

    if not need_decl:
        sf.write(
'''    catch (...) {}

    return false;
}
''')
 
 
def _pyqt5(spec):
    """ Return True if the PyQt5 plugin was specified. """

    return 'PyQt5' in spec.plugins


def _pyqt6(spec):
    """ Return True if the PyQt6 plugin was specified. """

    return 'PyQt6' in spec.plugins


def _append_qualifier_defines(module, bindings, qualifier_defines):
    """ Append the #defines for each feature defined in a module to a list of
    them.
    """

    for qualifier in module.qualifiers:
        qualifier_type_name = None

        if qualifier.type is QualifierType.TIME:
            if _qualifier_enabled(qualifier, bindings):
                qualifier_type_name = 'TIMELINE'

        elif qualifier.type is QualifierType.PLATFORM:
            if _qualifier_enabled(qualifier, bindings):
                qualifier_type_name = 'PLATFORM'

        elif qualifier.type is QualifierType.FEATURE:
            if qualifier.name not in bindings.disabled_features and qualifier.enabled_by_default:
                qualifier_type_name = 'FEATURE'

        if qualifier_type_name is not None:
            qualifier_defines.append(f'#define SIP_{qualifier_type_name}_{qualifier.name}')


def _qualifier_enabled(qualifier, bindings):
    """ Return True if a qualifier is enabled. """

    for tag in bindings.tags:
        if qualifier.name == tag:
            return qualifier.enabled_by_default

    return False


def _overload_auto_docstring(sf, spec, overload, is_method=True):
    """ Generate the docstring for a single API overload. """

    need_self = is_method and not overload.is_static
    signature = fmt_signature_as_type_hint(spec, overload.py_signature,
            need_self=need_self)
    sf.write(overload.common.py_name.name + signature)


def _sequence_support(sf, spec, klass, overload):
    """ Generate extra support for sequences because the class has an overload
    that has been annotated with __len__.
    """

    # We require a single int argument.
    if len(overload.py_signature.args) != 1:
        return

    arg0 = overload.py_signature.args[0]

    if not py_as_int(arg0):
        return

    # At the moment all we do is check that an index to __getitem__ is within
    # range so that the class supports Python iteration.  In the future we
    # should add support for negative indices, slices, __setitem__ and
    # __delitem__ (which will require enhancements to the sip module ABI).
    if overload.common.py_slot is PySlot.GETITEM:
        index_arg = fmt_argument_as_name(spec, arg0, 0)

        sf.write(
f'''            if ({index_arg} < 0 || {index_arg} >= sipCpp->{klass.len_cpp_name}())
            {{
                PyErr_SetNone(PyExc_IndexError);
                return SIP_NULLPTR;
            }}

''')


def _abi_has_next_exception_handler(spec):
    """ Return True if the ABI implements sipNextExceptionHandler(). """

    return _abi_version_check(spec, (12, 9), (13, 1))


def _abi_has_working_char_conversion(spec):
    """ Return True if the ABI has working char to/from a Python integer
    converters (ie. char is not assumed to be signed).
    """

    return _abi_version_check(spec, (12, 15), (13, 8))


def _abi_supports_array(spec):
    """ Return True if the ABI supports sip.array. """

    return _abi_version_check(spec, (12, 11), (13, 4))


def _abi_version_check(spec, min_12, min_13):
    """ Return True if the ABI version meets minimum version requirements. """

    return spec.abi_version >= min_13 or (min_12 <= spec.abi_version < (13, 0))


def _cached_name_ref(cached_name, as_nr=False):
    """ Return a reference to a cached name. """

    prefix = 'sipNameNr_' if as_nr else 'sipName_'

    return prefix + _get_normalised_cached_name(cached_name)


def _const_cast(spec, type, value):
    """ Return a value with an appropriate const_cast to a type. """

    if type.is_const:
        cpp_type = fmt_argument_as_cpp_type(spec, type, plain=True,
                no_derefs=True)

        return f'const_cast<{cpp_type} *>({value})'

    return value


def _callable_overloads(member, overloads):
    """ An iterator over the non-private and non-signal overloads. """

    for overload in overloads:
        if overload.common is member and overload.access_specifier is not AccessSpecifier.PRIVATE and overload.pyqt_method_specifier is not PyQtMethodSpecifier.SIGNAL:
            yield overload


def _gto_name(wrapped_object):
    """ Return the name of the generated type object for a wrapped object. """

    fq_cpp_name = wrapped_object.fq_cpp_name if isinstance(wrapped_object, WrappedEnum) else wrapped_object.iface_file.fq_cpp_name

    return 'sipType_' + fq_cpp_name.as_word


def _unique_class_ctors(spec, klass):
    """ An iterator over non-private ctors that have a unique C++ signature.
    """

    for ctor in klass.ctors:
        if ctor.access_specifier is AccessSpecifier.PRIVATE:
            continue

        if ctor.cpp_signature is None:
            continue

        for do_ctor in klass.ctors:
            if do_ctor is ctor:
                yield ctor
                break

            if do_ctor.cpp_signature is not None and same_signature(spec, do_ctor.cpp_signature, ctor.cpp_signature):
                break


def _unique_class_virtual_overloads(spec, klass):
    """ An iterator over non-private virtual overloads that have a unique C++
    signature.
    """

    for virtual_overload in klass.virtual_overloads:
        overload = virtual_overload.overload

        if overload.access_specifier is AccessSpecifier.PRIVATE:
            continue

        for do_virtual_overload in klass.virtual_overloads:
            if do_virtual_overload is virtual_overload:
                yield virtual_overload
                break

            do_overload = do_virtual_overload.overload

            if do_overload.cpp_name == overload.cpp_name and same_signature(spec, do_overload.cpp_signature, overload.cpp_signature):
                break


def _module_supports_qt(spec):
    """ Return True if the module implements Qt support. """

    return spec.pyqt_qobject is not None and spec.pyqt_qobject.iface_file.module is spec.module


def _optional_ptr(is_ptr, name):
    """ Return an appropriate reference to an optional pointer. """

    return name if is_ptr else 'SIP_NULLPTR'


# The map of slots to C++ names.
_SLOT_NAME_MAP = {
    PySlot.ADD:         'operator+',
    PySlot.SUB:         'operator-',
    PySlot.MUL:         'operator*',
    PySlot.TRUEDIV:     'operator/',
    PySlot.MOD:         'operator%',
    PySlot.AND:         'operator&',
    PySlot.OR:          'operator|',
    PySlot.XOR:         'operator^',
    PySlot.LSHIFT:      'operator<<',
    PySlot.RSHIFT:      'operator>>',
    PySlot.IADD:        'operator+=',
    PySlot.ISUB:        'operator-=',
    PySlot.IMUL:        'operator*=',
    PySlot.ITRUEDIV:    'operator/=',
    PySlot.IMOD:        'operator%=',
    PySlot.IAND:        'operator&=',
    PySlot.IOR:         'operator|=',
    PySlot.IXOR:        'operator^=',
    PySlot.ILSHIFT:     'operator<<=',
    PySlot.IRSHIFT:     'operator>>=',
    PySlot.INVERT:      'operator~',
    PySlot.CALL:        'operator()',
    PySlot.GETITEM:     'operator[]',
    PySlot.LT:          'operator<',
    PySlot.LE:          'operator<=',
    PySlot.EQ:          'operator==',
    PySlot.NE:          'operator!=',
    PySlot.GT:          'operator>',
    PySlot.GE:          'operator>=',
}

def _overload_cpp_name(overload):
    """ Return the C++ name of an overload. """

    py_slot = overload.common.py_slot

    return overload.cpp_name if py_slot is None else _SLOT_NAME_MAP[py_slot]


def _py_scope(scope):
    """ Return the Python scope by accounting for hidden C++ namespaces. """

    return None if isinstance(scope, WrappedClass) and scope.is_hidden_namespace else scope


def _release_gil(gil_action, bindings):
    """ Return True if the GIL is to be released. """

    return bindings.release_gil if gil_action is GILAction.DEFAULT else gil_action is GILAction.RELEASE


def _variables_in_scope(spec, scope, check_handler=True):
    """ An iterator over the variables in a scope. """

    for variable in spec.variables:
        if _py_scope(variable.scope) is scope and variable.module is spec.module:
            if check_handler and variable.needs_handler:
                continue

            yield variable


def _write_instances_table(sf, scope, instances, declaration_template):
    """ Write a table of instances.  Return True if there was a table written.
    """

    if len(instances) == 0:
        return False

    if scope is None:
        dict_type = 'module'
        suffix = ''
    else:
        dict_type = 'type'
        suffix = '_' + scope.iface_file.fq_cpp_name.as_word

    declaration = declaration_template.format(dict_type=dict_type,
            suffix=suffix)
    sf.write(f'\n\n{declaration} = {{\n')

    for instance in instances:
        entry = ', '.join(instance)
        sf.write(f'    {{{entry}}},\n')

    sentinals = ', '.join('0' * len(instances[0]))
    sf.write(f'    {{{sentinals}}}\n}};\n')

    return True


class SourceFile:
    """ The encapsulation of a source file. """

    def __init__(self, source_name, description, module, project, generated):
        """ Initialise the object. """

        self._description = description
        self._module = module

        self.open(source_name, project)

        generated.append(source_name)

    def __enter__(self):
        """ Implement a context manager for the file. """

        return self

    def __exit__(self, exc_type, exc_value, traceback):
        """ Implement a context manager for the file. """

        self.close()

    def close(self):
        """ Close the source file. """

        self._f.close()

    def open(self, source_name, project):
        """ Open a source file and make it current. """

        self._f = open(source_name, 'w', encoding='UTF-8')

        self._line_nr = 1

        self._write_header_comments(self._description, self._module,
                project.version_info)

    def write(self, s):
        """ Write a string while tracking the current line number. """

        self._f.write(s)
        self._line_nr += s.count('\n')

    def write_code(self, code):
        """ Write some handwritten code. """

        # A trivial case.
        if code is None:
            return

        # The code may be a single code block or a list of them.
        code_blocks = code if isinstance(code, list) else [code]

        # Another trivial case.
        if not code_blocks:
            return

        for code_block in code_blocks:
            self.write(f'#line {code_block.line_nr} "{self._posix_path(code_block.sip_file)}"\n')
            self.write(code_block.text)

        self.write(f'#line {self._line_nr + 1} "{self._posix_path(self._f.name)}"\n')

    @staticmethod
    def _posix_path(path):
        """ Return the POSIX format of a path. """

        return path.replace('\\', '/')

    def _write_header_comments(self, description, module, version_info):
        """ Write the comments at the start of the file. """

        version_info_s = f' *\n * Generated by SIP {SIP_VERSION_STR}\n' if version_info else ''

        copying_s = fmt_copying(module.copying, ' *')

        self.write(
f'''/*
 * {description}
{version_info_s}{copying_s} */

''')


class CompilationUnit(SourceFile):
    """ Encapsulate a compilation unit, ie. a C or C++ source file. """

    def __init__(self, source_name, description, module, project, buildable,
            sip_api_file=True):
        """ Initialise the object. """

        super().__init__(source_name, description, module, project,
                buildable.sources)

        self.write_code(module.unit_code)

        if sip_api_file:
            self.write(f'#include "sipAPI{module.py_name}.h"\n')
