mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 12:55:05 +00:00

## 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
2.5 KiB
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]]