Skip to content

Commit 834efe4

Browse files
authored
Allow generic class variables (#7906)
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.
1 parent 6e0c6ef commit 834efe4

File tree

5 files changed

+54
-24
lines changed

5 files changed

+54
-24
lines changed

mypy/checkmember.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,11 @@ def analyze_class_attribute_access(itype: Instance,
716716
if isinstance(t, TypeVarType) or get_type_vars(t):
717717
# Exception: access on Type[...], including first argument of class methods is OK.
718718
if not isinstance(get_proper_type(mx.original_type), TypeType):
719-
mx.msg.fail(message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS, mx.context)
719+
if node.node.is_classvar:
720+
message = message_registry.GENERIC_CLASS_VAR_ACCESS
721+
else:
722+
message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS
723+
mx.msg.fail(message, mx.context)
720724

721725
# Erase non-mapped variables, but keep mapped ones, even if there is an error.
722726
# In the above example this means that we infer following types:

mypy/message_registry.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@
8888
# Generic
8989
GENERIC_INSTANCE_VAR_CLASS_ACCESS = \
9090
'Access to generic instance variables via class is ambiguous' # type: Final
91+
GENERIC_CLASS_VAR_ACCESS = \
92+
'Access to generic class variables is ambiguous' # type: Final
9193
BARE_GENERIC = 'Missing type parameters for generic type {}' # type: Final
9294
IMPLICIT_GENERIC_ANY_BUILTIN = \
9395
'Implicit generic "Any". Use "{}" and specify generic parameters' # type: Final

mypy/typeanal.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
Type, UnboundType, TypeVarType, TupleType, TypedDictType, UnionType, Instance, AnyType,
1616
CallableType, NoneType, ErasedType, DeletedType, TypeList, TypeVarDef, SyntheticTypeVisitor,
1717
StarType, PartialType, EllipsisType, UninhabitedType, TypeType, replace_alias_tvars,
18-
CallableArgument, get_type_vars, TypeQuery, union_items, TypeOfAny,
19-
LiteralType, RawExpressionType, PlaceholderType, Overloaded, get_proper_type, ProperType
18+
CallableArgument, TypeQuery, union_items, TypeOfAny, LiteralType, RawExpressionType,
19+
PlaceholderType, Overloaded, get_proper_type, ProperType
2020
)
2121

2222
from mypy.nodes import (
@@ -311,9 +311,6 @@ def try_analyze_special_unbound_type(self, t: UnboundType, fullname: str) -> Opt
311311
self.fail('ClassVar[...] must have at most one type argument', t)
312312
return AnyType(TypeOfAny.from_error)
313313
item = self.anal_type(t.args[0])
314-
if isinstance(item, TypeVarType) or get_type_vars(item):
315-
self.fail('Invalid type: ClassVar cannot be generic', t)
316-
return AnyType(TypeOfAny.from_error)
317314
return item
318315
elif fullname in ('mypy_extensions.NoReturn', 'typing.NoReturn'):
319316
return UninhabitedType(is_noreturn=True)

test-data/unit/check-classvar.test

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,3 +280,48 @@ class A:
280280
[out]
281281
main:2: note: Revealed type is 'builtins.int'
282282
main:3: error: Cannot assign to class variable "x" via instance
283+
284+
[case testClassVarWithGeneric]
285+
from typing import ClassVar, Generic, TypeVar
286+
T = TypeVar('T')
287+
class A(Generic[T]):
288+
x: ClassVar[T]
289+
@classmethod
290+
def foo(cls) -> T:
291+
return cls.x # OK
292+
293+
A.x # E: Access to generic class variables is ambiguous
294+
A.x = 1 # E: Access to generic class variables is ambiguous
295+
A[int].x # E: Access to generic class variables is ambiguous
296+
297+
class Bad(A[int]):
298+
pass
299+
Bad.x # E: Access to generic class variables is ambiguous
300+
301+
class Good(A[int]):
302+
x = 42
303+
reveal_type(Good.x) # N: Revealed type is 'builtins.int'
304+
[builtins fixtures/classmethod.pyi]
305+
306+
[case testClassVarWithNestedGeneric]
307+
from typing import ClassVar, Generic, Tuple, TypeVar, Union, Type
308+
T = TypeVar('T')
309+
U = TypeVar('U')
310+
class A(Generic[T, U]):
311+
x: ClassVar[Union[T, Tuple[U, Type[U]]]]
312+
@classmethod
313+
def foo(cls) -> Union[T, Tuple[U, Type[U]]]:
314+
return cls.x # OK
315+
316+
A.x # E: Access to generic class variables is ambiguous
317+
A.x = 1 # E: Access to generic class variables is ambiguous
318+
A[int, str].x # E: Access to generic class variables is ambiguous
319+
320+
class Bad(A[int, str]):
321+
pass
322+
Bad.x # E: Access to generic class variables is ambiguous
323+
324+
class Good(A[int, str]):
325+
x = 42
326+
reveal_type(Good.x) # N: Revealed type is 'builtins.int'
327+
[builtins fixtures/classmethod.pyi]

test-data/unit/semanal-classvar.test

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -206,21 +206,3 @@ class B:
206206
pass
207207
[out]
208208
main:4: error: ClassVar can only be used for assignments in class body
209-
210-
[case testClassVarWithGeneric]
211-
from typing import ClassVar, Generic, TypeVar
212-
T = TypeVar('T')
213-
class A(Generic[T]):
214-
x = None # type: ClassVar[T]
215-
[out]
216-
main:4: error: Invalid type: ClassVar cannot be generic
217-
218-
[case testClassVarWithNestedGeneric]
219-
from typing import ClassVar, Generic, List, TypeVar, Union
220-
T = TypeVar('T')
221-
U = TypeVar('U')
222-
class A(Generic[T, U]):
223-
x = None # type: ClassVar[Union[T, List[U]]]
224-
[builtins fixtures/list.pyi]
225-
[out]
226-
main:5: error: Invalid type: ClassVar cannot be generic

0 commit comments

Comments
 (0)