[ty] Use type context for inference of generic constructors (#20933)
## Summary Resolves https://github.com/astral-sh/ty/issues/1228. This PR is stacked on https://github.com/astral-sh/ruff/pull/21210.
This commit is contained in:
@@ -1111,6 +1111,22 @@ impl<'db> Type<'db> {
|
||||
}
|
||||
}
|
||||
|
||||
/// If the type is a generic class constructor, returns the class instance type.
|
||||
pub(crate) fn synthesized_constructor_return_ty(self, db: &'db dyn Db) -> Option<Type<'db>> {
|
||||
// TODO: This does not correctly handle unions or intersections. It also does not handle
|
||||
// constructors that are not represented as bound methods, e.g. `__new__`, or synthesized
|
||||
// dataclass initializers.
|
||||
if let Type::BoundMethod(method) = self
|
||||
&& let Type::NominalInstance(instance) = method.self_instance(db)
|
||||
&& method.function(db).name(db).as_str() == "__init__"
|
||||
{
|
||||
let class_ty = instance.class_literal(db).identity_specialization(db);
|
||||
Some(Type::instance(db, class_ty))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn is_property_instance(&self) -> bool {
|
||||
matches!(self, Type::PropertyInstance(..))
|
||||
}
|
||||
|
||||
@@ -2687,6 +2687,7 @@ struct ArgumentTypeChecker<'a, 'db> {
|
||||
arguments: &'a CallArguments<'a, 'db>,
|
||||
argument_matches: &'a [MatchedArgument<'db>],
|
||||
parameter_tys: &'a mut [Option<Type<'db>>],
|
||||
callable_type: Type<'db>,
|
||||
call_expression_tcx: TypeContext<'db>,
|
||||
return_ty: Type<'db>,
|
||||
errors: &'a mut Vec<BindingError<'db>>,
|
||||
@@ -2703,6 +2704,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
arguments: &'a CallArguments<'a, 'db>,
|
||||
argument_matches: &'a [MatchedArgument<'db>],
|
||||
parameter_tys: &'a mut [Option<Type<'db>>],
|
||||
callable_type: Type<'db>,
|
||||
call_expression_tcx: TypeContext<'db>,
|
||||
return_ty: Type<'db>,
|
||||
errors: &'a mut Vec<BindingError<'db>>,
|
||||
@@ -2713,6 +2715,7 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
arguments,
|
||||
argument_matches,
|
||||
parameter_tys,
|
||||
callable_type,
|
||||
call_expression_tcx,
|
||||
return_ty,
|
||||
errors,
|
||||
@@ -2754,8 +2757,9 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
};
|
||||
|
||||
let return_with_tcx = self
|
||||
.signature
|
||||
.return_ty
|
||||
.callable_type
|
||||
.synthesized_constructor_return_ty(self.db)
|
||||
.or(self.signature.return_ty)
|
||||
.zip(self.call_expression_tcx.annotation);
|
||||
|
||||
self.inferable_typevars = generic_context.inferable_typevars(self.db);
|
||||
@@ -2763,7 +2767,9 @@ impl<'a, 'db> ArgumentTypeChecker<'a, 'db> {
|
||||
|
||||
// Prefer the declared type of generic classes.
|
||||
let preferred_type_mappings = return_with_tcx.and_then(|(return_ty, tcx)| {
|
||||
tcx.class_specialization(self.db)?;
|
||||
tcx.filter_union(self.db, |ty| ty.class_specialization(self.db).is_some())
|
||||
.class_specialization(self.db)?;
|
||||
|
||||
builder.infer(return_ty, tcx).ok()?;
|
||||
Some(builder.type_mappings().clone())
|
||||
});
|
||||
@@ -3196,6 +3202,7 @@ impl<'db> Binding<'db> {
|
||||
arguments,
|
||||
&self.argument_matches,
|
||||
&mut self.parameter_tys,
|
||||
self.callable_type,
|
||||
call_expression_tcx,
|
||||
self.return_ty,
|
||||
&mut self.errors,
|
||||
|
||||
@@ -6025,9 +6025,13 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
||||
// TODO: Checking assignability against the full declared type could help avoid
|
||||
// cases where the constraint solver is not smart enough to solve complex unions.
|
||||
// We should see revisit this after the new constraint solver is implemented.
|
||||
if !speculated_bindings
|
||||
.return_type(db)
|
||||
.is_assignable_to(db, narrowed_ty)
|
||||
if speculated_bindings
|
||||
.callable_type()
|
||||
.synthesized_constructor_return_ty(db)
|
||||
.is_none()
|
||||
&& !speculated_bindings
|
||||
.return_type(db)
|
||||
.is_assignable_to(db, narrowed_ty)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user