From ea402a9d3aaf1d40b4ba384501ef38dd93368ded Mon Sep 17 00:00:00 2001 From: Lucas Franceschino Date: Thu, 20 Jun 2024 14:47:05 +0200 Subject: [PATCH] fix(frontend): make `path_to` breadth-first --- frontend/exporter/src/traits.rs | 122 ++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/frontend/exporter/src/traits.rs b/frontend/exporter/src/traits.rs index 67d7cd47c..c108e7f8d 100644 --- a/frontend/exporter/src/traits.rs +++ b/frontend/exporter/src/traits.rs @@ -201,65 +201,83 @@ pub mod rustc { .collect() } - #[tracing::instrument(level = "trace", skip(s))] - fn path_to( - self, - s: &S, - target: PolyTraitRef<'tcx>, - param_env: rustc_middle::ty::ParamEnv<'tcx>, - ) -> Option> { - let tcx = s.base().tcx; - if predicate_equality(self.upcast(tcx), target.upcast(tcx), param_env, s) { - return Some(vec![]); + #[tracing::instrument(level = "trace", skip(s))] + fn path_to( + self, + s: &S, + target: PolyTraitRef<'tcx>, + param_env: rustc_middle::ty::ParamEnv<'tcx>, + ) -> Option> { + let tcx = s.base().tcx; + + /// A candidate projects `self` along a path reaching some + /// predicate. A candidate is selected when its predicate + /// is the one expected, aka `target`. + #[derive(Debug)] + struct Candidate<'tcx> { + path: Path<'tcx>, + pred: PolyTraitPredicate<'tcx>, + } + + use std::collections::VecDeque; + let mut candidates: VecDeque> = vec![Candidate { + path: vec![], + pred: self, + }] + .into(); + + let target_pred = target.to_predicate(tcx); + let mut seen = std::collections::HashSet::new(); + + while let Some(candidate) = candidates.pop_front() { + { + // If a predicate was already seen, we know it is + // not the one we are looking for: we skip it. + if seen.contains(&candidate.pred) { + continue; + } + seen.insert(candidate.pred.clone()); } + tracing::trace!("candidate={:#?}", candidate); - let recurse = |p: Self| { - if p == self { - return None; + // if the candidate equals the target, let's return its path + if predicate_equality(candidate.pred.to_predicate(tcx), target_pred, param_env, s) { + return Some(candidate.path); + } + + // otherwise, we add to the queue all paths reachable from the candidate + for (index, parent_pred) in self.parents_trait_predicates(s) { + let mut path = candidate.path.clone(); + path.push(PathChunk::Parent { + predicate: parent_pred.clone(), + predicate_id: parent_pred.predicate_id(s), + index, + }); + candidates.push_back(Candidate { + pred: parent_pred.clone(), + path, + }); + } + for (item, binder) in self.associated_items_trait_predicates(s) { + for (index, parent_pred) in binder.skip_binder().into_iter() { + let mut path = candidate.path.clone(); + path.push(PathChunk::AssocItem { + item, + predicate_id: parent_pred.predicate_id(s), + predicate: parent_pred.clone(), + index, + }); + candidates.push_back(Candidate { + pred: parent_pred.clone(), + path, + }); } - p.path_to(s, target, param_env) - }; - fn cons(hd: T, tail: Vec) -> Vec { - vec![hd].into_iter().chain(tail.into_iter()).collect() } - self.parents_trait_predicates(s) - .into_iter() - .filter_map(|(index, p)| { - recurse(p).map(|path| { - cons( - PathChunk::Parent { - predicate: p, - predicate_id: p.predicate_id(s), - index, - }, - path, - ) - }) - }) - .max_by_key(|path| path.len()) - .or_else(|| { - self.associated_items_trait_predicates(s) - .into_iter() - .filter_map(|(item, binder)| { - binder.skip_binder().into_iter().find_map(|(index, p)| { - recurse(p).map(|path| { - cons( - PathChunk::AssocItem { - item, - predicate_id: p.predicate_id(s), - predicate: p, - index, - }, - path, - ) - }) - }) - }) - .max_by_key(|path| path.len()) - }) } + None } } +} impl ImplExprAtom { fn with_args(self, args: Vec, r#trait: TraitRef) -> ImplExpr {