Skip to content

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

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 8, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions compiler/rustc_borrowck/src/type_check/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,10 +266,6 @@ impl<'tcx, OP> TypeVisitor<TyCtxt<'tcx>> for ConstrainOpaqueTypeRegionVisitor<'t
where
OP: FnMut(ty::Region<'tcx>),
{
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) {
t.super_visit_with(self);
}

fn visit_region(&mut self, r: ty::Region<'tcx>) {
match r.kind() {
// ignore bound regions, keep visiting
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{AmbigArg, HirId};
use rustc_middle::bug;
use rustc_middle::ty::{
self as ty, IsSuggestable, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
TypeVisitor, Upcast,
self as ty, IsSuggestable, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
TypeVisitableExt, TypeVisitor, Upcast,
};
use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, kw, sym};
use rustc_trait_selection::traits;
Expand Down Expand Up @@ -996,7 +996,7 @@ struct GenericParamAndBoundVarCollector<'a, 'tcx> {
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for GenericParamAndBoundVarCollector<'_, 'tcx> {
type Result = ControlFlow<ErrorGuaranteed>;

fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
fn visit_binder<T: TypeFoldable<TyCtxt<'tcx>>>(
&mut self,
binder: &ty::Binder<'tcx, T>,
) -> Self::Result {
Expand Down
4 changes: 0 additions & 4 deletions compiler/rustc_infer/src/infer/outlives/for_liveness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ impl<'tcx, OP> TypeVisitor<TyCtxt<'tcx>> for FreeRegionsVisitor<'tcx, OP>
where
OP: FnMut(ty::Region<'tcx>),
{
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) {
t.super_visit_with(self);
}

fn visit_region(&mut self, r: ty::Region<'tcx>) {
match r.kind() {
// ignore bound regions, keep visiting
Expand Down
5 changes: 3 additions & 2 deletions compiler/rustc_lint/src/impl_trait_overcaptures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ use rustc_middle::ty::relate::{
Relate, RelateResult, TypeRelation, structurally_relate_consts, structurally_relate_tys,
};
use rustc_middle::ty::{
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
self, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt,
TypeVisitor,
};
use rustc_middle::{bug, span_bug};
use rustc_session::lint::FutureIncompatibilityReason;
Expand Down Expand Up @@ -209,7 +210,7 @@ where
VarFn: FnOnce() -> FxHashMap<DefId, ty::Variance>,
OutlivesFn: FnOnce() -> OutlivesEnvironment<'tcx>,
{
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) {
fn visit_binder<T: TypeFoldable<TyCtxt<'tcx>>>(&mut self, t: &ty::Binder<'tcx, T>) {
// When we get into a binder, we need to add its own bound vars to the scope.
let mut added = vec![];
for arg in t.bound_vars() {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/ty/print/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2934,7 +2934,7 @@ impl<'tcx> FmtPrinter<'_, 'tcx> {

fn prepare_region_info<T>(&mut self, value: &ty::Binder<'tcx, T>)
where
T: TypeVisitable<TyCtxt<'tcx>>,
T: TypeFoldable<TyCtxt<'tcx>>,
{
struct RegionNameCollector<'tcx> {
used_region_names: FxHashSet<Symbol>,
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/ty/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl<'tcx> TyCtxt<'tcx> {
{
type Result = ControlFlow<()>;

fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
fn visit_binder<T: TypeFoldable<TyCtxt<'tcx>>>(
&mut self,
t: &Binder<'tcx, T>,
) -> Self::Result {
Expand Down Expand Up @@ -168,7 +168,7 @@ impl LateBoundRegionsCollector {
}

impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for LateBoundRegionsCollector {
fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(&mut self, t: &Binder<'tcx, T>) {
fn visit_binder<T: TypeFoldable<TyCtxt<'tcx>>>(&mut self, t: &Binder<'tcx, T>) {
self.current_index.shift_in(1);
t.super_visit_with(self);
self.current_index.shift_out(1);
Expand Down
176 changes: 153 additions & 23 deletions compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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::ParamEnv(ParamEnvSource::Global),
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 `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>,
Expand Down Expand Up @@ -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));
}
}

Expand Down Expand Up @@ -943,4 +989,88 @@ where
}
}
}

/// Compute whether a param-env assumption is global or non-global after normalizing it.
///
/// This is necessary because, for example, given:
///
/// ```ignore,rust
/// where
/// T: Trait<Assoc = u32>,
/// i32: From<T::Assoc>,
/// ```
///
/// The `i32: From<T::Assoc>` bound is non-global before normalization, but is global after.
/// Since the old trait solver normalized param-envs eagerly, we want to emulate this
/// behavior lazily.
fn characterize_param_env_assumption(
&mut self,
param_env: I::ParamEnv,
assumption: I::Clause,
) -> Result<CandidateSource<I>, NoSolution> {
// FIXME: This should be fixed, but it also requires changing the behavior
// in the old solver which is currently relied on.
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));
};

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));
};

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 self.ecx.eager_resolve_region(r).kind() {
ty::ReStatic | ty::ReError(_) => ControlFlow::Continue(()),
ty::ReVar(_) | ty::RePlaceholder(_) => ControlFlow::Break(Ok(())),
ty::ReErased | ty::ReEarlyParam(_) | ty::ReLateParam(_) | ty::ReBound(..) => {
unreachable!()
}
}
}
}
45 changes: 22 additions & 23 deletions compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,38 @@ where
self.def_id()
}

fn probe_and_match_goal_against_assumption(
fn fast_reject_assumption(
ecx: &mut EvalCtxt<'_, D>,
source: rustc_type_ir::solve::CandidateSource<I>,
goal: Goal<I, Self>,
assumption: <I as Interner>::Clause,
then: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution> {
assumption: I::Clause,
) -> Result<(), NoSolution> {
if let Some(host_clause) = assumption.as_host_effect_clause() {
if host_clause.def_id() == goal.predicate.def_id()
&& host_clause.constness().satisfies(goal.predicate.constness)
{
if !DeepRejectCtxt::relate_rigid_rigid(ecx.cx()).args_may_unify(
if DeepRejectCtxt::relate_rigid_rigid(ecx.cx()).args_may_unify(
goal.predicate.trait_ref.args,
host_clause.skip_binder().trait_ref.args,
) {
return Err(NoSolution);
return Ok(());
}

ecx.probe_trait_candidate(source).enter(|ecx| {
let assumption_trait_pred = ecx.instantiate_binder_with_infer(host_clause);
ecx.eq(
goal.param_env,
goal.predicate.trait_ref,
assumption_trait_pred.trait_ref,
)?;
then(ecx)
})
} else {
Err(NoSolution)
}
} else {
Err(NoSolution)
}

Err(NoSolution)
}

fn match_assumption(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
assumption: I::Clause,
) -> Result<(), NoSolution> {
let host_clause = assumption.as_host_effect_clause().unwrap();

let assumption_trait_pred = ecx.instantiate_binder_with_infer(host_clause);
ecx.eq(goal.param_env, goal.predicate.trait_ref, assumption_trait_pred.trait_ref)?;

Ok(())
}

/// Register additional assumptions for aliases corresponding to `~const` item bounds.
Expand Down Expand Up @@ -124,7 +123,7 @@ where
fn consider_impl_candidate(
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
impl_def_id: <I as Interner>::DefId,
impl_def_id: I::DefId,
) -> Result<Candidate<I>, NoSolution> {
let cx = ecx.cx();

Expand Down Expand Up @@ -178,7 +177,7 @@ where

fn consider_error_guaranteed_candidate(
ecx: &mut EvalCtxt<'_, D>,
_guar: <I as Interner>::ErrorGuaranteed,
_guar: I::ErrorGuaranteed,
) -> Result<Candidate<I>, NoSolution> {
ecx.probe_builtin_trait_candidate(BuiltinImplSource::Misc)
.enter(|ecx| ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes))
Expand Down
Loading
Loading