]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-94673: [c-analyzer] Add a Script to Identify Static Types (#94989)
authorEric Snow <ericsnowcurrently@gmail.com>
Tue, 19 Jul 2022 01:03:57 +0000 (19:03 -0600)
committerGitHub <noreply@github.com>
Tue, 19 Jul 2022 01:03:57 +0000 (19:03 -0600)
issue: https://github.com/python/cpython/issues/94673

Objects/exceptions.c
Tools/c-analyzer/c_common/tables.py
Tools/c-analyzer/cpython/__main__.py
Tools/c-analyzer/cpython/_builtin_types.py [new file with mode: 0644]
Tools/c-analyzer/cpython/globals-to-fix.tsv

index e212e02352efb37e57a792c442598206b9788926..9aab683fd1ca7f8fa8f1e5997675b6423e9cd580 100644 (file)
@@ -600,17 +600,9 @@ StopIteration_traverse(PyStopIterationObject *self, visitproc visit, void *arg)
     return BaseException_traverse((PyBaseExceptionObject *)self, visit, arg);
 }
 
-ComplexExtendsException(
-    PyExc_Exception,       /* base */
-    StopIteration,         /* name */
-    StopIteration,         /* prefix for *_init, etc */
-    0,                     /* new */
-    0,                     /* methods */
-    StopIteration_members, /* members */
-    0,                     /* getset */
-    0,                     /* str */
-    "Signal the end from iterator.__next__()."
-);
+ComplexExtendsException(PyExc_Exception, StopIteration, StopIteration,
+                        0, 0, StopIteration_members, 0, 0,
+                        "Signal the end from iterator.__next__().");
 
 
 /*
index 130be6beba5f81579a7c169ba999fc28fbda6fb1..fe8e8cf473de4f6aa3f4d28373dfe7b5c5353643 100644 (file)
@@ -1,3 +1,4 @@
+from collections import namedtuple
 import csv
 import re
 import textwrap
@@ -225,7 +226,11 @@ WIDTH = 20
 def resolve_columns(specs):
     if isinstance(specs, str):
         specs = specs.replace(',', ' ').strip().split()
-    return _resolve_colspecs(specs)
+    resolved = []
+    for raw in specs:
+        column = ColumnSpec.from_raw(raw)
+        resolved.append(column)
+    return resolved
 
 
 def build_table(specs, *, sep=' ', defaultwidth=None):
@@ -233,37 +238,145 @@ def build_table(specs, *, sep=' ', defaultwidth=None):
     return _build_table(columns, sep=sep, defaultwidth=defaultwidth)
 
 
-_COLSPEC_RE = re.compile(textwrap.dedent(r'''
-    ^
-    (?:
-        \[
-        (
-            (?: [^\s\]] [^\]]* )?
-            [^\s\]]
-        )  # <label>
-        ]
-    )?
-    ( \w+ )  # <field>
-    (?:
+class ColumnSpec(namedtuple('ColumnSpec', 'field label fmt')):
+
+    REGEX = re.compile(textwrap.dedent(r'''
+        ^
         (?:
-            :
-            ( [<^>] )  # <align>
-            ( \d+ )  # <width1>
-        )
-        |
+            \[
+            (
+                (?: [^\s\]] [^\]]* )?
+                [^\s\]]
+            )  # <label>
+            ]
+        )?
+        ( [-\w]+ )  # <field>
         (?:
             (?:
                 :
-                ( \d+ )  # <width2>
-            )?
+                ( [<^>] )  # <align>
+                ( \d+ )?  # <width1>
+            )
+            |
             (?:
-                :
-                ( .*? )  # <fmt>
-            )?
-        )
-    )?
-    $
-'''), re.VERBOSE)
+                (?:
+                    :
+                    ( \d+ )  # <width2>
+                )?
+                (?:
+                    :
+                    ( .*? )  # <fmt>
+                )?
+            )
+        )?
+        $
+    '''), re.VERBOSE)
+
+    @classmethod
+    def from_raw(cls, raw):
+        if not raw:
+            raise ValueError('missing column spec')
+        elif isinstance(raw, cls):
+            return raw
+
+        if isinstance(raw, str):
+            *values, _ = cls._parse(raw)
+        else:
+            *values, _ = cls._normalize(raw)
+        if values is None:
+            raise ValueError(f'unsupported column spec {raw!r}')
+        return cls(*values)
+
+    @classmethod
+    def parse(cls, specstr):
+        parsed = cls._parse(specstr)
+        if not parsed:
+            return None
+        *values, _ = parsed
+        return cls(*values)
+
+    @classmethod
+    def _parse(cls, specstr):
+        m = cls.REGEX.match(specstr)
+        if not m:
+            return None
+        (label, field,
+         align, width1,
+         width2, fmt,
+         ) = m.groups()
+        if not label:
+            label = field
+        if fmt:
+            assert not align and not width1, (specstr,)
+            _parsed = _parse_fmt(fmt)
+            if not _parsed:
+                raise NotImplementedError
+            elif width2:
+                width, _ = _parsed
+                if width != int(width2):
+                    raise NotImplementedError(specstr)
+        elif width2:
+            fmt = width2
+            width = int(width2)
+        else:
+            assert not fmt, (fmt, specstr)
+            if align:
+                width = int(width1) if width1 else len(label)
+                fmt = f'{align}{width}'
+            else:
+                width = None
+        return field, label, fmt, width
+
+    @classmethod
+    def _normalize(cls, spec):
+        if len(spec) == 1:
+            raw, = spec
+            raise NotImplementedError
+            return _resolve_column(raw)
+
+        if len(spec) == 4:
+            label, field, width, fmt = spec
+            if width:
+                if not fmt:
+                    fmt = str(width)
+                elif _parse_fmt(fmt)[0] != width:
+                    raise ValueError(f'width mismatch in {spec}')
+        elif len(raw) == 3:
+            label, field, fmt = spec
+            if not field:
+                label, field = None, label
+            elif not isinstance(field, str) or not field.isidentifier():
+                # XXX This doesn't seem right...
+                fmt = f'{field}:{fmt}' if fmt else field
+                label, field = None, label
+        elif len(raw) == 2:
+            label = None
+            field, fmt = raw
+            if not field:
+                field, fmt = fmt, None
+            elif not field.isidentifier() or fmt.isidentifier():
+                label, field = field, fmt
+        else:
+            raise NotImplementedError
+
+        fmt = f':{fmt}' if fmt else ''
+        if label:
+            return cls._parse(f'[{label}]{field}{fmt}')
+        else:
+            return cls._parse(f'{field}{fmt}')
+
+    @property
+    def width(self):
+        if not self.fmt:
+            return None
+        parsed = _parse_fmt(self.fmt)
+        if not parsed:
+            return None
+        width, _ = parsed
+        return width
+
+    def resolve_width(self, default=None):
+        return _resolve_width(self.width, self.fmt, self.label, default)
 
 
 def _parse_fmt(fmt):
@@ -272,100 +385,31 @@ def _parse_fmt(fmt):
         width = fmt[1:]
         if width.isdigit():
             return int(width), align
-    return None, None
+    elif fmt.isdigit():
+        return int(fmt), '<'
+    return None
 
 
-def _parse_colspec(raw):
-    m = _COLSPEC_RE.match(raw)
-    if not m:
-        return None
-    label, field, align, width1, width2, fmt = m.groups()
-    if not label:
-        label = field
-    if width1:
-        width = None
-        fmt = f'{align}{width1}'
-    elif width2:
-        width = int(width2)
-        if fmt:
-            _width, _ = _parse_fmt(fmt)
-            if _width == width:
-                width = None
-    else:
-        width = None
-    return field, label, width, fmt
-
-
-def _normalize_colspec(spec):
-    if len(spec) == 1:
-        raw, = spec
-        return _resolve_column(raw)
-
-    if len(spec) == 4:
-        label, field, width, fmt = spec
-        if width:
-            fmt = f'{width}:{fmt}' if fmt else width
-    elif len(raw) == 3:
-        label, field, fmt = spec
-        if not field:
-            label, field = None, label
-        elif not isinstance(field, str) or not field.isidentifier():
-            fmt = f'{field}:{fmt}' if fmt else field
-            label, field = None, label
-    elif len(raw) == 2:
-        label = None
-        field, fmt = raw
-        if not field:
-            field, fmt = fmt, None
-        elif not field.isidentifier() or fmt.isidentifier():
-            label, field = field, fmt
-    else:
-        raise NotImplementedError
-
-    fmt = f':{fmt}' if fmt else ''
-    if label:
-        return _parse_colspec(f'[{label}]{field}{fmt}')
-    else:
-        return _parse_colspec(f'{field}{fmt}')
-
-
-def _resolve_colspec(raw):
-    if isinstance(raw, str):
-        spec = _parse_colspec(raw)
-    else:
-        spec = _normalize_colspec(raw)
-    if spec is None:
-        raise ValueError(f'unsupported column spec {raw!r}')
-    return spec
-
-
-def _resolve_colspecs(columns):
-    parsed = []
-    for raw in columns:
-        column = _resolve_colspec(raw)
-        parsed.append(column)
-    return parsed
-
-
-def _resolve_width(spec, defaultwidth):
-    _, label, width, fmt = spec
+def _resolve_width(width, fmt, label, default):
     if width:
         if not isinstance(width, int):
             raise NotImplementedError
         return width
-    elif width and fmt:
-        width, _ = _parse_fmt(fmt)
-        if width:
-            return width
-
-    if not defaultwidth:
+    elif fmt:
+        parsed = _parse_fmt(fmt)
+        if parsed:
+            width, _ = parsed
+            if width:
+                return width
+
+    if not default:
         return WIDTH
-    elif not hasattr(defaultwidth, 'get'):
-        return defaultwidth or WIDTH
-
-    defaultwidths = defaultwidth
-    defaultwidth = defaultwidths.get(None) or WIDTH
-    return defaultwidths.get(label) or defaultwidth
+    elif hasattr(default, 'get'):
+        defaults = default
+        default = defaults.get(None) or WIDTH
+        return defaults.get(label) or default
+    else:
+        return default or WIDTH
 
 
 def _build_table(columns, *, sep=' ', defaultwidth=None):
@@ -373,16 +417,13 @@ def _build_table(columns, *, sep=' ', defaultwidth=None):
     div = []
     rowfmt = []
     for spec in columns:
-        label, field, _, colfmt = spec
-        width = _resolve_width(spec, defaultwidth)
-        if colfmt:
-            colfmt = f':{colfmt}'
-        else:
-            colfmt = f':{width}'
+        width = spec.resolve_width(defaultwidth)
+        colfmt = spec.fmt
+        colfmt = f':{spec.fmt}' if spec.fmt else f':{width}'
 
-        header.append(f' {{:^{width}}} '.format(label))
+        header.append(f' {{:^{width}}} '.format(spec.label))
         div.append('-' * (width + 2))
-        rowfmt.append(f' {{{field}{colfmt}}} ')
+        rowfmt.append(f' {{{spec.field}{colfmt}}} ')
     return (
         sep.join(header),
         sep.join(div),
index be331d50427d53300d52d1fe627678fd7832f6d6..2b9e4233b95ac46a24390aa7fe9384f40ba4a6d8 100644 (file)
@@ -20,7 +20,7 @@ import c_parser.__main__ as c_parser
 import c_analyzer.__main__ as c_analyzer
 import c_analyzer as _c_analyzer
 from c_analyzer.info import UNKNOWN
-from . import _analyzer, _capi, _files, _parser, REPO_ROOT
+from . import _analyzer, _builtin_types, _capi, _files, _parser, REPO_ROOT
 
 
 logger = logging.getLogger(__name__)
@@ -325,6 +325,47 @@ def cmd_capi(filenames=None, *,
         print(line)
 
 
+def _cli_builtin_types(parser):
+    parser.add_argument('--format', dest='fmt', default='table')
+#    parser.add_argument('--summary', dest='format',
+#                        action='store_const', const='summary')
+    def process_format(args, *, argv=None):
+        orig = args.fmt
+        args.fmt = _builtin_types.resolve_format(args.fmt)
+        if isinstance(args.fmt, str):
+            if args.fmt not in _builtin_types._FORMATS:
+                parser.error(f'unsupported format {orig!r}')
+
+    parser.add_argument('--include-modules', dest='showmodules',
+                        action='store_true')
+    def process_modules(args, *, argv=None):
+        pass
+
+    return [
+        process_format,
+        process_modules,
+    ]
+
+
+def cmd_builtin_types(fmt, *,
+                      showmodules=False,
+                      verbosity=VERBOSITY,
+                      ):
+    render = _builtin_types.get_renderer(fmt)
+    types = _builtin_types.iter_builtin_types()
+    match = _builtin_types.resolve_matcher(showmodules)
+    if match:
+        types = (t for t in types if match(t, log=lambda msg: logger.log(1, msg)))
+
+    lines = render(
+        types,
+#        verbose=verbosity > VERBOSITY,
+    )
+    print()
+    for line in lines:
+        print(line)
+
+
 # We do not define any other cmd_*() handlers here,
 # favoring those defined elsewhere.
 
@@ -354,6 +395,11 @@ COMMANDS = {
         [_cli_capi],
         cmd_capi,
     ),
+    'builtin-types': (
+        'show the builtin types',
+        [_cli_builtin_types],
+        cmd_builtin_types,
+    ),
 }
 
 
diff --git a/Tools/c-analyzer/cpython/_builtin_types.py b/Tools/c-analyzer/cpython/_builtin_types.py
new file mode 100644 (file)
index 0000000..faa0b7a
--- /dev/null
@@ -0,0 +1,365 @@
+from collections import namedtuple
+import os.path
+import re
+import textwrap
+
+from c_common import tables
+from . import REPO_ROOT
+from ._files import iter_header_files, iter_filenames
+
+
+CAPI_PREFIX = os.path.join('Include', '')
+INTERNAL_PREFIX = os.path.join('Include', 'internal', '')
+
+REGEX = re.compile(textwrap.dedent(rf'''
+    (?:
+        ^
+        (?:
+            (?:
+                (?:
+                    (?:
+                        (?:
+                            ( static )  # <static>
+                            \s+
+                            |
+                            ( extern )  # <extern>
+                            \s+
+                         )?
+                        PyTypeObject \s+
+                     )
+                    |
+                    (?:
+                        ( PyAPI_DATA )  # <capi>
+                        \s* [(] \s* PyTypeObject \s* [)] \s*
+                     )
+                 )
+                (\w+)  # <name>
+                \s*
+                (?:
+                    (?:
+                        ( = \s* {{ )  # <def>
+                        $
+                     )
+                    |
+                    ( ; )  # <decl>
+                 )
+             )
+            |
+            (?:
+                # These are specific to Objects/exceptions.c:
+                (?:
+                    SimpleExtendsException
+                    |
+                    MiddlingExtendsException
+                    |
+                    ComplexExtendsException
+                 )
+                \( \w+ \s* , \s*
+                ( \w+ )  # <excname>
+                \s* ,
+             )
+         )
+    )
+'''), re.VERBOSE)
+
+
+def _parse_line(line):
+    m = re.match(REGEX, line)
+    if not m:
+        return None
+    (static, extern, capi,
+     name,
+     def_, decl,
+     excname,
+     ) = m.groups()
+    if def_:
+        isdecl = False
+        if extern or capi:
+            raise NotImplementedError(line)
+        kind = 'static' if static else None
+    elif excname:
+        name = f'_PyExc_{excname}'
+        isdecl = False
+        kind = 'static'
+    else:
+        isdecl = True
+        if static:
+            kind = 'static'
+        elif extern:
+            kind = 'extern'
+        elif capi:
+            kind = 'capi'
+        else:
+            kind = None
+    return name, isdecl, kind
+
+
+class BuiltinTypeDecl(namedtuple('BuiltinTypeDecl', 'file lno name kind')):
+
+    KINDS = {
+        'static',
+        'extern',
+        'capi',
+        'forward',
+    }
+
+    @classmethod
+    def from_line(cls, line, filename, lno):
+        # This is similar to ._capi.CAPIItem.from_line().
+        parsed = _parse_line(line)
+        if not parsed:
+            return None
+        name, isdecl, kind = parsed
+        if not isdecl:
+            return None
+        return cls.from_parsed(name, kind, filename, lno)
+
+    @classmethod
+    def from_parsed(cls, name, kind, filename, lno):
+        if not kind:
+            kind = 'forward'
+        return cls.from_values(filename, lno, name, kind)
+
+    @classmethod
+    def from_values(cls, filename, lno, name, kind):
+        if kind not in cls.KINDS:
+            raise ValueError(f'unsupported kind {kind!r}')
+        self = cls(filename, lno, name, kind)
+        if self.kind not in ('extern', 'capi') and self.api:
+            raise NotImplementedError(self)
+        elif self.kind == 'capi' and not self.api:
+            raise NotImplementedError(self)
+        return self
+
+    @property
+    def relfile(self):
+        return self.file[len(REPO_ROOT) + 1:]
+
+    @property
+    def api(self):
+        return self.relfile.startswith(CAPI_PREFIX)
+
+    @property
+    def internal(self):
+        return self.relfile.startswith(INTERNAL_PREFIX)
+
+    @property
+    def private(self):
+        if not self.name.startswith('_'):
+            return False
+        return self.api and not self.internal
+
+    @property
+    def public(self):
+        if self.kind != 'capi':
+            return False
+        return not self.internal and not self.private
+
+
+class BuiltinTypeInfo(namedtuple('BuiltinTypeInfo', 'file lno name static decl')):
+
+    @classmethod
+    def from_line(cls, line, filename, lno, *, decls=None):
+        parsed = _parse_line(line)
+        if not parsed:
+            return None
+        name, isdecl, kind = parsed
+        if isdecl:
+            return None
+        return cls.from_parsed(name, kind, filename, lno, decls=decls)
+
+    @classmethod
+    def from_parsed(cls, name, kind, filename, lno, *, decls=None):
+        if not kind:
+            static = False
+        elif kind == 'static':
+            static = True
+        else:
+            raise NotImplementedError((filename, line, kind))
+        decl = decls.get(name) if decls else None
+        return cls(filename, lno, name, static, decl)
+
+    @property
+    def relfile(self):
+        return self.file[len(REPO_ROOT) + 1:]
+
+    @property
+    def exported(self):
+        return not self.static
+
+    @property
+    def api(self):
+        if not self.decl:
+            return False
+        return self.decl.api
+
+    @property
+    def internal(self):
+        if not self.decl:
+            return False
+        return self.decl.internal
+
+    @property
+    def private(self):
+        if not self.decl:
+            return False
+        return self.decl.private
+
+    @property
+    def public(self):
+        if not self.decl:
+            return False
+        return self.decl.public
+
+    @property
+    def inmodule(self):
+        return self.relfile.startswith('Modules' + os.path.sep)
+
+    def render_rowvalues(self, kinds):
+        row = {
+            'name': self.name,
+            **{k: '' for k in kinds},
+            'filename': f'{self.relfile}:{self.lno}',
+        }
+        if self.static:
+            kind = 'static'
+        else:
+            if self.internal:
+                kind = 'internal'
+            elif self.private:
+                kind = 'private'
+            elif self.public:
+                kind = 'public'
+            else:
+                kind = 'global'
+        row['kind'] = kind
+        row[kind] = kind
+        return row
+
+
+def _ensure_decl(decl, decls):
+    prev = decls.get(decl.name)
+    if prev:
+        if decl.kind == 'forward':
+            return None
+        if prev.kind != 'forward':
+            if decl.kind == prev.kind and decl.file == prev.file:
+                assert decl.lno != prev.lno, (decl, prev)
+                return None
+            raise NotImplementedError(f'duplicate {decl} (was {prev}')
+    decls[decl.name] = decl
+
+
+def iter_builtin_types(filenames=None):
+    decls = {}
+    seen = set()
+    for filename in iter_header_files():
+        seen.add(filename)
+        with open(filename) as infile:
+            for lno, line in enumerate(infile, 1):
+                decl = BuiltinTypeDecl.from_line(line, filename, lno)
+                if not decl:
+                    continue
+                _ensure_decl(decl, decls)
+    srcfiles = []
+    for filename in iter_filenames():
+        if filename.endswith('.c'):
+            srcfiles.append(filename)
+            continue
+        if filename in seen:
+            continue
+        with open(filename) as infile:
+            for lno, line in enumerate(infile, 1):
+                decl = BuiltinTypeDecl.from_line(line, filename, lno)
+                if not decl:
+                    continue
+                _ensure_decl(decl, decls)
+
+    for filename in srcfiles:
+        with open(filename) as infile:
+            localdecls = {}
+            for lno, line in enumerate(infile, 1):
+                parsed = _parse_line(line)
+                if not parsed:
+                    continue
+                name, isdecl, kind = parsed
+                if isdecl:
+                    decl = BuiltinTypeDecl.from_parsed(name, kind, filename, lno)
+                    if not decl:
+                        raise NotImplementedError((filename, line))
+                    _ensure_decl(decl, localdecls)
+                else:
+                    builtin = BuiltinTypeInfo.from_parsed(
+                            name, kind, filename, lno,
+                            decls=decls if name in decls else localdecls)
+                    if not builtin:
+                        raise NotImplementedError((filename, line))
+                    yield builtin
+
+
+def resolve_matcher(showmodules=False):
+    def match(info, *, log=None):
+        if not info.inmodule:
+            return True
+        if log is not None:
+            log(f'ignored {info.name!r}')
+        return False
+    return match
+
+
+##################################
+# CLI rendering
+
+def resolve_format(fmt):
+    if not fmt:
+        return 'table'
+    elif isinstance(fmt, str) and fmt in _FORMATS:
+        return fmt
+    else:
+        raise NotImplementedError(fmt)
+
+
+def get_renderer(fmt):
+    fmt = resolve_format(fmt)
+    if isinstance(fmt, str):
+        try:
+            return _FORMATS[fmt]
+        except KeyError:
+            raise ValueError(f'unsupported format {fmt!r}')
+    else:
+        raise NotImplementedError(fmt)
+
+
+def render_table(types):
+    types = sorted(types, key=(lambda t: t.name))
+    colspecs = tables.resolve_columns(
+            'name:<33 static:^ global:^ internal:^ private:^ public:^ filename:<30')
+    header, div, rowfmt = tables.build_table(colspecs)
+    leader = ' ' * sum(c.width+2 for c in colspecs[:3]) + '   '
+    yield leader + f'{"API":^29}'
+    yield leader + '-' * 29
+    yield header
+    yield div
+    kinds = [c[0] for c in colspecs[1:-1]]
+    counts = {k: 0 for k in kinds}
+    base = {k: '' for k in kinds}
+    for t in types:
+        row = t.render_rowvalues(kinds)
+        kind = row['kind']
+        yield rowfmt.format(**row)
+        counts[kind] += 1
+    yield ''
+    yield f'total: {sum(counts.values()):>3}'
+    for kind in kinds:
+        yield f'  {kind:>10}: {counts[kind]:>3}'
+
+
+def render_repr(types):
+    for t in types:
+        yield repr(t)
+
+
+_FORMATS = {
+    'table': render_table,
+    'repr': render_repr,
+}
index c92f64df1c5de5eff2d5f2e212b2769952e2e2c6..bc9d6aabb0936b9494be67510f7661ce78a03646 100644 (file)
@@ -7,7 +7,7 @@ filename        funcname        name    reason
 # global objects to fix in core code
 
 #-----------------------
-# static types
+# exported builtin types (C-API)
 
 Objects/boolobject.c   -       PyBool_Type     -
 Objects/bytearrayobject.c      -       PyByteArrayIter_Type    -
@@ -18,8 +18,6 @@ Objects/capsule.c     -       PyCapsule_Type  -
 Objects/cellobject.c   -       PyCell_Type     -
 Objects/classobject.c  -       PyInstanceMethod_Type   -
 Objects/classobject.c  -       PyMethod_Type   -
-Objects/codeobject.c   -       _PyLineIterator -
-Objects/codeobject.c   -       _PyPositionsIterator    -
 Objects/codeobject.c   -       PyCode_Type     -
 Objects/complexobject.c        -       PyComplex_Type  -
 Objects/descrobject.c  -       PyClassMethodDescr_Type -
@@ -42,16 +40,12 @@ Objects/dictobject.c        -       PyDictValues_Type       -
 Objects/dictobject.c   -       PyDict_Type     -
 Objects/enumobject.c   -       PyEnum_Type     -
 Objects/enumobject.c   -       PyReversed_Type -
-Objects/exceptions.c   -       _PyExc_BaseExceptionGroup       -
-Objects/exceptions.c   -       _PyExc_EncodingWarning  -
 Objects/fileobject.c   -       PyStdPrinter_Type       -
-Objects/floatobject.c  -       FloatInfoType   -
 Objects/floatobject.c  -       PyFloat_Type    -
 Objects/frameobject.c  -       PyFrame_Type    -
 Objects/funcobject.c   -       PyClassMethod_Type      -
 Objects/funcobject.c   -       PyFunction_Type -
 Objects/funcobject.c   -       PyStaticMethod_Type     -
-Objects/genericaliasobject.c   -       _Py_GenericAliasIterType        -
 Objects/genericaliasobject.c   -       Py_GenericAliasType     -
 Objects/genobject.c    -       PyAsyncGen_Type -
 Objects/genobject.c    -       PyCoro_Type     -
@@ -63,13 +57,10 @@ Objects/genobject.c -       _PyCoroWrapper_Type     -
 Objects/interpreteridobject.c  -       _PyInterpreterID_Type   -
 Objects/iterobject.c   -       PyCallIter_Type -
 Objects/iterobject.c   -       PySeqIter_Type  -
-Objects/iterobject.c   -       _PyAnextAwaitable_Type  -
 Objects/listobject.c   -       PyListIter_Type -
 Objects/listobject.c   -       PyListRevIter_Type      -
 Objects/listobject.c   -       PyList_Type     -
-Objects/longobject.c   -       Int_InfoType    -
 Objects/longobject.c   -       PyLong_Type     -
-Objects/memoryobject.c -       _PyMemoryIter_Type      -
 Objects/memoryobject.c -       PyMemoryView_Type       -
 Objects/memoryobject.c -       _PyManagedBuffer_Type   -
 Objects/methodobject.c -       PyCFunction_Type        -
@@ -91,7 +82,6 @@ Objects/rangeobject.c -       PyRange_Type    -
 Objects/setobject.c    -       PyFrozenSet_Type        -
 Objects/setobject.c    -       PySetIter_Type  -
 Objects/setobject.c    -       PySet_Type      -
-Objects/setobject.c    -       _PySetDummy_Type        -
 Objects/sliceobject.c  -       PyEllipsis_Type -
 Objects/sliceobject.c  -       PySlice_Type    -
 Objects/tupleobject.c  -       PyTupleIter_Type        -
@@ -99,11 +89,8 @@ Objects/tupleobject.c        -       PyTuple_Type    -
 Objects/typeobject.c   -       PyBaseObject_Type       -
 Objects/typeobject.c   -       PySuper_Type    -
 Objects/typeobject.c   -       PyType_Type     -
-Objects/unicodeobject.c        -       EncodingMapType -
 Objects/unicodeobject.c        -       PyUnicodeIter_Type      -
 Objects/unicodeobject.c        -       PyUnicode_Type  -
-Objects/unionobject.c  -       _PyUnion_Type   -
-Objects/unionobject.c  -       _Py_UnionType   -
 Objects/weakrefobject.c        -       _PyWeakref_CallableProxyType    -
 Objects/weakrefobject.c        -       _PyWeakref_ProxyType    -
 Objects/weakrefobject.c        -       _PyWeakref_RefType      -
@@ -113,8 +100,23 @@ Python/bltinmodule.c       -       PyZip_Type      -
 Python/context.c       -       PyContextToken_Type     -
 Python/context.c       -       PyContextVar_Type       -
 Python/context.c       -       PyContext_Type  -
+Python/traceback.c     -       PyTraceBack_Type        -
+
+#-----------------------
+# other exported builtin types
+
+# Not in a .h file:
+Objects/codeobject.c   -       _PyLineIterator -
+# Not in a .h file:
+Objects/codeobject.c   -       _PyPositionsIterator    -
+Objects/genericaliasobject.c   -       _Py_GenericAliasIterType        -
+# Not in a .h file:
+Objects/iterobject.c   -       _PyAnextAwaitable_Type  -
+# Not in a .h file:
+Objects/memoryobject.c -       _PyMemoryIter_Type      -
+#Objects/unicodeobject.c       -       _PyUnicodeASCIIIter_Type        -
+Objects/unionobject.c  -       _PyUnion_Type   -
 Python/context.c       -       _PyContextTokenMissing_Type     -
-Python/errors.c        -       UnraisableHookArgsType  -
 Python/hamt.c  -       _PyHamtItems_Type       -
 Python/hamt.c  -       _PyHamtKeys_Type        -
 Python/hamt.c  -       _PyHamtValues_Type      -
@@ -123,17 +125,32 @@ Python/hamt.c     -       _PyHamt_BitmapNode_Type -
 Python/hamt.c  -       _PyHamt_CollisionNode_Type      -
 Python/hamt.c  -       _PyHamt_Type    -
 Python/symtable.c      -       PySTEntry_Type  -
+
+#-----------------------
+# private static builtin types
+
+Objects/setobject.c    -       _PySetDummy_Type        -
+Objects/unicodeobject.c        -       EncodingMapType -
+#Objects/unicodeobject.c       -       PyFieldNameIter_Type    -
+#Objects/unicodeobject.c       -       PyFormatterIter_Type    -
+
+#-----------------------
+# static builtin structseq
+
+Objects/floatobject.c  -       FloatInfoType   -
+Objects/longobject.c   -       Int_InfoType    -
+Python/errors.c        -       UnraisableHookArgsType  -
 Python/sysmodule.c     -       AsyncGenHooksType       -
 Python/sysmodule.c     -       FlagsType       -
 Python/sysmodule.c     -       Hash_InfoType   -
 Python/sysmodule.c     -       VersionInfoType -
 Python/thread.c        -       ThreadInfoType  -
-Python/traceback.c     -       PyTraceBack_Type        -
 
 #-----------------------
 # builtin exception types
 
 Objects/exceptions.c   -       _PyExc_BaseException    -
+Objects/exceptions.c   -       _PyExc_BaseExceptionGroup       -
 Objects/exceptions.c   -       _PyExc_UnicodeEncodeError       -
 Objects/exceptions.c   -       _PyExc_UnicodeDecodeError       -
 Objects/exceptions.c   -       _PyExc_UnicodeTranslateError    -
@@ -197,9 +214,11 @@ Objects/exceptions.c       -       _PyExc_ImportWarning    -
 Objects/exceptions.c   -       _PyExc_UnicodeWarning   -
 Objects/exceptions.c   -       _PyExc_BytesWarning     -
 Objects/exceptions.c   -       _PyExc_ResourceWarning  -
+Objects/exceptions.c   -       _PyExc_EncodingWarning  -
 Objects/exceptions.c   -       PyExc_EnvironmentError  -
 Objects/exceptions.c   -       PyExc_IOError   -
 Objects/exceptions.c   -       PyExc_BaseException     -
+Objects/exceptions.c   -       PyExc_BaseExceptionGroup        -
 Objects/exceptions.c   -       PyExc_Exception -
 Objects/exceptions.c   -       PyExc_TypeError -
 Objects/exceptions.c   -       PyExc_StopAsyncIteration        -
@@ -263,6 +282,7 @@ Objects/exceptions.c        -       PyExc_ImportWarning     -
 Objects/exceptions.c   -       PyExc_UnicodeWarning    -
 Objects/exceptions.c   -       PyExc_BytesWarning      -
 Objects/exceptions.c   -       PyExc_ResourceWarning   -
+Objects/exceptions.c   -       PyExc_EncodingWarning   -
 
 #-----------------------
 # singletons
@@ -354,8 +374,6 @@ Objects/unicodeobject.c     -       static_strings  -
 # other
 
 # initialized once
-Objects/exceptions.c   -       PyExc_BaseExceptionGroup        -
-Objects/exceptions.c   -       PyExc_EncodingWarning   -
 # XXX This should have been found by the analyzer but wasn't:
 Python/context.c       -       _token_missing  -
 # XXX This should have been found by the analyzer but wasn't: