Skip to content

Commit c61bce4

Browse files
authored
[mypyc] Fixing __init__ for classes with @attr.s(slots=True). (#18447)
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.
1 parent 55a8840 commit c61bce4

File tree

6 files changed

+52
-6
lines changed

6 files changed

+52
-6
lines changed

mypyc/irbuild/classdef.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -381,9 +381,10 @@ def finalize(self, ir: ClassIR) -> None:
381381
dec = self.builder.accept(
382382
next(d for d in self.cdef.decorators if is_dataclass_decorator(d))
383383
)
384+
dataclass_type_val = self.builder.load_str(dataclass_type(self.cdef) or "unknown")
384385
self.builder.call_c(
385386
dataclass_sleight_of_hand,
386-
[dec, self.type_obj, self.non_ext.dict, self.non_ext.anns],
387+
[dec, self.type_obj, self.non_ext.dict, self.non_ext.anns, dataclass_type_val],
387388
self.cdef.line,
388389
)
389390

mypyc/irbuild/util.py

+2
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ def is_dataclass(cdef: ClassDef) -> bool:
7373
return any(is_dataclass_decorator(d) for d in cdef.decorators)
7474

7575

76+
# The string values returned by this function are inspected in
77+
# mypyc/lib-rt/misc_ops.c:CPyDataclass_SleightOfHand(...).
7678
def dataclass_type(cdef: ClassDef) -> str | None:
7779
for d in cdef.decorators:
7880
typ = dataclass_decorator_type(d)

mypyc/lib-rt/CPy.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -860,7 +860,8 @@ PyObject *CPyType_FromTemplateWrapper(PyObject *template_,
860860
PyObject *orig_bases,
861861
PyObject *modname);
862862
int CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
863-
PyObject *dict, PyObject *annotations);
863+
PyObject *dict, PyObject *annotations,
864+
PyObject *dataclass_type);
864865
PyObject *CPyPickle_SetState(PyObject *obj, PyObject *state);
865866
PyObject *CPyPickle_GetState(PyObject *obj);
866867
CPyTagged CPyTagged_Id(PyObject *o);

mypyc/lib-rt/misc_ops.c

+25-3
Original file line numberDiff line numberDiff line change
@@ -347,13 +347,15 @@ static int _CPy_UpdateObjFromDict(PyObject *obj, PyObject *dict)
347347
* tp: The class we are making a dataclass
348348
* dict: The dictionary containing values that dataclasses needs
349349
* annotations: The type annotation dictionary
350+
* dataclass_type: A str object with the return value of util.py:dataclass_type()
350351
*/
351352
int
352353
CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
353-
PyObject *dict, PyObject *annotations) {
354+
PyObject *dict, PyObject *annotations,
355+
PyObject *dataclass_type) {
354356
PyTypeObject *ttp = (PyTypeObject *)tp;
355357
Py_ssize_t pos;
356-
PyObject *res;
358+
PyObject *res = NULL;
357359

358360
/* Make a copy of the original class __dict__ */
359361
PyObject *orig_dict = PyDict_Copy(ttp->tp_dict);
@@ -381,17 +383,37 @@ CPyDataclass_SleightOfHand(PyObject *dataclass_dec, PyObject *tp,
381383
if (!res) {
382384
goto fail;
383385
}
384-
Py_DECREF(res);
386+
const char *dataclass_type_ptr = PyUnicode_AsUTF8(dataclass_type);
387+
if (dataclass_type_ptr == NULL) {
388+
goto fail;
389+
}
390+
if (strcmp(dataclass_type_ptr, "attr") == 0 ||
391+
strcmp(dataclass_type_ptr, "attr-auto") == 0) {
392+
// These attributes are added or modified by @attr.s(slots=True).
393+
const char * const keys[] = {"__attrs_attrs__", "__attrs_own_setattr__", "__init__", ""};
394+
for (const char * const *key_iter = keys; **key_iter != '\0'; key_iter++) {
395+
PyObject *value = NULL;
396+
int rv = PyObject_GetOptionalAttrString(res, *key_iter, &value);
397+
if (rv == 1) {
398+
PyObject_SetAttrString(tp, *key_iter, value);
399+
Py_DECREF(value);
400+
} else if (rv == -1) {
401+
goto fail;
402+
}
403+
}
404+
}
385405

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

411+
Py_DECREF(res);
391412
Py_DECREF(orig_dict);
392413
return 1;
393414

394415
fail:
416+
Py_XDECREF(res);
395417
Py_XDECREF(orig_dict);
396418
return 0;
397419
}

mypyc/primitives/misc_ops.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,13 @@
224224
# Create a dataclass from an extension class. See
225225
# CPyDataclass_SleightOfHand for more docs.
226226
dataclass_sleight_of_hand = custom_op(
227-
arg_types=[object_rprimitive, object_rprimitive, dict_rprimitive, dict_rprimitive],
227+
arg_types=[
228+
object_rprimitive,
229+
object_rprimitive,
230+
dict_rprimitive,
231+
dict_rprimitive,
232+
str_rprimitive,
233+
],
228234
return_type=bit_rprimitive,
229235
c_function_name="CPyDataclass_SleightOfHand",
230236
error_kind=ERR_FALSE,

mypyc/test-data/run-classes.test

+14
Original file line numberDiff line numberDiff line change
@@ -2705,3 +2705,17 @@ print(native.ColorCode.OKGREEN.value)
27052705

27062706
[out]
27072707
okgreen
2708+
2709+
[case testAttrWithSlots]
2710+
import attr
2711+
2712+
@attr.s(slots=True)
2713+
class A:
2714+
ints: list[int] = attr.ib()
2715+
2716+
[file driver.py]
2717+
import native
2718+
print(native.A(ints=[1, -17]).ints)
2719+
2720+
[out]
2721+
\[1, -17]

0 commit comments

Comments
 (0)