Skip to content

Commit

Permalink
[mypyc] Fixing __init__ for classes with @attr.s(slots=True). (#18447)
Browse files Browse the repository at this point in the history
Fixes mypyc/mypyc#1079.

`@attr.s` generates a `__init__` function which was getting lost in
`CPyDataclass_SleightOfHand`. This change copies the generated
`__init__` function and a couple of others ones to maintain consistency
with CPython.
  • Loading branch information
advait-dixit authored Jan 17, 2025
1 parent 55a8840 commit c61bce4
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 6 deletions.
3 changes: 2 additions & 1 deletion mypyc/irbuild/classdef.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,9 +381,10 @@ def finalize(self, ir: ClassIR) -> None:
dec = self.builder.accept(
next(d for d in self.cdef.decorators if is_dataclass_decorator(d))
)
dataclass_type_val = self.builder.load_str(dataclass_type(self.cdef) or "unknown")
self.builder.call_c(
dataclass_sleight_of_hand,
[dec, self.type_obj, self.non_ext.dict, self.non_ext.anns],
[dec, self.type_obj, self.non_ext.dict, self.non_ext.anns, dataclass_type_val],
self.cdef.line,
)

Expand Down
2 changes: 2 additions & 0 deletions mypyc/irbuild/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ def is_dataclass(cdef: ClassDef) -> bool:
return any(is_dataclass_decorator(d) for d in cdef.decorators)


# The string values returned by this function are inspected in
# mypyc/lib-rt/misc_ops.c:CPyDataclass_SleightOfHand(...).
def dataclass_type(cdef: ClassDef) -> str | None:
for d in cdef.decorators:
typ = dataclass_decorator_type(d)
Expand Down
3 changes: 2 additions & 1 deletion mypyc/lib-rt/CPy.h
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,8 @@ PyObject *CPyType_FromTemplateWrapper(PyObject *template_,
PyObject *orig_bases,
PyObject *modname);
int CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
PyObject *dict, PyObject *annotations);
PyObject *dict, PyObject *annotations,
PyObject *dataclass_type);
PyObject *CPyPickle_SetState(PyObject *obj, PyObject *state);
PyObject *CPyPickle_GetState(PyObject *obj);
CPyTagged CPyTagged_Id(PyObject *o);
Expand Down
28 changes: 25 additions & 3 deletions mypyc/lib-rt/misc_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -347,13 +347,15 @@ static int _CPy_UpdateObjFromDict(PyObject *obj, PyObject *dict)
* tp: The class we are making a dataclass
* dict: The dictionary containing values that dataclasses needs
* annotations: The type annotation dictionary
* dataclass_type: A str object with the return value of util.py:dataclass_type()
*/
int
CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
PyObject *dict, PyObject *annotations) {
PyObject *dict, PyObject *annotations,
PyObject *dataclass_type) {
PyTypeObject *ttp = (PyTypeObject *)tp;
Py_ssize_t pos;
PyObject *res;
PyObject *res = NULL;

/* Make a copy of the original class __dict__ */
PyObject *orig_dict = PyDict_Copy(ttp->tp_dict);
Expand Down Expand Up @@ -381,17 +383,37 @@ CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
if (!res) {
goto fail;
}
Py_DECREF(res);
const char *dataclass_type_ptr = PyUnicode_AsUTF8(dataclass_type);
if (dataclass_type_ptr == NULL) {
goto fail;
}
if (strcmp(dataclass_type_ptr, "attr") == 0 ||
strcmp(dataclass_type_ptr, "attr-auto") == 0) {
// These attributes are added or modified by @attr.s(slots=True).
const char * const keys[] = {"__attrs_attrs__", "__attrs_own_setattr__", "__init__", ""};
for (const char * const *key_iter = keys; **key_iter != '\0'; key_iter++) {
PyObject *value = NULL;
int rv = PyObject_GetOptionalAttrString(res, *key_iter, &value);
if (rv == 1) {
PyObject_SetAttrString(tp, *key_iter, value);
Py_DECREF(value);
} else if (rv == -1) {
goto fail;
}
}
}

/* Copy back the original contents of the dict */
if (_CPy_UpdateObjFromDict(tp, orig_dict) != 0) {
goto fail;
}

Py_DECREF(res);
Py_DECREF(orig_dict);
return 1;

fail:
Py_XDECREF(res);
Py_XDECREF(orig_dict);
return 0;
}
Expand Down
8 changes: 7 additions & 1 deletion mypyc/primitives/misc_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,13 @@
# Create a dataclass from an extension class. See
# CPyDataclass_SleightOfHand for more docs.
dataclass_sleight_of_hand = custom_op(
arg_types=[object_rprimitive, object_rprimitive, dict_rprimitive, dict_rprimitive],
arg_types=[
object_rprimitive,
object_rprimitive,
dict_rprimitive,
dict_rprimitive,
str_rprimitive,
],
return_type=bit_rprimitive,
c_function_name="CPyDataclass_SleightOfHand",
error_kind=ERR_FALSE,
Expand Down
14 changes: 14 additions & 0 deletions mypyc/test-data/run-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -2705,3 +2705,17 @@ print(native.ColorCode.OKGREEN.value)

[out]
okgreen

[case testAttrWithSlots]
import attr

@attr.s(slots=True)
class A:
ints: list[int] = attr.ib()

[file driver.py]
import native
print(native.A(ints=[1, -17]).ints)

[out]
\[1, -17]

0 comments on commit c61bce4

Please sign in to comment.