diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 0927149deeac..85683d3c82c1 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -716,7 +716,11 @@ def analyze_class_attribute_access(itype: Instance, if isinstance(t, TypeVarType) or get_type_vars(t): # Exception: access on Type[...], including first argument of class methods is OK. if not isinstance(get_proper_type(mx.original_type), TypeType): - mx.msg.fail(message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS, mx.context) + if node.node.is_classvar: + message = message_registry.GENERIC_CLASS_VAR_ACCESS + else: + message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS + mx.msg.fail(message, mx.context) # Erase non-mapped variables, but keep mapped ones, even if there is an error. # In the above example this means that we infer following types: diff --git a/mypy/message_registry.py b/mypy/message_registry.py index 195807216a2b..e7bc3f2e3bb0 100644 --- a/mypy/message_registry.py +++ b/mypy/message_registry.py @@ -88,6 +88,8 @@ # Generic GENERIC_INSTANCE_VAR_CLASS_ACCESS = \ 'Access to generic instance variables via class is ambiguous' # type: Final +GENERIC_CLASS_VAR_ACCESS = \ + 'Access to generic class variables is ambiguous' # type: Final BARE_GENERIC = 'Missing type parameters for generic type {}' # type: Final IMPLICIT_GENERIC_ANY_BUILTIN = \ 'Implicit generic "Any". Use "{}" and specify generic parameters' # type: Final diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 10004f17b357..4c36355e80b6 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -15,8 +15,8 @@ Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType, CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor, StarType, PartialType, EllipsisType, UninhabitedType, TypeType, replace_alias_tvars, - CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny, - LiteralType, RawExpressionType, PlaceholderType, Overloaded, get_proper_type, ProperType + CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType, + PlaceholderType, Overloaded, get_proper_type, ProperType ) from mypy.nodes import ( @@ -311,9 +311,6 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt self.fail('ClassVar[...] must have at most one type argument', t) return AnyType(TypeOfAny.from_error) item = self.anal_type(t.args[0]) - if isinstance(item, TypeVarType) or get_type_vars(item): - self.fail('Invalid type: ClassVar cannot be generic', t) - return AnyType(TypeOfAny.from_error) return item elif fullname in ('mypy_extensions.NoReturn', 'typing.NoReturn'): return UninhabitedType(is_noreturn=True) diff --git a/test-data/unit/check-classvar.test b/test-data/unit/check-classvar.test index 213aa42c776a..c288fef39283 100644 --- a/test-data/unit/check-classvar.test +++ b/test-data/unit/check-classvar.test @@ -280,3 +280,48 @@ class A: [out] main:2: note: Revealed type is 'builtins.int' main:3: error: Cannot assign to class variable "x" via instance + +[case testClassVarWithGeneric] +from typing import ClassVar, Generic, TypeVar +T = TypeVar('T') +class A(Generic[T]): + x: ClassVar[T] + @classmethod + def foo(cls) -> T: + return cls.x # OK + +A.x # E: Access to generic class variables is ambiguous +A.x = 1 # E: Access to generic class variables is ambiguous +A[int].x # E: Access to generic class variables is ambiguous + +class Bad(A[int]): + pass +Bad.x # E: Access to generic class variables is ambiguous + +class Good(A[int]): + x = 42 +reveal_type(Good.x) # N: Revealed type is 'builtins.int' +[builtins fixtures/classmethod.pyi] + +[case testClassVarWithNestedGeneric] +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]]]] + @classmethod + def foo(cls) -> Union[T, Tuple[U, Type[U]]]: + return cls.x # OK + +A.x # E: Access to generic class variables is ambiguous +A.x = 1 # E: Access to generic class variables is ambiguous +A[int, str].x # E: Access to generic class variables is ambiguous + +class Bad(A[int, str]): + pass +Bad.x # E: Access to generic class variables is ambiguous + +class Good(A[int, str]): + x = 42 +reveal_type(Good.x) # N: Revealed type is 'builtins.int' +[builtins fixtures/classmethod.pyi] diff --git a/test-data/unit/semanal-classvar.test b/test-data/unit/semanal-classvar.test index 0cd809b3a597..d39ee221efa2 100644 --- a/test-data/unit/semanal-classvar.test +++ b/test-data/unit/semanal-classvar.test @@ -206,21 +206,3 @@ class B: pass [out] main:4: error: ClassVar can only be used for assignments in class body - -[case testClassVarWithGeneric] -from typing import ClassVar, Generic, TypeVar -T = TypeVar('T') -class A(Generic[T]): - x = None # type: ClassVar[T] -[out] -main:4: error: Invalid type: ClassVar cannot be generic - -[case testClassVarWithNestedGeneric] -from typing import ClassVar, Generic, List, TypeVar, Union -T = TypeVar('T') -U = TypeVar('U') -class A(Generic[T, U]): - x = None # type: ClassVar[Union[T, List[U]]] -[builtins fixtures/list.pyi] -[out] -main:5: error: Invalid type: ClassVar cannot be generic