From 9c17cd3214987c35a60dd4076fc3530f2b79bf52 Mon Sep 17 00:00:00 2001 From: "Miss Islington (bot)" <31488909+miss-islington@users.noreply.github.com> Date: Wed, 16 May 2018 15:04:39 -0700 Subject: [PATCH] Fix ClassVar as string fails when getting type hints (GH-6824) (#6912) (cherry picked from commit 2d2d3b170bdebc085900bfa2a3bc81b5d132d0a8) Co-authored-by: Nina Zakharenko --- Lib/test/test_typing.py | 24 +++++++++++++++++++ Lib/typing.py | 19 ++++++++++----- .../2018-05-15-18-02-03.bpo-0.pj2Mbb.rst | 1 + 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2018-05-15-18-02-03.bpo-0.pj2Mbb.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 314716cd7d..be768f12fb 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1599,6 +1599,30 @@ class ForwardRefTests(BaseTestCase): # verify that @no_type_check never affects bases 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): @no_type_check_decorator diff --git a/Lib/typing.py b/Lib/typing.py index 8025dfd932..b10615c07f 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -106,7 +106,7 @@ __all__ = [ # 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). 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). """ + invalid_generic_forms = (Generic, _Protocol) + if not is_argument: + invalid_generic_forms = invalid_generic_forms + (ClassVar, ) + if arg is None: return type(None) if isinstance(arg, str): return ForwardRef(arg) 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") if (isinstance(arg, _SpecialForm) and arg is not Any or arg in (Generic, _Protocol)): @@ -464,9 +468,10 @@ class ForwardRef(_Final, _root=True): """Internal wrapper to hold a forward reference.""" __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): raise TypeError(f"Forward reference must be a string -- got {arg!r}") try: @@ -477,6 +482,7 @@ class ForwardRef(_Final, _root=True): self.__forward_code__ = code self.__forward_evaluated__ = False self.__forward_value__ = None + self.__forward_is_argument__ = is_argument def _evaluate(self, globalns, localns): if not self.__forward_evaluated__ or localns is not globalns: @@ -488,7 +494,8 @@ class ForwardRef(_Final, _root=True): localns = globalns self.__forward_value__ = _type_check( 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 return self.__forward_value__ @@ -998,7 +1005,7 @@ def get_type_hints(obj, globalns=None, localns=None): if value is None: value = type(None) if isinstance(value, str): - value = ForwardRef(value) + value = ForwardRef(value, is_argument=True) value = _eval_type(value, base_globals, localns) hints[name] = value return hints diff --git a/Misc/NEWS.d/next/Library/2018-05-15-18-02-03.bpo-0.pj2Mbb.rst b/Misc/NEWS.d/next/Library/2018-05-15-18-02-03.bpo-0.pj2Mbb.rst new file mode 100644 index 0000000000..ba8514cdd8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-05-15-18-02-03.bpo-0.pj2Mbb.rst @@ -0,0 +1 @@ +Fix failure in `typing.get_type_hints()` when ClassVar was provided as a string forward reference. -- 2.40.0