Skip to content

gh-117482: Preserve the Mapping Between Type Slot Name and Attributes #122866

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Lib/test/levenshtein_examples.json generated
Lib/test/test_stable_abi_ctypes.py generated
Lib/token.py generated
Misc/sbom.spdx.json generated
Modules/_testinternalcapi/tpslots_generated.h generated
Objects/typeslots.inc generated
PC/python3dll.c generated
Parser/parser.c generated
Expand Down
24 changes: 24 additions & 0 deletions Modules/_testinternalcapi.c
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,29 @@ gh_119213_getargs_impl(PyObject *module, PyObject *spam)
}


#include "_testinternalcapi/tpslots_generated.h"

static PyObject *
identify_type_slots(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject *slots = PyList_New(Py_ARRAY_LENGTH(slotdefs)-1);
if (slots == NULL) {
return NULL;
}
Py_ssize_t i;
const struct pytype_slot *p;
for (i = 0, p = slotdefs; p->slot != NULL; p++, i++) {
PyObject *item = Py_BuildValue("ss", p->slot, p->attr);
if (item == NULL) {
Py_DECREF(slots);
return NULL;
}
PyList_SET_ITEM(slots, i, item);
}
return slots;
}


static PyMethodDef module_functions[] = {
{"get_configs", get_configs, METH_NOARGS},
{"get_recursion_depth", get_recursion_depth, METH_NOARGS},
Expand Down Expand Up @@ -2129,6 +2152,7 @@ static PyMethodDef module_functions[] = {
{"uop_symbols_test", _Py_uop_symbols_test, METH_NOARGS},
#endif
GH_119213_GETARGS_METHODDEF
{"identify_type_slots", identify_type_slots, METH_NOARGS},
{NULL, NULL} /* sentinel */
};

Expand Down
120 changes: 120 additions & 0 deletions Modules/_testinternalcapi/tpslots_generated.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 94 additions & 1 deletion Tools/build/generate_global_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import io
import os.path
import re
import textwrap


SCRIPT_NAME = 'Tools/build/generate_global_objects.py'
__file__ = os.path.abspath(__file__)
Expand Down Expand Up @@ -175,6 +177,29 @@ def iter_global_strings():
yield varname, string, filename, lno, line


def iter_tp_slots():
regex = re.compile(r'^ +\w+SLOT\((\w+), (\w+),')
filename = os.path.join(ROOT, 'Objects', 'typeobject.c')
with open(filename, encoding='utf-8') as infile:
for line in infile:
if line.startswith('static pytype_slotdef slotdefs[] = {'):
break
for line in infile:
m = regex.match(line)
if not m:
line = line.strip()
if line == '{NULL}':
break
assert line != '};', (line,)
assert 'SLOT(' not in line, (line,)
continue
attr, slot = m.groups()
yield slot, attr
# Add in slots that aren't in slotdefs.
if slot == 'am_anext':
yield 'am_send', None


def iter_to_marker(lines, marker):
for line in lines:
if line.rstrip() == marker:
Expand Down Expand Up @@ -218,8 +243,15 @@ def block(self, prefix, suffix="", *, continuation=None):


@contextlib.contextmanager
def open_for_changes(filename, orig):
def open_for_changes(filename, orig=None):
"""Like open() but only write to the file if it changed."""
if orig is None:
try:
with open(filename, encoding='utf-8') as infile:
orig = infile.read()
except FileNotFoundError:
orig = None

outfile = io.StringIO()
yield outfile
text = outfile.getvalue()
Expand Down Expand Up @@ -447,6 +479,65 @@ def get_identifiers_and_strings() -> 'tuple[set[str], dict[str, str]]':
return identifiers, strings


#######################################
# info about types

def generate_tp_slot_names():
filename = os.path.join(
ROOT, 'Modules', '_testinternalcapi', 'tpslots_generated.h')
template = textwrap.dedent(f"""
{START}

struct pytype_slot {{
const char *slot;
const char *attr;
}};

// These are derived from the "slotdefs" array in Objects/typeobject.c.
static const struct pytype_slot slotdefs[] = {{
%s

/* sentinel */
{{NULL}}
}};

{END}
""")
subslots = {
'bf_': 'tp_as_buffer',
'am_': 'tp_as_async',
'nb_': 'tp_as_number',
'mp_': 'tp_as_mapping',
'sq_': 'tp_as_sequence',
}
rows = []
groups = []
for slot, attr in iter_tp_slots():
if slot.startswith('tp_'):
group = 'primary'
else:
subslot = slot
slot = subslots[slot[:3]]
group = slot[6:]
slot = f'{slot}.{subslot}'

if not groups:
groups.append(group)
elif groups[-1] != group:
assert group not in groups, (group, groups)
rows.append('')
rows.append(f' /* {group} */')
groups.append(group)

if attr is None:
rows.append(' /* Does not have a corresponding slot wrapper: */')
attr = 'NULL'
rows.append(f' {{"{slot}", "{attr}"}},')
text = template % (os.linesep.join(rows).strip())
with open_for_changes(filename) as outfile:
outfile.write(text)


#######################################
# the script

Expand All @@ -458,6 +549,8 @@ def main() -> None:
generate_static_strings_initializer(identifiers, strings)
generate_global_object_finalizers(generated_immortal_objects)

generate_tp_slot_names()


if __name__ == '__main__':
main()
Loading