Rollup merge of #109493 - compiler-errors:new-solver-vars-obligations, r=lcnr

Return nested obligations from canonical response var unification

Handle alias-eq obligations being emitted from `instantiate_and_apply_query_response` in:
* `EvalCtxt` - by processing the nested obligations in the next loop by `new_goals`
* `FulfillCtxt` - by adding the nested obligations to the fulfillment's pending obligations
* `InferCtxt::evaluate_obligation` - ~~by returning `EvaluationResult::EvaluatedToAmbig` (boo 👎, see the FIXME)~~ same behavior as above, since we use fulfillment and `select_where_possible`

The only one that's truly sketchy is `evaluate_obligation`, but it's not hard to modify this behavior moving forward.

From #109037, I think a smaller repro could be crafted if I were smarter, but I am not, so I just took this from #105878.

r? `@lcnr` cc `@BoxyUwU`
This commit is contained in:
Matthias Krüger 2023-03-24 01:22:06 +01:00 committed by GitHub
commit 1c7ef3b483
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 84 additions and 46 deletions

View file

@ -99,20 +99,20 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
param_env: ty::ParamEnv<'tcx>, param_env: ty::ParamEnv<'tcx>,
original_values: Vec<ty::GenericArg<'tcx>>, original_values: Vec<ty::GenericArg<'tcx>>,
response: CanonicalResponse<'tcx>, response: CanonicalResponse<'tcx>,
) -> Result<Certainty, NoSolution> { ) -> Result<(Certainty, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), NoSolution> {
let substitution = self.compute_query_response_substitution(&original_values, &response); let substitution = self.compute_query_response_substitution(&original_values, &response);
let Response { var_values, external_constraints, certainty } = let Response { var_values, external_constraints, certainty } =
response.substitute(self.tcx(), &substitution); 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. // FIXME: implement external constraints.
let ExternalConstraintsData { region_constraints, opaque_types: _ } = let ExternalConstraintsData { region_constraints, opaque_types: _ } =
external_constraints.deref(); external_constraints.deref();
self.register_region_constraints(region_constraints); self.register_region_constraints(region_constraints);
Ok(certainty) Ok((certainty, nested_goals))
} }
/// This returns the substitutions to instantiate the bound variables of /// This returns the substitutions to instantiate the bound variables of
@ -205,21 +205,15 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
param_env: ty::ParamEnv<'tcx>, param_env: ty::ParamEnv<'tcx>,
original_values: &[ty::GenericArg<'tcx>], original_values: &[ty::GenericArg<'tcx>],
var_values: CanonicalVarValues<'tcx>, var_values: CanonicalVarValues<'tcx>,
) -> Result<(), NoSolution> { ) -> Result<Vec<Goal<'tcx, ty::Predicate<'tcx>>>, NoSolution> {
assert_eq!(original_values.len(), var_values.len()); 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) { for (&orig, response) in iter::zip(original_values, var_values.var_values) {
// This can fail due to the occurs check, see nested_goals.extend(self.eq_and_get_goals(param_env, orig, response)?);
// `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:?}");
} }
Ok(()) Ok(nested_goals)
} }
fn register_region_constraints(&mut self, region_constraints: &QueryRegionConstraints<'tcx>) { fn register_region_constraints(&mut self, region_constraints: &QueryRegionConstraints<'tcx>) {

View file

@ -70,7 +70,7 @@ pub trait InferCtxtEvalExt<'tcx> {
fn evaluate_root_goal( fn evaluate_root_goal(
&self, &self,
goal: Goal<'tcx, ty::Predicate<'tcx>>, goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Result<(bool, Certainty), NoSolution>; ) -> Result<(bool, Certainty, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), NoSolution>;
} }
impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> { impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
@ -78,9 +78,8 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
fn evaluate_root_goal( fn evaluate_root_goal(
&self, &self,
goal: Goal<'tcx, ty::Predicate<'tcx>>, goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Result<(bool, Certainty), NoSolution> { ) -> Result<(bool, Certainty, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), NoSolution> {
let mode = if self.intercrate { SolverMode::Coherence } else { SolverMode::Normal }; let mode = if self.intercrate { SolverMode::Coherence } else { SolverMode::Normal };
let mut search_graph = search_graph::SearchGraph::new(self.tcx, mode); let mut search_graph = search_graph::SearchGraph::new(self.tcx, mode);
let mut ecx = EvalCtxt { let mut ecx = EvalCtxt {
@ -152,13 +151,13 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
&mut self, &mut self,
is_normalizes_to_hack: IsNormalizesToHack, is_normalizes_to_hack: IsNormalizesToHack,
goal: Goal<'tcx, ty::Predicate<'tcx>>, goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Result<(bool, Certainty), NoSolution> { ) -> Result<(bool, Certainty, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), NoSolution> {
let (orig_values, canonical_goal) = self.canonicalize_goal(goal); let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
let canonical_response = let canonical_response =
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?; EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
let has_changed = !canonical_response.value.var_values.is_identity(); 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, goal.param_env,
orig_values, orig_values,
canonical_response, canonical_response,
@ -186,7 +185,7 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
assert_eq!(certainty, canonical_response.value.certainty); 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> { 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); let mut has_changed = Err(Certainty::Yes);
if let Some(goal) = goals.normalizes_to_hack_goal.take() { 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, IsNormalizesToHack::Yes,
goal.with(this.tcx(), ty::Binder::dummy(goal.predicate)), goal.with(this.tcx(), ty::Binder::dummy(goal.predicate)),
) { ) {
Ok(r) => r, Ok(r) => r,
Err(NoSolution) => return Some(Err(NoSolution)), Err(NoSolution) => return Some(Err(NoSolution)),
}; };
new_goals.goals.extend(nested_goals);
if goal.predicate.projection_ty if goal.predicate.projection_ty
!= this.resolve_vars_if_possible(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(..) { for nested_goal in goals.goals.drain(..) {
let (changed, certainty) = let (changed, certainty, nested_goals) =
match this.evaluate_goal(IsNormalizesToHack::No, nested_goal) { match this.evaluate_goal(IsNormalizesToHack::No, nested_goal) {
Ok(result) => result, Ok(result) => result,
Err(NoSolution) => return Some(Err(NoSolution)), Err(NoSolution) => return Some(Err(NoSolution)),
}; };
new_goals.goals.extend(nested_goals);
if changed { if changed {
has_changed = Ok(()); has_changed = Ok(());

View file

@ -1,6 +1,7 @@
use std::mem; use std::mem;
use rustc_infer::infer::InferCtxt; use rustc_infer::infer::InferCtxt;
use rustc_infer::traits::Obligation;
use rustc_infer::traits::{ use rustc_infer::traits::{
query::NoSolution, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes, query::NoSolution, FulfillmentError, FulfillmentErrorCode, MismatchedProjectionTypes,
PredicateObligation, SelectionError, TraitEngine, PredicateObligation, SelectionError, TraitEngine,
@ -61,7 +62,7 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
let mut has_changed = false; let mut has_changed = false;
for obligation in mem::take(&mut self.obligations) { for obligation in mem::take(&mut self.obligations) {
let goal = obligation.clone().into(); 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, Ok(result) => result,
Err(NoSolution) => { Err(NoSolution) => {
errors.push(FulfillmentError { errors.push(FulfillmentError {
@ -125,7 +126,16 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> {
continue; 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; has_changed |= changed;
match certainty { match certainty {
Certainty::Yes => {} Certainty::Yes => {}

View file

@ -1,9 +1,8 @@
use rustc_middle::traits::solve::{Certainty, Goal, MaybeCause}; use rustc_infer::traits::{TraitEngine, TraitEngineExt};
use rustc_middle::ty; use rustc_middle::ty;
use crate::infer::canonical::OriginalQueryValues; use crate::infer::canonical::OriginalQueryValues;
use crate::infer::InferCtxt; use crate::infer::InferCtxt;
use crate::solve::InferCtxtEvalExt;
use crate::traits::{EvaluationResult, OverflowError, PredicateObligation, SelectionContext}; use crate::traits::{EvaluationResult, OverflowError, PredicateObligation, SelectionContext};
pub trait InferCtxtExt<'tcx> { pub trait InferCtxtExt<'tcx> {
@ -81,28 +80,21 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
if self.tcx.trait_solver_next() { if self.tcx.trait_solver_next() {
self.probe(|snapshot| { self.probe(|snapshot| {
if let Ok((_, certainty)) = let mut fulfill_cx = crate::solve::FulfillmentCtxt::new();
self.evaluate_root_goal(Goal::new(self.tcx, param_env, obligation.predicate)) fulfill_cx.register_predicate_obligation(self, obligation.clone());
{ // True errors
match certainty { // FIXME(-Ztrait-solver=next): Overflows are reported as ambig here, is that OK?
Certainty::Yes => { if !fulfill_cx.select_where_possible(self).is_empty() {
if self.opaque_types_added_in_snapshot(snapshot) { 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) Ok(EvaluationResult::EvaluatedToOkModuloOpaqueTypes)
} else if self.region_constraints_added_in_snapshot(snapshot).is_some() } else if self.region_constraints_added_in_snapshot(snapshot).is_some() {
{
Ok(EvaluationResult::EvaluatedToOkModuloRegions) Ok(EvaluationResult::EvaluatedToOkModuloRegions)
} else { } else {
Ok(EvaluationResult::EvaluatedToOk) Ok(EvaluationResult::EvaluatedToOk)
} }
}
Certainty::Maybe(MaybeCause::Ambiguity) => {
Ok(EvaluationResult::EvaluatedToAmbig)
}
Certainty::Maybe(MaybeCause::Overflow) => Err(OverflowError::Canonical),
}
} else {
Ok(EvaluationResult::EvaluatedToErr)
}
}) })
} else { } else {
let c_pred = self.canonicalize_query_keep_static( let c_pred = self.canonicalize_query_keep_static(

View file

@ -618,6 +618,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
let mut fulfill_cx = crate::solve::FulfillmentCtxt::new(); let mut fulfill_cx = crate::solve::FulfillmentCtxt::new();
fulfill_cx.register_predicate_obligations(self.infcx, predicates); fulfill_cx.register_predicate_obligations(self.infcx, predicates);
// True errors // 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() { if !fulfill_cx.select_where_possible(self.infcx).is_empty() {
return Ok(EvaluatedToErr); return Ok(EvaluatedToErr);
} }

View file

@ -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<T> {
Some(T),
None,
}
impl<T> Option<T> {
fn as_ref(&self) -> Option<&T> {
match self {
Option::Some(t) => Option::Some(t),
Option::None => Option::None,
}
}
fn map<U>(self, f: impl FnOnce(T) -> U) -> Option<U> {
match self {
Option::Some(t) => Option::Some(f(t)),
Option::None => Option::None,
}
}
}
impl<T: Foo + 'static> Foo for Option<T> {
type Gat<'a> = Option<<T as Foo>::Gat<'a>> where Self: 'a;
fn bar(&self) -> Self::Gat<'_> {
self.as_ref().map(Foo::bar)
}
}
fn main() {}