Skip to content

Commit ad85bc5

Browse files
authored
Fix accessing unannotated implicit class methods (#7739)
Fixes #7735 The fix is quite straightforward, this was not triggered before because all common cases go troigh other code paths.
1 parent bd94dff commit ad85bc5

File tree

3 files changed

+54
-2
lines changed

3 files changed

+54
-2
lines changed

mypy/checkmember.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -736,7 +736,14 @@ def analyze_class_attribute_access(itype: Instance,
736736
mx.not_ready_callback(name, mx.context)
737737
return AnyType(TypeOfAny.from_error)
738738
else:
739-
return function_type(cast(FuncBase, node.node), mx.builtin_type('builtins.function'))
739+
assert isinstance(node.node, FuncBase)
740+
typ = function_type(node.node, mx.builtin_type('builtins.function'))
741+
# Note: if we are accessing class method on class object, the cls argument is bound.
742+
# Annotated and/or explicit class methods go through other code paths above, for
743+
# unannotated implicit class methods we do this here.
744+
if node.node.is_class:
745+
typ = bind_self(typ, is_classmethod=True)
746+
return typ
740747

741748

742749
def add_class_tvars(t: ProperType, itype: Instance, isuper: Optional[Instance],

test-data/unit/check-classes.test

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6387,6 +6387,42 @@ class MidBase(Base): pass
63876387
[file init_subclass/__init__.py]
63886388
[builtins fixtures/object_with_init_subclass.pyi]
63896389

6390+
[case testInitSubclassUnannotated]
6391+
class A:
6392+
def __init_subclass__(cls, *args, **kwargs):
6393+
super().__init_subclass__(*args, **kwargs)
6394+
6395+
class B(A):
6396+
pass
6397+
6398+
reveal_type(A.__init_subclass__) # N: Revealed type is 'def (*args: Any, **kwargs: Any) -> Any'
6399+
[builtins fixtures/object_with_init_subclass.pyi]
6400+
6401+
[case testInitSubclassUnannotatedMulti]
6402+
from typing import ClassVar, List, Type
6403+
6404+
class A:
6405+
registered_classes: ClassVar[List[Type[A]]] = []
6406+
def __init_subclass__(cls, *args, register=True, **kwargs):
6407+
if register:
6408+
cls.registered_classes.append(cls)
6409+
super().__init_subclass__(*args, **kwargs)
6410+
6411+
class B(A): ...
6412+
class C(A, register=False): ...
6413+
class D(C): ...
6414+
[builtins fixtures/object_with_init_subclass.pyi]
6415+
6416+
[case testClassMethodUnannotated]
6417+
class C:
6418+
def __new__(cls): ...
6419+
@classmethod
6420+
def meth(cls): ...
6421+
6422+
reveal_type(C.meth) # N: Revealed type is 'def () -> Any'
6423+
reveal_type(C.__new__) # N: Revealed type is 'def (cls: Type[__main__.C]) -> Any'
6424+
[builtins fixtures/classmethod.pyi]
6425+
63906426
[case testOverrideGenericSelfClassMethod]
63916427
from typing import Generic, TypeVar, Type, List
63926428

test-data/unit/fixtures/object_with_init_subclass.pyi

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Sequence, Iterator, TypeVar, Mapping, Iterable, Optional, Union, overload, Tuple, Generic
1+
from typing import Sequence, Iterator, TypeVar, Mapping, Iterable, Optional, Union, overload, Tuple, Generic, List
22

33
class object:
44
def __init__(self) -> None: ...
@@ -34,6 +34,15 @@ class tuple(Generic[T]): pass
3434
class function: pass
3535
class ellipsis: pass
3636

37+
# copy-pasted from list.pyi
38+
class list(Sequence[T]):
39+
def __iter__(self) -> Iterator[T]: pass
40+
def __mul__(self, x: int) -> list[T]: pass
41+
def __setitem__(self, x: int, v: T) -> None: pass
42+
def __getitem__(self, x: int) -> T: pass
43+
def __add__(self, x: List[T]) -> T: pass
44+
def __contains__(self, item: object) -> bool: pass
45+
3746
# copy-pasted from dict.pyi
3847
class dict(Mapping[KT, VT]):
3948
@overload

0 commit comments

Comments
 (0)