From f5e7b1999f46e592d42dfab51563ea5411946fb7 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 20 May 2018 08:48:12 +0300 Subject: [PATCH] bpo-23722: Raise a RuntimeError for absent __classcell__. (GH-6931) A DeprecationWarning was emitted in Python 3.6-3.7. --- Doc/reference/datamodel.rst | 3 +- Doc/whatsnew/3.8.rst | 5 +++ Lib/test/test_super.py | 44 +++++-------------- .../2018-05-17-13-06-36.bpo-23722.xisqZk.rst | 4 ++ Python/bltinmodule.c | 19 ++------ 5 files changed, 24 insertions(+), 51 deletions(-) create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-05-17-13-06-36.bpo-23722.xisqZk.rst diff --git a/Doc/reference/datamodel.rst b/Doc/reference/datamodel.rst index cc8dc958a80..855f2412b13 100644 --- a/Doc/reference/datamodel.rst +++ b/Doc/reference/datamodel.rst @@ -1967,8 +1967,7 @@ current call is identified based on the first argument passed to the method. as a ``__classcell__`` entry in the class namespace. If present, this must be propagated up to the ``type.__new__`` call in order for the class to be initialised correctly. - Failing to do so will result in a :exc:`DeprecationWarning` in Python 3.6, - and a :exc:`RuntimeError` in Python 3.8. + Failing to do so will result in a :exc:`RuntimeError` in Python 3.8. When using the default metaclass :class:`type`, or any metaclass that ultimately calls ``type.__new__``, the following additional customisation steps are diff --git a/Doc/whatsnew/3.8.rst b/Doc/whatsnew/3.8.rst index 11538e26691..9aad908f927 100644 --- a/Doc/whatsnew/3.8.rst +++ b/Doc/whatsnew/3.8.rst @@ -146,6 +146,11 @@ Changes in the Python API a database if it does not exist. (Contributed by Serhiy Storchaka in :issue:`32749`.) +* A :exc:`RuntimeError` is now raised when the custom metaclass doesn't + provide the ``__classcell__`` entry in the namespace passed to + ``type.__new__``. A :exc:`DeprecationWarning` was emitted in Python + 3.6--3.7. (Contributed by Serhiy Storchaka in :issue:`23722`.) + CPython bytecode changes ------------------------ diff --git a/Lib/test/test_super.py b/Lib/test/test_super.py index cb2d7c32236..5d94372bf6e 100644 --- a/Lib/test/test_super.py +++ b/Lib/test/test_super.py @@ -1,8 +1,6 @@ """Unit tests for zero-argument super() & related machinery.""" import unittest -import warnings -from test.support import check_warnings class A: @@ -173,14 +171,10 @@ class TestSuper(unittest.TestCase): test_namespace = namespace return None - # This case shouldn't trigger the __classcell__ deprecation warning - with check_warnings() as w: - warnings.simplefilter("always", DeprecationWarning) - class A(metaclass=Meta): - @staticmethod - def f(): - return __class__ - self.assertEqual(w.warnings, []) + class A(metaclass=Meta): + @staticmethod + def f(): + return __class__ self.assertIs(A, None) @@ -244,37 +238,19 @@ class TestSuper(unittest.TestCase): namespace.pop('__classcell__', None) return super().__new__(cls, name, bases, namespace) - # The default case should continue to work without any warnings - with check_warnings() as w: - warnings.simplefilter("always", DeprecationWarning) - class WithoutClassRef(metaclass=Meta): - pass - self.assertEqual(w.warnings, []) + # The default case should continue to work without any errors + class WithoutClassRef(metaclass=Meta): + pass # With zero-arg super() or an explicit __class__ reference, we expect - # __build_class__ to emit a DeprecationWarning complaining that + # __build_class__ to raise a RuntimeError complaining that # __class__ was not set, and asking if __classcell__ was propagated # to type.__new__. - # In Python 3.7, that warning will become a RuntimeError. - expected_warning = ( - '__class__ not set.*__classcell__ propagated', - DeprecationWarning - ) - with check_warnings(expected_warning): - warnings.simplefilter("always", DeprecationWarning) + expected_error = '__class__ not set.*__classcell__ propagated' + with self.assertRaisesRegex(RuntimeError, expected_error): class WithClassRef(metaclass=Meta): def f(self): return __class__ - # Check __class__ still gets set despite the warning - self.assertIs(WithClassRef().f(), WithClassRef) - - # Check the warning is turned into an error as expected - with warnings.catch_warnings(): - warnings.simplefilter("error", DeprecationWarning) - with self.assertRaises(DeprecationWarning): - class WithClassRef(metaclass=Meta): - def f(self): - return __class__ def test___classcell___overwrite(self): # See issue #23722 diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-05-17-13-06-36.bpo-23722.xisqZk.rst b/Misc/NEWS.d/next/Core and Builtins/2018-05-17-13-06-36.bpo-23722.xisqZk.rst new file mode 100644 index 00000000000..dfd1e79786a --- /dev/null +++ b/Misc/NEWS.d/next/Core and Builtins/2018-05-17-13-06-36.bpo-23722.xisqZk.rst @@ -0,0 +1,4 @@ +A :exc:`RuntimeError` is now raised when the custom metaclass doesn't +provide the ``__classcell__`` entry in the namespace passed to +``type.__new__``. A :exc:`DeprecationWarning` was emitted in Python +3.6--3.7. diff --git a/Python/bltinmodule.c b/Python/bltinmodule.c index 839258b8749..88a4bf991d8 100644 --- a/Python/bltinmodule.c +++ b/Python/bltinmodule.c @@ -254,30 +254,19 @@ builtin___build_class__(PyObject *self, PyObject *const *args, Py_ssize_t nargs, if (cls != NULL && PyType_Check(cls) && PyCell_Check(cell)) { PyObject *cell_cls = PyCell_GET(cell); if (cell_cls != cls) { - /* TODO: In 3.7, DeprecationWarning will become RuntimeError. - * At that point, cell_error won't be needed. - */ - int cell_error; if (cell_cls == NULL) { const char *msg = "__class__ not set defining %.200R as %.200R. " "Was __classcell__ propagated to type.__new__?"; - cell_error = PyErr_WarnFormat( - PyExc_DeprecationWarning, 1, msg, name, cls); + PyErr_Format(PyExc_RuntimeError, msg, name, cls); } else { const char *msg = "__class__ set to %.200R defining %.200R as %.200R"; PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls); - cell_error = 1; - } - if (cell_error) { - Py_DECREF(cls); - cls = NULL; - goto error; - } else { - /* Fill in the cell, since type.__new__ didn't do it */ - PyCell_Set(cell, cls); } + Py_DECREF(cls); + cls = NULL; + goto error; } } }