[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.
This commit is contained in:
Charlie Marsh 2025-12-08 10:24:05 -05:00 committed by GitHub
parent 7519f6c27b
commit 385dd2770b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 18 additions and 1 deletions

View File

@ -128,3 +128,16 @@ InvalidEmptyUnion = Union[]
def _(u: InvalidEmptyUnion): def _(u: InvalidEmptyUnion):
reveal_type(u) # revealed: Unknown 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
```

View File

@ -1172,7 +1172,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
) )
.in_type_expression(db, self.scope(), None) .in_type_expression(db, self.scope(), None)
.unwrap_or_else(|err| err.into_fallback_type(&self.context, subscript, true)); .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 ty
} }
SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) { SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) {