[ty] Add support for Optional and Annotated in implicit type aliases (#21321)

## Summary

Add support for `Optional` and `Annotated` in implicit type aliases

part of https://github.com/astral-sh/ty/issues/221

## Typing conformance changes

New expected diagnostics.

## Ecosystem

A lot of true positives, some known limitations unrelated to this PR.

## Test Plan

New Markdown tests
This commit is contained in:
David Peter 2025-11-10 10:24:38 +01:00 committed by GitHub
parent 3fa609929f
commit 238f151371
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 343 additions and 125 deletions

View file

@ -76,8 +76,7 @@ from ty_extensions import reveal_mro
class C(Annotated[int, "foo"]): ...
# TODO: Should be `(<class 'C'>, <class 'int'>, <class 'object'>)`
reveal_mro(C) # revealed: (<class 'C'>, @Todo(Inference of subscript on special form), <class 'object'>)
reveal_mro(C) # revealed: (<class 'C'>, <class 'int'>, <class 'object'>)
```
### Not parameterized

View file

@ -33,7 +33,7 @@ g(None)
We also support unions in type aliases:
```py
from typing_extensions import Any, Never, Literal
from typing_extensions import Any, Never, Literal, LiteralString, Tuple, Annotated, Optional
from ty_extensions import Unknown
IntOrStr = int | str
@ -56,6 +56,14 @@ UnknownOrInt = Unknown | int
IntOrUnknown = int | Unknown
StrOrZero = str | Literal[0]
ZeroOrStr = Literal[0] | str
LiteralStringOrInt = LiteralString | int
IntOrLiteralString = int | LiteralString
NoneOrTuple = None | Tuple[int, str]
TupleOrNone = Tuple[int, str] | None
IntOrAnnotated = int | Annotated[str, "meta"]
AnnotatedOrInt = Annotated[str, "meta"] | int
IntOrOptional = int | Optional[str]
OptionalOrInt = Optional[str] | int
reveal_type(IntOrStr) # revealed: types.UnionType
reveal_type(IntOrStrOrBytes1) # revealed: types.UnionType
@ -77,6 +85,14 @@ reveal_type(UnknownOrInt) # revealed: types.UnionType
reveal_type(IntOrUnknown) # revealed: types.UnionType
reveal_type(StrOrZero) # revealed: types.UnionType
reveal_type(ZeroOrStr) # revealed: types.UnionType
reveal_type(IntOrLiteralString) # revealed: types.UnionType
reveal_type(LiteralStringOrInt) # revealed: types.UnionType
reveal_type(NoneOrTuple) # revealed: types.UnionType
reveal_type(TupleOrNone) # revealed: types.UnionType
reveal_type(IntOrAnnotated) # revealed: types.UnionType
reveal_type(AnnotatedOrInt) # revealed: types.UnionType
reveal_type(IntOrOptional) # revealed: types.UnionType
reveal_type(OptionalOrInt) # revealed: types.UnionType
def _(
int_or_str: IntOrStr,
@ -99,6 +115,14 @@ def _(
int_or_unknown: IntOrUnknown,
str_or_zero: StrOrZero,
zero_or_str: ZeroOrStr,
literal_string_or_int: LiteralStringOrInt,
int_or_literal_string: IntOrLiteralString,
none_or_tuple: NoneOrTuple,
tuple_or_none: TupleOrNone,
int_or_annotated: IntOrAnnotated,
annotated_or_int: AnnotatedOrInt,
int_or_optional: IntOrOptional,
optional_or_int: OptionalOrInt,
):
reveal_type(int_or_str) # revealed: int | str
reveal_type(int_or_str_or_bytes1) # revealed: int | str | bytes
@ -120,6 +144,14 @@ def _(
reveal_type(int_or_unknown) # revealed: int | Unknown
reveal_type(str_or_zero) # revealed: str | Literal[0]
reveal_type(zero_or_str) # revealed: Literal[0] | str
reveal_type(literal_string_or_int) # revealed: LiteralString | int
reveal_type(int_or_literal_string) # revealed: int | LiteralString
reveal_type(none_or_tuple) # revealed: None | tuple[int, str]
reveal_type(tuple_or_none) # revealed: tuple[int, str] | None
reveal_type(int_or_annotated) # revealed: int | str
reveal_type(annotated_or_int) # revealed: str | int
reveal_type(int_or_optional) # revealed: int | str | None
reveal_type(optional_or_int) # revealed: str | None | int
```
If a type is unioned with itself in a value expression, the result is just that type. No
@ -325,6 +357,115 @@ def _(weird: IntLiteral1[int]):
reveal_type(weird) # revealed: Unknown
```
## `Annotated`
Basic usage:
```py
from typing import Annotated
MyAnnotatedInt = Annotated[int, "some metadata", 1, 2, 3]
def _(annotated_int: MyAnnotatedInt):
reveal_type(annotated_int) # revealed: int
```
Usage with generics:
```py
from typing import TypeVar
T = TypeVar("T")
Deprecated = Annotated[T, "deprecated attribute"]
class C:
old: Deprecated[int]
# TODO: Should be `int`
reveal_type(C().old) # revealed: @Todo(Generic specialization of typing.Annotated)
```
If the metadata argument is missing, we emit an error (because this code fails at runtime), but
still use the first element as the type, when used in annotations:
```py
# error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)"
WronglyAnnotatedInt = Annotated[int]
def _(wrongly_annotated_int: WronglyAnnotatedInt):
reveal_type(wrongly_annotated_int) # revealed: int
```
## `Optional`
Starting with Python 3.14, `Optional[int]` creates an instance of `typing.Union`, which is an alias
for `types.UnionType`. We only support this new behavior and do not attempt to model the details of
the pre-3.14 behavior:
```py
from typing import Optional
MyOptionalInt = Optional[int]
reveal_type(MyOptionalInt) # revealed: types.UnionType
def _(optional_int: MyOptionalInt):
reveal_type(optional_int) # revealed: int | None
```
A special case is `Optional[None]`, which is equivalent to `None`:
```py
JustNone = Optional[None]
reveal_type(JustNone) # revealed: None
def _(just_none: JustNone):
reveal_type(just_none) # revealed: None
```
Invalid uses:
```py
# error: [invalid-type-form] "`typing.Optional` requires exactly one argument"
Optional[int, str]
```
## `LiteralString`, `NoReturn`, `Never`
```py
from typing_extensions import LiteralString, NoReturn, Never
MyLiteralString = LiteralString
MyNoReturn = NoReturn
MyNever = Never
reveal_type(MyLiteralString) # revealed: typing.LiteralString
reveal_type(MyNoReturn) # revealed: typing.NoReturn
reveal_type(MyNever) # revealed: typing.Never
def _(
ls: MyLiteralString,
nr: MyNoReturn,
nv: MyNever,
):
reveal_type(ls) # revealed: LiteralString
reveal_type(nr) # revealed: Never
reveal_type(nv) # revealed: Never
```
## `Tuple`
```py
from typing import Tuple
IntAndStr = Tuple[int, str]
def _(int_and_str: IntAndStr):
reveal_type(int_and_str) # revealed: tuple[int, str]
```
## Stringified annotations?
From the [typing spec on type aliases](https://typing.python.org/en/latest/spec/aliases.html):

View file

@ -63,7 +63,7 @@ error[invalid-argument-type]: Invalid second argument to `isinstance`
10 | # error: [invalid-argument-type]
|
info: A `UnionType` instance can only be used as the second argument to `isinstance` if all elements are class objects
info: Elements `typing.Literal` and `<class 'list[int]'>` in the union are not class objects
info: Elements `<typing.Literal special form>` and `<class 'list[int]'>` in the union are not class objects
info: rule `invalid-argument-type` is enabled by default
```

View file

@ -6462,7 +6462,12 @@ impl<'db> Type<'db> {
}
Ok(builder.build())
}
KnownInstanceType::Literal(list) => Ok(list.to_union(db)),
KnownInstanceType::Literal(ty) => Ok(ty.inner(db)),
KnownInstanceType::Annotated(ty) => {
Ok(ty
.inner(db)
.in_type_expression(db, scope_id, typevar_binding_context)?)
}
},
Type::SpecialForm(special_form) => match special_form {
@ -7676,10 +7681,13 @@ pub enum KnownInstanceType<'db> {
/// A single instance of `types.UnionType`, which stores the left- and
/// right-hand sides of a PEP 604 union.
UnionType(TypeList<'db>),
UnionType(InternedTypes<'db>),
/// A single instance of `typing.Literal`
Literal(TypeList<'db>),
Literal(InternedType<'db>),
/// A single instance of `typing.Annotated`
Annotated(InternedType<'db>),
}
fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
@ -7706,11 +7714,14 @@ fn walk_known_instance_type<'db, V: visitor::TypeVisitor<'db> + ?Sized>(
visitor.visit_type(db, default_ty);
}
}
KnownInstanceType::UnionType(list) | KnownInstanceType::Literal(list) => {
KnownInstanceType::UnionType(list) => {
for element in list.elements(db) {
visitor.visit_type(db, *element);
}
}
KnownInstanceType::Literal(ty) | KnownInstanceType::Annotated(ty) => {
visitor.visit_type(db, ty.inner(db));
}
}
}
@ -7748,7 +7759,8 @@ impl<'db> KnownInstanceType<'db> {
Self::ConstraintSet(set)
}
Self::UnionType(list) => Self::UnionType(list.normalized_impl(db, visitor)),
Self::Literal(list) => Self::Literal(list.normalized_impl(db, visitor)),
Self::Literal(ty) => Self::Literal(ty.normalized_impl(db, visitor)),
Self::Annotated(ty) => Self::Annotated(ty.normalized_impl(db, visitor)),
}
}
@ -7768,6 +7780,7 @@ impl<'db> KnownInstanceType<'db> {
Self::ConstraintSet(_) => KnownClass::ConstraintSet,
Self::UnionType(_) => KnownClass::UnionType,
Self::Literal(_) => KnownClass::GenericAlias,
Self::Annotated(_) => KnownClass::GenericAlias,
}
}
@ -7848,7 +7861,10 @@ impl<'db> KnownInstanceType<'db> {
)
}
KnownInstanceType::UnionType(_) => f.write_str("types.UnionType"),
KnownInstanceType::Literal(_) => f.write_str("typing.Literal"),
KnownInstanceType::Literal(_) => f.write_str("<typing.Literal special form>"),
KnownInstanceType::Annotated(_) => {
f.write_str("<typing.Annotated special form>")
}
}
}
}
@ -8045,7 +8061,7 @@ impl<'db> InvalidTypeExpressionError<'db> {
fn into_fallback_type(
self,
context: &InferContext,
node: &ast::Expr,
node: &impl Ranged,
is_reachable: bool,
) -> Type<'db> {
let InvalidTypeExpressionError {
@ -8984,29 +9000,25 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
/// # Ordering
/// Ordering is based on the context's salsa-assigned id and not on its values.
/// The id may change between runs, or when the context was garbage collected and recreated.
#[salsa::interned(debug)]
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct TypeList<'db> {
pub struct InternedTypes<'db> {
#[returns(deref)]
elements: Box<[Type<'db>]>,
}
impl get_size2::GetSize for TypeList<'_> {}
impl get_size2::GetSize for InternedTypes<'_> {}
impl<'db> TypeList<'db> {
impl<'db> InternedTypes<'db> {
pub(crate) fn from_elements(
db: &'db dyn Db,
elements: impl IntoIterator<Item = Type<'db>>,
) -> TypeList<'db> {
TypeList::new(db, elements.into_iter().collect::<Box<[_]>>())
}
pub(crate) fn singleton(db: &'db dyn Db, element: Type<'db>) -> TypeList<'db> {
TypeList::from_elements(db, [element])
) -> InternedTypes<'db> {
InternedTypes::new(db, elements.into_iter().collect::<Box<[_]>>())
}
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
TypeList::new(
InternedTypes::new(
db,
self.elements(db)
.iter()
@ -9014,10 +9026,24 @@ impl<'db> TypeList<'db> {
.collect::<Box<[_]>>(),
)
}
}
/// Turn this list of types `[T1, T2, ...]` into a union type `T1 | T2 | ...`.
pub(crate) fn to_union(self, db: &'db dyn Db) -> Type<'db> {
UnionType::from_elements(db, self.elements(db))
/// A salsa-interned `Type`
///
/// # Ordering
/// Ordering is based on the context's salsa-assigned id and not on its values.
/// The id may change between runs, or when the context was garbage collected and recreated.
#[salsa::interned(debug, heap_size=ruff_memory_usage::heap_size)]
#[derive(PartialOrd, Ord)]
pub struct InternedType<'db> {
inner: Type<'db>,
}
impl get_size2::GetSize for InternedType<'_> {}
impl<'db> InternedType<'db> {
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
InternedType::new(db, self.inner(db).normalized_impl(db, visitor))
}
}

View file

@ -170,6 +170,7 @@ impl<'db> ClassBase<'db> {
| KnownInstanceType::ConstraintSet(_)
| KnownInstanceType::UnionType(_)
| KnownInstanceType::Literal(_) => None,
KnownInstanceType::Annotated(ty) => Self::try_from_type(db, ty.inner(db), subclass),
},
Type::SpecialForm(special_form) => match special_form {

View file

@ -69,12 +69,12 @@ use crate::types::diagnostic::{
hint_if_stdlib_submodule_exists_on_other_versions, report_attempted_protocol_instantiation,
report_bad_dunder_set_call, report_cannot_pop_required_field_on_typed_dict,
report_duplicate_bases, report_implicit_return_type, report_index_out_of_bounds,
report_instance_layout_conflict, report_invalid_assignment,
report_invalid_attribute_assignment, report_invalid_exception_caught,
report_invalid_exception_cause, report_invalid_exception_raised,
report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict,
report_invalid_or_unsupported_base, report_invalid_return_type,
report_invalid_type_checking_constant,
report_instance_layout_conflict, report_invalid_arguments_to_annotated,
report_invalid_assignment, report_invalid_attribute_assignment,
report_invalid_exception_caught, report_invalid_exception_cause,
report_invalid_exception_raised, report_invalid_generator_function_return_type,
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
report_invalid_return_type, report_invalid_type_checking_constant,
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
report_possibly_missing_attribute, report_possibly_unresolved_reference,
report_rebound_typevar, report_slice_step_size_zero,
@ -100,10 +100,10 @@ use crate::types::typed_dict::{
use crate::types::visitor::any_over_type;
use crate::types::{
CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams,
DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType,
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
TypeAliasType, TypeAndQualifiers, TypeContext, TypeList, TypeQualifiers,
DynamicType, InternedType, InternedTypes, IntersectionBuilder, IntersectionType, KnownClass,
KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
ParameterForm, Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness,
Type, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType,
binding_type, todo_type,
@ -8755,14 +8755,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
| Type::GenericAlias(..)
| Type::SpecialForm(_)
| Type::KnownInstance(
KnownInstanceType::UnionType(_) | KnownInstanceType::Literal(_),
KnownInstanceType::UnionType(_)
| KnownInstanceType::Literal(_)
| KnownInstanceType::Annotated(_),
),
Type::ClassLiteral(..)
| Type::SubclassOf(..)
| Type::GenericAlias(..)
| Type::SpecialForm(_)
| Type::KnownInstance(
KnownInstanceType::UnionType(_) | KnownInstanceType::Literal(_),
KnownInstanceType::UnionType(_)
| KnownInstanceType::Literal(_)
| KnownInstanceType::Annotated(_),
),
ast::Operator::BitOr,
) if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310 => {
@ -8770,7 +8774,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Some(left_ty)
} else {
Some(Type::KnownInstance(KnownInstanceType::UnionType(
TypeList::from_elements(self.db(), [left_ty, right_ty]),
InternedTypes::from_elements(self.db(), [left_ty, right_ty]),
)))
}
}
@ -8795,7 +8799,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
&& instance.has_known_class(self.db(), KnownClass::NoneType) =>
{
Some(Type::KnownInstance(KnownInstanceType::UnionType(
TypeList::from_elements(self.db(), [left_ty, right_ty]),
InternedTypes::from_elements(self.db(), [left_ty, right_ty]),
)))
}
@ -9863,14 +9867,23 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
fn infer_subscript_load(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
let value_ty = self.infer_expression(&subscript.value, TypeContext::default());
self.infer_subscript_load_impl(value_ty, subscript)
}
fn infer_subscript_load_impl(
&mut self,
value_ty: Type<'db>,
subscript: &ast::ExprSubscript,
) -> Type<'db> {
let ast::ExprSubscript {
range: _,
node_index: _,
value,
value: _,
slice,
ctx,
} = subscript;
let value_ty = self.infer_expression(value, TypeContext::default());
let mut constraint_keys = vec![];
// If `value` is a valid reference, we attempt type narrowing by assignment.
@ -9896,60 +9909,117 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
Type::from(tuple.to_class_type(db))
};
// HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
// subscript inference logic and treat this as an explicit specialization.
// TODO: Move this logic into a custom callable, and update `find_name_in_mro` to return
// this callable as the `__class_getitem__` method on `type`. That probably requires
// updating all of the subscript logic below to use custom callables for all of the _other_
// special cases, too.
if let Type::ClassLiteral(class) = value_ty {
if class.is_tuple(self.db()) {
match value_ty {
Type::ClassLiteral(class) => {
// HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
// subscript inference logic and treat this as an explicit specialization.
// TODO: Move this logic into a custom callable, and update `find_name_in_mro` to return
// this callable as the `__class_getitem__` method on `type`. That probably requires
// updating all of the subscript logic below to use custom callables for all of the _other_
// special cases, too.
if class.is_tuple(self.db()) {
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
}
if let Some(generic_context) = class.generic_context(self.db()) {
return self.infer_explicit_class_specialization(
subscript,
value_ty,
class,
generic_context,
);
}
}
Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias)) => {
if let Some(generic_context) = type_alias.generic_context(self.db()) {
return self.infer_explicit_type_alias_specialization(
subscript,
value_ty,
type_alias,
generic_context,
);
}
}
Type::SpecialForm(SpecialFormType::Tuple) => {
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
}
if let Some(generic_context) = class.generic_context(self.db()) {
return self.infer_explicit_class_specialization(
subscript,
value_ty,
class,
generic_context,
);
}
}
if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty {
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
}
if let Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias)) = value_ty {
if let Some(generic_context) = type_alias.generic_context(self.db()) {
return self.infer_explicit_type_alias_specialization(
subscript,
value_ty,
type_alias,
generic_context,
);
}
}
if value_ty == Type::SpecialForm(SpecialFormType::Literal) {
match self.infer_literal_parameter_type(slice) {
Ok(result) => {
return Type::KnownInstance(KnownInstanceType::Literal(TypeList::singleton(
self.db(),
result,
)));
}
Err(nodes) => {
for node in nodes {
let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node)
else {
continue;
};
builder.into_diagnostic(
"Type arguments for `Literal` must be `None`, \
a literal value (int, bool, str, or bytes), or an enum member",
);
Type::SpecialForm(SpecialFormType::Literal) => {
match self.infer_literal_parameter_type(slice) {
Ok(result) => {
return Type::KnownInstance(KnownInstanceType::Literal(InternedType::new(
self.db(),
result,
)));
}
Err(nodes) => {
for node in nodes {
let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node)
else {
continue;
};
builder.into_diagnostic(
"Type arguments for `Literal` must be `None`, \
a literal value (int, bool, str, or bytes), or an enum member",
);
}
return Type::unknown();
}
return Type::unknown();
}
}
Type::SpecialForm(SpecialFormType::Annotated) => {
let ast::Expr::Tuple(ast::ExprTuple {
elts: ref arguments,
..
}) = **slice
else {
report_invalid_arguments_to_annotated(&self.context, subscript);
return self.infer_expression(slice, TypeContext::default());
};
if arguments.len() < 2 {
report_invalid_arguments_to_annotated(&self.context, subscript);
}
let [type_expr, metadata @ ..] = &arguments[..] else {
for argument in arguments {
self.infer_expression(argument, TypeContext::default());
}
self.store_expression_type(slice, Type::unknown());
return Type::unknown();
};
for element in metadata {
self.infer_expression(element, TypeContext::default());
}
let ty = self.infer_expression(type_expr, TypeContext::default());
return Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new(
self.db(),
ty,
)));
}
Type::SpecialForm(SpecialFormType::Optional) => {
if matches!(**slice, ast::Expr::Tuple(_)) {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
"`typing.Optional` requires exactly one argument"
));
}
}
let ty = self.infer_expression(slice, TypeContext::default());
// `Optional[None]` is equivalent to `None`:
if ty.is_none(self.db()) {
return ty;
}
return Type::KnownInstance(KnownInstanceType::UnionType(
InternedTypes::from_elements(self.db(), [ty, Type::none(self.db())]),
));
}
_ => {}
}
let slice_ty = self.infer_expression(slice, TypeContext::default());

View file

@ -4,7 +4,7 @@ use ruff_python_ast as ast;
use super::{DeferredExpressionState, TypeInferenceBuilder};
use crate::types::diagnostic::{
self, INVALID_TYPE_FORM, NON_SUBSCRIPTABLE, report_invalid_argument_number_to_special_form,
report_invalid_arguments_to_annotated, report_invalid_arguments_to_callable,
report_invalid_arguments_to_callable,
};
use crate::types::signatures::Signature;
use crate::types::string_annotation::parse_string_annotation;
@ -819,11 +819,15 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
builder.into_diagnostic(format_args!(
"`{ty}` is not a generic class",
ty = ty.to_union(self.db()).display(self.db())
ty = ty.inner(self.db()).display(self.db())
));
}
Type::unknown()
}
KnownInstanceType::Annotated(_) => {
self.infer_type_expression(slice);
todo_type!("Generic specialization of typing.Annotated")
}
},
Type::Dynamic(DynamicType::Todo(_)) => {
self.infer_type_expression(slice);
@ -900,7 +904,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
ty
}
fn infer_parameterized_special_form_type_expression(
pub(crate) fn infer_parameterized_special_form_type_expression(
&mut self,
subscript: &ast::ExprSubscript,
special_form: SpecialFormType,
@ -909,36 +913,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
let arguments_slice = &*subscript.slice;
match special_form {
SpecialFormType::Annotated => {
let ast::Expr::Tuple(ast::ExprTuple {
elts: arguments, ..
}) = arguments_slice
else {
report_invalid_arguments_to_annotated(&self.context, subscript);
// `Annotated[]` with less than two arguments is an error at runtime.
// However, we still treat `Annotated[T]` as `T` here for the purpose of
// giving better diagnostics later on.
// Pyright also does this. Mypy doesn't; it falls back to `Any` instead.
return self.infer_type_expression(arguments_slice);
};
if arguments.len() < 2 {
report_invalid_arguments_to_annotated(&self.context, subscript);
}
let [type_expr, metadata @ ..] = &arguments[..] else {
for argument in arguments {
self.infer_expression(argument, TypeContext::default());
}
self.store_expression_type(arguments_slice, Type::unknown());
return Type::unknown();
};
for element in metadata {
self.infer_expression(element, TypeContext::default());
}
let ty = self.infer_type_expression(type_expr);
let ty = self
.infer_subscript_load_impl(
Type::SpecialForm(SpecialFormType::Annotated),
subscript,
)
.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);
ty
}
@ -1453,8 +1434,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
return Ok(value_ty);
}
}
Type::KnownInstance(KnownInstanceType::Literal(list)) => {
return Ok(list.to_union(self.db()));
Type::KnownInstance(KnownInstanceType::Literal(ty)) => {
return Ok(ty.inner(self.db()));
}
// `Literal[SomeEnum.Member]`
Type::EnumLiteral(_) => {