From 296d67a496f4261fe5e0734b81e0f8b91a1de312 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 20 Mar 2025 21:46:02 +0000 Subject: [PATCH] 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: https://github.com/astral-sh/ruff/blob/23382f5f8c7b4e356368cdeb1049b8c1910baff3/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`: https://github.com/astral-sh/ruff/blob/23382f5f8c7b4e356368cdeb1049b8c1910baff3/crates/red_knot_vendored/vendor/typeshed/stdlib/typing.pyi#L198-L200 We have existing false positives in our test suite due to this: https://github.com/astral-sh/ruff/blob/23382f5f8c7b4e356368cdeb1049b8c1910baff3/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md?plain=1#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 --- .../resources/mdtest/annotations/annotated.md | 4 +--- crates/red_knot_python_semantic/src/types/class.rs | 4 ++++ crates/red_knot_python_semantic/src/types/infer.rs | 5 +++++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md index a9f7b91618..3fb2cc3138 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md @@ -73,12 +73,10 @@ Inheriting from `Annotated[T, ...]` is equivalent to inheriting from `T` itself. ```py from typing_extensions import Annotated -# TODO: False positive -# error: [invalid-base] class C(Annotated[int, "foo"]): ... # TODO: Should be `tuple[Literal[C], Literal[int], Literal[object]]` -reveal_type(C.__mro__) # revealed: tuple[Literal[C], Unknown, Literal[object]] +reveal_type(C.__mro__) # revealed: tuple[Literal[C], @Todo(Inference of subscript on special form), Literal[object]] ``` ### Not parameterized diff --git a/crates/red_knot_python_semantic/src/types/class.rs b/crates/red_knot_python_semantic/src/types/class.rs index 0de0a2b38c..5ae487e725 100644 --- a/crates/red_knot_python_semantic/src/types/class.rs +++ b/crates/red_knot_python_semantic/src/types/class.rs @@ -851,6 +851,10 @@ impl<'db> KnownClass { matches!(self, Self::Bool) } + pub(crate) const fn is_special_form(self) -> bool { + matches!(self, Self::SpecialForm) + } + /// Determine whether instances of this class are always truthy, always falsy, /// or have an ambiguous truthiness. pub(crate) const fn bool(self) -> Truthiness { diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index ba07aefb2c..4b8936e7d5 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -5631,6 +5631,11 @@ impl<'db> TypeInferenceBuilder<'db> { (Type::KnownInstance(KnownInstanceType::Protocol), _) => { Type::Dynamic(DynamicType::TodoProtocol) } + (Type::KnownInstance(known_instance), _) + if known_instance.class().is_special_form() => + { + todo_type!("Inference of subscript on special form") + } (value_ty, slice_ty) => { // If the class defines `__getitem__`, return its return type. //