From 10461b41080bd7a0f6d9a4a728f065eafa17f57c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Sun, 21 Nov 2021 12:40:10 +0300 Subject: [PATCH 1/2] Now `ClassVar` cannot contain type variables, refs #11538 --- mypy/message_registry.py | 4 ++++ mypy/semanal.py | 10 ++++++++-- mypy/test/testsemanal.py | 30 +++++++++++++++------------- test-data/unit/semanal-classvar.test | 11 ++++++++++ 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/mypy/message_registry.py b/mypy/message_registry.py index eda3081ae204..67d8bb486682 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -204,6 +204,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/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] From 366b58d9ca4abc51958b0ede06b8cd114d0e549a Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 23 Nov 2021 18:31:50 +0300 Subject: [PATCH 2/2] Fixes CI --- test-data/unit/check-classvar.test | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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