Skip to content

Commit c3ca807

Browse files
committed
New approach
1 parent 03dedfe commit c3ca807

File tree

2 files changed

+53
-6
lines changed

2 files changed

+53
-6
lines changed

mypy/checker.py

+35-6
Original file line numberDiff line numberDiff line change
@@ -2091,6 +2091,7 @@ def check_assignment(self, lvalue: Lvalue, rvalue: Expression, infer_lvalue_type
20912091
infer_lvalue_type)
20922092
else:
20932093
self.try_infer_partial_generic_type_from_assignment(lvalue, rvalue, '=')
2094+
self.try_infer_partial_generic_type_from_super(lvalue, rvalue)
20942095
lvalue_type, index_lvalue, inferred = self.check_lvalue(lvalue)
20952096
# If we're assigning to __getattr__ or similar methods, check that the signature is
20962097
# valid.
@@ -2241,6 +2242,40 @@ def try_infer_partial_generic_type_from_assignment(self,
22412242
var.type = fill_typevars_with_any(typ.type)
22422243
del partial_types[var]
22432244

2245+
def try_infer_partial_generic_type_from_super(self, lvalue: Lvalue,
2246+
rvalue: Expression) -> None:
2247+
"""Try to infer a precise type for partial generic type from super types.
2248+
2249+
Example where this happens:
2250+
2251+
class P:
2252+
x: List[int]
2253+
2254+
class C(P):
2255+
x = [] # Infer List[int] as type of 'x'
2256+
2257+
"""
2258+
var = None
2259+
if (isinstance(lvalue, NameExpr)
2260+
and isinstance(lvalue.node, Var)
2261+
and lvalue.node.type is None):
2262+
var = lvalue.node
2263+
self.infer_partial_type(var, lvalue,
2264+
self.expr_checker.accept(rvalue))
2265+
2266+
if var is not None:
2267+
partial_types = self.find_partial_types(var)
2268+
if partial_types is None:
2269+
return
2270+
2271+
parent_type = self.get_defined_in_base_class(var)
2272+
if parent_type is not None and is_valid_inferred_type(parent_type):
2273+
self.set_inferred_type(var, lvalue, parent_type)
2274+
if isinstance(lvalue, RefExpr):
2275+
# We need this to escape another round of inference:
2276+
lvalue.is_inferred_def = False
2277+
del partial_types[var]
2278+
22442279
def check_compatibility_all_supers(self, lvalue: RefExpr, lvalue_type: Optional[Type],
22452280
rvalue: Expression) -> bool:
22462281
lvalue_node = lvalue.node
@@ -4870,12 +4905,6 @@ def enter_partial_types(self, *, is_function: bool = False,
48704905
and not permissive):
48714906
var.type = NoneType()
48724907
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)
48794908
if var not in self.partial_reported and not permissive:
48804909
self.msg.need_annotation_for_var(var, context, self.options.python_version)
48814910
self.partial_reported.add(var)

test-data/unit/check-classes.test

+18
Original file line numberDiff line numberDiff line change
@@ -992,6 +992,24 @@ class C(P, M):
992992
reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.int]"
993993
[builtins fixtures/list.pyi]
994994

995+
[case testAccessingClassAttributeWithTypeInferenceWithMixinTypeConflict]
996+
from typing import List
997+
class P:
998+
x: List[int]
999+
class M:
1000+
x: List[str]
1001+
class C(P, M):
1002+
x = [] # E: Incompatible types in assignment (expression has type "List[int]", base class "M" defined the type as "List[str]")
1003+
reveal_type(C.x) # N: Revealed type is "builtins.list[builtins.int]"
1004+
[builtins fixtures/list.pyi]
1005+
1006+
[case testClassAttributeWithTypeInferenceNoParentType]
1007+
class P:
1008+
...
1009+
class C(P):
1010+
x = [] # E: Need type annotation for "x" (hint: "x: List[<type>] = ...")
1011+
[builtins fixtures/list.pyi]
1012+
9951013
[case testClassAttributeWithTypeInferenceInvalidType]
9961014
from typing import List
9971015
class P:

0 commit comments

Comments
 (0)