From 2bab4223939da298593bf1f46154bd8cc0af6070 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 22 Mar 2023 16:23:36 +0000 Subject: [PATCH 1/2] Return nested obligations from canonical response var unification --- .../src/solve/canonical/mod.rs | 22 ++++------ .../src/solve/eval_ctxt.rs | 17 ++++---- .../src/solve/fulfill.rs | 14 ++++++- .../src/traits/query/evaluate_obligation.rs | 10 ++++- .../alias-eq-in-canonical-response.rs | 40 +++++++++++++++++++ 5 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 tests/ui/traits/new-solver/alias-eq-in-canonical-response.rs diff --git a/compiler/rustc_trait_selection/src/solve/canonical/mod.rs b/compiler/rustc_trait_selection/src/solve/canonical/mod.rs index 9d45e78ebab04..efecaf33ef987 100644 --- a/compiler/rustc_trait_selection/src/solve/canonical/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/canonical/mod.rs @@ -99,20 +99,20 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { param_env: ty::ParamEnv<'tcx>, original_values: Vec>, response: CanonicalResponse<'tcx>, - ) -> Result { + ) -> Result<(Certainty, Vec>>), NoSolution> { let substitution = self.compute_query_response_substitution(&original_values, &response); let Response { var_values, external_constraints, certainty } = response.substitute(self.tcx(), &substitution); - self.unify_query_var_values(param_env, &original_values, var_values)?; + let nested_goals = self.unify_query_var_values(param_env, &original_values, var_values)?; // FIXME: implement external constraints. let ExternalConstraintsData { region_constraints, opaque_types: _ } = external_constraints.deref(); self.register_region_constraints(region_constraints); - Ok(certainty) + Ok((certainty, nested_goals)) } /// This returns the substitutions to instantiate the bound variables of @@ -205,21 +205,15 @@ impl<'tcx> EvalCtxt<'_, 'tcx> { param_env: ty::ParamEnv<'tcx>, original_values: &[ty::GenericArg<'tcx>], var_values: CanonicalVarValues<'tcx>, - ) -> Result<(), NoSolution> { + ) -> Result>>, NoSolution> { assert_eq!(original_values.len(), var_values.len()); + + let mut nested_goals = vec![]; for (&orig, response) in iter::zip(original_values, var_values.var_values) { - // This can fail due to the occurs check, see - // `tests/ui/typeck/lazy-norm/equating-projection-cyclically.rs` for an example - // where that can happen. - // - // FIXME: To deal with #105787 I also expect us to emit nested obligations here at - // some point. We can figure out how to deal with this once we actually have - // an ICE. - let nested_goals = self.eq_and_get_goals(param_env, orig, response)?; - assert!(nested_goals.is_empty(), "{nested_goals:?}"); + nested_goals.extend(self.eq_and_get_goals(param_env, orig, response)?); } - Ok(()) + Ok(nested_goals) } fn register_region_constraints(&mut self, region_constraints: &QueryRegionConstraints<'tcx>) { diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs index f1f0132c7b805..e47b5ae21b5af 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt.rs @@ -70,7 +70,7 @@ pub trait InferCtxtEvalExt<'tcx> { fn evaluate_root_goal( &self, goal: Goal<'tcx, ty::Predicate<'tcx>>, - ) -> Result<(bool, Certainty), NoSolution>; + ) -> Result<(bool, Certainty, Vec>>), NoSolution>; } impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> { @@ -78,9 +78,8 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> { fn evaluate_root_goal( &self, goal: Goal<'tcx, ty::Predicate<'tcx>>, - ) -> Result<(bool, Certainty), NoSolution> { + ) -> Result<(bool, Certainty, Vec>>), NoSolution> { let mode = if self.intercrate { SolverMode::Coherence } else { SolverMode::Normal }; - let mut search_graph = search_graph::SearchGraph::new(self.tcx, mode); let mut ecx = EvalCtxt { @@ -152,13 +151,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { &mut self, is_normalizes_to_hack: IsNormalizesToHack, goal: Goal<'tcx, ty::Predicate<'tcx>>, - ) -> Result<(bool, Certainty), NoSolution> { + ) -> Result<(bool, Certainty, Vec>>), NoSolution> { let (orig_values, canonical_goal) = self.canonicalize_goal(goal); let canonical_response = EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?; let has_changed = !canonical_response.value.var_values.is_identity(); - let certainty = self.instantiate_and_apply_query_response( + let (certainty, nested_goals) = self.instantiate_and_apply_query_response( goal.param_env, orig_values, canonical_response, @@ -186,7 +185,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { assert_eq!(certainty, canonical_response.value.certainty); } - Ok((has_changed, certainty)) + Ok((has_changed, certainty, nested_goals)) } fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult<'tcx> { @@ -263,13 +262,14 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { let mut has_changed = Err(Certainty::Yes); if let Some(goal) = goals.normalizes_to_hack_goal.take() { - let (_, certainty) = match this.evaluate_goal( + let (_, certainty, nested_goals) = match this.evaluate_goal( IsNormalizesToHack::Yes, goal.with(this.tcx(), ty::Binder::dummy(goal.predicate)), ) { Ok(r) => r, Err(NoSolution) => return Some(Err(NoSolution)), }; + new_goals.goals.extend(nested_goals); if goal.predicate.projection_ty != this.resolve_vars_if_possible(goal.predicate.projection_ty) @@ -308,11 +308,12 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> { } for nested_goal in goals.goals.drain(..) { - let (changed, certainty) = + let (changed, certainty, nested_goals) = match this.evaluate_goal(IsNormalizesToHack::No, nested_goal) { Ok(result) => result, Err(NoSolution) => return Some(Err(NoSolution)), }; + new_goals.goals.extend(nested_goals); if changed { has_changed = Ok(()); diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index 01f171762ab67..76a2a5879114d 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -1,6 +1,7 @@ use std::mem; use rustc_infer::infer::InferCtxt; +use rustc_infer::traits::Obligation; use rustc_infer::traits::{ query::NoSolution, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes, PredicateObligation, SelectionError, TraitEngine, @@ -61,7 +62,7 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { let mut has_changed = false; for obligation in mem::take(&mut self.obligations) { let goal = obligation.clone().into(); - let (changed, certainty) = match infcx.evaluate_root_goal(goal) { + let (changed, certainty, nested_goals) = match infcx.evaluate_root_goal(goal) { Ok(result) => result, Err(NoSolution) => { errors.push(FulfillmentError { @@ -125,7 +126,16 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { continue; } }; - + // Push any nested goals that we get from unifying our canonical response + // with our obligation onto the fulfillment context. + self.obligations.extend(nested_goals.into_iter().map(|goal| { + Obligation::new( + infcx.tcx, + obligation.cause.clone(), + goal.param_env, + goal.predicate, + ) + })); has_changed |= changed; match certainty { Certainty::Yes => {} diff --git a/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs b/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs index f84b2f4428d1a..af42983bd9cf0 100644 --- a/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs +++ b/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs @@ -81,10 +81,18 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { if self.tcx.trait_solver_next() { self.probe(|snapshot| { - if let Ok((_, certainty)) = + if let Ok((_, certainty, nested_goals)) = self.evaluate_root_goal(Goal::new(self.tcx, param_env, obligation.predicate)) { match certainty { + // If we have nested obligations from instantiating the canonical + // response from this goal, just treat the response as ambiguous. + // + // FIXME(deferred_projection_equality): We need to process this + // in a loop probably... can't be worse than an ICE though + Certainty::Yes if !nested_goals.is_empty() => { + Ok(EvaluationResult::EvaluatedToAmbig) + } Certainty::Yes => { if self.opaque_types_added_in_snapshot(snapshot) { Ok(EvaluationResult::EvaluatedToOkModuloOpaqueTypes) diff --git a/tests/ui/traits/new-solver/alias-eq-in-canonical-response.rs b/tests/ui/traits/new-solver/alias-eq-in-canonical-response.rs new file mode 100644 index 0000000000000..4bfb6323a5366 --- /dev/null +++ b/tests/ui/traits/new-solver/alias-eq-in-canonical-response.rs @@ -0,0 +1,40 @@ +// check-pass +// compile-flags: -Ztrait-solver=next + +trait Foo { + type Gat<'a> + where + Self: 'a; + fn bar(&self) -> Self::Gat<'_>; +} + +enum Option { + Some(T), + None, +} + +impl Option { + fn as_ref(&self) -> Option<&T> { + match self { + Option::Some(t) => Option::Some(t), + Option::None => Option::None, + } + } + + fn map(self, f: impl FnOnce(T) -> U) -> Option { + match self { + Option::Some(t) => Option::Some(f(t)), + Option::None => Option::None, + } + } +} + +impl Foo for Option { + type Gat<'a> = Option<::Gat<'a>> where Self: 'a; + + fn bar(&self) -> Self::Gat<'_> { + self.as_ref().map(Foo::bar) + } +} + +fn main() {} From 1680334928c7bf36d0bf4c3329aa34bbfd343c72 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 23 Mar 2023 19:22:32 +0000 Subject: [PATCH 2/2] Use fulfillment in InferCtxt::evaluate_obligation --- .../src/traits/query/evaluate_obligation.rs | 44 ++++++------------- .../src/traits/select/mod.rs | 1 + 2 files changed, 15 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs b/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs index af42983bd9cf0..edbe2de8105e6 100644 --- a/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs +++ b/compiler/rustc_trait_selection/src/traits/query/evaluate_obligation.rs @@ -1,9 +1,8 @@ -use rustc_middle::traits::solve::{Certainty, Goal, MaybeCause}; +use rustc_infer::traits::{TraitEngine, TraitEngineExt}; use rustc_middle::ty; use crate::infer::canonical::OriginalQueryValues; use crate::infer::InferCtxt; -use crate::solve::InferCtxtEvalExt; use crate::traits::{EvaluationResult, OverflowError, PredicateObligation, SelectionContext}; pub trait InferCtxtExt<'tcx> { @@ -81,35 +80,20 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { if self.tcx.trait_solver_next() { self.probe(|snapshot| { - if let Ok((_, certainty, nested_goals)) = - self.evaluate_root_goal(Goal::new(self.tcx, param_env, obligation.predicate)) - { - match certainty { - // If we have nested obligations from instantiating the canonical - // response from this goal, just treat the response as ambiguous. - // - // FIXME(deferred_projection_equality): We need to process this - // in a loop probably... can't be worse than an ICE though - Certainty::Yes if !nested_goals.is_empty() => { - Ok(EvaluationResult::EvaluatedToAmbig) - } - Certainty::Yes => { - if self.opaque_types_added_in_snapshot(snapshot) { - Ok(EvaluationResult::EvaluatedToOkModuloOpaqueTypes) - } else if self.region_constraints_added_in_snapshot(snapshot).is_some() - { - Ok(EvaluationResult::EvaluatedToOkModuloRegions) - } else { - Ok(EvaluationResult::EvaluatedToOk) - } - } - Certainty::Maybe(MaybeCause::Ambiguity) => { - Ok(EvaluationResult::EvaluatedToAmbig) - } - Certainty::Maybe(MaybeCause::Overflow) => Err(OverflowError::Canonical), - } - } else { + let mut fulfill_cx = crate::solve::FulfillmentCtxt::new(); + fulfill_cx.register_predicate_obligation(self, obligation.clone()); + // True errors + // FIXME(-Ztrait-solver=next): Overflows are reported as ambig here, is that OK? + if !fulfill_cx.select_where_possible(self).is_empty() { Ok(EvaluationResult::EvaluatedToErr) + } else if !fulfill_cx.select_all_or_error(self).is_empty() { + Ok(EvaluationResult::EvaluatedToAmbig) + } else if self.opaque_types_added_in_snapshot(snapshot) { + Ok(EvaluationResult::EvaluatedToOkModuloOpaqueTypes) + } else if self.region_constraints_added_in_snapshot(snapshot).is_some() { + Ok(EvaluationResult::EvaluatedToOkModuloRegions) + } else { + Ok(EvaluationResult::EvaluatedToOk) } }) } else { diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index bffefdf359ad5..4f429f018edf7 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -618,6 +618,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { let mut fulfill_cx = crate::solve::FulfillmentCtxt::new(); fulfill_cx.register_predicate_obligations(self.infcx, predicates); // True errors + // FIXME(-Ztrait-solver=next): Overflows are reported as ambig here, is that OK? if !fulfill_cx.select_where_possible(self.infcx).is_empty() { return Ok(EvaluatedToErr); }