#!/usr/bin/env python3 """Check only the two static type initializer mappings at the VC6 frontier.""" import re from pathlib import Path source = Path('cpython/Objects/typeobject.c').read_text() header = Path('cpython/Include/cpython/object.h').read_text() def uncomment(text): return re.sub(r'/\*.*?\*/|//[^\n]*', '', text, flags=re.S) def check(name, declaration, layout, prefix, expected, head=''): match = re.search(declaration + ' ' + name + r' = \{(.*?)\n\};', source, re.S) assert match, name + ': initializer missing' body = uncomment(match.group(1)).strip() assert not re.search(r'\.\w+\s*=', body), name + ': designated initializer is not VC6/C89 compatible' if head: assert body.startswith(head), name + ': object header changed' body = body[len(head):] fields = re.findall(r'\b(' + prefix + r'\w+)\b', uncomment(layout)) assert len(fields) == len(set(fields)) == (36 if prefix == 'nb_' else 48) # Split only at top-level commas (offsetof arguments contain commas). values = [v.strip() for v in re.split(r',\s*(?![^()]*\))', body) if v.strip()] assert len(values) <= len(fields), name + ': excess initializer entries' values += ['0'] * (len(fields) - len(values)) normalize = lambda value: re.sub(r'\s+', '', value) actual = dict(zip(fields, map(normalize, values))) wanted = {field: normalize(expected.get(field, '0')) for field in fields} assert expected.keys() <= actual.keys(), name + ': expected fields absent from header' assert actual == wanted, name + ': field mappings changed: ' + repr({k: (actual[k], wanted[k]) for k in fields if actual[k] != wanted[k]}) print(f'{name}: C89 positional structure and all {len(fields)} field mappings PASS') number_layout = re.search(r'typedef struct \{([^{}]*)\} PyNumberMethods;', header, re.S) assert number_layout, 'PyNumberMethods layout missing' check('type_as_number', 'static PyNumberMethods', number_layout.group(1), 'nb_', { 'nb_or': '_Py_union_type_or', }) type_layout = re.search(r'struct _typeobject \{(.*?)\n\};', header, re.S) assert type_layout, 'PyTypeObject layout missing' check('PyType_Type', 'PyTypeObject', type_layout.group(1), 'tp_', { 'tp_name': '"type"', 'tp_basicsize': 'sizeof(PyHeapTypeObject)', 'tp_itemsize': 'sizeof(PyMemberDef)', 'tp_dealloc': '(destructor)type_dealloc', 'tp_vectorcall_offset': 'offsetof(PyTypeObject, tp_vectorcall)', 'tp_repr': '(reprfunc)type_repr', 'tp_as_number': '&type_as_number', 'tp_call': '(ternaryfunc)type_call', 'tp_getattro': '(getattrofunc)type_getattro', 'tp_setattro': '(setattrofunc)type_setattro', 'tp_flags': 'Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_TYPE_SUBCLASS | Py_TPFLAGS_HAVE_VECTORCALL', 'tp_doc': 'type_doc', 'tp_traverse': '(traverseproc)type_traverse', 'tp_clear': '(inquiry)type_clear', 'tp_weaklistoffset': 'offsetof(PyTypeObject, tp_weaklist)', 'tp_methods': 'type_methods', 'tp_members': 'type_members', 'tp_getset': 'type_getsets', 'tp_dictoffset': 'offsetof(PyTypeObject, tp_dict)', 'tp_init': 'type_init', 'tp_new': 'type_new', 'tp_free': 'PyObject_GC_Del', 'tp_is_gc': '(inquiry)type_is_gc', 'tp_vectorcall': 'type_vectorcall', }, 'PyVarObject_HEAD_INIT(&PyType_Type, 0)')