[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:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user