use std::{fmt, ops::ControlFlow}; use hir_def::attrs::AttrFlags; use rustc_next_trait_solver::solve::{GoalEvaluation, SolverDelegateEvalExt}; use rustc_type_ir::{ AliasRelationDirection, AliasTermKind, PredicatePolarity, error::ExpectedFound, inherent::IntoKind as _, solve::{CandidateSource, Certainty, GoalSource, MaybeCause, MaybeInfo, NoSolution}, }; use tracing::{instrument, trace}; use crate::{ Span, db::GeneralConstId, next_solver::{ AliasTerm, AnyImplId, Binder, ClauseKind, Const, ConstKind, DbInterner, HostEffectPredicate, PolyTraitPredicate, PredicateKind, SolverContext, Term, TraitPredicate, Ty, TyKind, TypeError, fulfill::NextSolverError, infer::{ InferCtxt, select::SelectionError, traits::{Obligation, ObligationCause, PredicateObligation, PredicateObligations}, }, inspect::{self, ProofTreeVisitor}, normalize::deeply_normalize_for_diagnostics, }, }; #[derive(Debug)] pub struct FulfillmentError<'db> { pub obligation: PredicateObligation<'db>, pub code: FulfillmentErrorCode<'db>, /// Diagnostics only: the 'root' obligation which resulted in /// the failure to process `obligation`. This is the obligation /// that was initially passed to `register_predicate_obligation` pub root_obligation: PredicateObligation<'db>, } impl<'db> FulfillmentError<'db> { pub fn new( obligation: PredicateObligation<'db>, code: FulfillmentErrorCode<'db>, root_obligation: PredicateObligation<'db>, ) -> FulfillmentError<'db> { FulfillmentError { obligation, code, root_obligation } } pub fn is_true_error(&self) -> bool { match self.code { FulfillmentErrorCode::Select(_) | FulfillmentErrorCode::Project(_) | FulfillmentErrorCode::Subtype(_, _) | FulfillmentErrorCode::ConstEquate(_, _) => true, FulfillmentErrorCode::Cycle(_) | FulfillmentErrorCode::Ambiguity { overflow: _ } => { false } } } } #[derive(Clone)] pub enum FulfillmentErrorCode<'db> { /// Inherently impossible to fulfill; this trait is implemented if and only /// if it is already implemented. Cycle(PredicateObligations<'db>), Select(SelectionError<'db>), Project(MismatchedProjectionTypes<'db>), Subtype(ExpectedFound>, TypeError<'db>), // always comes from a SubtypePredicate ConstEquate(ExpectedFound>, TypeError<'db>), Ambiguity { /// Overflow is only `Some(suggest_recursion_limit)` when using the next generation /// trait solver `-Znext-solver`. With the old solver overflow is eagerly handled by /// emitting a fatal error instead. overflow: Option, }, } impl<'db> fmt::Debug for FulfillmentErrorCode<'db> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { FulfillmentErrorCode::Select(ref e) => write!(f, "{e:?}"), FulfillmentErrorCode::Project(ref e) => write!(f, "{e:?}"), FulfillmentErrorCode::Subtype(ref a, ref b) => { write!(f, "CodeSubtypeError({a:?}, {b:?})") } FulfillmentErrorCode::ConstEquate(ref a, ref b) => { write!(f, "CodeConstEquateError({a:?}, {b:?})") } FulfillmentErrorCode::Ambiguity { overflow: None } => write!(f, "Ambiguity"), FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) } => { write!(f, "Overflow({suggest_increasing_limit})") } FulfillmentErrorCode::Cycle(ref cycle) => write!(f, "Cycle({cycle:?})"), } } } #[derive(Clone)] pub struct MismatchedProjectionTypes<'db> { pub err: TypeError<'db>, } impl<'db> fmt::Debug for MismatchedProjectionTypes<'db> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "MismatchedProjectionTypes({:?})", self.err) } } impl<'db> NextSolverError<'db> { pub fn into_fulfillment_error(self, infcx: &InferCtxt<'db>) -> FulfillmentError<'db> { match self { NextSolverError::TrueError(obligation) => { fulfillment_error_for_no_solution(infcx, obligation) } NextSolverError::Ambiguity(obligation) => { fulfillment_error_for_stalled(infcx, obligation) } NextSolverError::Overflow(obligation) => { fulfillment_error_for_overflow(infcx, obligation) } } } } fn fulfillment_error_for_no_solution<'db>( infcx: &InferCtxt<'db>, root_obligation: PredicateObligation<'db>, ) -> FulfillmentError<'db> { let interner = infcx.interner; let db = interner.db; let obligation = find_best_leaf_obligation(infcx, &root_obligation, false); let code = match obligation.predicate.kind().skip_binder() { PredicateKind::Clause(ClauseKind::Projection(_)) => { FulfillmentErrorCode::Project( // FIXME: This could be a `Sorts` if the term is a type MismatchedProjectionTypes { err: TypeError::Mismatch }, ) } PredicateKind::Clause(ClauseKind::ConstArgHasType(ct, expected_ty)) => { let ct_ty = match ct.kind() { ConstKind::Unevaluated(uv) => { let ct_ty = match uv.def.0 { GeneralConstId::ConstId(konst) => db.value_ty(konst.into()).unwrap(), GeneralConstId::StaticId(statik) => db.value_ty(statik.into()).unwrap(), GeneralConstId::AnonConstId(konst) => konst.loc(db).ty.get(), }; ct_ty.instantiate(interner, uv.args).skip_norm_wip() } ConstKind::Param(param_ct) => param_ct.find_const_ty_from_env(obligation.param_env), ConstKind::Value(cv) => cv.ty, kind => panic!( "ConstArgHasWrongType failed but we don't know how to compute type for {kind:?}" ), }; FulfillmentErrorCode::Select(SelectionError::ConstArgHasWrongType { ct, ct_ty, expected_ty, }) } PredicateKind::NormalizesTo(..) => { FulfillmentErrorCode::Project(MismatchedProjectionTypes { err: TypeError::Mismatch }) } PredicateKind::AliasRelate(_, _, _) => { FulfillmentErrorCode::Project(MismatchedProjectionTypes { err: TypeError::Mismatch }) } PredicateKind::Subtype(pred) => { let (a, b) = infcx.enter_forall_and_leak_universe( obligation.predicate.kind().rebind((pred.a, pred.b)), ); let expected_found = ExpectedFound::new(a, b); FulfillmentErrorCode::Subtype(expected_found, TypeError::Sorts(expected_found)) } PredicateKind::Coerce(pred) => { let (a, b) = infcx.enter_forall_and_leak_universe( obligation.predicate.kind().rebind((pred.a, pred.b)), ); let expected_found = ExpectedFound::new(b, a); FulfillmentErrorCode::Subtype(expected_found, TypeError::Sorts(expected_found)) } PredicateKind::Clause(_) | PredicateKind::DynCompatible(_) | PredicateKind::Ambiguous => { FulfillmentErrorCode::Select(SelectionError::Unimplemented) } PredicateKind::ConstEquate(..) => { panic!("unexpected goal: {obligation:?}") } }; FulfillmentError { obligation, code, root_obligation } } fn fulfillment_error_for_stalled<'db>( infcx: &InferCtxt<'db>, root_obligation: PredicateObligation<'db>, ) -> FulfillmentError<'db> { let (code, refine_obligation) = infcx.probe(|_| { match <&SolverContext<'db>>::from(infcx).evaluate_root_goal( root_obligation.as_goal(), root_obligation.cause.span(), None, ) { Ok(GoalEvaluation { certainty: Certainty::Maybe(MaybeInfo { cause: MaybeCause::Ambiguity, .. }), .. }) => (FulfillmentErrorCode::Ambiguity { overflow: None }, true), Ok(GoalEvaluation { certainty: Certainty::Maybe(MaybeInfo { cause: MaybeCause::Overflow { suggest_increasing_limit, keep_constraints: _ }, .. }), .. }) => ( FulfillmentErrorCode::Ambiguity { overflow: Some(suggest_increasing_limit) }, // Don't look into overflows because we treat overflows weirdly anyways. // We discard the inference constraints from overflowing goals, so // recomputing the goal again during `find_best_leaf_obligation` may apply // inference guidance that makes other goals go from ambig -> pass, for example. // // FIXME: We should probably just look into overflows here. false, ), Ok(GoalEvaluation { certainty: Certainty::Yes, .. }) => { panic!( "did not expect successful goal when collecting ambiguity errors for `{:?}`", infcx.resolve_vars_if_possible(root_obligation.predicate), ) } Err(_) => { panic!( "did not expect selection error when collecting ambiguity errors for `{:?}`", infcx.resolve_vars_if_possible(root_obligation.predicate), ) } } }); FulfillmentError { obligation: if refine_obligation { find_best_leaf_obligation(infcx, &root_obligation, true) } else { root_obligation.clone() }, code, root_obligation, } } fn fulfillment_error_for_overflow<'db>( infcx: &InferCtxt<'db>, root_obligation: PredicateObligation<'db>, ) -> FulfillmentError<'db> { FulfillmentError { obligation: find_best_leaf_obligation(infcx, &root_obligation, true), code: FulfillmentErrorCode::Ambiguity { overflow: Some(true) }, root_obligation, } } #[instrument(level = "debug", skip(infcx), ret)] fn find_best_leaf_obligation<'db>( infcx: &InferCtxt<'db>, obligation: &PredicateObligation<'db>, consider_ambiguities: bool, ) -> PredicateObligation<'db> { let obligation = infcx.resolve_vars_if_possible(obligation.clone()); // FIXME: we use a probe here as the `BestObligation` visitor does not // check whether it uses candidates which get shadowed by where-bounds. // // We should probably fix the visitor to not do so instead, as this also // means the leaf obligation may be incorrect. let obligation = infcx .fudge_inference_if_ok(|| { infcx .visit_proof_tree( obligation.as_goal(), &mut BestObligation { obligation: obligation.clone(), consider_ambiguities }, ) .break_value() .ok_or(()) // walk around the fact that the cause in `Obligation` is ignored by folders so that // we can properly fudge the infer vars in cause code. .map(|o| (o.cause, o)) }) .map(|(cause, o)| PredicateObligation { cause, ..o }) .unwrap_or(obligation); deeply_normalize_for_diagnostics(infcx, obligation.param_env, obligation) } struct BestObligation<'db> { obligation: PredicateObligation<'db>, consider_ambiguities: bool, } impl<'db> BestObligation<'db> { fn with_derived_obligation( &mut self, derived_obligation: PredicateObligation<'db>, and_then: impl FnOnce(&mut Self) -> >::Result, ) -> >::Result { let old_obligation = std::mem::replace(&mut self.obligation, derived_obligation); let res = and_then(self); self.obligation = old_obligation; res } /// Filter out the candidates that aren't interesting to visit for the /// purposes of reporting errors. For ambiguities, we only consider /// candidates that may hold. For errors, we only consider candidates that /// *don't* hold and which have impl-where clauses that also don't hold. fn non_trivial_candidates<'a>( &self, goal: &'a inspect::InspectGoal<'a, 'db>, ) -> Vec> { let mut candidates = goal.candidates(); match self.consider_ambiguities { true => { // If we have an ambiguous obligation, we must consider *all* candidates // that hold, or else we may guide inference causing other goals to go // from ambig -> pass/fail. candidates.retain(|candidate| candidate.result().is_ok()); } false => { // We always handle rigid alias candidates separately as we may not add them for // aliases whose trait bound doesn't hold. candidates.retain(|c| !matches!(c.kind(), inspect::ProbeKind::RigidAlias { .. })); // If we have >1 candidate, one may still be due to "boring" reasons, like // an alias-relate that failed to hold when deeply evaluated. We really // don't care about reasons like this. if candidates.len() > 1 { candidates.retain(|candidate| { goal.infcx().probe(|_| { candidate.instantiate_nested_goals(self.span()).iter().any( |nested_goal| { matches!( nested_goal.source(), GoalSource::ImplWhereBound | GoalSource::AliasBoundConstCondition | GoalSource::AliasWellFormed ) && nested_goal.result().is_err() }, ) }) }); } } } candidates } /// HACK: We walk the nested obligations for a well-formed arg manually, /// since there's nontrivial logic in `wf.rs` to set up an obligation cause. /// Ideally we'd be able to track this better. fn visit_well_formed_goal( &mut self, candidate: &inspect::InspectCandidate<'_, 'db>, term: Term<'db>, ) -> ControlFlow> { let _ = (candidate, term); // FIXME: rustc does this, but we don't process WF obligations yet: // let infcx = candidate.goal().infcx(); // let param_env = candidate.goal().goal().param_env; // let body_id = self.obligation.cause.body_id; // for obligation in wf::unnormalized_obligations(infcx, param_env, term, self.span(), body_id) // .into_iter() // .flatten() // { // let nested_goal = candidate.instantiate_proof_tree_for_nested_goal( // GoalSource::Misc, // obligation.as_goal(), // self.span(), // ); // // Skip nested goals that aren't the *reason* for our goal's failure. // match (self.consider_ambiguities, nested_goal.result()) { // (true, Ok(Certainty::Maybe { cause: MaybeCause::Ambiguity, .. })) // | (false, Err(_)) => {} // _ => continue, // } // self.with_derived_obligation(obligation, |this| nested_goal.visit_with(this))?; // } ControlFlow::Break(self.obligation.clone()) } /// If a normalization of an associated item or a trait goal fails without trying any /// candidates it's likely that normalizing its self type failed. We manually detect /// such cases here. fn detect_error_in_self_ty_normalization( &mut self, goal: &inspect::InspectGoal<'_, 'db>, self_ty: Ty<'db>, ) -> ControlFlow> { assert!(!self.consider_ambiguities); let interner = goal.infcx().interner; if let TyKind::Alias(..) = self_ty.kind() { let infer_term = goal.infcx().next_ty_var(self.obligation.cause.span()); let pred = PredicateKind::AliasRelate( self_ty.into(), infer_term.into(), AliasRelationDirection::Equate, ); let obligation = Obligation::new(interner, self.obligation.cause, goal.goal().param_env, pred); self.with_derived_obligation(obligation, |this| { goal.infcx().visit_proof_tree_at_depth( goal.goal().with(interner, pred), goal.depth() + 1, this, ) }) } else { ControlFlow::Continue(()) } } /// When a higher-ranked projection goal fails, check that the corresponding /// higher-ranked trait goal holds or not. This is because the process of /// instantiating and then re-canonicalizing the binder of the projection goal /// forces us to be unable to see that the leak check failed in the nested /// `NormalizesTo` goal, so we don't fall back to the rigid projection check /// that should catch when a projection goal fails due to an unsatisfied trait /// goal. fn detect_trait_error_in_higher_ranked_projection( &mut self, goal: &inspect::InspectGoal<'_, 'db>, ) -> ControlFlow> { let interner = goal.infcx().interner; if let Some(projection_clause) = goal.goal().predicate.as_projection_clause() && !projection_clause.bound_vars().is_empty() { let pred = projection_clause.map_bound(|proj| proj.projection_term.trait_ref(interner)); let obligation = Obligation::new( interner, self.obligation.cause, goal.goal().param_env, deeply_normalize_for_diagnostics(goal.infcx(), goal.goal().param_env, pred), ); self.with_derived_obligation(obligation, |this| { goal.infcx().visit_proof_tree_at_depth( goal.goal().with(interner, pred), goal.depth() + 1, this, ) }) } else { ControlFlow::Continue(()) } } /// It is likely that `NormalizesTo` failed without any applicable candidates /// because the alias is not well-formed. /// /// As we only enter `RigidAlias` candidates if the trait bound of the associated type /// holds, we discard these candidates in `non_trivial_candidates` and always manually /// check this here. fn detect_non_well_formed_assoc_item( &mut self, goal: &inspect::InspectGoal<'_, 'db>, alias: AliasTerm<'db>, ) -> ControlFlow> { let interner = goal.infcx().interner; let obligation = Obligation::new( interner, self.obligation.cause, goal.goal().param_env, alias.trait_ref(interner), ); self.with_derived_obligation(obligation, |this| { goal.infcx().visit_proof_tree_at_depth( goal.goal().with(interner, alias.trait_ref(interner)), goal.depth() + 1, this, ) }) } /// If we have no candidates, then it's likely that there is a /// non-well-formed alias in the goal. fn detect_error_from_empty_candidates( &mut self, goal: &inspect::InspectGoal<'_, 'db>, ) -> ControlFlow> { let interner = goal.infcx().interner; let pred_kind = goal.goal().predicate.kind(); match pred_kind.no_bound_vars() { Some(PredicateKind::Clause(ClauseKind::Trait(pred))) => { self.detect_error_in_self_ty_normalization(goal, pred.self_ty())?; } Some(PredicateKind::NormalizesTo(pred)) if let AliasTermKind::ProjectionTy { .. } | AliasTermKind::ProjectionConst { .. } = pred.alias.kind(interner) => { self.detect_error_in_self_ty_normalization(goal, pred.alias.self_ty())?; self.detect_non_well_formed_assoc_item(goal, pred.alias)?; } Some(_) | None => {} } ControlFlow::Break(self.obligation.clone()) } } impl<'db> ProofTreeVisitor<'db> for BestObligation<'db> { type Result = ControlFlow>; fn span(&self) -> Span { self.obligation.cause.span() } #[instrument(level = "trace", skip(self, goal), fields(goal = ?goal.goal()))] fn visit_goal(&mut self, goal: &inspect::InspectGoal<'_, 'db>) -> Self::Result { let interner = goal.infcx().interner; // Skip goals that aren't the *reason* for our goal's failure. match (self.consider_ambiguities, goal.result()) { (true, Ok(Certainty::Maybe(MaybeInfo { cause: MaybeCause::Ambiguity, .. }))) | (false, Err(_)) => {} _ => return ControlFlow::Continue(()), } let pred = goal.goal().predicate; let candidates = self.non_trivial_candidates(goal); let candidate = match candidates.as_slice() { [candidate] => candidate, [] => return self.detect_error_from_empty_candidates(goal), _ => return ControlFlow::Break(self.obligation.clone()), }; // Don't walk into impls that have `do_not_recommend`. if let inspect::ProbeKind::TraitCandidate { source: CandidateSource::Impl(impl_def_id), result: _, } = candidate.kind() && let AnyImplId::ImplId(impl_def_id) = impl_def_id && AttrFlags::query(interner.db, impl_def_id.into()) .contains(AttrFlags::DIAGNOSTIC_DO_NOT_RECOMMEND) { trace!("#[diagnostic::do_not_recommend] -> exit"); return ControlFlow::Break(self.obligation.clone()); } // FIXME: Also, what about considering >1 layer up the stack? May be necessary // for normalizes-to. let child_mode = match pred.kind().skip_binder() { PredicateKind::Clause(ClauseKind::Trait(trait_pred)) => { ChildMode::Trait(pred.kind().rebind(trait_pred)) } PredicateKind::Clause(ClauseKind::HostEffect(host_pred)) => { ChildMode::Host(pred.kind().rebind(host_pred)) } PredicateKind::NormalizesTo(normalizes_to) if matches!( normalizes_to.alias.kind(interner), AliasTermKind::ProjectionTy { .. } | AliasTermKind::ProjectionConst { .. } ) => { ChildMode::Trait(pred.kind().rebind(TraitPredicate { trait_ref: normalizes_to.alias.trait_ref(interner), polarity: PredicatePolarity::Positive, })) } PredicateKind::Clause(ClauseKind::WellFormed(term)) => { return self.visit_well_formed_goal(candidate, term); } _ => ChildMode::PassThrough, }; let nested_goals = candidate.instantiate_nested_goals(self.span()); // If the candidate requires some `T: FnPtr` bound which does not hold should not be treated as // an actual candidate, instead we should treat them as if the impl was never considered to // have potentially applied. As if `impl Trait for for<..> fn(..A) -> R` was written // instead of `impl Trait for T`. // // We do this as a separate loop so that we do not choose to tell the user about some nested // goal before we encounter a `T: FnPtr` nested goal. for nested_goal in &nested_goals { if let Some(poly_trait_pred) = nested_goal.goal().predicate.as_trait_clause() && Some(poly_trait_pred.def_id().0) == interner.lang_items().FnPtrTrait && let Err(NoSolution) = nested_goal.result() { return ControlFlow::Break(self.obligation.clone()); } } let mut impl_where_bound_count = 0; for nested_goal in nested_goals { trace!(nested_goal = ?(nested_goal.goal(), nested_goal.source(), nested_goal.result())); let nested_pred = nested_goal.goal().predicate; let make_obligation = |cause| Obligation { cause, param_env: nested_goal.goal().param_env, predicate: nested_pred, recursion_depth: self.obligation.recursion_depth + 1, }; let obligation; match (child_mode, nested_goal.source()) { ( ChildMode::Trait(_) | ChildMode::Host(_), GoalSource::Misc | GoalSource::TypeRelating | GoalSource::NormalizeGoal(_), ) => { continue; } (ChildMode::Trait(parent_trait_pred), GoalSource::ImplWhereBound) => { obligation = make_obligation(derive_cause( interner, candidate.kind(), self.obligation.cause, impl_where_bound_count, parent_trait_pred, )); impl_where_bound_count += 1; } ( ChildMode::Host(parent_host_pred), GoalSource::ImplWhereBound | GoalSource::AliasBoundConstCondition, ) => { obligation = make_obligation(derive_host_cause( interner, candidate.kind(), self.obligation.cause, impl_where_bound_count, parent_host_pred, )); impl_where_bound_count += 1; } (ChildMode::PassThrough, _) | (_, GoalSource::AliasWellFormed | GoalSource::AliasBoundConstCondition) => { obligation = make_obligation(self.obligation.cause); } } self.with_derived_obligation(obligation, |this| nested_goal.visit_with(this))?; } // alias-relate may fail because the lhs or rhs can't be normalized, // and therefore is treated as rigid. if let Some(PredicateKind::AliasRelate(lhs, rhs, _)) = pred.kind().no_bound_vars() { goal.infcx().visit_proof_tree_at_depth( goal.goal().with(interner, ClauseKind::WellFormed(lhs)), goal.depth() + 1, self, )?; goal.infcx().visit_proof_tree_at_depth( goal.goal().with(interner, ClauseKind::WellFormed(rhs)), goal.depth() + 1, self, )?; } self.detect_trait_error_in_higher_ranked_projection(goal)?; ControlFlow::Break(self.obligation.clone()) } } #[derive(Debug, Copy, Clone)] enum ChildMode<'db> { // Try to derive an `ObligationCause::{ImplDerived,BuiltinDerived}`, // and skip all `GoalSource::Misc`, which represent useless obligations // such as alias-eq which may not hold. Trait(PolyTraitPredicate<'db>), // Try to derive an `ObligationCause::{ImplDerived,BuiltinDerived}`, // and skip all `GoalSource::Misc`, which represent useless obligations // such as alias-eq which may not hold. Host(Binder<'db, HostEffectPredicate<'db>>), // Skip trying to derive an `ObligationCause` from this obligation, and // report *all* sub-obligations as if they came directly from the parent // obligation. PassThrough, } fn derive_cause<'db>( _interner: DbInterner<'db>, _candidate_kind: inspect::ProbeKind>, cause: ObligationCause, _idx: usize, _parent_trait_pred: PolyTraitPredicate<'db>, ) -> ObligationCause { cause } fn derive_host_cause<'db>( _interner: DbInterner<'db>, _candidate_kind: inspect::ProbeKind>, cause: ObligationCause, _idx: usize, _parent_host_pred: Binder<'db, HostEffectPredicate<'db>>, ) -> ObligationCause { cause }