mirror of https://github.com/astral-sh/ruff
[ty] Default-specialization of generic type aliases (#21765)
## Summary Implement default-specialization of generic type aliases (implicit or PEP-613) if they are used in a type expression without an explicit specialization. closes https://github.com/astral-sh/ty/issues/1690 ## Typing conformance ```diff -generics_defaults_specialization.py:26:5: error[type-assertion-failure] Type `SomethingWithNoDefaults[int, str]` does not match asserted type `SomethingWithNoDefaults[int, DefaultStrT]` ``` That's exactly what we want ✔️ All other tests in this file pass as well, with the exception of this assertion, which is just wrong (at least according to our interpretation, `type[Bar] != <class 'Bar'>`). I checked that we do correctly default-specialize the type parameter which is not displayed in the diagnostic that we raise. ```py class Bar(SubclassMe[int, DefaultStrT]): ... assert_type(Bar, type[Bar[str]]) # ty: Type `type[Bar[str]]` does not match asserted type `<class 'Bar'>` ``` ## Ecosystem impact Looks like I should have included this last week 😎 ## Test Plan Updated pre-existing tests and add a few new ones.
This commit is contained in:
parent
c5b8d551df
commit
e6ddeed386
|
|
@ -190,14 +190,10 @@ def _(
|
|||
reveal_type(type_of_str_or_int) # revealed: type[str] | int
|
||||
reveal_type(int_or_callable) # revealed: int | ((str, /) -> bytes)
|
||||
reveal_type(callable_or_int) # revealed: ((str, /) -> bytes) | int
|
||||
# TODO should be Unknown | int
|
||||
reveal_type(type_var_or_int) # revealed: T@TypeVarOrInt | int
|
||||
# TODO should be int | Unknown
|
||||
reveal_type(int_or_type_var) # revealed: int | T@IntOrTypeVar
|
||||
# TODO should be Unknown | None
|
||||
reveal_type(type_var_or_none) # revealed: T@TypeVarOrNone | None
|
||||
# TODO should be None | Unknown
|
||||
reveal_type(none_or_type_var) # revealed: None | T@NoneOrTypeVar
|
||||
reveal_type(type_var_or_int) # revealed: Unknown | int
|
||||
reveal_type(int_or_type_var) # revealed: int | Unknown
|
||||
reveal_type(type_var_or_none) # revealed: Unknown | None
|
||||
reveal_type(none_or_type_var) # revealed: None | Unknown
|
||||
```
|
||||
|
||||
If a type is unioned with itself in a value expression, the result is just that type. No
|
||||
|
|
@ -529,28 +525,18 @@ def _(
|
|||
annotated_unknown: AnnotatedType,
|
||||
optional_unknown: MyOptional,
|
||||
):
|
||||
# TODO: This should be `list[Unknown]`
|
||||
reveal_type(list_unknown) # revealed: list[T@MyList]
|
||||
# TODO: This should be `dict[Unknown, Unknown]`
|
||||
reveal_type(dict_unknown) # revealed: dict[T@MyDict, U@MyDict]
|
||||
# TODO: Should be `type[Unknown]`
|
||||
reveal_type(subclass_of_unknown) # revealed: type[T@MyType]
|
||||
# TODO: Should be `tuple[int, Unknown]`
|
||||
reveal_type(int_and_unknown) # revealed: tuple[int, T@IntAndType]
|
||||
# TODO: Should be `tuple[Unknown, Unknown]`
|
||||
reveal_type(pair_of_unknown) # revealed: tuple[T@Pair, T@Pair]
|
||||
# TODO: Should be `tuple[Unknown, Unknown]`
|
||||
reveal_type(unknown_and_unknown) # revealed: tuple[T@Sum, U@Sum]
|
||||
# TODO: Should be `list[Unknown] | tuple[Unknown, ...]`
|
||||
reveal_type(list_or_tuple) # revealed: list[T@ListOrTuple] | tuple[T@ListOrTuple, ...]
|
||||
# TODO: Should be `list[Unknown] | tuple[Unknown, ...]`
|
||||
reveal_type(list_or_tuple_legacy) # revealed: list[T@ListOrTupleLegacy] | tuple[T@ListOrTupleLegacy, ...]
|
||||
# TODO: Should be `(...) -> Unknown`
|
||||
reveal_type(list_unknown) # revealed: list[Unknown]
|
||||
reveal_type(dict_unknown) # revealed: dict[Unknown, Unknown]
|
||||
reveal_type(subclass_of_unknown) # revealed: type[Unknown]
|
||||
reveal_type(int_and_unknown) # revealed: tuple[int, Unknown]
|
||||
reveal_type(pair_of_unknown) # revealed: tuple[Unknown, Unknown]
|
||||
reveal_type(unknown_and_unknown) # revealed: tuple[Unknown, Unknown]
|
||||
reveal_type(list_or_tuple) # revealed: list[Unknown] | tuple[Unknown, ...]
|
||||
reveal_type(list_or_tuple_legacy) # revealed: list[Unknown] | tuple[Unknown, ...]
|
||||
# TODO: should be (...) -> Unknown
|
||||
reveal_type(my_callable) # revealed: @Todo(Callable[..] specialized with ParamSpec)
|
||||
# TODO: Should be `Unknown`
|
||||
reveal_type(annotated_unknown) # revealed: T@AnnotatedType
|
||||
# TODO: Should be `Unknown | None`
|
||||
reveal_type(optional_unknown) # revealed: T@MyOptional | None
|
||||
reveal_type(annotated_unknown) # revealed: Unknown
|
||||
reveal_type(optional_unknown) # revealed: Unknown | None
|
||||
```
|
||||
|
||||
For a type variable with a default, we use the default type:
|
||||
|
|
@ -563,10 +549,13 @@ MyListWithDefault = list[T_default]
|
|||
def _(
|
||||
list_of_str: MyListWithDefault[str],
|
||||
list_of_int: MyListWithDefault,
|
||||
list_of_str_or_none: MyListWithDefault[str] | None,
|
||||
list_of_int_or_none: MyListWithDefault | None,
|
||||
):
|
||||
reveal_type(list_of_str) # revealed: list[str]
|
||||
# TODO: this should be `list[int]`
|
||||
reveal_type(list_of_int) # revealed: list[T_default@MyListWithDefault]
|
||||
reveal_type(list_of_int) # revealed: list[int]
|
||||
reveal_type(list_of_str_or_none) # revealed: list[str] | None
|
||||
reveal_type(list_of_int_or_none) # revealed: list[int] | None
|
||||
```
|
||||
|
||||
(Generic) implicit type aliases can be used as base classes:
|
||||
|
|
@ -601,7 +590,7 @@ Generic implicit type aliases can be imported from other modules and specialized
|
|||
```py
|
||||
from typing_extensions import TypeVar
|
||||
|
||||
T = TypeVar("T")
|
||||
T = TypeVar("T", default=str)
|
||||
|
||||
MyList = list[T]
|
||||
```
|
||||
|
|
@ -615,9 +604,13 @@ import my_types as mt
|
|||
def _(
|
||||
list_of_ints1: MyList[int],
|
||||
list_of_ints2: mt.MyList[int],
|
||||
list_of_str: mt.MyList,
|
||||
list_of_str_or_none: mt.MyList | None,
|
||||
):
|
||||
reveal_type(list_of_ints1) # revealed: list[int]
|
||||
reveal_type(list_of_ints2) # revealed: list[int]
|
||||
reveal_type(list_of_str) # revealed: list[str]
|
||||
reveal_type(list_of_str_or_none) # revealed: list[str] | None
|
||||
```
|
||||
|
||||
### In stringified annotations
|
||||
|
|
|
|||
|
|
@ -8263,6 +8263,16 @@ impl<'db> Type<'db> {
|
|||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Default-specialize all legacy typevars in this type.
|
||||
///
|
||||
/// This is used when an implicit type alias is referenced without explicitly specializing it.
|
||||
pub(crate) fn default_specialize(self, db: &'db dyn Db) -> Type<'db> {
|
||||
let mut variables = FxOrderSet::default();
|
||||
self.find_legacy_typevars(db, None, &mut variables);
|
||||
let generic_context = GenericContext::from_typevar_instances(db, variables);
|
||||
self.apply_specialization(db, generic_context.default_specialization(db, None))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'db> From<&Type<'db>> for Type<'db> {
|
||||
|
|
|
|||
|
|
@ -144,18 +144,19 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
)
|
||||
}
|
||||
_ => TypeAndQualifiers::declared(
|
||||
ty.in_type_expression(
|
||||
builder.db(),
|
||||
builder.scope(),
|
||||
builder.typevar_binding_context,
|
||||
)
|
||||
.unwrap_or_else(|error| {
|
||||
error.into_fallback_type(
|
||||
&builder.context,
|
||||
annotation,
|
||||
builder.is_reachable(annotation),
|
||||
ty.default_specialize(builder.db())
|
||||
.in_type_expression(
|
||||
builder.db(),
|
||||
builder.scope(),
|
||||
builder.typevar_binding_context,
|
||||
)
|
||||
}),
|
||||
.unwrap_or_else(|error| {
|
||||
error.into_fallback_type(
|
||||
&builder.context,
|
||||
annotation,
|
||||
builder.is_reachable(annotation),
|
||||
)
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
ast::Expr::Name(name) => match name.ctx {
|
||||
ast::ExprContext::Load => self
|
||||
.infer_name_expression(name)
|
||||
.default_specialize(self.db())
|
||||
.in_type_expression(self.db(), self.scope(), self.typevar_binding_context)
|
||||
.unwrap_or_else(|error| {
|
||||
error.into_fallback_type(
|
||||
|
|
@ -108,6 +109,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> {
|
|||
ast::Expr::Attribute(attribute_expression) => match attribute_expression.ctx {
|
||||
ast::ExprContext::Load => self
|
||||
.infer_attribute_expression(attribute_expression)
|
||||
.default_specialize(self.db())
|
||||
.in_type_expression(self.db(), self.scope(), self.typevar_binding_context)
|
||||
.unwrap_or_else(|error| {
|
||||
error.into_fallback_type(
|
||||
|
|
|
|||
Loading…
Reference in New Issue