Fix ClassVar as string fails when getting type hints (GH-6824)

This commit is contained in:
Nina Zakharenko 2018-05-16 12:27:03 -04:00 committed by Łukasz Langa
parent 8b94b41ab7
commit 2d2d3b170b
3 changed files with 38 additions and 6 deletions

View File

@ -1599,6 +1599,30 @@ class ForwardRefTests(BaseTestCase):
# verify that @no_type_check never affects bases # verify that @no_type_check never affects bases
self.assertEqual(get_type_hints(C.meth), {'x': int}) self.assertEqual(get_type_hints(C.meth), {'x': int})
def test_no_type_check_forward_ref_as_string(self):
class C:
foo: typing.ClassVar[int] = 7
class D:
foo: ClassVar[int] = 7
class E:
foo: 'typing.ClassVar[int]' = 7
class F:
foo: 'ClassVar[int]' = 7
expected_result = {'foo': typing.ClassVar[int]}
for clazz in [C, D, E, F]:
self.assertEqual(get_type_hints(clazz), expected_result)
def test_nested_classvar_fails_forward_ref_check(self):
class E:
foo: 'typing.ClassVar[typing.ClassVar[int]]' = 7
class F:
foo: ClassVar['ClassVar[int]'] = 7
for clazz in [E, F]:
with self.assertRaises(TypeError):
get_type_hints(clazz)
def test_meta_no_type_check(self): def test_meta_no_type_check(self):
@no_type_check_decorator @no_type_check_decorator

View File

@ -106,7 +106,7 @@ __all__ = [
# legitimate imports of those modules. # legitimate imports of those modules.
def _type_check(arg, msg): def _type_check(arg, msg, is_argument=False):
"""Check that the argument is a type, and return it (internal helper). """Check that the argument is a type, and return it (internal helper).
As a special case, accept None and return type(None) instead. Also wrap strings As a special case, accept None and return type(None) instead. Also wrap strings
@ -118,12 +118,16 @@ def _type_check(arg, msg):
We append the repr() of the actual value (truncated to 100 chars). We append the repr() of the actual value (truncated to 100 chars).
""" """
invalid_generic_forms = (Generic, _Protocol)
if not is_argument:
invalid_generic_forms = invalid_generic_forms + (ClassVar, )
if arg is None: if arg is None:
return type(None) return type(None)
if isinstance(arg, str): if isinstance(arg, str):
return ForwardRef(arg) return ForwardRef(arg)
if (isinstance(arg, _GenericAlias) and if (isinstance(arg, _GenericAlias) and
arg.__origin__ in (Generic, _Protocol, ClassVar)): arg.__origin__ in invalid_generic_forms):
raise TypeError(f"{arg} is not valid as type argument") raise TypeError(f"{arg} is not valid as type argument")
if (isinstance(arg, _SpecialForm) and arg is not Any or if (isinstance(arg, _SpecialForm) and arg is not Any or
arg in (Generic, _Protocol)): arg in (Generic, _Protocol)):
@ -464,9 +468,10 @@ class ForwardRef(_Final, _root=True):
"""Internal wrapper to hold a forward reference.""" """Internal wrapper to hold a forward reference."""
__slots__ = ('__forward_arg__', '__forward_code__', __slots__ = ('__forward_arg__', '__forward_code__',
'__forward_evaluated__', '__forward_value__') '__forward_evaluated__', '__forward_value__',
'__forward_is_argument__')
def __init__(self, arg): def __init__(self, arg, is_argument=False):
if not isinstance(arg, str): if not isinstance(arg, str):
raise TypeError(f"Forward reference must be a string -- got {arg!r}") raise TypeError(f"Forward reference must be a string -- got {arg!r}")
try: try:
@ -477,6 +482,7 @@ class ForwardRef(_Final, _root=True):
self.__forward_code__ = code self.__forward_code__ = code
self.__forward_evaluated__ = False self.__forward_evaluated__ = False
self.__forward_value__ = None self.__forward_value__ = None
self.__forward_is_argument__ = is_argument
def _evaluate(self, globalns, localns): def _evaluate(self, globalns, localns):
if not self.__forward_evaluated__ or localns is not globalns: if not self.__forward_evaluated__ or localns is not globalns:
@ -488,7 +494,8 @@ class ForwardRef(_Final, _root=True):
localns = globalns localns = globalns
self.__forward_value__ = _type_check( self.__forward_value__ = _type_check(
eval(self.__forward_code__, globalns, localns), eval(self.__forward_code__, globalns, localns),
"Forward references must evaluate to types.") "Forward references must evaluate to types.",
is_argument=self.__forward_is_argument__)
self.__forward_evaluated__ = True self.__forward_evaluated__ = True
return self.__forward_value__ return self.__forward_value__
@ -998,7 +1005,7 @@ def get_type_hints(obj, globalns=None, localns=None):
if value is None: if value is None:
value = type(None) value = type(None)
if isinstance(value, str): if isinstance(value, str):
value = ForwardRef(value) value = ForwardRef(value, is_argument=True)
value = _eval_type(value, base_globals, localns) value = _eval_type(value, base_globals, localns)
hints[name] = value hints[name] = value
return hints return hints

View File

@ -0,0 +1 @@
Fix failure in `typing.get_type_hints()` when ClassVar was provided as a string forward reference.