ruff/crates/ty_python_semantic/resources/mdtest/annotations/deferred.md
Charlie Marsh 5bb9ee2a9d
Some checks are pending
CI / cargo clippy (push) Blocked by required conditions
CI / pre-commit (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / test scripts (push) Blocked by required conditions
CI / benchmarks instrumented (ruff) (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (${{ github.repository == 'astral-sh/ruff' && 'depot-windows-2022-16' || 'windows-latest' }}) (push) Blocked by required conditions
CI / cargo test (macos-latest) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / ty completion evaluation (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks instrumented (ty) (push) Blocked by required conditions
CI / benchmarks walltime (medium|multithreaded) (push) Blocked by required conditions
CI / benchmarks walltime (small|large) (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run
[ty] Respect deferred values in keyword arguments et al for .pyi files (#22029)
## Summary

Closes https://github.com/astral-sh/ty/issues/2019.
2025-12-17 14:02:10 -08:00

5.6 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: ...

Default argument values

Not deferred in regular files

# error: [unresolved-reference]
def f(mode: int = ParseMode.test):
    pass

class ParseMode:
    test = 1

Deferred in stub files

Forward references in default argument values are allowed in stub files.

def f(mode: int = ParseMode.test): ...

class ParseMode:
    test: int

Undefined names are still errors in stub files

# error: [unresolved-reference]
def f(mode: int = NeverDefined.test): ...

Class keyword arguments

Not deferred in regular files

# error: [unresolved-reference]
class Foo(metaclass=SomeMeta):
    pass

class SomeMeta(type):
    pass

Deferred in stub files

Forward references in class keyword arguments are allowed in stub files.

class Foo(metaclass=SomeMeta): ...

class SomeMeta(type): ...

Undefined names are still errors in stub files

# error: [unresolved-reference]
class Foo(metaclass=NeverDefined): ...

Lambda default argument values

Not deferred in regular files

# error: [unresolved-reference]
f = lambda x=Foo(): x

class Foo:
    pass

Deferred in stub files

Forward references in lambda default argument values are allowed in stub files.

f = lambda x=Foo(): x

class Foo: ...

Undefined names are still errors in stub files

# error: [unresolved-reference]
f = lambda x=NeverDefined(): x