Skip to content

Commit 45992fc

Browse files
authored
Process overloads as a whole in two-phase type-checking (#22231)
Fixes #22229 This fixes a regression that can (often) cause false negatives in settable properties. The problem is that we currently may have some part of an overloaded function to be scheduled for processing as part of interface, and other parts to be processed as parts of implementation. This is especially problematic for properties (which are internally stored as overloads). I propose to always keep overloads as a whole. It may be marginally slower in parallel mode, but much simpler to reason about.
1 parent 2ec1257 commit 45992fc

2 files changed

Lines changed: 28 additions & 3 deletions

File tree

‎mypy/semanal.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4724,7 +4724,7 @@ def make_name_lvalue_point_to_existing_def(
47244724
and original_def.node.is_inferred
47254725
):
47264726
for func in self.scope.functions:
4727-
func.def_or_infer_vars = True
4727+
mark_as_top_level(func)
47284728

47294729
def analyze_tuple_or_list_lvalue(self, lval: TupleExpr, explicit_type: bool = False) -> None:
47304730
"""Analyze an lvalue or assignment target that is a list or tuple."""
@@ -4809,7 +4809,7 @@ def analyze_member_lvalue(
48094809
# TODO: should we also set lval.kind = MDEF?
48104810
self.type.names[lval.name] = SymbolTableNode(MDEF, v, implicit=True)
48114811
for func in self.scope.functions:
4812-
func.def_or_infer_vars = True
4812+
mark_as_top_level(func)
48134813

48144814
if self.is_self_member_ref(lval) or self.is_cls_member_ref(lval):
48154815
assert self.type, "Self or cls member outside a class"
@@ -4821,7 +4821,7 @@ def analyze_member_lvalue(
48214821
and cur_node.node.is_initialized_in_class
48224822
):
48234823
for func in self.scope.functions:
4824-
func.def_or_infer_vars = True
4824+
mark_as_top_level(func)
48254825
self.check_lvalue_validity(lval.node, lval)
48264826

48274827
def is_self_member_ref(self, memberexpr: MemberExpr) -> bool:
@@ -8617,6 +8617,18 @@ def erase_func_annotations(func: FuncDef) -> None:
86178617
func.unanalyzed_type = None
86188618

86198619

8620+
def mark_as_top_level(func: FuncBase) -> None:
8621+
"""Mark a function to be processed as part of module top level.
8622+
8623+
A non-trivial part here is that we want to keep overloads as a whole.
8624+
"""
8625+
func.def_or_infer_vars = True
8626+
if isinstance(func, OverloadedFuncDef):
8627+
for item in func.items:
8628+
defn = item if isinstance(item, FuncDef) else item.func
8629+
defn.def_or_infer_vars = True
8630+
8631+
86208632
def dotted_identifier_leftmost(s: str) -> str | None:
86218633
"""The leftmost component of s, if s is a dotted identifier, else None.
86228634

‎test-data/unit/check-classes.test‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9789,3 +9789,16 @@ class C(Generic[T]): ...
97899789
[file lib.py]
97909790
from typing import TypeVar
97919791
T = TypeVar("T", default=int, bound=str) # E: TypeVar default must be a subtype of the bound type
9792+
9793+
[case testSettablePropertyGetterCheckedWhenPartOfInterface]
9794+
class A:
9795+
@property
9796+
def p(self) -> str:
9797+
def nested() -> int:
9798+
return "no" # E: Incompatible return value type (got "str", expected "int")
9799+
return 0 # E: Incompatible return value type (got "int", expected "str")
9800+
9801+
@p.setter
9802+
def p(self, value: int) -> None:
9803+
self.x = value
9804+
[builtins fixtures/property.pyi]

0 commit comments

Comments
 (0)