[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:
David Peter
2025-12-03 09:10:45 +01:00
committed by GitHub
parent c5b8d551df
commit e6ddeed386
4 changed files with 49 additions and 43 deletions

View File

@@ -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> {

View File

@@ -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),
)
}),
),
}
}

View File

@@ -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(