Skip to content

Commit

Permalink
Rollup merge of rust-lang#124418 - compiler-errors:better-cause, r=lcnr
Browse files Browse the repository at this point in the history
Use a proof tree visitor to refine the `Obligation` for error reporting in new solver

With the magic of `ProofTreeVisitor`, we can close the gap that we have on `ObligationCause`s being not as descriptive in the new trait solver.

r? lcnr

Needs some work and obviously documentation.
  • Loading branch information
compiler-errors authored May 4, 2024
2 parents 09cd00f + 34e91ec commit 8941e00
Show file tree
Hide file tree
Showing 39 changed files with 474 additions and 102 deletions.
2 changes: 2 additions & 0 deletions compiler/rustc_middle/src/traits/solve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,8 @@ pub enum GoalSource {
/// they are from an impl where-clause. This is necessary due to
/// backwards compatability, cc trait-system-refactor-initiatitive#70.
ImplWhereBound,
/// Instantiating a higher-ranked goal and re-proving it.
InstantiateHigherRanked,
}

/// Possible ways the given goal can be proven.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/traits/solve/inspect/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
let source = match source {
GoalSource::Misc => "misc",
GoalSource::ImplWhereBound => "impl where-bound",
GoalSource::InstantiateHigherRanked => "higher-ranked goal",
};
writeln!(this.f, "ADDED GOAL ({source}): {goal:?}")?
}
Expand Down
15 changes: 7 additions & 8 deletions compiler/rustc_trait_selection/src/solve/assembly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ pub(super) trait GoalKind<'tcx>:
/// goal by equating it with the assumption.
fn probe_and_consider_implied_clause(
ecx: &mut EvalCtxt<'_, 'tcx>,
source: CandidateSource,
parent_source: CandidateSource,
goal: Goal<'tcx, Self>,
assumption: ty::Clause<'tcx>,
requirements: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
requirements: impl IntoIterator<Item = (GoalSource, Goal<'tcx, ty::Predicate<'tcx>>)>,
) -> Result<Candidate<'tcx>, NoSolution> {
Self::probe_and_match_goal_against_assumption(ecx, source, goal, assumption, |ecx| {
// FIXME(-Znext-solver=coinductive): check whether this should be
// `GoalSource::ImplWhereBound` for any caller.
ecx.add_goals(GoalSource::Misc, requirements);
Self::probe_and_match_goal_against_assumption(ecx, parent_source, goal, assumption, |ecx| {
for (nested_source, goal) in requirements {
ecx.add_goal(nested_source, goal);
}
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
Expand All @@ -85,9 +85,8 @@ pub(super) trait GoalKind<'tcx>:
let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else {
bug!("expected object type in `probe_and_consider_object_bound_candidate`");
};
// FIXME(-Znext-solver=coinductive): Should this be `GoalSource::ImplWhereBound`?
ecx.add_goals(
GoalSource::Misc,
GoalSource::ImplWhereBound,
structural_traits::predicates_for_object_candidate(
ecx,
goal.param_env,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
&mut self,
certainty: Certainty,
) -> QueryResult<'tcx> {
self.inspect.make_canonical_response(certainty);

let goals_certainty = self.try_evaluate_added_goals()?;
assert_eq!(
self.tainted,
Expand All @@ -98,8 +100,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
previous call to `try_evaluate_added_goals!`"
);

self.inspect.make_canonical_response(certainty);

// When normalizing, we've replaced the expected term with an unconstrained
// inference variable. This means that we dropped information which could
// have been important. We handle this by instead returning the nested goals
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
} else {
self.infcx.enter_forall(kind, |kind| {
let goal = goal.with(self.tcx(), ty::Binder::dummy(kind));
self.add_goal(GoalSource::Misc, goal);
self.add_goal(GoalSource::InstantiateHigherRanked, goal);
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
}
Expand Down
156 changes: 147 additions & 9 deletions compiler/rustc_trait_selection/src/solve/fulfill.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
use std::mem;
use std::ops::ControlFlow;

use rustc_infer::infer::InferCtxt;
use rustc_infer::traits::solve::MaybeCause;
use rustc_infer::traits::query::NoSolution;
use rustc_infer::traits::solve::inspect::ProbeKind;
use rustc_infer::traits::solve::{CandidateSource, GoalSource, MaybeCause};
use rustc_infer::traits::{
query::NoSolution, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes,
PredicateObligation, SelectionError, TraitEngine,
self, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes, Obligation,
ObligationCause, PredicateObligation, SelectionError, TraitEngine,
};
use rustc_middle::ty;
use rustc_middle::ty::error::{ExpectedFound, TypeError};
use rustc_middle::ty::{self, TyCtxt};

use super::eval_ctxt::GenerateProofTree;
use super::inspect::{ProofTreeInferCtxtExt, ProofTreeVisitor};
use super::{Certainty, InferCtxtEvalExt};

/// A trait engine using the new trait solver.
Expand Down Expand Up @@ -133,9 +137,9 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
.collect();

errors.extend(self.obligations.overflowed.drain(..).map(|obligation| FulfillmentError {
root_obligation: obligation.clone(),
obligation: find_best_leaf_obligation(infcx, &obligation),
code: FulfillmentErrorCode::Ambiguity { overflow: Some(true) },
obligation,
root_obligation: obligation,
}));

errors
Expand Down Expand Up @@ -192,8 +196,10 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {

fn fulfillment_error_for_no_solution<'tcx>(
infcx: &InferCtxt<'tcx>,
obligation: PredicateObligation<'tcx>,
root_obligation: PredicateObligation<'tcx>,
) -> FulfillmentError<'tcx> {
let obligation = find_best_leaf_obligation(infcx, &root_obligation);

let code = match obligation.predicate.kind().skip_binder() {
ty::PredicateKind::Clause(ty::ClauseKind::Projection(_)) => {
FulfillmentErrorCode::ProjectionError(
Expand Down Expand Up @@ -234,7 +240,8 @@ fn fulfillment_error_for_no_solution<'tcx>(
bug!("unexpected goal: {obligation:?}")
}
};
FulfillmentError { root_obligation: obligation.clone(), code, obligation }

FulfillmentError { obligation, code, root_obligation }
}

fn fulfillment_error_for_stalled<'tcx>(
Expand All @@ -258,5 +265,136 @@ fn fulfillment_error_for_stalled<'tcx>(
}
});

FulfillmentError { obligation: obligation.clone(), code, root_obligation: obligation }
FulfillmentError {
obligation: find_best_leaf_obligation(infcx, &obligation),
code,
root_obligation: obligation,
}
}

fn find_best_leaf_obligation<'tcx>(
infcx: &InferCtxt<'tcx>,
obligation: &PredicateObligation<'tcx>,
) -> PredicateObligation<'tcx> {
let obligation = infcx.resolve_vars_if_possible(obligation.clone());
infcx
.visit_proof_tree(
obligation.clone().into(),
&mut BestObligation { obligation: obligation.clone() },
)
.break_value()
.unwrap_or(obligation)
}

