Skip to content

Commit 95fdb41

Browse files
committed
Adds attribute type inference from super-types for partial types, refs #10870
1 parent 524c924 commit 95fdb41

File tree

3 files changed

+49
-1
lines changed

3 files changed

+49
-1
lines changed

mypy/checker.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4870,6 +4870,12 @@ def enter_partial_types(self, *, is_function: bool = False,
48704870
and not permissive):
48714871
var.type = NoneType()
48724872
else:
4873+
if is_class:
4874+
# Special case: possibly super-type defines the type for us?
4875+
parent_type = self.get_defined_in_base_class(var)
4876+
if parent_type is not None:
4877+
var.type = parent_type
4878+
self.partial_reported.add(var)
48734879
if var not in self.partial_reported and not permissive:
48744880
self.msg.need_annotation_for_var(var, context, self.options.python_version)
48754881
self.partial_reported.add(var)
@@ -4926,6 +4932,14 @@ def is_defined_in_base_class(self, var: Var) -> bool:
49264932
return True
49274933
return False
49284934

4935+
def get_defined_in_base_class(self, var: Var) -> Optional[Type]:
4936+
if var.info:
4937+
for base in var.info.mro[1:]:
4938+
found = base.get(var.name)
4939+
if found is not None:
4940+
return found.type
4941+
return None
4942+
49294943
def find_partial_types(self, var: Var) -> Optional[Dict[Var, Context]]:
49304944
"""Look for an active partial type scope containing variable.
49314945

test-data/unit/check-classes.test

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -972,6 +972,39 @@ x = C.x
972972
[out]
973973
main:2: error: Need type annotation for "x" (hint: "x: List[<type>] = ...")
974974

975+
[case testAccessingClassAttributeWithTypeInferenceWithSuperType]
976+
from typing import List
977+
class P:
978+
x: List[int]
979+
class C(P):
980+
x = []
981+
reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.int]"
982+
[builtins fixtures/list.pyi]
983+
984+
[case testAccessingClassAttributeWithTypeInferenceWithMixinType]
985+
from typing import List
986+
class P:
987+
pass
988+
class M:
989+
x: List[int]
990+
class C(P, M):
991+
x = []
992+
reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.int]"
993+
[builtins fixtures/list.pyi]
994+
995+
[case testClassAttributeWithTypeInferenceInvalidType]
996+
from typing import List
997+
class P:
998+
x: List[int]
999+
class C(P):
1000+
x = ['a'] # E: List item 0 has incompatible type "str"; expected "int"
1001+
[builtins fixtures/list.pyi]
1002+
1003+
[case testClassSlotsAttributeWithTypeInference]
1004+
class P:
1005+
__slots__ = []
1006+
[builtins fixtures/list.pyi]
1007+
9751008
[case testAccessingGenericClassAttribute]
9761009
from typing import Generic, TypeVar
9771010
T = TypeVar('T')

test-data/unit/fixtures/list.pyi

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Builtins stub used in list-related test cases.
22

3-
from typing import TypeVar, Generic, Iterable, Iterator, Sequence, overload
3+
from typing import TypeVar, Generic, Iterable, Iterator, Union, Sequence, overload
44

55
T = TypeVar('T')
66

77
class object:
8+
__slots__: Union['str', Iterable['str']]
89
def __init__(self) -> None: pass
910

1011
class type: pass

0 commit comments

Comments
 (0)