Skip to content

Commit

Permalink
Now ClassVar cannot contain type variables (#11585)
Browse files Browse the repository at this point in the history
Resolves part of #11538
  • Loading branch information
sobolevn committed Nov 24, 2021
1 parent 077e820 commit f79e7af
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 18 deletions.
4 changes: 4 additions & 0 deletions mypy/message_registry.py
Expand Up @@ -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(
Expand Down
10 changes: 8 additions & 2 deletions mypy/semanal.py
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down
30 changes: 16 additions & 14 deletions mypy/test/testsemanal.py
Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-classvar.test
Expand Up @@ -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
Expand All @@ -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
Expand Down
11 changes: 11 additions & 0 deletions test-data/unit/semanal-classvar.test
Expand Up @@ -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]

0 comments on commit f79e7af

Please sign in to comment.