[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

@ -150,6 +150,17 @@ reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str]
reveal_type(Constrained[object]()) # revealed: Unknown reveal_type(Constrained[object]()) # revealed: Unknown
``` ```
If the type variable has a default, it can be omitted:
```py
WithDefaultU = TypeVar("WithDefaultU", default=int)
class WithDefault(Generic[T, WithDefaultU]): ...
reveal_type(WithDefault[str, str]()) # revealed: WithDefault[str, str]
reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
```
## Inferring generic class parameters ## Inferring generic class parameters
We can infer the type parameter from a type context: We can infer the type parameter from a type context:

View file

@ -133,6 +133,15 @@ reveal_type(Constrained[int | str]()) # revealed: Constrained[int | str]
reveal_type(Constrained[object]()) # revealed: Unknown reveal_type(Constrained[object]()) # revealed: Unknown
``` ```
If the type variable has a default, it can be omitted:
```py
class WithDefault[T, U = int]: ...
reveal_type(WithDefault[str, str]()) # revealed: WithDefault[str, str]
reveal_type(WithDefault[str]()) # revealed: WithDefault[str, int]
```
## Inferring generic class parameters ## Inferring generic class parameters
We can infer the type parameter from a type context: We can infer the type parameter from a type context:

View file

@ -1316,10 +1316,27 @@ impl<'db> Binding<'db> {
self.inherited_specialization 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>>] { pub(crate) fn parameter_types(&self) -> &[Option<Type<'db>>] {
&self.parameter_tys &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>( pub(crate) fn arguments_for_parameter<'a>(
&'a self, &'a self,
argument_types: &'a CallArgumentTypes<'a, 'db>, argument_types: &'a CallArgumentTypes<'a, 'db>,

View file

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

View file

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