Skip to content

Commit

Permalink
Allow generic class variables (#7906)
Browse files Browse the repository at this point in the history
The existing check is overly strict, because we already have a check at the use site. Also this makes instance variables, class variables and class methods more consistent/homogeneous.
  • Loading branch information
ilevkivskyi authored Nov 8, 2019
1 parent 6e0c6ef commit 834efe4
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 24 deletions.
6 changes: 5 additions & 1 deletion mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions mypy/message_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 2 additions & 5 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand Down
45 changes: 45 additions & 0 deletions test-data/unit/check-classvar.test
Original file line number Diff line number Diff line change
Expand Up @@ -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]
18 changes: 0 additions & 18 deletions test-data/unit/semanal-classvar.test
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit 834efe4

Please sign in to comment.