From f79e7afec2c863c34d7a9b41ebb732dc26128fff Mon Sep 17 00:00:00 2001 From: Nikita Sobolev Date: Thu, 25 Nov 2021 01:56:24 +0300 Subject: [PATCH] Now `ClassVar` cannot contain type variables (#11585) Resolves part of #11538 --- mypy/message_registry.py | 4 ++++ mypy/semanal.py | 10 ++++++++-- mypy/test/testsemanal.py | 30 +++++++++++++++------------- test-data/unit/check-classvar.test | 4 ++-- test-data/unit/semanal-classvar.test | 11 ++++++++++ 5 files changed, 41 insertions(+), 18 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 6ad10816556f..77dff1154833 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -212,6 +212,10 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage": 'Cannot override class variable (previously declared on base class "{}") with instance ' "variable" ) +CLASS_VAR_WITH_TYPEVARS: Final = 'ClassVar cannot contain type variables' +CLASS_VAR_OUTSIDE_OF_CLASS: Final = ( + 'ClassVar can only be used for assignments in class body' +) # Protocol RUNTIME_PROTOCOL_EXPECTED: Final = ErrorMessage( diff --git a/mypy/semanal.py b/mypy/semanal.py index 86183704f680..e55fada5689c 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -94,7 +94,7 @@ TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType, get_proper_type, get_proper_types, TypeAliasType, TypeVarLikeType ) -from mypy.typeops import function_type +from mypy.typeops import function_type, get_type_vars from mypy.type_visitor import TypeQuery from mypy.typeanal import ( TypeAnalyser, analyze_type_alias, no_subscript_builtin_alias, @@ -3337,6 +3337,12 @@ def check_classvar(self, s: AssignmentStmt) -> None: node = lvalue.node if isinstance(node, Var): node.is_classvar = True + analyzed = self.anal_type(s.type) + if analyzed is not None and get_type_vars(analyzed): + # This means that we have a type var defined inside of a ClassVar. + # This is not allowed by PEP526. + # See https://github.com/python/mypy/issues/11538 + self.fail(message_registry.CLASS_VAR_WITH_TYPEVARS, s) elif not isinstance(lvalue, MemberExpr) or self.is_self_member_ref(lvalue): # In case of member access, report error only when assigning to self # Other kinds of member assignments should be already reported @@ -3359,7 +3365,7 @@ def is_final_type(self, typ: Optional[Type]) -> bool: return sym.node.fullname in ('typing.Final', 'typing_extensions.Final') def fail_invalid_classvar(self, context: Context) -> None: - self.fail('ClassVar can only be used for assignments in class body', context) + self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context) def process_module_assignment(self, lvals: List[Lvalue], rval: Expression, ctx: AssignmentStmt) -> None: diff --git a/mypy/test/testsemanal.py b/mypy/test/testsemanal.py index ca7b1663cec8..a71bac53619d 100644 --- a/mypy/test/testsemanal.py +++ b/mypy/test/testsemanal.py @@ -20,20 +20,22 @@ # Semantic analyzer test cases: dump parse tree # Semantic analysis test case description files. -semanal_files = ['semanal-basic.test', - 'semanal-expressions.test', - 'semanal-classes.test', - 'semanal-types.test', - 'semanal-typealiases.test', - 'semanal-modules.test', - 'semanal-statements.test', - 'semanal-abstractclasses.test', - 'semanal-namedtuple.test', - 'semanal-typeddict.test', - 'semenal-literal.test', - 'semanal-classvar.test', - 'semanal-python2.test', - 'semanal-lambda.test'] +semanal_files = [ + 'semanal-basic.test', + 'semanal-expressions.test', + 'semanal-classes.test', + 'semanal-types.test', + 'semanal-typealiases.test', + 'semanal-modules.test', + 'semanal-statements.test', + 'semanal-abstractclasses.test', + 'semanal-namedtuple.test', + 'semanal-typeddict.test', + 'semenal-literal.test', + 'semanal-classvar.test', + 'semanal-python2.test', + 'semanal-lambda.test', +] def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Options: diff --git a/test-data/unit/check-classvar.test b/test-data/unit/check-classvar.test index f572db7225f2..d84bc8d5bf9d 100644 --- a/test-data/unit/check-classvar.test +++ b/test-data/unit/check-classvar.test @@ -285,7 +285,7 @@ main:3: error: Cannot assign to class variable "x" via instance from typing import ClassVar, Generic, TypeVar T = TypeVar('T') class A(Generic[T]): - x: ClassVar[T] + x: ClassVar[T] # E: ClassVar cannot contain type variables @classmethod def foo(cls) -> T: return cls.x # OK @@ -308,7 +308,7 @@ from typing import ClassVar, Generic, Tuple, TypeVar, Union, Type T = TypeVar('T') U = TypeVar('U') class A(Generic[T, U]): - x: ClassVar[Union[T, Tuple[U, Type[U]]]] + x: ClassVar[Union[T, Tuple[U, Type[U]]]] # E: ClassVar cannot contain type variables @classmethod def foo(cls) -> Union[T, Tuple[U, Type[U]]]: return cls.x # OK diff --git a/test-data/unit/semanal-classvar.test b/test-data/unit/semanal-classvar.test index 8add559bdd27..a7bcec0324dc 100644 --- a/test-data/unit/semanal-classvar.test +++ b/test-data/unit/semanal-classvar.test @@ -207,3 +207,14 @@ class B: pass [out] main:4: error: ClassVar can only be used for assignments in class body + +[case testClassVarWithTypeVariable] +from typing import ClassVar, TypeVar, Generic, List + +T = TypeVar('T') + +class Some(Generic[T]): + error: ClassVar[T] # E: ClassVar cannot contain type variables + nested: ClassVar[List[List[T]]] # E: ClassVar cannot contain type variables + ok: ClassVar[int] +[out]