[neon/forks/sip6/Neon/release] /: New upstream version 6.7.3+dfsg

Dmitry Shachnev null at kde.org
Sat Feb 4 02:04:45 GMT 2023


Git commit f1be3fc300d917c5b5eacf2d4cb6f845fc830b92 by Dmitry Shachnev.
Committed on 28/10/2022 at 16:57.
Pushed by carlosdem into branch 'Neon/release'.

New upstream version 6.7.3+dfsg

M  +177  -0    ChangeLog
M  +3    -0    NEWS
M  +1    -1    PKG-INFO
D  +0    -1110 code_generator/export.c
M  +2    -2    code_generator/gencode.c
M  +134  -134  code_generator/py2c.c
M  +0    -50   code_generator/pybinding.c
M  +4    -10   code_generator/sip.h
M  +58   -984  code_generator/type_hints.c
M  +1    -1    sip.egg-info/PKG-INFO
M  +15   -14   sip.egg-info/SOURCES.txt
M  +6    -5    sipbuild/bindings.py
M  +4    -7    sipbuild/generator/instantiations.py
M  +1    -0    sipbuild/generator/outputs/__init__.py
M  +17   -14   sipbuild/generator/outputs/api.py
R  +3    -0    sipbuild/generator/outputs/formatters/__init__.py [from: sipbuild/generator/formatters/__init__.py - 091% similarity]
R  +150  -58   sipbuild/generator/outputs/formatters/argument.py [from: sipbuild/generator/formatters/argument.py - 070% similarity]
R  +0    -0    sipbuild/generator/outputs/formatters/base_formatter.py [from: sipbuild/generator/formatters/base_formatter.py - 100% similarity]
R  +53   -1    sipbuild/generator/outputs/formatters/enum.py [from: sipbuild/generator/formatters/enum.py - 050% similarity]
R  +28   -3    sipbuild/generator/outputs/formatters/klass.py [from: sipbuild/generator/formatters/klass.py - 063% similarity]
R  +1    -1    sipbuild/generator/outputs/formatters/overload.py [from: sipbuild/generator/formatters/overload.py - 098% similarity]
R  +0    -0    sipbuild/generator/outputs/formatters/scoped.py [from: sipbuild/generator/formatters/scoped.py - 100% similarity]
R  +11   -8    sipbuild/generator/outputs/formatters/signature.py [from: sipbuild/generator/formatters/signature.py - 080% similarity]
R  +6    -9    sipbuild/generator/outputs/formatters/template.py [from: sipbuild/generator/formatters/template.py - 085% similarity]
R  +33   -0    sipbuild/generator/outputs/formatters/utils.py [from: sipbuild/generator/formatters/utils.py - 066% similarity]
R  +51   -11   sipbuild/generator/outputs/formatters/value_list.py [from: sipbuild/generator/formatters/value_list.py - 065% similarity]
R  +10   -0    sipbuild/generator/outputs/formatters/variable.py [from: sipbuild/generator/formatters/variable.py - 079% similarity]
A  +685  -0    sipbuild/generator/outputs/pyi.py
A  +497  -0    sipbuild/generator/outputs/type_hints.py
A  +418  -0    sipbuild/generator/outputs/xml.py
M  +4    -2    sipbuild/generator/parser/parser.py
M  +13   -16   sipbuild/generator/parser/parser_manager.py
M  +6    -5    sipbuild/generator/resolver/resolver.py
M  +5    -10   sipbuild/generator/specification.py
D  +0    -70   sipbuild/generator/type_hints.py
M  +2    -2    sipbuild/version.py

https://invent.kde.org/neon/forks/sip6/commit/f1be3fc300d917c5b5eacf2d4cb6f845fc830b92

diff --git a/ChangeLog b/ChangeLog
index 8b2d6ef..ea9bb1a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,182 @@
+2022-10-27  Phil Thompson  <phil at riverbankcomputing.com>
+
+	* sipbuild/generator/outputs/pyi.py:
+	Fixed the .pyi file __eq__ and __ne__ entries.
+	[ea12891e29e2] [6.7.3] <6.7-maint>
+
+	* NEWS, sipbuild/generator/outputs/pyi.py:
+	Added the Python .pyi code to the repo.
+	[1246f197548e] <6.7-maint>
+
+	* sipbuild/generator/outputs/type_hints.py:
+	Brackets are allowed in non-typing module type hints.
+	[b0f99d3165dc] <6.7-maint>
+
+	* code_generator/gencode.c, code_generator/sip.h,
+	code_generator/type_hints.c:
+	Stripped out redundant C code.
+	[4dedf2303b4a] <6.7-maint>
+
+	* sipbuild/generator/outputs/type_hints.py:
+	Completed the Python implementation of type hints.
+	[ce9d2cb4dc51] <6.7-maint>
+
+	* sipbuild/generator/outputs/formatters/argument.py,
+	sipbuild/generator/outputs/formatters/enum.py,
+	sipbuild/generator/outputs/formatters/klass.py,
+	sipbuild/generator/outputs/formatters/utils.py,
+	sipbuild/generator/outputs/type_hints.py:
+	Debugging of new type hint code.
+	[1756cefda6bb] <6.7-maint>
+
+2022-10-25  Phil Thompson  <phil at riverbankcomputing.com>
+
+	* NEWS, code_generator/pybinding.c, sipbuild/bindings.py,
+	sipbuild/generator/outputs/__init__.py,
+	sipbuild/generator/outputs/api.py,
+	sipbuild/generator/outputs/formatters/__init__.py,
+	sipbuild/generator/outputs/formatters/argument.py,
+	sipbuild/generator/outputs/formatters/enum.py,
+	sipbuild/generator/outputs/formatters/klass.py,
+	sipbuild/generator/outputs/formatters/signature.py,
+	sipbuild/generator/outputs/formatters/utils.py,
+	sipbuild/generator/outputs/formatters/value_list.py,
+	sipbuild/generator/outputs/formatters/variable.py,
+	sipbuild/generator/outputs/type_hints.py,
+	sipbuild/generator/outputs/xml.py,
+	sipbuild/generator/parser/parser.py,
+	sipbuild/generator/parser/parser_manager.py,
+	sipbuild/generator/specification.py:
+	Initial re-implementation of the .pyi file generation in Python.
+	[f81ba5225472] <6.7-maint>
+
+2022-10-23  Phil Thompson  <phil at riverbankcomputing.com>
+
+	* code_generator/py2c.c:
+	Removed a couple of unused conversion functions.
+	[62dfbdfe52f8] <6.7-maint>
+
+	* code_generator/py2c.c:
+	Fixed a regression in the convertion of type hints to C.
+	[d5c985671fc1] <6.7-maint>
+
+	* sipbuild/generator/outputs/formatters/argument.py,
+	sipbuild/generator/outputs/formatters/signature.py,
+	sipbuild/generator/outputs/formatters/template.py,
+	sipbuild/generator/outputs/xml.py:
+	Fixes to the XML output.
+	[11942a8be37d] <6.7-maint>
+
+	* code_generator/py2c.c:
+	Fixed a regression in the converting of type hints to C.
+	[464398062a62] <6.7-maint>
+
+	* code_generator/py2c.c, sipbuild/generator/formatters/__init__.py,
+	sipbuild/generator/formatters/argument.py,
+	sipbuild/generator/formatters/base_formatter.py,
+	sipbuild/generator/formatters/enum.py,
+	sipbuild/generator/formatters/klass.py,
+	sipbuild/generator/formatters/overload.py,
+	sipbuild/generator/formatters/scoped.py,
+	sipbuild/generator/formatters/signature.py,
+	sipbuild/generator/formatters/template.py,
+	sipbuild/generator/formatters/utils.py,
+	sipbuild/generator/formatters/value_list.py,
+	sipbuild/generator/formatters/variable.py,
+	sipbuild/generator/instantiations.py,
+	sipbuild/generator/outputs/api.py,
+	sipbuild/generator/outputs/formatters/__init__.py,
+	sipbuild/generator/outputs/formatters/argument.py,
+	sipbuild/generator/outputs/formatters/base_formatter.py,
+	sipbuild/generator/outputs/formatters/enum.py,
+	sipbuild/generator/outputs/formatters/klass.py,
+	sipbuild/generator/outputs/formatters/overload.py,
+	sipbuild/generator/outputs/formatters/scoped.py,
+	sipbuild/generator/outputs/formatters/signature.py,
+	sipbuild/generator/outputs/formatters/template.py,
+	sipbuild/generator/outputs/formatters/utils.py,
+	sipbuild/generator/outputs/formatters/value_list.py,
+	sipbuild/generator/outputs/formatters/variable.py,
+	sipbuild/generator/outputs/type_hints.py,
+	sipbuild/generator/outputs/xml.py,
+	sipbuild/generator/parser/parser_manager.py,
+	sipbuild/generator/specification.py,
+	sipbuild/generator/type_hints.py:
+	Refactoring based on a type hint just being an optional string.
+	[fb794ebfa872] <6.7-maint>
+
+	* NEWS, sipbuild/generator/outputs/xml.py:
+	The XML is now generated as an etree root node rather than an XML
+	file.
+	[9b31d8115ad0] <6.7-maint>
+
+2022-10-22  Phil Thompson  <phil at riverbankcomputing.com>
+
+	* sipbuild/generator/type_hints.py:
+	Completed the Python implementation of the XML generation.
+	[59c9a1dcd3dc] <6.7-maint>
+
+	* sipbuild/generator/type_hints.py:
+	Fixed the parsing of type hints for class enums defined with Python
+	scopes.
+	[46dfc03ef680] <6.7-maint>
+
+	* sipbuild/generator/type_hints.py:
+	Fixed the parsing of mapped type type hints.
+	[990804c44eec] <6.7-maint>
+
+	* sipbuild/generator/formatters/value_list.py,
+	sipbuild/generator/type_hints.py:
+	Debugging of XML generation.
+	[888053404043] <6.7-maint>
+
+	* code_generator/export.c, code_generator/pybinding.c,
+	code_generator/sip.h, sipbuild/generator/formatters/__init__.py,
+	sipbuild/generator/formatters/argument.py,
+	sipbuild/generator/formatters/enum.py,
+	sipbuild/generator/formatters/klass.py,
+	sipbuild/generator/formatters/signature.py,
+	sipbuild/generator/formatters/template.py,
+	sipbuild/generator/formatters/utils.py,
+	sipbuild/generator/formatters/value_list.py,
+	sipbuild/generator/outputs/xml.py,
+	sipbuild/generator/resolver/resolver.py,
+	sipbuild/generator/type_hints.py:
+	Completed the Python implementation of the XML generation.
+	[492f413d83bf] <6.7-maint>
+
+2022-10-20  Phil Thompson  <phil at riverbankcomputing.com>
+
+	* Merged the latest maint branch changes.
+	[c014ebe8e7f2] <6.7-maint>
+
+2022-10-18  Phil Thompson  <phil at riverbankcomputing.com>
+
+	* sipbuild/generator/formatters/argument.py,
+	sipbuild/generator/formatters/enum.py,
+	sipbuild/generator/formatters/klass.py,
+	sipbuild/generator/formatters/utils.py,
+	sipbuild/generator/formatters/value_list.py,
+	sipbuild/generator/formatters/variable.py,
+	sipbuild/generator/instantiations.py,
+	sipbuild/generator/outputs/xml.py,
+	sipbuild/generator/parser/parser_manager.py,
+	sipbuild/generator/type_hints.py:
+	Reimplemented enough of the XML output to represent a type.
+	[c9313bf161cb] <6.7-maint>
+
+2022-10-20  Phil Thompson  <phil at riverbankcomputing.com>
+
+	* NEWS, sipbuild/generator/resolver/resolver.py:
+	Fixed a bug when logging resolver errors in overloads.
+	[3b317b873f00] <6.7-maint>
+
 2022-10-12  Phil Thompson  <phil at riverbankcomputing.com>
 
+	* .hgtags:
+	Added tag 6.7.2 for changeset 02e1e42540d2
+	[b359afe150b3] <6.7-maint>
+
 	* NEWS, sipbuild/bindings.py, sipbuild/generator/__init__.py,
 	sipbuild/generator/api.py, sipbuild/generator/extracts.py,
 	sipbuild/generator/outputs/__init__.py,
diff --git a/NEWS b/NEWS
index e1a2747..d459f60 100644
--- a/NEWS
+++ b/NEWS
@@ -1,3 +1,6 @@
+v6.7.3 27th October 2022
+  - Bug fixes.
+
 v6.7.2 12th October 2022
   - Bug fixes.
 
diff --git a/PKG-INFO b/PKG-INFO
index 48fe4c5..be2f1a9 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: sip
-Version: 6.7.2
+Version: 6.7.3
 Summary: A Python bindings generator for C/C++ libraries
 Home-page: https://www.riverbankcomputing.com/software/sip/
 Author: Riverbank Computing Limited
