Skip to content

Commit 0f61182

Browse files
committed
Cleanup generic class variable access
1 parent 61cbe0c commit 0f61182

File tree

7 files changed

+58
-35
lines changed

7 files changed

+58
-35
lines changed

mypy/checkmember.py

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
freeze_all_type_vars,
4646
function_type,
4747
get_all_type_vars,
48-
get_type_vars,
4948
make_simplified_union,
5049
supported_self_type,
5150
tuple_fallback,
@@ -1192,31 +1191,36 @@ def analyze_class_attribute_access(
11921191

11931192
if isinstance(node.node, Var):
11941193
assert isuper is not None
1194+
object_type = get_proper_type(mx.self_type)
11951195
# Check if original variable type has type variables. For example:
11961196
# class C(Generic[T]):
11971197
# x: T
11981198
# C.x # Error, ambiguous access
11991199
# C[int].x # Also an error, since C[int] is same as C at runtime
12001200
# Exception is Self type wrapped in ClassVar, that is safe.
1201+
prohibit_self = not node.node.is_classvar
12011202
def_vars = set(node.node.info.defn.type_vars)
1202-
if not node.node.is_classvar and node.node.info.self_type:
1203+
if prohibit_self and node.node.info.self_type:
12031204
def_vars.add(node.node.info.self_type)
1204-
# TODO: should we include ParamSpec etc. here (i.e. use get_all_type_vars)?
1205-
typ_vars = set(get_type_vars(t))
1206-
if def_vars & typ_vars:
1207-
# Exception: access on Type[...], including first argument of class methods is OK.
1208-
if not isinstance(get_proper_type(mx.original_type), TypeType) or node.implicit:
1209-
if node.node.is_classvar:
1210-
message = message_registry.GENERIC_CLASS_VAR_ACCESS
1211-
else:
1212-
message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS
1213-
mx.fail(message)
1205+
# Exception: access on Type[...], including first argument of class methods is OK.
1206+
prohibit_generic = not isinstance(object_type, TypeType) or node.implicit
1207+
if prohibit_generic and def_vars & set(get_all_type_vars(t)):
1208+
if node.node.is_classvar:
1209+
message = message_registry.GENERIC_CLASS_VAR_ACCESS
1210+
else:
1211+
message = message_registry.GENERIC_INSTANCE_VAR_CLASS_ACCESS
1212+
mx.fail(message)
12141213
t = expand_self_type_if_needed(t, mx, node.node, itype, is_class=True)
1214+
t = expand_type_by_instance(t, isuper)
12151215
# Erase non-mapped variables, but keep mapped ones, even if there is an error.
12161216
# In the above example this means that we infer following types:
12171217
# C.x -> Any
12181218
# C[int].x -> int
1219-
t = erase_typevars(expand_type_by_instance(t, isuper), {tv.id for tv in def_vars})
1219+
if prohibit_generic:
1220+
erase_vars = set(itype.type.defn.type_vars)
1221+
if prohibit_self and itype.type.self_type:
1222+
erase_vars.add(itype.type.self_type)
1223+
t = erase_typevars(t, {tv.id for tv in erase_vars})
12201224

12211225
is_classmethod = (is_decorated and cast(Decorator, node.node).func.is_class) or (
12221226
isinstance(node.node, SYMBOL_FUNCBASE_TYPES) and node.node.is_class

mypy/message_registry.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,6 @@ def with_additional_msg(self, info: str) -> ErrorMessage:
253253
'Cannot override class variable (previously declared on base class "{}") with instance '
254254
"variable"
255255
)
256-
CLASS_VAR_WITH_TYPEVARS: Final = "ClassVar cannot contain type variables"
257256
CLASS_VAR_WITH_GENERIC_SELF: Final = "ClassVar cannot contain Self type in generic classes"
258257
CLASS_VAR_OUTSIDE_OF_CLASS: Final = "ClassVar can only be used for assignments in class body"
259258

mypy/semanal.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5071,14 +5071,6 @@ def check_classvar(self, s: AssignmentStmt) -> None:
50715071
node.is_classvar = True
50725072
analyzed = self.anal_type(s.type)
50735073
assert self.type is not None
5074-
if analyzed is not None and set(get_type_vars(analyzed)) & set(
5075-
self.type.defn.type_vars
5076-
):
5077-
# This means that we have a type var defined inside of a ClassVar.
5078-
# This is not allowed by PEP526.
5079-
# See https://github.com/python/mypy/issues/11538
5080-
5081-
self.fail(message_registry.CLASS_VAR_WITH_TYPEVARS, s)
50825074
if (
50835075
analyzed is not None
50845076
and self.type.self_type in get_type_vars(analyzed)

test-data/unit/check-classvar.test

Lines changed: 17 additions & 2 deletions
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] # E: ClassVar cannot contain type variables
288+
x: ClassVar[T] # Error reported at access site
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]]]] # E: ClassVar cannot contain type variables
311+
x: ClassVar[Union[T, Tuple[U, Type[U]]]] # Error reported at access site
312312
@classmethod
313313
def foo(cls) -> Union[T, Tuple[U, Type[U]]]:
314314
return cls.x # OK
@@ -343,3 +343,18 @@ class C:
343343
g: ClassVar[Union[Callable[[C], int], int]] = f
344344

