blob: ca878026b9873dc113cf9981421c17636e5c0672 [file] [log] [blame]
use crate::clauses::ClauseBuilder;
use crate::infer::instantiate::IntoBindersAndValue;
use crate::rust_ir::{ClosureKind, FnDefInputsAndOutputDatum, WellKnownTrait};
use crate::{Interner, RustIrDatabase, TraitRef};
use chalk_ir::cast::Cast;
use chalk_ir::{
AliasTy, ApplicationTy, Binders, Floundered, Normalize, ProjectionTy, Substitution, TraitId,
Ty, TyData, TypeName, VariableKinds,
};
fn push_clauses<I: Interner>(
db: &dyn RustIrDatabase<I>,
builder: &mut ClauseBuilder<'_, I>,
well_known: WellKnownTrait,
trait_id: TraitId<I>,
self_ty: Ty<I>,
arg_sub: Substitution<I>,
return_type: Ty<I>,
) {
let interner = db.interner();
let tupled = TyData::Apply(ApplicationTy {
name: TypeName::Tuple(arg_sub.len(interner)),
substitution: arg_sub,
})
.intern(interner);
let substitution =
Substitution::from(interner, &[self_ty.cast(interner), tupled.cast(interner)]);
builder.push_fact(TraitRef {
trait_id,
substitution: substitution.clone(),
});
// The `Output` type is defined on the `FnOnce`
if let WellKnownTrait::FnOnce = well_known {
let trait_datum = db.trait_datum(trait_id);
assert_eq!(
trait_datum.associated_ty_ids.len(),
1,
"FnOnce trait should have exactly one associated type, found {:?}",
trait_datum.associated_ty_ids
);
// Constructs the alias. For `Fn`, for example, this would look like
// `Normalize(<fn(A) -> B as FnOnce<(A,)>>::Output -> B)`
let output_id = trait_datum.associated_ty_ids[0];
let alias = AliasTy::Projection(ProjectionTy {
associated_ty_id: output_id,
substitution,
});
builder.push_fact(Normalize {
alias,
ty: return_type,
});
}
}
fn push_clauses_for_apply<I: Interner>(
db: &dyn RustIrDatabase<I>,
builder: &mut ClauseBuilder<'_, I>,
well_known: WellKnownTrait,
trait_id: TraitId<I>,
self_ty: Ty<I>,
inputs_and_output: &Binders<FnDefInputsAndOutputDatum<I>>,
) {
let interner = db.interner();
builder.push_binders(inputs_and_output, |builder, inputs_and_output| {
let arg_sub = inputs_and_output
.argument_types
.iter()
.cloned()
.map(|ty| ty.cast(interner));
let arg_sub = Substitution::from(interner, arg_sub);
let output_ty = inputs_and_output.return_type;
push_clauses(
db, builder, well_known, trait_id, self_ty, arg_sub, output_ty,
);
});
}
/// Handles clauses for FnOnce/FnMut/Fn.
/// If `self_ty` is a function, we push a clause of the form
/// `fn(A1, A2, ..., AN) -> O: FnTrait<(A1, A2, ..., AN)>`, where `FnTrait`
/// is the trait corresponding to `trait_id` (FnOnce/FnMut/Fn)
///
/// If `trait_id` is `FnOnce`, we also push a clause for the output type of the form:
/// `Normalize(<fn(A) -> B as FnOnce<(A,)>>::Output -> B)`
/// We do not add the usual `Implemented(fn(A) -> b as FnOnce<(A,)>` clause
/// as a condition, since we already called `push_fact` with it
pub fn add_fn_trait_program_clauses<I: Interner>(
db: &dyn RustIrDatabase<I>,
builder: &mut ClauseBuilder<'_, I>,
well_known: WellKnownTrait,
self_ty: Ty<I>,
) -> Result<(), Floundered> {
let interner = db.interner();
let trait_id = db.well_known_trait_id(well_known).unwrap();
match self_ty.data(interner) {
TyData::Apply(apply) => match apply.name {
TypeName::FnDef(fn_def_id) => {
let fn_def_datum = builder.db.fn_def_datum(fn_def_id);
let bound = fn_def_datum
.binders
.substitute(builder.interner(), &apply.substitution);
let self_ty = ApplicationTy {
name: apply.name,
substitution: builder.substitution_in_scope(),
}
.intern(interner);
push_clauses_for_apply(
db,
builder,
well_known,
trait_id,
self_ty,
&bound.inputs_and_output,
);
Ok(())
}
TypeName::Closure(closure_id) => {
let closure_kind = db.closure_kind(closure_id, &apply.substitution);
let trait_matches = match (well_known, closure_kind) {
(WellKnownTrait::Fn, ClosureKind::Fn) => true,
(WellKnownTrait::FnMut, ClosureKind::FnMut)
| (WellKnownTrait::FnMut, ClosureKind::Fn) => true,
(WellKnownTrait::FnOnce, _) => true,
_ => false,
};
if !trait_matches {
return Ok(());
}
let closure_inputs_and_output =
db.closure_inputs_and_output(closure_id, &apply.substitution);
push_clauses_for_apply(
db,
builder,
well_known,
trait_id,
self_ty,
&closure_inputs_and_output,
);
Ok(())
}
_ => Ok(()),
},
TyData::Function(fn_val) => {
let (binders, orig_sub) = fn_val.into_binders_and_value(interner);
let bound_ref = Binders::new(VariableKinds::from(interner, binders), orig_sub);
builder.push_binders(&bound_ref, |builder, orig_sub| {
// The last parameter represents the function return type
let (arg_sub, fn_output_ty) = orig_sub
.parameters(interner)
.split_at(orig_sub.len(interner) - 1);
let arg_sub = Substitution::from(interner, arg_sub);
let output_ty = fn_output_ty[0].assert_ty_ref(interner).clone();
push_clauses(
db,
builder,
well_known,
trait_id,
self_ty.clone(),
arg_sub,
output_ty,
);
});
Ok(())
}
// Function traits are non-enumerable
TyData::InferenceVar(..) | TyData::Alias(..) => Err(Floundered),
_ => Ok(()),
}
}