# 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
# 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
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)):
"""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:
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:
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__
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