345345
reveal_type(C().g) # N: Revealed type is "Union[def () -> builtins.int, builtins.int]"
346+
347+
[case testGenericSubclassAccessNoLeak]
348+
from typing import ClassVar, Generic, TypeVar
349+
350+
T = TypeVar("T")
351+
class B(Generic[T]):
352+
x: T
353+
y: ClassVar[T]
354+
355+
class C(B[T]): ...
356+
357+
reveal_type(C.x) # E: Access to generic instance variables via class is ambiguous \
358+
# N: Revealed type is "Any"
359+
reveal_type(C.y) # E: Access to generic class variables is ambiguous \
360+
# N: Revealed type is "Any"

test-data/unit/check-selftype.test

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2219,3 +2219,16 @@ class B:
22192219
class C(A, B): # OK: both methods take Self
22202220
pass
22212221
[builtins fixtures/tuple.pyi]
2222+
2223+
[case testSelfTypeClassMethodNotSilentlyErased]
2224+
from typing import ClassVar, Self, Optional
2225+
2226+
class X:
2227+
_inst: Optional[Self] = None
2228+
@classmethod
2229+
def default(cls) -> Self:
2230+
reveal_type(cls._inst) # N: Revealed type is "Union[Self`0, None]"
2231+
if cls._inst is None:
2232+
cls._inst = cls()
2233+
return cls._inst
2234+
[builtins fixtures/tuple.pyi]

test-data/unit/check-typevar-tuple.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2628,3 +2628,14 @@ def fn(f: Callable[[*tuple[T]], int]) -> Callable[[*tuple[T]], int]: ...
26282628
def test(*args: Unpack[tuple[T]]) -> int: ...
26292629
reveal_type(fn(test)) # N: Revealed type is "def [T] (T`1) -> builtins.int"
26302630
[builtins fixtures/tuple.pyi]
2631+
2632+
[case testNoGenericTypeVarTupleClassVarAccess]
2633+
from typing import Generic, Tuple, TypeVarTuple, Unpack
2634+
2635+
Ts = TypeVarTuple("Ts")
2636+
class C(Generic[Unpack[Ts]]):
2637+
x: Tuple[Unpack[Ts]]
2638+
2639+
reveal_type(C.x) # E: Access to generic instance variables via class is ambiguous \
2640+
# N: Revealed type is "builtins.tuple[Any, ...]"
2641+
[builtins fixtures/tuple.pyi]

test-data/unit/semanal-classvar.test

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -207,14 +207,3 @@ 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)