blob: 57dd81061dc3bca8f25da82010739970ea8283bc [file] [log] [blame]
use crate::coherence::{CoherenceError, CoherenceSolver};
use crate::debug_span;
use crate::ext::*;
use crate::rust_ir::*;
use crate::{goal_builder::GoalBuilder, Solution};
use chalk_ir::cast::*;
use chalk_ir::fold::shift::Shift;
use chalk_ir::interner::Interner;
use chalk_ir::*;
use itertools::Itertools;
use tracing::{debug, instrument};
impl<I: Interner> CoherenceSolver<'_, I> {
pub(super) fn visit_specializations_of_trait(
&self,
mut record_specialization: impl FnMut(ImplId<I>, ImplId<I>),
) -> Result<(), CoherenceError<I>> {
// Ignore impls for marker traits as they are allowed to overlap.
let trait_datum = self.db.trait_datum(self.trait_id);
if trait_datum.flags.marker {
return Ok(());
}
// Iterate over every pair of impls for the same trait.
let impls = self.db.local_impls_to_coherence_check(self.trait_id);
for (l_id, r_id) in impls.into_iter().tuple_combinations() {
let lhs = &self.db.impl_datum(l_id);
let rhs = &self.db.impl_datum(r_id);
// Two negative impls never overlap.
if !lhs.is_positive() && !rhs.is_positive() {
continue;
}
// Check if the impls overlap, then if they do, check if one specializes
// the other. Note that specialization can only run one way - if both
// specialization checks return *either* true or false, that's an error.
if !self.disjoint(lhs, rhs) {
match (self.specializes(l_id, r_id), self.specializes(r_id, l_id)) {
(true, false) => record_specialization(l_id, r_id),
(false, true) => record_specialization(r_id, l_id),
(_, _) => {
return Err(CoherenceError::OverlappingImpls(self.trait_id));
}
}
}
}
Ok(())
}
// Test if the set of types that these two impls apply to overlap. If the test succeeds, these
// two impls are disjoint.
//
// We combine the binders of the two impls & treat them as existential quantifiers. Then we
// attempt to unify the input types to the trait provided by each impl, as well as prove that
// the where clauses from both impls all hold. At the end, we apply the `compatible` modality
// and negate the query. Negating the query means that we are asking chalk to prove that no
// such overlapping impl exists. By applying `compatible { G }`, chalk attempts to prove that
// "there exists a compatible world where G is provable." When we negate compatible, it turns
// into the statement "for all compatible worlds, G is not provable." This is exactly what we
// want since we want to ensure that there is no overlap in *all* compatible worlds, not just
// that there is no overlap in *some* compatible world.
//
// Examples:
//
// Impls:
// impl<T> Foo for T { } // rhs
// impl Foo for i32 { } // lhs
// Generates:
// not { compatible { exists<T> { exists<> { T = i32 } } } }
//
// Impls:
// impl<T1, U> Foo<T1> for Vec<U> { } // rhs
// impl<T2> Foo<T2> for Vec<i32> { } // lhs
// Generates:
// not { compatible { exists<T1, U> { exists<T2> { Vec<U> = Vec<i32>, T1 = T2 } } } }
//
// Impls:
// impl<T> Foo for Vec<T> where T: Bar { }
// impl<U> Foo for Vec<U> where U: Baz { }
// Generates:
// not { compatible { exists<T> { exists<U> { Vec<T> = Vec<U>, T: Bar, U: Baz } } } }
//
#[instrument(level = "debug", skip(self))]
fn disjoint(&self, lhs: &ImplDatum<I>, rhs: &ImplDatum<I>) -> bool {
let interner = self.db.interner();
let (lhs_binders, lhs_bound) = lhs.binders.as_ref().into();
let (rhs_binders, rhs_bound) = rhs.binders.as_ref().into();
// Upshift the rhs variables in params to account for the joined binders
let lhs_params = lhs_bound
.trait_ref
.substitution
.as_slice(interner)
.iter()
.cloned();
let rhs_params = rhs_bound
.trait_ref
.substitution
.as_slice(interner)
.iter()
.map(|param| param.clone().shifted_in(interner));
// Create an equality goal for every input type the trait, attempting
// to unify the inputs to both impls with one another
let params_goals = lhs_params
.zip(rhs_params)
.map(|(a, b)| GoalData::EqGoal(EqGoal { a, b }).intern(interner));
// Upshift the rhs variables in where clauses
let lhs_where_clauses = lhs_bound.where_clauses.iter().cloned();
let rhs_where_clauses = rhs_bound
.where_clauses
.iter()
.map(|wc| wc.clone().shifted_in(interner));
// Create a goal for each clause in both where clauses
let wc_goals = lhs_where_clauses
.chain(rhs_where_clauses)
.map(|wc| wc.cast(interner));
// Join all the goals we've created together with And, then quantify them
// over the joined binders. This is our query.
let goal = Box::new(Goal::all(interner, params_goals.chain(wc_goals)))
.quantify(interner, QuantifierKind::Exists, lhs_binders)
.quantify(interner, QuantifierKind::Exists, rhs_binders)
.compatible(interner)
.negate(interner);
let canonical_goal = &goal.into_closed_goal(interner);
let mut fresh_solver = (self.solver_builder)();
let solution = fresh_solver.solve(self.db, canonical_goal);
let result = match solution {
// Goal was proven with a unique solution, so no impl was found that causes these two
// to overlap
Some(Solution::Unique(_)) => true,
// Goal was ambiguous, so there *may* be overlap
Some(Solution::Ambig(_)) |
// Goal cannot be proven, so there is some impl that causes overlap
None => false,
};
debug!("overlaps: result = {:?}", result);
result
}
// Creates a goal which, if provable, means "more special" impl specializes the "less special" one.
//
// # General rule
//
// Given the more special impl:
//
// ```ignore
// impl<P0..Pn> SomeTrait<T1..Tm> for T0 where WC_more
// ```
//
// and less special impl
//
// ```ignore
// impl<Q0..Qo> SomeTrait<U1..Um> for U0 where WC_less
// ```
//
// create the goal:
//
// ```ignore
// forall<P0..Pn> {
// if (WC_more) {}
// exists<Q0..Qo> {
// T0 = U0, ..., Tm = Um,
// WC_less
// }
// }
// }
// ```
//
// # Example
//
// Given:
//
// * more: `impl<T: Clone> Foo for Vec<T>`
// * less: `impl<U: Clone> Foo for U`
//
// Resulting goal:
//
// ```ignore
// forall<T> {
// if (T: Clone) {
// exists<U> {
// Vec<T> = U, U: Clone
// }
// }
// }
// ```
#[instrument(level = "debug", skip(self))]
fn specializes(&self, less_special_id: ImplId<I>, more_special_id: ImplId<I>) -> bool {
let more_special = &self.db.impl_datum(more_special_id);
let less_special = &self.db.impl_datum(less_special_id);
debug_span!("specializes", ?less_special, ?more_special);
let interner = self.db.interner();
let gb = &mut GoalBuilder::new(self.db);
// forall<P0..Pn> { ... }
let goal = gb.forall(
&more_special.binders,
less_special_id,
|gb, _, more_special_impl, less_special_id| {
// if (WC_more) { ... }
gb.implies(more_special_impl.where_clauses.iter().cloned(), |gb| {
let less_special = &gb.db().impl_datum(less_special_id);
// exists<Q0..Qn> { ... }
gb.exists(
&less_special.binders,
more_special_impl.trait_ref.clone(),
|gb, _, less_special_impl, more_special_trait_ref| {
let interner = gb.interner();
// T0 = U0, ..., Tm = Um
let params_goals = more_special_trait_ref
.substitution
.as_slice(interner)
.iter()
.cloned()
.zip(
less_special_impl
.trait_ref
.substitution
.as_slice(interner)
.iter()
.cloned(),
)
.map(|(a, b)| GoalData::EqGoal(EqGoal { a, b }).intern(interner));
// <less_special_wc_goals> = where clauses from the less special impl
let less_special_wc_goals = less_special_impl
.where_clauses
.iter()
.cloned()
.casted(interner);
// <equality_goals> && WC_less
gb.all(params_goals.chain(less_special_wc_goals))
},
)
})
},
);
let canonical_goal = &goal.into_closed_goal(interner);
let mut fresh_solver = (self.solver_builder)();
let result = fresh_solver.has_unique_solution(self.db, canonical_goal);
debug!("specializes: result = {:?}", result);
result
}
}