mirror of https://github.com/astral-sh/ruff
[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
|
|
@ -76,8 +76,7 @@ from ty_extensions import reveal_mro
|
||||||
|
|
||||||
class C(Annotated[int, "foo"]): ...
|
class C(Annotated[int, "foo"]): ...
|
||||||
|
|
||||||
# TODO: Should be `(<class 'C'>, <class 'int'>, <class 'object'>)`
|
reveal_mro(C) # revealed: (<class 'C'>, <class 'int'>, <class 'object'>)
|
||||||
reveal_mro(C) # revealed: (<class 'C'>, @Todo(Inference of subscript on special form), <class 'object'>)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Not parameterized
|
### Not parameterized
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ g(None)
|
||||||
We also support unions in type aliases:
|
We also support unions in type aliases:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from typing_extensions import Any, Never, Literal
|
from typing_extensions import Any, Never, Literal, LiteralString, Tuple, Annotated, Optional
|
||||||
from ty_extensions import Unknown
|
from ty_extensions import Unknown
|
||||||
|
|
||||||
IntOrStr = int | str
|
IntOrStr = int | str
|
||||||
|
|
@ -56,6 +56,14 @@ UnknownOrInt = Unknown | int
|
||||||
IntOrUnknown = int | Unknown
|
IntOrUnknown = int | Unknown
|
||||||
StrOrZero = str | Literal[0]
|
StrOrZero = str | Literal[0]
|
||||||
ZeroOrStr = Literal[0] | str
|
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(IntOrStr) # revealed: types.UnionType
|
||||||
reveal_type(IntOrStrOrBytes1) # 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(IntOrUnknown) # revealed: types.UnionType
|
||||||
reveal_type(StrOrZero) # revealed: types.UnionType
|
reveal_type(StrOrZero) # revealed: types.UnionType
|
||||||
reveal_type(ZeroOrStr) # 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 _(
|
def _(
|
||||||
int_or_str: IntOrStr,
|
int_or_str: IntOrStr,
|
||||||
|
|
@ -99,6 +115,14 @@ def _(
|
||||||
int_or_unknown: IntOrUnknown,
|
int_or_unknown: IntOrUnknown,
|
||||||
str_or_zero: StrOrZero,
|
str_or_zero: StrOrZero,
|
||||||
zero_or_str: ZeroOrStr,
|
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) # revealed: int | str
|
||||||
reveal_type(int_or_str_or_bytes1) # revealed: int | str | bytes
|
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(int_or_unknown) # revealed: int | Unknown
|
||||||
reveal_type(str_or_zero) # revealed: str | Literal[0]
|
reveal_type(str_or_zero) # revealed: str | Literal[0]
|
||||||
reveal_type(zero_or_str) # revealed: Literal[0] | str
|
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
|
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
|
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?
|
## Stringified annotations?
|
||||||
|
|
||||||
From the [typing spec on type aliases](https://typing.python.org/en/latest/spec/aliases.html):
|
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]
|
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: 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
|
info: rule `invalid-argument-type` is enabled by default
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -6462,7 +6462,12 @@ impl<'db> Type<'db> {
|
||||||
}
|
}
|
||||||
Ok(builder.build())
|
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 {
|
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
|
/// A single instance of `types.UnionType`, which stores the left- and
|
||||||
/// right-hand sides of a PEP 604 union.
|
/// right-hand sides of a PEP 604 union.
|
||||||
UnionType(TypeList<'db>),
|
UnionType(InternedTypes<'db>),
|
||||||
|
|
||||||
/// A single instance of `typing.Literal`
|
/// 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>(
|
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);
|
visitor.visit_type(db, default_ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
KnownInstanceType::UnionType(list) | KnownInstanceType::Literal(list) => {
|
KnownInstanceType::UnionType(list) => {
|
||||||
for element in list.elements(db) {
|
for element in list.elements(db) {
|
||||||
visitor.visit_type(db, *element);
|
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::ConstraintSet(set)
|
||||||
}
|
}
|
||||||
Self::UnionType(list) => Self::UnionType(list.normalized_impl(db, visitor)),
|
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::ConstraintSet(_) => KnownClass::ConstraintSet,
|
||||||
Self::UnionType(_) => KnownClass::UnionType,
|
Self::UnionType(_) => KnownClass::UnionType,
|
||||||
Self::Literal(_) => KnownClass::GenericAlias,
|
Self::Literal(_) => KnownClass::GenericAlias,
|
||||||
|
Self::Annotated(_) => KnownClass::GenericAlias,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7848,7 +7861,10 @@ impl<'db> KnownInstanceType<'db> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
KnownInstanceType::UnionType(_) => f.write_str("types.UnionType"),
|
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(
|
fn into_fallback_type(
|
||||||
self,
|
self,
|
||||||
context: &InferContext,
|
context: &InferContext,
|
||||||
node: &ast::Expr,
|
node: &impl Ranged,
|
||||||
is_reachable: bool,
|
is_reachable: bool,
|
||||||
) -> Type<'db> {
|
) -> Type<'db> {
|
||||||
let InvalidTypeExpressionError {
|
let InvalidTypeExpressionError {
|
||||||
|
|
@ -8984,29 +9000,25 @@ impl<'db> TypeVarBoundOrConstraints<'db> {
|
||||||
/// # Ordering
|
/// # Ordering
|
||||||
/// Ordering is based on the context's salsa-assigned id and not on its values.
|
/// 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.
|
/// 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)]
|
#[derive(PartialOrd, Ord)]
|
||||||
pub struct TypeList<'db> {
|
pub struct InternedTypes<'db> {
|
||||||
#[returns(deref)]
|
#[returns(deref)]
|
||||||
elements: Box<[Type<'db>]>,
|
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(
|
pub(crate) fn from_elements(
|
||||||
db: &'db dyn Db,
|
db: &'db dyn Db,
|
||||||
elements: impl IntoIterator<Item = Type<'db>>,
|
elements: impl IntoIterator<Item = Type<'db>>,
|
||||||
) -> TypeList<'db> {
|
) -> InternedTypes<'db> {
|
||||||
TypeList::new(db, elements.into_iter().collect::<Box<[_]>>())
|
InternedTypes::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])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
pub(crate) fn normalized_impl(self, db: &'db dyn Db, visitor: &NormalizedVisitor<'db>) -> Self {
|
||||||
TypeList::new(
|
InternedTypes::new(
|
||||||
db,
|
db,
|
||||||
self.elements(db)
|
self.elements(db)
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -9014,10 +9026,24 @@ impl<'db> TypeList<'db> {
|
||||||
.collect::<Box<[_]>>(),
|
.collect::<Box<[_]>>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Turn this list of types `[T1, T2, ...]` into a union type `T1 | T2 | ...`.
|
/// A salsa-interned `Type`
|
||||||
pub(crate) fn to_union(self, db: &'db dyn Db) -> Type<'db> {
|
///
|
||||||
UnionType::from_elements(db, self.elements(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, 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::ConstraintSet(_)
|
||||||
| KnownInstanceType::UnionType(_)
|
| KnownInstanceType::UnionType(_)
|
||||||
| KnownInstanceType::Literal(_) => None,
|
| KnownInstanceType::Literal(_) => None,
|
||||||
|
KnownInstanceType::Annotated(ty) => Self::try_from_type(db, ty.inner(db), subclass),
|
||||||
},
|
},
|
||||||
|
|
||||||
Type::SpecialForm(special_form) => match special_form {
|
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,
|
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_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_duplicate_bases, report_implicit_return_type, report_index_out_of_bounds,
|
||||||
report_instance_layout_conflict, report_invalid_assignment,
|
report_instance_layout_conflict, report_invalid_arguments_to_annotated,
|
||||||
report_invalid_attribute_assignment, report_invalid_exception_caught,
|
report_invalid_assignment, report_invalid_attribute_assignment,
|
||||||
report_invalid_exception_cause, report_invalid_exception_raised,
|
report_invalid_exception_caught, report_invalid_exception_cause,
|
||||||
report_invalid_generator_function_return_type, report_invalid_key_on_typed_dict,
|
report_invalid_exception_raised, report_invalid_generator_function_return_type,
|
||||||
report_invalid_or_unsupported_base, report_invalid_return_type,
|
report_invalid_key_on_typed_dict, report_invalid_or_unsupported_base,
|
||||||
report_invalid_type_checking_constant,
|
report_invalid_return_type, report_invalid_type_checking_constant,
|
||||||
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
report_namedtuple_field_without_default_after_field_with_default, report_non_subscriptable,
|
||||||
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
report_possibly_missing_attribute, report_possibly_unresolved_reference,
|
||||||
report_rebound_typevar, report_slice_step_size_zero,
|
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::visitor::any_over_type;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams,
|
CallDunderError, CallableBinding, CallableType, ClassLiteral, ClassType, DataclassParams,
|
||||||
DynamicType, IntersectionBuilder, IntersectionType, KnownClass, KnownInstanceType,
|
DynamicType, InternedType, InternedTypes, IntersectionBuilder, IntersectionType, KnownClass,
|
||||||
MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter, ParameterForm,
|
KnownInstanceType, MemberLookupPolicy, MetaclassCandidate, PEP695TypeAliasType, Parameter,
|
||||||
Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness, Type,
|
ParameterForm, Parameters, SpecialFormType, SubclassOfType, TrackedConstraintSet, Truthiness,
|
||||||
TypeAliasType, TypeAndQualifiers, TypeContext, TypeList, TypeQualifiers,
|
Type, TypeAliasType, TypeAndQualifiers, TypeContext, TypeQualifiers,
|
||||||
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
|
TypeVarBoundOrConstraintsEvaluation, TypeVarDefaultEvaluation, TypeVarIdentity,
|
||||||
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType,
|
TypeVarInstance, TypeVarKind, TypeVarVariance, TypedDictType, UnionBuilder, UnionType,
|
||||||
binding_type, todo_type,
|
binding_type, todo_type,
|
||||||
|
|
@ -8755,14 +8755,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
| Type::GenericAlias(..)
|
| Type::GenericAlias(..)
|
||||||
| Type::SpecialForm(_)
|
| Type::SpecialForm(_)
|
||||||
| Type::KnownInstance(
|
| Type::KnownInstance(
|
||||||
KnownInstanceType::UnionType(_) | KnownInstanceType::Literal(_),
|
KnownInstanceType::UnionType(_)
|
||||||
|
| KnownInstanceType::Literal(_)
|
||||||
|
| KnownInstanceType::Annotated(_),
|
||||||
),
|
),
|
||||||
Type::ClassLiteral(..)
|
Type::ClassLiteral(..)
|
||||||
| Type::SubclassOf(..)
|
| Type::SubclassOf(..)
|
||||||
| Type::GenericAlias(..)
|
| Type::GenericAlias(..)
|
||||||
| Type::SpecialForm(_)
|
| Type::SpecialForm(_)
|
||||||
| Type::KnownInstance(
|
| Type::KnownInstance(
|
||||||
KnownInstanceType::UnionType(_) | KnownInstanceType::Literal(_),
|
KnownInstanceType::UnionType(_)
|
||||||
|
| KnownInstanceType::Literal(_)
|
||||||
|
| KnownInstanceType::Annotated(_),
|
||||||
),
|
),
|
||||||
ast::Operator::BitOr,
|
ast::Operator::BitOr,
|
||||||
) if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310 => {
|
) if Program::get(self.db()).python_version(self.db()) >= PythonVersion::PY310 => {
|
||||||
|
|
@ -8770,7 +8774,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||||
Some(left_ty)
|
Some(left_ty)
|
||||||
} else {
|
} else {
|
||||||
Some(Type::KnownInstance(KnownInstanceType::UnionType(
|
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) =>
|
&& instance.has_known_class(self.db(), KnownClass::NoneType) =>
|
||||||
{
|
{
|
||||||
Some(Type::KnownInstance(KnownInstanceType::UnionType(
|
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> {
|
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 {
|
let ast::ExprSubscript {
|
||||||
range: _,
|
range: _,
|
||||||
node_index: _,
|
node_index: _,
|
||||||
value,
|
value: _,
|
||||||
slice,
|
slice,
|
||||||
ctx,
|
ctx,
|
||||||
} = subscript;
|
} = subscript;
|
||||||
let value_ty = self.infer_expression(value, TypeContext::default());
|
|
||||||
let mut constraint_keys = vec![];
|
let mut constraint_keys = vec![];
|
||||||
|
|
||||||
// If `value` is a valid reference, we attempt type narrowing by assignment.
|
// 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))
|
Type::from(tuple.to_class_type(db))
|
||||||
};
|
};
|
||||||
|
|
||||||
// HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
|
match value_ty {
|
||||||
// subscript inference logic and treat this as an explicit specialization.
|
Type::ClassLiteral(class) => {
|
||||||
// TODO: Move this logic into a custom callable, and update `find_name_in_mro` to return
|
// HACK ALERT: If we are subscripting a generic class, short-circuit the rest of the
|
||||||
// this callable as the `__class_getitem__` method on `type`. That probably requires
|
// subscript inference logic and treat this as an explicit specialization.
|
||||||
// updating all of the subscript logic below to use custom callables for all of the _other_
|
// TODO: Move this logic into a custom callable, and update `find_name_in_mro` to return
|
||||||
// special cases, too.
|
// this callable as the `__class_getitem__` method on `type`. That probably requires
|
||||||
if let Type::ClassLiteral(class) = value_ty {
|
// updating all of the subscript logic below to use custom callables for all of the _other_
|
||||||
if class.is_tuple(self.db()) {
|
// 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));
|
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
|
||||||
}
|
}
|
||||||
if let Some(generic_context) = class.generic_context(self.db()) {
|
Type::SpecialForm(SpecialFormType::Literal) => {
|
||||||
return self.infer_explicit_class_specialization(
|
match self.infer_literal_parameter_type(slice) {
|
||||||
subscript,
|
Ok(result) => {
|
||||||
value_ty,
|
return Type::KnownInstance(KnownInstanceType::Literal(InternedType::new(
|
||||||
class,
|
self.db(),
|
||||||
generic_context,
|
result,
|
||||||
);
|
)));
|
||||||
}
|
}
|
||||||
}
|
Err(nodes) => {
|
||||||
if let Type::SpecialForm(SpecialFormType::Tuple) = value_ty {
|
for node in nodes {
|
||||||
return tuple_generic_alias(self.db(), self.infer_tuple_type_expression(slice));
|
let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, node)
|
||||||
}
|
else {
|
||||||
if let Type::KnownInstance(KnownInstanceType::TypeAliasType(type_alias)) = value_ty {
|
continue;
|
||||||
if let Some(generic_context) = type_alias.generic_context(self.db()) {
|
};
|
||||||
return self.infer_explicit_type_alias_specialization(
|
builder.into_diagnostic(
|
||||||
subscript,
|
"Type arguments for `Literal` must be `None`, \
|
||||||
value_ty,
|
a literal value (int, bool, str, or bytes), or an enum member",
|
||||||
type_alias,
|
);
|
||||||
generic_context,
|
}
|
||||||
);
|
return Type::unknown();
|
||||||
}
|
|
||||||
}
|
|
||||||
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",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
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());
|
let slice_ty = self.infer_expression(slice, TypeContext::default());
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use ruff_python_ast as ast;
|
||||||
use super::{DeferredExpressionState, TypeInferenceBuilder};
|
use super::{DeferredExpressionState, TypeInferenceBuilder};
|
||||||
use crate::types::diagnostic::{
|
use crate::types::diagnostic::{
|
||||||
self, INVALID_TYPE_FORM, NON_SUBSCRIPTABLE, report_invalid_argument_number_to_special_form,
|
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::signatures::Signature;
|
||||||
use crate::types::string_annotation::parse_string_annotation;
|
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) {
|
if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) {
|
||||||
builder.into_diagnostic(format_args!(
|
builder.into_diagnostic(format_args!(
|
||||||
"`{ty}` is not a generic class",
|
"`{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()
|
Type::unknown()
|
||||||
}
|
}
|
||||||
|
KnownInstanceType::Annotated(_) => {
|
||||||
|
self.infer_type_expression(slice);
|
||||||
|
todo_type!("Generic specialization of typing.Annotated")
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Type::Dynamic(DynamicType::Todo(_)) => {
|
Type::Dynamic(DynamicType::Todo(_)) => {
|
||||||
self.infer_type_expression(slice);
|
self.infer_type_expression(slice);
|
||||||
|
|
@ -900,7 +904,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
ty
|
ty
|
||||||
}
|
}
|
||||||
|
|
||||||
fn infer_parameterized_special_form_type_expression(
|
pub(crate) fn infer_parameterized_special_form_type_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
subscript: &ast::ExprSubscript,
|
subscript: &ast::ExprSubscript,
|
||||||
special_form: SpecialFormType,
|
special_form: SpecialFormType,
|
||||||
|
|
@ -909,36 +913,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
let arguments_slice = &*subscript.slice;
|
let arguments_slice = &*subscript.slice;
|
||||||
match special_form {
|
match special_form {
|
||||||
SpecialFormType::Annotated => {
|
SpecialFormType::Annotated => {
|
||||||
let ast::Expr::Tuple(ast::ExprTuple {
|
let ty = self
|
||||||
elts: arguments, ..
|
.infer_subscript_load_impl(
|
||||||
}) = arguments_slice
|
Type::SpecialForm(SpecialFormType::Annotated),
|
||||||
else {
|
subscript,
|
||||||
report_invalid_arguments_to_annotated(&self.context, subscript);
|
)
|
||||||
|
.in_type_expression(db, self.scope(), None)
|
||||||
// `Annotated[]` with less than two arguments is an error at runtime.
|
.unwrap_or_else(|err| err.into_fallback_type(&self.context, subscript, true));
|
||||||
// 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);
|
|
||||||
self.store_expression_type(arguments_slice, ty);
|
self.store_expression_type(arguments_slice, ty);
|
||||||
ty
|
ty
|
||||||
}
|
}
|
||||||
|
|
@ -1453,8 +1434,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
||||||
return Ok(value_ty);
|
return Ok(value_ty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Type::KnownInstance(KnownInstanceType::Literal(list)) => {
|
Type::KnownInstance(KnownInstanceType::Literal(ty)) => {
|
||||||
return Ok(list.to_union(self.db()));
|
return Ok(ty.inner(self.db()));
|
||||||
}
|
}
|
||||||
// `Literal[SomeEnum.Member]`
|
// `Literal[SomeEnum.Member]`
|
||||||
Type::EnumLiteral(_) => {
|
Type::EnumLiteral(_) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue