From 385dd2770b2d95c0c67739a1468c954ac8b0ba65 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 8 Dec 2025 10:24:05 -0500 Subject: [PATCH] [ty] Avoid double-inference on non-tuple argument to `Annotated` (#21837) ## Summary If you pass a non-tuple to `Annotated`, we end up running inference on it twice. I _think_ the only case here is `Annotated[]`, where we insert a (fake) empty `Name` node in the slice. Closes https://github.com/astral-sh/ty/issues/1801. --- .../resources/mdtest/invalid_syntax.md | 13 +++++++++++++ .../src/types/infer/builder/type_expression.rs | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/crates/ty_python_semantic/resources/mdtest/invalid_syntax.md b/crates/ty_python_semantic/resources/mdtest/invalid_syntax.md index 9594492982..cac8006cd6 100644 --- a/crates/ty_python_semantic/resources/mdtest/invalid_syntax.md +++ b/crates/ty_python_semantic/resources/mdtest/invalid_syntax.md @@ -128,3 +128,16 @@ InvalidEmptyUnion = Union[] def _(u: InvalidEmptyUnion): reveal_type(u) # revealed: Unknown ``` + +### `typing.Annotated` + +```py +from typing import Annotated + +# error: [invalid-syntax] "Expected index or slice expression" +# error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)" +InvalidEmptyAnnotated = Annotated[] + +def _(a: InvalidEmptyAnnotated): + reveal_type(a) # revealed: Unknown +``` diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index 114cedb734..fbae2c8948 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -1172,7 +1172,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ) .in_type_expression(db, self.scope(), None) .unwrap_or_else(|err| err.into_fallback_type(&self.context, subscript, true)); - self.store_expression_type(arguments_slice, ty); + // Only store on the tuple slice; non-tuple cases are handled by + // `infer_subscript_load_impl` via `infer_expression`. + if arguments_slice.is_tuple_expr() { + self.store_expression_type(arguments_slice, ty); + } ty } SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) {