#!/usr/bin/env python3 """Check the empty-bases declaration and preserve tuple allocation timing.""" import re from pathlib import Path source = Path("cpython/Objects/typeobject.c").read_text() match = re.search( r"\nstatic int\ntype_new_get_bases\(type_new_ctx \*ctx, PyObject \*\*type\)\n\{(.*?)\n\}", source, re.S, ) assert match, "type_new_get_bases definition missing" body = re.sub(r"/\*.*?\*/|//[^\n]*", "", match.group(1), flags=re.S) assert re.search( r"if \(nbases == 0\) \{\s*PyObject \*new_bases;\s*ctx->base =", body, ), "type_new_get_bases: new_bases must open the empty-bases block (C89)" assert len(re.findall(r"\bPyObject\s*\*\s*new_bases\b", body)) == 1, ( "declare new_bases exactly once" ) assert len(re.findall(r"\bnew_bases\s*=(?!=)", body)) == 1, ( "assign new_bases exactly once" ) prefix = body.split("\n for (", 1)[0] # Permit the separately tested C89 loop-index and winner declarations. prefix = re.sub( r"\n (?:Py_ssize_t i|PyTypeObject \*(?:winner|base));", "", prefix ) expected = """ Py_ssize_t nbases = PyTuple_GET_SIZE(ctx->bases); if (nbases == 0) { PyObject *new_bases; ctx->base = &PyBaseObject_Type; new_bases = PyTuple_Pack(1, ctx->base); if (new_bases == NULL) { return -1; } ctx->bases = new_bases; return 0; } """ assert prefix.split() == expected.split(), ( "preserve empty-bases guard, base assignment, tuple allocation, NULL check, " "bases transfer, and early returns at their original evaluation points" ) print("type_new_get_bases new_bases C89 declaration and allocation timing: PASS")