diff --git a/Lib/test/test_type_annotations.py b/Lib/test/test_type_annotations.py index 4c58fade1..95fca51a5 100644 --- a/Lib/test/test_type_annotations.py +++ b/Lib/test/test_type_annotations.py @@ -836,6 +836,24 @@ class RegressionTests(unittest.TestCase): lamb = list(genexp)[0] self.assertEqual(lamb(), 42) + @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: '__annotate__' != 'f.__annotate__' + def test_annotate_qualname(self): + code = """ + def f() -> None: + def nested() -> None: pass + return nested + class Outer: + x: int + def method(self, x: int): + pass + """ + ns = run_code(code) + method = ns["Outer"].method + self.assertEqual(ns["f"].__annotate__.__qualname__, "f.__annotate__") + self.assertEqual(ns["f"]().__annotate__.__qualname__, "f..nested.__annotate__") + self.assertEqual(method.__annotate__.__qualname__, "Outer.method.__annotate__") + self.assertEqual(ns["Outer"].__annotate__.__qualname__, "Outer.__annotate__") + # gh-138349 def test_module_level_annotation_plus_listcomp(self): cases = [ diff --git a/Lib/test/test_type_params.py b/Lib/test/test_type_params.py index af26a6a0a..0594f9427 100644 --- a/Lib/test/test_type_params.py +++ b/Lib/test/test_type_params.py @@ -152,6 +152,13 @@ class TypeParamsInvalidTest(unittest.TestCase): with self.assertRaisesRegex(TypeError, r"\(MRO\) for bases object, Generic"): class My[X](object): ... + def test_compile_error_in_type_param_bound(self): + # This should not crash, see gh-145187 + check_syntax_error( + self, + "if True:\n class h[l:{7for*()in 0}]:2" + ) + class TypeParamsNonlocalTest(unittest.TestCase): def test_nonlocal_disallowed_01(self): diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 3ab16478c..50e491229 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -13,7 +13,7 @@ import os import pickle import re import sys -from unittest import TestCase, main, skip +from unittest import TestCase, main from unittest.mock import patch from copy import copy, deepcopy @@ -6691,11 +6691,7 @@ class GetTypeHintsTests(BaseTestCase): self.assertEqual(gth(ann_module2), {}) self.assertEqual(gth(ann_module3), {}) - @skip("known bug") def test_get_type_hints_modules_forwardref(self): - # FIXME: This currently exposes a bug in typing. Cached forward references - # don't account for the case where there are multiple types of the same - # name coming from different modules in the same program. mgc_hints = {'default_a': Optional[mod_generics_cache.A], 'default_b': Optional[mod_generics_cache.B]} self.assertEqual(gth(mod_generics_cache), mgc_hints) @@ -6783,6 +6779,24 @@ class GetTypeHintsTests(BaseTestCase): self.assertEqual(gth(ForRefExample.func), expects) self.assertEqual(gth(ForRefExample.nested), expects) + def test_get_type_hints_wrapped_cycle_self(self): + # gh-146553: __wrapped__ self-reference must raise ValueError, + # not loop forever. + def f(x: int) -> str: ... + f.__wrapped__ = f + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + get_type_hints(f) + + def test_get_type_hints_wrapped_cycle_mutual(self): + # gh-146553: mutual __wrapped__ cycle (a -> b -> a) must raise + # ValueError, not loop forever. + def a(): ... + def b(): ... + a.__wrapped__ = b + b.__wrapped__ = a + with self.assertRaisesRegex(ValueError, 'wrapper loop'): + get_type_hints(a) + def test_get_type_hints_annotated(self): def foobar(x: List['X']): ... X = Annotated[int, (1, 10)] @@ -10850,6 +10864,10 @@ class NoDefaultTests(BaseTestCase): with self.assertRaises(AttributeError): type(NoDefault).foo + def test_no_subclassing(self): + with self.assertRaises(TypeError): + class Test(type(NoDefault)): ... + class AllTests(BaseTestCase): """Tests for __all__.""" diff --git a/Lib/typing.py b/Lib/typing.py index 380211183..5da5cb0d2 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -2416,8 +2416,12 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False, else: nsobj = obj # Find globalns for the unwrapped object. + seen = {id(nsobj)} while hasattr(nsobj, '__wrapped__'): nsobj = nsobj.__wrapped__ + if id(nsobj) in seen: + raise ValueError(f'wrapper loop when unwrapping {obj!r}') + seen.add(id(nsobj)) globalns = getattr(nsobj, '__globals__', {}) if localns is None: localns = globalns