if '__slots__' in cls.__dict__:
raise TypeError(f'{cls.__name__} already specifies __slots__')
+ # gh-102069: Remove existing __weakref__ descriptor.
+ # gh-135228: Make sure the original class can be garbage collected.
+ sys._clear_type_descriptors(cls)
+
# Create a new dict for our new class.
cls_dict = dict(cls.__dict__)
field_names = tuple(f.name for f in fields(cls))
# available in _MARKER.
cls_dict.pop(field_name, None)
- # Remove __dict__ itself.
- cls_dict.pop('__dict__', None)
-
- # Clear existing `__weakref__` descriptor, it belongs to a previous type:
- cls_dict.pop('__weakref__', None) # gh-102069
-
# And finally create the class.
qualname = getattr(cls, '__qualname__', None)
newcls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
# that we create internally.
self.assertEqual(CorrectSuper.args, ["default", "default"])
+ def test_original_class_is_gced(self):
+ # gh-135228: Make sure when we replace the class with slots=True, the original class
+ # gets garbage collected.
+ def make_simple():
+ @dataclass(slots=True)
+ class SlotsTest:
+ pass
+
+ return SlotsTest
+
+ def make_with_annotations():
+ @dataclass(slots=True)
+ class SlotsTest:
+ x: int
+
+ return SlotsTest
+
+ def make_with_annotations_and_method():
+ @dataclass(slots=True)
+ class SlotsTest:
+ x: int
+
+ def method(self) -> int:
+ return self.x
+
+ return SlotsTest
+
+ for make in (make_simple, make_with_annotations, make_with_annotations_and_method):
+ with self.subTest(make=make):
+ C = make()
+ support.gc_collect()
+ candidates = [cls for cls in object.__subclasses__() if cls.__name__ == 'SlotsTest'
+ and cls.__firstlineno__ == make.__code__.co_firstlineno + 1]
+ self.assertEqual(candidates, [C])
+
class TestDescriptors(unittest.TestCase):
def test_set_name(self):
--- /dev/null
+When :mod:`dataclasses` replaces a class with a slotted dataclass, the
+original class can now be garbage collected again. Earlier changes in Python
+3.14 caused this class to always remain in existence together with the replacement
+class synthesized by :mod:`dataclasses`.
return sys__baserepl_impl(module);
}
+PyDoc_STRVAR(sys__clear_type_descriptors__doc__,
+"_clear_type_descriptors($module, type, /)\n"
+"--\n"
+"\n"
+"Private function for clearing certain descriptors from a type\'s dictionary.\n"
+"\n"
+"See gh-135228 for context.");
+
+#define SYS__CLEAR_TYPE_DESCRIPTORS_METHODDEF \
+ {"_clear_type_descriptors", (PyCFunction)sys__clear_type_descriptors, METH_O, sys__clear_type_descriptors__doc__},
+
+static PyObject *
+sys__clear_type_descriptors_impl(PyObject *module, PyObject *type);
+
+static PyObject *
+sys__clear_type_descriptors(PyObject *module, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ PyObject *type;
+
+ if (!PyObject_TypeCheck(arg, &PyType_Type)) {
+ _PyArg_BadArgument("_clear_type_descriptors", "argument", (&PyType_Type)->tp_name, arg);
+ goto exit;
+ }
+ type = arg;
+ return_value = sys__clear_type_descriptors_impl(module, type);
+
+exit:
+ return return_value;
+}
+
PyDoc_STRVAR(sys__is_gil_enabled__doc__,
"_is_gil_enabled($module, /)\n"
"--\n"
#ifndef SYS_GETANDROIDAPILEVEL_METHODDEF
#define SYS_GETANDROIDAPILEVEL_METHODDEF
#endif /* !defined(SYS_GETANDROIDAPILEVEL_METHODDEF) */
-/*[clinic end generated code: output=449d16326e69dcf6 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=9052f399f40ca32d input=a9049054013a1b77]*/
Py_RETURN_NONE;
}
+/*[clinic input]
+sys._clear_type_descriptors
+
+ type: object(subclass_of='&PyType_Type')
+ /
+
+Private function for clearing certain descriptors from a type's dictionary.
+
+See gh-135228 for context.
+[clinic start generated code]*/
+
+static PyObject *
+sys__clear_type_descriptors_impl(PyObject *module, PyObject *type)
+/*[clinic end generated code: output=5ad17851b762b6d9 input=dc536c97fde07251]*/
+{
+ PyTypeObject *typeobj = (PyTypeObject *)type;
+ if (_PyType_HasFeature(typeobj, Py_TPFLAGS_IMMUTABLETYPE)) {
+ PyErr_SetString(PyExc_TypeError, "argument is immutable");
+ return NULL;
+ }
+ PyObject *dict = _PyType_GetDict(typeobj);
+ PyObject *dunder_dict = NULL;
+ if (PyDict_Pop(dict, &_Py_ID(__dict__), &dunder_dict) < 0) {
+ return NULL;
+ }
+ PyObject *dunder_weakref = NULL;
+ if (PyDict_Pop(dict, &_Py_ID(__weakref__), &dunder_weakref) < 0) {
+ PyType_Modified(typeobj);
+ Py_XDECREF(dunder_dict);
+ return NULL;
+ }
+ PyType_Modified(typeobj);
+ // We try to hold onto a reference to these until after we call
+ // PyType_Modified(), in case their deallocation triggers somer user code
+ // that tries to do something to the type.
+ Py_XDECREF(dunder_dict);
+ Py_XDECREF(dunder_weakref);
+ Py_RETURN_NONE;
+}
+
+
/*[clinic input]
sys._is_gil_enabled -> bool
SYS__STATS_DUMP_METHODDEF
#endif
SYS__GET_CPU_COUNT_CONFIG_METHODDEF
+ SYS__CLEAR_TYPE_DESCRIPTORS_METHODDEF
SYS__IS_GIL_ENABLED_METHODDEF
SYS__DUMP_TRACELETS_METHODDEF
{NULL, NULL} // sentinel