1- """Unit tests for new super() implementation ."""
1+ """Unit tests for zero-argument super() & related machinery ."""
22
33import sys
44import unittest
5+ import warnings
6+ from test .support import check_warnings
57
68
79class A :
@@ -144,6 +146,8 @@ def f():
144146 self .assertIs (X .f (), X )
145147
146148 def test___class___new (self ):
149+ # See issue #23722
150+ # Ensure zero-arg super() works as soon as type.__new__() is completed
147151 test_class = None
148152
149153 class Meta (type ):
@@ -161,6 +165,7 @@ def f():
161165 self .assertIs (test_class , A )
162166
163167 def test___class___delayed (self ):
168+ # See issue #23722
164169 test_namespace = None
165170
166171 class Meta (type ):
@@ -169,17 +174,22 @@ def __new__(cls, name, bases, namespace):
169174 test_namespace = namespace
170175 return None
171176
172- class A (metaclass = Meta ):
173- @staticmethod
174- def f ():
175- return __class__
177+ # This case shouldn't trigger the __classcell__ deprecation warning
178+ with check_warnings () as w :
179+ warnings .simplefilter ("always" , DeprecationWarning )
180+ class A (metaclass = Meta ):
181+ @staticmethod
182+ def f ():
183+ return __class__
184+ self .assertEqual (w .warnings , [])
176185
177186 self .assertIs (A , None )
178187
179188 B = type ("B" , (), test_namespace )
180189 self .assertIs (B .f (), B )
181190
182191 def test___class___mro (self ):
192+ # See issue #23722
183193 test_class = None
184194
185195 class Meta (type ):
@@ -195,34 +205,105 @@ def f():
195205
196206 self .assertIs (test_class , A )
197207
198- def test___classcell___deleted (self ):
208+ def test___classcell___expected_behaviour (self ):
209+ # See issue #23722
199210 class Meta (type ):
200211 def __new__ (cls , name , bases , namespace ):
201- del namespace ['__classcell__' ]
212+ nonlocal namespace_snapshot
213+ namespace_snapshot = namespace .copy ()
202214 return super ().__new__ (cls , name , bases , namespace )
203215
204- class A (metaclass = Meta ):
205- @staticmethod
206- def f ():
207- __class__
208-
209- with self .assertRaises (NameError ):
210- A .f ()
216+ # __classcell__ is injected into the class namespace by the compiler
217+ # when at least one method needs it, and should be omitted otherwise
218+ namespace_snapshot = None
219+ class WithoutClassRef (metaclass = Meta ):
220+ pass
221+ self .assertNotIn ("__classcell__" , namespace_snapshot )
222+
223+ # With zero-arg super() or an explicit __class__ reference,
224+ # __classcell__ is the exact cell reference to be populated by
225+ # type.__new__
226+ namespace_snapshot = None
227+ class WithClassRef (metaclass = Meta ):
228+ def f (self ):
229+ return __class__
211230
212- def test___classcell___reset (self ):
231+ class_cell = namespace_snapshot ["__classcell__" ]
232+ method_closure = WithClassRef .f .__closure__
233+ self .assertEqual (len (method_closure ), 1 )
234+ self .assertIs (class_cell , method_closure [0 ])
235+ # Ensure the cell reference *doesn't* get turned into an attribute
236+ with self .assertRaises (AttributeError ):
237+ WithClassRef .__classcell__
238+
239+ def test___classcell___missing (self ):
240+ # See issue #23722
241+ # Some metaclasses may not pass the original namespace to type.__new__
242+ # We test that case here by forcibly deleting __classcell__
213243 class Meta (type ):
214244 def __new__ (cls , name , bases , namespace ):
215- namespace [ '__classcell__' ] = 0
245+ namespace . pop ( '__classcell__' , None )
216246 return super ().__new__ (cls , name , bases , namespace )
217247
218- class A (metaclass = Meta ):
219- @staticmethod
220- def f ():
221- __class__
248+ # The default case should continue to work without any warnings
249+ with check_warnings () as w :
250+ warnings .simplefilter ("always" , DeprecationWarning )
251+ class WithoutClassRef (metaclass = Meta ):
252+ pass
253+ self .assertEqual (w .warnings , [])
254+
255+ # With zero-arg super() or an explicit __class__ reference, we expect
256+ # __build_class__ to emit a DeprecationWarning complaining that
257+ # __class__ was not set, and asking if __classcell__ was propagated
258+ # to type.__new__.
259+ # In Python 3.7, that warning will become a RuntimeError.
260+ expected_warning = (
261+ '__class__ not set.*__classcell__ propagated' ,
262+ DeprecationWarning
263+ )
264+ with check_warnings (expected_warning ):
265+ warnings .simplefilter ("always" , DeprecationWarning )
266+ class WithClassRef (metaclass = Meta ):
267+ def f (self ):
268+ return __class__
269+ # Check __class__ still gets set despite the warning
270+ self .assertIs (WithClassRef ().f (), WithClassRef )
271+
272+ # Check the warning is turned into an error as expected
273+ with warnings .catch_warnings ():
274+ warnings .simplefilter ("error" , DeprecationWarning )
275+ with self .assertRaises (DeprecationWarning ):
276+ class WithClassRef (metaclass = Meta ):
277+ def f (self ):
278+ return __class__
279+
280+ def test___classcell___overwrite (self ):
281+ # See issue #23722
282+ # Overwriting __classcell__ with nonsense is explicitly prohibited
283+ class Meta (type ):
284+ def __new__ (cls , name , bases , namespace , cell ):
285+ namespace ['__classcell__' ] = cell
286+ return super ().__new__ (cls , name , bases , namespace )
287+
288+ for bad_cell in (None , 0 , "" , object ()):
289+ with self .subTest (bad_cell = bad_cell ):
290+ with self .assertRaises (TypeError ):
291+ class A (metaclass = Meta , cell = bad_cell ):
292+ pass
222293
223- with self .assertRaises (NameError ):
224- A .f ()
225- self .assertEqual (A .__classcell__ , 0 )
294+ def test___classcell___wrong_cell (self ):
295+ # See issue #23722
296+ # Pointing the cell reference at the wrong class is also prohibited
297+ class Meta (type ):
298+ def __new__ (cls , name , bases , namespace ):
299+ cls = super ().__new__ (cls , name , bases , namespace )
300+ B = type ("B" , (), namespace )
301+ return cls
302+
303+ with self .assertRaises (TypeError ):
304+ class A (metaclass = Meta ):
305+ def f (self ):
306+ return __class__
226307
227308 def test_obscure_super_errors (self ):
228309 def f ():
0 commit comments