[ty] more precise lazy scope place lookup (#19932)

## Summary

This is a follow-up to https://github.com/astral-sh/ruff/pull/19321.

Now lazy snapshots are updated to take into account new bindings on
every symbol reassignment.

```python
def outer(x: A | None):
    if x is None:
        x = A()

    reveal_type(x)  # revealed: A

    def inner() -> None:
        # lazy snapshot: {x: A}
        reveal_type(x)  # revealed: A
    inner()

def outer() -> None:
    x = None

    x = 1

    def inner() -> None:
        # lazy snapshot: {x: Literal[1]} -> {x: Literal[1, 2]}
        reveal_type(x)  # revealed: Literal[1, 2]
    inner()

    x = 2
```

Closes astral-sh/ty#559.

## Test Plan

Some TODOs in `public_types.md` now work properly.

---------

Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
Shunsuke Shibayama
2025-09-09 06:08:35 +09:00
committed by GitHub
parent aa5d665d52
commit 08a561fc05
7 changed files with 208 additions and 84 deletions

View File

@@ -248,6 +248,13 @@ def f(x: str | None):
reveal_type(x) # revealed: str | None
x = None
def f(x: str | None):
def _(x: str | None):
if x is not None:
def closure():
reveal_type(x) # revealed: str
x = None
def f(x: str | None):
class C:
def _():
@@ -303,13 +310,17 @@ no longer valid in the inner lazy scope.
def f(l: list[str | None]):
if l[0] is not None:
def _():
reveal_type(l[0]) # revealed: str | None
# TODO: should be `str | None`
reveal_type(l[0]) # revealed: str | None | @Todo(list literal element type)
# TODO: should be of type `list[None]`
l = [None]
def f(l: list[str | None]):
l[0] = "a"
def _():
reveal_type(l[0]) # revealed: str | None
# TODO: should be `str | None`
reveal_type(l[0]) # revealed: str | None | @Todo(list literal element type)
# TODO: should be of type `list[None]`
l = [None]
def f(l: list[str | None]):