Skip to content

Adds attribute type inference from super-types for partial types #10871

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

Closed
wants to merge 5 commits into from
Closed
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
14 changes: 14 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4870,6 +4870,12 @@ def enter_partial_types(self, *, is_function: bool = False,
and not permissive):
var.type = NoneType()
else:
if is_class:
# Special case: possibly super-type defines the type for us?
parent_type = self.get_defined_in_base_class(var)
if parent_type is not None:
var.type = parent_type
self.partial_reported.add(var)
if var not in self.partial_reported and not permissive:
self.msg.need_annotation_for_var(var, context, self.options.python_version)
self.partial_reported.add(var)
Expand Down Expand Up @@ -4926,6 +4932,14 @@ def is_defined_in_base_class(self, var: Var) -> bool:
return True
return False

def get_defined_in_base_class(self, var: Var) -> Optional[Type]:
if var.info:
for base in var.info.mro[1:]:
found = base.get(var.name)
if found is not None:
return found.type
return None

def find_partial_types(self, var: Var) -> Optional[Dict[Var, Context]]:
"""Look for an active partial type scope containing variable.

Expand Down
47 changes: 47 additions & 0 deletions test-data/unit/check-classes.test
Original file line number Diff line number Diff line change
Expand Up @@ -972,6 +972,53 @@ x = C.x
[out]
main:2: error: Need type annotation for "x" (hint: "x: List[<type>] = ...")

[case testAccessingClassAttributeWithTypeInferenceWithSuperType]
from typing import List
class P:
x: List[int]
class C(P):
x = []
reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.int]"
[builtins fixtures/list.pyi]

[case testAccessingClassAttributeWithTypeInferenceWithMixinType]
from typing import List
class P:
pass
class M:
x: List[int]
class C(P, M):
x = []
reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.int]"
[builtins fixtures/list.pyi]

[case testClassAttributeWithTypeInferenceInvalidType]
from typing import List
class P:
x: List[int]
class C(P):
x = ['a'] # E: List item 0 has incompatible type "str"; expected "int"
[builtins fixtures/list.pyi]

case testAccessingClassAttributeWithTypeInferenceWithMixinTypeConflict]
from typing import List
class P:
x: List[int]
class M:
x: List[str]
class C(P, M):
x = [] # This hides subclassing issue, but that's how it is for now

reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.int]"
[builtins fixtures/list.pyi]

[case testClassAttributeWithTypeInferenceNoParentType]
class P:
...
class C(P):
x = [] # E: Need type annotation for "x" (hint: "x: List[<type>] = ...")
[builtins fixtures/list.pyi]

[case testAccessingGenericClassAttribute]
from typing import Generic, TypeVar
T = TypeVar('T')
Expand Down