From 87599ddd86afc706f424870ca1a6f8dc4ae0aa8e Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 21 May 2024 19:30:26 +0000 Subject: [PATCH 1/4] add debug_assert to alias-relate --- compiler/rustc_trait_selection/src/solve/alias_relate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/rustc_trait_selection/src/solve/alias_relate.rs b/compiler/rustc_trait_selection/src/solve/alias_relate.rs index 43e61de955af7..33b30bef68328 100644 --- a/compiler/rustc_trait_selection/src/solve/alias_relate.rs +++ b/compiler/rustc_trait_selection/src/solve/alias_relate.rs @@ -28,6 +28,7 @@ impl<'tcx> EvalCtxt<'_, InferCtxt<'tcx>> { ) -> QueryResult<'tcx> { let tcx = self.tcx(); let Goal { param_env, predicate: (lhs, rhs, direction) } = goal; + debug_assert!(lhs.to_alias_term().is_some() || rhs.to_alias_term().is_some()); // Structurally normalize the lhs. let lhs = if let Some(alias) = lhs.to_alias_term() { From 4d5a9bcb86630417002c9ab6d77ba023d30077b8 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 21 May 2024 19:30:47 +0000 Subject: [PATCH 2/4] change selection test to run-pass --- tests/ui/traits/next-solver/dyn-any-dont-prefer-impl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/traits/next-solver/dyn-any-dont-prefer-impl.rs b/tests/ui/traits/next-solver/dyn-any-dont-prefer-impl.rs index a63fe729fd687..1554d74f21453 100644 --- a/tests/ui/traits/next-solver/dyn-any-dont-prefer-impl.rs +++ b/tests/ui/traits/next-solver/dyn-any-dont-prefer-impl.rs @@ -1,5 +1,5 @@ //@ compile-flags: -Znext-solver -//@ check-pass +//@ run-pass // Test that selection prefers the builtin trait object impl for `Any` // instead of the user defined impl. Both impls apply to the trait From 13ce22904265dbb7ab9d5bd8d508d8ea0ca4c4de Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 21 May 2024 19:56:18 +0000 Subject: [PATCH 3/4] refactor analyse visitor to instantiate states in order --- .../src/solve/inspect/analyse.rs | 86 +++++++++---------- 1 file changed, 41 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs index 447357f8b3f64..1f27978e5a622 100644 --- a/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs +++ b/compiler/rustc_trait_selection/src/solve/inspect/analyse.rs @@ -89,10 +89,8 @@ impl<'tcx> NormalizesToTermHack<'tcx> { pub struct InspectCandidate<'a, 'tcx> { goal: &'a InspectGoal<'a, 'tcx>, kind: inspect::ProbeKind>, - nested_goals: - Vec<(GoalSource, inspect::CanonicalState, Goal<'tcx, ty::Predicate<'tcx>>>)>, + steps: Vec<&'a inspect::ProbeStep>>, final_state: inspect::CanonicalState, ()>, - impl_args: Option, ty::GenericArgsRef<'tcx>>>, result: QueryResult<'tcx>, shallow_certainty: Certainty, } @@ -148,7 +146,7 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { #[instrument( level = "debug", skip_all, - fields(goal = ?self.goal.goal, nested_goals = ?self.nested_goals) + fields(goal = ?self.goal.goal, steps = ?self.steps) )] pub fn instantiate_nested_goals_and_opt_impl_args( &self, @@ -157,22 +155,34 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { let infcx = self.goal.infcx; let param_env = self.goal.goal.param_env; let mut orig_values = self.goal.orig_values.to_vec(); - let instantiated_goals: Vec<_> = self - .nested_goals - .iter() - .map(|(source, goal)| { - ( - *source, + + let mut instantiated_goals = vec![]; + let mut opt_impl_args = None; + for step in &self.steps { + match **step { + inspect::ProbeStep::AddGoal(source, goal) => instantiated_goals.push(( + source, canonical::instantiate_canonical_state( infcx, span, param_env, &mut orig_values, - *goal, + goal, ), - ) - }) - .collect(); + )), + inspect::ProbeStep::RecordImplArgs { impl_args } => { + opt_impl_args = Some(canonical::instantiate_canonical_state( + infcx, + span, + param_env, + &mut orig_values, + impl_args, + )); + } + inspect::ProbeStep::MakeCanonicalResponse { .. } + | inspect::ProbeStep::NestedProbe(_) => unreachable!(), + } + } let () = canonical::instantiate_canonical_state( infcx, @@ -182,17 +192,6 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { self.final_state, ); - let impl_args = self.impl_args.map(|impl_args| { - canonical::instantiate_canonical_state( - infcx, - span, - param_env, - &mut orig_values, - impl_args, - ) - .fold_with(&mut EagerResolver::new(infcx)) - }); - if let Some(term_hack) = self.goal.normalizes_to_term_hack { // FIXME: We ignore the expected term of `NormalizesTo` goals // when computing the result of its candidates. This is @@ -200,6 +199,9 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { let _ = term_hack.constrain(infcx, span, param_env); } + let opt_impl_args = + opt_impl_args.map(|impl_args| impl_args.fold_with(&mut EagerResolver::new(infcx))); + let goals = instantiated_goals .into_iter() .map(|(source, goal)| match goal.predicate.kind().no_bound_vars() { @@ -249,7 +251,7 @@ impl<'a, 'tcx> InspectCandidate<'a, 'tcx> { }) .collect(); - (goals, impl_args) + (goals, opt_impl_args) } /// Visit all nested goals of this candidate, rolling back @@ -279,17 +281,18 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { fn candidates_recur( &'a self, candidates: &mut Vec>, - nested_goals: &mut Vec<( - GoalSource, - inspect::CanonicalState, Goal<'tcx, ty::Predicate<'tcx>>>, - )>, - probe: &inspect::Probe>, + steps: &mut Vec<&'a inspect::ProbeStep>>, + probe: &'a inspect::Probe>, ) { let mut shallow_certainty = None; - let mut impl_args = None; for step in &probe.steps { match *step { - inspect::ProbeStep::AddGoal(source, goal) => nested_goals.push((source, goal)), + inspect::ProbeStep::AddGoal(..) | inspect::ProbeStep::RecordImplArgs { .. } => { + steps.push(step) + } + inspect::ProbeStep::MakeCanonicalResponse { shallow_certainty: c } => { + assert_eq!(shallow_certainty.replace(c), None); + } inspect::ProbeStep::NestedProbe(ref probe) => { match probe.kind { // These never assemble candidates for the goal we're trying to solve. @@ -305,18 +308,12 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { // Nested probes have to prove goals added in their parent // but do not leak them, so we truncate the added goals // afterwards. - let num_goals = nested_goals.len(); - self.candidates_recur(candidates, nested_goals, probe); - nested_goals.truncate(num_goals); + let num_steps = steps.len(); + self.candidates_recur(candidates, steps, probe); + steps.truncate(num_steps); } } } - inspect::ProbeStep::MakeCanonicalResponse { shallow_certainty: c } => { - assert_eq!(shallow_certainty.replace(c), None); - } - inspect::ProbeStep::RecordImplArgs { impl_args: i } => { - assert_eq!(impl_args.replace(i), None); - } } } @@ -338,11 +335,10 @@ impl<'a, 'tcx> InspectGoal<'a, 'tcx> { candidates.push(InspectCandidate { goal: self, kind: probe.kind, - nested_goals: nested_goals.clone(), + steps: steps.clone(), final_state: probe.final_state, - result, shallow_certainty, - impl_args, + result, }); } } From 98bfd54b0abd938d6b424d45b57766e6f2f41ea2 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 21 May 2024 19:56:53 +0000 Subject: [PATCH 4/4] eagerly normalize when adding goals --- compiler/rustc_middle/src/ty/predicate.rs | 9 +- .../src/solve/eval_ctxt/mod.rs | 77 +++++++++++++++- ...ap-unnormalizable-projection-1.next.stderr | 2 +- .../occurs-check/opaques.next.stderr | 2 +- .../as_expression.next.stderr | 20 ++++- .../do_not_recommend/as_expression.rs | 5 +- ...st-region-infer-to-static-in-binder.stderr | 4 +- .../cycles/cycle-modulo-ambig-aliases.rs | 89 +++++++++++++++++++ .../cycles/cycle-modulo-ambig-aliases.stderr | 15 ++++ 9 files changed, 207 insertions(+), 16 deletions(-) create mode 100644 tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs create mode 100644 tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs index efb6cf2554625..c730f5117c561 100644 --- a/compiler/rustc_middle/src/ty/predicate.rs +++ b/compiler/rustc_middle/src/ty/predicate.rs @@ -121,17 +121,14 @@ impl<'tcx> Predicate<'tcx> { #[inline] pub fn allow_normalization(self) -> bool { match self.kind().skip_binder() { - PredicateKind::Clause(ClauseKind::WellFormed(_)) => false, - // `NormalizesTo` is only used in the new solver, so this shouldn't - // matter. Normalizing `term` would be 'wrong' however, as it changes whether - // `normalizes-to(::Assoc, ::Assoc)` holds. - PredicateKind::NormalizesTo(..) => false, + PredicateKind::Clause(ClauseKind::WellFormed(_)) + | PredicateKind::AliasRelate(..) + | PredicateKind::NormalizesTo(..) => false, PredicateKind::Clause(ClauseKind::Trait(_)) | PredicateKind::Clause(ClauseKind::RegionOutlives(_)) | PredicateKind::Clause(ClauseKind::TypeOutlives(_)) | PredicateKind::Clause(ClauseKind::Projection(_)) | PredicateKind::Clause(ClauseKind::ConstArgHasType(..)) - | PredicateKind::AliasRelate(..) | PredicateKind::ObjectSafe(_) | PredicateKind::Subtype(_) | PredicateKind::Coerce(_) diff --git a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs index ce408ddea3780..4cf0af948119d 100644 --- a/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_trait_selection/src/solve/eval_ctxt/mod.rs @@ -13,11 +13,14 @@ use rustc_middle::traits::solve::{ inspect, CanonicalInput, CanonicalResponse, Certainty, PredefinedOpaquesData, QueryResult, }; use rustc_middle::traits::specialization_graph; +use rustc_middle::ty::AliasRelationDirection; +use rustc_middle::ty::TypeFolder; use rustc_middle::ty::{ self, InferCtxtLike, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, }; use rustc_span::DUMMY_SP; +use rustc_type_ir::fold::TypeSuperFoldable; use rustc_type_ir::{self as ir, CanonicalVarValues, Interner}; use rustc_type_ir_macros::{Lift_Generic, TypeFoldable_Generic, TypeVisitable_Generic}; use std::ops::ControlFlow; @@ -455,13 +458,23 @@ impl<'a, 'tcx> EvalCtxt<'a, InferCtxt<'tcx>> { } #[instrument(level = "trace", skip(self))] - pub(super) fn add_normalizes_to_goal(&mut self, goal: Goal<'tcx, ty::NormalizesTo<'tcx>>) { + pub(super) fn add_normalizes_to_goal(&mut self, mut goal: Goal<'tcx, ty::NormalizesTo<'tcx>>) { + goal.predicate = goal + .predicate + .fold_with(&mut ReplaceAliasWithInfer { ecx: self, param_env: goal.param_env }); self.inspect.add_normalizes_to_goal(self.infcx, self.max_input_universe, goal); self.nested_goals.normalizes_to_goals.push(goal); } #[instrument(level = "debug", skip(self))] - pub(super) fn add_goal(&mut self, source: GoalSource, goal: Goal<'tcx, ty::Predicate<'tcx>>) { + pub(super) fn add_goal( + &mut self, + source: GoalSource, + mut goal: Goal<'tcx, ty::Predicate<'tcx>>, + ) { + goal.predicate = goal + .predicate + .fold_with(&mut ReplaceAliasWithInfer { ecx: self, param_env: goal.param_env }); self.inspect.add_goal(self.infcx, self.max_input_universe, source, goal); self.nested_goals.goals.push((source, goal)); } @@ -1084,3 +1097,63 @@ impl<'tcx> EvalCtxt<'_, InferCtxt<'tcx>> { }); } } + +/// Eagerly replace aliases with inference variables, emitting `AliasRelate` +/// goals, used when adding goals to the `EvalCtxt`. We compute the +/// `AliasRelate` goals before evaluating the actual goal to get all the +/// constraints we can. +/// +/// This is a performance optimization to more eagerly detect cycles during trait +/// solving. See tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs. +struct ReplaceAliasWithInfer<'me, 'a, 'tcx> { + ecx: &'me mut EvalCtxt<'a, InferCtxt<'tcx>>, + param_env: ty::ParamEnv<'tcx>, +} + +impl<'tcx> TypeFolder> for ReplaceAliasWithInfer<'_, '_, 'tcx> { + fn interner(&self) -> TyCtxt<'tcx> { + self.ecx.tcx() + } + + fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> { + match *ty.kind() { + ty::Alias(..) if !ty.has_escaping_bound_vars() => { + let infer_ty = self.ecx.next_ty_infer(); + let normalizes_to = ty::PredicateKind::AliasRelate( + ty.into(), + infer_ty.into(), + AliasRelationDirection::Equate, + ); + self.ecx.add_goal( + GoalSource::Misc, + Goal::new(self.interner(), self.param_env, normalizes_to), + ); + infer_ty + } + _ => ty.super_fold_with(self), + } + } + + fn fold_const(&mut self, ct: ty::Const<'tcx>) -> ty::Const<'tcx> { + match ct.kind() { + ty::ConstKind::Unevaluated(..) if !ct.has_escaping_bound_vars() => { + let infer_ct = self.ecx.next_const_infer(ct.ty()); + let normalizes_to = ty::PredicateKind::AliasRelate( + ct.into(), + infer_ct.into(), + AliasRelationDirection::Equate, + ); + self.ecx.add_goal( + GoalSource::Misc, + Goal::new(self.interner(), self.param_env, normalizes_to), + ); + infer_ct + } + _ => ct.super_fold_with(self), + } + } + + fn fold_predicate(&mut self, predicate: ty::Predicate<'tcx>) -> ty::Predicate<'tcx> { + if predicate.allow_normalization() { predicate.super_fold_with(self) } else { predicate } + } +} diff --git a/tests/ui/coherence/coherence-overlap-unnormalizable-projection-1.next.stderr b/tests/ui/coherence/coherence-overlap-unnormalizable-projection-1.next.stderr index 49b236f9d2aa2..781ab0fcbf766 100644 --- a/tests/ui/coherence/coherence-overlap-unnormalizable-projection-1.next.stderr +++ b/tests/ui/coherence/coherence-overlap-unnormalizable-projection-1.next.stderr @@ -12,7 +12,7 @@ LL | impl Trait for Box {} | ^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Box<_>` | = note: downstream crates may implement trait `WithAssoc<'a>` for type `std::boxed::Box<_>` - = note: downstream crates may implement trait `WhereBound` for type `std::boxed::Box< as WithAssoc<'a>>::Assoc>` + = note: downstream crates may implement trait `WhereBound` for type `std::boxed::Box<_>` error: aborting due to 1 previous error diff --git a/tests/ui/coherence/occurs-check/opaques.next.stderr b/tests/ui/coherence/occurs-check/opaques.next.stderr index f6c5255a18693..11d1edcca2f91 100644 --- a/tests/ui/coherence/occurs-check/opaques.next.stderr +++ b/tests/ui/coherence/occurs-check/opaques.next.stderr @@ -11,7 +11,7 @@ error[E0282]: type annotations needed --> $DIR/opaques.rs:13:20 | LL | pub fn cast(x: Container, T>) -> Container { - | ^ cannot infer type for associated type `>::Assoc` + | ^ cannot infer type error: aborting due to 2 previous errors diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.next.stderr b/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.next.stderr index 47acf5b968b79..568cb8931a198 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.next.stderr +++ b/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.next.stderr @@ -16,6 +16,22 @@ LL | where LL | T: AsExpression, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `Foo::check` -error: aborting due to 1 previous error +error[E0277]: the trait bound `&str: AsExpression` is not satisfied + --> $DIR/as_expression.rs:57:15 + | +LL | SelectInt.check("bar"); + | ^^^^^ the trait `AsExpression` is not implemented for `&str` + | + = help: the trait `AsExpression` is implemented for `&str` + = help: for that trait implementation, expected `Text`, found `Integer` + +error[E0271]: type mismatch resolving `<&str as AsExpression<::SqlType>>::Expression == _` + --> $DIR/as_expression.rs:57:5 + | +LL | SelectInt.check("bar"); + | ^^^^^^^^^^^^^^^^^^^^^^ types differ + +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0277`. +Some errors have detailed explanations: E0271, E0277. +For more information about an error, try `rustc --explain E0271`. diff --git a/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.rs b/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.rs index 5fd5cc544009f..37b4429f694cf 100644 --- a/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.rs +++ b/tests/ui/diagnostic_namespace/do_not_recommend/as_expression.rs @@ -55,6 +55,7 @@ impl Foo for T where T: Expression {} fn main() { SelectInt.check("bar"); - //[next]~^ ERROR the trait bound `&str: AsExpression<::SqlType>` is not satisfied - //[current]~^^ ERROR the trait bound `&str: AsExpression` is not satisfied + //~^ ERROR the trait bound `&str: AsExpression` is not satisfied + //[next]~| the trait bound `&str: AsExpression<::SqlType>` is not satisfied + //[next]~| type mismatch } diff --git a/tests/ui/traits/next-solver/canonical/const-region-infer-to-static-in-binder.stderr b/tests/ui/traits/next-solver/canonical/const-region-infer-to-static-in-binder.stderr index 170f2c7d34c45..9dde1963bd49d 100644 --- a/tests/ui/traits/next-solver/canonical/const-region-infer-to-static-in-binder.stderr +++ b/tests/ui/traits/next-solver/canonical/const-region-infer-to-static-in-binder.stderr @@ -1,8 +1,8 @@ -error[E0284]: type annotations needed: cannot satisfy `the constant `{ || {} }` can be evaluated` +error[E0284]: type annotations needed: cannot satisfy `{ || {} } == _` --> $DIR/const-region-infer-to-static-in-binder.rs:4:10 | LL | struct X; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot satisfy `the constant `{ || {} }` can be evaluated` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot satisfy `{ || {} } == _` error: using function pointers as const generic parameters is forbidden --> $DIR/const-region-infer-to-static-in-binder.rs:4:20 diff --git a/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs new file mode 100644 index 0000000000000..5c13a871a7b8d --- /dev/null +++ b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.rs @@ -0,0 +1,89 @@ +//@ compile-flags: -Znext-solver + +// A regression test for #125269. We previously ended up +// recursively proving `&<_ as SpeciesPackedElem>::Assoc: Typed` +// for all aliases which ended up causing exponential blowup. +// +// This has been fixed by eagerly normalizing the associated +// type before computing the nested goals, resulting in an +// immediate inductive cycle. + +pub trait Typed {} + +pub struct SpeciesCases(E); + +pub trait SpeciesPackedElim { + type Ogre; + type Cyclops; + type Wendigo; + type Cavetroll; + type Mountaintroll; + type Swamptroll; + type Dullahan; + type Werewolf; + type Occultsaurok; + type Mightysaurok; + type Slysaurok; + type Mindflayer; + type Minotaur; + type Tidalwarrior; + type Yeti; + type Harvester; + type Blueoni; + type Redoni; + type Cultistwarlord; + type Cultistwarlock; + type Huskbrute; + type Tursus; + type Gigasfrost; + type AdletElder; + type SeaBishop; + type HaniwaGeneral; + type TerracottaBesieger; + type TerracottaDemolisher; + type TerracottaPunisher; + type TerracottaPursuer; + type Cursekeeper; +} + +impl<'b, E: SpeciesPackedElim> Typed for &'b SpeciesCases +where + &'b E::Ogre: Typed, + &'b E::Cyclops: Typed, + &'b E::Wendigo: Typed, + &'b E::Cavetroll: Typed, + &'b E::Mountaintroll: Typed, + &'b E::Swamptroll: Typed, + &'b E::Dullahan: Typed, + &'b E::Werewolf: Typed, + &'b E::Occultsaurok: Typed, + &'b E::Mightysaurok: Typed, + &'b E::Slysaurok: Typed, + &'b E::Mindflayer: Typed, + &'b E::Minotaur: Typed, + &'b E::Tidalwarrior: Typed, + &'b E::Yeti: Typed, + &'b E::Harvester: Typed, + &'b E::Blueoni: Typed, + &'b E::Redoni: Typed, + &'b E::Cultistwarlord: Typed, + &'b E::Cultistwarlock: Typed, + &'b E::Huskbrute: Typed, + &'b E::Tursus: Typed, + &'b E::Gigasfrost: Typed, + &'b E::AdletElder: Typed, + &'b E::SeaBishop: Typed, + &'b E::HaniwaGeneral: Typed, + &'b E::TerracottaBesieger: Typed, + &'b E::TerracottaDemolisher: Typed, + &'b E::TerracottaPunisher: Typed, + &'b E::TerracottaPursuer: Typed, + &'b E::Cursekeeper: Typed, +{} + +fn foo() {} + +fn main() { + foo::<&_>(); + //~^ ERROR overflow evaluating the requirement `&_: Typed` +} diff --git a/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr new file mode 100644 index 0000000000000..d350eb0f7795c --- /dev/null +++ b/tests/ui/traits/next-solver/cycles/cycle-modulo-ambig-aliases.stderr @@ -0,0 +1,15 @@ +error[E0275]: overflow evaluating the requirement `&_: Typed` + --> $DIR/cycle-modulo-ambig-aliases.rs:87:11 + | +LL | foo::<&_>(); + | ^^ + | +note: required by a bound in `foo` + --> $DIR/cycle-modulo-ambig-aliases.rs:84:11 + | +LL | fn foo() {} + | ^^^^^ required by this bound in `foo` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0275`.