[ty] Don't require default typevars when specializing (#17872)

If a typevar is declared as having a default, we shouldn't require a
type to be specified for that typevar when explicitly specializing a
generic class:

```py
class WithDefault[T, U = int]: ...

reveal_type(WithDefault[str]())  # revealed: WithDefault[str, int]
```

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
This commit is contained in:
Douglas Creager 2025-05-05 18:29:30 -04:00 committed by GitHub
parent bb6c7cad07
commit ada4c4cb1f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 46 additions and 12 deletions

View file

@ -1316,10 +1316,27 @@ impl<'db> Binding<'db> {
self.inherited_specialization
}
/// Returns the bound types for each parameter, in parameter source order, or `None` if no
/// argument was matched to that parameter.
pub(crate) fn parameter_types(&self) -> &[Option<Type<'db>>] {
&self.parameter_tys
}
/// Returns the bound types for each parameter, in parameter source order, with default values
/// applied for arguments that weren't matched to a parameter. Returns `None` if there are any
/// non-default arguments that weren't matched to a parameter.
pub(crate) fn parameter_types_with_defaults(
&self,
signature: &Signature<'db>,
) -> Option<Box<[Type<'db>]>> {
signature
.parameters()
.iter()
.zip(&self.parameter_tys)
.map(|(parameter, parameter_ty)| parameter_ty.or(parameter.default_type()))
.collect()
}
pub(crate) fn arguments_for_parameter<'a>(
&'a self,
argument_types: &'a CallArgumentTypes<'a, 'db>,

View file

@ -123,6 +123,9 @@ impl<'db> GenericContext<'db> {
}
None => {}
}
if let Some(default_ty) = typevar.default_ty(db) {
parameter = parameter.with_default_type(default_ty);
}
parameter
}

View file

@ -6711,10 +6711,8 @@ impl<'db> TypeInferenceBuilder<'db> {
}
_ => CallArgumentTypes::positional([self.infer_type_expression(slice_node)]),
};
let signatures = Signatures::single(CallableSignature::single(
value_ty,
generic_context.signature(self.db()),
));
let signature = generic_context.signature(self.db());
let signatures = Signatures::single(CallableSignature::single(value_ty, signature.clone()));
let bindings = match Bindings::match_parameters(signatures, &call_argument_types)
.check_types(self.db(), &call_argument_types)
{
@ -6732,14 +6730,10 @@ impl<'db> TypeInferenceBuilder<'db> {
.matching_overloads()
.next()
.expect("valid bindings should have matching overload");
let specialization = generic_context.specialize(
self.db(),
overload
.parameter_types()
.iter()
.map(|ty| ty.unwrap_or(Type::unknown()))
.collect(),
);
let parameters = overload
.parameter_types_with_defaults(&signature)
.expect("matching overload should not have missing arguments");
let specialization = generic_context.specialize(self.db(), parameters);
Type::from(GenericAlias::new(self.db(), generic_class, specialization))
}