mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-19 03:48:29 +00:00
[ty] Support stringified annotations in value-position Annotated instances (#21447)
## Summary Infer the first argument `type` inside `Annotated[type, …]` as a type expression. This allows us to support stringified annotations inside `Annotated`. ## Ecosystem * The removed diagnostic on `prefect` shows that we now understand the `State.data` type annotation in `src/prefect/client/schemas/objects.py:230`, which uses a stringified annotation in `Annoated`. The other diagnostics are downstream changes that result from this, it seems to be a commonly used data type. * `artigraph` does something like `Annotated[cast(Any, field_info.annotation), *field_info.metadata]` which I'm not sure we need to allow? It's unfortunate since this is probably supported at runtime, but it seems reasonable that they need to add a `# type: ignore` for that. * `pydantic` uses something like `Annotated[(self.annotation, *self.metadata)]` but adds a `# type: ignore` ## Test Plan New Markdown test
This commit is contained in:
parent
05cf53aae8
commit
e9a5337136
5 changed files with 28 additions and 9 deletions
|
|
@ -76,7 +76,18 @@ from ty_extensions import reveal_mro
|
|||
|
||||
class C(Annotated[int, "foo"]): ...
|
||||
|
||||
reveal_mro(C) # revealed: (<class 'C'>, <class 'int'>, <class 'object'>)
|
||||
# revealed: (<class 'C'>, <class 'int'>, <class 'object'>)
|
||||
reveal_mro(C)
|
||||
|
||||
class D(Annotated[list[str], "foo"]): ...
|
||||
|
||||
# revealed: (<class 'D'>, <class 'list[str]'>, <class 'MutableSequence[str]'>, <class 'Sequence[str]'>, <class 'Reversible[str]'>, <class 'Collection[str]'>, <class 'Iterable[str]'>, <class 'Container[str]'>, typing.Protocol, typing.Generic, <class 'object'>)
|
||||
reveal_mro(D)
|
||||
|
||||
class E(Annotated[list["E"], "metadata"]): ...
|
||||
|
||||
# error: [revealed-type] "Revealed MRO: (<class 'E'>, <class 'list[E]'>, <class 'MutableSequence[E]'>, <class 'Sequence[E]'>, <class 'Reversible[E]'>, <class 'Collection[E]'>, <class 'Iterable[E]'>, <class 'Container[E]'>, typing.Protocol, typing.Generic, <class 'object'>)"
|
||||
reveal_mro(E)
|
||||
```
|
||||
|
||||
### Not parameterized
|
||||
|
|
|
|||
|
|
@ -974,13 +974,14 @@ We *do* support stringified annotations if they appear in a position where a typ
|
|||
syntactically expected:
|
||||
|
||||
```py
|
||||
from typing import Union, List, Dict
|
||||
from typing import Union, List, Dict, Annotated
|
||||
|
||||
ListOfInts1 = list["int"]
|
||||
ListOfInts2 = List["int"]
|
||||
StrOrStyle = Union[str, "Style"]
|
||||
SubclassOfStyle = type["Style"]
|
||||
DictStrToStyle = Dict[str, "Style"]
|
||||
AnnotatedStyle = Annotated["Style", "metadata"]
|
||||
|
||||
class Style: ...
|
||||
|
||||
|
|
@ -990,12 +991,14 @@ def _(
|
|||
str_or_style: StrOrStyle,
|
||||
subclass_of_style: SubclassOfStyle,
|
||||
dict_str_to_style: DictStrToStyle,
|
||||
annotated_style: AnnotatedStyle,
|
||||
):
|
||||
reveal_type(list_of_ints1) # revealed: list[int]
|
||||
reveal_type(list_of_ints2) # revealed: list[int]
|
||||
reveal_type(str_or_style) # revealed: str | Style
|
||||
reveal_type(subclass_of_style) # revealed: type[Style]
|
||||
reveal_type(dict_str_to_style) # revealed: dict[str, Style]
|
||||
reveal_type(annotated_style) # revealed: Style
|
||||
```
|
||||
|
||||
## Recursive
|
||||
|
|
|
|||
|
|
@ -6599,11 +6599,7 @@ impl<'db> Type<'db> {
|
|||
Ok(builder.build())
|
||||
}
|
||||
KnownInstanceType::Literal(ty) => Ok(ty.inner(db)),
|
||||
KnownInstanceType::Annotated(ty) => {
|
||||
Ok(ty
|
||||
.inner(db)
|
||||
.in_type_expression(db, scope_id, typevar_binding_context)?)
|
||||
}
|
||||
KnownInstanceType::Annotated(ty) => Ok(ty.inner(db)),
|
||||
KnownInstanceType::TypeGenericAlias(ty) => {
|
||||
// When `type[…]` appears in a value position (e.g. in an implicit type alias),
|
||||
// we infer its argument as a type expression. This ensures that we can emit
|
||||
|
|
|
|||
|
|
@ -183,7 +183,16 @@ impl<'db> ClassBase<'db> {
|
|||
KnownInstanceType::TypeGenericAlias(_) => {
|
||||
Self::try_from_type(db, KnownClass::Type.to_class_literal(db), subclass)
|
||||
}
|
||||
KnownInstanceType::Annotated(ty) => Self::try_from_type(db, ty.inner(db), subclass),
|
||||
KnownInstanceType::Annotated(ty) => {
|
||||
// Unions are not supported in this position, so we only need to support
|
||||
// something like `class C(Annotated[Base, "metadata"]): ...`, which we
|
||||
// can do by turning the instance type (`Base` in this example) back into
|
||||
// a class.
|
||||
let annotated_ty = ty.inner(db);
|
||||
let instance_ty = annotated_ty.as_nominal_instance()?;
|
||||
|
||||
Some(Self::Class(instance_ty.class(db)))
|
||||
}
|
||||
},
|
||||
|
||||
Type::SpecialForm(special_form) => match special_form {
|
||||
|
|
|
|||
|
|
@ -10726,7 +10726,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
self.infer_expression(element, TypeContext::default());
|
||||
}
|
||||
|
||||
let ty = self.infer_expression(type_expr, TypeContext::default());
|
||||
let ty = self.infer_type_expression(type_expr);
|
||||
|
||||
return Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new(
|
||||
self.db(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue