diff -r 62c9a89a2103 Lib/test/test_super.py --- a/Lib/test/test_super.py Fri Dec 02 23:15:22 2016 +0200 +++ b/Lib/test/test_super.py Sat Dec 03 16:31:52 2016 +1000 @@ -1,7 +1,9 @@ -"""Unit tests for new super() implementation.""" +"""Unit tests for zero-argument super() & related machinery.""" import sys import unittest +import warnings +from test.support import check_warnings class A: @@ -144,6 +146,8 @@ self.assertIs(X.f(), X) def test___class___new(self): + # See issue #23722 + # Ensure zero-arg super() works as soon as type.__new__() is completed test_class = None class Meta(type): @@ -161,6 +165,7 @@ self.assertIs(test_class, A) def test___class___delayed(self): + # See issue #23722 test_namespace = None class Meta(type): @@ -180,6 +185,7 @@ self.assertIs(B.f(), B) def test___class___mro(self): + # See issue #23722 test_class = None class Meta(type): @@ -195,12 +201,80 @@ self.assertIs(test_class, A) - def test___classcell___deleted(self): + def test___classcell___expected_behaviour(self): + # See issue #23722 + # __classcell__ is injected into the class namespace by the compiler + # The injected reference is None by default + class_cell = object() + class Meta(type): + def __new__(cls, name, bases, namespace): + nonlocal class_cell + class_cell = namespace["__classcell__"] + return super().__new__(cls, name, bases, namespace) + + class A(metaclass=Meta): + pass + + with self.assertRaises(AttributeError): + A.__classcell__ + self.assertIsNone(class_cell) + + # With zero-arg super() or an explicit __class__ reference, + # __classcell__ is an actual cell reference that gets populated by + # type.__new__ + class B(metaclass=Meta): + @staticmethod + def f(): + __class__ + + with self.assertRaises(AttributeError): + B.__classcell__ + self.assertIsNotNone(class_cell) + + def test___classcell___missing(self): + # See issue #23722 + # Some classes may not pass the original namespace up to type.__new__ + # We test that case by deleting __classcell__, and check that it + # still works for now, but emits a RuntimeWarning & DeprecationWarning + # In Python 3.7, it will become a RuntimeError to leave it out. class Meta(type): def __new__(cls, name, bases, namespace): del namespace['__classcell__'] return super().__new__(cls, name, bases, namespace) + # Check default behaviour is to emit warnings + expected_warnings = ( + ('__classcell__ missing', RuntimeWarning), + ('__class__ not set', DeprecationWarning), + ) + with check_warnings(*expected_warnings): + class A(metaclass=Meta): + @staticmethod + def f(): + return __class__ + # Check __class__ still gets set despite the warnings + self.assertIs(A.f(), A) + + # Check the warnings are turned into errors as expected + with warnings.catch_warnings(): + warnings.simplefilter("error", DeprecationWarning) + with self.assertRaises(DeprecationWarning): + class B(metaclass=Meta): + pass + + warnings.simplefilter("error", RuntimeWarning) + with self.assertRaises(RuntimeWarning): + class C(metaclass=Meta): + pass + + def test___classcell___reset(self): + # See issue #23722 + # Setting __classcell__ to None will disable zero-argument super() + class Meta(type): + def __new__(cls, name, bases, namespace): + namespace['__classcell__'] = None + return super().__new__(cls, name, bases, namespace) + class A(metaclass=Meta): @staticmethod def f(): @@ -209,20 +283,17 @@ with self.assertRaises(NameError): A.f() - def test___classcell___reset(self): + def test___classcell___overwrite(self): + # See issue #23722 + # Overwriting __classcell__ with nonsense is just plain disallowed class Meta(type): def __new__(cls, name, bases, namespace): namespace['__classcell__'] = 0 return super().__new__(cls, name, bases, namespace) - class A(metaclass=Meta): - @staticmethod - def f(): - __class__ - - with self.assertRaises(NameError): - A.f() - self.assertEqual(A.__classcell__, 0) + with self.assertRaises(TypeError): + class A(metaclass=Meta): + pass def test_obscure_super_errors(self): def f():