mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	bpo-23722: Raise a RuntimeError for absent __classcell__. (GH-6931)
A DeprecationWarning was emitted in Python 3.6-3.7.
This commit is contained in:
		
							parent
							
								
									8ae8e6af37
								
							
						
					
					
						commit
						f5e7b1999f
					
				
					 5 changed files with 24 additions and 51 deletions
				
			
		| 
						 | 
					@ -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
 | 
					   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
 | 
					   be propagated up to the ``type.__new__`` call in order for the class to be
 | 
				
			||||||
   initialised correctly.
 | 
					   initialised correctly.
 | 
				
			||||||
   Failing to do so will result in a :exc:`DeprecationWarning` in Python 3.6,
 | 
					   Failing to do so will result in a :exc:`RuntimeError` in Python 3.8.
 | 
				
			||||||
   and a :exc:`RuntimeError` in Python 3.8.
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
When using the default metaclass :class:`type`, or any metaclass that ultimately
 | 
					When using the default metaclass :class:`type`, or any metaclass that ultimately
 | 
				
			||||||
calls ``type.__new__``, the following additional customisation steps are
 | 
					calls ``type.__new__``, the following additional customisation steps are
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,6 +146,11 @@ Changes in the Python API
 | 
				
			||||||
  a database if it does not exist.
 | 
					  a database if it does not exist.
 | 
				
			||||||
  (Contributed by Serhiy Storchaka in :issue:`32749`.)
 | 
					  (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
 | 
					CPython bytecode changes
 | 
				
			||||||
------------------------
 | 
					------------------------
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,8 +1,6 @@
 | 
				
			||||||
"""Unit tests for zero-argument super() & related machinery."""
 | 
					"""Unit tests for zero-argument super() & related machinery."""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import unittest
 | 
					import unittest
 | 
				
			||||||
import warnings
 | 
					 | 
				
			||||||
from test.support import check_warnings
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class A:
 | 
					class A:
 | 
				
			||||||
| 
						 | 
					@ -173,14 +171,10 @@ def __new__(cls, name, bases, namespace):
 | 
				
			||||||
                test_namespace = namespace
 | 
					                test_namespace = namespace
 | 
				
			||||||
                return None
 | 
					                return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # This case shouldn't trigger the __classcell__ deprecation warning
 | 
					 | 
				
			||||||
        with check_warnings() as w:
 | 
					 | 
				
			||||||
            warnings.simplefilter("always", DeprecationWarning)
 | 
					 | 
				
			||||||
        class A(metaclass=Meta):
 | 
					        class A(metaclass=Meta):
 | 
				
			||||||
            @staticmethod
 | 
					            @staticmethod
 | 
				
			||||||
            def f():
 | 
					            def f():
 | 
				
			||||||
                return __class__
 | 
					                return __class__
 | 
				
			||||||
        self.assertEqual(w.warnings, [])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertIs(A, None)
 | 
					        self.assertIs(A, None)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -244,34 +238,16 @@ def __new__(cls, name, bases, namespace):
 | 
				
			||||||
                namespace.pop('__classcell__', None)
 | 
					                namespace.pop('__classcell__', None)
 | 
				
			||||||
                return super().__new__(cls, name, bases, namespace)
 | 
					                return super().__new__(cls, name, bases, namespace)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The default case should continue to work without any warnings
 | 
					        # The default case should continue to work without any errors
 | 
				
			||||||
        with check_warnings() as w:
 | 
					 | 
				
			||||||
            warnings.simplefilter("always", DeprecationWarning)
 | 
					 | 
				
			||||||
        class WithoutClassRef(metaclass=Meta):
 | 
					        class WithoutClassRef(metaclass=Meta):
 | 
				
			||||||
            pass
 | 
					            pass
 | 
				
			||||||
        self.assertEqual(w.warnings, [])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # With zero-arg super() or an explicit __class__ reference, we expect
 | 
					        # 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
 | 
					        # __class__ was not set, and asking if __classcell__ was propagated
 | 
				
			||||||
        # to type.__new__.
 | 
					        # to type.__new__.
 | 
				
			||||||
        # In Python 3.7, that warning will become a RuntimeError.
 | 
					        expected_error = '__class__ not set.*__classcell__ propagated'
 | 
				
			||||||
        expected_warning = (
 | 
					        with self.assertRaisesRegex(RuntimeError, expected_error):
 | 
				
			||||||
            '__class__ not set.*__classcell__ propagated',
 | 
					 | 
				
			||||||
            DeprecationWarning
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        with check_warnings(expected_warning):
 | 
					 | 
				
			||||||
            warnings.simplefilter("always", DeprecationWarning)
 | 
					 | 
				
			||||||
            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):
 | 
					            class WithClassRef(metaclass=Meta):
 | 
				
			||||||
                def f(self):
 | 
					                def f(self):
 | 
				
			||||||
                    return __class__
 | 
					                    return __class__
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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.
 | 
				
			||||||
| 
						 | 
					@ -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)) {
 | 
					        if (cls != NULL && PyType_Check(cls) && PyCell_Check(cell)) {
 | 
				
			||||||
            PyObject *cell_cls = PyCell_GET(cell);
 | 
					            PyObject *cell_cls = PyCell_GET(cell);
 | 
				
			||||||
            if (cell_cls != cls) {
 | 
					            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) {
 | 
					                if (cell_cls == NULL) {
 | 
				
			||||||
                    const char *msg =
 | 
					                    const char *msg =
 | 
				
			||||||
                        "__class__ not set defining %.200R as %.200R. "
 | 
					                        "__class__ not set defining %.200R as %.200R. "
 | 
				
			||||||
                        "Was __classcell__ propagated to type.__new__?";
 | 
					                        "Was __classcell__ propagated to type.__new__?";
 | 
				
			||||||
                    cell_error = PyErr_WarnFormat(
 | 
					                    PyErr_Format(PyExc_RuntimeError, msg, name, cls);
 | 
				
			||||||
                        PyExc_DeprecationWarning, 1, msg, name, cls);
 | 
					 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    const char *msg =
 | 
					                    const char *msg =
 | 
				
			||||||
                        "__class__ set to %.200R defining %.200R as %.200R";
 | 
					                        "__class__ set to %.200R defining %.200R as %.200R";
 | 
				
			||||||
                    PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls);
 | 
					                    PyErr_Format(PyExc_TypeError, msg, cell_cls, name, cls);
 | 
				
			||||||
                    cell_error = 1;
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                if (cell_error) {
 | 
					 | 
				
			||||||
                Py_DECREF(cls);
 | 
					                Py_DECREF(cls);
 | 
				
			||||||
                cls = NULL;
 | 
					                cls = NULL;
 | 
				
			||||||
                goto error;
 | 
					                goto error;
 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    /* Fill in the cell, since type.__new__ didn't do it */
 | 
					 | 
				
			||||||
                    PyCell_Set(cell, cls);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue