Skip to content

ignore head usages from ignored candidates #144991

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
99 changes: 66 additions & 33 deletions compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ 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::search_graph::CandidateHeadUsages;
use rustc_type_ir::solve::SizedTraitKind;
use rustc_type_ir::{
self as ty, Interner, TypeFlags, TypeFoldable, TypeSuperVisitable, TypeVisitable,
Expand All @@ -33,10 +34,11 @@ enum AliasBoundKind {
///
/// It consists of both the `source`, which describes how that goal would be proven,
/// and the `result` when using the given `source`.
#[derive_where(Clone, Debug; I: Interner)]
#[derive_where(Debug; I: Interner)]
pub(super) struct Candidate<I: Interner> {
pub(super) source: CandidateSource<I>,
pub(super) result: CanonicalResponse<I>,
pub(super) head_usages: CandidateHeadUsages,
}

/// Methods used to assemble candidates for either trait or projection goals.
Expand Down Expand Up @@ -116,8 +118,11 @@ where
ecx: &mut EvalCtxt<'_, D>,
goal: Goal<I, Self>,
assumption: I::Clause,
) -> Result<Candidate<I>, NoSolution> {
Self::fast_reject_assumption(ecx, goal, assumption)?;
) -> Result<Candidate<I>, CandidateHeadUsages> {
match Self::fast_reject_assumption(ecx, goal, assumption) {
Ok(()) => {}
Err(NoSolution) => return Err(CandidateHeadUsages::default()),
}

// Dealing with `ParamEnv` candidates is a bit of a mess as we need to lazily
// check whether the candidate is global while considering normalization.
Expand All @@ -126,18 +131,23 @@ where
// in `probe` even if the candidate does not apply before we get there. We handle this
// by using a `Cell` here. We only ever write into it inside of `match_assumption`.
let source = Cell::new(CandidateSource::ParamEnv(ParamEnvSource::Global));
ecx.probe(|result: &QueryResult<I>| inspect::ProbeKind::TraitCandidate {
source: source.get(),
result: *result,
})
.enter(|ecx| {
Self::match_assumption(ecx, goal, assumption, |ecx| {
ecx.try_evaluate_added_goals()?;
source.set(ecx.characterize_param_env_assumption(goal.param_env, assumption)?);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
let (result, head_usages) = ecx
.probe(|result: &QueryResult<I>| inspect::ProbeKind::TraitCandidate {
source: source.get(),
result: *result,
})
})
.map(|result| Candidate { source: source.get(), result })
.enter_single_candidate(|ecx| {
Self::match_assumption(ecx, goal, assumption, |ecx| {
ecx.try_evaluate_added_goals()?;
source.set(ecx.characterize_param_env_assumption(goal.param_env, assumption)?);
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
})
});

match result {
Ok(result) => Ok(Candidate { source: source.get(), result, head_usages }),
Err(NoSolution) => Err(head_usages),
}
}

/// Try equating an assumption predicate against a goal's predicate. If it
Expand Down Expand Up @@ -355,6 +365,19 @@ pub(super) enum AssembleCandidatesFrom {
EnvAndBounds,
}

/// This is currently used to track the [CandidateHeadUsages] of all failed `ParamEnv`
/// candidates. This is then used to ignore their head usages in case there's another
/// always applicable `ParamEnv` candidate. Look at how `param_env_head_usages` is
/// used in the code for more details.
///
/// We could easily extend this to also ignore head usages of other ignored candidates.
/// However, we currently don't have any tests where this matters and the complexity of
/// doing so does not feel worth it for now.
#[derive(Debug)]
pub(super) struct FailedCandidateInfo {
pub param_env_head_usages: CandidateHeadUsages,
}

impl<D, I> EvalCtxt<'_, D>
where
D: SolverDelegate<Interner = I>,
Expand All @@ -364,16 +387,20 @@ where
&mut self,
goal: Goal<I, G>,
assemble_from: AssembleCandidatesFrom,
) -> Vec<Candidate<I>> {
) -> (Vec<Candidate<I>>, FailedCandidateInfo) {
let mut candidates = vec![];
let mut failed_candidate_info =
FailedCandidateInfo { param_env_head_usages: CandidateHeadUsages::default() };
let Ok(normalized_self_ty) =
self.structurally_normalize_ty(goal.param_env, goal.predicate.self_ty())
else {
return vec![];
return (candidates, failed_candidate_info);
};

if normalized_self_ty.is_ty_var() {
debug!("self type has been normalized to infer");
return self.forced_ambiguity(MaybeCause::Ambiguity).into_iter().collect();
candidates.extend(self.forced_ambiguity(MaybeCause::Ambiguity));
return (candidates, failed_candidate_info);
}

let goal: Goal<I, G> = goal
Expand All @@ -382,16 +409,15 @@ where
// normalizing the self type as well, since type variables are not uniquified.
let goal = self.resolve_vars_if_possible(goal);

let mut candidates = vec![];

if let TypingMode::Coherence = self.typing_mode()
&& let Ok(candidate) = self.consider_coherence_unknowable_candidate(goal)
{
return vec![candidate];
candidates.push(candidate);
return (candidates, failed_candidate_info);
}

self.assemble_alias_bound_candidates(goal, &mut candidates);
self.assemble_param_env_candidates(goal, &mut candidates);
self.assemble_param_env_candidates(goal, &mut candidates, &mut failed_candidate_info);

match assemble_from {
AssembleCandidatesFrom::All => {
Expand Down Expand Up @@ -423,7 +449,7 @@ where
AssembleCandidatesFrom::EnvAndBounds => {}
}

candidates
(candidates, failed_candidate_info)
}

pub(super) fn forced_ambiguity(
Expand Down Expand Up @@ -584,9 +610,15 @@ where
&mut self,
goal: Goal<I, G>,
candidates: &mut Vec<Candidate<I>>,
failed_candidate_info: &mut FailedCandidateInfo,
) {
for assumption in goal.param_env.caller_bounds().iter() {
candidates.extend(G::probe_and_consider_param_env_candidate(self, goal, assumption));
match G::probe_and_consider_param_env_candidate(self, goal, assumption) {
Ok(candidate) => candidates.push(candidate),
Err(head_usages) => {
failed_candidate_info.param_env_head_usages.merge_usages(head_usages)
}
}
}
}

Expand Down Expand Up @@ -661,7 +693,11 @@ where
if let Ok(result) =
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
{
candidates.push(Candidate { source: CandidateSource::AliasBound, result });
candidates.push(Candidate {
source: CandidateSource::AliasBound,
result,
head_usages: CandidateHeadUsages::default(),
});
}
return;
}
Expand Down Expand Up @@ -959,7 +995,7 @@ where
// Even when a trait bound has been proven using a where-bound, we
// still need to consider alias-bounds for normalization, see
// `tests/ui/next-solver/alias-bound-shadowed-by-env.rs`.
let mut candidates: Vec<_> = self
let (mut candidates, _) = self
.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::EnvAndBounds);

// We still need to prefer where-bounds over alias-bounds however.
Expand All @@ -972,23 +1008,20 @@ where
return inject_normalize_to_rigid_candidate(self);
}

if let Some(response) = self.try_merge_candidates(&candidates) {
if let Some((response, _)) = self.try_merge_candidates(&candidates) {
Ok(response)
} else {
self.flounder(&candidates)
}
}
TraitGoalProvenVia::Misc => {
let mut candidates =
let (mut candidates, _) =
self.assemble_and_evaluate_candidates(goal, AssembleCandidatesFrom::All);

// Prefer "orphaned" param-env normalization predicates, which are used
// (for example, and ideally only) when proving item bounds for an impl.
let candidates_from_env: Vec<_> = candidates
.extract_if(.., |c| matches!(c.source, CandidateSource::ParamEnv(_)))
.collect();
if let Some(response) = self.try_merge_candidates(&candidates_from_env) {
return Ok(response);
if candidates.iter().any(|c| matches!(c.source, CandidateSource::ParamEnv(_))) {
candidates.retain(|c| matches!(c.source, CandidateSource::ParamEnv(_)));
}

// We drop specialized impls to allow normalization via a final impl here. In case
Expand All @@ -997,7 +1030,7 @@ where
// means we can just ignore inference constraints and don't have to special-case
// constraining the normalized-to `term`.
self.filter_specialized_impls(AllowInferenceConstraints::Yes, &mut candidates);
if let Some(response) = self.try_merge_candidates(&candidates) {
if let Some((response, _)) = self.try_merge_candidates(&candidates) {
Ok(response)
} else {
self.flounder(&candidates)
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use rustc_type_ir::fast_reject::DeepRejectCtxt;
use rustc_type_ir::inherent::*;
use rustc_type_ir::relate::Relate;
use rustc_type_ir::relate::solver_relating::RelateExt;
use rustc_type_ir::search_graph::PathKind;
use rustc_type_ir::search_graph::{CandidateHeadUsages, PathKind};
use rustc_type_ir::{
self as ty, CanonicalVarValues, InferCtxtLike, Interner, TypeFoldable, TypeFolder,
TypeSuperFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
Expand Down Expand Up @@ -399,6 +399,10 @@ where
result
}

pub(super) fn ignore_candidate_head_usages(&mut self, usages: CandidateHeadUsages) {
self.search_graph.ignore_candidate_head_usages(usages);
}

/// Recursively evaluates `goal`, returning whether any inference vars have
/// been constrained and the certainty of the result.
fn evaluate_goal(
Expand Down
18 changes: 17 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::marker::PhantomData;

use rustc_type_ir::search_graph::CandidateHeadUsages;
use rustc_type_ir::{InferCtxtLike, Interner};
use tracing::instrument;

Expand All @@ -25,6 +26,20 @@ where
D: SolverDelegate<Interner = I>,
I: Interner,
{
pub(in crate::solve) fn enter_single_candidate(
self,
f: impl FnOnce(&mut EvalCtxt<'_, D>) -> T,
) -> (T, CandidateHeadUsages) {
self.ecx.search_graph.enter_single_candidate();
let mut candidate_usages = CandidateHeadUsages::default();
let result = self.enter(|ecx| {
let result = f(ecx);
candidate_usages = ecx.search_graph.finish_single_candidate();
result
});
(result, candidate_usages)
}

pub(in crate::solve) fn enter(self, f: impl FnOnce(&mut EvalCtxt<'_, D>) -> T) -> T {
let ProbeCtxt { ecx: outer, probe_kind, _result } = self;

Expand Down Expand Up @@ -78,7 +93,8 @@ where
self,
f: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> Result<Candidate<I>, NoSolution> {
self.cx.enter(|ecx| f(ecx)).map(|result| Candidate { source: self.source, result })
let (result, head_usages) = self.cx.enter_single_candidate(f);
result.map(|result| Candidate { source: self.source, result, head_usages })
}
}

Expand Down
26 changes: 17 additions & 9 deletions compiler/rustc_next_trait_solver/src/solve/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ where
}
}

#[derive(Debug)]
enum MergeCandidateInfo {
AlwaysApplicable(usize),
EqualResponse,
}

impl<D, I> EvalCtxt<'_, D>
where
D: SolverDelegate<Interner = I>,
Expand All @@ -248,23 +254,25 @@ where
fn try_merge_candidates(
&mut self,
candidates: &[Candidate<I>],
) -> Option<CanonicalResponse<I>> {
) -> Option<(CanonicalResponse<I>, MergeCandidateInfo)> {
if candidates.is_empty() {
return None;
}

let always_applicable = candidates.iter().enumerate().find(|(_, candidate)| {
candidate.result.value.certainty == Certainty::Yes
&& has_no_inference_or_external_constraints(candidate.result)
});
if let Some((i, c)) = always_applicable {
return Some((c.result, MergeCandidateInfo::AlwaysApplicable(i)));
}

let one: CanonicalResponse<I> = candidates[0].result;
if candidates[1..].iter().all(|candidate| candidate.result == one) {
return Some(one);
return Some((one, MergeCandidateInfo::EqualResponse));
}

candidates
.iter()
.find(|candidate| {
candidate.result.value.certainty == Certainty::Yes
&& has_no_inference_or_external_constraints(candidate.result)
})
.map(|candidate| candidate.result)
None
}

fn bail_with_ambiguity(&mut self, candidates: &[Candidate<I>]) -> CanonicalResponse<I> {
Expand Down
Loading