Skip to content

Commit f79e7af

Browse files
authored
Now ClassVar cannot contain type variables (#11585)
Resolves part of #11538
1 parent 077e820 commit f79e7af

File tree

5 files changed

+41
-18
lines changed

5 files changed

+41
-18
lines changed

mypy/message_registry.py

+4
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,10 @@ def format(self, *args: object, **kwargs: object) -> "ErrorMessage":
212212
'Cannot override class variable (previously declared on base class "{}") with instance '
213213
"variable"
214214
)
215+
CLASS_VAR_WITH_TYPEVARS: Final = 'ClassVar cannot contain type variables'
216+
CLASS_VAR_OUTSIDE_OF_CLASS: Final = (
217+
'ClassVar can only be used for assignments in class body'
218+
)
215219

216220
# Protocol
217221
RUNTIME_PROTOCOL_EXPECTED: Final = ErrorMessage(

mypy/semanal.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@
9494
TypeTranslator, TypeOfAny, TypeType, NoneType, PlaceholderType, TPDICT_NAMES, ProperType,
9595
get_proper_type, get_proper_types, TypeAliasType, TypeVarLikeType
9696
)
97-
from mypy.typeops import function_type
97+
from mypy.typeops import function_type, get_type_vars
9898
from mypy.type_visitor import TypeQuery
9999
from mypy.typeanal import (
100100
TypeAnalyser, analyze_type_alias, no_subscript_builtin_alias,
@@ -3337,6 +3337,12 @@ def check_classvar(self, s: AssignmentStmt) -> None:
33373337
node = lvalue.node
33383338
if isinstance(node, Var):
33393339
node.is_classvar = True
3340+
analyzed = self.anal_type(s.type)
3341+
if analyzed is not None and get_type_vars(analyzed):
3342+
# This means that we have a type var defined inside of a ClassVar.
3343+
# This is not allowed by PEP526.
3344+
# See https://github.com/python/mypy/issues/11538
3345+
self.fail(message_registry.CLASS_VAR_WITH_TYPEVARS, s)
33403346
elif not isinstance(lvalue, MemberExpr) or self.is_self_member_ref(lvalue):
33413347
# In case of member access, report error only when assigning to self
33423348
# Other kinds of member assignments should be already reported
@@ -3359,7 +3365,7 @@ def is_final_type(self, typ: Optional[Type]) -> bool:
33593365
return sym.node.fullname in ('typing.Final', 'typing_extensions.Final')
33603366

33613367
def fail_invalid_classvar(self, context: Context) -> None:
3362-
self.fail('ClassVar can only be used for assignments in class body', context)
3368+
self.fail(message_registry.CLASS_VAR_OUTSIDE_OF_CLASS, context)
33633369

33643370
def process_module_assignment(self, lvals: List[Lvalue], rval: Expression,
33653371
ctx: AssignmentStmt) -> None:

mypy/test/testsemanal.py

+16-14
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,22 @@
2020
# Semantic analyzer test cases: dump parse tree
2121

2222
# Semantic analysis test case description files.
23-
semanal_files = ['semanal-basic.test',
24-
'semanal-expressions.test',
25-
'semanal-classes.test',
26-
'semanal-types.test',
27-
'semanal-typealiases.test',
28-
'semanal-modules.test',
29-
'semanal-statements.test',
30-
'semanal-abstractclasses.test',
31-
'semanal-namedtuple.test',
32-
'semanal-typeddict.test',
33-
'semenal-literal.test',
34-
'semanal-classvar.test',
35-
'semanal-python2.test',
36-
'semanal-lambda.test']
23+
semanal_files = [
24+
'semanal-basic.test',
25+
'semanal-expressions.test',
26+
'semanal-classes.test',
27+
'semanal-types.test',
28+
'semanal-typealiases.test',
29+
'semanal-modules.test',
30+
'semanal-statements.test',
31+
'semanal-abstractclasses.test',
32+
'semanal-namedtuple.test',
33+
'semanal-typeddict.test',
34+
'semenal-literal.test',
35+
'semanal-classvar.test',
36+
'semanal-python2.test',
37+
'semanal-lambda.test',
38+
]
3739

3840

3941
def get_semanal_options(program_text: str, testcase: DataDrivenTestCase) -> Options:

test-data/unit/check-classvar.test

+2-2
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ main:3: error: Cannot assign to class variable "x" via instance
285285
from typing import ClassVar, Generic, TypeVar
286286
T = TypeVar('T')
287287
class A(Generic[T]):
288-
x: ClassVar[T]
288+
x: ClassVar[T] # E: ClassVar cannot contain type variables
289289
@classmethod
290290
def foo(cls) -> T:
291291
return cls.x # OK
@@ -308,7 +308,7 @@ from typing import ClassVar, Generic, Tuple, TypeVar, Union, Type
308308
T = TypeVar('T')
309309
U = TypeVar('U')
310310
class A(Generic[T, U]):
311-
x: ClassVar[Union[T, Tuple[U, Type[U]]]]
311+
x: ClassVar[Union[T, Tuple[U, Type[U]]]] # E: ClassVar cannot contain type variables
312312
@classmethod
313313
def foo(cls) -> Union[T, Tuple[U, Type[U]]]:
314314
return cls.x # OK

test-data/unit/semanal-classvar.test

+11
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,14 @@ class B:
207207
pass
208208
[out]
209209
main:4: error: ClassVar can only be used for assignments in class body
210+
211+
[case testClassVarWithTypeVariable]
212+
from typing import ClassVar, TypeVar, Generic, List
213+
214+
T = TypeVar('T')
215+
216+
class Some(Generic[T]):
217+
error: ClassVar[T] # E: ClassVar cannot contain type variables
218+
nested: ClassVar[List[List[T]]] # E: ClassVar cannot contain type variables
219+
ok: ClassVar[int]
220+
[out]

0 commit comments

Comments
 (0)