Skip to content

Be stricter about access to generic vars from class #18100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1081,19 +1081,18 @@ def analyze_class_attribute_access(
# x: T
# C.x # Error, ambiguous access
# C[int].x # Also an error, since C[int] is same as C at runtime
# Exception is Self type wrapped in ClassVar, that is safe.
# Exception is Self type, which is always allowed
def_vars = set(node.node.info.defn.type_vars)
if not node.node.is_classvar and node.node.info.self_type:
def_vars.add(node.node.info.self_type)
if node.node.info.self_type:
def_vars.discard(node.node.info.self_type)
typ_vars = set(get_type_vars(t))
if def_vars & typ_vars:
# Exception: access on Type[...], including first argument of class methods is OK.
if not isinstance(get_proper_type(mx.original_type), TypeType) or node.implicit:
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)
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)

t = expand_self_type_if_needed(t, mx, node.node, itype, is_class=True)
# 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
23 changes: 17 additions & 6 deletions test-data/unit/check-classvar.test
Original file line number Diff line number Diff line change
Expand Up @@ -282,17 +282,28 @@ 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
import types
from typing import ClassVar, Generic, TypeVar, Type
T = TypeVar('T')
class A(Generic[T]):
x: ClassVar[T] # E: ClassVar cannot contain type variables
@classmethod
def foo(cls) -> T:
return cls.x # OK
return cls.x # E: Access to generic class variables is ambiguous

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
def main(A_T: Type[A]):
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

A_T.x # E: Access to generic class variables is ambiguous
A_T.x = 1 # E: Access to generic class variables is ambiguous
A_T[int].x # E: Value of type "Type[A[Any]]" is not indexable

a = A
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: The type "Type[A[Any]]" is not generic and not indexable

class Bad(A[int]):
pass
Expand All @@ -311,7 +322,7 @@ class A(Generic[T, 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
return cls.x # E: Access to generic class variables is ambiguous

A.x # E: Access to generic class variables is ambiguous
A.x = 1 # E: Access to generic class variables is ambiguous
Expand Down
42 changes: 40 additions & 2 deletions test-data/unit/check-generics.test
Original file line number Diff line number Diff line change
Expand Up @@ -2225,14 +2225,52 @@ class C(Generic[T]):
x: T
@classmethod
def get(cls) -> T:
return cls.x # OK
return cls.x # E: Access to generic instance variables via class is ambiguous

x = C.x # E: Access to generic instance variables via class is ambiguous
reveal_type(x) # N: Revealed type is "Any"
xi = C[int].x # E: Access to generic instance variables via class is ambiguous
reveal_type(xi) # N: Revealed type is "builtins.int"
[builtins fixtures/classmethod.pyi]

[case testGenericClassAttrSelfType]
from typing import TypeVar, Generic, Self, Type, Mapping, ClassVar

class A:
d: Mapping[str, Self]

@classmethod
def make(cls) -> Self:
return cls.d["asdf"]

class B(A): ...

def check_A(A_t: Type[A]) -> None:
reveal_type(A.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.A]"
reveal_type(A_t.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.A]"

def check_B(B_t: Type[B]) -> None:
reveal_type(B.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.B]"
reveal_type(B_t.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.B]"

class AA:
d: ClassVar[Mapping[str, Self]]

@classmethod
def make(cls) -> Self:
return cls.d["asdf"]

class BB(AA): ...

def check_AA(AA_t: Type[AA]) -> None:
reveal_type(AA.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.AA]"
reveal_type(AA_t.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.AA]"

def check_BB(BB_t: Type[BB]) -> None:
reveal_type(BB.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.BB]"
reveal_type(BB_t.d) # N: Revealed type is "typing.Mapping[builtins.str, __main__.BB]"
[builtins fixtures/classmethod.pyi]

[case testGenericClassAttrUnboundOnSubClass]
from typing import Generic, TypeVar, Tuple
T = TypeVar('T')
Expand Down Expand Up @@ -2288,7 +2326,7 @@ reveal_type(b) # N: Revealed type is "__main__.B"

def g(t: Type[Maker[T]]) -> T:
if bool():
return t.x
return t.x # E: Access to generic instance variables via class is ambiguous
return t.get()
bb = g(B)
reveal_type(bb) # N: Revealed type is "__main__.B"
Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-selftype.test
Original file line number Diff line number Diff line change
Expand Up @@ -1485,7 +1485,7 @@ class C:
class D(C): ...

reveal_type(C.meth) # N: Revealed type is "def [Self <: __main__.C] (self: Self`1) -> builtins.list[Self`1]"
C.attr # E: Access to generic instance variables via class is ambiguous
C.attr
reveal_type(D().meth()) # N: Revealed type is "builtins.list[__main__.D]"
reveal_type(D().attr) # N: Revealed type is "builtins.list[__main__.D]"

Expand Down Expand Up @@ -2120,7 +2120,7 @@ c: C
reveal_type(c.x) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.C]"
reveal_type(c.y) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.C]"
reveal_type(C.y) # N: Revealed type is "Tuple[builtins.int, builtins.str, fallback=__main__.C]"
C.x # E: Access to generic instance variables via class is ambiguous
C.x
[builtins fixtures/classmethod.pyi]

[case testAccessingTypingSelfUnion]
Expand Down
Loading