[ty] fix incorrect member narrowing (#19802)

## Summary

Reported in:
https://github.com/astral-sh/ruff/pull/19795#issuecomment-3161981945

If a root expression is reassigned, narrowing on the member should be
invalidated, but there was an oversight in the current implementation.

This PR fixes that, and also removes some unnecessary handling.

## Test Plan

New tests cases in `narrow/conditionals/nested.md`.
This commit is contained in:
Shunsuke Shibayama 2025-08-08 08:04:07 +09:00 committed by GitHub
parent f51a228f04
commit 462adfd0e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 20 deletions

View file

@ -275,7 +275,49 @@ def f(a: A):
a.x = None
```
Narrowing is invalidated if a `nonlocal` declaration is made within a lazy scope.
The opposite is not true, that is, if a root expression is reassigned, narrowing on the member are
no longer valid in the inner lazy scope.
```py
def f(l: list[str | None]):
if l[0] is not None:
def _():
reveal_type(l[0]) # revealed: str | None
l = [None]
def f(l: list[str | None]):
l[0] = "a"
def _():
reveal_type(l[0]) # revealed: str | None
l = [None]
def f(l: list[str | None]):
l[0] = "a"
def _():
l: list[str | None] = [None]
def _():
# TODO: should be `str | None`
reveal_type(l[0]) # revealed: Unknown
def _():
def _():
reveal_type(l[0]) # revealed: str | None
l: list[str | None] = [None]
def f(a: A):
if a.x is not None:
def _():
reveal_type(a.x) # revealed: str | None
a = A()
def f(a: A):
a.x = "a"
def _():
reveal_type(a.x) # revealed: str | None
a = A()
```
Narrowing is also invalidated if a `nonlocal` declaration is made within a lazy scope.
```py
def f(non_local: str | None):