initial info dump

This commit is contained in:
Boxy 2023-06-08 19:10:07 +01:00
parent 8d1fa473dd
commit 3009b2c647
11 changed files with 929 additions and 402 deletions

View File

@ -981,7 +981,7 @@ pub enum CodegenObligationError {
FulfillmentError,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub enum DefiningAnchor {
/// `DefId` of the item.
Bind(LocalDefId),

View File

@ -92,7 +92,7 @@ pub fn new(value: T) -> Self {
pub type CanonicalTypeOpNormalizeGoal<'tcx, T> =
Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize<T>>>;
#[derive(Copy, Clone, Debug, HashStable, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, Hash, HashStable, PartialEq, Eq)]
pub struct NoSolution;
impl<'tcx> From<TypeError<'tcx>> for NoSolution {

View File

@ -11,6 +11,8 @@
TypeVisitor,
};
pub mod inspect;
pub type EvaluationCache<'tcx> = Cache<CanonicalInput<'tcx>, QueryResult<'tcx>>;
/// A goal is a statement, i.e. `predicate`, we want to prove
@ -18,7 +20,7 @@
///
/// Most of the time the `param_env` contains the `where`-bounds of the function
/// we're currently typechecking while the `predicate` is some trait bound.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct Goal<'tcx, P> {
pub predicate: P,
pub param_env: ty::ParamEnv<'tcx>,
@ -39,7 +41,7 @@ pub fn with<Q>(self, tcx: TyCtxt<'tcx>, predicate: impl ToPredicate<'tcx, Q>) ->
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct Response<'tcx> {
pub certainty: Certainty,
pub var_values: CanonicalVarValues<'tcx>,
@ -47,7 +49,7 @@ pub struct Response<'tcx> {
pub external_constraints: ExternalConstraints<'tcx>,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub enum Certainty {
Yes,
Maybe(MaybeCause),
@ -86,7 +88,7 @@ pub fn unify_with(self, other: Certainty) -> Certainty {
}
/// Why we failed to evaluate a goal.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub enum MaybeCause {
/// We failed due to ambiguity. This ambiguity can either
/// be a true ambiguity, i.e. there are multiple different answers,
@ -96,7 +98,7 @@ pub enum MaybeCause {
Overflow,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, HashStable, TypeFoldable, TypeVisitable)]
pub struct QueryInput<'tcx, T> {
pub goal: Goal<'tcx, T>,
pub anchor: DefiningAnchor,
@ -104,12 +106,12 @@ pub struct QueryInput<'tcx, T> {
}
/// Additional constraints returned on success.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
#[derive(Debug, PartialEq, Eq, Clone, Hash, HashStable, Default)]
pub struct PredefinedOpaquesData<'tcx> {
pub opaque_types: Vec<(ty::OpaqueTypeKey<'tcx>, Ty<'tcx>)>,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, HashStable)]
pub struct PredefinedOpaques<'tcx>(pub(crate) Interned<'tcx, PredefinedOpaquesData<'tcx>>);
impl<'tcx> std::ops::Deref for PredefinedOpaques<'tcx> {
@ -132,7 +134,7 @@ fn deref(&self) -> &Self::Target {
/// solver, merge the two responses again.
pub type QueryResult<'tcx> = Result<CanonicalResponse<'tcx>, NoSolution>;
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash, HashStable)]
pub struct ExternalConstraints<'tcx>(pub(crate) Interned<'tcx, ExternalConstraintsData<'tcx>>);
impl<'tcx> std::ops::Deref for ExternalConstraints<'tcx> {
@ -144,7 +146,7 @@ fn deref(&self) -> &Self::Target {
}
/// Additional constraints returned on success.
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
#[derive(Debug, PartialEq, Eq, Clone, Hash, HashStable, Default)]
pub struct ExternalConstraintsData<'tcx> {
// FIXME: implement this.
pub region_constraints: QueryRegionConstraints<'tcx>,

View File

@ -0,0 +1,168 @@
use super::{CanonicalInput, Certainty, Goal, NoSolution, QueryInput, QueryResult};
use crate::ty;
use std::fmt::{Debug, Write};
#[derive(Eq, PartialEq, Hash, HashStable)]
pub struct GoalEvaluation<'tcx> {
pub uncanonicalized_goal: Goal<'tcx, ty::Predicate<'tcx>>,
pub canonicalized_goal: Option<CanonicalInput<'tcx>>,
/// To handle coinductive cycles we can end up re-evaluating a goal
/// multiple times with different results for a nested goal. Each rerun
/// is represented as an entry in this vec.
pub evaluation_steps: Vec<GoalEvaluationStep<'tcx>>,
pub cache_hit: bool,
pub result: Option<QueryResult<'tcx>>,
}
impl Debug for GoalEvaluation<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ProofTreeFormatter { f, on_newline: true }.format_goal_evaluation(self)
}
}
#[derive(Eq, PartialEq, Hash, HashStable)]
pub struct AddedGoalsEvaluation<'tcx> {
pub evaluations: Vec<Vec<GoalEvaluation<'tcx>>>,
pub result: Option<Result<Certainty, NoSolution>>,
}
impl Debug for AddedGoalsEvaluation<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ProofTreeFormatter { f, on_newline: true }.format_nested_goal_evaluation(self)
}
}
#[derive(Eq, PartialEq, Hash, HashStable)]
pub struct GoalEvaluationStep<'tcx> {
pub instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
pub nested_goal_evaluations: Vec<AddedGoalsEvaluation<'tcx>>,
pub candidates: Vec<GoalCandidate<'tcx>>,
pub result: Option<QueryResult<'tcx>>,
}
impl Debug for GoalEvaluationStep<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ProofTreeFormatter { f, on_newline: true }.format_evaluation_step(self)
}
}
#[derive(Eq, PartialEq, Hash, HashStable)]
pub struct GoalCandidate<'tcx> {
pub nested_goal_evaluations: Vec<AddedGoalsEvaluation<'tcx>>,
pub candidates: Vec<GoalCandidate<'tcx>>,
pub name: Option<String>,
pub result: Option<QueryResult<'tcx>>,
}
impl Debug for GoalCandidate<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
ProofTreeFormatter { f, on_newline: true }.format_candidate(self)
}
}
struct ProofTreeFormatter<'a, 'b> {
f: &'a mut (dyn Write + 'b),
on_newline: bool,
}
impl Write for ProofTreeFormatter<'_, '_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
for line in s.split_inclusive("\n") {
if self.on_newline {
self.f.write_str(" ")?;
}
self.on_newline = line.ends_with("\n");
self.f.write_str(line)?;
}
Ok(())
}
}
impl ProofTreeFormatter<'_, '_> {
fn nested(&mut self) -> ProofTreeFormatter<'_, '_> {
ProofTreeFormatter { f: self, on_newline: true }
}
fn format_goal_evaluation(&mut self, goal: &GoalEvaluation<'_>) -> std::fmt::Result {
let f = &mut *self.f;
writeln!(f, "GOAL: {:?}", goal.uncanonicalized_goal)?;
writeln!(f, "CANONICALIZED: {:?}", goal.canonicalized_goal)?;
match goal.cache_hit {
true => writeln!(f, "CACHE HIT: {:?}", goal.result),
false => {
for (n, step) in goal.evaluation_steps.iter().enumerate() {
let f = &mut *self.f;
writeln!(f, "REVISION {n}: {:?}", step.result.unwrap())?;
let mut f = self.nested();
f.format_evaluation_step(step)?;
}
let f = &mut *self.f;
writeln!(f, "RESULT: {:?}", goal.result.unwrap())
}
}
}
fn format_evaluation_step(
&mut self,
evaluation_step: &GoalEvaluationStep<'_>,
) -> std::fmt::Result {
let f = &mut *self.f;
writeln!(f, "INSTANTIATED: {:?}", evaluation_step.instantiated_goal)?;
for candidate in &evaluation_step.candidates {
let mut f = self.nested();
f.format_candidate(candidate)?;
}
for nested_goal_evaluation in &evaluation_step.nested_goal_evaluations {
let mut f = self.nested();
f.format_nested_goal_evaluation(nested_goal_evaluation)?;
}
Ok(())
}
fn format_candidate(&mut self, candidate: &GoalCandidate<'_>) -> std::fmt::Result {
let f = &mut *self.f;
match (candidate.name.as_ref(), candidate.result) {
(Some(name), Some(result)) => writeln!(f, "CANDIDATE {}: {:?}", name, result,)?,
(None, None) => writeln!(f, "MISC PROBE")?,
(None, Some(_)) => unreachable!("unexpected probe with no name but a result"),
(Some(_), None) => unreachable!("unexpected probe with a name but no candidate"),
};
let mut f = self.nested();
for candidate in &candidate.candidates {
f.format_candidate(candidate)?;
}
for nested_evaluations in &candidate.nested_goal_evaluations {
f.format_nested_goal_evaluation(nested_evaluations)?;
}
Ok(())
}
fn format_nested_goal_evaluation(
&mut self,
nested_goal_evaluation: &AddedGoalsEvaluation<'_>,
) -> std::fmt::Result {
let f = &mut *self.f;
writeln!(f, "TRY_EVALUATE_ADDED_GOALS: {:?}", nested_goal_evaluation.result.unwrap())?;
for (n, revision) in nested_goal_evaluation.evaluations.iter().enumerate() {
let f = &mut *self.f;
writeln!(f, "REVISION {n}")?;
let mut f = self.nested();
for goal_evaluation in revision {
f.format_goal_evaluation(goal_evaluation)?;
}
}
Ok(())
}
}