diff --git a/code_generator/export.c b/code_generator/export.c
deleted file mode 100644
index cd3c3f7..0000000
--- a/code_generator/export.c
+++ /dev/null
@@ -1,1110 +0,0 @@
-/*
- * The XML and API file generator module for SIP.
- *
- * Copyright (c) 2022 Riverbank Computing Limited <info at riverbankcomputing.com>
- *
- * This file is part of SIP.
- *
- * This copy of SIP is licensed for use under the terms of the SIP License
- * Agreement.  See the file LICENSE for more details.
- *
- * This copy of SIP may also used under the terms of the GNU General Public
- * License v2 or v3 as published by the Free Software Foundation which can be
- * found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
- *
- * SIP is supplied WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- */
-
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "sip.h"
-
-
-#define XML_VERSION_NR  0       /* The schema version number. */
-
-/* Icon numbers.  The values are those used by the eric IDE. */
-#define CLASS_ID        1
-#define METHOD_ID       4
-#define VARIABLE_ID     7
-#define ENUM_ID         10
-
-
-static void xmlClass(sipSpec *pt, moduleDef *mod, classDef *cd, FILE *fp);
-static void xmlEnums(sipSpec *pt, moduleDef *mod, classDef *scope, int indent,
-        FILE *fp);
-static void xmlVars(sipSpec *pt, moduleDef *mod, classDef *scope, int indent,
-        FILE *fp);
-static void xmlFunction(sipSpec *pt, moduleDef *mod, classDef *scope,
-        memberDef *md, overDef *oloads, int indent, FILE *fp);
-static void xmlCtor(sipSpec *pt, moduleDef *mod, classDef *scope, ctorDef *ct,
-        int indent, FILE *fp);
-static void xmlOverload(sipSpec *pt, moduleDef *mod, classDef *scope,
-        memberDef *md, overDef *od, classDef *xtnds, int stat, int indent,
-        FILE *fp);
-static void xmlCppSignature(FILE *fp, signatureDef *sd, int is_const);
-static void xmlArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
-        KwArgs kwargs, int res_xfer, int indent, FILE *fp);
-static void xmlType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
-        KwArgs kwargs, FILE *fp);
-static void xmlIndent(int indent, FILE *fp);
-static void xmlRealName(scopedNameDef *fqcname, const char *member, FILE *fp);
-static void xmlRealScopedName(classDef *scope, const char *cppname, FILE *fp);
-static const char *pyType(sipSpec *pt, argDef *ad, classDef **scope);
-static void restPyEnumMember(enumMemberDef *emd, FILE *fp);
-static void restPyAttribute(moduleDef *mod, classDef *scope, nameDef *name,
-        FILE *fp);
-static int restValue(sipSpec *pt, valueDef *value, FILE *fp);
-static const char *reflectedSlot(slotType st);
-static int hasCppSignature(signatureDef *sd);
-static void appendScopedName(scopedNameDef **headp, scopedNameDef *newsnd);
-static void freeScopedName(scopedNameDef *snd);
-static scopedNameDef *text2scopePart(char *text);
-
-
-/*
- * Generate the XML export file.
- */
-int generateXML(sipSpec *pt, moduleDef *mod, const char *xmlFile)
-{
-    FILE *fp;
-    classDef *cd;
-    memberDef *md;
-
-    if ((fp = fopen(xmlFile, "w")) == NULL)
-        return error("Unable to create file \"%s\"\n", xmlFile);
-
-    fprintf(fp, "<?xml version=\"1.0\"?>\n");
-    fprintf(fp, "<Module version=\"%u\" name=\"%s\">\n",
-            XML_VERSION_NR, mod->name);
-
-    /*
-     * Note that we don't yet handle mapped types, templates or exceptions.
-     */
-
-    for (cd = pt->classes; cd != NULL; cd = cd->next)
-    {
-        if (cd->iff->module != mod)
-            continue;
-
-        if (isExternal(cd))
-            continue;
-
-        xmlClass(pt, mod, cd, fp);
-    }
-
-    for (cd = mod->proxies; cd != NULL; cd = cd->next)
-        xmlClass(pt, mod, cd, fp);
-
-    xmlEnums(pt, mod, NULL, 1, fp);
-    xmlVars(pt, mod, NULL, 1, fp);
-
-    for (md = mod->othfuncs; md != NULL; md = md->next)
-        xmlFunction(pt, mod, NULL, md, mod->overs, 1, fp);
-
-    fprintf(fp, "</Module>\n");
-
-    fclose(fp);
-
-    return 0;
-}
-
-
-/*
- * Generate a 'realname' attribute containing a fully qualified C/C++ name of
- * an object.
- */
-static void xmlRealScopedName(classDef *scope, const char *cppname, FILE *fp)
-{
-    const char *sep = "";
-
-    fprintf(fp, " realname=\"");
-
-    if (scope != NULL)
-    {
-        scopedNameDef *snd;
-
-        for (snd = removeGlobalScope(classFQCName(scope)); snd != NULL; snd = snd->next)
-        {
-            fprintf(fp, "%s%s", sep, snd->name);
-            sep = "::";
-        }
-    }
-
-    fprintf(fp, "%s%s\"", sep, cppname);
-}
-
-
-/*
- * Generate a 'realname' attribute containing a fully qualified C/C++ name.
- */
-static void xmlRealName(scopedNameDef *fqcname, const char *member, FILE *fp)
-{
-    const char *sep = "";
-    scopedNameDef *snd;
-
-    fprintf(fp, " realname=\"");
-
-    for (snd = removeGlobalScope(fqcname); snd != NULL; snd = snd->next)
-    {
-        fprintf(fp, "%s%s", sep, snd->name);
-        sep = "::";
-    }
-
-    if (member != NULL)
-        fprintf(fp, "::%s", member);
-
-    fprintf(fp, "\"");
-}
-
-
-/*
- * Generate the XML for a class.
- */
-static void xmlClass(sipSpec *pt, moduleDef *mod, classDef *cd, FILE *fp)
-{
-    int indent = 1;
-    ctorDef *ct;
-    memberDef *md;
-
-    if (isOpaque(cd))
-    {
-        xmlIndent(indent, fp);
-        fprintf(fp, "<OpaqueClass name=\"");
-        prScopedPythonName(fp, cd->ecd, cd->pyname->text);
-        fprintf(fp, "\"/>\n");
-
-        return;
-    }
-
-    if (!isHiddenNamespace(cd))
-    {
-        xmlIndent(indent++, fp);
-        fprintf(fp, "<Class name=\"");
-        prScopedPythonName(fp, cd->ecd, cd->pyname->text);
-        fprintf(fp, "\"");
-
-        xmlRealName(classFQCName(cd), NULL, fp);
-
-        if (cd->picklecode != NULL)
-            fprintf(fp, " pickle=\"1\"");
-
-        if (cd->convtocode != NULL)
-            fprintf(fp, " convert=\"1\"");
-
-        if (cd->convfromcode != NULL)
-            fprintf(fp, " convertfrom=\"1\"");
-
-        if (cd->real != NULL)
-            fprintf(fp, " extends=\"%s\"", cd->real->iff->module->name);
-
-        if (cd->pyqt_flags_enums != NULL)
-        {
-            const char *sep;
-            stringList *sl;
-
-            fprintf(fp, " flagsenums=\"");
-            sep = "";
-
-            for (sl = cd->pyqt_flags_enums; sl != NULL; sl = sl->next)
-            {
-                fprintf(fp, "%s%s", sep, sl->s);
-                sep = " ";
-            }
-
-            fprintf(fp, "\"");
-        }
-
-        if (cd->supers != NULL)
-        {
-            classList *cl;
-
-            fprintf(fp, " inherits=\"");
-
-            for (cl = cd->supers; cl != NULL; cl = cl->next)
-            {
-                if (cl != cd->supers)
-                    fprintf(fp, " ");
-
-                restPyClass(cl->cd, fp);
-            }
-
-            fprintf(fp, "\"");
-        }
-
-        fprintf(fp, ">\n");
-    }
-
-    for (ct = cd->ctors; ct != NULL; ct = ct->next)
-    {
-        if (isPrivateCtor(ct))
-            continue;
-
-        xmlCtor(pt, mod, cd, ct, indent, fp);
-    }
-
-    xmlEnums(pt, mod, cd, indent, fp);
-    xmlVars(pt, mod, cd, indent, fp);
-
-    for (md = cd->members; md != NULL; md = md->next)
-        xmlFunction(pt, mod, cd, md, cd->overs, indent, fp);
-
-    if (!isHiddenNamespace(cd))
-    {
-        xmlIndent(--indent, fp);
-        fprintf(fp, "</Class>\n");
-    }
-}
-
-
-/*
- * Generate the XML for all the enums in a scope.
- */
-static void xmlEnums(sipSpec *pt, moduleDef *mod, classDef *scope, int indent,
-        FILE *fp)
-{
-    enumDef *ed;
-
-    for (ed = pt->enums; ed != NULL; ed = ed->next)
-    {
-        if (ed->module != mod)
-            continue;
-
-        if (ed->ecd != scope)
-            continue;
-
-        if (ed->pyname != NULL)
-        {
-            enumMemberDef *emd;
-
-            xmlIndent(indent++, fp);
-            fprintf(fp, "<Enum name=\"");
-            prScopedPythonName(fp, ed->ecd, ed->pyname->text);
-            fprintf(fp, "\"");
-
-            xmlRealName(ed->fqcname, NULL, fp);
-
-            fprintf(fp, ">\n");
-
-            for (emd = ed->members; emd != NULL; emd = emd->next)
-            {
-                xmlIndent(indent, fp);
-                fprintf(fp, "<EnumMember name=\"");
-                prScopedPythonName(fp, ed->ecd, ed->pyname->text);
-                fprintf(fp, ".%s\"", emd->pyname->text);
-
-                xmlRealName(ed->fqcname, emd->cname, fp);
-
-                fprintf(fp, "/>\n");
-            }
-
-            xmlIndent(--indent, fp);
-            fprintf(fp, "</Enum>\n");
-        }
-        else
-        {
-            enumMemberDef *emd;
-
-            for (emd = ed->members; emd != NULL; emd = emd->next)
-            {
-                xmlIndent(indent, fp);
-                fprintf(fp, "<Member name=\"");
-                prScopedPythonName(fp, ed->ecd, emd->pyname->text);
-                fprintf(fp, "\"");
-
-                xmlRealScopedName(scope, emd->cname, fp);
-
-                fprintf(fp, " const=\"1\" typename=\"int\"/>\n");
-            }
-        }
-    }
-}
-
-
-/*
- * Generate the XML for all the variables in a scope.
- */
-static void xmlVars(sipSpec *pt, moduleDef *mod, classDef *scope, int indent,
-        FILE *fp)
-{
-    varDef *vd;
-
-    for (vd = pt->vars; vd != NULL; vd = vd->next)
-    {
-        if (vd->module != mod)
-            continue;
-
-        if (vd->ecd != scope)
-            continue;
-
-        xmlIndent(indent, fp);
-        fprintf(fp, "<Member name=\"");
-        prScopedPythonName(fp, vd->ecd, vd->pyname->text);
-        fprintf(fp, "\"");
-
-        xmlRealName(vd->fqcname, NULL, fp);
-
-        if (isConstArg(&vd->type) || scope == NULL)
-            fprintf(fp, " const=\"1\"");
-
-        if (isStaticVar(vd))
-            fprintf(fp, " static=\"1\"");
-
-        xmlType(pt, mod, &vd->type, FALSE, NoKwArgs, fp);
-        fprintf(fp, "/>\n");
-    }
-}
-
-
-/*
- * Generate the XML for a ctor.
- */
-static void xmlCtor(sipSpec *pt, moduleDef *mod, classDef *scope, ctorDef *ct,
-        int indent, FILE *fp)
-{
-    int a;
-
-    xmlIndent(indent++, fp);
-    fprintf(fp, "<Function name=\"");
-    prScopedPythonName(fp, scope, "__init__");
-    fprintf(fp, "\"");
-
-    xmlRealScopedName(scope, "__init__", fp);
-
-    if (hasCppSignature(ct->cppsig))
-    {
-        fprintf(fp, " cppsig=\"");
-        xmlCppSignature(fp, ct->cppsig, FALSE);
-        fprintf(fp, "\"");
-    }
-
-    /* Handle the trivial case. */
-    if (ct->pysig.nrArgs == 0)
-    {
-        fprintf(fp, "/>\n");
-        return;
-    }
-
-    fprintf(fp, ">\n");
-
-    for (a = 0; a < ct->pysig.nrArgs; ++a)
-    {
-        argDef *ad = &ct->pysig.args[a];
-
-        if (isInArg(ad))
-            xmlArgument(pt, mod, ad, FALSE, ct->kwargs, FALSE, indent, fp);
-
-        if (isOutArg(ad))
-            xmlArgument(pt, mod, ad, TRUE, ct->kwargs, FALSE, indent, fp);
-    }
-
-    xmlIndent(--indent, fp);
-    fprintf(fp, "</Function>\n");
-}
-
-
-/*
- * Generate the XML for a function.
- */
-static void xmlFunction(sipSpec *pt, moduleDef *mod, classDef *scope,
-        memberDef *md, overDef *oloads, int indent, FILE *fp)
-{
-    overDef *od;
-
-    for (od = oloads; od != NULL; od = od->next)
-    {
-        int isstat;
-        classDef *xtnds;
-
-        if (od->common != md)
-            continue;
-
-        if (isPrivate(od))
-            continue;
-
-        if (isSignal(od))
-        {
-            int a;
-
-            xmlIndent(indent++, fp);
-            fprintf(fp, "<Signal name=\"");
-            prScopedPythonName(fp, scope, md->pyname->text);
-            fprintf(fp, "\"");
-
-            xmlRealScopedName(scope, od->cppname, fp);
-
-            if (hasCppSignature(od->cppsig))
-            {
-                fprintf(fp, " cppsig=\"");
-                xmlCppSignature(fp, od->cppsig, FALSE);
-                fprintf(fp, "\"");
-            }
-
-            /* Handle the trivial case. */
-            if (od->pysig.nrArgs == 0)
-            {
-                fprintf(fp, "/>\n");
-                continue;
-            }
-
-            fprintf(fp, ">\n");
-
-            for (a = 0; a < od->pysig.nrArgs; ++a)
-            {
-                argDef *ad = &od->pysig.args[a];
-
-                xmlArgument(pt, mod, ad, FALSE, od->kwargs, FALSE, indent, fp);
-            }
-
-            xmlIndent(--indent, fp);
-            fprintf(fp, "</Signal>\n");
-
-            continue;
-        }
-
-        xtnds = NULL;
-        isstat = (scope == NULL || scope->iff->type == namespace_iface || isStatic(od));
-
-        if (scope == NULL && md->slot != no_slot && od->pysig.args[0].atype == class_type)
-        {
-            xtnds = od->pysig.args[0].u.cd;
-            isstat = FALSE;
-        }
-
-        xmlOverload(pt, mod, scope, md, od, xtnds, isstat, indent, fp);
-    }
-}
-
-
-/*
- * Generate the XML for an overload.
- */
-static void xmlOverload(sipSpec *pt, moduleDef *mod, classDef *scope,
-        memberDef *md, overDef *od, classDef *xtnds, int stat, int indent,
-        FILE *fp)
-{
-    const char *name, *cppname = od->cppname;
-    int a, no_res;
-
-    xmlIndent(indent++, fp);
-    fprintf(fp, "<Function name=\"");
-
-    if (isReflected(od))
-    {
-        if ((name = reflectedSlot(md->slot)) != NULL)
-            cppname = name;
-        else
-            name = md->pyname->text;
-    }
-    else
-    {
-        name = md->pyname->text;
-    }
-
-    prScopedPythonName(fp, scope, name);
-
-    fprintf(fp, "\"");
-
-    xmlRealScopedName(scope, cppname, fp);
-
-    if (hasCppSignature(od->cppsig))
-    {
-        fprintf(fp, " cppsig=\"");
-        xmlCppSignature(fp, od->cppsig, isConst(od));
-        fprintf(fp, "\"");
-    }
-
-    if (isAbstract(od))
-        fprintf(fp, " abstract=\"1\"");
-
-    if (stat)
-        fprintf(fp, " static=\"1\"");
-
-    if (isSlot(od))
-        fprintf(fp, " slot=\"1\"");
-
-    if (isVirtual(od))
-    {
-        fprintf(fp, " virtual=\"1\"");
-    }
-
-    if (xtnds != NULL)
-    {
-        fprintf(fp, " extends=\"");
-        prScopedPythonName(fp, xtnds->ecd, xtnds->pyname->text);
-        fprintf(fp, "\"");
-    }
-
-    /* An empty type hint specifies a void return. */
-    if (od->pysig.result.typehint_out != NULL && od->pysig.result.typehint_out->raw_hint[0] == '\0')
-        no_res = TRUE;
-    else
-        no_res = (od->pysig.result.atype == void_type && od->pysig.result.nrderefs == 0);
-
-    /* Handle the trivial case. */
-    if (no_res && od->pysig.nrArgs == 0)
-    {
-        fprintf(fp, "/>\n");
-        return;
-    }
-
-    fprintf(fp, ">\n");
-
-    if (!no_res)
-        xmlArgument(pt, mod, &od->pysig.result, TRUE, NoKwArgs,
-                isResultTransferredBack(od), indent, fp);
-
-    for (a = 0; a < od->pysig.nrArgs; ++a)
-    {
-        argDef *ad = &od->pysig.args[a];
-
-        /*
-         * Ignore the first argument of non-reflected number slots and the
-         * second argument of reflected number slots.
-         */
-        if (isNumberSlot(md) && od->pysig.nrArgs == 2)
-            if ((a == 0 && !isReflected(od)) || (a == 1 && isReflected(od)))
-                continue;
-
-        if (isInArg(ad))
-            xmlArgument(pt, mod, ad, FALSE, od->kwargs, FALSE, indent, fp);
-
-        if (isOutArg(ad))
-            xmlArgument(pt, mod, ad, TRUE, od->kwargs, FALSE, indent, fp);
-    }
-
-    xmlIndent(--indent, fp);
-    fprintf(fp, "</Function>\n");
-}
-
-
-/*
- * Return TRUE if there is a C/C++ signature.
- */
-static int hasCppSignature(signatureDef *sd)
-{
-    int a;
-
-    if (sd == NULL)
-        return FALSE;
-
-    /*
-     * See if there are any arguments that could only have come from
-     * handwritten code.
-     */
-    for (a = 0; a < sd->nrArgs; ++a)
-    {
-        switch (sd->args[a].atype)
-        {
-        case pyobject_type:
-        case pytuple_type:
-        case pylist_type:
-        case pydict_type:
-        case pycallable_type:
-        case pyslice_type:
-        case pytype_type:
-        case pybuffer_type:
-        case pyenum_type:
-        case capsule_type:
-            return FALSE;
-
-        default:
-            break;
-        }
-    }
-
-    return TRUE;
-}
-
-
-/*
- * Generate the XML for a C++ signature.
- */
-static void xmlCppSignature(FILE *fp, signatureDef *sd, int is_const)
-{
-    int a;
-
-    prcode(fp, "%M");
-    normaliseArgs(sd);
-
-    prcode(fp, "(");
-
-    for (a = 0; a < sd->nrArgs; ++a)
-    {
-        argDef *ad = &sd->args[a];
-
-        if (a > 0)
-            prcode(fp, ",");
-
-        generateBaseType(NULL, ad, TRUE, STRIP_GLOBAL, fp);
-    }
-
-    prcode(fp, ")%s", (is_const ? " const" : ""));
-
-    restoreArgs(sd);
-    prcode(fp, "%M");
-}
-
-
-/*
- * Generate the XML for an argument.
- */
-static void xmlArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
-        KwArgs kwargs, int res_xfer, int indent, FILE *fp)
-{
-    if (isArraySize(ad))
-        return;
-
-    xmlIndent(indent, fp);
-    fprintf(fp, "<%s", (out ? "Return" : "Argument"));
-    xmlType(pt, mod, ad, out, kwargs, fp);
-
-    if (!out)
-    {
-        if (isAllowNone(ad))
-            fprintf(fp, " allownone=\"1\"");
-
-        if (isDisallowNone(ad))
-            fprintf(fp, " disallownone=\"1\"");
-
-        if (isTransferred(ad))
-            fprintf(fp, " transfer=\"to\"");
-        else if (isThisTransferred(ad))
-            fprintf(fp, " transfer=\"this\"");
-    }
-
-    if (res_xfer || isTransferredBack(ad))
-        fprintf(fp, " transfer=\"back\"");
-
-    fprintf(fp, "/>\n");
-}
-
-
-/*
- * Generate the XML for a type.
- */
-static void xmlType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
-        KwArgs kwargs, FILE *fp)
-{
-    const char *type_name;
-    classDef *type_scope;
-    typeHintDef *thd;
-
-    fprintf(fp, " typename=\"");
-
-    /* Handle the argument name. */
-    if (!out && ad->name != NULL)
-    {
-        if (kwargs == AllKwArgs || (kwargs == OptionalKwArgs && ad->defval != NULL))
-            fprintf(fp, "%s: ", ad->name->text);
-    }
-
-    /* Use any explicit type hint unless the argument is constrained. */
-    thd = (out ? ad->typehint_out : (isConstrained(ad) ? NULL : ad->typehint_in));
-
-    if (thd != NULL)
-    {
-        pyiTypeHint(pt, thd, mod, out, NULL, FALSE, TRUE, fp);
-    }
-    else
-    {
-        switch (ad->atype)
-        {
-        case class_type:
-            restPyClass(ad->u.cd, fp);
-            break;
-
-        case enum_type:
-            if (ad->u.ed->pyname != NULL)
-                restPyEnum(ad->u.ed, fp);
-            else
-                fprintf(fp, "int");
-
-            break;
-
-        case mapped_type:
-            /* There would normally be a type hint. */
-            fprintf(fp, "unknown-type");
-            break;
-
-        default:
-            if ((type_name = pyType(pt, ad, &type_scope)) != NULL)
-                prScopedPythonName(fp, type_scope, type_name);
-        }
-    }
-
-    if (!out && ad->name != NULL && ad->defval != NULL)
-    {
-        fprintf(fp, " = ");
-
-        /*
-         * Try and convert the value to a reST reference.  We don't try very
-         * hard but will get most cases.
-         */
-        if (!restValue(pt, ad->defval, fp))
-            prDefaultValue(ad, FALSE, fp);
-    }
-
-    fprintf(fp, "\"");
-}
-
-
-/*
- * Generate the indentation for a line.
- */
-static void xmlIndent(int indent, FILE *fp)
-{
-    while (indent-- > 0)
-        fprintf(fp, "  ");
-}
-
-
-/*
- * Get the Python representation of a type.
- */
-static const char *pyType(sipSpec *pt, argDef *ad, classDef **scope)
-{
-    const char *type_name;
-
-    *scope = NULL;
-
-    switch (ad->atype)
-    {
-    case class_type:
-        *scope = ad->u.cd->ecd;
-        type_name = ad->u.cd->pyname->text;
-        break;
-
-    case mapped_type:
-        if (ad->u.mtd->pyname != NULL)
-            type_name = ad->u.mtd->pyname->text;
-        else
-            type_name = "unknown-type";
-
-        break;
-
-    case capsule_type:
-        type_name = scopedNameTail(ad->u.cap);
-        break;
-
-    case struct_type:
-    case union_type:
-    case void_type:
-        type_name = "sip.voidptr";
-        break;
-
-    case enum_type:
-        if (ad->u.ed->pyname != NULL)
-        {
-            type_name = ad->u.ed->pyname->text;
-            *scope = ad->u.ed->ecd;
-        }
-        else
-            type_name = "int";
-        break;
-
-    case ustring_type:
-        /* Correct for Python v3. */
-        type_name = "bytes";
-        break;
-
-    case string_type:
-    case sstring_type:
-    case wstring_type:
-    case ascii_string_type:
-    case latin1_string_type:
-    case utf8_string_type:
-        type_name = isArray(ad) ? "bytes" : "str";
-        break;
-
-    case byte_type:
-    case sbyte_type:
-    case ubyte_type:
-    case ushort_type:
-    case uint_type:
-    case long_type:
-    case longlong_type:
-    case ulong_type:
-    case ulonglong_type:
-    case short_type:
-    case int_type:
-    case cint_type:
-    case ssize_type:
-    case size_type:
-    case hash_type:
-        type_name = "int";
-        break;
-
-    case float_type:
-    case cfloat_type:
-    case double_type:
-    case cdouble_type:
-        type_name = "float";
-        break;
-
-    case bool_type:
-    case cbool_type:
-        type_name = "bool";
-        break;
-
-    case pyobject_type:
-        type_name = "object";
-        break;
-
-    case pytuple_type:
-        type_name = "tuple";
-        break;
-
-    case pylist_type:
-        type_name = "list";
-        break;
-
-    case pydict_type:
-        type_name = "dict";
-        break;
-
-    case pycallable_type:
-        type_name = "callable";
-        break;
-
-    case pyslice_type:
-        type_name = "slice";
-        break;
-
-    case pytype_type:
-        type_name = "type";
-        break;
-
-    case pybuffer_type:
-        type_name = "buffer";
-        break;
-
-    case pyenum_type:
-        type_name = "enum";
-        break;
-
-    case ellipsis_type:
-        type_name = "...";
-        break;
-
-    default:
-        type_name = NULL;
-    }
-
-    return type_name;
-}
-
-
-/*
- * Generate a fully qualified attribute name as a reST reference.
- */
-static void restPyEnumMember(enumMemberDef *emd, FILE *fp)
-{
-    fprintf(fp, ":sip:ref:`~%s.", emd->ed->module->fullname->text);
-    prScopedPythonName(fp, emd->ed->ecd, emd->ed->pyname->text);
-    fprintf(fp, ".%s`", emd->pyname->text);
-}
-
-
-/*
- * Generate a fully qualified attribute name as a reST reference.
- */
-static void restPyAttribute(moduleDef *mod, classDef *scope, nameDef *name,
-        FILE *fp)
-{
-    fprintf(fp, ":sip:ref:`~%s.", mod->fullname->text);
-    prScopedPythonName(fp, scope, name->text);
-    fprintf(fp, "`");
-}
-
-
-/*
- * Generate a reST reference for a scoped name if possible.  Return TRUE if
- * something was generated.
- */
-static int restValue(sipSpec *pt, valueDef *value, FILE *fp)
-{
-    const char *name;
-    scopedNameDef *target, *scope, *snd;
-    varDef *vd;
-    enumDef *ed;
-
-    /* The value must be a scoped name and we don't handle expressions. */
-    if (value->vtype != scoped_value || value->next != NULL)
-        return FALSE;
-
-    target = value->u.vscp;
-
-    /* See if it is an attribute. */
-    for (vd = pt->vars; vd != NULL; vd = vd->next)
-        if (compareScopedNames(vd->fqcname, target) == 0)
-        {
-            restPyAttribute(vd->module, vd->ecd, vd->pyname, fp);
-
-            return TRUE;
-        }
-
-    /* Get the name and scope. */
-    name = scopedNameTail(target);
-
-    scope = NULL;
-
-    for (snd = target; snd->name != name; snd = snd->next)
-        appendScopedName(&scope, text2scopePart(snd->name));
-
-    /* See if it is an enum member. */
-    for (ed = pt->enums; ed != NULL; ed = ed->next)
-    {
-        enumMemberDef *emd;
-
-        /*
-         * Look for the member name first before working out if it is the
-         * correct enum.
-         */
-        for (emd = ed->members; emd != NULL; emd = emd->next)
-        {
-            if (strcmp(emd->cname, name) == 0)
-            {
-                if (isScopedEnum(ed))
-                {
-                    /*
-                     * It's a scoped enum so the fully qualified name of the
-                     * enum must match the scope of the name.
-                     */
-                    if (scope != NULL && compareScopedNames(ed->fqcname, scope) == 0)
-                    {
-                        restPyEnumMember(emd, fp);
-
-                        freeScopedName(scope);
-
-                        return TRUE;
-                    }
-                }
-                else
-                {
-                    /*
-                     * It's a traditional enum so the scope of the enum must
-                     * match the scope of the name.
-                     */
-                    if ((ed->ecd == NULL && scope == NULL) || (ed->ecd != NULL && scope != NULL && compareScopedNames(ed->ecd->iff->fqcname, scope) == 0))
-                    {
-                        if (ed->fqcname == NULL)
-                            restPyAttribute(ed->module, ed->ecd, emd->pyname,
-                                    fp);
-                        else
-                            restPyEnumMember(emd, fp);
-
-                        freeScopedName(scope);
-
-                        return TRUE;
-                    }
-                }
-
-                break;
-            }
-        }
-    }
-
-    freeScopedName(scope);
-
-    return FALSE;
-}
-
-
-/*
- * Return the name of the reflected version of a slot or NULL if it doesn't
- * have one.
- */
-static const char *reflectedSlot(slotType st)
-{
-    switch (st)
-    {
-    case add_slot:
-        return "__radd__";
-
-    case sub_slot:
-        return "__rsub__";
-
-    case mul_slot:
-        return "__rmul__";
-
-    case matmul_slot:
-        return "__rmatmul__";
-
-    case truediv_slot:
-        return "__rtruediv__";
-
-    case floordiv_slot:
-        return "__rfloordiv__";
-
-    case mod_slot:
-        return "__rmod__";
-
-    case lshift_slot:
-        return "__rlshift__";
-
-    case rshift_slot:
-        return "__rrshift__";
-
-    case and_slot:
-        return "__rand__";
-
-    case or_slot:
-        return "__ror__";
-
-    case xor_slot:
-        return "__rxor__";
-
-    default:
-        break;
-    }
-
-    return NULL;
-}
-
-
-/*
- * Append a name to a list of scopes.
- */
-static void appendScopedName(scopedNameDef **headp, scopedNameDef *newsnd)
-{
-    while (*headp != NULL)
-        headp = &(*headp)->next;
-
-    *headp = newsnd;
-}
-
-
-/*
- * Free a scoped name - but not the text itself.
- */
-static void freeScopedName(scopedNameDef *snd)
-{
-    while (snd != NULL)
-    {
-        scopedNameDef *next = snd->next;
-
-        free(snd);
-
-        snd = next;
-    }
-}
-
-
-/*
- * Convert a text string to a scope part structure.
- */
-static scopedNameDef *text2scopePart(char *text)
-{
-    scopedNameDef *snd;
-
-    snd = sipMalloc(sizeof (scopedNameDef));
-
-    snd->name = text;
-    snd->next = NULL;
-
-    return snd;
-}
diff --git a/code_generator/gencode.c b/code_generator/gencode.c
index 0b61abb..0ca5354 100644
--- a/code_generator/gencode.c
+++ b/code_generator/gencode.c
@@ -14904,7 +14904,7 @@ static void generateCtorAutoDocstring(sipSpec *pt, classDef *cd, ctorDef *ct,
 {
     if (docstrings)
     {
-        pyiCtor(pt, pt->module, cd, ct, FALSE, NULL, 0, fp);
+        pyiCtor(pt, pt->module, cd, ct, fp);
         ++currentLineNr;
     }
 }
@@ -15840,5 +15840,5 @@ memberDef *findMethod(classDef *cd, const char *name)
  */
 static void dsOverload(sipSpec *pt, overDef *od, int is_method, FILE *fp)
 {
-    pyiOverload(pt, pt->module, od, FALSE, is_method, NULL, 0, FALSE, fp);
+    pyiOverload(pt, pt->module, od, is_method, fp);
 }
diff --git a/code_generator/py2c.c b/code_generator/py2c.c
index a734851..af2f708 100644
--- a/code_generator/py2c.c
+++ b/code_generator/py2c.c
@@ -62,21 +62,30 @@
 #define TRANSFER_THIS           3
 
 
-/* Support for object caches. */
+/* Support for caches. */
 typedef struct _objectCache {
     PyObject *py_obj;           /* The original Python object. */
     void *c_struct;             /* The converted C structure. */
     struct _objectCache *next;  /* The next in the list. */
 } objectCache;
 
-static void cache(objectCache **head, PyObject *py_obj, void *c_struct);
-static void clear_cache(objectCache **head);
+typedef struct _strCache {
+    char *py_str;               /* The original Python string. */
+    void *c_struct;             /* The converted C structure. */
+    struct _strCache *next;     /* The next in the list. */
+} strCache;
+
 static void clear_caches(void);
-static void *search_cache(const objectCache *cache, PyObject *py_obj);
+static void cache_object(objectCache **head, PyObject *py_obj, void *c_struct);
+static void clear_object_cache(objectCache **head);
+static void *search_object_cache(const objectCache *cache, PyObject *py_obj);
+static void cache_str(strCache **head, char *py_str, void *c_struct);
+static void clear_str_cache(strCache **head);
+static void *search_str_cache(const strCache *cache, char *py_str);
 
 
 /*
- * The object caches.
+ * The caches.
  */
 static objectCache *cache_cachedname = NULL;
 static objectCache *cache_class = NULL;
@@ -88,38 +97,38 @@ static objectCache *cache_mappedtype = NULL;
 static objectCache *cache_member = NULL;
 static objectCache *cache_module = NULL;
 static objectCache *cache_qual = NULL;
-static objectCache *cache_typehint = NULL;
 static objectCache *cache_virtualerrorhandler = NULL;
 static objectCache *cache_wrappedenum = NULL;
 static objectCache *cache_wrappedtypedef = NULL;
+static strCache *cache_typehint = NULL;
 
 
 /*
- * Clear all the object caches.
+ * Clear all the caches.
  */
 static void clear_caches(void)
 {
-    clear_cache(&cache_cachedname);
-    clear_cache(&cache_class);
-    clear_cache(&cache_codeblock);
-    clear_cache(&cache_constructor);
-    clear_cache(&cache_exception);
-    clear_cache(&cache_ifacefile);
-    clear_cache(&cache_mappedtype);
-    clear_cache(&cache_member);
-    clear_cache(&cache_module);
-    clear_cache(&cache_qual);
-    clear_cache(&cache_typehint);
-    clear_cache(&cache_virtualerrorhandler);
-    clear_cache(&cache_wrappedenum);
-    clear_cache(&cache_wrappedtypedef);
+    clear_object_cache(&cache_cachedname);
+    clear_object_cache(&cache_class);
+    clear_object_cache(&cache_codeblock);
+    clear_object_cache(&cache_constructor);
+    clear_object_cache(&cache_exception);
+    clear_object_cache(&cache_ifacefile);
+    clear_object_cache(&cache_mappedtype);
+    clear_object_cache(&cache_member);
+    clear_object_cache(&cache_module);
+    clear_object_cache(&cache_qual);
+    clear_object_cache(&cache_virtualerrorhandler);
+    clear_object_cache(&cache_wrappedenum);
+    clear_object_cache(&cache_wrappedtypedef);
+    clear_str_cache(&cache_typehint);
 }
 
 
 /*
  * Clear an object cache.
  */
-static void clear_cache(objectCache **head)
+static void clear_object_cache(objectCache **head)
 {
     objectCache *entry = *head;
 
@@ -138,9 +147,9 @@ static void clear_cache(objectCache **head)
 
 
 /*
- * Add a Python object/C structure pair to a cache.
+ * Add a Python object/C structure pair to an object cache.
  */
-static void cache(objectCache **head, PyObject *py_obj, void *c_struct)
+static void cache_object(objectCache **head, PyObject *py_obj, void *c_struct)
 {
     objectCache *entry = sipMalloc(sizeof (objectCache));
 
@@ -153,10 +162,10 @@ static void cache(objectCache **head, PyObject *py_obj, void *c_struct)
 
 
 /*
- * Search a cache for a Python object and return the already converted C
- * structure.
+ * Search an object cache for a Python object and return the already converted
+ * C structure.
  */
-static void *search_cache(const objectCache *cache, PyObject *py_obj)
+static void *search_object_cache(const objectCache *cache, PyObject *py_obj)
 {
     while (cache != NULL)
     {
@@ -170,6 +179,60 @@ static void *search_cache(const objectCache *cache, PyObject *py_obj)
 }
 
 
+/*
+ * Clear a str cache.
+ */
+static void clear_str_cache(strCache **head)
+{
+    strCache *entry = *head;
+
+    while (entry != NULL)
+    {
+        strCache *next = entry->next;
+
+        free(entry->c_struct);
+        free(entry);
+
+        entry = next;
+    }
+
+    *head = NULL;
+}
+
+
+/*
+ * Add a Python str/C structure pair to a str cache.
+ */
+static void cache_str(strCache **head, char *py_str, void *c_struct)
+{
+    strCache *entry = sipMalloc(sizeof (strCache));
+
+    entry->py_str = py_str;
+    entry->c_struct = c_struct;
+    entry->next = *head;
+
+    *head = entry;
+}
+
+
+/*
+ * Search a str cache for a Python string and return the already converted
+ * C structure.
+ */
+static void *search_str_cache(const strCache *cache, char *py_str)
+{
+    while (cache != NULL)
+    {
+        if (strcmp(cache->py_str, py_str) == 0)
+            return cache->c_struct;
+
+        cache = cache->next;
+    }
+
+    return NULL;
+}
+
+
 /* Forward declarations of convertors. */
 static argDef *argument(sipSpec *pt, PyObject *obj, const char *encoding);
 static argDef *argument_attr(sipSpec *pt, PyObject *obj, const char *name,
@@ -190,8 +253,6 @@ static classDef *class_list_attr(sipSpec *pt, PyObject *obj, const char *name,
 static classList *classlist_attr(sipSpec *pt, PyObject *obj, const char *name,
         const char *encoding);
 static codeBlock *codeblock(PyObject *obj, const char *encoding);
-static codeBlock *codeblock_attr(PyObject *obj, const char *name,
-        const char *encoding);
 static codeBlockList *codeblock_list_attr(PyObject *obj, const char *name,
         const char *encoding);
 static ctorDef *constructor(sipSpec *pt, PyObject *obj, const char *encoding);
@@ -227,8 +288,6 @@ static licenseDef *license_attr(PyObject *obj, const char *name,
         const char *encoding);
 static mappedTypeDef *mappedtype(sipSpec *pt, PyObject *obj,
         const char *encoding);
-static mappedTypeDef *mappedtype_attr(sipSpec *pt, PyObject *obj,
-        const char *name, const char *encoding);
 static mappedTypeDef *mappedtype_list_attr(sipSpec *pt, PyObject *obj,
         const char *name, const char *encoding);
 static memberDef *member(sipSpec *pt, PyObject *obj, const char *encoding);
@@ -277,7 +336,6 @@ static throwArgs *throw_arguments(sipSpec *pt, PyObject *obj,
         const char *encoding);
 static throwArgs *throw_arguments_attr(sipSpec *pt, PyObject *obj,
         const char *name, const char *encoding);
-static typeHintDef *typehint(PyObject *obj, const char *encoding);
 static typeHintDef *typehint_attr(PyObject *obj, const char *name,
         const char *encoding);
 static void typehints_attr(PyObject *obj, const char *name,
@@ -326,7 +384,6 @@ static varDef *wrappedvariable_list_attr(sipSpec *pt, PyObject *obj,
 /* Other forward declarations. */
 static void appendCodeBlock(codeBlockList **headp, codeBlock *cb);
 static scopedNameDef *text2scopePart(char *text);
-static typeHintDef *newTypeHint(char *raw_hint);
 
 
 /*
@@ -586,12 +643,12 @@ static nameDef *cachedname(PyObject *obj, const char *encoding)
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_cachedname, obj)) != NULL)
+    if ((value = search_object_cache(cache_cachedname, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (nameDef));
 
-    cache(&cache_cachedname, obj, value);
+    cache_object(&cache_cachedname, obj, value);
 
     value->text = str_attr(obj, "name", encoding);
     value->len = strlen(value->text);
@@ -666,12 +723,12 @@ static classDef *class(sipSpec *pt, PyObject *obj, const char *encoding)
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_class, obj)) != NULL)
+    if ((value = search_object_cache(cache_class, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (classDef));
 
-    cache(&cache_class, obj, value);
+    cache_object(&cache_class, obj, value);
 
     value->iff = ifacefile_attr(pt, obj, "iface_file", encoding);
     value->docstring = docstring_attr(obj, "docstring", encoding);
@@ -895,12 +952,12 @@ static codeBlock *codeblock(PyObject *obj, const char *encoding)
 {
     codeBlock *value;
 
-    if ((value = search_cache(cache_codeblock, obj)) != NULL)
+    if ((value = search_object_cache(cache_codeblock, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (codeBlock));
 
-    cache(&cache_codeblock, obj, value);
+    cache_object(&cache_codeblock, obj, value);
 
     value->frag = str_attr(obj, "text", encoding);
     value->filename = str_attr(obj, "sip_file", encoding);
@@ -910,25 +967,6 @@ static codeBlock *codeblock(PyObject *obj, const char *encoding)
 }
 
 
-/*
- * Convert a CodeBlock attribute.
- */
-static codeBlock *codeblock_attr(PyObject *obj, const char *name,
-        const char *encoding)
-{
-    PyObject *attr = PyObject_GetAttrString(obj, name);
-    codeBlock *value;
-
-    assert(attr != NULL);
-
-    value = codeblock(attr, encoding);
-
-    Py_DECREF(attr);
-
-    return value;
-}
-
-
 /*
  * Convert a CodeBlock list or an optional CodeBlock attribute.
  */
@@ -973,12 +1011,12 @@ static ctorDef *constructor(sipSpec *pt, PyObject *obj, const char *encoding)
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_constructor, obj)) != NULL)
+    if ((value = search_object_cache(cache_constructor, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (ctorDef));
 
-    cache(&cache_constructor, obj, value);
+    cache_object(&cache_constructor, obj, value);
 
     value->docstring = docstring_attr(obj, "docstring", encoding);
 
@@ -1158,12 +1196,12 @@ static exceptionDef *exception(sipSpec *pt, PyObject *obj,
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_exception, obj)) != NULL)
+    if ((value = search_object_cache(cache_exception, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (exceptionDef));
 
-    cache(&cache_exception, obj, value);
+    cache_object(&cache_exception, obj, value);
 
     value->exceptionnr = int_attr(obj, "exception_nr");
     value->iff = ifacefile_attr(pt, obj, "iface_file", encoding);
@@ -1262,12 +1300,12 @@ static ifaceFileDef *ifacefile(sipSpec *pt, PyObject *obj,
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_ifacefile, obj)) != NULL)
+    if ((value = search_object_cache(cache_ifacefile, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (ifaceFileDef));
 
-    cache(&cache_ifacefile, obj, value);
+    cache_object(&cache_ifacefile, obj, value);
 
     value->name = cachedname_attr(obj, "cpp_name", encoding);
     value->needed = bool_attr(obj, "needed");
@@ -1419,12 +1457,12 @@ static mappedTypeDef *mappedtype(sipSpec *pt, PyObject *obj,
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_mappedtype, obj)) != NULL)
+    if ((value = search_object_cache(cache_mappedtype, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (mappedTypeDef));
 
-    cache(&cache_mappedtype, obj, value);
+    cache_object(&cache_mappedtype, obj, value);
 
     if (bool_attr(obj, "no_assignment_operator"))
         setNoAssignOp(value);
@@ -1467,25 +1505,6 @@ static mappedTypeDef *mappedtype(sipSpec *pt, PyObject *obj,
 }
 
 
-/*
- * Convert an optional MappedType attribute.
- */
-static mappedTypeDef *mappedtype_attr(sipSpec *pt, PyObject *obj,
-        const char *name, const char *encoding)
-{
-    PyObject *attr = PyObject_GetAttrString(obj, name);
-    mappedTypeDef *value;
-
-    assert(attr != NULL);
-
-    value = mappedtype(pt, attr, encoding);
-
-    Py_DECREF(attr);
-
-    return value;
-}
-
-
 /*
  * Convert a MappedType list attribute.
  */
@@ -1523,12 +1542,12 @@ static memberDef *member(sipSpec *pt, PyObject *obj, const char *encoding)
     memberDef *value;
     int slot;
 
-    if ((value = search_cache(cache_member, obj)) != NULL)
+    if ((value = search_object_cache(cache_member, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (memberDef));
 
-    cache(&cache_member, obj, value);
+    cache_object(&cache_member, obj, value);
 
     value->pyname = cachedname_attr(obj, "py_name", encoding);
 
@@ -1621,12 +1640,12 @@ static moduleDef *module(sipSpec *pt, PyObject *obj, const char *encoding)
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_module, obj)) != NULL)
+    if ((value = search_object_cache(cache_module, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (moduleDef));
 
-    cache(&cache_module, obj, value);
+    cache_object(&cache_module, obj, value);
 
     value->fullname = cachedname_attr(obj, "fq_py_name", encoding);
     value->name = str_attr(obj, "py_name", encoding);
@@ -2049,12 +2068,12 @@ static qualDef *qual(sipSpec *pt, PyObject *obj, const char *encoding)
 {
     qualDef *value;
 
-    if ((value = search_cache(cache_qual, obj)) != NULL)
+    if ((value = search_object_cache(cache_qual, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (qualDef));
 
-    cache(&cache_qual, obj, value);
+    cache_object(&cache_qual, obj, value);
 
     value->name = str_attr(obj, "name", encoding);
     value->qtype = (qualType)enum_attr(obj, "type");
@@ -2400,38 +2419,33 @@ static throwArgs *throw_arguments_attr(sipSpec *pt, PyObject *obj,
 
 
 /*
- * Convert an optional TypeHint object.
- */
-static typeHintDef *typehint(PyObject *obj, const char *encoding)
-{
-    typeHintDef *value;
-
-    if (obj == Py_None)
-        return NULL;
-
-    if ((value = search_cache(cache_typehint, obj)) != NULL)
-        return value;
-
-    value = newTypeHint(str_attr(obj, "text", encoding));
-
-    cache(&cache_typehint, obj, value);
-
-    return value;
-}
-
-
-/*
- * Convert an optional TypeHint attribute.
+ * Convert an optional str attribute as a typeHintDef.
  */
 static typeHintDef *typehint_attr(PyObject *obj, const char *name,
         const char *encoding)
 {
     PyObject *attr = PyObject_GetAttrString(obj, name);
     typeHintDef *value;
+    char *raw_hint;
 
     assert(attr != NULL);
 
-    value = typehint(attr, encoding);
+    if ((raw_hint = str(attr, encoding)) != NULL)
+    {
+        if ((value = search_str_cache(cache_typehint, raw_hint)) == NULL)
+        {
+            value = sipMalloc(sizeof (typeHintDef));
+
+            cache_str(&cache_typehint, raw_hint, value);
+
+            value->status = needs_parsing;
+            value->raw_hint = raw_hint;
+        }
+    }
+    else
+    {
+        value = NULL;
+    }
 
     Py_DECREF(attr);
 
@@ -2587,12 +2601,12 @@ static virtErrorHandler *virtualerrorhandler(sipSpec *pt, PyObject *obj,
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_virtualerrorhandler, obj)) != NULL)
+    if ((value = search_object_cache(cache_virtualerrorhandler, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (virtErrorHandler));
 
-    cache(&cache_virtualerrorhandler, obj, value);
+    cache_object(&cache_virtualerrorhandler, obj, value);
 
     value->name = str_attr(obj, "name", encoding);
     value->code = codeblock_list_attr(obj, "code", encoding);
@@ -2825,12 +2839,12 @@ static enumDef *wrappedenum(sipSpec *pt, PyObject *obj, const char *encoding)
     int base_type;
     PyObject *scope_obj;
 
-    if ((value = search_cache(cache_wrappedenum, obj)) != NULL)
+    if ((value = search_object_cache(cache_wrappedenum, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (enumDef));
 
-    cache(&cache_wrappedenum, obj, value);
+    cache_object(&cache_wrappedenum, obj, value);
 
     if (bool_attr(obj, "is_protected"))
         setIsProtectedEnum(value);
@@ -3004,12 +3018,12 @@ static typedefDef *wrappedtypedef(sipSpec *pt, PyObject *obj,
     if (obj == Py_None)
         return NULL;
 
-    if ((value = search_cache(cache_wrappedtypedef, obj)) != NULL)
+    if ((value = search_object_cache(cache_wrappedtypedef, obj)) != NULL)
         return value;
 
     value = sipMalloc(sizeof (typedefDef));
 
-    cache(&cache_wrappedtypedef, obj, value);
+    cache_object(&cache_wrappedtypedef, obj, value);
 
     if (bool_attr(obj, "no_type_name"))
         setNoTypeName(value);
@@ -3172,17 +3186,3 @@ static scopedNameDef *text2scopePart(char *text)
 
     return snd;
 }
-
-
-/*
- * Create a new type hint for a raw string.
- */
-static typeHintDef *newTypeHint(char *raw_hint)
-{
-    typeHintDef *thd = sipMalloc(sizeof (typeHintDef));
-
-    thd->status = needs_parsing;
-    thd->raw_hint = raw_hint;
-
-    return thd;
-}
diff --git a/code_generator/pybinding.c b/code_generator/pybinding.c
index c4745b9..28c786c 100644
--- a/code_generator/pybinding.c
+++ b/code_generator/pybinding.c
@@ -46,8 +46,6 @@ static void raise_exception(void);
 static PyObject *py_set_globals(PyObject *self, PyObject *args);
 static PyObject *py_py2c(PyObject *self, PyObject *args);
 static PyObject *py_generateCode(PyObject *self, PyObject *args);
-static PyObject *py_generateXML(PyObject *self, PyObject *args);
-static PyObject *py_generateTypeHints(PyObject *self, PyObject *args);
 
 static int fs_convertor(PyObject *obj, char **fsp);
 static int sipSpec_convertor(PyObject *obj, sipSpec **ptp);
@@ -65,8 +63,6 @@ PyMODINIT_FUNC PyInit_code_generator(void)
         {"set_globals", py_set_globals, METH_VARARGS, NULL},
         {"py2c", py_py2c, METH_VARARGS, NULL},
         {"generateCode", py_generateCode, METH_VARARGS, NULL},
-        {"generateXML", py_generateXML, METH_VARARGS, NULL},
-        {"generateTypeHints", py_generateTypeHints, METH_VARARGS, NULL},
         {NULL, NULL, 0, NULL},
     };
 
@@ -170,52 +166,6 @@ static PyObject *py_generateCode(PyObject *self, PyObject *args)
 }
 
 
-/*
- * Wrapper around generateXML().
- */
-static PyObject *py_generateXML(PyObject *self, PyObject *args)
-{
-    sipSpec *pt;
-    char *xmlFile;
-
-    if (!PyArg_ParseTuple(args, "O&O&",
-            sipSpec_convertor, &pt,
-            fs_convertor, &xmlFile))
-        return NULL;
-
-    if (generateXML(pt, pt->module, xmlFile) < 0)
-    {
-        raise_exception();
-        return NULL;
-    }
-
-    Py_RETURN_NONE;
-}
-
-
-/*
- * Wrapper around generateTypeHints().
- */
-static PyObject *py_generateTypeHints(PyObject *self, PyObject *args)
-{
-    sipSpec *pt;
-    char *pyiFile;
-
-    if (!PyArg_ParseTuple(args, "O&O&",
-            sipSpec_convertor, &pt,
-            fs_convertor, &pyiFile))
-        return NULL;
-
-    if (generateTypeHints(pt, pt->module, pyiFile) < 0)
-    {
-        raise_exception();
-        return NULL;
-    }
-
-    Py_RETURN_NONE;
-}
-
-
 /*
  * Convert a callable argument to a filesystem name.
  */
diff --git a/code_generator/sip.h b/code_generator/sip.h
index a458be8..31fcbb0 100644
--- a/code_generator/sip.h
+++ b/code_generator/sip.h
@@ -1298,8 +1298,6 @@ void get_bindings_configuration(const char *sip_file, stringList **tags,
 stringList *generateCode(sipSpec *, char *, const char *, int, int, int, int,
         stringList *needed_qualifiers, stringList *, int, int,
         const char **api_header);
-int generateXML(sipSpec *pt, moduleDef *mod, const char *xmlFile);
-int generateTypeHints(sipSpec *pt, moduleDef *mod, const char *pyiFile);
 void generateExpression(valueDef *vd, int in_str, FILE *fp);
 int error(const char *fmt, ...);
 void errorAppend(const char *fmt, ...);
@@ -1312,23 +1310,19 @@ int compareScopedNames(scopedNameDef *snd1, scopedNameDef *snd2);
 char *scopedNameTail(scopedNameDef *);
 void prcode(FILE *fp, const char *fmt, ...);
 void prCopying(FILE *fp, moduleDef *mod, const char *comment);
-void prDefaultValue(argDef *ad, int in_str, FILE *fp);
+void prDefaultValue(argDef *ad, FILE *fp);
 void prScopedPythonName(FILE *fp, classDef *scope, const char *pyname);
 int isNumberSlot(memberDef *md);
 void appendString(stringList **headp, const char *s);
 int pluginPyQt5(sipSpec *pt);
 int pluginPyQt6(sipSpec *pt);
 memberDef *findMethod(classDef *cd, const char *name);
-void pyiCtor(sipSpec *pt, moduleDef *mod, classDef *cd, ctorDef *ct,
-        int overloaded, ifaceFileList *defined, int indent, FILE *fp);
-void pyiOverload(sipSpec *pt, moduleDef *mod, overDef *od, int overloaded,
-        int is_method, ifaceFileList *defined, int indent, int pep484,
+void pyiCtor(sipSpec *pt, moduleDef *mod, classDef *cd, ctorDef *ct, FILE *fp);
+void pyiOverload(sipSpec *pt, moduleDef *mod, overDef *od, int is_method,
         FILE *fp);
 scopedNameDef *removeGlobalScope(scopedNameDef *snd);
 void pyiTypeHint(sipSpec *pt, typeHintDef *thd, moduleDef *mod, int out,
-        ifaceFileList *defined, int pep484, int rest, FILE *fp);
-void restPyClass(classDef *cd, FILE *fp);
-void restPyEnum(enumDef *ed, FILE *fp);
+        FILE *fp);
 void generateBaseType(ifaceFileDef *scope, argDef *ad, int use_typename,
         int strip, FILE *fp);
 void normaliseArgs(signatureDef *sd);
diff --git a/code_generator/type_hints.c b/code_generator/type_hints.c
index 583b48a..0b813cb 100644
--- a/code_generator/type_hints.c
+++ b/code_generator/type_hints.c
@@ -23,47 +23,17 @@
 #include "sip.h"
 
 
-/* Return a string referring to an object of any type. */
-#define anyObject(pep484)   ((pep484) ? "typing.Any" : "object")
-
-
-static void pyiCompositeModule(sipSpec *pt, moduleDef *comp_mod, FILE *fp);
-static void pyiModule(sipSpec *pt, moduleDef *mod, FILE *fp);
-static void pyiTypeHintCode(codeBlockList *thc, int indent, FILE *fp);
-static void pyiEnums(sipSpec *pt, moduleDef *mod, ifaceFileDef *scope,
-        ifaceFileList *defined, int indent, FILE *fp);
-static void pyiVars(sipSpec *pt, moduleDef *mod, classDef *scope,
-        ifaceFileList *defined, int indent, FILE *fp);
-static void pyiClass(sipSpec *pt, moduleDef *mod, classDef *cd,
-        ifaceFileList **defined, int indent, FILE *fp);
-static void pyiMappedType(sipSpec *pt, moduleDef *mod, mappedTypeDef *mtd,
-        ifaceFileList **defined, int indent, FILE *fp);
-static void pyiCallable(sipSpec *pt, moduleDef *mod, memberDef *md,
-        overDef *overloads, int is_method, ifaceFileList *defined, int indent,
-        FILE *fp);
-static void pyiProperty(sipSpec *pt, moduleDef *mod, propertyDef *pd,
-        int is_setter, memberDef *md, overDef *overloads,
-        ifaceFileList *defined, int indent, FILE *fp);
 static void pyiPythonSignature(sipSpec *pt, moduleDef *mod, signatureDef *sd,
-        int need_self, ifaceFileList *defined, KwArgs kwargs, int pep484,
-        FILE *fp);
+        int need_self, KwArgs kwargs, FILE *fp);
 static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
-        int out, int need_comma, int names, int defaults,
-        ifaceFileList *defined, KwArgs kwargs, int pep484, FILE *fp);
+        int out, int need_comma, int names, int defaults, KwArgs kwargs,
+        FILE *fp);
 static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
-        ifaceFileList *defined, int pep484, FILE *fp);
-static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod,
-        ifaceFileList *defined, int pep484, int rest, FILE *fp);
-static void prIndent(int indent, FILE *fp);
-static int separate(int first, int indent, FILE *fp);
-static void prClassRef(classDef *cd, moduleDef *mod, ifaceFileList *defined,
-        int pep484, FILE *fp);
-static void prEnumRef(enumDef *ed, moduleDef *mod, ifaceFileList *defined,
-        int pep484, FILE *fp);
+        FILE *fp);
+static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod, FILE *fp);
+static void prClassRef(classDef *cd, FILE *fp);
+static void prEnumRef(enumDef *ed, FILE *fp);
 static void prScopedEnumName(FILE *fp, enumDef *ed);
-static int isDefined(ifaceFileDef *iff, classDef *cd, moduleDef *mod,
-        ifaceFileList *defined);
-static int inIfaceFileList(ifaceFileDef *iff, ifaceFileList *defined);
 static void parseTypeHint(sipSpec *pt, typeHintDef *thd, int out);
 static int parseTypeHintNode(sipSpec *pt, int out, int top_level, char *start,
         char *end, typeHintNodeDef **thnp);
@@ -74,738 +44,43 @@ static enumDef *lookupEnum(sipSpec *pt, const char *name, classDef *scope_cd,
 static mappedTypeDef *lookupMappedType(sipSpec *pt, const char *name);
 static classDef *lookupClass(sipSpec *pt, const char *name,
         classDef *scope_cd);
-static void maybeAnyObject(const char *hint, int pep484, FILE *fp);
+static void maybeAnyObject(const char *hint, FILE *fp);
 static void strip_leading(char **startp, char *end);
 static void strip_trailing(char *start, char **endp);
 static typeHintNodeDef *flatten_unions(typeHintNodeDef *nodes);
 static typeHintNodeDef *copyTypeHintNode(sipSpec *pt, typeHintDef *thd,
         int out);
 static int isPyKeyword(const char *word);
-static void appendToIfaceFileList(ifaceFileList **ifflp, ifaceFileDef *iff);
-
-
-/*
- * Generate the .pyi file.
- */
-int generateTypeHints(sipSpec *pt, moduleDef *mod, const char *pyiFile)
-{
-    FILE *fp;
-
-    /* Generate the file. */
-    if ((fp = fopen(pyiFile, "w")) == NULL)
-        return error("Unable to create file \"%s\"\n", pyiFile);
-
-    /* Write the header. */
-    fprintf(fp,
-"# The PEP 484 type hints stub file for the %s module.\n"
-        , mod->name);
-
-    if (sipVersionStr != NULL)
-        fprintf(fp,
-"#\n"
-"# Generated by SIP %s\n"
-            , sipVersionStr);
-
-    prCopying(fp, mod, "#");
-
-    fprintf(fp,
-"\n"
-"\n"
-        );
-
-    if (isComposite(mod))
-        pyiCompositeModule(pt, mod, fp);
-    else
-        pyiModule(pt, mod, fp);
-
-    fclose(fp);
-
-    return 0;
-}
-
-
-/*
- * Generate the type hints for a composite module.
- */
-static void pyiCompositeModule(sipSpec *pt, moduleDef *comp_mod, FILE *fp)
-{
-    moduleDef *mod;
-
-    for (mod = pt->modules; mod != NULL; mod = mod->next)
-        if (mod->container == comp_mod)
-            fprintf(fp, "from %s import *\n", mod->fullname->text);
-}
-
-
-/*
- * Generate the type hints for an ordinary module.
- */
-static void pyiModule(sipSpec *pt, moduleDef *mod, FILE *fp)
-{
-    char *cp;
-    int first;
-    memberDef *md;
-    classDef *cd;
-    mappedTypeDef *mtd;
-    ifaceFileList *defined;
-    moduleListDef *mld;
-
-    /*
-     * Generate the imports. Note that we assume the super-types are the
-     * standard SIP ones.
-     */
-    if (abiVersion >= ABI_13_0)
-        fprintf(fp,
-"import enum\n"
-            );
-
-    fprintf(fp,
-"import typing\n"
-"\n"
-"import %s\n"
-        , (sipName != NULL) ? sipName : "sip");
-
-    first = TRUE;
-
-    for (mld = mod->imports; mld != NULL; mld = mld->next)
-    {
-        /* We lie about the indent because we only want one blank line. */
-        first = separate(first, 1, fp);
-
-        if ((cp = strrchr(mld->module->fullname->text, '.')) == NULL)
-        {
-            fprintf(fp, "import %s\n", mld->module->name);
-        }
-        else
-        {
-            *cp = '\0';
-            fprintf(fp, "from %s import %s\n", mld->module->fullname->text,
-                    mld->module->name);
-            *cp = '.';
-        }
-    }
-
-    /*
-     * Generate any exported type hint code and any module-specific type hint
-     * code.
-     */
-    pyiTypeHintCode(pt->exptypehintcode, 0, fp);
-    pyiTypeHintCode(mod->typehintcode, 0, fp);
-
-    /* Generate the types - global enums must be first. */
-    pyiEnums(pt, mod, NULL, NULL, 0, fp);
-
-    defined = NULL;
-
-    for (cd = pt->classes; cd != NULL; cd = cd->next)
-    {
-        if (cd->iff->module != mod)
-            continue;
-
-        if (isExternal(cd))
-            continue;
-
-        if (cd->no_typehint)
-            continue;
-
-        /* Only handle non-nested classes here. */
-        if (cd->ecd != NULL)
-            continue;
-
-        /* We can't handle extenders. */
-        if (cd->real != NULL)
-            continue;
-
-        pyiClass(pt, mod, cd, &defined, 0, fp);
-    }
-
-    for (mtd = pt->mappedtypes; mtd != NULL; mtd = mtd->next)
-    {
-        if (mtd->iff->module != mod)
-            continue;
-
-        if (mtd->pyname != NULL)
-            pyiMappedType(pt, mod, mtd, &defined, 0, fp);
-    }
-
-    pyiVars(pt, mod, NULL, defined, 0, fp);
-
-    first = TRUE;
-
-    for (md = mod->othfuncs; md != NULL; md = md->next)
-        if (md->slot == no_slot)
-        {
-            first = separate(first, 0, fp);
-
-            pyiCallable(pt, mod, md, mod->overs, FALSE, defined, 0, fp);
-        }
-}
-
-
-/*
- * Generate handwritten type hint code.
- */
-static void pyiTypeHintCode(codeBlockList *thc, int indent, FILE *fp)
-{
-    while (thc != NULL)
-    {
-        int need_indent = TRUE;
-        const char *cp;
-
-        fprintf(fp, "\n");
-
-        for (cp = thc->block->frag; *cp != '\0'; ++cp)
-        {
-            if (need_indent)
-            {
-                need_indent = FALSE;
-                prIndent(indent, fp);
-            }
-
-            fprintf(fp, "%c", *cp);
-
-            if (*cp == '\n')
-                need_indent = TRUE;
-        }
-
-        thc = thc->next;
-    }
-}
-
-
-/*
- * Generate the type hints for a class.
- */
-static void pyiClass(sipSpec *pt, moduleDef *mod, classDef *cd,
-        ifaceFileList **defined, int indent, FILE *fp)
-{
-    int first, no_body, nr_overloads;
-    classDef *nested;
-    ctorDef *ct;
-    memberDef *md;
-    propertyDef *pd;
-
-    nr_overloads = 0;
-
-    if (!isHiddenNamespace(cd))
-    {
-        separate(TRUE, indent, fp);
-        prIndent(indent, fp);
-        fprintf(fp, "class %s(", cd->pyname->text);
-
-        if (cd->supers != NULL)
-        {
-            classList *cl;
-
-            for (cl = cd->supers; cl != NULL; cl = cl->next)
-            {
-                if (cl != cd->supers)
-                    fprintf(fp, ", ");
-
-                prClassRef(cl->cd, mod, *defined, TRUE, fp);
-            }
-        }
-        else if (cd->supertype != NULL)
-        {
-            /*
-             * This is a hack to correct the .pyi file when the supertype
-             * hasn't been given a fully qualified name (ie. with ABI v12).
-             */
-            if (sipName != NULL && strncmp(cd->supertype->text, "sip.", 4) == 0)
-                fprintf(fp, "%s.%s", sipName, &cd->supertype->text[4]);
-            else
-                fprintf(fp, "%s", cd->supertype->text);
-        }
-        else
-        {
-            fprintf(fp, "%s.%swrapper", (sipName != NULL ? sipName : "sip"),
-                    (cd->iff->type == namespace_iface ? "simple" : ""));
-        }
-
-        /* See if there is anything in the class body. */
-        for (ct = cd->ctors; ct != NULL; ct = ct->next)
-        {
-            if (isPrivateCtor(ct))
-                continue;
-
-            if (ct->no_typehint)
-                continue;
-
-            ++nr_overloads;
-        }
-
-        no_body = (cd->typehintcode == NULL && nr_overloads == 0);
-
-        if (no_body)
-        {
-            overDef *od;
-
-            for (od = cd->overs; od != NULL; od = od->next)
-            {
-                if (isPrivate(od))
-                    continue;
-
-                if (od->no_typehint)
-                    continue;
-
-                no_body = FALSE;
-                break;
-            }
-        }
-
-        if (no_body)
-        {
-            enumDef *ed;
-
-            for (ed = pt->enums; ed != NULL; ed = ed->next)
-            {
-                if (ed->no_typehint)
-                    continue;
-
-                if (ed->ecd == cd)
-                {
-                    no_body = FALSE;
-                    break;
-                }
-            }
-        }
-
-        if (no_body)
-        {
-            for (nested = pt->classes; nested != NULL; nested = nested->next)
-            {
-                if (nested->no_typehint)
-                    continue;
-
-                if (nested->ecd == cd)
-                {
-                    no_body = FALSE;
-                    break;
-                }
-            }
-        }
-
-        if (no_body)
-        {
-            varDef *vd;
-
-            for (vd = pt->vars; vd != NULL; vd = vd->next)
-            {
-                if (vd->no_typehint)
-                    continue;
-
-                if (vd->ecd == cd)
-                {
-                    no_body = FALSE;
-                    break;
-                }
-            }
-        }
-
-        fprintf(fp, "):%s\n", (no_body ? " ..." : ""));
-
-        ++indent;
-
-        pyiTypeHintCode(cd->typehintcode, indent, fp);
-    }
-
-    pyiEnums(pt, mod, cd->iff, *defined, indent, fp);
-
-    /* Handle any nested classes. */
-    for (nested = pt->classes; nested != NULL; nested = nested->next)
-        if (nested->ecd == cd && !nested->no_typehint)
-            pyiClass(pt, mod, nested, defined, indent, fp);
-
-    pyiVars(pt, mod, cd, *defined, indent, fp);
-
-    first = TRUE;
-
-    for (ct = cd->ctors; ct != NULL; ct = ct->next)
-    {
-        int overloaded;
-
-        if (isPrivateCtor(ct))
-            continue;
-
-        if (ct->no_typehint)
-            continue;
-
-        overloaded = (nr_overloads > 1);
-
-        first = separate(first, indent, fp);
-
-        pyiCtor(pt, mod, NULL, ct, overloaded, *defined, indent, fp);
-    }
-
-    first = TRUE;
-
-    for (md = cd->members; md != NULL; md = md->next)
-    {
-        first = separate(first, indent, fp);
-
-        pyiCallable(pt, mod, md, cd->overs, !isHiddenNamespace(cd), *defined,
-                indent, fp);
-    }
-
-    for (pd = cd->properties; pd != NULL; pd = pd->next)
-    {
-        first = separate(first, indent, fp);
-
-        if (pd->get != NULL)
-        {
-            if ((md = findMethod(cd, pd->get)) != NULL)
-            {
-                pyiProperty(pt, mod, pd, FALSE, md, cd->overs, *defined,
-                        indent, fp);
-
-                if (pd->set != NULL)
-                {
-                    if ((md = findMethod(cd, pd->set)) != NULL)
-                        pyiProperty(pt, mod, pd, TRUE, md, cd->overs, *defined,
-                                indent, fp);
-                }
-            }
-        }
-    }
-
-    if (!isHiddenNamespace(cd))
-    {
-        /*
-         * Keep track of what has been defined so that forward references are
-         * no longer required.
-         */
-        appendToIfaceFileList(defined, cd->iff);
-    }
-}
-
-
-/*
- * Generate the type hints for a mapped type.
- */
-static void pyiMappedType(sipSpec *pt, moduleDef *mod, mappedTypeDef *mtd,
-        ifaceFileList **defined, int indent, FILE *fp)
-{
-    int first, no_body;
-    memberDef *md;
-
-    /* See if there is anything in the mapped type body. */
-    no_body = (mtd->members == NULL);
-
-    if (no_body)
-    {
-        enumDef *ed;
-
-        for (ed = pt->enums; ed != NULL; ed = ed->next)
-        {
-            if (ed->no_typehint)
-                continue;
-
-            if (ed->emtd == mtd)
-            {
-                no_body = FALSE;
-                break;
-            }
-        }
-    }
-
-    if (!no_body)
-    {
-        separate(TRUE, indent, fp);
-        prIndent(indent, fp);
-        fprintf(fp, "class %s(%s.wrapper):\n", mtd->pyname->text, (sipName != NULL ? sipName : "sip"));
-
-        ++indent;
-
-        pyiEnums(pt, mod, mtd->iff, *defined, indent, fp);
-
-        first = TRUE;
-
-        for (md = mtd->members; md != NULL; md = md->next)
-        {
-            first = separate(first, indent, fp);
-
-            pyiCallable(pt, mod, md, mtd->overs, TRUE, *defined, indent, fp);
-        }
-    }
-
-    /*
-     * Keep track of what has been defined so that forward references are no
-     * longer required.
-     */
-    appendToIfaceFileList(defined, mtd->iff);
-}
 
 
 /*
  * Generate an ctor type hint.
  */
-void pyiCtor(sipSpec *pt, moduleDef *mod, classDef *cd, ctorDef *ct,
-        int overloaded, ifaceFileList *defined, int indent, FILE *fp)
+void pyiCtor(sipSpec *pt, moduleDef *mod, classDef *cd, ctorDef *ct, FILE *fp)
 {
-    int a, need_comma;
-
-    if (overloaded)
-    {
-        prIndent(indent, fp);
-        fprintf(fp, "@typing.overload\n");
-    }
+    int a, need_comma = FALSE;
 
-    prIndent(indent, fp);
-
-    if (cd == NULL)
-    {
-        fprintf(fp, "def __init__(self");
-        need_comma = TRUE;
-    }
-    else
-    {
-        prScopedPythonName(fp, cd->ecd, cd->pyname->text);
-        fprintf(fp, "(");
-        need_comma = FALSE;
-    }
+    prScopedPythonName(fp, cd->ecd, cd->pyname->text);
+    fprintf(fp, "(");
 
     for (a = 0; a < ct->pysig.nrArgs; ++a)
         need_comma = pyiArgument(pt, mod, &ct->pysig.args[a], a, FALSE,
-                need_comma, TRUE, TRUE, defined, ct->kwargs, (cd == NULL),
-                fp);
-
-    fprintf(fp, (cd == NULL) ? ") -> None: ...\n" : ")");
-}
-
-
-/*
- * Generate the APIs for all the enums in a scope.
- */
-static void pyiEnums(sipSpec *pt, moduleDef *mod, ifaceFileDef *scope,
-        ifaceFileList *defined, int indent, FILE *fp)
-{
-    enumDef *ed;
-
-    for (ed = pt->enums; ed != NULL; ed = ed->next)
-    {
-        enumMemberDef *emd;
-
-        if (ed->module != mod)
-            continue;
-
-        if (ed->no_typehint)
-            continue;
-
-        if (scope != NULL)
-        {
-            if ((ed->ecd == NULL || ed->ecd->iff != scope) && (ed->emtd == NULL || ed->emtd->iff != scope))
-                continue;
-        }
-        else if (ed->ecd != NULL || ed->emtd != NULL)
-        {
-            continue;
-        }
-
-        separate(TRUE, indent, fp);
-
-        if (ed->pyname != NULL)
-        {
-            const char *super = "int";
-
-            prIndent(indent, fp);
-
-            if (abiVersion >= ABI_13_0)
-            {
-                if (isEnumEnum(ed))
-                    super = "enum.Enum";
-                else if (isEnumFlag(ed))
-                    super = "enum.Flag";
-                else if (isEnumIntEnum(ed) || isEnumUIntEnum(ed))
-                    super = "enum.IntEnum";
-                else if (isEnumIntFlag(ed))
-                    super = "enum.IntFlag";
-            }
-
-            fprintf(fp, "class %s(%s):\n", ed->pyname->text, super);
-
-            ++indent;
-        }
-
-        for (emd = ed->members; emd != NULL; emd = emd->next)
-        {
-            if (emd->no_typehint)
-                continue;
-
-            prIndent(indent, fp);
-            fprintf(fp, "%s = ... # type: ", emd->pyname->text);
-
-            if (ed->pyname != NULL)
-                prScopedPythonName(fp, ed->ecd, ed->pyname->text);
-            else
-                fprintf(fp, "int");
-
-            fprintf(fp, "\n");
-        }
-
-        if (ed->pyname != NULL)
-            --indent;
-    }
-}
-
-
-/*
- * Generate the APIs for all the variables in a scope.
- */
-static void pyiVars(sipSpec *pt, moduleDef *mod, classDef *scope,
-        ifaceFileList *defined, int indent, FILE *fp)
-{
-    int first = TRUE;
-    varDef *vd;
-
-    for (vd = pt->vars; vd != NULL; vd = vd->next)
-    {
-        if (vd->module != mod)
-            continue;
-
-        if (vd->ecd != scope)
-            continue;
-
-        if (vd->no_typehint)
-            continue;
-
-        first = separate(first, indent, fp);
-
-        prIndent(indent, fp);
-        fprintf(fp, "%s = ... # type: ", vd->pyname->text);
-        pyiType(pt, mod, &vd->type, FALSE, defined, TRUE, fp);
-        fprintf(fp, "\n");
-    }
-}
+                need_comma, TRUE, TRUE, ct->kwargs, fp);
 
-
-/*
- * Generate the type hints for a callable.
- */
-static void pyiCallable(sipSpec *pt, moduleDef *mod, memberDef *md,
-        overDef *overloads, int is_method, ifaceFileList *defined, int indent,
-        FILE *fp)
-{
-    int nr_overloads;
-    overDef *od;
-
-    /* Count the number of overloads. */
-    nr_overloads = 0;
-
-    for (od = overloads; od != NULL; od = od->next)
-    {
-        if (isPrivate(od))
-            continue;
-
-        if (od->common != md)
-            continue;
-
-        if (od->no_typehint)
-            continue;
-
-        ++nr_overloads;
-    }
-
-    /* Handle each overload. */
-    for (od = overloads; od != NULL; od = od->next)
-    {
-        int overloaded;
-
-        if (isPrivate(od))
-            continue;
-
-        if (od->common != md)
-            continue;
-
-        if (od->no_typehint)
-            continue;
-
-        overloaded = (nr_overloads > 1);
-
-        pyiOverload(pt, mod, od, overloaded, is_method, defined, indent, TRUE,
-                fp);
-    }
-}
-
-
-/*
- * Generate the type hints for a property.
- */
-static void pyiProperty(sipSpec *pt, moduleDef *mod, propertyDef *pd,
-        int is_setter, memberDef *md, overDef *overloads,
-        ifaceFileList *defined, int indent, FILE *fp)
-{
-    overDef *od;
-
-    /* Handle each overload. */
-    for (od = overloads; od != NULL; od = od->next)
-    {
-        if (isPrivate(od))
-            continue;
-
-        if (od->common != md)
-            continue;
-
-        if (od->no_typehint)
-            continue;
-
-        prIndent(indent, fp);
-
-        if (is_setter)
-            fprintf(fp, "@%s.setter\n", pd->name->text);
-        else
-            fprintf(fp, "@property\n");
-
-        prIndent(indent, fp);
-
-        fprintf(fp, "def %s", pd->name->text);
-
-        pyiPythonSignature(pt, mod, &od->pysig, TRUE, defined, od->kwargs,
-                TRUE, fp);
-
-        fprintf(fp, ": ...\n");
-
-        break;
-    }
+    fprintf(fp, ")");
 }
 
 
 /*
  * Generate the type hints for a single API overload.
  */
-void pyiOverload(sipSpec *pt, moduleDef *mod, overDef *od, int overloaded,
-        int is_method, ifaceFileList *defined, int indent, int pep484,
+void pyiOverload(sipSpec *pt, moduleDef *mod, overDef *od, int is_method,
         FILE *fp)
 {
-    if (overloaded)
-    {
-        prIndent(indent, fp);
-        fprintf(fp, "@typing.overload\n");
-    }
-
-    if (pep484 && is_method && isStatic(od))
-    {
-        prIndent(indent, fp);
-        fprintf(fp, "@staticmethod\n");
-    }
-
-    prIndent(indent, fp);
-    fprintf(fp, "%s%s", (pep484 ? "def " : ""), od->common->pyname->text);
-
-    if (pep484 && (od->common->slot == eq_slot || od->common->slot == ne_slot))
-    {
-        /* mypy recommends using 'object' as the argument type. */
-        fprintf(fp, "(self, other: object)");
-    }
-    else
-    {
-        int need_self = (is_method && !isStatic(od));
-
-        pyiPythonSignature(pt, mod, &od->pysig, need_self, defined, od->kwargs,
-                pep484, fp);
-    }
+    int need_self = (is_method && !isStatic(od));
 
-    if (pep484)
-        fprintf(fp, ": ...\n");
+    fprintf(fp, "%s", od->common->pyname->text);
+    pyiPythonSignature(pt, mod, &od->pysig, need_self, od->kwargs, fp);
 }
 
 
@@ -813,8 +88,8 @@ void pyiOverload(sipSpec *pt, moduleDef *mod, overDef *od, int overloaded,
  * Generate a Python argument.
  */
 static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
-        int out, int need_comma, int names, int defaults,
-        ifaceFileList *defined, KwArgs kwargs, int pep484, FILE *fp)
+        int out, int need_comma, int names, int defaults, KwArgs kwargs,
+        FILE *fp)
 {
     int optional, use_optional;
 
@@ -826,13 +101,6 @@ static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
 
     optional = (defaults && ad->defval && !out);
 
-    /*
-     * We only show names for PEP 484 type hints and when they are part of the
-     * API.
-     */
-    if (names)
-        names = (pep484 || kwargs == AllKwArgs || (kwargs == OptionalKwArgs && optional));
-
     if (names && ad->atype != ellipsis_type)
     {
         if (ad->name != NULL)
@@ -844,7 +112,7 @@ static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
 
     use_optional = FALSE;
 
-    if (optional && pep484)
+    if (optional)
     {
         /* Assume pointers can be None unless specified otherwise. */
         if (isAllowNone(ad) || (!isDisallowNone(ad) && ad->nrderefs > 0))
@@ -857,7 +125,7 @@ static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
     if (isArray(ad))
         fprintf(fp, "%s.array[", (sipName != NULL) ? sipName : "sip");
 
-    pyiType(pt, mod, ad, out, defined, pep484, fp);
+    pyiType(pt, mod, ad, out, fp);
 
     if (names && ad->atype == ellipsis_type)
     {
@@ -878,10 +146,7 @@ static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
 
         fprintf(fp, " = ");
 
-        if (pep484)
-            fprintf(fp, "...");
-        else
-            prDefaultValue(ad, TRUE, fp);
+        prDefaultValue(ad, fp);
     }
 
     return TRUE;
@@ -891,7 +156,7 @@ static int pyiArgument(sipSpec *pt, moduleDef *mod, argDef *ad, int arg_nr,
 /*
  * Generate the default value of an argument.
  */
-void prDefaultValue(argDef *ad, int in_str, FILE *fp)
+void prDefaultValue(argDef *ad, FILE *fp)
 {
     /* Use any explicitly provided documentation. */
     if (ad->typehint_value != NULL)
@@ -918,7 +183,7 @@ void prDefaultValue(argDef *ad, int in_str, FILE *fp)
 
     /* SIP v5 won't need this. */
     prcode(fp, "%M");
-    generateExpression(ad->defval, in_str, fp);
+    generateExpression(ad->defval, TRUE, fp);
     prcode(fp, "%M");
 }
 
@@ -926,8 +191,7 @@ void prDefaultValue(argDef *ad, int in_str, FILE *fp)
 /*
  * Generate the Python representation of a type.
  */
-static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
-        ifaceFileList *defined, int pep484, FILE *fp)
+static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out, FILE *fp)
 {
     const char *type_name, *sip_name;
     typeHintDef *thd;
@@ -937,7 +201,7 @@ static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
 
     if (thd != NULL)
     {
-        pyiTypeHint(pt, thd, mod, out, defined, pep484, FALSE, fp);
+        pyiTypeHint(pt, thd, mod, out, fp);
         return;
     }
 
@@ -948,7 +212,7 @@ static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
     switch (ad->atype)
     {
     case class_type:
-        prClassRef(ad->u.cd, mod, defined, pep484, fp);
+        prClassRef(ad->u.cd, fp);
         break;
 
     case mapped_type:
@@ -956,12 +220,12 @@ static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
          * This should never happen as it should have been picked up when
          * generating code - but maybe we haven't been asked to generate code.
          */
-        fprintf(fp, anyObject(pep484));
+        fprintf(fp, "object");
         break;
 
     case enum_type:
         if (ad->u.ed->pyname != NULL)
-            prEnumRef(ad->u.ed, mod, defined, pep484, fp);
+            prEnumRef(ad->u.ed, fp);
         else
             type_name = "int";
 
@@ -1020,23 +284,23 @@ static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
         break;
 
     case pyobject_type:
-        type_name = anyObject(pep484);
+        type_name = "object";
         break;
 
     case pytuple_type:
-        type_name = (pep484 ? "typing.Tuple" : "Tuple");
+        type_name = "Tuple";
         break;
 
     case pylist_type:
-        type_name = (pep484 ? "typing.List" : "List");
+        type_name = "List";
         break;
 
     case pydict_type:
-        type_name = (pep484 ? "typing.Dict" : "Dict");
+        type_name = "Dict";
         break;
 
     case pycallable_type:
-        type_name = (pep484 ? "typing.Callable[..., None]" : "Callable[..., None]");
+        type_name = "Callable[..., None]";
         break;
 
     case pyslice_type:
@@ -1048,12 +312,8 @@ static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
         break;
 
     case pybuffer_type:
-        if (pep484)
-            fprintf(fp, "%s.Buffer", sip_name);
-        else
-            /* This replicates sip.pyi. */
-            fprintf(fp, "Union[bytes, bytearray, memoryview, %s.array, %s.voidptr]", sip_name, sip_name);
-
+        /* This replicates sip.pyi. */
+        fprintf(fp, "Union[bytes, bytearray, memoryview, %s.array, %s.voidptr]", sip_name, sip_name);
         break;
 
     case pyenum_type:
@@ -1065,7 +325,7 @@ static void pyiType(sipSpec *pt, moduleDef *mod, argDef *ad, int out,
         break;
 
     default:
-        type_name = anyObject(pep484);
+        type_name = "object";
     }
 
     if (type_name != NULL)
@@ -1093,8 +353,7 @@ void prScopedPythonName(FILE *fp, classDef *scope, const char *pyname)
  * Generate a Python signature.
  */
 static void pyiPythonSignature(sipSpec *pt, moduleDef *mod, signatureDef *sd,
-        int need_self, ifaceFileList *defined, KwArgs kwargs, int pep484,
-        FILE *fp)
+        int need_self, KwArgs kwargs, FILE *fp)
 {
     int void_return, need_comma, is_res, nr_out, a;
 
@@ -1122,7 +381,7 @@ static void pyiPythonSignature(sipSpec *pt, moduleDef *mod, signatureDef *sd,
             continue;
 
         need_comma = pyiArgument(pt, mod, ad, a, FALSE, need_comma, TRUE, TRUE,
-                defined, kwargs, pep484, fp);
+                kwargs, fp);
     }
 
     fprintf(fp, ")");
@@ -1141,11 +400,11 @@ static void pyiPythonSignature(sipSpec *pt, moduleDef *mod, signatureDef *sd,
         fprintf(fp, " -> ");
 
         if ((is_res && nr_out > 0) || nr_out > 1)
-            fprintf(fp, "%sTuple[", (pep484 ? "typing." : ""));
+            fprintf(fp, "Tuple[");
 
         if (is_res)
             need_comma = pyiArgument(pt, mod, &sd->result, -1, TRUE, FALSE,
-                    FALSE, FALSE, defined, kwargs, pep484, fp);
+                    FALSE, FALSE, kwargs, fp);
         else
             need_comma = FALSE;
 
@@ -1156,38 +415,12 @@ static void pyiPythonSignature(sipSpec *pt, moduleDef *mod, signatureDef *sd,
             if (isOutArg(ad))
                 /* We don't want the name in the result tuple. */
                 need_comma = pyiArgument(pt, mod, ad, -1, TRUE, need_comma,
-                        FALSE, FALSE, defined, kwargs, pep484, fp);
+                        FALSE, FALSE, kwargs, fp);
         }
 
         if ((is_res && nr_out > 0) || nr_out > 1)
             fprintf(fp, "]");
     }
-    else if (pep484)
-    {
-        fprintf(fp, " -> None");
-    }
-}
-
-
-/*
- * Generate the required indentation.
- */
-static void prIndent(int indent, FILE *fp)
-{
-    while (indent--)
-        fprintf(fp, "    ");
-}
-
-
-/*
- * Generate a newline if not already done.
- */
-static int separate(int first, int indent, FILE *fp)
-{
-    if (first)
-        fprintf(fp, (indent ? "\n" : "\n\n"));
-
-    return FALSE;
 }
 
 
@@ -1195,32 +428,9 @@ static int separate(int first, int indent, FILE *fp)
  * Generate a class reference, including its owning module if necessary and
  * handling forward references if necessary.
  */
-static void prClassRef(classDef *cd, moduleDef *mod, ifaceFileList *defined,
-        int pep484, FILE *fp)
+static void prClassRef(classDef *cd, FILE *fp)
 {
-    if (pep484)
-    {
-        /*
-         * We assume that an external class will be handled properly by some
-         * handwritten type hint code.
-         */
-        int is_defined = (isExternal(cd) || isDefined(cd->iff, cd->ecd, mod, defined));
-
-        if (!is_defined)
-            fprintf(fp, "'");
-
-        if (cd->iff->module != mod)
-            fprintf(fp, "%s.", cd->iff->module->name);
-
-        prScopedPythonName(fp, cd->ecd, cd->pyname->text);
-
-        if (!is_defined)
-            fprintf(fp, "'");
-    }
-    else
-    {
-        prScopedPythonName(fp, cd->ecd, cd->pyname->text);
-    }
+    prScopedPythonName(fp, cd->ecd, cd->pyname->text);
 }
 
 
@@ -1228,42 +438,9 @@ static void prClassRef(classDef *cd, moduleDef *mod, ifaceFileList *defined,
  * Generate an enum reference, including its owning module if necessary and
  * handling forward references if necessary.
  */
-static void prEnumRef(enumDef *ed, moduleDef *mod, ifaceFileList *defined,
-        int pep484, FILE *fp)
+static void prEnumRef(enumDef *ed, FILE *fp)
 {
-    if (pep484)
-    {
-        int is_defined;
-
-        if (ed->ecd != NULL)
-        {
-            is_defined = isDefined(ed->ecd->iff, ed->ecd->ecd, mod, defined);
-        }
-        else if (ed->emtd != NULL)
-        {
-            is_defined = isDefined(ed->emtd->iff, NULL, mod, defined);
-        }
-        else
-        {
-            /* Global enums are defined early on. */
-            is_defined = TRUE;
-        }
-
-        if (!is_defined)
-            fprintf(fp, "'");
-
-        if (ed->module != mod)
-            fprintf(fp, "%s.", ed->module->name);
-
-        prScopedEnumName(fp, ed);
-
-        if (!is_defined)
-            fprintf(fp, "'");
-    }
-    else
-    {
-        prScopedEnumName(fp, ed);
-    }
+    prScopedEnumName(fp, ed);
 }
 
 
@@ -1279,75 +456,31 @@ static void prScopedEnumName(FILE *fp, enumDef *ed)
 }
 
 
-/*
- * Check if a type has been defined.
- */
-static int isDefined(ifaceFileDef *iff, classDef *scope, moduleDef *mod,
-        ifaceFileList *defined)
-{
-    /* A type in another module would have been imported. */
-    if (iff->module != mod)
-        return TRUE;
-
-    if (!inIfaceFileList(iff, defined))
-        return FALSE;
-
-    /* Check all enclosing scopes have been defined as well. */
-    while (scope != NULL)
-    {
-        if (!inIfaceFileList(scope->iff, defined))
-            return FALSE;
-
-        scope = scope->ecd;
-    }
-
-    return TRUE;
-}
-
-
-/*
- * Check if an interface file appears in a list of them.
- */
-static int inIfaceFileList(ifaceFileDef *iff, ifaceFileList *defined)
-{
-    while (defined != NULL)
-    {
-        if (defined->iff == iff)
-            return TRUE;
-
-        defined = defined->next;
-    }
-
-    return FALSE;
-}
-
-
 /*
  * Generate a type hint from a /TypeHint/ annotation.
  */
 void pyiTypeHint(sipSpec *pt, typeHintDef *thd, moduleDef *mod, int out,
-        ifaceFileList *defined, int pep484, int rest, FILE *fp)
+        FILE *fp)
 {
     parseTypeHint(pt, thd, out);
 
     if (thd->root != NULL)
-        pyiTypeHintNode(thd->root, mod, defined, pep484, rest, fp);
+        pyiTypeHintNode(thd->root, mod, fp);
     else
-        maybeAnyObject(thd->raw_hint, pep484, fp);
+        maybeAnyObject(thd->raw_hint, fp);
 }
 
 
 /*
  * Generate a single node of a type hint.
  */
-static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod,
-        ifaceFileList *defined, int pep484, int rest, FILE *fp)
+static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod, FILE *fp)
 {
     switch (node->type)
     {
     case typing_node:
         if (node->u.name != NULL)
-            fprintf(fp, "%s%s", (pep484 ? "typing." : ""), node->u.name);
+            fprintf(fp, "%s", node->u.name);
 
         if (node->children != NULL)
         {
@@ -1363,7 +496,7 @@ static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod,
 
                 need_comma = TRUE;
 
-                pyiTypeHintNode(thnd, mod, defined, pep484, rest, fp);
+                pyiTypeHintNode(thnd, mod, fp);
             }
 
             fprintf(fp, "]");
@@ -1372,23 +505,15 @@ static void pyiTypeHintNode(typeHintNodeDef *node, moduleDef *mod,
         break;
 
     case class_node:
-        if (rest)
-            restPyClass(node->u.cd, fp);
-        else
-            prClassRef(node->u.cd, mod, defined, pep484, fp);
-
+        prClassRef(node->u.cd, fp);
         break;
 
     case enum_node:
-        if (rest)
-            restPyEnum(node->u.ed, fp);
-        else
-            prEnumRef(node->u.ed, mod, defined, pep484, fp);
-
+        prEnumRef(node->u.ed, fp);
         break;
 
     case other_node:
-        maybeAnyObject(node->u.name, pep484, fp);
+        maybeAnyObject(node->u.name, fp);
         break;
     }
 }
@@ -1860,9 +985,9 @@ static classDef *lookupClass(sipSpec *pt, const char *name, classDef *scope_cd)
 /*
  * Generate a hint taking into account that it may be any sort of object.
  */
-static void maybeAnyObject(const char *hint, int pep484, FILE *fp)
+static void maybeAnyObject(const char *hint, FILE *fp)
 {
-    fprintf(fp, "%s", (strcmp(hint, "Any") != 0 ? hint : anyObject(pep484)));
+    fprintf(fp, "%s", (strcmp(hint, "Any") != 0 ? hint : "object"));
 }
 
 
@@ -1890,54 +1015,3 @@ static int isPyKeyword(const char *word)
 
     return FALSE;
 }
-
-
-/*
- * Add an interface file to an interface file list if it isn't already there.
- */
-static void appendToIfaceFileList(ifaceFileList **ifflp, ifaceFileDef *iff)
-{
-    /* Make sure we don't try to add an interface file to its own list. */
-    if (&iff->used != ifflp)
-    {
-        ifaceFileList *iffl;
-
-        while ((iffl = *ifflp) != NULL)
-        {
-            /* Don't bother if it is already there. */
-            if (iffl->iff == iff)
-                return;
-
-            ifflp = &iffl -> next;
-        }
-
-        iffl = sipMalloc(sizeof (ifaceFileList));
-
-        iffl->iff = iff;
-        iffl->next = NULL;
-
-        *ifflp = iffl;
-    }
-}
-
-
-/*
- * Generate a fully qualified class name as a reST reference.
- */
-void restPyClass(classDef *cd, FILE *fp)
-{
-    fprintf(fp, ":sip:ref:`~%s.", cd->iff->module->fullname->text);
-    prScopedPythonName(fp, cd->ecd, cd->pyname->text);
-    fprintf(fp, "`");
-}
-
-
-/*
- * Generate a fully qualified enum name as a reST reference.
- */
-void restPyEnum(enumDef *ed, FILE *fp)
-{
-    fprintf(fp, ":sip:ref:`~%s.", ed->module->fullname->text);
-    prScopedPythonName(fp, ed->ecd, ed->pyname->text);
-    fprintf(fp, "`");
-}
diff --git a/sip.egg-info/PKG-INFO b/sip.egg-info/PKG-INFO
index 48fe4c5..be2f1a9 100644
--- a/sip.egg-info/PKG-INFO
+++ b/sip.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: sip
-Version: 6.7.2
+Version: 6.7.3
 Summary: A Python bindings generator for C/C++ libraries
 Home-page: https://www.riverbankcomputing.com/software/sip/
 Author: Riverbank Computing Limited
diff --git a/sip.egg-info/SOURCES.txt b/sip.egg-info/SOURCES.txt
index c9aa2a1..262f31f 100644
--- a/sip.egg-info/SOURCES.txt
+++ b/sip.egg-info/SOURCES.txt
@@ -8,7 +8,6 @@ README
 pyproject.toml
 setup.cfg
 setup.py
-code_generator/export.c
 code_generator/gencode.c
 code_generator/heap.c
 code_generator/py2c.c
@@ -137,23 +136,25 @@ sipbuild/generator/python_slots.py
 sipbuild/generator/scoped_name.py
 sipbuild/generator/specification.py
 sipbuild/generator/templates.py
-sipbuild/generator/type_hints.py
 sipbuild/generator/utils.py
-sipbuild/generator/formatters/__init__.py
-sipbuild/generator/formatters/argument.py
-sipbuild/generator/formatters/base_formatter.py
-sipbuild/generator/formatters/enum.py
-sipbuild/generator/formatters/klass.py
-sipbuild/generator/formatters/overload.py
-sipbuild/generator/formatters/scoped.py
-sipbuild/generator/formatters/signature.py
-sipbuild/generator/formatters/template.py
-sipbuild/generator/formatters/utils.py
-sipbuild/generator/formatters/value_list.py
-sipbuild/generator/formatters/variable.py
 sipbuild/generator/outputs/__init__.py
 sipbuild/generator/outputs/api.py
 sipbuild/generator/outputs/extracts.py
+sipbuild/generator/outputs/pyi.py
+sipbuild/generator/outputs/type_hints.py
+sipbuild/generator/outputs/xml.py
+sipbuild/generator/outputs/formatters/__init__.py
+sipbuild/generator/outputs/formatters/argument.py
+sipbuild/generator/outputs/formatters/base_formatter.py
+sipbuild/generator/outputs/formatters/enum.py
+sipbuild/generator/outputs/formatters/klass.py
+sipbuild/generator/outputs/formatters/overload.py
+sipbuild/generator/outputs/formatters/scoped.py
+sipbuild/generator/outputs/formatters/signature.py
+sipbuild/generator/outputs/formatters/template.py
+sipbuild/generator/outputs/formatters/utils.py
+sipbuild/generator/outputs/formatters/value_list.py
+sipbuild/generator/outputs/formatters/variable.py
 sipbuild/generator/parser/__init__.py
 sipbuild/generator/parser/annotations.py
 sipbuild/generator/parser/parser.py
diff --git a/sipbuild/bindings.py b/sipbuild/bindings.py
index 1c254a4..7f0a40e 100644
--- a/sipbuild/bindings.py
+++ b/sipbuild/bindings.py
@@ -25,11 +25,11 @@ import os
 import sys
 
 from .buildable import BuildableBindings
-from .code_generator import generateCode, generateTypeHints, py2c
+from .code_generator import generateCode, py2c
 from .configurable import Configurable, Option
 from .exceptions import UserException
 from .generator import parse, resolve
-from .generator.outputs import output_api, output_extract
+from .generator.outputs import output_api, output_extract, output_pyi
 from .installable import Installable
 from .module import copy_nonshared_sources
 from .version import SIP_VERSION
@@ -165,7 +165,8 @@ class Bindings(Configurable):
         # Parse the input file.
         spec, sip_files = parse(self.sip_file, SIP_VERSION, encoding,
                 project.abi_version, self.tags, self.disabled_features,
-                self.protected_is_public, self._sip_include_dirs)
+                self.protected_is_public, self._sip_include_dirs,
+                project.sip_module or 'sip')
 
         # Resolve the types.
         resolve(spec)
@@ -195,7 +196,7 @@ class Bindings(Configurable):
             project.progress(
                     "Generating the {0} .api file".format(buildable.target))
 
-            output_api(spec, module,
+            output_api(spec,
                     os.path.join(project.build_dir, buildable.target + '.api'))
 
         # Generate any extracts.
@@ -210,7 +211,7 @@ class Bindings(Configurable):
             pyi_path = os.path.join(buildable.build_dir,
                     buildable.target + '.pyi')
 
-            generateTypeHints(pt, pyi_path)
+            output_pyi(spec, pyi_path)
 
             installable = Installable('pyi',
                     target_subdir=buildable.get_install_subdir())
diff --git a/sipbuild/generator/instantiations.py b/sipbuild/generator/instantiations.py
index 342ad0e..7885d0d 100644
--- a/sipbuild/generator/instantiations.py
+++ b/sipbuild/generator/instantiations.py
@@ -28,7 +28,6 @@ from .specification import (Argument, ArgumentType, FunctionCall,
         IfaceFileType, KwArgs, Signature, TypeHints, Value, ValueType)
 from .templates import (template_code, template_code_blocks,
         template_expansions, template_string)
-from .type_hints import get_type_hint
 from .utils import append_iface_file, cached_name, normalised_scoped_name
 
 
@@ -391,16 +390,14 @@ def instantiate_type_hints(spec, proto_type_hints, expansions):
     """ Return an instantiated TypeHints object. """
 
     if proto_type_hints.hint_in is not None:
-        hint_in = get_type_hint(spec,
-                template_string(proto_type_hints.hint_in.text, expansions,
-                        scope_replacement='.'))
+        hint_in = template_string(proto_type_hints.hint_in, expansions,
+                        scope_replacement='.')
     else:
         hint_in = None
 
     if proto_type_hints.hint_out is not None:
-        hint_out = get_type_hint(spec,
-                template_string(proto_type_hints.hint_out.text, expansions,
-                        scope_replacement='.'))
+        hint_out = template_string(proto_type_hints.hint_out, expansions,
+                        scope_replacement='.')
     else:
         hint_out = None
 
diff --git a/sipbuild/generator/outputs/__init__.py b/sipbuild/generator/outputs/__init__.py
index d34eb36..c2bada1 100644
--- a/sipbuild/generator/outputs/__init__.py
+++ b/sipbuild/generator/outputs/__init__.py
@@ -24,3 +24,4 @@
 # Publish the API.  This is private to the rest of sip.
 from .api import output_api
 from .extracts import output_extract
+from .pyi import output_pyi
diff --git a/sipbuild/generator/outputs/api.py b/sipbuild/generator/outputs/api.py
index 05e2ce1..4a5fde0 100644
--- a/sipbuild/generator/outputs/api.py
+++ b/sipbuild/generator/outputs/api.py
@@ -23,10 +23,11 @@
 
 from enum import IntEnum
 
-from ..formatters import (ClassFormatter, EnumFormatter, OverloadFormatter,
-        SignatureFormatter, VariableFormatter)
 from ..specification import AccessSpecifier
 
+from .formatters import (ClassFormatter, EnumFormatter, OverloadFormatter,
+        SignatureFormatter, VariableFormatter)
+
 
 class IconNumber(IntEnum):
     """ The numbers of the different icons.  The values are those used by the
@@ -39,32 +40,34 @@ class IconNumber(IntEnum):
     ENUM = 10
 
 
-def output_api(spec, module, api_filename):
+def output_api(spec, api_filename):
     """ Output a QScintilla API file. """
 
     with open(api_filename, 'w') as af:
-        _api_enums(af, spec, module)
-        _api_variables(af, spec, module)
+        module = spec.modules[0]
+
+        _enums(af, spec, module)
+        _variables(af, spec, module)
 
         for overload in module.overloads:
             if overload.common.module is module and overload.common.py_slot is None:
-                _api_overload(af, spec, module, overload)
+                _overload(af, spec, module, overload)
 
         for klass in spec.classes:
             if klass.iface_file.module is module and not klass.external:
-                _api_enums(af, spec, module, scope=klass)
-                _api_variables(af, spec, module, scope=klass)
+                _enums(af, spec, module, scope=klass)
+                _variables(af, spec, module, scope=klass)
 
                 for ctor in klass.ctors:
                     if ctor.access_specifier is not AccessSpecifier.PRIVATE:
-                        _api_ctor(af, spec, module, ctor, klass)
+                        _ctor(af, spec, module, ctor, klass)
 
                 for overload in klass.overloads:
                     if overload.access_specifier is not AccessSpecifier.PRIVATE and overload.common.py_slot is None:
-                        _api_overload(af, spec, module, overload, scope=klass)
+                        _overload(af, spec, module, overload, scope=klass)
 
 
-def _api_ctor(af, spec, module, ctor, scope):
+def _ctor(af, spec, module, ctor, scope):
     """ Generate an API ctor. """
 
     py_class = module.py_name + '.' + ClassFormatter(spec, scope).fq_py_name
@@ -82,7 +85,7 @@ def _api_ctor(af, spec, module, ctor, scope):
     af.write(f'{py_class}.__init__?{IconNumber.CLASS}({py_arguments})\n')
 
 
-def _api_enums(af, spec, module, scope=None):
+def _enums(af, spec, module, scope=None):
     """ Generate the APIs for all the enums in a scope. """
 
     for enum in spec.enums:
@@ -96,7 +99,7 @@ def _api_enums(af, spec, module, scope=None):
                 af.write(f'{member_s}?{IconNumber.ENUM}\n')
 
 
-def _api_variables(af, spec, module, scope=None):
+def _variables(af, spec, module, scope=None):
     """ Generate the APIs for all the variables in a scope. """
 
     for variable in spec.variables:
@@ -106,7 +109,7 @@ def _api_variables(af, spec, module, scope=None):
             af.write(f'{module.py_name}.{formatter.fq_py_name}?{IconNumber.VARIABLE}\n')
 
 
-def _api_overload(af, spec, module, overload, scope=None):
+def _overload(af, spec, module, overload, scope=None):
     """ Generate a single API overload. """
 
     sig_formatter = SignatureFormatter(spec, overload.py_signature)
diff --git a/sipbuild/generator/formatters/__init__.py b/sipbuild/generator/outputs/formatters/__init__.py
similarity index 91%
rename from sipbuild/generator/formatters/__init__.py
rename to sipbuild/generator/outputs/formatters/__init__.py
index 562c8fd..0d4afcb 100644
--- a/sipbuild/generator/formatters/__init__.py
+++ b/sipbuild/generator/outputs/formatters/__init__.py
@@ -22,8 +22,11 @@
 
 
 # Publish the API.  This is private to the rest of sip.
+from .argument import ArgumentFormatter
 from .enum import EnumFormatter
 from .klass import ClassFormatter
 from .overload import OverloadFormatter
 from .signature import SignatureFormatter
+from .utils import format_copying, format_scoped_py_name
+from .value_list import ValueListFormatter
 from .variable import VariableFormatter
diff --git a/sipbuild/generator/formatters/argument.py b/sipbuild/generator/outputs/formatters/argument.py
similarity index 70%
rename from sipbuild/generator/formatters/argument.py
rename to sipbuild/generator/outputs/formatters/argument.py
index 6d518f7..f060e85 100644
--- a/sipbuild/generator/formatters/argument.py
+++ b/sipbuild/generator/outputs/formatters/argument.py
@@ -21,8 +21,10 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
-from ..scoped_name import STRIP_NONE
-from ..specification import ArgumentType, ArrayArgument, ClassKey, ValueType
+from ...scoped_name import STRIP_NONE
+from ...specification import ArgumentType, ArrayArgument, ClassKey, ValueType
+
+from ..type_hints import TypeHintManager
 
 from .base_formatter import BaseFormatter
 from .utils import format_scoped_py_name
@@ -32,8 +34,8 @@ class ArgumentFormatter(BaseFormatter):
     """ This creates various string representations of an argument. """
 
     def cpp_type(self, *, name=None, scope=None, strip=STRIP_NONE,
-            use_typename=True, as_xml=False):
-        """ Return the C++ representation of the argument type. """
+            make_public=False, use_typename=True, as_xml=False):
+        """ Return the argument as a C++ type. """
 
         arg = self.object
 
@@ -61,7 +63,7 @@ class ArgumentFormatter(BaseFormatter):
             if arg.type is ArgumentType.FUNCTION:
                 s += ArgumentFormatter(self.spec,
                         arg.definition.result).cpp_type(scope=scope,
-                                strip=strip)
+                                strip=strip, as_xml=as_xml)
 
                 s += ' (' + '*' * nr_derefs + name + ')('
 
@@ -158,7 +160,8 @@ class ArgumentFormatter(BaseFormatter):
 
             elif arg.type is ArgumentType.MAPPED:
                 s += ArgumentFormatter(self.spec,
-                        arg.definition.type).cpp_type(scope=scope, strip=strip)
+                        arg.definition.type).cpp_type(scope=scope, strip=strip,
+                                as_xml=as_xml)
 
             elif arg.type is ArgumentType.CLASS:
                 from .klass import ClassFormatter
@@ -167,18 +170,19 @@ class ArgumentFormatter(BaseFormatter):
                     s += 'union ' if arg.definition.class_key is ClassKey.UNION else 'struct '
 
                 s += ClassFormatter(self.spec, arg.definition).scoped_name(
-                        scope=scope, strip=strip, as_xml=as_xml)
+                        scope=scope, strip=strip, make_public=make_public,
+                        as_xml=as_xml)
 
             elif arg.type is ArgumentType.TEMPLATE:
                 from .template import TemplateFormatter
 
-                s += TemplateFormatter(self.spec, arg.definition).cpp_type(
-                        scope=scope, strip=strip, as_xml=as_xml)
+                s += TemplateFormatter(self.spec, arg.definition, scope).cpp_type(
+                        strip=strip, as_xml=as_xml)
 
             elif arg.type is ArgumentType.ENUM:
                 enum = arg.definition
 
-                if enum.fq_cpp_name is None or enum.is_protected:
+                if enum.fq_cpp_name is None or (enum.is_protected and not make_public):
                     s += 'int'
                 else:
                     s += enum.fq_cpp_name.cpp_stripped(strip)
@@ -199,7 +203,7 @@ class ArgumentFormatter(BaseFormatter):
                 space_before_name = True
 
         if is_reference:
-            s += '&' if as_xml else '&'
+            s += '&'
 
         if name:
             if space_before_name:
@@ -209,12 +213,37 @@ class ArgumentFormatter(BaseFormatter):
 
         return s
 
-    def py_type(self, default_value=False):
-        """ Return the Python representation of the argument type. """
+    def py_default_value(self, embedded=False):
+        """ Return the Python representation of the argument's default value.
+        """
+
+        from .value_list import ValueListFormatter
+
+        arg = self.object
+
+        # Use any explicitly provided documentation.
+        if arg.type_hints is not None and arg.type_hints.default_value is not None:
+            return arg.type_hints.default_value
+
+        # Translate some special cases.
+        if len(arg.default_value) == 1 and arg.default_value[0].value_type is ValueType.NUMERIC:
+            value = arg.default_value[0].value
+
+            if len(arg.derefs) > 0 and value == 0:
+                return 'None'
+
+            if arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL):
+                return 'True' if value else 'False'
+
+        return ValueListFormatter(self.spec, arg.default_value).py_expression(
+                embedded=embedded)
+
+    def as_py_type(self, pep484=False, default_value=False):
+        """ Return the argument as a Python type. """
 
         arg = self.object
 
-        scope, name = self._py_arg()
+        scope, name = self._py_arg(pep484)
 
         s = format_scoped_py_name(scope, name)
 
@@ -222,15 +251,98 @@ class ArgumentFormatter(BaseFormatter):
             if arg.name is not None:
                 s += ' ' + arg.name.name
 
-            s += '=' + self._py_default_value()
+            s += '=' + self.py_default_value()
+
+        return s
+
+    def as_rest_ref(self, out):
+        """ Return the argument as a reST reference. """
+
+        arg = self.object
+
+        s = ''
+
+        hint = self._get_hint(out)
+
+        if hint is None:
+            if arg.type is ArgumentType.CLASS:
+                from .klass import ClassFormatter
+
+                s += ClassFormatter(self.spec, arg.definition).as_rest_ref()
+            elif arg.type is ArgumentType.ENUM:
+                if arg.definition.py_name is not None:
+                    from .enum import EnumFormatter
+
+                    s += EnumFormatter(self.spec, arg.definition).as_rest_ref()
+                else:
+                    s += 'int'
+            elif arg.type is ArgumentType.MAPPED:
+                # There would normally be a type hint.
+                s += "unknown-type"
+            else:
+                s += self.as_py_type()
+        else:
+            s += TypeHintManager(self.spec).as_rest_ref(hint, out)
 
         return s
 
-    def _py_arg(self):
+    def as_type_hint(self, module, out, defined):
+        """ Return the argument as a type hint. """
+
+        arg = self.object
+
+        s = ''
+
+        hint = self._get_hint(out)
+
+        if hint is None:
+            if arg.type is ArgumentType.CLASS:
+                from .klass import ClassFormatter
+
+                s += ClassFormatter(self.spec, arg.definition).as_type_hint(
+                        module, defined)
+            elif arg.type is ArgumentType.ENUM:
+                if arg.definition.py_name is not None:
+                    from .enum import EnumFormatter
+
+                    s += EnumFormatter(self.spec, arg.definition).as_type_hint(
+                            module, defined)
+                else:
+                    s += 'int'
+            elif arg.type is ArgumentType.MAPPED:
+                # There would normally be a type hint.
+                s += 'typing.Any'
+            else:
+                s += self.as_py_type(pep484=True)
+        else:
+            s += TypeHintManager(self.spec).as_type_hint(hint, module, out,
+                    defined)
+
+        return s
+
+    def _get_hint(self, out):
+        """ Return the raw type hint. """
+
+        arg = self.object
+
+        # Use any explicit type hint unless the argument is constrained.
+        if arg.type_hints is None:
+            hint = None
+        elif out:
+            hint = arg.type_hints.hint_out
+        elif arg.is_constrained:
+            hint = None
+        else:
+            hint = arg.type_hints.hint_in
+
+        return hint
+
+    def _py_arg(self, pep484):
         """ Return an argument as a 2-tuple of scope and name. """
 
         type = self.object.type
         definition = self.object.definition
+        sip_module = self.spec.sip_module
 
         scope = None
         name = "unknown-type"
@@ -243,6 +355,13 @@ class ArgumentFormatter(BaseFormatter):
             if definition.py_name is not None:
                 name = definition.py_name.name
 
+        elif type is ArgumentType.ENUM:
+            if definition.py_name is None:
+                name = 'int'
+            else:
+                scope = definition.scope
+                name = definition.py_name.name
+
         elif type is ArgumentType.DEFINED:
             name = definition.as_py
 
@@ -250,19 +369,12 @@ class ArgumentFormatter(BaseFormatter):
             name = definition.base_name
 
         elif type in (ArgumentType.STRUCT, ArgumentType.UNION, ArgumentType.VOID):
-            name = 'sip.voidptr'
+            name = sip_module + '.voidptr'
 
-        elif type is ArgumentType.ENUM:
-            if definition.py_name is None:
-                name = 'int'
-            else:
-                scope = definition.scope
-                name = definition.py_name.name
-
-        elif type is ArgumentType.USTRING:
+        elif type in (ArgumentType.STRING, ArgumentType.SSTRING, ArgumentType.USTRING):
             name = 'bytes'
 
-        elif type in (ArgumentType.STRING, ArgumentType.SSTRING, ArgumentType.WSTRING, ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING):
+        elif type in (ArgumentType.WSTRING, ArgumentType.ASCII_STRING, ArgumentType.LATIN1_STRING, ArgumentType.UTF8_STRING):
             name = 'bytes' if self.object.array is ArrayArgument.ARRAY else 'str'
 
         elif type in (ArgumentType.BYTE, ArgumentType.SBYTE, ArgumentType.UBYTE, ArgumentType.USHORT, ArgumentType.UINT, ArgumentType.LONG, ArgumentType.LONGLONG, ArgumentType.ULONG, ArgumentType.ULONGLONG, ArgumentType.SHORT, ArgumentType.INT, ArgumentType.CINT, ArgumentType.SSIZE, ArgumentType.SIZE, ArgumentType.HASH):
@@ -275,19 +387,19 @@ class ArgumentFormatter(BaseFormatter):
             name = 'bool'
 
         elif type is ArgumentType.PYOBJECT:
-            name = 'object'
+            name = 'typing.Any' if pep484 else 'Any'
 
         elif type is ArgumentType.PYTUPLE:
-            name = 'tuple'
+            name = 'typing.Tuple' if pep484 else 'Tuple'
 
         elif type is ArgumentType.PYLIST:
-            name = 'list'
+            name = 'typing.List' if pep484 else 'List'
 
         elif type is ArgumentType.PYDICT:
-            name = 'dict'
+            name = 'typing.Dict' if pep484 else 'Dict'
 
         elif type is ArgumentType.PYCALLABLE:
-            name = 'callable'
+            name = 'typing.Callable[..., None]' if pep484 else 'Callable[..., None]'
 
         elif type is ArgumentType.PYSLICE:
             name = 'slice'
@@ -296,36 +408,16 @@ class ArgumentFormatter(BaseFormatter):
             name = 'type'
 
         elif type is ArgumentType.PYBUFFER:
-            name = 'buffer'
+            if pep484:
+                name = sip_module + '.Buffer'
+            else:
+                # This replicates sip.pyi.
+                name = f'Union[bytes, bytearray, memoryview, {sip_module}.array, {sip_module}.voidptr]'
 
         elif type is ArgumentType.PYENUM:
-            name = 'enum'
+            name = 'enum.Enum'
 
         elif type is ArgumentType.ELLIPSIS:
-            name = '...'
+            name = '*'
 
         return scope, name
-
-    def _py_default_value(self):
-        """ Return the Python representation of the argument's default value.
-        """
-
-        from .value_list import ValueListFormatter
-
-        arg = self.object
-
-        # Use any explicitly provided documentation.
-        if arg.type_hints is not None and arg.type_hints.default_value is not None:
-            return arg.type_hints.default_value
-
-        # Translate some special cases.
-        if len(arg.default_value) == 1 and arg.default_value[0].value_type is ValueType.NUMERIC:
-            value = arg.default_value[0].value
-
-            if len(arg.derefs) > 0 and value == 0:
-                return 'None'
-
-            if arg.type in (ArgumentType.BOOL, ArgumentType.CBOOL):
-                return 'True' if value else 'False'
-
-        return ValueListFormatter(self.spec, arg.default_value).py_expression
diff --git a/sipbuild/generator/formatters/base_formatter.py b/sipbuild/generator/outputs/formatters/base_formatter.py
similarity index 100%
rename from sipbuild/generator/formatters/base_formatter.py
rename to sipbuild/generator/outputs/formatters/base_formatter.py
diff --git a/sipbuild/generator/formatters/enum.py b/sipbuild/generator/outputs/formatters/enum.py
similarity index 50%
rename from sipbuild/generator/formatters/enum.py
rename to sipbuild/generator/outputs/formatters/enum.py
index eb6e5d8..889f3a6 100644
--- a/sipbuild/generator/formatters/enum.py
+++ b/sipbuild/generator/outputs/formatters/enum.py
@@ -21,8 +21,10 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
+from ...specification import IfaceFileType
+
 from .scoped import EmbeddedScopeFormatter
-from .utils import format_scoped_py_name
+from .utils import format_scoped_py_name, iface_is_defined
 
 
 class EnumFormatter(EmbeddedScopeFormatter):
@@ -44,3 +46,53 @@ class EnumFormatter(EmbeddedScopeFormatter):
 
         for member in enum.members:
             yield enum_name + member.py_name.name
+
+    def member_as_rest_ref(self, member):
+        """ Return the fully qualified Python name of a member as a reST
+        reference.
+        """
+
+        enum = self.object
+        module_name = enum.module.fq_py_name.name
+
+        if enum.py_name is None:
+            member_name = format_scoped_py_name(self.scope,
+                    member.py_name.name)
+
+            return f':sip:ref:`~{module_name}.{member_name}`'
+
+        enum_name = format_scoped_py_name(self.scope, enum.py_name.name)
+        member_name = member.py_name.name
+
+        return f':sip:ref:`~{module_name}.{enum_name}.{member_name}`'
+
+    def as_rest_ref(self):
+        """ Return the fully qualified Python name as a reST reference. """
+
+        enum = self.object
+        module_name = enum.module.fq_py_name.name
+        enum_name = format_scoped_py_name(self.scope, enum.py_name.name)
+
+        return f':sip:ref:`~{module_name}.{enum_name}`'
+
+    def as_type_hint(self, module, defined):
+        """ Return the type hint. """
+
+        enum = self.object
+
+        if self.scope is None:
+            # Global enums are defined early on.
+            is_defined = True
+        else:
+            scope_iface = self.scope.iface_file
+            outer_scope = self.scope.scope if scope_iface.type is IfaceFileType.CLASS else None
+
+            is_defined = iface_is_defined(scope_iface, outer_scope, module,
+                    defined)
+
+        quote = '' if is_defined else "'"
+
+        # Include the module name if it is not the current one.
+        module_name = enum.module.py_name + '.' if enum.module is not module else ''
+
+        return f'{quote}{module_name}{self.fq_py_name}{quote}'
diff --git a/sipbuild/generator/formatters/klass.py b/sipbuild/generator/outputs/formatters/klass.py
similarity index 63%
rename from sipbuild/generator/formatters/klass.py
rename to sipbuild/generator/outputs/formatters/klass.py
index d680431..6c41637 100644
--- a/sipbuild/generator/formatters/klass.py
+++ b/sipbuild/generator/outputs/formatters/klass.py
@@ -21,16 +21,27 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
-from ..scoped_name import STRIP_NONE
+from ...scoped_name import STRIP_NONE
 
 from .scoped import EmbeddedScopeFormatter
 from .template import TemplateFormatter
+from .utils import format_scoped_py_name, iface_is_defined
 
 
 class ClassFormatter(EmbeddedScopeFormatter):
     """ This creates various string representations of a class. """
 
-    def scoped_name(self, *, scope=None, strip=STRIP_NONE, as_xml=False):
+    def as_rest_ref(self):
+        """ Return the fully qualified Python name as a reST reference. """
+
+        klass = self.object
+        module_name = klass.iface_file.module.fq_py_name.name
+        klass_name = format_scoped_py_name(self.scope, klass.py_name.name)
+
+        return f':sip:ref:`~{module_name}.{klass_name}`'
+
+    def scoped_name(self, *, scope=None, strip=STRIP_NONE, make_public=False,
+            as_xml=False):
         """ Return an appropriately scoped class name. """
 
         klass = self.object
@@ -40,7 +51,7 @@ class ClassFormatter(EmbeddedScopeFormatter):
                     scope=scope, strip=strip, as_xml=as_xml)
 
         # Protected classes have to be explicitly scoped.
-        if klass.is_protected:
+        if klass.is_protected and not make_public:
             # This should never happen.
             if scope is None:
                 scope = klass.iface_file
@@ -48,3 +59,17 @@ class ClassFormatter(EmbeddedScopeFormatter):
             return 'sip{scope.fq_cpp_name.as_word}::sip{klass.iface_file.fq_cpp_name.base_name}'
 
         return klass.iface_file.fq_cpp_name.cpp_stripped(strip)
+
+    def as_type_hint(self, module, defined):
+        """ Return the type hint. """
+
+        klass = self.object
+
+        # We assume that an external class will be handled properly by some
+        # handwritten type hint code.
+        quote = '' if klass.external or iface_is_defined(klass.iface_file, klass.scope, module, defined) else "'"
+
+        # Include the module name if it is not the current one.
+        module_name = klass.iface_file.module.py_name + '.' if klass.iface_file.module is not module else ''
+
+        return f'{quote}{module_name}{self.fq_py_name}{quote}'
diff --git a/sipbuild/generator/formatters/overload.py b/sipbuild/generator/outputs/formatters/overload.py
similarity index 98%
rename from sipbuild/generator/formatters/overload.py
rename to sipbuild/generator/outputs/formatters/overload.py
index 3f0b0da..dc78106 100644
--- a/sipbuild/generator/formatters/overload.py
+++ b/sipbuild/generator/outputs/formatters/overload.py
@@ -21,7 +21,7 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
-from ..specification import PySlot
+from ...specification import PySlot
 
 from .scoped import ScopedFormatter
 from .utils import format_scoped_py_name
diff --git a/sipbuild/generator/formatters/scoped.py b/sipbuild/generator/outputs/formatters/scoped.py
similarity index 100%
rename from sipbuild/generator/formatters/scoped.py
rename to sipbuild/generator/outputs/formatters/scoped.py
diff --git a/sipbuild/generator/formatters/signature.py b/sipbuild/generator/outputs/formatters/signature.py
similarity index 80%
rename from sipbuild/generator/formatters/signature.py
rename to sipbuild/generator/outputs/formatters/signature.py
index 17be380..e9070fd 100644
--- a/sipbuild/generator/formatters/signature.py
+++ b/sipbuild/generator/outputs/formatters/signature.py
@@ -21,8 +21,8 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
-from ..scoped_name import STRIP_NONE
-from ..specification import ArgumentType, ArrayArgument
+from ...scoped_name import STRIP_NONE
+from ...specification import ArgumentType, ArrayArgument
 
 from .argument import ArgumentFormatter
 from .base_formatter import BaseFormatter
@@ -31,14 +31,17 @@ from .base_formatter import BaseFormatter
 class SignatureFormatter(BaseFormatter):
     """ This creates various string representations of a signature. """
 
-    def cpp_arguments(self, *, scope=None, strip=STRIP_NONE,
+    def cpp_arguments(self, *, scope=None, strip=STRIP_NONE, make_public=False,
             as_xml=False):
         """ Return the C++ representation of the signature arguments. """
 
         args = [ArgumentFormatter(self.spec, arg).cpp_type(scope=scope,
-                strip=strip, as_xml=as_xml) for arg in self.object.args]
+                strip=strip, make_public=make_public, as_xml=as_xml)
+                for arg in self.object.args]
 
-        return ', '.join(args)
+        # Note the lack of separating space (although this may be for XML only
+        # to be consistent with previous implementations).
+        return ','.join(args)
 
     @property
     def py_arguments(self):
@@ -50,7 +53,7 @@ class SignatureFormatter(BaseFormatter):
             if arg.array is not ArrayArgument.ARRAY_SIZE and arg.is_in:
                 args.append(
                         ArgumentFormatter(self.spec,
-                                arg).py_type(default_value=True))
+                                arg).as_py_type(default_value=True))
 
         return ', '.join(args)
 
@@ -65,10 +68,10 @@ class SignatureFormatter(BaseFormatter):
         if sig.result is not None:
             if sig.result.type is not ArgumentType.VOID or len(sig.result.derefs) != 0:
                 results.append(ArgumentFormatter(self.spec,
-                        sig.result).py_type())
+                        sig.result).as_py_type())
 
         for arg in sig.args:
             if arg.is_out:
-                results.append(ArgumentFormatter(self.spec, arg).py_type())
+                results.append(ArgumentFormatter(self.spec, arg).as_py_type())
 
         return ', '.join(results)
diff --git a/sipbuild/generator/formatters/template.py b/sipbuild/generator/outputs/formatters/template.py
similarity index 85%
rename from sipbuild/generator/formatters/template.py
rename to sipbuild/generator/outputs/formatters/template.py
index ae1df10..c7491a0 100644
--- a/sipbuild/generator/formatters/template.py
+++ b/sipbuild/generator/outputs/formatters/template.py
@@ -21,7 +21,7 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
-from ..scoped_name import STRIP_GLOBAL, STRIP_NONE
+from ...scoped_name import STRIP_GLOBAL, STRIP_NONE
 
 from .scoped import ScopedFormatter
 from .signature import SignatureFormatter
@@ -30,7 +30,7 @@ from .signature import SignatureFormatter
 class TemplateFormatter(ScopedFormatter):
     """ This creates various string representations of a template. """
 
-    def cpp_type(self, *, scope=None, strip=STRIP_NONE, as_xml=False):
+    def cpp_type(self, *, strip=STRIP_NONE, as_xml=False):
         """ Return the C++ representation of the template type. """
 
         template = self.object
@@ -42,17 +42,14 @@ class TemplateFormatter(ScopedFormatter):
 
         s += template.cpp_name.cpp_stripped(strip)
 
-        s += '<' if as_xml else '<'
+        s += '<'
 
         s += SignatureFormatter(self.spec, template.types).cpp_arguments(
                 strip=strip, as_xml=as_xml)
 
-        if as_xml:
-            s += '>'
-        else:
-            if s.endswith('>'):
-                s += ' '
+        if s.endswith('>') and not as_xml:
+            s += ' '
 
-            s += '>'
+        s += '>'
 
         return s
diff --git a/sipbuild/generator/formatters/utils.py b/sipbuild/generator/outputs/formatters/utils.py
similarity index 66%
rename from sipbuild/generator/formatters/utils.py
rename to sipbuild/generator/outputs/formatters/utils.py
index 242742a..b08ec09 100644
--- a/sipbuild/generator/formatters/utils.py
+++ b/sipbuild/generator/outputs/formatters/utils.py
@@ -21,6 +21,17 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
+def format_copying(copying, comment):
+    """ Return a formatted %Copying text. """
+
+    s = comment + ' ' + ''.join([b.text for b in copying]).rstrip().replace('\n', '\n' + comment + ' ')
+
+    if s:
+        s = comment + '\n' + s
+
+    return s
+
+
 def format_scoped_py_name(scope, py_name):
     """ Return a formatted scoped Python name. """
 
@@ -35,3 +46,25 @@ def format_scoped_py_name(scope, py_name):
         py_name_s = py_name
 
     return scope_s + py_name_s
+
+
+def iface_is_defined(iface_file, scope, module, defined):
+    """ Return True if a type corresponding to an interface file has been
+    defined in the context of a module.
+    """
+
+    # A type in another module would have been imported.
+    if iface_file.module is not module:
+        return True
+
+    if iface_file not in defined:
+        return False
+
+    # Check all enclosing scopes have been defined as well.
+    while scope is not None:
+        if scope.iface_file not in defined:
+            return False
+
+        scope = scope.scope
+
+    return True
diff --git a/sipbuild/generator/formatters/value_list.py b/sipbuild/generator/outputs/formatters/value_list.py
similarity index 65%
rename from sipbuild/generator/formatters/value_list.py
rename to sipbuild/generator/outputs/formatters/value_list.py
index 031a9d6..0de7314 100644
--- a/sipbuild/generator/formatters/value_list.py
+++ b/sipbuild/generator/outputs/formatters/value_list.py
@@ -21,10 +21,12 @@
 # POSSIBILITY OF SUCH DAMAGE.
 
 
-from ..specification import ValueType
+from ...specification import ValueType
 
 from .argument import ArgumentFormatter
 from .base_formatter import BaseFormatter
+from .enum import EnumFormatter
+from .variable import VariableFormatter
 
 
 class ValueListFormatter(BaseFormatter):
@@ -36,19 +38,57 @@ class ValueListFormatter(BaseFormatter):
 
         return self._expression()
 
-    @property
-    def embedded_py_expression(self):
-        """ The Python representation of the value list as an expression
-        embedded in a string.
+    def py_expression(self, embedded=False):
+        """ The Python representation of the value list as an expression. """
+
+        return self._expression(as_python=True, embedded=embedded)
+
+    def as_rest_ref(self):
+        """ Return the Python representation of the value list as a reST
+        reference.
         """
 
-        return self._expression(as_python=True, embedded=True)
+        value_list = self.object
 
-    @property
-    def py_expression(self):
-        """ The Python representation of the value list as an expression. """
+        # The value must be a scoped name and we don't handle expressions.
+        if len(value_list) != 1 or value_list[0].value_type is not ValueType.SCOPED:
+            return None
+
+        target = value_list[0].value
+
+        # See if it is an attribute.
+        for variable in self.spec.variables:
+            if variable.fq_cpp_name == target:
+                return VariableFormatter(self.spec, variable).as_rest_ref()
+
+        # See if it is an enum member.
+        target_scope = target.scope
+        if target_scope is not None:
+            target_scope.make_absolute()
+
+        target_base_name = target.base_name
+
+        for enum in self.spec.enums:
+            # Look for the member name first before working out if it is the
+            # correct enum.
+            for member in enum.members:
+                if member.cpp_name == target_base_name:
+                    formatter = EnumFormatter(self.spec, enum)
+
+                    if enum.is_scoped:
+                        # It's a scoped enum so the fully qualified name of the
+                        # enum must match the scope of the name.
+                        if target_scope is not None and enum.fq_cpp_name == target_scope:
+                            return formatter.member_as_rest_ref(member)
+                    else:
+                        # It's a traditional enum so the scope of the enum must
+                        # match the scope of the name.
+                        if (enum.scope is None and target_scope is None) or (enum.scope is not None and target_scope is not None and enum.scope.iface_file.fq_cpp_name == target_scope):
+                            return formatter.member_as_rest_ref(member)
+
+                    break
 
-        return self._expression(as_python=True)
+        return None
 
     def _expression(self, as_python=False, embedded=False):
         """ The representation of the value list as an expression. """
@@ -110,7 +150,7 @@ class ValueListFormatter(BaseFormatter):
                         value.value.result)
 
                 if as_python:
-                    s += arg_formatter.py_type()
+                    s += arg_formatter.as_py_type()
                 else:
                     s += arg_formatter.cpp_type()
 
diff --git a/sipbuild/generator/formatters/variable.py b/sipbuild/generator/outputs/formatters/variable.py
similarity index 79%
rename from sipbuild/generator/formatters/variable.py
rename to sipbuild/generator/outputs/formatters/variable.py
index 41d340c..ea7142f 100644
--- a/sipbuild/generator/formatters/variable.py
+++ b/sipbuild/generator/outputs/formatters/variable.py
@@ -26,3 +26,13 @@ from .scoped import EmbeddedScopeFormatter
 
 class VariableFormatter(EmbeddedScopeFormatter):
     """ This creates various string representations of a variable. """
+
+    def as_rest_ref(self):
+        """ Return the fully qualified Python name as a reST reference. """
+
+        variable = self.object
+        module_name = variable.module.fq_py_name.name
+        variable_name = format_scoped_py_name(self.scope,
+                variable.py_name.name)
+
+        return f':sip:ref:`~{module_name}.{variable_name}`'
diff --git a/sipbuild/generator/outputs/pyi.py b/sipbuild/generator/outputs/pyi.py
new file mode 100644
index 0000000..4c3705c
--- /dev/null
+++ b/sipbuild/generator/outputs/pyi.py
@@ -0,0 +1,685 @@
+# Copyright (c) 2022, Riverbank Computing Limited
+# All rights reserved.
+#
+# This copy of SIP is licensed for use under the terms of the SIP License
+# Agreement.  See the file LICENSE for more details.
+#
+# This copy of SIP may also used under the terms of the GNU General Public
+# License v2 or v3 as published by the Free Software Foundation which can be
+# found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+from ...version import SIP_VERSION_STR
+
+from ..specification import (AccessSpecifier, ArgumentType, ArrayArgument,
+        EnumBaseType, IfaceFileType, PySlot)
+from ..utils import append_iface_file, find_method
+
+from .formatters import (ArgumentFormatter, ClassFormatter, format_copying,
+        format_scoped_py_name)
+
+
+def output_pyi(spec, pyi_filename):
+    """ Output a .pyi file. """
+
+    with open(pyi_filename, 'w') as pf:
+        module = spec.modules[0]
+
+        # Write the header.
+        copying = format_copying(module.copying, '#')
+
+        pf.write(
+f'''# The PEP 484 type hints stub file for the {module.py_name} module.
+#
+# Generated by SIP {SIP_VERSION_STR}
+{copying}
+
+
+''')
+
+        if module.is_composite:
+            _composite_module(pf, spec, module)
+        else:
+            _module(pf, spec, module)
+
+
+def _composite_module(pf, spec, module):
+    """ Output the type hints for a composite module. """
+
+    for mod in spec.modules:
+        if mod.composite is module:
+            pf.write(f'from {mod.fq_py_name.name} import *\n')
+
+
+def _module(pf, spec, module):
+    """ Output the type hints for an ordinary module. """
+
+    # Generate the imports. Note that we assume the super-types are the
+    # standard SIP ones.
+    if spec.abi_version >= (13, 0):
+        pf.write('import enum\n')
+
+    pf.write(
+f'''import typing
+
+import {spec.sip_module}
+''')
+
+    imports = []
+
+    for mod in module.imports:
+        parts = mod.fq_py_name.name.split('.')
+
+        if mod.fq_py_name.name == mod.py_name:
+            imports.append('import ' + mod.py_name)
+        else:
+            scope = mod.fq_py_name.name[:-len(mod.py_name) - 1]
+            imports.append('from ' + scope + ' import ' + mod.py_name)
+
+    if imports:
+        pf.write('\n' + '\n'.join(imports) + '\n')
+
+    # Generate any exported type hint code and any module-specific type hint
+    # code.
+    _type_hint_code(pf, spec.exported_type_hint_code)
+    _type_hint_code(pf, module.type_hint_code)
+
+    # Generate the types - global enums must be first.
+    _enums(pf, spec, module)
+
+    # The list of enums and classes that have been defined at any particular
+    # point so we know if they can be referenced directly rather than by their
+    # names as a string.
+    defined = []
+
+    for klass in spec.classes:
+        if klass.iface_file.module is not module:
+            continue
+
+        if klass.external:
+            continue
+
+        if klass.no_type_hint:
+            continue
+
+        # Only handle non-nested classes here.
+        if klass.scope is not None:
+            continue
+
+        # We can't handle extenders.
+        if klass.real_class is not None:
+            continue
+
+        _class(pf, spec, module, klass, defined)
+
+    for mapped_type in spec.mapped_types:
+        if mapped_type.iface_file.module is not module:
+            continue
+
+        if mapped_type.py_name is not None:
+            _mapped_type(pf, spec, module, mapped_type, defined)
+
+    _variables(pf, spec, module, defined)
+
+    first = True
+
+    for member in module.global_functions:
+        if member.py_slot is None:
+            first = _separate(pf, first)
+            _callable(pf, spec, module, member, module.overloads, False,
+                    defined)
+
+
+def _type_hint_code(pf, type_hint_code, indent=0):
+    """ Output handwritten type hint code. """
+
+    s = ''
+
+    for block in type_hint_code:
+        s += '\n'
+
+        need_indent = True
+
+        for ch in block.text:
+            if need_indent:
+                s += _indent(indent)
+                need_indent = False
+
+            s += ch
+
+            if ch == '\n':
+                need_indent = True
+
+    pf.write(s)
+
+
+def _class(pf, spec, module, klass, defined, indent=0):
+    """ Output the type hints for a class. """
+
+    nr_overloads = 0
+
+    if not klass.is_hidden_namespace:
+        _separate(pf, True, indent)
+
+        s = _indent(indent)
+
+        s += f'class {klass.py_name.name}('
+
+        if klass.superclasses:
+            s += ', '.join(
+                    [ClassFormatter(spec, sc).as_type_hint(module, defined)
+                            for sc in klass.superclasses])
+
+        elif klass.supertype is not None:
+            # In ABI v12 the default supertype does not contain the fully
+            # qualified name of the sip module so we fix it here.
+            if spec.abi_version[0] == 12 and klass.supertype.name.startswith('sip.'):
+                s += spec.sip_module + klass.supertype.name[4:]
+            else:
+                s += klass.supertype.name
+
+        else:
+            simple = 'simple' if klass.iface_file.type is IfaceFileType.NAMESPACE else ''
+
+            s += f'{spec.sip_module}.{simple}wrapper'
+
+        # See if there is anything in the class body.
+        for ctor in klass.ctors:
+            if ctor.access_specifier is AccessSpecifier.PRIVATE:
+                continue
+
+            if ctor.no_type_hint:
+                continue
+
+            nr_overloads += 1
+
+        no_body = (klass.type_hint_code is None and nr_overloads == 0)
+
+        if no_body:
+            for overload in klass.overloads:
+                if overload.access_specifier is AccessSpecifier.PRIVATE:
+                    continue
+
+                if overload.no_type_hint:
+                    continue
+
+                no_body = False
+                break
+
+        if no_body:
+            for enum in spec.enums:
+                if enum.scope is klass and not enum.no_type_hint:
+                    no_body = False
+                    break
+
+        if no_body:
+            for nested in spec.classes:
+                if nested.scope is klass and not nested.no_type_hint:
+                    no_body = False
+                    break
+
+        if no_body:
+            for variable in spec.variables:
+                if variable.scope is klass and not variable.no_type_hint:
+                    no_body = False
+                    break
+
+        suffix = ' ...' if no_body else ''
+
+        s += f'):{suffix}\n'
+
+        pf.write(s)
+
+        indent += 1
+
+        if klass.type_hint_code is not None:
+            _type_hint_code(pf, [klass.type_hint_code], indent)
+
+    _enums(pf, spec, module, defined=defined, scope=klass, indent=indent)
+
+    # Handle any nested classes.
+    for nested in spec.classes:
+        if nested.scope is klass and not nested.no_type_hint:
+            _class(pf, spec, module, nested, defined, indent)
+
+    _variables(pf, spec, module, defined, scope=klass, indent=indent)
+
+    first = True
+
+    for ctor in klass.ctors:
+        if ctor.access_specifier is AccessSpecifier.PRIVATE:
+            continue
+
+        if ctor.no_type_hint:
+            continue
+
+        first = _separate(pf, first, indent)
+
+        _ctor(pf, spec, module, ctor, nr_overloads > 1, defined, indent)
+
+    first = True
+
+    for member in klass.members:
+        first = _separate(pf, first, indent)
+
+        _callable(pf, spec, module, member, klass.overloads,
+                not klass.is_hidden_namespace, defined, indent)
+
+    for prop in klass.properties:
+        first = _separate(pf, first, indent)
+
+        getter = find_method(klass, prop.getter)
+        if getter is not None:
+            _property(pf, spec, module, prop, False, getter, klass.overloads,
+                    defined, indent)
+
+            if prop.setter is not None:
+                setter = find_method(klass, prop.setter)
+                if setter is not None:
+                    _property(pf, spec, module, prop, True, setter,
+                            klass.overloads, defined, indent)
+
+    if not klass.is_hidden_namespace:
+        # Keep track of what has been defined so that forward references are no
+        # longer required.
+        append_iface_file(defined, klass.iface_file)
+
+def _mapped_type(pf, spec, module, mapped_type, defined, indent=0):
+    """ Output the type hints for a mapped type. """
+
+    # See if there is anything in the mapped type body.
+    no_body = (len(mapped_type.members) == 0)
+
+    if no_body:
+        for enum in spec.enums:
+            if enum.scope is mapped_type and not enum.no_type_hint:
+                no_body = FALSE
+                break
+
+    if not no_body:
+        _separate(pf, True, indent)
+
+        s = _indent(indent)
+        s += f'class {mapped_type.py_name.name}({spec.sip_module}.wrapper):\n'
+        pf.write(s)
+
+        indent += 1
+
+        _enums(pf, spec, module, defined=defined, scope=mapped_type,
+                indent=indent)
+
+        first = True
+
+        for member in mapped_type.members:
+            first = _separate(pf, first, indent)
+
+            _callable(pf, spec, module, member, member.overloads, True,
+                    defined, indent)
+
+    # Keep track of what has been defined so that forward references are no
+    # longer required.
+    append_iface_file(defined, mapped_type.iface_file)
+
+
+def _ctor(pf, spec, module, ctor, overloaded, defined, indent):
+    """ Output a ctor type hint. """
+
+    if overloaded:
+        s = _indent(indent)
+        s += '@typing.overload\n'
+        pf.write(s)
+
+    s = _indent(indent)
+    s += 'def __init__('
+
+    args = ['self']
+
+    for arg_nr, arg in enumerate(ctor.py_signature.args):
+        as_str = _argument(spec, module, arg, defined, arg_nr=arg_nr)
+        if as_str:
+            args.append(as_str)
+
+    s += ', '.join(args) + ') -> None: ...\n'
+
+    pf.write(s)
+
+
+def _enums(pf, spec, module, defined=None, scope=None, indent=0):
+    """ Output the type hints for all the enums in a scope. """
+
+    for enum in spec.enums:
+        if enum.module is not module:
+            continue
+
+        if enum.scope is not scope:
+            continue
+
+        if enum.no_type_hint:
+            continue
+
+        _separate(pf, True, indent)
+
+        if enum.py_name is not None:
+            enum_type = format_scoped_py_name(enum.scope, enum.py_name.name)
+
+            superclass = 'int'
+
+            if spec.abi_version >= (13, 0):
+                if enum.base_type is EnumBaseType.ENUM:
+                    superclass = 'enum.Enum'
+                elif enum.base_type is EnumBaseType.FLAG:
+                    superclass = 'enum.Flag'
+                elif enum.base_type in (EnumBaseType.INT_ENUM, EnumBaseType.UNSIGNED_INT_ENUM):
+                    superclass = 'enum.IntEnum'
+                elif enum.base_type is EnumBaseType.INT_FLAG:
+                    superclass = 'enum.IntFlag'
+
+            s = _indent(indent)
+            s+= f'class {enum.py_name.name}({superclass}):\n'
+            pf.write(s)
+
+            indent += 1
+        else:
+            enum_type = 'int'
+
+        for member in enum.members:
+            if member.no_type_hint:
+                continue
+
+            s = _indent(indent)
+            s += f'{member.py_name.name} = ... # type: {enum_type}\n'
+            pf.write(s)
+
+        if enum.py_name is not None:
+            indent -= 1
+
+
+def _variables(pf, spec, module, defined, scope=None, indent=0):
+    """ Output the type hints for all the variables in a scope. """
+
+    first = True
+
+    for variable in spec.variables:
+        if variable.module is not module:
+            continue
+
+        if variable.scope is not scope:
+            continue
+
+        if variable.no_type_hint:
+            continue
+
+        py_type = _type(spec, module, variable.type, defined)
+
+        first = _separate(pf, first, indent)
+
+        s = _indent(indent)
+        s += f'{variable.py_name.name} = ... # type: {py_type}\n'
+        pf.write(s)
+
+
+def _callable(pf, spec, module, member, overloads, is_method, defined,
+        indent=0):
+    """ Output the type hints for a callable. """
+
+    # Count the number of overloads.
+    nr_overloads = 0
+
+    for overload in overloads:
+        if overload.access_specifier is AccessSpecifier.PRIVATE:
+            continue
+
+        if overload.common is not member:
+            continue
+
+        if overload.no_type_hint:
+            continue
+
+        nr_overloads += 1
+
+    # Handle each overload.
+    overload_nr = 0
+
+    for overload in overloads:
+        if overload.access_specifier is AccessSpecifier.PRIVATE:
+            continue
+
+        if overload.common is not member:
+            continue
+
+        if overload.no_type_hint:
+            continue
+
+        _overload(pf, spec, module, overload, nr_overloads > 1, overload_nr,
+                is_method, defined, indent)
+
+        overload_nr += 1
+
+
+def _property(pf, spec, module, prop, is_setter, member, overloads, defined,
+        indent):
+    """ Output the type hints for a property. """
+
+    for overload in overloads:
+        if overload.access_specifier is AccessSpecifier.PRIVATE:
+            continue
+
+        if overload.common is not member:
+            continue
+
+        if overload.no_type_hint:
+            continue
+
+        s = _indent(indent)
+
+        if is_setter:
+            s += f'@{prop.name.name}.setter\n'
+        else:
+            s += '@property\n'
+
+        pf.write(s)
+
+        signature = _python_signature(spec, module, overload.py_signature,
+                defined)
+
+        s = _indent(indent)
+        s += f'def {prop.name.name}{sinature}: ...\n'
+        pf.write(s)
+
+        break
+
+
+def _overload(pf, spec, module, overload, overloaded, overload_nr, is_method,
+        defined, indent):
+    """ Output the type hints for a single overload. """
+
+    # mypy recommends using 'object' as the argument type.
+    is_eq_slot = (overload.common.py_slot in (PySlot.EQ, PySlot.NE))
+
+    # The recommendation means any subsequent overloads are pointless.
+    if is_eq_slot:
+        if overload_nr > 0:
+            return
+    elif overloaded:
+        pf.write(_indent(indent) + '@typing.overload\n')
+
+    if is_method and overload.is_static:
+        pf.write(_indent(indent) + '@staticmethod\n')
+
+    if is_eq_slot:
+        signature = '(self, other: object)'
+    else:
+        need_self = (is_method and not overload.is_static)
+
+        signature = _python_signature(spec, module, overload.py_signature,
+                defined, need_self=need_self)
+
+    s = _indent(indent)
+    s += f'def {overload.common.py_name.name}{signature}: ...\n'
+    pf.write(s)
+
+
+def _python_signature(spec, module, signature, defined, need_self=True):
+    """ Return a Python signature. """
+
+    # Handle the input values.
+    in_args = []
+
+    if need_self:
+        in_args.append('self')
+
+    nr_out = 0
+
+    for arg_nr, arg in enumerate(signature.args):
+        if arg.is_out:
+            nr_out += 1
+
+        if arg.is_in:
+            as_str = _argument(spec, module, arg, defined, arg_nr=arg_nr)
+            if as_str:
+                in_args.append(as_str)
+
+    in_args = ', '.join(in_args)
+
+    # Handle the output values.
+    result = signature.result
+
+    if result.type is ArgumentType.VOID and len(result.derefs) == 0:
+        is_result = False
+    else:
+        type_hints = result.type_hints
+
+        # An empty type hint specifies a void return.
+        if type_hints is not None and type_hints.hint_out is not None and type_hints.hint_out == '':
+            is_result = False
+        else:
+            is_result = True
+
+    if is_result or nr_out > 0:
+        results_s = ''
+
+        needs_tuple = ((is_result and nr_out > 0) or nr_out > 1)
+
+        if needs_tuple:
+            results_s += 'typing.Tuple['
+
+        out_args = []
+
+        if is_result:
+            as_str = _argument(spec, module, result, defined)
+            if as_str:
+                out_args.append(as_str)
+
+        for arg in signature.args:
+            if arg.is_out:
+                as_str = _argument(spec, module, arg, defined)
+                if as_str:
+                    out_args.append(as_str)
+
+        results_s += ', '.join(out_args)
+
+        if needs_tuple:
+            results_s += ']'
+    else:
+        results_s = 'None'
+
+    return f'({in_args}) -> {results_s}'
+
+
+def _argument(spec, module, arg, defined, arg_nr=-1):
+    """ Return a Python argument. """
+
+    if arg.array is ArrayArgument.ARRAY_SIZE:
+        return None
+
+    optional = (arg_nr >= 0 and arg.default_value is not None)
+    use_optional = False
+
+    s = ''
+
+    if arg_nr >= 0 and arg.type is not ArgumentType.ELLIPSIS:
+        if arg.name is None:
+            s += f'a{arg_nr}: '
+        else:
+            name = _fix_py_keyword(arg.name.name)
+            s += f'{name}: '
+
+    if optional:
+        # Assume pointers can be None unless specified otherwise.
+        if arg.allow_none or (not arg.disallow_none and arg.derefs):
+            s += 'typing.Optional['
+            use_optional = True
+
+    if arg.array is ArrayArgument.ARRAY:
+        s += spec.sip_module + '.array['
+
+    s += _type(spec, module, arg, defined, out=(arg_nr < 0))
+
+    if arg_nr >= 0 and arg.type is ArgumentType.ELLIPSIS:
+        if arg.name is None:
+            s += f'a{arg_nr}'
+        else:
+            s += _fix_py_keyword(arg.name.name)
+
+    if arg.array is ArrayArgument.ARRAY:
+        s += ']'
+
+    if optional:
+        if use_optional:
+            s += ']'
+
+        s += ' = ...'
+
+    return s
+
+
+def _type(spec, module, arg, defined, out=False):
+    """ Return the type hint of a type. """
+
+    return ArgumentFormatter(spec, arg).as_type_hint(module, out, defined)
+
+
+def _indent(indent):
+    """ Return the required indentation. """
+
+    return ' ' * (4 * indent)
+
+
+def _separate(pf, first, indent=0):
+    """ Output a newline if not already done. """
+
+    if first:
+        pf.write('\n' if indent else '\n\n')
+
+    return False
+
+
+_PYTHON_KEYWORDS = (
+    'False', 'None', 'True', 'and', 'as', 'assert', 'break', 'class',
+    'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for',
+    'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not',
+    'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield',
+    'exec', 'print',
+)
+
+def _fix_py_keyword(word):
+    """ Return a fixed word if it is a Python keyword (or has been at any
+    time).
+    """
+
+    if word in _PYTHON_KEYWORDS:
+        word += '_'
+
+    return word
diff --git a/sipbuild/generator/outputs/type_hints.py b/sipbuild/generator/outputs/type_hints.py
new file mode 100644
index 0000000..9a702dc
--- /dev/null
+++ b/sipbuild/generator/outputs/type_hints.py
@@ -0,0 +1,497 @@
+# Copyright (c) 2022, Riverbank Computing Limited
+# All rights reserved.
+#
+# This copy of SIP is licensed for use under the terms of the SIP License
+# Agreement.  See the file LICENSE for more details.
+#
+# This copy of SIP may also used under the terms of the GNU General Public
+# License v2 or v3 as published by the Free Software Foundation which can be
+# found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+from copy import copy
+from dataclasses import dataclass, field
+from enum import auto, Enum
+from typing import List, Optional, Union
+from weakref import WeakKeyDictionary
+
+from ...exceptions import UserException
+
+from ..scoped_name import ScopedName
+from ..specification import WrappedClass, WrappedEnum
+
+
+# The types defined in the typing module.
+_TYPING_MODULE = (
+    'Any', 'NoReturn', 'Tuple', 'Union', 'Optional', 'Callable', 'Type',
+    'Literal', 'ClassVar', 'Final', 'Annotated', 'AnyStr', 'Protocol',
+    'NamedTuple', 'Dict', 'List', 'Set', 'FrozenSet', 'IO', 'TextIO',
+    'BinaryIO', 'Pattern', 'Match', 'Text', 'Iterable', 'Iterator',
+    'Generator', 'Mapping', 'Sequence',
+)
+
+
+class NodeType(Enum):
+    """ The node types. """
+
+    TYPING = auto()
+    CLASS = auto()
+    ENUM = auto()
+    OTHER = auto()
+
+
+class ParseState(Enum):
+    """ The different parse states of a type hint. """
+
+    REQUIRED = auto()
+    PARSING = auto()
+    PARSED = auto()
+
+
+ at dataclass
+class ManagedTypeHint:
+    """ Encapsulate a managed type hint. """
+
+    # The type hint being managed.
+    type_hint: str
+
+    # The rendered docstring.
+    as_docstring: Optional[str] = None
+
+    # The rendered reST reference.
+    as_rest_ref: Optional[str] = None
+
+    # The parse state.
+    parse_state: ParseState = ParseState.REQUIRED
+
+    # The root node.
+    root: Optional['TypeHintNode'] = None
+
+
+ at dataclass
+class TypeHintNode:
+    """ Encapsulate a node of a parsed type hint. """
+
+    # The type.
+    type: NodeType
+
+    # The list of child nodes.
+    children: Optional[List['TypeHintNode']] = None
+
+    # The type-dependent definition.
+    definition: Optional[Union[str, WrappedClass, WrappedEnum]] = None
+
+    # The next sibling node.
+    next: Optional['TypeHintNode'] = None
+
+
+class TypeHintManager:
+    """ A manager for type hints on behalf of a Specification object. """
+
+    # The map of specification objects and the corresponding manager object.
+    _spec_manager_map = WeakKeyDictionary()
+
+    def __new__(cls, spec):
+        """ Return the existing manager for a specification or create a new one
+        if necessary.
+        """
+
+        try:
+            manager = cls._spec_manager_map[spec]
+        except KeyError:
+            manager = object.__new__(cls)
+
+            manager._spec = spec
+            manager._managed_type_hints = {}
+
+            cls._spec_manager_map[spec] = manager
+
+        return manager
+
+    def as_docstring(self, type_hint, module, out, defined):
+        """ Return the type hint as a docstring. """
+
+        managed_type_hint = self._get_managed_type_hint(type_hint, out)
+
+        # See if it needs rendering.
+        if managed_type_hint.as_docstring is None:
+            managed_type_hint.as_docstring = self._render(managed_type_hint,
+                    out, module=module, defined=defined)
+
+        return managed_type_hint.as_docstring
+
+    def as_rest_ref(self, type_hint, out):
+        """ Return the type hint with appropriate reST references. """
+
+        managed_type_hint = self._get_managed_type_hint(type_hint, out)
+
+        # See if it needs rendering.
+        if managed_type_hint.as_rest_ref is None:
+            managed_type_hint.as_rest_ref = self._render(managed_type_hint,
+                    out, rest_ref=True)
+
+        return managed_type_hint.as_rest_ref
+
+    def as_type_hint(self, type_hint, module, out, defined):
+        """ Return the type hint as a type hint. """
+
+        managed_type_hint = self._get_managed_type_hint(type_hint, out)
+
+        # Note that we always render type hints as they can be different before
+        # and after a class or enum is defined in the .pyi file.
+        return self._render(managed_type_hint, out, pep484=True,
+                module=module, defined=defined)
+
+    def _get_managed_type_hint(self, type_hint, out):
+        """ Return the unique (for the specification) managed type hint for a
+        type hint.
+        """
+
+        try:
+            hint_in, hint_out = self._managed_type_hints[type_hint]
+        except KeyError:
+            hint_in = ManagedTypeHint(type_hint)
+            hint_out = ManagedTypeHint(type_hint)
+            self._managed_type_hints[type_hint] = (hint_in, hint_out)
+
+        return hint_out if out else hint_in
+
+    def _parse(self, managed_type_hint, out):
+        """ Ensure a type hint has been parsed. """
+
+        if managed_type_hint.parse_state is ParseState.REQUIRED:
+            managed_type_hint.parse_state = ParseState.PARSING
+            managed_type_hint.root = self._parse_node(out,
+                    managed_type_hint.type_hint)
+            managed_type_hint.parse_state = ParseState.PARSED
+
+    def _parse_node(self, out, text, start=0, end=None):
+        """ Return a single node of a parsed type hint. """
+
+        if end is None:
+            end = len(text)
+            top_level = True
+        else:
+            top_level = False
+
+        # Find the name and any opening and closing brackets.
+        start = self._strip_leading(text, start, end)
+        name_start = start
+
+        end = self._strip_trailing(text, start, end)
+        name_end = end
+
+        children = None
+
+        i = text[start:end].find('[')
+        if i >= 0:
+            i += start
+
+            children = []
+
+            # The last character must be a closing bracket.
+            if text[end - 1] != ']':
+                raise UserException(
+                        f"type hint '{text}': ']' expected at position {end}")
+
+            # Find the end of any name.
+            name_end = self._strip_trailing(text, name_start, i)
+
+            while True:
+                # Skip the opening bracket or comma.
+                i += 1
+
+                # Find the next comma, if any.
+                depth = 0
+
+                for part_i in range(i, end):
+                    if text[part_i] == '[':
+                        depth += 1
+
+                    elif text[part_i] == ']' and depth != 0:
+                        depth -= 1
+
+                    elif text[part_i] in ',]' and depth == 0:
+                        # Recursively parse this part.
+                        new_child = self._parse_node(out, text, i, part_i)
+                        if new_child is not None:
+                            self._append_child(children, new_child)
+
+                        i = part_i
+                        break
+                else:
+                    break
+
+        # See if we have a name.
+        if name_start != name_end:
+            # Get the name. */
+            name = text[name_start:name_end]
+
+            # See if it is an object in the typing module.
+            if name in _TYPING_MODULE:
+                if name == 'Union':
+                    # If there are no children assume it is because they have
+                    # been omitted.
+                    if len(children) == 0:
+                        return None
+
+                    # Flatten any unions.
+                    flattened = []
+
+                    for child in children:
+                        if child.type is NodeType.TYPING and child.definition == 'Union':
+                            for grandchild in child.children:
+                                self._append_child(flattened, grandchild)
+                        else:
+                            self._append_child(flattened, child)
+
+                    children = flattened
+
+                node = TypeHintNode(NodeType.TYPING, children=children,
+                        definition=name)
+            else:
+                # Search for the type.
+                node = self._lookup_type(name, out)
+        else:
+            # At the top level we must have brackets and they must not be empty.
+            if top_level and (children is None or len(children) == 0):
+                raise UserException(
+                        f"type hint '{text}': must have non-empty brackets")
+
+            # Return the representation of brackets.
+            node = TypeHintNode(NodeType.TYPING, children=children)
+
+        return node
+
+    def _render(self, managed_type_hint, out, pep484=False, rest_ref=False,
+            module=None, defined=None):
+        """ Return a rendered type hint. """
+
+        self._parse(managed_type_hint, out)
+
+        if managed_type_hint.root is not None:
+            s = self._render_node(managed_type_hint.root, out, pep484,
+                    rest_ref, module, defined)
+        else:
+            s = self._maybe_any_object(managed_type_hint.type_hint,
+                    pep484=pep484)
+
+        return s
+
+    def _render_node(self, node, out, pep484, rest_ref, module, defined):
+        """ Render a single node. """
+
+        if node.type is NodeType.TYPING:
+            if node.definition is None:
+                s = ''
+            elif pep484:
+                s = 'typing.' + node.definition
+            else:
+                s = node.definition
+
+            if node.children is not None:
+                children = [self._render_node(c, out, pep484, rest_ref, module,
+                        defined) for c in node.children]
+
+                s += '[' + ', '.join(children) + ']'
+
+        elif node.type is NodeType.CLASS:
+            from .formatters import ClassFormatter
+
+            formatter = ClassFormatter(self._spec, node.definition)
+
+            if rest_ref:
+                s = formatter.as_rest_ref()
+            elif pep484:
+                s = formatter.as_type_hint(module, defined)
+            else:
+                s = formatter.fq_py_name
+
+        elif node.type is NodeType.ENUM:
+            from .formatters import EnumFormatter
+
+            formatter = EnumFormatter(self._spec, node.definition)
+
+            if rest_ref:
+                s = formatter.as_rest_ref()
+            elif pep484:
+                s = formatter.as_type_hint(module, defined)
+            else:
+                s = formatter.fq_py_name
+
+        else:
+            s = self._maybe_any_object(node.definition, pep484)
+
+        return s
+
+    @staticmethod
+    def _append_child(children, new_child):
+        """ Append a child to an existing list of children. """
+
+        if len(children) > 1:
+            children[-1].next = new_child
+
+        children.append(new_child)
+
+    def _copy_type_hint(self, type_hint, out):
+        """ Copy the root node of a type hint. """
+
+        managed_type_hint = self._get_managed_type_hint(type_hint, out)
+
+        self._parse(managed_type_hint, out)
+
+        if managed_type_hint.root is None:
+            return None
+
+        node = copy(managed_type_hint.root)
+        node.next = None
+
+        return node
+
+    def _lookup_enum(self, name, scopes):
+        """ Lookup an enum using its C/C++ name. """
+
+        for enum in self._spec.enums:
+            if enum.fq_cpp_name is not None and enum.fq_cpp_name.base_name == name and enum.scope in scopes:
+                return enum
+
+        return None
+
+    def _lookup_class(self, name, scope):
+        """ Lookup a class/struct/union using its C/C++ name. """
+
+        for klass in self._spec.classes:
+            if klass.scope is scope and not klass.external and klass.iface_file.fq_cpp_name.base_name == name:
+                return klass
+
+        return None
+
+    def _lookup_mapped_type(self, name):
+        """ Lookup a mapped type using its C/C++ name. """
+
+        for mapped_type in self._spec.mapped_types:
+            if mapped_type.cpp_name is not None and mapped_type.cpp_name.name == name:
+                return mapped_type
+
+        return None
+
+    def _lookup_type(self, name, out):
+        """ Look up a qualified Python type and return the corresponding node.
+        """
+
+        # Start searching at the global level.
+        scope_klass = None
+        scope_mapped_type = None
+
+        # We allow both Python and C++ scope separators.
+        scoped_name = ScopedName.parse(name.replace('.', '::'))
+
+        for part_i, part in enumerate(scoped_name):
+            is_last_part = ((part_i + 1) == len(scoped_name))
+
+            # See if it's an enum.
+            enum = self._lookup_enum(part, (scope_klass, scope_mapped_type))
+            if enum is not None:
+                if is_last_part:
+                    return TypeHintNode(NodeType.ENUM, definition=enum)
+
+                # There is some left so the whole lookup has failed.
+                break
+
+            # If we have a mapped type scope then we must be looking for an
+            # enum, which we have failed to find.
+            if scope_mapped_type is not None:
+                break
+
+            if scope_klass is None:
+                # We are looking at the global level, so see if it is a mapped
+                # type.
+                mapped_type = self._lookup_mapped_type(part)
+                if mapped_type is not None:
+                    # If we have used the whole name then the lookup has
+                    # succeeded.
+                    if is_last_part:
+                        if mapped_type.type_hints is not None:
+                            type_hint = mapped_type.type_hints.hint_out if out else mapped_type.type_hints.hint_in
+
+                            if type_hint is not None:
+                                if self._get_managed_type_hint(type_hint, out).parse_state is not ParseState.PARSING:
+                                    return self._copy_type_hint(type_hint, out)
+
+                        return None
+
+                    # Otherwise this is the scope for the next part.
+                    scope_mapped_type = mapped_type
+
+            if scope_mapped_type is None:
+                # If we get here then it must be a class.
+                klass = self._lookup_class(part, scope_klass)
+                if klass is None:
+                    break
+
+                # If we have used the whole name then the lookup has succeeded.
+                if is_last_part:
+                    if klass.type_hints is not None:
+                        type_hint = klass.type_hints.hint_out if out else klass.type_hints.hint_in
+
+                        if type_hint is not None:
+                            if self._get_managed_type_hint(type_hint, out).parse_state is not ParseState.PARSING:
+                                return self._copy_type_hint(type_hint, out)
+
+                    return TypeHintNode(NodeType.CLASS, definition=klass)
+
+                # Otherwise this is the scope for the next part.
+                scope_klass = klass
+
+            # If we have run out of name then the lookup has failed.
+            if is_last_part:
+                break
+
+        # Nothing was found.
+        return TypeHintNode(NodeType.OTHER, definition=name)
+
+    @classmethod
+    def _maybe_any_object(cls, hint, pep484):
+        """ Return a hint taking into account that it may be any sort of
+        object.
+        """
+
+        return hint if hint != 'Any' else cls._any_object(pep484)
+
+    @staticmethod
+    def _any_object(pep484):
+        """ Return a hint taking into account that it may be any sort of
+        object.
+        """
+
+        return 'typing.Any' if pep484 else 'object'
+
+    @staticmethod
+    def _strip_leading(text, start, end):
+        """ Return the index of the first non-space of a string. """
+    
+        while start < end and text[start] == ' ':
+            start += 1
+
+        return start
+
+    @staticmethod
+    def _strip_trailing(text, start, end):
+        """ Return the index after the last non-space of a string. """
+
+        while end > start and text[end - 1] == ' ':
+            end -= 1
+
+        return end
diff --git a/sipbuild/generator/outputs/xml.py b/sipbuild/generator/outputs/xml.py
new file mode 100644
index 0000000..c180395
--- /dev/null
+++ b/sipbuild/generator/outputs/xml.py
@@ -0,0 +1,418 @@
+# Copyright (c) 2022, Riverbank Computing Limited
+# All rights reserved.
+#
+# This copy of SIP is licensed for use under the terms of the SIP License
+# Agreement.  See the file LICENSE for more details.
+#
+# This copy of SIP may also used under the terms of the GNU General Public
+# License v2 or v3 as published by the Free Software Foundation which can be
+# found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+from xml.etree.ElementTree import Element, SubElement
+
+from ..python_slots import is_number_slot
+from ..scoped_name import ScopedName, STRIP_GLOBAL
+from ..specification import (AccessSpecifier, ArgumentType, ArrayArgument,
+        IfaceFileType, KwArgs, PyQtMethodSpecifier, PySlot, Transfer)
+
+from .formatters import (ArgumentFormatter, ClassFormatter, EnumFormatter,
+        format_scoped_py_name, SignatureFormatter, ValueListFormatter,
+        VariableFormatter)
+
+
+# The schema version number.
+_XML_VERSION_NR = '0'
+
+
+def output_xml(spec, module_name):
+    """ Return the root Module element of the XML for a module. """
+
+    # Note that we don't yet handle mapped types, templates or exceptions.
+
+    for module in spec.modules:
+        if module.py_name == module_name:
+            break
+    else:
+        return None
+
+    root = Element('Module', version=_XML_VERSION_NR, name=module.py_name)
+
+    for klass in spec.classes:
+        if klass.iface_file.module is module and not klass.external:
+            _xml_class(root, spec, module, klass)
+
+    for klass in module.proxies:
+        _class(root, spec, module, klass)
+
+    _enums(root, spec, module)
+    _variables(root, spec, module)
+
+    for member in module.global_functions:
+        _function(root, spec, member, module.overloads)
+
+    return root
+
+
+def _realname(scope, member=None):
+    """ Return a 'realname' attribute containing a fully qualified C/C++ name.
+    """
+
+    if scope is None:
+        # The member should have a name in this context.
+        fq_cpp_name = member
+    else:
+        if isinstance(scope, ScopedName):
+            fq_cpp_name = scope
+        else:
+            fq_cpp_name = scope.iface_file.fq_cpp_name
+
+        fq_cpp_name = fq_cpp_name.cpp_stripped(STRIP_GLOBAL)
+
+        if member is not None:
+            fq_cpp_name += '::' + member
+
+    return fq_cpp_name
+
+
+def _class(parent, spec, module, klass):
+    """ Output the XML for a class. """
+
+    if klass.is_opaque:
+        return SubElement(parent, 'OpaqueClass',
+                name=ClassFormatter(spec, klass).fq_py_name)
+
+    if klass.is_hidden_namespace:
+        parent_klass = parent
+    else:
+        attrib = {}
+
+        if klass.pickle_code is not None:
+            attrib['pickle'] = '1'
+
+        if klass.convert_to_type_code is not None:
+            attrib['convert'] = '1'
+
+        if klass.convert_from_type_code is not None:
+            attrib['convertfrom'] = '1'
+
+        if klass.real_class is not None:
+            attrib['extends'] = klass.real_class.iface_file.module.py_name
+
+        if klass.pyqt_flags_enums is not None:
+            attrib['flagsenums'] = ' '.join(klass.pyqt_flags_enums)
+
+        if len(klass.superclasses) != 0:
+            attrib['inherits'] = ' '.join(
+                    [ClassFormatter(spec, s).as_rest_ref()
+                            for s in klass.superclasses])
+
+        parent_klass = SubElement(parent, 'Class', attrib,
+                name=ClassFormatter(spec, klass).fq_py_name,
+                realname=_realname(klass.iface_file.fq_cpp_name))
+
+    for ctor in klass.ctors:
+        if ctor.access_specifier is not AccessSpecifier.PRIVATE:
+            _ctor(parent_klass, spec, klass, ctor)
+
+    _enums(parent_klass, spec, module, klass)
+    _variables(parent_klass, spec, module, klass)
+
+    for member in klass.members:
+        _function(parent_klass, spec, member, klass.overloads, klass)
+
+
+def _enums(parent, spec, module, scope=None):
+    """ Output the XML for all the enums in a scope. """
+
+    for enum in spec.enums:
+        if enum.module is module and enum.scope is scope:
+            if enum.py_name is None:
+                for member in enum.members:
+                    SubElement(parent, 'Member',
+                            name=format_scoped_py_name(enum.scope,
+                                    member.py_name.name),
+                            realname=_realname(enum.scope, member.cpp_name),
+                            const='1', typename='int')
+            else:
+                enum_el = SubElement(parent, 'Enum',
+                        name=format_scoped_py_name(enum.scope,
+                                enum.py_name.name),
+                        realname=_realname(enum.fq_cpp_name))
+
+                for member in enum.members:
+                    name = format_scoped_py_name(enum.scope, enum.py_name.name) + '.' + member.py_name.name
+
+                    SubElement(enum_el, 'EnumMember', name=name,
+                            realname=_realname(enum.fq_cpp_name,
+                                    member.cpp_name))
+
+
+def _variables(parent, spec, module, scope=None):
+    """ Output the XML for all the variables in a scope. """
+
+    for variable in spec.variables:
+        if variable.module is module and variable.scope is scope:
+            formatter = VariableFormatter(spec, variable)
+
+            attrib = {}
+
+            if variable.type.is_const or scope is None:
+                attrib['const'] = '1'
+
+            if variable.is_static:
+                attrib['static'] = '1'
+
+            SubElement(parent, 'Member', attrib, name=formatter.fq_py_name,
+                    realname=_realname(variable.fq_cpp_name),
+                    typename=_typename(spec, variable.type))
+
+
+def _ctor(parent, spec, scope, ctor):
+    """ Output the XML for a ctor. """
+
+    attrib = {}
+
+    if _has_cpp_signature(ctor.cpp_signature):
+        attrib['cppsig'] = _cpp_signature(spec, ctor.cpp_signature)
+
+    function_el = SubElement(parent, 'Function', attrib,
+            name=format_scoped_py_name(scope, '__init__'),
+            realname=_realname(scope, '__init__'))
+
+    for arg in ctor.py_signature.args:
+        if arg.is_in:
+            _argument(function_el, spec, arg, ctor.kw_args)
+
+        if arg.is_out:
+            _argument(function_el, spec, arg, ctor.kw_args, out=True)
+
+
+def _function(parent, spec, member, overloads, scope=None):
+    """ Output the XML for a function. """
+
+    for overload in overloads:
+        if overload.common is member and overload.access_specifier is not AccessSpecifier.PRIVATE:
+            if overload.pyqt_method_specifier is PyQtMethodSpecifier.SIGNAL:
+                attrib = {}
+
+                if _has_cpp_signature(overload.cpp_signature):
+                    attrib['cppsig'] = _cpp_signature(spec,
+                            overload.cpp_signature)
+
+                signal_el = SubElement(parent, 'Signal', attrib,
+                        name=format_scoped_py_name(scope, member.py_name.name),
+                        realname=_realname(scope, overload.cpp_name))
+
+                for arg in overload.py_signature.args:
+                    _argument(signal_el, spec, arg, overload.kw_args)
+            else:
+                extends = None
+                is_static = (scope is None or scope.iface_file.type is IfaceFileType.NAMESPACE or overload.is_static)
+
+                if scope is None and member.py_slot is not None and overload.py_signature.args[0].type is ArgumentType.CLASS:
+                    extends = overload.py_signature.args[0].definition
+                    is_static = False
+
+                _overload(parent, spec, scope, overload, extends,
+                        is_static)
+
+
+def _overload(parent, spec, scope, overload, extends, is_static):
+    """ Output the XML for an overload. """
+
+    if overload.is_reflected:
+        name = _reflected_slot(overload.common.py_slot)
+    else:
+        name = None
+
+    if name is None:
+        name = overload.common.py_name.name
+        cpp_name = overload.cpp_name
+    else:
+        cpp_name = name
+
+    attrib = {}
+
+    if _has_cpp_signature(overload.cpp_signature):
+        attrib['cppsig'] = _cpp_signature(spec, overload.cpp_signature,
+                is_const=overload.is_const)
+
+    if overload.is_abstract:
+        attrib['abstract'] = '1'
+
+    if is_static:
+        attrib['static'] = '1'
+
+    if overload.pyqt_method_specifier is PyQtMethodSpecifier.SLOT:
+        attrib['slot'] = '1'
+
+    if overload.is_virtual:
+        attrib['virtual'] = '1'
+
+    if extends is not None:
+        attrib['extends'] = ClassFormatter(spec, extends).fq_py_name
+
+    function_el = SubElement(parent, 'Function', attrib,
+            name=format_scoped_py_name(scope, name),
+            realname=_realname(scope, cpp_name))
+
+    # An empty type hint specifies a void return.
+    result = overload.py_signature.result
+
+    if result.type_hints is not None and result.type_hints.hint_out is not None and result.type_hints.hint_out == '':
+        no_result = True
+    else:
+        no_result = (result.type is ArgumentType.VOID and len(result.derefs) == 0)
+
+    if not no_result:
+        _argument(function_el, spec, overload.py_signature.result, KwArgs.NONE,
+                out=True,
+                transfer_result=overload.transfer is Transfer.TRANSFER_BACK)
+
+    # Ignore the first argument of non-reflected number slots and the second
+    # argument of reflected number slots.
+    might_ignore = (is_number_slot(overload.common.py_slot) and len(overload.py_signature.args) == 2)
+
+    for a, arg in enumerate(overload.py_signature.args):
+        if might_ignore:
+            if a == 0 and not overload.is_reflected:
+                continue
+
+            if a == 1 and overload.is_reflected:
+                continue
+
+        if arg.is_in:
+            _argument(function_el, spec, arg, overload.kw_args)
+
+        if arg.is_out:
+            _argument(function_el, spec, arg, overload.kw_args, out=True)
+
+
+# Argument types that imply handwritten code.
+_HANDWRITTEN_CODE_TYPES = (
+    ArgumentType.PYOBJECT,
+    ArgumentType.PYTUPLE,
+    ArgumentType.PYLIST,
+    ArgumentType.PYDICT,
+    ArgumentType.PYCALLABLE,
+    ArgumentType.PYSLICE,
+    ArgumentType.PYTYPE,
+    ArgumentType.PYBUFFER,
+    ArgumentType.PYENUM,
+    ArgumentType.CAPSULE,
+)
+
+def _has_cpp_signature(signature):
+    """ Return True if there is a C/C++ signature. """
+
+    if signature is None:
+        return False
+
+    # See if there are any arguments that could only have come from handwritten
+    # code.
+    for arg in signature.args:
+        if arg.type in _HANDWRITTEN_CODE_TYPES:
+            return False
+
+    return True
+
+
+def _cpp_signature(spec, signature, is_const=False):
+    """ Return the XML for a C++ signature. """
+
+    formatter = SignatureFormatter(spec, signature)
+
+    args = formatter.cpp_arguments(strip=STRIP_GLOBAL, make_public=True,
+        as_xml=True)
+    const = ' const' if is_const else ''
+
+    return f'({args}){const}'
+
+
+def _argument(parent, spec, arg, kw_args, out=False,
+        transfer_result=False):
+    """ Ouput the XML for an argument. """
+
+    if arg.array is not ArrayArgument.ARRAY_SIZE:
+        attrib = {}
+
+        if not out:
+            if arg.allow_none:
+                attrib['allownone'] = '1'
+
+            if arg.disallow_none:
+                attrib['disallownone'] = '1'
+
+            if arg.transfer is Transfer.TRANSFER:
+                attrib['transfer'] = 'to'
+            elif arg.transfer is Transfer.TRANSFER_THIS:
+                attrib['transfer'] = 'this'
+
+        if transfer_result or arg.transfer is Transfer.TRANSFER_BACK:
+            attrib['transfer'] = 'back'
+
+        SubElement(parent, 'Return' if out else 'Argument', attrib,
+                typename=_typename(spec, arg, kw_args=kw_args, out=out))
+
+
+def _typename(spec, arg, kw_args=KwArgs.NONE, out=False):
+    """ Return the XML for a type. """
+
+    s = ''
+
+    # Handle the argument name.
+    if not out and arg.name is not None:
+        if kw_args is KwArgs.ALL or (kw_args is KwArgs.OPTIONAL and arg.default_value is not None):
+            s += arg.name.name + ': '
+
+    s += ArgumentFormatter(spec, arg).as_rest_ref(out)
+
+    if not out and arg.name is not None and arg.default_value is not None:
+        s += ' = '
+
+        # Try and convert the value to a reST reference.  We don't try very
+        # hard but will get most cases.
+        rest_ref = ValueListFormatter(spec, arg.default_value).as_rest_ref()
+        if rest_ref is None:
+            rest_ref = formatter.py_default_value()
+
+        s += rest_ref
+
+    return s
+
+
+# A map of slots and the names of their reflections.
+_SLOT_REFLECTIONS = {
+    PySlot.ADD: '__radd__',
+    PySlot.SUB: '__rsub__',
+    PySlot.MUL: '__rmul__',
+    PySlot.MATMUL: '__rmatmul__',
+    PySlot.TRUEDIV: '__rtruediv__',
+    PySlot.FLOORDIV: '__rfloordiv__',
+    PySlot.MOD: '__rmod__',
+    PySlot.LSHIFT: '__rlshift__',
+    PySlot.RSHIFT: '__rrshift__',
+    PySlot.AND: '__rand__',
+    PySlot.OR: '__ror__',
+    PySlot.XOR: '__rxor__',
+}
+
+def _reflected_slot(py_slot):
+    """ Return the name of the reflected version of a slot or None if it
+    doesn't have one.
+    """
+
+    return _SLOT_REFLECTIONS.get(py_slot)
diff --git a/sipbuild/generator/parser/parser.py b/sipbuild/generator/parser/parser.py
index 66db745..54e613f 100644
--- a/sipbuild/generator/parser/parser.py
+++ b/sipbuild/generator/parser/parser.py
@@ -25,11 +25,13 @@ from .parser_manager import ParserManager
 
 
 def parse(sip_file, hex_version, encoding, abi_version, tags,
-        disabled_features, protected_is_public, include_dirs, strict=True):
+        disabled_features, protected_is_public, include_dirs, sip_module,
+        is_strict=True):
     """ Parse a .sip specification file returning a corresponding Specification
     object and a list of the .sip files that define the module to be generated.
     """
 
     return ParserManager(
             hex_version, encoding, abi_version, tags, disabled_features,
-            protected_is_public, include_dirs, strict).parse(sip_file)
+            protected_is_public, include_dirs, sip_module, is_strict).parse(
+                    sip_file)
diff --git a/sipbuild/generator/parser/parser_manager.py b/sipbuild/generator/parser/parser_manager.py
index ed00e43..b55c5f6 100644
--- a/sipbuild/generator/parser/parser_manager.py
+++ b/sipbuild/generator/parser/parser_manager.py
@@ -41,7 +41,6 @@ from ..specification import (AccessSpecifier, Argument, ArgumentType,
         Specification, Transfer, TypeHints, WrappedClass, WrappedException,
         WrappedEnum, WrappedEnumMember)
 from ..templates import encoded_template_name, same_template_signature
-from ..type_hints import get_type_hint
 from ..utils import (argument_as_str, cached_name, find_iface_file,
         normalised_scoped_name, same_base_type)
 
@@ -56,7 +55,8 @@ class ParserManager:
     """
 
     def __init__(self, hex_version, encoding, abi_version, tags,
-            disabled_features, protected_is_public, include_dirs, strict):
+            disabled_features, protected_is_public, include_dirs, sip_module,
+            is_strict):
         """ Initialise the manager. """
 
         # Create the lexer.
@@ -78,7 +78,8 @@ class ParserManager:
         self.tags = tags
 
         self.spec = Specification(
-                tuple([int(v) for v in abi_version.split('.')]), strict)
+                tuple([int(v) for v in abi_version.split('.')]), is_strict,
+                sip_module)
 
         self.c_bindings = None
         self.code_block = None
@@ -1418,31 +1419,27 @@ class ParserManager:
         None if none were specified.
         """
 
-        th_text = annotations.get('TypeHint')
-        th_in_text = annotations.get('TypeHintIn')
-        th_out_text = annotations.get('TypeHintOut')
+        th = annotations.get('TypeHint')
+        th_in = annotations.get('TypeHintIn')
+        th_out = annotations.get('TypeHintOut')
         th_value = annotations.get('TypeHintValue')
 
-        if th_in_text is None:
-            th_in_text = th_text
-        elif th_text is not None:
+        if th_in is None:
+            th_in = th
+        elif th is not None:
             self.parser_error(p, symbol,
                     "'TypeHint' and 'TypeHintIn' cannot both be specified")
 
             return None
 
-        th_in = None if th_in_text is None else get_type_hint(self.spec, th_in_text)
-
-        if th_out_text is None:
-            th_out_text = th_text
-        elif th_text is not None:
+        if th_out is None:
+            th_out = th
+        elif th is not None:
             self.parser_error(p, symbol,
                     "'TypeHint' and 'TypeHintOut' cannot both be specified")
 
             return None
 
-        th_out = None if th_out_text is None else get_type_hint(self.spec, th_out_text)
-
         if th_in is not None or th_out is not None or th_value is not None:
             # Check that type hints haven't been suppressed.
             if annotations.get('NoTypeHint') is not None:
diff --git a/sipbuild/generator/resolver/resolver.py b/sipbuild/generator/resolver/resolver.py
index 6585113..6097a78 100644
--- a/sipbuild/generator/resolver/resolver.py
+++ b/sipbuild/generator/resolver/resolver.py
@@ -601,7 +601,7 @@ def _move_global_slot(spec, mod, global_slot, error_log):
 
         # Move the overload to the end of the destination list.
         if is_second:
-            overload.is_reflected
+            overload.is_reflected = True
 
         overload.access_specifier = AccessSpecifier.PUBLIC
         overload.common = arg_member
@@ -2190,9 +2190,10 @@ def _check_properties(klass, error_log):
 def _log_overload_error(error_log, text, overload, scope=None):
     """ Log an error about an overload. """
 
-    from ..formatters import OverloadFormatter
-
-    formatter = OverloadFormatter(overload, scope)
+    if scope is None:
+        fq_cpp_name = overload.cpp_name
+    else:
+        fq_cpp_name = f'{scope.iface_file.fq_cpp_name}::{overload.cpp_name}'
 
-    error_log.log(f"'{formatter.fq_cpp_name}' {text}",
+    error_log.log(f"'{fq_cpp_name}' {text}",
             source_location=overload.source_location)
diff --git a/sipbuild/generator/specification.py b/sipbuild/generator/specification.py
index 4c8ea86..62f711d 100644
--- a/sipbuild/generator/specification.py
+++ b/sipbuild/generator/specification.py
@@ -1289,6 +1289,9 @@ class Specification:
     # Set if the specification is strict.
     is_strict: bool
 
+    # The name of the sip module.
+    sip_module: str
+
     # Set if the bindings are for C rather than C++.
     c_bindings: bool = False
 
@@ -1374,23 +1377,15 @@ class ThrowArguments:
     arguments: Optional[List['WrappedException']] = None
 
 
- at dataclass
-class TypeHint:
-    """ Encapsulate a PEP 484 type hint. """
-
-    # The text of the hint.
-    text: str
-
-
 @dataclass
 class TypeHints:
     """ Encapsulate a set of PEP 484 type hints for a type. """
 
     # The type hint when used to pass a value into a callable.
-    hint_in: Optional[TypeHint]
+    hint_in: Optional[str]
 
     # The type hint used to return a value from a callable.
-    hint_out: Optional[TypeHint]
+    hint_out: Optional[str]
 
     # The representation of a default value in a type hint.
     default_value: Optional[str]
diff --git a/sipbuild/generator/type_hints.py b/sipbuild/generator/type_hints.py
deleted file mode 100644
index 872b6be..0000000
--- a/sipbuild/generator/type_hints.py
+++ /dev/null
@@ -1,70 +0,0 @@
-# Copyright (c) 2022, Riverbank Computing Limited
-# All rights reserved.
-#
-# This copy of SIP is licensed for use under the terms of the SIP License
-# Agreement.  See the file LICENSE for more details.
-#
-# This copy of SIP may also used under the terms of the GNU General Public
-# License v2 or v3 as published by the Free Software Foundation which can be
-# found in the files LICENSE-GPL2 and LICENSE-GPL3 included in this package.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
-# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
-# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
-# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
-# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
-# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
-# POSSIBILITY OF SUCH DAMAGE.
-
-
-from weakref import WeakKeyDictionary
-
-from .specification import TypeHint
-
-
-class TypeHintManager:
-    """ A manager for type hints on behalf of a Specification object. """
-
-    # The map of specification objects and the corresponding manager object.
-    _spec_manager_map = WeakKeyDictionary()
-
-    def __new__(cls, spec):
-        """ Return the existing manager for a specification or create a new one
-        if necessary.
-        """
-
-        try:
-            manager = cls._spec_manager_map[spec]
-        except KeyError:
-            manager = object.__new__(cls)
-            cls._spec_manager_map[spec] = manager
-
-        return manager
-
-    def __init__(self, spec):
-        """ Initialise the manager. """
-
-        self._type_hints = {}
-
-    def get_type_hint(self, text):
-        """ Return a unique (to the specification) TypeHint object for the text
-        of a hint.
-        """
-
-        try:
-            type_hint = self._type_hints[text]
-        except KeyError:
-            type_hint = TypeHint(text)
-            self._type_hints[text] = type_hint
-
-        return type_hint
-
-
-def get_type_hint(spec, text):
-    """ Return a unique TypeHint object for the text of a hint. """
-
-    return TypeHintManager(spec).get_type_hint(text)
diff --git a/sipbuild/version.py b/sipbuild/version.py
index e31c7a6..918f304 100644
--- a/sipbuild/version.py
+++ b/sipbuild/version.py
@@ -1,2 +1,2 @@
-SIP_VERSION = 0x060702
-SIP_VERSION_STR = '6.7.2'
+SIP_VERSION = 0x060703
+SIP_VERSION_STR = '6.7.3'



More information about the Neon-commits mailing list