ruff/crates/ty_python_semantic/resources/mdtest/annotations/deferred.md
Shunsuke Shibayama 9dd666d677
[ty] fix global symbol lookup from eager scopes (#21317)
## Summary

cf. https://github.com/astral-sh/ruff/pull/20962

In the following code, `foo` in the comprehension was not reported as
unresolved:

```python
# error: [unresolved-reference] "Name `foo` used when not defined"
foo
foo = [
    # no error!
    # revealed: Divergent
    reveal_type(x) for _ in () for x in [foo]
]

baz = [
    # error: [unresolved-reference] "Name `baz` used when not defined"
    # revealed: Unknown
    reveal_type(x) for _ in () for x in [baz]
]
```

In fact, this is a more serious bug than it looks: for `foo`,
[`explicit_global_symbol` is
called](6cc3393ccd/crates/ty_python_semantic/src/types/infer/builder.rs (L8052)),
causing a symbol that should actually be `Undefined` to be reported as
being of type `Divergent`.

This PR fixes this bug. As a result, the code in
`mdtest/regression/pr_20962_comprehension_panics.md` no longer panics.

## Test Plan

`corpus\cyclic_symbol_in_comprehension.py` is added.
New tests are added in `mdtest/comprehensions/basic.md`.

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
Co-authored-by: Carl Meyer <carl@astral.sh>
2025-11-12 10:15:51 -08:00

4.2 KiB

Deferred annotations

Deferred annotations in stubs always resolve

mod.pyi:

def get_foo() -> Foo: ...
class Foo: ...
from mod import get_foo

reveal_type(get_foo())  # revealed: Foo

Deferred annotations in regular code fail

In (regular) source files, annotations are not deferred. This also tests that imports from __future__ that are not annotations are ignored.

from __future__ import with_statement as annotations

# error: [unresolved-reference]
def get_foo() -> Foo: ...

class Foo: ...

reveal_type(get_foo())  # revealed: Unknown

Deferred annotations in regular code with __future__.annotations

If __future__.annotations is imported, annotations are deferred.

from __future__ import annotations

def get_foo() -> Foo:
    return Foo()

class Foo: ...

reveal_type(get_foo())  # revealed: Foo

Deferred self-reference annotations in a class definition

[environment]
python-version = "3.12"
from __future__ import annotations

class Foo:
    this: Foo
    # error: [unresolved-reference]
    _ = Foo()
    # error: [unresolved-reference]
    [Foo for _ in range(1)]
    a = int

    def f(self, x: Foo):
        reveal_type(x)  # revealed: Foo

    def g(self) -> Foo:
        _: Foo = self
        return self

    class Bar:
        foo: Foo
        b = int

        def f(self, x: Foo):
            return self
        # error: [unresolved-reference]
        def g(self) -> Bar:
            return self
        # error: [unresolved-reference]
        def h[T: Bar](self):
            pass

        class Baz[T: Foo]:
            pass

        # error: [unresolved-reference] "Name `Foo` used when not defined"
        # error: [unresolved-reference] "Name `Bar` used when not defined"
        class Qux(Foo, Bar, Baz):
            pass

        # error: [unresolved-reference] "Name `Foo` used when not defined"
        # error: [unresolved-reference] "Name `Bar` used when not defined"
        class Quux[_T](Foo, Bar, Baz):
            pass

        # error: [unresolved-reference]
        type S = a
        type T = b
        type U = Foo
        # error: [unresolved-reference]
        type V = Bar
        type W = Baz

    def h[T: Bar]():
        # error: [unresolved-reference]
        return Bar()
    type Baz = Foo

Non-deferred self-reference annotations in a class definition

[environment]
python-version = "3.12"
class Foo:
    # error: [unresolved-reference]
    this: Foo
    ok: "Foo"
    # error: [unresolved-reference]
    _ = Foo()
    # error: [unresolved-reference]
    [Foo for _ in range(1)]
    a = int

    # error: [unresolved-reference]
    def f(self, x: Foo):
        reveal_type(x)  # revealed: Unknown
    # error: [unresolved-reference]
    def g(self) -> Foo:
        _: Foo = self
        return self

    class Bar:
        # error: [unresolved-reference]
        foo: Foo
        b = int

        # error: [unresolved-reference]
        def f(self, x: Foo):
            return self
        # error: [unresolved-reference]
        def g(self) -> Bar:
            return self
        # error: [unresolved-reference]
        def h[T: Bar](self):
            pass

        class Baz[T: Foo]:
            pass

        # error: [unresolved-reference] "Name `Foo` used when not defined"
        # error: [unresolved-reference] "Name `Bar` used when not defined"
        class Qux(Foo, Bar, Baz):
            pass

        # error: [unresolved-reference] "Name `Foo` used when not defined"
        # error: [unresolved-reference] "Name `Bar` used when not defined"
        class Quux[_T](Foo, Bar, Baz):
            pass

        # error: [unresolved-reference]
        type S = a
        type T = b
        type U = Foo
        # error: [unresolved-reference]
        type V = Bar
        type W = Baz

    def h[T: Bar]():
        # error: [unresolved-reference]
        return Bar()
    type Qux = Foo

def _():
    class C:
        # error: [unresolved-reference]
        def f(self) -> C:
            return self

Base class references

Not deferred by future.annotations

from __future__ import annotations

class A(B):  # error: [unresolved-reference]
    pass

class B:
    pass

Deferred in stub files

class A(B): ...
class B: ...