View File

@ -109,10 +109,13 @@ fn assemble_normalizes_to_candidate(
direction: ty::AliasRelationDirection,
invert: Invert,
) -> QueryResult<'tcx> {
self.probe(|ecx| {
ecx.normalizes_to_inner(param_env, alias, other, direction, invert)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
self.probe_candidate(
|ecx| {
ecx.normalizes_to_inner(param_env, alias, other, direction, invert)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "normalizes-to".into(),
)
}
fn normalizes_to_inner(
@ -153,18 +156,21 @@ fn assemble_subst_relate_candidate(
alias_rhs: ty::AliasTy<'tcx>,
direction: ty::AliasRelationDirection,
) -> QueryResult<'tcx> {
self.probe(|ecx| {
match direction {
ty::AliasRelationDirection::Equate => {
ecx.eq(param_env, alias_lhs, alias_rhs)?;
self.probe_candidate(
|ecx| {
match direction {
ty::AliasRelationDirection::Equate => {
ecx.eq(param_env, alias_lhs, alias_rhs)?;
}
ty::AliasRelationDirection::Subtype => {
ecx.sub(param_env, alias_lhs, alias_rhs)?;
}
}
ty::AliasRelationDirection::Subtype => {
ecx.sub(param_env, alias_lhs, alias_rhs)?;
}
}
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "substs relate".into(),
)
}
fn assemble_bidirectional_normalizes_to_candidate(
@ -174,22 +180,25 @@ fn assemble_bidirectional_normalizes_to_candidate(
rhs: ty::Term<'tcx>,
direction: ty::AliasRelationDirection,
) -> QueryResult<'tcx> {
self.probe(|ecx| {
ecx.normalizes_to_inner(
param_env,
lhs.to_alias_ty(ecx.tcx()).unwrap(),
rhs,
direction,
Invert::No,
)?;
ecx.normalizes_to_inner(
param_env,
rhs.to_alias_ty(ecx.tcx()).unwrap(),
lhs,
direction,
Invert::Yes,
)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
self.probe_candidate(
|ecx| {
ecx.normalizes_to_inner(
param_env,
lhs.to_alias_ty(ecx.tcx()).unwrap(),
rhs,
direction,
Invert::No,
)?;
ecx.normalizes_to_inner(
param_env,
rhs.to_alias_ty(ecx.tcx()).unwrap(),
lhs,
direction,
Invert::Yes,
)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "bidir normalizes-to".into(),
)
}
}

View File

@ -21,8 +21,10 @@
use rustc_span::DUMMY_SP;
use std::ops::ControlFlow;
use crate::solve::inspect::DebugSolver;
use crate::traits::specialization_graph;
use super::inspect::InspectSolve;
use super::search_graph::{self, OverflowHandler};
use super::SolverMode;
use super::{search_graph::SearchGraph, Goal};
@ -73,6 +75,8 @@ pub struct EvalCtxt<'a, 'tcx> {
// ambiguous goals. Instead, a probe needs to be introduced somewhere in the
// evaluation code.
tainted: Result<(), NoSolution>,
inspect: Box<dyn InspectSolve<'tcx> + 'tcx>,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -143,9 +147,18 @@ fn evaluate_root_goal(
var_values: CanonicalVarValues::dummy(),
nested_goals: NestedGoals::new(),
tainted: Ok(()),
inspect: Box::new(DebugSolver::new()),
};
let result = ecx.evaluate_goal(IsNormalizesToHack::No, goal);
let tree = match ecx.inspect.into_debug_solver() {
Some(tree) => match Box::leak(tree) {
DebugSolver::GoalEvaluation(tree) => tree,
_ => unreachable!("unable to convert to `DebugSolver::GoalEvaluation`"),
},
_ => unreachable!("unable to convert to `DebugSolver::GoalEvaluation`"),
};
assert!(
ecx.nested_goals.is_empty(),
"root `EvalCtxt` should not have any goals added to it"
@ -170,58 +183,72 @@ pub(super) fn solver_mode(&self) -> SolverMode {
/// Instead of calling this function directly, use either [EvalCtxt::evaluate_goal]
/// if you're inside of the solver or [InferCtxtEvalExt::evaluate_root_goal] if you're
/// outside of it.
#[instrument(level = "debug", skip(tcx, search_graph), ret)]
#[instrument(level = "debug", skip(tcx, search_graph, goal_evaluation), ret)]
fn evaluate_canonical_goal(
tcx: TyCtxt<'tcx>,
search_graph: &'a mut search_graph::SearchGraph<'tcx>,
canonical_input: CanonicalInput<'tcx>,
mut goal_evaluation: &mut dyn InspectSolve<'tcx>,
) -> QueryResult<'tcx> {
goal_evaluation.canonicalized_goal(canonical_input);
// Deal with overflow, caching, and coinduction.
//
// The actual solver logic happens in `ecx.compute_goal`.
search_graph.with_new_goal(tcx, canonical_input, |search_graph| {
let intercrate = match search_graph.solver_mode() {
SolverMode::Normal => false,
SolverMode::Coherence => true,
};
let (ref infcx, input, var_values) = tcx
.infer_ctxt()
.intercrate(intercrate)
.with_next_trait_solver(true)
.with_opaque_type_inference(canonical_input.value.anchor)
.build_with_canonical(DUMMY_SP, &canonical_input);
search_graph.with_new_goal(
tcx,
canonical_input,
goal_evaluation,
|search_graph, goal_evaluation| {
let intercrate = match search_graph.solver_mode() {
SolverMode::Normal => false,
SolverMode::Coherence => true,
};
let (ref infcx, input, var_values) = tcx
.infer_ctxt()
.intercrate(intercrate)
.with_next_trait_solver(true)
.with_opaque_type_inference(canonical_input.value.anchor)
.build_with_canonical(DUMMY_SP, &canonical_input);
let mut ecx = EvalCtxt {
infcx,
var_values,
predefined_opaques_in_body: input.predefined_opaques_in_body,
max_input_universe: canonical_input.max_universe,
search_graph,
nested_goals: NestedGoals::new(),
tainted: Ok(()),
};
let mut ecx = EvalCtxt {
infcx,
var_values,
predefined_opaques_in_body: input.predefined_opaques_in_body,
max_input_universe: canonical_input.max_universe,
search_graph,
nested_goals: NestedGoals::new(),
tainted: Ok(()),
inspect: goal_evaluation.new_goal_evaluation_step(input),
};
for &(key, ty) in &input.predefined_opaques_in_body.opaque_types {
ecx.insert_hidden_type(key, input.goal.param_env, ty)
.expect("failed to prepopulate opaque types");
}
for &(key, ty) in &input.predefined_opaques_in_body.opaque_types {
ecx.insert_hidden_type(key, input.goal.param_env, ty)
.expect("failed to prepopulate opaque types");
}
if !ecx.nested_goals.is_empty() {
panic!("prepopulating opaque types shouldn't add goals: {:?}", ecx.nested_goals);
}
if !ecx.nested_goals.is_empty() {
panic!(
"prepopulating opaque types shouldn't add goals: {:?}",
ecx.nested_goals
);
}
let result = ecx.compute_goal(input.goal);
let result = ecx.compute_goal(input.goal);
ecx.inspect.query_result(result);
goal_evaluation.goal_evaluation_step(ecx.inspect);
// When creating a query response we clone the opaque type constraints
// instead of taking them. This would cause an ICE here, since we have
// assertions against dropping an `InferCtxt` without taking opaques.
// FIXME: Once we remove support for the old impl we can remove this.
if input.anchor != DefiningAnchor::Error {
let _ = infcx.take_opaque_types();
}
// When creating a query response we clone the opaque type constraints
// instead of taking them. This would cause an ICE here, since we have
// assertions against dropping an `InferCtxt` without taking opaques.
// FIXME: Once we remove support for the old impl we can remove this.
if input.anchor != DefiningAnchor::Error {
let _ = infcx.take_opaque_types();
}
result
})
result
},
)
}
/// Recursively evaluates `goal`, returning whether any inference vars have
@ -232,8 +259,16 @@ fn evaluate_goal(
goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Result<(bool, Certainty, Vec<Goal<'tcx, ty::Predicate<'tcx>>>), 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 mut goal_evaluation = self.inspect.new_goal_evaluation(goal);
let canonical_response = EvalCtxt::evaluate_canonical_goal(
self.tcx(),
self.search_graph,
canonical_goal,
&mut *goal_evaluation,
);
goal_evaluation.query_result(canonical_response);
self.inspect.goal_evaluation(goal_evaluation);
let canonical_response = canonical_response?;
let has_changed = !canonical_response.value.var_values.is_identity()
|| !canonical_response.value.external_constraints.opaque_types.is_empty();
@ -261,8 +296,13 @@ fn evaluate_goal(
{
debug!("rerunning goal to check result is stable");
let (_orig_values, canonical_goal) = self.canonicalize_goal(goal);
let new_canonical_response =
EvalCtxt::evaluate_canonical_goal(self.tcx(), self.search_graph, canonical_goal)?;
let new_canonical_response = EvalCtxt::evaluate_canonical_goal(
self.tcx(),
self.search_graph,
canonical_goal,
// FIXME(-Ztrait-solver=next): we do not track what happens in `evaluate_canonical_goal`
&mut (),
)?;
// We only check for modulo regions as we convert all regions in
// the input to new existentials, even if they're expected to be
// `'static` or a placeholder region.
@ -353,12 +393,17 @@ fn compute_goal(&mut self, goal: Goal<'tcx, ty::Predicate<'tcx>>) -> QueryResult
// the certainty of all the goals.
#[instrument(level = "debug", skip(self))]
pub(super) fn try_evaluate_added_goals(&mut self) -> Result<Certainty, NoSolution> {
let inspect = self.inspect.new_evaluate_added_goals();
let inspect = core::mem::replace(&mut self.inspect, inspect);
let mut goals = core::mem::replace(&mut self.nested_goals, NestedGoals::new());
let mut new_goals = NestedGoals::new();
let response = self.repeat_while_none(
|_| Ok(Certainty::Maybe(MaybeCause::Overflow)),
|this| {
this.inspect.evaluate_added_goals_loop_start();
let mut has_changed = Err(Certainty::Yes);
if let Some(goal) = goals.normalizes_to_hack_goal.take() {
@ -447,10 +492,15 @@ pub(super) fn try_evaluate_added_goals(&mut self) -> Result<Certainty, NoSolutio
},
);
self.inspect.eval_added_goals_result(response);
if response.is_err() {
self.tainted = Err(NoSolution);
}
let goal_evaluations = std::mem::replace(&mut self.inspect, inspect);
self.inspect.added_goals_evaluation(goal_evaluations);
self.nested_goals = goals;
response
}
@ -466,8 +516,24 @@ pub(super) fn probe<T>(&mut self, f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> T)
search_graph: self.search_graph,
nested_goals: self.nested_goals.clone(),
tainted: self.tainted,
inspect: self.inspect.new_goal_candidate(),
};
self.infcx.probe(|_| f(&mut ecx))
let r = self.infcx.probe(|_| f(&mut ecx));
self.inspect.goal_candidate(ecx.inspect);
r
}
pub(super) fn probe_candidate(
&mut self,
f: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> QueryResult<'tcx>,
mut name: impl FnMut() -> String,
) -> QueryResult<'tcx> {
self.probe(|ecx| {
let result = f(ecx);
ecx.inspect.candidate_name(&mut name);
ecx.inspect.query_result(result);
result
})
}
pub(super) fn tcx(&self) -> TyCtxt<'tcx> {
@ -781,14 +847,17 @@ pub(super) fn unify_existing_opaque_tys(
if candidate_key.def_id != key.def_id {
continue;
}
values.extend(self.probe(|ecx| {
for (a, b) in std::iter::zip(candidate_key.substs, key.substs) {
ecx.eq(param_env, a, b)?;
}
ecx.eq(param_env, candidate_ty, ty)?;
ecx.add_item_bounds_for_hidden_type(candidate_key, param_env, candidate_ty);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}));
values.extend(self.probe(
|ecx| {
for (a, b) in std::iter::zip(candidate_key.substs, key.substs) {
ecx.eq(param_env, a, b)?;
}
ecx.eq(param_env, candidate_ty, ty)?;
ecx.add_item_bounds_for_hidden_type(candidate_key, param_env, candidate_ty);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "opaque type storage".into(),
));
}
values
}

View File

@ -0,0 +1,244 @@
use rustc_middle::{
traits::{
query::NoSolution,
solve::{inspect::*, CanonicalInput, Certainty, Goal, QueryInput, QueryResult},
},
ty,
};
#[derive(Debug)]
pub enum DebugSolver<'tcx> {
Root,
GoalEvaluation(GoalEvaluation<'tcx>),
AddedGoalsEvaluation(AddedGoalsEvaluation<'tcx>),
GoalEvaluationStep(GoalEvaluationStep<'tcx>),
GoalCandidate(GoalCandidate<'tcx>),
}
pub trait InspectSolve<'tcx> {
fn into_debug_solver(self: Box<Self>) -> Option<Box<DebugSolver<'tcx>>>;
fn new_goal_evaluation(
&mut self,
goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Box<dyn InspectSolve<'tcx> + 'tcx>;
fn canonicalized_goal(&mut self, canonical_goal: CanonicalInput<'tcx>);
fn cache_hit(&mut self);
fn goal_evaluation(&mut self, goal_evaluation: Box<dyn InspectSolve<'tcx> + 'tcx>);
fn new_goal_evaluation_step(
&mut self,
instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
) -> Box<dyn InspectSolve<'tcx> + 'tcx>;
fn goal_evaluation_step(&mut self, goal_eval_step: Box<dyn InspectSolve<'tcx> + 'tcx>);
fn new_goal_candidate(&mut self) -> Box<dyn InspectSolve<'tcx> + 'tcx>;
fn candidate_name(&mut self, f: &mut dyn FnMut() -> String);
fn goal_candidate(&mut self, candidate: Box<dyn InspectSolve<'tcx> + 'tcx>);
fn new_evaluate_added_goals(&mut self) -> Box<dyn InspectSolve<'tcx> + 'tcx>;
fn evaluate_added_goals_loop_start(&mut self);
fn eval_added_goals_result(&mut self, result: Result<Certainty, NoSolution>);
fn added_goals_evaluation(&mut self, goals_evaluation: Box<dyn InspectSolve<'tcx> + 'tcx>);
fn query_result(&mut self, result: QueryResult<'tcx>);
}
/// No-op `InspectSolve` impl to use for normal trait solving when we do not want
/// to take a performance hit from recording information about how things are being
/// proven.
impl<'tcx> InspectSolve<'tcx> for () {
fn into_debug_solver(self: Box<Self>) -> Option<Box<DebugSolver<'tcx>>> {
None
}
fn new_goal_evaluation(
&mut self,
_goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(())
}
fn canonicalized_goal(&mut self, _canonical_goal: CanonicalInput<'tcx>) {}
fn cache_hit(&mut self) {}
fn goal_evaluation(&mut self, _goal_evaluation: Box<dyn InspectSolve<'tcx> + 'tcx>) {}
fn new_goal_evaluation_step(
&mut self,
_instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(())
}
fn goal_evaluation_step(&mut self, _goal_eval_step: Box<dyn InspectSolve<'tcx> + 'tcx>) {}
fn new_goal_candidate(&mut self) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(())
}
fn candidate_name(&mut self, _f: &mut dyn FnMut() -> String) {}
fn goal_candidate(&mut self, _candidate: Box<dyn InspectSolve<'tcx> + 'tcx>) {}
fn new_evaluate_added_goals(&mut self) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(())
}
fn evaluate_added_goals_loop_start(&mut self) {}
fn eval_added_goals_result(&mut self, _result: Result<Certainty, NoSolution>) {}
fn added_goals_evaluation(&mut self, _goals_evaluation: Box<dyn InspectSolve<'tcx> + 'tcx>) {}
fn query_result(&mut self, _result: QueryResult<'tcx>) {}
}
impl<'tcx> DebugSolver<'tcx> {
pub fn new() -> Self {
Self::Root
}
}
impl<'tcx> InspectSolve<'tcx> for DebugSolver<'tcx> {
fn into_debug_solver(self: Box<Self>) -> Option<Box<DebugSolver<'tcx>>> {
Some(self)
}
fn new_goal_evaluation(
&mut self,
goal: Goal<'tcx, ty::Predicate<'tcx>>,
) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(DebugSolver::GoalEvaluation(GoalEvaluation {
uncanonicalized_goal: goal,
canonicalized_goal: None,
evaluation_steps: vec![],
cache_hit: false,
result: None,
}))
}
fn canonicalized_goal(&mut self, canonical_goal: CanonicalInput<'tcx>) {
match self {
DebugSolver::GoalEvaluation(goal_evaluation) => {
assert!(goal_evaluation.canonicalized_goal.is_none());
goal_evaluation.canonicalized_goal = Some(canonical_goal)
}
_ => unreachable!(),
}
}
fn cache_hit(&mut self) {
match self {
DebugSolver::GoalEvaluation(goal_evaluation) => goal_evaluation.cache_hit = true,
_ => unreachable!(),
};
}
fn goal_evaluation(&mut self, goal_evaluation: Box<dyn InspectSolve<'tcx> + 'tcx>) {
let goal_evaluation = goal_evaluation.into_debug_solver().unwrap();
match (self, *goal_evaluation) {
(
DebugSolver::AddedGoalsEvaluation(AddedGoalsEvaluation { evaluations, .. }),
DebugSolver::GoalEvaluation(goal_evaluation),
) => evaluations.last_mut().unwrap().push(goal_evaluation),
(this @ DebugSolver::Root, goal_evaluation) => *this = goal_evaluation,
_ => unreachable!(),
}
}
fn new_goal_evaluation_step(
&mut self,
instantiated_goal: QueryInput<'tcx, ty::Predicate<'tcx>>,
) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(DebugSolver::GoalEvaluationStep(GoalEvaluationStep {
instantiated_goal,
nested_goal_evaluations: vec![],
candidates: vec![],
result: None,
}))
}
fn goal_evaluation_step(&mut self, goal_eval_step: Box<dyn InspectSolve<'tcx> + 'tcx>) {
let goal_eval_step = goal_eval_step.into_debug_solver().unwrap();
match (self, *goal_eval_step) {
(DebugSolver::GoalEvaluation(goal_eval), DebugSolver::GoalEvaluationStep(step)) => {
goal_eval.evaluation_steps.push(step);
}
_ => unreachable!(),
}
}
fn new_goal_candidate(&mut self) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(DebugSolver::GoalCandidate(GoalCandidate {
nested_goal_evaluations: vec![],
candidates: vec![],
name: None,
result: None,
}))
}
fn candidate_name(&mut self, f: &mut dyn FnMut() -> String) {
let name = f();
match self {
DebugSolver::GoalCandidate(goal_candidate) => {
assert!(goal_candidate.name.is_none());
goal_candidate.name = Some(name);
}
_ => unreachable!(),
}
}
fn goal_candidate(&mut self, candidate: Box<dyn InspectSolve<'tcx> + 'tcx>) {
let candidate = candidate.into_debug_solver().unwrap();
match (self, *candidate) {
(
DebugSolver::GoalCandidate(GoalCandidate { candidates, .. })
| DebugSolver::GoalEvaluationStep(GoalEvaluationStep { candidates, .. }),
DebugSolver::GoalCandidate(candidate),
) => candidates.push(candidate),
_ => unreachable!(),
}
}
fn new_evaluate_added_goals(&mut self) -> Box<dyn InspectSolve<'tcx> + 'tcx> {
Box::new(DebugSolver::AddedGoalsEvaluation(AddedGoalsEvaluation {
evaluations: vec![],
result: None,
}))
}
fn evaluate_added_goals_loop_start(&mut self) {
match self {
DebugSolver::AddedGoalsEvaluation(this) => {
this.evaluations.push(vec![]);
}
_ => unreachable!(),
}
}
fn eval_added_goals_result(&mut self, result: Result<Certainty, NoSolution>) {
match self {
DebugSolver::AddedGoalsEvaluation(this) => {
assert!(this.result.is_none());
this.result = Some(result);
}
_ => unreachable!(),
}
}
fn added_goals_evaluation(&mut self, goals_evaluation: Box<dyn InspectSolve<'tcx> + 'tcx>) {
let goals_evaluation = goals_evaluation.into_debug_solver().unwrap();
match (self, *goals_evaluation) {
(
DebugSolver::GoalEvaluationStep(GoalEvaluationStep {
nested_goal_evaluations, ..
})
| DebugSolver::GoalCandidate(GoalCandidate { nested_goal_evaluations, .. }),
DebugSolver::AddedGoalsEvaluation(added_goals_evaluation),
) => nested_goal_evaluations.push(added_goals_evaluation),
_ => unreachable!(),
}
}
fn query_result(&mut self, result: QueryResult<'tcx>) {
match self {
DebugSolver::GoalEvaluation(goal_evaluation) => {
assert!(goal_evaluation.result.is_none());
goal_evaluation.result = Some(result);
}
DebugSolver::Root | DebugSolver::AddedGoalsEvaluation(_) => unreachable!(),
DebugSolver::GoalEvaluationStep(evaluation_step) => {
assert!(evaluation_step.result.is_none());
evaluation_step.result = Some(result);
}
DebugSolver::GoalCandidate(candidate) => {
assert!(candidate.result.is_none());
candidate.result = Some(result);
}
}
}
}

View File

@ -25,6 +25,7 @@
mod canonicalize;
mod eval_ctxt;
mod fulfill;
pub mod inspect;
mod opaques;
mod project_goals;
mod search_graph;

View File

@ -112,7 +112,7 @@ fn probe_and_match_goal_against_assumption(
if let Some(projection_pred) = assumption.as_projection_clause()
&& projection_pred.projection_def_id() == goal.predicate.def_id()
{
ecx.probe(|ecx| {
ecx.probe_candidate(|ecx| {
let assumption_projection_pred =
ecx.instantiate_binder_with_infer(projection_pred);
ecx.eq(
@ -123,7 +123,7 @@ fn probe_and_match_goal_against_assumption(
ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)
.expect("expected goal term to be fully unconstrained");
then(ecx)
})
}, || "assumption".into())
} else {
Err(NoSolution)
}
@ -143,87 +143,90 @@ fn consider_impl_candidate(
return Err(NoSolution);
}
ecx.probe(|ecx| {
let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
ecx.probe_candidate(
|ecx| {
let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
ecx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;
ecx.eq(goal.param_env, goal_trait_ref, impl_trait_ref)?;
let where_clause_bounds = tcx
.predicates_of(impl_def_id)
.instantiate(tcx, impl_substs)
.predicates
.into_iter()
.map(|pred| goal.with(tcx, pred));
ecx.add_goals(where_clause_bounds);
let where_clause_bounds = tcx
.predicates_of(impl_def_id)
.instantiate(tcx, impl_substs)
.predicates
.into_iter()
.map(|pred| goal.with(tcx, pred));
ecx.add_goals(where_clause_bounds);
// In case the associated item is hidden due to specialization, we have to
// return ambiguity this would otherwise be incomplete, resulting in
// unsoundness during coherence (#105782).
let Some(assoc_def) = fetch_eligible_assoc_item_def(
ecx,
goal.param_env,
goal_trait_ref,
goal.predicate.def_id(),
impl_def_id
)? else {
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
};
if !assoc_def.item.defaultness(tcx).has_value() {
let guar = tcx.sess.delay_span_bug(
tcx.def_span(assoc_def.item.def_id),
"missing value for assoc item in impl",
);
let error_term = match assoc_def.item.kind {
ty::AssocKind::Const => tcx
.const_error(
tcx.type_of(goal.predicate.def_id())
.subst(tcx, goal.predicate.projection_ty.substs),
guar,
)
.into(),
ty::AssocKind::Type => tcx.ty_error(guar).into(),
ty::AssocKind::Fn => unreachable!(),
// In case the associated item is hidden due to specialization, we have to
// return ambiguity this would otherwise be incomplete, resulting in
// unsoundness during coherence (#105782).
let Some(assoc_def) = fetch_eligible_assoc_item_def(
ecx,
goal.param_env,
goal_trait_ref,
goal.predicate.def_id(),
impl_def_id
)? else {
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
};
ecx.eq(goal.param_env, goal.predicate.term, error_term)
if !assoc_def.item.defaultness(tcx).has_value() {
let guar = tcx.sess.delay_span_bug(
tcx.def_span(assoc_def.item.def_id),
"missing value for assoc item in impl",
);
let error_term = match assoc_def.item.kind {
ty::AssocKind::Const => tcx
.const_error(
tcx.type_of(goal.predicate.def_id())
.subst(tcx, goal.predicate.projection_ty.substs),
guar,
)
.into(),
ty::AssocKind::Type => tcx.ty_error(guar).into(),
ty::AssocKind::Fn => unreachable!(),
};
ecx.eq(goal.param_env, goal.predicate.term, error_term)
.expect("expected goal term to be fully unconstrained");
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
}
// Getting the right substitutions here is complex, e.g. given:
// - a goal `<Vec<u32> as Trait<i32>>::Assoc<u64>`
// - the applicable impl `impl<T> Trait<i32> for Vec<T>`
// - and the impl which defines `Assoc` being `impl<T, U> Trait<U> for Vec<T>`
//
// We first rebase the goal substs onto the impl, going from `[Vec<u32>, i32, u64]`
// to `[u32, u64]`.
//
// And then map these substs to the substs of the defining impl of `Assoc`, going
// from `[u32, u64]` to `[u32, i32, u64]`.
let impl_substs_with_gat = goal.predicate.projection_ty.substs.rebase_onto(
tcx,
goal_trait_ref.def_id,
impl_substs,
);
let substs = ecx.translate_substs(
goal.param_env,
impl_def_id,
impl_substs_with_gat,
assoc_def.defining_node,
);
// Finally we construct the actual value of the associated type.
let term = match assoc_def.item.kind {
ty::AssocKind::Type => tcx.type_of(assoc_def.item.def_id).map_bound(|ty| ty.into()),
ty::AssocKind::Const => bug!("associated const projection is not supported yet"),
ty::AssocKind::Fn => unreachable!("we should never project to a fn"),
};
ecx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))
.expect("expected goal term to be fully unconstrained");
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
}
// Getting the right substitutions here is complex, e.g. given:
// - a goal `<Vec<u32> as Trait<i32>>::Assoc<u64>`
// - the applicable impl `impl<T> Trait<i32> for Vec<T>`
// - and the impl which defines `Assoc` being `impl<T, U> Trait<U> for Vec<T>`
//
// We first rebase the goal substs onto the impl, going from `[Vec<u32>, i32, u64]`
// to `[u32, u64]`.
//
// And then map these substs to the substs of the defining impl of `Assoc`, going
// from `[u32, u64]` to `[u32, i32, u64]`.
let impl_substs_with_gat = goal.predicate.projection_ty.substs.rebase_onto(
tcx,
goal_trait_ref.def_id,
impl_substs,
);
let substs = ecx.translate_substs(
goal.param_env,
impl_def_id,
impl_substs_with_gat,
assoc_def.defining_node,
);
// Finally we construct the actual value of the associated type.
let term = match assoc_def.item.kind {
ty::AssocKind::Type => tcx.type_of(assoc_def.item.def_id).map_bound(|ty| ty.into()),
ty::AssocKind::Const => bug!("associated const projection is not supported yet"),
ty::AssocKind::Fn => unreachable!("we should never project to a fn"),
};
ecx.eq(goal.param_env, goal.predicate.term, term.subst(tcx, substs))
.expect("expected goal term to be fully unconstrained");
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "impl".into(),
)
}
fn consider_auto_trait_candidate(
@ -318,53 +321,69 @@ fn consider_builtin_pointee_candidate(
goal: Goal<'tcx, Self>,
) -> QueryResult<'tcx> {
let tcx = ecx.tcx();
ecx.probe(|ecx| {
let metadata_ty = match goal.predicate.self_ty().kind() {
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Array(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Closure(..)
| ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
| ty::Generator(..)
| ty::GeneratorWitness(..)
| ty::GeneratorWitnessMIR(..)
| ty::Never
| ty::Foreign(..) => tcx.types.unit,
ecx.probe_candidate(
|ecx| {
let metadata_ty = match goal.predicate.self_ty().kind() {
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(..)
| ty::Array(..)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Closure(..)
| ty::Infer(ty::IntVar(..) | ty::FloatVar(..))
| ty::Generator(..)
| ty::GeneratorWitness(..)
| ty::GeneratorWitnessMIR(..)
| ty::Never
| ty::Foreign(..) => tcx.types.unit,
ty::Error(e) => tcx.ty_error(*e),
ty::Error(e) => tcx.ty_error(*e),
ty::Str | ty::Slice(_) => tcx.types.usize,
ty::Str | ty::Slice(_) => tcx.types.usize,
ty::Dynamic(_, _, _) => {
let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, None);
tcx.type_of(dyn_metadata)
.subst(tcx, &[ty::GenericArg::from(goal.predicate.self_ty())])
}
ty::Dynamic(_, _, _) => {
let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, None);
tcx.type_of(dyn_metadata)
.subst(tcx, &[ty::GenericArg::from(goal.predicate.self_ty())])
}
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
// FIXME(ptr_metadata): It would also be possible to return a `Ok(Ambig)` with no constraints.
let sized_predicate = ty::TraitRef::from_lang_item(
tcx,
LangItem::Sized,
DUMMY_SP,
[ty::GenericArg::from(goal.predicate.self_ty())],
);
ecx.add_goal(goal.with(tcx, sized_predicate));
tcx.types.unit
}
ty::Alias(_, _) | ty::Param(_) | ty::Placeholder(..) => {
// FIXME(ptr_metadata): It would also be possible to return a `Ok(Ambig)` with no constraints.
let sized_predicate = ty::TraitRef::from_lang_item(
tcx,
LangItem::Sized,
DUMMY_SP,
[ty::GenericArg::from(goal.predicate.self_ty())],
);
ecx.add_goal(goal.with(tcx, sized_predicate));
tcx.types.unit
}
ty::Adt(def, substs) if def.is_struct() => {
match def.non_enum_variant().fields.raw.last() {
ty::Adt(def, substs) if def.is_struct() => {
match def.non_enum_variant().fields.raw.last() {
None => tcx.types.unit,
Some(field_def) => {
let self_ty = field_def.ty(tcx, substs);
ecx.add_goal(goal.with(
tcx,
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
));
return ecx.evaluate_added_goals_and_make_canonical_response(
Certainty::Yes,
);
}
}
}
ty::Adt(_, _) => tcx.types.unit,
ty::Tuple(elements) => match elements.last() {
None => tcx.types.unit,
Some(field_def) => {
let self_ty = field_def.ty(tcx, substs);
Some(&self_ty) => {
ecx.add_goal(goal.with(
tcx,
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
@ -372,35 +391,23 @@ fn consider_builtin_pointee_candidate(
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
}
}
}
ty::Adt(_, _) => tcx.types.unit,
},
ty::Tuple(elements) => match elements.last() {
None => tcx.types.unit,
Some(&self_ty) => {
ecx.add_goal(goal.with(
tcx,
ty::Binder::dummy(goal.predicate.with_self_ty(tcx, self_ty)),
));
return ecx
.evaluate_added_goals_and_make_canonical_response(Certainty::Yes);
}
},
ty::Infer(
ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_),
)
| ty::Bound(..) => bug!(
"unexpected self ty `{:?}` when normalizing `<T as Pointee>::Metadata`",
goal.predicate.self_ty()
),
};
ty::Infer(
ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_),
)
| ty::Bound(..) => bug!(
"unexpected self ty `{:?}` when normalizing `<T as Pointee>::Metadata`",
goal.predicate.self_ty()
),
};
ecx.eq(goal.param_env, goal.predicate.term, metadata_ty.into())
.expect("expected goal term to be fully unconstrained");
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
ecx.eq(goal.param_env, goal.predicate.term, metadata_ty.into())
.expect("expected goal term to be fully unconstrained");
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "builtin pointee".into(),
)
}
fn consider_builtin_future_candidate(
@ -535,11 +542,14 @@ fn consider_builtin_discriminant_kind_candidate(
),
};
ecx.probe(|ecx| {
ecx.eq(goal.param_env, goal.predicate.term, discriminant_ty.into())
.expect("expected goal term to be fully unconstrained");
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
ecx.probe_candidate(
|ecx| {
ecx.eq(goal.param_env, goal.predicate.term, discriminant_ty.into())
.expect("expected goal term to be fully unconstrained");
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "builtin discriminant kind".into(),
)
}
fn consider_builtin_destruct_candidate(

View File

@ -12,6 +12,7 @@
use rustc_middle::ty::TyCtxt;
use std::{collections::hash_map::Entry, mem};
use super::inspect::InspectSolve;
use super::SolverMode;
rustc_index::newtype_index! {
@ -205,11 +206,13 @@ pub(super) fn with_new_goal(
&mut self,
tcx: TyCtxt<'tcx>,
canonical_input: CanonicalInput<'tcx>,
mut loop_body: impl FnMut(&mut Self) -> QueryResult<'tcx>,
inspect: &mut dyn InspectSolve<'tcx>,
mut loop_body: impl FnMut(&mut Self, &mut dyn InspectSolve<'tcx>) -> QueryResult<'tcx>,
) -> QueryResult<'tcx> {
if self.should_use_global_cache() {
if let Some(result) = tcx.new_solver_evaluation_cache.get(&canonical_input, tcx) {
debug!(?canonical_input, ?result, "cache hit");
inspect.cache_hit();
return result;
}
}
@ -231,7 +234,7 @@ pub(super) fn with_new_goal(
result
},
|this| {
let result = loop_body(this);
let result = loop_body(this, inspect);
this.try_finalize_goal(canonical_input, result).then(|| result)
},
)

View File

@ -61,21 +61,24 @@ fn consider_impl_candidate(
},
};
ecx.probe(|ecx| {
let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
ecx.probe_candidate(
|ecx| {
let impl_substs = ecx.fresh_substs_for_item(impl_def_id);
let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs);
ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
let where_clause_bounds = tcx
.predicates_of(impl_def_id)
.instantiate(tcx, impl_substs)
.predicates
.into_iter()
.map(|pred| goal.with(tcx, pred));
ecx.add_goals(where_clause_bounds);
ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
let where_clause_bounds = tcx
.predicates_of(impl_def_id)
.instantiate(tcx, impl_substs)
.predicates
.into_iter()
.map(|pred| goal.with(tcx, pred));
ecx.add_goals(where_clause_bounds);
ecx.evaluate_added_goals_and_make_canonical_response(maximal_certainty)
})
ecx.evaluate_added_goals_and_make_canonical_response(maximal_certainty)
},
|| "impl".into(),
)
}
fn probe_and_match_goal_against_assumption(
@ -89,7 +92,7 @@ fn probe_and_match_goal_against_assumption(
&& trait_clause.polarity() == goal.predicate.polarity
{
// FIXME: Constness
ecx.probe(|ecx| {
ecx.probe_candidate(|ecx| {
let assumption_trait_pred =
ecx.instantiate_binder_with_infer(trait_clause);
ecx.eq(
@ -98,7 +101,7 @@ fn probe_and_match_goal_against_assumption(
assumption_trait_pred.trait_ref,
)?;
then(ecx)
})
}, || "assumption".into())
} else {
Err(NoSolution)
}
@ -132,13 +135,16 @@ fn consider_trait_alias_candidate(
let tcx = ecx.tcx();
ecx.probe(|ecx| {
let nested_obligations = tcx
.predicates_of(goal.predicate.def_id())
.instantiate(tcx, goal.predicate.trait_ref.substs);
ecx.add_goals(nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p)));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
ecx.probe_candidate(
|ecx| {
let nested_obligations = tcx
.predicates_of(goal.predicate.def_id())
.instantiate(tcx, goal.predicate.trait_ref.substs);
ecx.add_goals(nested_obligations.predicates.into_iter().map(|p| goal.with(tcx, p)));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "trait alias".into(),
)
}
fn consider_builtin_sized_candidate(
@ -344,109 +350,116 @@ fn consider_builtin_unsize_candidate(
if b_ty.is_ty_var() {
return ecx.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
}
ecx.probe(|ecx| {
match (a_ty.kind(), b_ty.kind()) {
// Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`
(&ty::Dynamic(_, _, ty::Dyn), &ty::Dynamic(_, _, ty::Dyn)) => {
// Dyn upcasting is handled separately, since due to upcasting,
// when there are two supertraits that differ by substs, we
// may return more than one query response.
Err(NoSolution)
}
// `T` -> `dyn Trait` unsizing
(_, &ty::Dynamic(data, region, ty::Dyn)) => {
// Can only unsize to an object-safe type
if data
.principal_def_id()
.is_some_and(|def_id| !tcx.check_is_object_safe(def_id))
{
return Err(NoSolution);
ecx.probe_candidate(
|ecx| {
match (a_ty.kind(), b_ty.kind()) {
// Trait upcasting, or `dyn Trait + Auto + 'a` -> `dyn Trait + 'b`
(&ty::Dynamic(_, _, ty::Dyn), &ty::Dynamic(_, _, ty::Dyn)) => {
// Dyn upcasting is handled separately, since due to upcasting,
// when there are two supertraits that differ by substs, we
// may return more than one query response.
Err(NoSolution)
}
// `T` -> `dyn Trait` unsizing
(_, &ty::Dynamic(data, region, ty::Dyn)) => {
// Can only unsize to an object-safe type
if data
.principal_def_id()
.is_some_and(|def_id| !tcx.check_is_object_safe(def_id))
{
return Err(NoSolution);
}
let Some(sized_def_id) = tcx.lang_items().sized_trait() else {
let Some(sized_def_id) = tcx.lang_items().sized_trait() else {
return Err(NoSolution);
};
// Check that the type implements all of the predicates of the def-id.
// (i.e. the principal, all of the associated types match, and any auto traits)
ecx.add_goals(
data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))),
);
// The type must be Sized to be unsized.
ecx.add_goal(goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty])));
// The type must outlive the lifetime of the `dyn` we're unsizing into.
ecx.add_goal(
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
// `[T; n]` -> `[T]` unsizing
(&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => {
// We just require that the element type stays the same
ecx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
// Struct unsizing `Struct<T>` -> `Struct<U>` where `T: Unsize<U>`
(&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs))
if a_def.is_struct() && a_def.did() == b_def.did() =>
{
let unsizing_params = tcx.unsizing_params_for_adt(a_def.did());
// We must be unsizing some type parameters. This also implies
// that the struct has a tail field.
if unsizing_params.is_empty() {
return Err(NoSolution);
// Check that the type implements all of the predicates of the def-id.
// (i.e. the principal, all of the associated types match, and any auto traits)
ecx.add_goals(
data.iter().map(|pred| goal.with(tcx, pred.with_self_ty(tcx, a_ty))),
);
// The type must be Sized to be unsized.
ecx.add_goal(goal.with(tcx, ty::TraitRef::new(tcx, sized_def_id, [a_ty])));
// The type must outlive the lifetime of the `dyn` we're unsizing into.
ecx.add_goal(
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_ty, region))),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
// `[T; n]` -> `[T]` unsizing
(&ty::Array(a_elem_ty, ..), &ty::Slice(b_elem_ty)) => {
// We just require that the element type stays the same
ecx.eq(goal.param_env, a_elem_ty, b_elem_ty)?;
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
// Struct unsizing `Struct<T>` -> `Struct<U>` where `T: Unsize<U>`
(&ty::Adt(a_def, a_substs), &ty::Adt(b_def, b_substs))
if a_def.is_struct() && a_def.did() == b_def.did() =>
{
let unsizing_params = tcx.unsizing_params_for_adt(a_def.did());
// We must be unsizing some type parameters. This also implies
// that the struct has a tail field.
if unsizing_params.is_empty() {
return Err(NoSolution);
}
let tail_field = a_def
.non_enum_variant()
.fields
.raw
.last()
.expect("expected unsized ADT to have a tail field");
let tail_field_ty = tcx.type_of(tail_field.did);
let tail_field = a_def
.non_enum_variant()
.fields
.raw
.last()
.expect("expected unsized ADT to have a tail field");
let tail_field_ty = tcx.type_of(tail_field.did);
let a_tail_ty = tail_field_ty.subst(tcx, a_substs);
let b_tail_ty = tail_field_ty.subst(tcx, b_substs);
let a_tail_ty = tail_field_ty.subst(tcx, a_substs);
let b_tail_ty = tail_field_ty.subst(tcx, b_substs);
// Substitute just the unsizing params from B into A. The type after
// this substitution must be equal to B. This is so we don't unsize
// unrelated type parameters.
let new_a_substs =
tcx.mk_substs_from_iter(a_substs.iter().enumerate().map(|(i, a)| {
if unsizing_params.contains(i as u32) { b_substs[i] } else { a }
}));
let unsized_a_ty = tcx.mk_adt(a_def, new_a_substs);
// Substitute just the unsizing params from B into A. The type after
// this substitution must be equal to B. This is so we don't unsize
// unrelated type parameters.
let new_a_substs =
tcx.mk_substs_from_iter(a_substs.iter().enumerate().map(|(i, a)| {
if unsizing_params.contains(i as u32) { b_substs[i] } else { a }
}));
let unsized_a_ty = tcx.mk_adt(a_def, new_a_substs);
// Finally, we require that `TailA: Unsize<TailB>` for the tail field
// types.
ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
ecx.add_goal(goal.with(
tcx,
ty::TraitRef::new(tcx, goal.predicate.def_id(), [a_tail_ty, b_tail_ty]),
));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
// Finally, we require that `TailA: Unsize<TailB>` for the tail field
// types.
ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
ecx.add_goal(goal.with(
tcx,
ty::TraitRef::new(tcx, goal.predicate.def_id(), [a_tail_ty, b_tail_ty]),
));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
// Tuple unsizing `(.., T)` -> `(.., U)` where `T: Unsize<U>`
(&ty::Tuple(a_tys), &ty::Tuple(b_tys))
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
{
let (a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
let b_last_ty = b_tys.last().unwrap();
// Substitute just the tail field of B., and require that they're equal.
let unsized_a_ty =
tcx.mk_tup_from_iter(a_rest_tys.iter().chain([b_last_ty]).copied());
ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
// Similar to ADTs, require that the rest of the fields are equal.
ecx.add_goal(goal.with(
tcx,
ty::TraitRef::new(
tcx,
goal.predicate.def_id(),
[*a_last_ty, *b_last_ty],
),
));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
_ => Err(NoSolution),
}
// Tuple unsizing `(.., T)` -> `(.., U)` where `T: Unsize<U>`
(&ty::Tuple(a_tys), &ty::Tuple(b_tys))
if a_tys.len() == b_tys.len() && !a_tys.is_empty() =>
{
let (a_last_ty, a_rest_tys) = a_tys.split_last().unwrap();
let b_last_ty = b_tys.last().unwrap();
// Substitute just the tail field of B., and require that they're equal.
let unsized_a_ty =
tcx.mk_tup_from_iter(a_rest_tys.iter().chain([b_last_ty]).copied());
ecx.eq(goal.param_env, unsized_a_ty, b_ty)?;
// Similar to ADTs, require that the rest of the fields are equal.
ecx.add_goal(goal.with(
tcx,
ty::TraitRef::new(tcx, goal.predicate.def_id(), [*a_last_ty, *b_last_ty]),
));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
}
_ => Err(NoSolution),
}
})
},
|| "builtin unsize".into(),
)
}
fn consider_builtin_dyn_upcast_candidates(
@ -475,34 +488,39 @@ fn consider_builtin_dyn_upcast_candidates(
return vec![];
}
let mut unsize_dyn_to_principal = |principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
ecx.probe(|ecx| -> Result<_, NoSolution> {
// Require that all of the trait predicates from A match B, except for
// the auto traits. We do this by constructing a new A type with B's
// auto traits, and equating these types.
let new_a_data = principal
.into_iter()
.map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait))
.chain(a_data.iter().filter(|a| {
matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_))
}))
.chain(
b_data
.auto_traits()
.map(ty::ExistentialPredicate::AutoTrait)
.map(ty::Binder::dummy),
);
let new_a_data = tcx.mk_poly_existential_predicates_from_iter(new_a_data);
let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);
let mut unsize_dyn_to_principal =
|principal: Option<ty::PolyExistentialTraitRef<'tcx>>| {
ecx.probe_candidate(
|ecx| -> Result<_, NoSolution> {
// Require that all of the trait predicates from A match B, except for
// the auto traits. We do this by constructing a new A type with B's
// auto traits, and equating these types.
let new_a_data = principal
.into_iter()
.map(|trait_ref| trait_ref.map_bound(ty::ExistentialPredicate::Trait))
.chain(a_data.iter().filter(|a| {
matches!(a.skip_binder(), ty::ExistentialPredicate::Projection(_))
}))
.chain(
b_data
.auto_traits()
.map(ty::ExistentialPredicate::AutoTrait)
.map(ty::Binder::dummy),
);
let new_a_data = tcx.mk_poly_existential_predicates_from_iter(new_a_data);
let new_a_ty = tcx.mk_dynamic(new_a_data, b_region, ty::Dyn);
// We also require that A's lifetime outlives B's lifetime.
ecx.eq(goal.param_env, new_a_ty, b_ty)?;
ecx.add_goal(
goal.with(tcx, ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region))),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
};
// We also require that A's lifetime outlives B's lifetime.
ecx.eq(goal.param_env, new_a_ty, b_ty)?;
ecx.add_goal(goal.with(
tcx,
ty::Binder::dummy(ty::OutlivesPredicate(a_region, b_region)),
));
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "upcast dyn to principle".into(),
)
};
let mut responses = vec![];
// If the principal def ids match (or are both none), then we're not doing
@ -698,20 +716,23 @@ fn probe_and_evaluate_goal_for_constituent_tys(
goal: Goal<'tcx, TraitPredicate<'tcx>>,
constituent_tys: impl Fn(&EvalCtxt<'_, 'tcx>, Ty<'tcx>) -> Result<Vec<Ty<'tcx>>, NoSolution>,
) -> QueryResult<'tcx> {
self.probe(|ecx| {
ecx.add_goals(
constituent_tys(ecx, goal.predicate.self_ty())?
.into_iter()
.map(|ty| {
goal.with(
ecx.tcx(),
ty::Binder::dummy(goal.predicate.with_self_ty(ecx.tcx(), ty)),
)
})
.collect::<Vec<_>>(),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
self.probe_candidate(
|ecx| {
ecx.add_goals(
constituent_tys(ecx, goal.predicate.self_ty())?
.into_iter()
.map(|ty| {
goal.with(
ecx.tcx(),
ty::Binder::dummy(goal.predicate.with_self_ty(ecx.tcx(), ty)),
)
})
.collect::<Vec<_>>(),
);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
},
|| "constituent tys".into(),
)
}
#[instrument(level = "debug", skip(self))]