struct BestObligation<'tcx> {
obligation: PredicateObligation<'tcx>,
}

impl<'tcx> BestObligation<'tcx> {
fn with_derived_obligation(
&mut self,
derived_obligation: PredicateObligation<'tcx>,
and_then: impl FnOnce(&mut Self) -> <Self as ProofTreeVisitor<'tcx>>::Result,
) -> <Self as ProofTreeVisitor<'tcx>>::Result {
let old_obligation = std::mem::replace(&mut self.obligation, derived_obligation);
let res = and_then(self);
self.obligation = old_obligation;
res
}
}

impl<'tcx> ProofTreeVisitor<'tcx> for BestObligation<'tcx> {
type Result = ControlFlow<PredicateObligation<'tcx>>;

fn span(&self) -> rustc_span::Span {
self.obligation.cause.span
}

fn visit_goal(&mut self, goal: &super::inspect::InspectGoal<'_, 'tcx>) -> Self::Result {
// FIXME: Throw out candidates that have no failing WC and >0 failing misc goal.
// This most likely means that the goal just didn't unify at all, e.g. a param
// candidate with an alias in it.
let candidates = goal.candidates();

let [candidate] = candidates.as_slice() else {
return ControlFlow::Break(self.obligation.clone());
};

// FIXME: Could we extract a trait ref from a projection here too?
// FIXME: Also, what about considering >1 layer up the stack? May be necessary
// for normalizes-to.
let Some(parent_trait_pred) = goal.goal().predicate.to_opt_poly_trait_pred() else {
return ControlFlow::Break(self.obligation.clone());
};

let tcx = goal.infcx().tcx;
let mut impl_where_bound_count = 0;
for nested_goal in candidate.instantiate_nested_goals(self.span()) {
let obligation;
match nested_goal.source() {
GoalSource::Misc => {
continue;
}
GoalSource::ImplWhereBound => {
obligation = Obligation {
cause: derive_cause(
tcx,
candidate.kind(),
self.obligation.cause.clone(),
impl_where_bound_count,
parent_trait_pred,
),
param_env: nested_goal.goal().param_env,
predicate: nested_goal.goal().predicate,
recursion_depth: self.obligation.recursion_depth + 1,
};
impl_where_bound_count += 1;
}
GoalSource::InstantiateHigherRanked => {
obligation = self.obligation.clone();
}
}

// Skip nested goals that hold.
//FIXME: We should change the max allowed certainty based on if we're
// visiting an ambiguity or error obligation.
if matches!(nested_goal.result(), Ok(Certainty::Yes)) {
continue;
}

self.with_derived_obligation(obligation, |this| nested_goal.visit_with(this))?;
}

ControlFlow::Break(self.obligation.clone())
}
}

fn derive_cause<'tcx>(
tcx: TyCtxt<'tcx>,
candidate_kind: ProbeKind<'tcx>,
mut cause: ObligationCause<'tcx>,
idx: usize,
parent_trait_pred: ty::PolyTraitPredicate<'tcx>,
) -> ObligationCause<'tcx> {
match candidate_kind {
ProbeKind::TraitCandidate { source: CandidateSource::Impl(impl_def_id), result: _ } => {
if let Some((_, span)) =
tcx.predicates_of(impl_def_id).instantiate_identity(tcx).iter().nth(idx)
{
cause = cause.derived_cause(parent_trait_pred, |derived| {
traits::ImplDerivedObligation(Box::new(traits::ImplDerivedObligationCause {
derived,
impl_or_alias_def_id: impl_def_id,
impl_def_predicate_index: Some(idx),
span,
}))
})
}
}
ProbeKind::TraitCandidate { source: CandidateSource::BuiltinImpl(..), result: _ } => {
cause = cause.derived_cause(parent_trait_pred, traits::BuiltinDerivedObligation);
}
_ => {}
};
cause
}
Loading

0 comments on commit 8941e00

Please sign in to comment.