diff --git a/CHANGELOG.md b/CHANGELOG.md index c2db6a6c..93ac32b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ No user-facing changes since 4.16.0rc2. +- Fix `get_type_hints()` raising `TypeError` for a stringified bare `ClassVar` + annotation on Python 3.9 and 3.10. + # Release 4.16.0rc2 (June 25, 2026) - Avoid a `DeprecationWarning` when `deprecated` is applied to a coroutine function on diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 996d466c..fb3f548e 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5648,6 +5648,18 @@ def test_compatibility(self): class GetTypeHintsTests(BaseTestCase): + def test_get_type_hints_lone_stringified_classvar(self): + class Parent: + parent: 'ClassVar' = 1 + + class Child(Parent): + child: 'typing.ClassVar' = 2 + + self.assertEqual( + get_type_hints(Child, globals()), + {'parent': ClassVar, 'child': typing.ClassVar}, + ) + def test_get_type_hints(self): def foobar(x: List['X']): ... X = Annotated[int, (1, 10)] diff --git a/src/typing_extensions.py b/src/typing_extensions.py index ced78373..48025961 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -1602,9 +1602,37 @@ def get_type_hints(obj, globalns=None, localns=None, include_extras=False): - If two dict arguments are passed, they specify globals and locals, respectively. """ - hint = typing.get_type_hints( - obj, globalns=globalns, localns=localns, include_extras=True - ) + if sys.version_info < (3, 11) and isinstance(obj, type): + hint = {} + for base in reversed(obj.__mro__): + base_globals = ( + getattr(sys.modules.get(base.__module__), '__dict__', {}) + if globalns is None else globalns + ) + annotations = base.__dict__.get('__annotations__', {}) + if isinstance(annotations, _types.GetSetDescriptorType): + annotations = {} + base_locals = dict(vars(base)) if localns is None else localns + if localns is None and globalns is None: + base_globals, base_locals = base_locals, base_globals + for name, value in annotations.items(): + if value is None: + value = type(None) + elif isinstance(value, str): + evaluated = eval(value, base_globals, base_locals) + if evaluated is ClassVar: + value = evaluated + else: + value = ForwardRef( + value, is_argument=False, is_class=True + ) + hint[name] = typing._eval_type( + value, base_globals, base_locals + ) + else: + hint = typing.get_type_hints( + obj, globalns=globalns, localns=localns, include_extras=True + ) # Breakpoint: https://github.com/python/cpython/pull/30304 if sys.version_info < (3, 11): _clean_optional(obj, hint, globalns, localns)