Only prefer param-env candidates if they remain non-global after norm

This commit is contained in:
Michael Goulet
2025-05-06 20:24:23 +00:00
parent df13f7c1fa
commit 1f774d74b3
8 changed files with 268 additions and 145 deletions

View File

@@ -2,21 +2,24 @@
pub(super) mod structural_traits;
use std::ops::ControlFlow;
use derive_where::derive_where;
use rustc_type_ir::inherent::*;
use rustc_type_ir::lang_items::TraitSolverLangItem;
use rustc_type_ir::{
self as ty, Interner, TypeFoldable, TypeVisitableExt as _, TypingMode, Upcast as _, elaborate,
self as ty, Interner, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt as _,
TypeVisitor, TypingMode, Upcast as _, elaborate,
};
use tracing::{debug, instrument};
use super::has_only_region_constraints;
use super::trait_goals::TraitGoalProvenVia;
use super::{has_only_region_constraints, inspect};
use crate::delegate::SolverDelegate;
use crate::solve::inspect::ProbeKind;
use crate::solve::{
BuiltinImplSource, CandidateSource, CanonicalResponse, Certainty, EvalCtxt, Goal, GoalSource,
MaybeCause, NoSolution, QueryResult,
MaybeCause, NoSolution, ParamEnvSource, QueryResult,
};
enum AliasBoundKind {
@@ -49,18 +52,6 @@ where
fn trait_def_id(self, cx: I) -> I::DefId;
/// Try equating an assumption predicate against a goal's predicate. If it
/// holds, then execute the `then` callback, which should do any additional
/// work, then produce a response (typically by executing
/// [`EvalCtxt::evaluate_added_goals_and_make_canonical_response`]).
fn probe_and_match_goal_against_assumption(
ecx: &mut EvalCtxt<'_, D>,
source: CandidateSource<I>,
goal: Goal<I, Self>,
assumption: I::Clause,
then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution>;
/// Consider a clause, which consists of a "assumption" and some "requirements",
/// to satisfy a goal. If the requirements hold, then attempt to satisfy our
/// goal by equating it with the assumption.
@@ -119,6 +110,67 @@ where
alias_ty: ty::AliasTy<I>,
) -> Vec<Candidate<I>>;
fn probe_and_consider_param_env_candidate(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
assumption: I::Clause,
) -> Result<Candidate<I>, NoSolution> {
Self::fast_reject_assumption(ecx, goal, assumption)?;
ecx.probe(|candidate: &Result<Candidate<I>, NoSolution>| match candidate {
Ok(candidate) => inspect::ProbeKind::TraitCandidate {
source: candidate.source,
result: Ok(candidate.result),
},
Err(NoSolution) => inspect::ProbeKind::TraitCandidate {
source: CandidateSource::BuiltinImpl(BuiltinImplSource::Misc),
result: Err(NoSolution),
},
})
.enter(|ecx| {
Self::match_assumption(ecx, goal, assumption)?;
let source = ecx.characterize_param_env_assumption(goal.param_env, assumption)?;
Ok(Candidate {
source,
result: ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)?,
})
})
}
/// Try equating an assumption predicate against a goal's predicate. If it
/// holds, then execute the `then` callback, which should do any additional
/// work, then produce a response (typically by executing
/// [`EvalCtxt::evaluate_added_goals_and_make_canonical_response`]).
fn probe_and_match_goal_against_assumption(
ecx: &mut EvalCtxt<'_, D>,
source: CandidateSource<I>,
goal: Goal<I, Self>,
assumption: I::Clause,
then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution> {
Self::fast_reject_assumption(ecx, goal, assumption)?;
ecx.probe_trait_candidate(source).enter(|ecx| {
Self::match_assumption(ecx, goal, assumption)?;
then(ecx)
})
}
/// Try to reject the assumption based off of simple heuristics, such as [`ty::ClauseKind`]
/// and [`I::DefId`].
fn fast_reject_assumption(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
assumption: I::Clause,
) -> Result<(), NoSolution>;
/// Relate the goal and assumption.
fn match_assumption(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
assumption: I::Clause,
) -> Result<(), NoSolution>;
fn consider_impl_candidate(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
@@ -500,14 +552,8 @@ where
goal: Goal<I, G>,
candidates: &mut Vec<Candidate<I>>,
) {
for (i, assumption) in goal.param_env.caller_bounds().iter().enumerate() {
candidates.extend(G::probe_and_consider_implied_clause(
self,
CandidateSource::ParamEnv(i),
goal,
assumption,
[],
));
for assumption in goal.param_env.caller_bounds().iter() {
candidates.extend(G::probe_and_consider_param_env_candidate(self, goal, assumption));
}
}
@@ -943,4 +989,76 @@ where
}
}
}
fn characterize_param_env_assumption(
&mut self,
param_env: I::ParamEnv,
assumption: I::Clause,
) -> Result<CandidateSource<I>, NoSolution> {
// FIXME:
if assumption.has_bound_vars() {
return Ok(CandidateSource::ParamEnv(ParamEnvSource::NonGlobal));
}
match assumption.visit_with(&mut FindParamInClause { ecx: self, param_env }) {
ControlFlow::Break(Err(NoSolution)) => Err(NoSolution),
ControlFlow::Break(Ok(())) => Ok(CandidateSource::ParamEnv(ParamEnvSource::NonGlobal)),
ControlFlow::Continue(()) => Ok(CandidateSource::ParamEnv(ParamEnvSource::Global)),
}
}
}
struct FindParamInClause<'a, 'b, D: SolverDelegate<Interner = I>, I: Interner> {
ecx: &'a mut EvalCtxt<'b, D>,
param_env: I::ParamEnv,
}
impl<D, I> TypeVisitor<I> for FindParamInClause<'_, '_, D, I>
where
D: SolverDelegate<Interner = I>,
I: Interner,
{
type Result = ControlFlow<Result<(), NoSolution>>;
fn visit_binder<T: TypeFoldable<I>>(&mut self, t: &ty::Binder<I, T>) -> Self::Result {
self.ecx.enter_forall(t.clone(), |ecx, v| {
v.visit_with(&mut FindParamInClause { ecx, param_env: self.param_env })
})
}
fn visit_ty(&mut self, ty: I::Ty) -> Self::Result {
let Ok(ty) = self.ecx.structurally_normalize_ty(self.param_env, ty) else {
return ControlFlow::Break(Err(NoSolution));
};
let ty = self.ecx.eager_resolve(ty);
if let ty::Placeholder(_) = ty.kind() {
ControlFlow::Break(Ok(()))
} else {
ty.super_visit_with(self)
}
}
fn visit_const(&mut self, ct: I::Const) -> Self::Result {
let Ok(ct) = self.ecx.structurally_normalize_const(self.param_env, ct) else {
return ControlFlow::Break(Err(NoSolution));
};
let ct = self.ecx.eager_resolve(ct);
if let ty::ConstKind::Placeholder(_) = ct.kind() {
ControlFlow::Break(Ok(()))
} else {
ct.super_visit_with(self)
}
}
fn visit_region(&mut self, r: I::Region) -> Self::Result {
match r.kind() {
ty::ReStatic | ty::ReError(_) => ControlFlow::Continue(()),
ty::ReVar(_) | ty::RePlaceholder(_) => ControlFlow::Break(Ok(())),
ty::ReErased | ty::ReEarlyParam(_) | ty::ReLateParam(_) | ty::ReBound(..) => {
unreachable!()
}
}
}
}