Skip to content

Commit 2d2d3b1

Browse files
nnjaambv
authored andcommitted
Fix ClassVar as string fails when getting type hints (GH-6824)
1 parent 8b94b41 commit 2d2d3b1

File tree

3 files changed

+38
-6
lines changed

3 files changed

+38
-6
lines changed

Lib/test/test_typing.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1599,6 +1599,30 @@ class D(C):
15991599
# verify that @no_type_check never affects bases
16001600
self.assertEqual(get_type_hints(C.meth), {'x': int})
16011601

1602+
def test_no_type_check_forward_ref_as_string(self):
1603+
class C:
1604+
foo: typing.ClassVar[int] = 7
1605+
class D:
1606+
foo: ClassVar[int] = 7
1607+
class E:
1608+
foo: 'typing.ClassVar[int]' = 7
1609+
class F:
1610+
foo: 'ClassVar[int]' = 7
1611+
1612+
expected_result = {'foo': typing.ClassVar[int]}
1613+
for clazz in [C, D, E, F]:
1614+
self.assertEqual(get_type_hints(clazz), expected_result)
1615+
1616+
def test_nested_classvar_fails_forward_ref_check(self):
1617+
class E:
1618+
foo: 'typing.ClassVar[typing.ClassVar[int]]' = 7
1619+
class F:
1620+
foo: ClassVar['ClassVar[int]'] = 7
1621+
1622+
for clazz in [E, F]:
1623+
with self.assertRaises(TypeError):
1624+
get_type_hints(clazz)
1625+
16021626
def test_meta_no_type_check(self):
16031627

16041628
@no_type_check_decorator

Lib/typing.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
# legitimate imports of those modules.
107107

108108

109-
def _type_check(arg, msg):
109+
def _type_check(arg, msg, is_argument=False):
110110
"""Check that the argument is a type, and return it (internal helper).
111111
112112
As a special case, accept None and return type(None) instead. Also wrap strings
@@ -118,12 +118,16 @@ def _type_check(arg, msg):
118118
119119
We append the repr() of the actual value (truncated to 100 chars).
120120
"""
121+
invalid_generic_forms = (Generic, _Protocol)
122+
if not is_argument:
123+
invalid_generic_forms = invalid_generic_forms + (ClassVar, )
124+
121125
if arg is None:
122126
return type(None)
123127
if isinstance(arg, str):
124128
return ForwardRef(arg)
125129
if (isinstance(arg, _GenericAlias) and
126-
arg.__origin__ in (Generic, _Protocol, ClassVar)):
130+
arg.__origin__ in invalid_generic_forms):
127131
raise TypeError(f"{arg} is not valid as type argument")
128132
if (isinstance(arg, _SpecialForm) and arg is not Any or
129133
arg in (Generic, _Protocol)):
@@ -464,9 +468,10 @@ class ForwardRef(_Final, _root=True):
464468
"""Internal wrapper to hold a forward reference."""
465469

466470
__slots__ = ('__forward_arg__', '__forward_code__',
467-
'__forward_evaluated__', '__forward_value__')
471+
'__forward_evaluated__', '__forward_value__',
472+
'__forward_is_argument__')
468473

469-
def __init__(self, arg):
474+
def __init__(self, arg, is_argument=False):
470475
if not isinstance(arg, str):
471476
raise TypeError(f"Forward reference must be a string -- got {arg!r}")
472477
try:
@@ -477,6 +482,7 @@ def __init__(self, arg):
477482
self.__forward_code__ = code
478483
self.__forward_evaluated__ = False
479484
self.__forward_value__ = None
485+
self.__forward_is_argument__ = is_argument
480486

481487
def _evaluate(self, globalns, localns):
482488
if not self.__forward_evaluated__ or localns is not globalns:
@@ -488,7 +494,8 @@ def _evaluate(self, globalns, localns):
488494
localns = globalns
489495
self.__forward_value__ = _type_check(
490496
eval(self.__forward_code__, globalns, localns),
491-
"Forward references must evaluate to types.")
497+
"Forward references must evaluate to types.",
498+
is_argument=self.__forward_is_argument__)
492499
self.__forward_evaluated__ = True
493500
return self.__forward_value__
494501

@@ -998,7 +1005,7 @@ def get_type_hints(obj, globalns=None, localns=None):
9981005
if value is None:
9991006
value = type(None)
10001007
if isinstance(value, str):
1001-
value = ForwardRef(value)
1008+
value = ForwardRef(value, is_argument=True)
10021009
value = _eval_type(value, base_globals, localns)
10031010
hints[name] = value
10041011
return hints
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix failure in `typing.get_type_hints()` when ClassVar was provided as a string forward reference.

0 commit comments

Comments
 (0)