mirror of
https://github.com/rust-lang/rust.git
synced 2025-04-28 11:07:42 +00:00
Rollup merge of #107348 - lcnr:project-solve-new, r=compiler-errors
small refactor to new projection code extract `eq_term_and_make_canonical_response` into a helper function which also is another guarantee that the expected term does not influence candidate selection for projections. also change `evaluate_all(vec![single_goal])` to use `evaluate_goal`. the second commit now also adds a `debug_assert!` to `evaluate_goal`.
This commit is contained in:
commit
d65f60d276
@ -161,6 +161,7 @@ impl<'tcx> InferCtxtEvalExt<'tcx> for InferCtxt<'tcx> {
|
|||||||
search_graph: &mut search_graph,
|
search_graph: &mut search_graph,
|
||||||
infcx: self,
|
infcx: self,
|
||||||
var_values: CanonicalVarValues::dummy(),
|
var_values: CanonicalVarValues::dummy(),
|
||||||
|
in_projection_eq_hack: false,
|
||||||
}
|
}
|
||||||
.evaluate_goal(goal);
|
.evaluate_goal(goal);
|
||||||
|
|
||||||
@ -174,6 +175,10 @@ struct EvalCtxt<'a, 'tcx> {
|
|||||||
var_values: CanonicalVarValues<'tcx>,
|
var_values: CanonicalVarValues<'tcx>,
|
||||||
|
|
||||||
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
|
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
|
||||||
|
|
||||||
|
/// This field is used by a debug assertion in [`EvalCtxt::evaluate_goal`],
|
||||||
|
/// see the comment in that method for more details.
|
||||||
|
in_projection_eq_hack: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
||||||
@ -209,7 +214,8 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||||||
loop {
|
loop {
|
||||||
let (ref infcx, goal, var_values) =
|
let (ref infcx, goal, var_values) =
|
||||||
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
|
tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal);
|
||||||
let mut ecx = EvalCtxt { infcx, var_values, search_graph };
|
let mut ecx =
|
||||||
|
EvalCtxt { infcx, var_values, search_graph, in_projection_eq_hack: false };
|
||||||
let result = ecx.compute_goal(goal);
|
let result = ecx.compute_goal(goal);
|
||||||
|
|
||||||
// FIXME: `Response` should be `Copy`
|
// FIXME: `Response` should be `Copy`
|
||||||
@ -239,10 +245,28 @@ impl<'a, 'tcx> EvalCtxt<'a, 'tcx> {
|
|||||||
let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values);
|
let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values);
|
||||||
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)?;
|
||||||
Ok((
|
|
||||||
!canonical_response.value.var_values.is_identity(),
|
let has_changed = !canonical_response.value.var_values.is_identity();
|
||||||
instantiate_canonical_query_response(self.infcx, &orig_values, canonical_response),
|
let certainty =
|
||||||
))
|
instantiate_canonical_query_response(self.infcx, &orig_values, canonical_response);
|
||||||
|
|
||||||
|
// Check that rerunning this query with its inference constraints applied
|
||||||
|
// doesn't result in new inference constraints and has the same result.
|
||||||
|
//
|
||||||
|
// If we have projection goals like `<T as Trait>::Assoc == u32` we recursively
|
||||||
|
// call `exists<U> <T as Trait>::Assoc == U` to enable better caching. This goal
|
||||||
|
// could constrain `U` to `u32` which would cause this check to result in a
|
||||||
|
// solver cycle.
|
||||||
|
if cfg!(debug_assertions) && has_changed && !self.in_projection_eq_hack {
|
||||||
|
let mut orig_values = OriginalQueryValues::default();
|
||||||
|
let canonical_goal = self.infcx.canonicalize_query(goal, &mut orig_values);
|
||||||
|
let canonical_response =
|
||||||
|
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
|
||||||
|
assert!(canonical_response.value.var_values.is_identity());
|
||||||
|
assert_eq!(certainty, canonical_response.value.certainty);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((has_changed, certainty))
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
|
@ -45,8 +45,9 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||||||
projection_ty: goal.predicate.projection_ty,
|
projection_ty: goal.predicate.projection_ty,
|
||||||
term: unconstrained_rhs,
|
term: unconstrained_rhs,
|
||||||
});
|
});
|
||||||
let (_has_changed, normalize_certainty) =
|
let (_has_changed, normalize_certainty) = self.in_projection_eq_hack(|this| {
|
||||||
self.evaluate_goal(goal.with(self.tcx(), unconstrained_predicate))?;
|
this.evaluate_goal(goal.with(this.tcx(), unconstrained_predicate))
|
||||||
|
})?;
|
||||||
|
|
||||||
let nested_eq_goals =
|
let nested_eq_goals =
|
||||||
self.infcx.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
|
self.infcx.eq(goal.param_env, unconstrained_rhs, predicate.term)?;
|
||||||
@ -55,6 +56,15 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This sets a flag used by a debug assert in [`EvalCtxt::evaluate_goal`],
|
||||||
|
/// see the comment in that method for more details.
|
||||||
|
fn in_projection_eq_hack<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
|
||||||
|
self.in_projection_eq_hack = true;
|
||||||
|
let result = f(self);
|
||||||
|
self.in_projection_eq_hack = false;
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
|
/// Is the projection predicate is of the form `exists<T> <Ty as Trait>::Assoc = T`.
|
||||||
///
|
///
|
||||||
/// This is the case if the `term` is an inference variable in the innermost universe
|
/// This is the case if the `term` is an inference variable in the innermost universe
|
||||||
@ -122,6 +132,28 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||||||
&& goal.param_env.visit_with(&mut visitor).is_continue()
|
&& goal.param_env.visit_with(&mut visitor).is_continue()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// After normalizing the projection to `normalized_alias` with the given
|
||||||
|
/// `normalization_certainty`, constrain the inference variable `term` to it
|
||||||
|
/// and return a query response.
|
||||||
|
fn eq_term_and_make_canonical_response(
|
||||||
|
&mut self,
|
||||||
|
goal: Goal<'tcx, ProjectionPredicate<'tcx>>,
|
||||||
|
normalization_certainty: Certainty,
|
||||||
|
normalized_alias: impl Into<ty::Term<'tcx>>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
// The term of our goal should be fully unconstrained, so this should never fail.
|
||||||
|
//
|
||||||
|
// It can however be ambiguous when the `normalized_alias` contains a projection.
|
||||||
|
let nested_goals = self
|
||||||
|
.infcx
|
||||||
|
.eq(goal.param_env, goal.predicate.term, normalized_alias.into())
|
||||||
|
.expect("failed to unify with unconstrained term");
|
||||||
|
let rhs_certainty =
|
||||||
|
self.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
|
||||||
|
|
||||||
|
self.make_canonical_response(normalization_certainty.unify_and(rhs_certainty))
|
||||||
|
}
|
||||||
|
|
||||||
fn merge_project_candidates(
|
fn merge_project_candidates(
|
||||||
&mut self,
|
&mut self,
|
||||||
mut candidates: Vec<Candidate<'tcx>>,
|
mut candidates: Vec<Candidate<'tcx>>,
|
||||||
@ -218,7 +250,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
.map(|pred| goal.with(tcx, pred));
|
.map(|pred| goal.with(tcx, pred));
|
||||||
|
|
||||||
nested_goals.extend(where_clause_bounds);
|
nested_goals.extend(where_clause_bounds);
|
||||||
let trait_ref_certainty = ecx.evaluate_all(nested_goals)?;
|
let match_impl_certainty = ecx.evaluate_all(nested_goals)?;
|
||||||
|
|
||||||
// In case the associated item is hidden due to specialization, we have to
|
// In case the associated item is hidden due to specialization, we have to
|
||||||
// return ambiguity this would otherwise be incomplete, resulting in
|
// return ambiguity this would otherwise be incomplete, resulting in
|
||||||
@ -230,7 +262,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
goal.predicate.def_id(),
|
goal.predicate.def_id(),
|
||||||
impl_def_id
|
impl_def_id
|
||||||
)? else {
|
)? else {
|
||||||
return ecx.make_canonical_response(trait_ref_certainty.unify_and(Certainty::AMBIGUOUS));
|
return ecx.make_canonical_response(match_impl_certainty.unify_and(Certainty::AMBIGUOUS));
|
||||||
};
|
};
|
||||||
|
|
||||||
if !assoc_def.item.defaultness(tcx).has_value() {
|
if !assoc_def.item.defaultness(tcx).has_value() {
|
||||||
@ -277,17 +309,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
ty.map_bound(|ty| ty.into())
|
ty.map_bound(|ty| ty.into())
|
||||||
};
|
};
|
||||||
|
|
||||||
// The term of our goal should be fully unconstrained, so this should never fail.
|
ecx.eq_term_and_make_canonical_response(goal, match_impl_certainty, term.subst(tcx, substs))
|
||||||
//
|
|
||||||
// It can however be ambiguous when the resolved type is a projection.
|
|
||||||
let nested_goals = ecx
|
|
||||||
.infcx
|
|
||||||
.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))
|
|
||||||
.expect("failed to unify with unconstrained term");
|
|
||||||
let rhs_certainty =
|
|
||||||
ecx.evaluate_all(nested_goals).expect("failed to unify with unconstrained term");
|
|
||||||
|
|
||||||
ecx.make_canonical_response(trait_ref_certainty.unify_and(rhs_certainty))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -309,18 +331,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
)?;
|
)?;
|
||||||
let subst_certainty = ecx.evaluate_all(nested_goals)?;
|
let subst_certainty = ecx.evaluate_all(nested_goals)?;
|
||||||
|
|
||||||
// The term of our goal should be fully unconstrained, so this should never fail.
|
ecx.eq_term_and_make_canonical_response(
|
||||||
//
|
goal,
|
||||||
// It can however be ambiguous when the resolved type is a projection.
|
subst_certainty,
|
||||||
let nested_goals = ecx
|
assumption_projection_pred.term,
|
||||||
.infcx
|
)
|
||||||
.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)
|
|
||||||
.expect("failed to unify with unconstrained term");
|
|
||||||
let rhs_certainty = ecx
|
|
||||||
.evaluate_all(nested_goals)
|
|
||||||
.expect("failed to unify with unconstrained term");
|
|
||||||
|
|
||||||
ecx.make_canonical_response(subst_certainty.unify_and(rhs_certainty))
|
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(NoSolution)
|
Err(NoSolution)
|
||||||
@ -437,14 +452,12 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
[ty::GenericArg::from(goal.predicate.self_ty())],
|
[ty::GenericArg::from(goal.predicate.self_ty())],
|
||||||
));
|
));
|
||||||
|
|
||||||
let mut nested_goals = ecx.infcx.eq(
|
let is_sized_certainty = ecx.evaluate_goal(goal.with(tcx, sized_predicate))?.1;
|
||||||
goal.param_env,
|
return ecx.eq_term_and_make_canonical_response(
|
||||||
goal.predicate.term.ty().unwrap(),
|
goal,
|
||||||
|
is_sized_certainty,
|
||||||
tcx.types.unit,
|
tcx.types.unit,
|
||||||
)?;
|
);
|
||||||
nested_goals.push(goal.with(tcx, sized_predicate));
|
|
||||||
|
|
||||||
return ecx.evaluate_all_and_make_canonical_response(nested_goals);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ty::Adt(def, substs) if def.is_struct() => {
|
ty::Adt(def, substs) if def.is_struct() => {
|
||||||
@ -456,7 +469,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
tcx,
|
tcx,
|
||||||
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
|
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
|
||||||
);
|
);
|
||||||
return ecx.evaluate_all_and_make_canonical_response(vec![new_goal]);
|
let (_, certainty) = ecx.evaluate_goal(new_goal)?;
|
||||||
|
return ecx.make_canonical_response(certainty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -469,7 +483,8 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
tcx,
|
tcx,
|
||||||
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
|
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
|
||||||
);
|
);
|
||||||
return ecx.evaluate_all_and_make_canonical_response(vec![new_goal]);
|
let (_, certainty) = ecx.evaluate_goal(new_goal)?;
|
||||||
|
return ecx.make_canonical_response(certainty);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -482,9 +497,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let nested_goals =
|
ecx.eq_term_and_make_canonical_response(goal, Certainty::Yes, metadata_ty)
|
||||||
ecx.infcx.eq(goal.param_env, goal.predicate.term.ty().unwrap(), metadata_ty)?;
|
|
||||||
ecx.evaluate_all_and_make_canonical_response(nested_goals)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ impl<'tcx> SearchGraph<'tcx> {
|
|||||||
/// Tries putting the new goal on the stack, returning an error if it is already cached.
|
/// Tries putting the new goal on the stack, returning an error if it is already cached.
|
||||||
///
|
///
|
||||||
/// This correctly updates the provisional cache if there is a cycle.
|
/// This correctly updates the provisional cache if there is a cycle.
|
||||||
|
#[instrument(level = "debug", skip(self, tcx), ret)]
|
||||||
pub(super) fn try_push_stack(
|
pub(super) fn try_push_stack(
|
||||||
&mut self,
|
&mut self,
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
@ -79,8 +80,10 @@ impl<'tcx> SearchGraph<'tcx> {
|
|||||||
Entry::Occupied(entry_index) => {
|
Entry::Occupied(entry_index) => {
|
||||||
let entry_index = *entry_index.get();
|
let entry_index = *entry_index.get();
|
||||||
|
|
||||||
cache.add_dependency_of_leaf_on(entry_index);
|
|
||||||
let stack_depth = cache.depth(entry_index);
|
let stack_depth = cache.depth(entry_index);
|
||||||
|
debug!("encountered cycle with depth {stack_depth:?}");
|
||||||
|
|
||||||
|
cache.add_dependency_of_leaf_on(entry_index);
|
||||||
|
|
||||||
self.stack[stack_depth].has_been_used = true;
|
self.stack[stack_depth].has_been_used = true;
|
||||||
// NOTE: The goals on the stack aren't the only goals involved in this cycle.
|
// NOTE: The goals on the stack aren't the only goals involved in this cycle.
|
||||||
@ -117,6 +120,7 @@ impl<'tcx> SearchGraph<'tcx> {
|
|||||||
/// updated the provisional cache and we have to recompute the current goal.
|
/// updated the provisional cache and we have to recompute the current goal.
|
||||||
///
|
///
|
||||||
/// FIXME: Refer to the rustc-dev-guide entry once it exists.
|
/// FIXME: Refer to the rustc-dev-guide entry once it exists.
|
||||||
|
#[instrument(level = "debug", skip(self, tcx, actual_goal), ret)]
|
||||||
pub(super) fn try_finalize_goal(
|
pub(super) fn try_finalize_goal(
|
||||||
&mut self,
|
&mut self,
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
|
Loading…
Reference in New Issue
Block a user