ruff/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md
Alex Waygood 296d67a496
Special-case value-expression inference of special form subscriptions (#16877)
## Summary

Currently for something like `X = typing.Tuple[str, str]`, we infer the
value of `X` as `object`. That's because `Tuple` (like many of the
symbols in the typing module) is annotated as a `_SpecialForm` instance
in typeshed's stubs:


23382f5f8c/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi (L215)

and we don't understand implicit type aliases yet, and the stub for
`_SpecialForm.__getitem__` says it always returns `object`:


23382f5f8c/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi (L198-L200)

We have existing false positives in our test suite due to this:


23382f5f8c/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md (L76-L78)

and it's causing _many_ new false positives in #16872, which tries to
make our annotation-expression parsing stricter in some ways.

This PR therefore adds some small special casing for `KnownInstanceType`
variants that fallback to `_SpecialForm`, so that these false positives
can be avoided.

## Test Plan

Existing mdtest altered.

Cc. @MatthewMckee4
2025-03-20 21:46:02 +00:00

2.5 KiB

Annotated

Annotated attaches arbitrary metadata to a given type.

Usages

Annotated[T, ...] is equivalent to T: All metadata arguments are simply ignored.

from typing_extensions import Annotated

def _(x: Annotated[int, "foo"]):
    reveal_type(x)  # revealed: int

def _(x: Annotated[int, lambda: 0 + 1 * 2 // 3, _(4)]):
    reveal_type(x)  # revealed: int

def _(x: Annotated[int, "arbitrary", "metadata", "elements", "are", "fine"]):
    reveal_type(x)  # revealed: int

def _(x: Annotated[tuple[str, int], bytes]):
    reveal_type(x)  # revealed: tuple[str, int]

Parameterization

It is invalid to parameterize Annotated with less than two arguments.

from typing_extensions import Annotated

# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
def _(x: Annotated):
    reveal_type(x)  # revealed: Unknown

def _(flag: bool):
    if flag:
        X = Annotated
    else:
        X = bool

    # error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
    def f(y: X):
        reveal_type(y)  # revealed: Unknown | bool

# error: [invalid-type-form] "`typing.Annotated` requires at least two arguments when used in a type expression"
def _(x: Annotated | bool):
    reveal_type(x)  # revealed: Unknown | bool

# error: [invalid-type-form]
def _(x: Annotated[()]):
    reveal_type(x)  # revealed: Unknown

# error: [invalid-type-form]
def _(x: Annotated[int]):
    # `Annotated[T]` is invalid and will raise an error at runtime,
    # but we treat it the same as `T` to provide better diagnostics later on.
    # The subscription itself is still reported, regardless.
    # Same for the `(int,)` form below.
    reveal_type(x)  # revealed: int

# error: [invalid-type-form]
def _(x: Annotated[(int,)]):
    reveal_type(x)  # revealed: int

Inheritance

Correctly parameterized

Inheriting from Annotated[T, ...] is equivalent to inheriting from T itself.

from typing_extensions import Annotated

class C(Annotated[int, "foo"]): ...

# TODO: Should be `tuple[Literal[C], Literal[int], Literal[object]]`
reveal_type(C.__mro__)  # revealed: tuple[Literal[C], @Todo(Inference of subscript on special form), Literal[object]]

Not parameterized

from typing_extensions import Annotated

# At runtime, this is an error.
# error: [invalid-base]
class C(Annotated): ...

reveal_type(C.__mro__)  # revealed: tuple[Literal[C], Unknown, Literal[object]]