diff --git a/mypy/binder.py b/mypy/binder.py index 9984781733cd4..cd6d9ae3ff89e 100644 --- a/mypy/binder.py +++ b/mypy/binder.py @@ -398,6 +398,8 @@ def update_from_options(self, frames: list[Frame]) -> bool: if ( current_value is None or not is_same_type(type, current_value.type) + # 新语义下,即使类型语义等价,也要保留合并得到的窄化表示。 + or (not old_semantics and type != current_value.type) # Manually carry over any narrowing from hasattr() from inner frames. This is # a bit ad-hoc, but our handling of hasattr() is on best effort basis anyway. or isinstance(p_type := get_proper_type(type), Instance) diff --git a/mypy/checker.py b/mypy/checker.py index fd4486218dc11..1f40f4fc86b28 100644 --- a/mypy/checker.py +++ b/mypy/checker.py @@ -5477,7 +5477,8 @@ def visit_if_stmt(self, s: IfStmt) -> None: s.unreachable_else = is_unreachable_map(else_map) # XXX Issue a warning if condition is always False? - with self.binder.frame_context(can_skip=True, fall_through=2): + # else 分支不可达时,不能把进入 if 前的状态作为可跳过路径合并。 + with self.binder.frame_context(can_skip=not s.unreachable_else, fall_through=2): self.push_type_map(if_map, from_assignment=False) self.accept(b) diff --git a/test-data/unit/check-redefine2.test b/test-data/unit/check-redefine2.test index b5a8aeac0e3da..12a352c4c0c4f 100644 --- a/test-data/unit/check-redefine2.test +++ b/test-data/unit/check-redefine2.test @@ -1297,6 +1297,44 @@ def foo() -> None: if bool(): name = "a" +[case testNarrowingSurvivesAlwaysTrueBlock] +# flags: --allow-redefinition +class Base: ... +class Sub(Base): ... + +def f(x: Sub | Base) -> None: + if True: + assert not isinstance(x, Sub) + reveal_type(x) # N: Revealed type is "__main__.Base" + reveal_type(x) # N: Revealed type is "__main__.Base" +[builtins fixtures/isinstance.pyi] + +[case testNarrowingSurvivesAlwaysTrueBlockDefault] +class Base: ... +class Sub(Base): ... + +def f(x: Sub | Base) -> None: + if True: + assert not isinstance(x, Sub) + reveal_type(x) # N: Revealed type is "__main__.Base" + reveal_type(x) # N: Revealed type is "__main__.Base" +[builtins fixtures/isinstance.pyi] + +[case testNarrowingSurvivesTypeCheckingBlock] +# flags: --allow-redefinition +from typing import TYPE_CHECKING + +class Base: ... +class Sub(Base): ... + +def f(x: Sub | Base) -> None: + if TYPE_CHECKING: + assert not isinstance(x, Sub) + reveal_type(x) # N: Revealed type is "__main__.Base" + reveal_type(x) # N: Revealed type is "__main__.Base" +[builtins fixtures/isinstance.pyi] +[typing fixtures/typing-full.pyi] + [case testNewRedefinePartialTypeForUnderscore] # flags: --allow-redefinition