mirror of
https://github.com/astral-sh/ruff.git
synced 2025-11-18 11:41:21 +00:00
[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:
parent
3fa609929f
commit
238f151371
7 changed files with 343 additions and 125 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -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(_) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue