#!/usr/bin/env python3 """Check NotImplemented's positional number slots against the CPython header.""" import re import sys from pathlib import Path source = Path(sys.argv[1]).read_text() header = Path(sys.argv[2]).read_text() layout = re.search(r"typedef struct \{([^{}]*)\} PyNumberMethods;", header, re.S) assert layout, "PyNumberMethods definition not found" fields = re.findall(r"\b(nb_\w+)\s*;", layout.group(1)) assert len(fields) == 36 and fields.index("nb_bool") == 9, ( "expected 36 PyNumberMethods slots with nb_bool in slot 10" ) initializer = re.search( r"static PyNumberMethods notimplemented_as_number\s*=\s*\{([^{}]*)\};", source, re.S, ) assert initializer, "notimplemented_as_number initializer not found" body = re.sub(r"/\*.*?\*/", "", initializer.group(1), flags=re.S) assert not re.search(r"\.\s*\w+\s*=", body), ( "notimplemented_as_number must use a VC6-compatible positional initializer" ) slots = [value.strip() for value in body.strip().rstrip(",").split(",")] assert len(slots) == len(fields), "every PyNumberMethods slot must be explicit" for field, value in zip(fields, slots): expected = "notimplemented_bool" if field == "nb_bool" else "0" assert value == expected, f"{field}: expected {expected}, got {value}" print("object NotImplemented C89 positional initializer: PASS (36 slots; nb_bool slot 10)") start = source.index("_PyTypes_InitTypes(PyInterpreterState *interp)\n{") opening = source.index("{", start) end = source.index("\n}\n", opening) init_types = source[opening:end] assert re.match(r"\{\s*size_t i;\s*if \(!_Py_IsMainInterpreter\(interp\)\)", init_types), ( "_PyTypes_InitTypes must declare size_t i before the interpreter guard" ) assert len(re.findall(r"\bsize_t\s+i\b", init_types)) == 1, ( "_PyTypes_InitTypes must declare its loop counter exactly once" ) assert "for (i=0; i < Py_ARRAY_LENGTH(static_types); i++) {" in init_types, ( "_PyTypes_InitTypes must use the C89 counter with unchanged loop bounds/traversal" ) assert init_types.index("_Py_GenericAlias_InitTypes();") < init_types.index("for (i=0;"), ( "_PyTypes_InitTypes loop initialization must remain after VC6 type initialization" ) print("object _PyTypes_InitTypes C89 loop counter declaration and initialization order: PASS") start = source.index("_PyTypes_FiniTypes(PyInterpreterState *interp)\n{") opening = source.index("{", start) end = source.index("\n}\n", opening) fini_types = source[opening:end] assert re.match(r"\{\s*Py_ssize_t i;\s*if \(!_Py_IsMainInterpreter\(interp\)\)", fini_types), ( "_PyTypes_FiniTypes must declare Py_ssize_t i before the interpreter guard" ) assert len(re.findall(r"\bPy_ssize_t\s+i\b", fini_types)) == 1, ( "_PyTypes_FiniTypes must declare its signed loop counter exactly once" ) expected = '''{ Py_ssize_t i; if (!_Py_IsMainInterpreter(interp)) { return; } for (i=Py_ARRAY_LENGTH(static_types)-1; i>=0; i--) { PyTypeObject *type = static_types[i]; _PyStaticType_Dealloc(type); } ''' assert re.sub(r"//[^\n]*", "", fini_types).split() == expected.split(), ( "_PyTypes_FiniTypes must preserve the interpreter guard, reverse loop bounds/traversal, " "and block-opening type lookup before each deallocation" ) print("object _PyTypes_FiniTypes C89 loop counter and reverse per-item deallocation order: PASS")