blob: 0734fc53ea3fb6a49f70465cb632541e8f8c3b28 [file] [log] [blame] [edit]
use crate::RustIrDatabase;
use chalk_derive::HasInterner;
use chalk_ir::interner::Interner;
use chalk_ir::*;
use std::fmt;
use tracing::debug;
pub mod truncate;
/// A (possible) solution for a proposed goal.
#[derive(Clone, Debug, PartialEq, Eq, HasInterner)]
pub enum Solution<I: Interner> {
/// The goal indeed holds, and there is a unique value for all existential
/// variables. In this case, we also record a set of lifetime constraints
/// which must also hold for the goal to be valid.
Unique(Canonical<ConstrainedSubst<I>>),
/// The goal may be provable in multiple ways, but regardless we may have some guidance
/// for type inference. In this case, we don't return any lifetime
/// constraints, since we have not "committed" to any particular solution
/// yet.
Ambig(Guidance<I>),
}
/// When a goal holds ambiguously (e.g., because there are multiple possible
/// solutions), we issue a set of *guidance* back to type inference.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Guidance<I: Interner> {
/// The existential variables *must* have the given values if the goal is
/// ever to hold, but that alone isn't enough to guarantee the goal will
/// actually hold.
Definite(Canonical<Substitution<I>>),
/// There are multiple plausible values for the existentials, but the ones
/// here are suggested as the preferred choice heuristically. These should
/// be used for inference fallback only.
Suggested(Canonical<Substitution<I>>),
/// There's no useful information to feed back to type inference
Unknown,
}
impl<I: Interner> Solution<I> {
/// There are multiple candidate solutions, which may or may not agree on
/// the values for existential variables; attempt to combine them. This
/// operation does not depend on the order of its arguments.
///
/// This actually isn't as precise as it could be, in two ways:
///
/// a. It might be that while there are multiple distinct candidates, they
/// all agree about *some things*. To be maximally precise, we would
/// compute the intersection of what they agree on. It's not clear though
/// that this is actually what we want Rust's inference to do, and it's
/// certainly not what it does today.
///
/// b. There might also be an ambiguous candidate and a successful candidate,
/// both with the same refined-goal. In that case, we could probably claim
/// success, since if the conditions of the ambiguous candidate were met,
/// we know the success would apply. Example: `?0: Clone` yields ambiguous
/// candidate `Option<?0>: Clone` and successful candidate `Option<?0>:
/// Clone`.
///
/// But you get the idea.
pub fn combine(self, other: Solution<I>, interner: I) -> Solution<I> {
use self::Guidance::*;
if self == other {
return self;
}
// Special case hack: if one solution is "true" without any constraints,
// that is always the combined result.
//
// This is not as general as it could be: ideally, if we had one solution
// that is Unique with a simpler substitution than the other one, or region constraints
// which are a subset, we'd combine them.
if self.is_trivial_and_always_true(interner) {
return self;
}
if other.is_trivial_and_always_true(interner) {
return other;
}
debug!(
"combine {} with {}",
self.display(interner),
other.display(interner)
);
// Otherwise, always downgrade to Ambig:
let guidance = match (self.into_guidance(), other.into_guidance()) {
(Definite(ref subst1), Definite(ref subst2)) if subst1 == subst2 => {
Definite(subst1.clone())
}
(Suggested(ref subst1), Suggested(ref subst2)) if subst1 == subst2 => {
Suggested(subst1.clone())
}
_ => Unknown,
};
Solution::Ambig(guidance)
}
pub fn is_trivial_and_always_true(&self, interner: I) -> bool {
match self {
Solution::Unique(constrained_subst) => {
constrained_subst.value.subst.is_identity_subst(interner)
&& constrained_subst.value.constraints.is_empty(interner)
}
Solution::Ambig(_) => false,
}
}
/// View this solution purely in terms of type inference guidance
pub fn into_guidance(self) -> Guidance<I> {
match self {
Solution::Unique(constrained) => Guidance::Definite(Canonical {
value: constrained.value.subst,
binders: constrained.binders,
}),
Solution::Ambig(guidance) => guidance,
}
}
/// Extract a constrained substitution from this solution, even if ambiguous.
pub fn constrained_subst(&self, interner: I) -> Option<Canonical<ConstrainedSubst<I>>> {
match *self {
Solution::Unique(ref constrained) => Some(constrained.clone()),
Solution::Ambig(Guidance::Definite(ref canonical))
| Solution::Ambig(Guidance::Suggested(ref canonical)) => {
let value = ConstrainedSubst {
subst: canonical.value.clone(),
constraints: Constraints::empty(interner),
};
Some(Canonical {
value,
binders: canonical.binders.clone(),
})
}
Solution::Ambig(_) => None,
}
}
/// Determine whether this solution contains type information that *must*
/// hold, and returns the subst in that case.
pub fn definite_subst(&self, interner: I) -> Option<Canonical<ConstrainedSubst<I>>> {
match self {
Solution::Unique(constrained) => Some(constrained.clone()),
Solution::Ambig(Guidance::Definite(canonical)) => {
let value = ConstrainedSubst {
subst: canonical.value.clone(),
constraints: Constraints::empty(interner),
};
Some(Canonical {
value,
binders: canonical.binders.clone(),
})
}
_ => None,
}
}
pub fn is_unique(&self) -> bool {
matches!(*self, Solution::Unique(..))
}
pub fn is_ambig(&self) -> bool {
matches!(*self, Solution::Ambig(_))
}
pub fn display(&self, interner: I) -> SolutionDisplay<'_, I> {
SolutionDisplay {
solution: self,
interner,
}
}
}
pub struct SolutionDisplay<'a, I: Interner> {
solution: &'a Solution<I>,
interner: I,
}
impl<'a, I: Interner> fmt::Display for SolutionDisplay<'a, I> {
#[rustfmt::skip]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
let SolutionDisplay { solution, interner } = self;
match solution {
// If a `Unique` solution has no associated data, omit the trailing semicolon.
// This makes blessed test output nicer to read.
Solution::Unique(Canonical { binders, value: ConstrainedSubst { subst, constraints } } )
if interner.constraints_data(constraints.interned()).is_empty()
&& interner.substitution_data(subst.interned()).is_empty()
&& interner.canonical_var_kinds_data(binders.interned()).is_empty()
=> write!(f, "Unique"),
Solution::Unique(constrained) => write!(f, "Unique; {}", constrained.display(*interner)),
Solution::Ambig(Guidance::Definite(subst)) => write!(
f,
"Ambiguous; definite substitution {}",
subst.display(*interner)
),
Solution::Ambig(Guidance::Suggested(subst)) => write!(
f,
"Ambiguous; suggested substitution {}",
subst.display(*interner)
),
Solution::Ambig(Guidance::Unknown) => write!(f, "Ambiguous; no inference guidance"),
}
}
}
#[derive(Debug)]
pub enum SubstitutionResult<S> {
Definite(S),
Ambiguous(S),
Floundered,
}
impl<S> SubstitutionResult<S> {
pub fn as_ref(&self) -> SubstitutionResult<&S> {
match self {
SubstitutionResult::Definite(subst) => SubstitutionResult::Definite(subst),
SubstitutionResult::Ambiguous(subst) => SubstitutionResult::Ambiguous(subst),
SubstitutionResult::Floundered => SubstitutionResult::Floundered,
}
}
pub fn map<U, F: FnOnce(S) -> U>(self, f: F) -> SubstitutionResult<U> {
match self {
SubstitutionResult::Definite(subst) => SubstitutionResult::Definite(f(subst)),
SubstitutionResult::Ambiguous(subst) => SubstitutionResult::Ambiguous(f(subst)),
SubstitutionResult::Floundered => SubstitutionResult::Floundered,
}
}
}
impl<S: fmt::Display> fmt::Display for SubstitutionResult<S> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
SubstitutionResult::Definite(subst) => write!(fmt, "{}", subst),
SubstitutionResult::Ambiguous(subst) => write!(fmt, "Ambiguous({})", subst),
SubstitutionResult::Floundered => write!(fmt, "Floundered"),
}
}
}
/// Finds the solution to "goals", or trait queries -- i.e., figures
/// out what sets of types implement which traits. Also, between
/// queries, this struct stores the cached state from previous solver
/// attempts, which can then be re-used later.
pub trait Solver<I: Interner>
where
Self: fmt::Debug,
{
/// Attempts to solve the given goal, which must be in canonical
/// form. Returns a unique solution (if one exists). This will do
/// only as much work towards `goal` as it has to (and that work
/// is cached for future attempts).
///
/// # Parameters
///
/// - `program` -- defines the program clauses in scope.
/// - **Important:** You must supply the same set of program clauses
/// each time you invoke `solve`, as otherwise the cached data may be
/// invalid.
/// - `goal` the goal to solve
///
/// # Returns
///
/// - `None` is the goal cannot be proven.
/// - `Some(solution)` if we succeeded in finding *some* answers,
/// although `solution` may reflect ambiguity and unknowns.
fn solve(
&mut self,
program: &dyn RustIrDatabase<I>,
goal: &UCanonical<InEnvironment<Goal<I>>>,
) -> Option<Solution<I>>;
/// Attempts to solve the given goal, which must be in canonical
/// form. Returns a unique solution (if one exists). This will do
/// only as much work towards `goal` as it has to (and that work
/// is cached for future attempts). In addition, the solving of the
/// goal can be limited by returning `false` from `should_continue`.
///
/// # Parameters
///
/// - `program` -- defines the program clauses in scope.
/// - **Important:** You must supply the same set of program clauses
/// each time you invoke `solve`, as otherwise the cached data may be
/// invalid.
/// - `goal` the goal to solve
/// - `should_continue` if `false` is returned, the no further solving
/// will be done. A `Guidance(Suggested(...))` will be returned a
/// `Solution`, using any answers that were generated up to that point.
///
/// # Returns
///
/// - `None` is the goal cannot be proven.
/// - `Some(solution)` if we succeeded in finding *some* answers,
/// although `solution` may reflect ambiguity and unknowns.
fn solve_limited(
&mut self,
program: &dyn RustIrDatabase<I>,
goal: &UCanonical<InEnvironment<Goal<I>>>,
should_continue: &dyn std::ops::Fn() -> bool,
) -> Option<Solution<I>>;
/// Attempts to solve the given goal, which must be in canonical
/// form. Provides multiple solutions to function `f`. This will do
/// only as much work towards `goal` as it has to (and that work
/// is cached for future attempts).
///
/// # Parameters
///
/// - `program` -- defines the program clauses in scope.
/// - **Important:** You must supply the same set of program clauses
/// each time you invoke `solve`, as otherwise the cached data may be
/// invalid.
/// - `goal` the goal to solve
/// - `f` -- function to proceed solution. New solutions will be generated
/// while function returns `true`.
/// - first argument is solution found
/// - second argument is the next solution present
/// - returns true if next solution should be handled
///
/// # Returns
///
/// - `true` all solutions were processed with the function.
/// - `false` the function returned `false` and solutions were interrupted.
fn solve_multiple(
&mut self,
program: &dyn RustIrDatabase<I>,
goal: &UCanonical<InEnvironment<Goal<I>>>,
f: &mut dyn FnMut(SubstitutionResult<Canonical<ConstrainedSubst<I>>>, bool) -> bool,
) -> bool;
/// A convenience method for when one doesn't need the actual solution,
/// only whether or not one exists.
fn has_unique_solution(
&mut self,
program: &dyn RustIrDatabase<I>,
goal: &UCanonical<InEnvironment<Goal<I>>>,
) -> bool {
match self.solve(program, goal) {
Some(sol) => sol.is_unique(),
None => false,
}
}
}