Skip to content

Commit 9ec287d

Browse files
authored
Rollup merge of #120584 - compiler-errors:u, r=lcnr
For a rigid projection, recursively look at the self type's item bounds to fix the `associated_type_bounds` feature Given a deeply nested rigid projection like `<<<T as Trait1>::Assoc1 as Trait2>::Assoc2 as Trait3>::Assoc3`, this PR adjusts both trait solvers to look at the item bounds for all of `Assoc3`, `Assoc2`, and `Assoc1` in order to satisfy a goal. We do this because the item bounds for projections may contain relevant bounds for *other* nested projections when the `associated_type_bounds` (ATB) feature is enabled. For example: ```rust #![feature(associated_type_bounds)] trait Trait1 { type Assoc1: Trait2<Assoc2: Foo>; // Item bounds for `Assoc1` are: // `<Self as Trait1>::Assoc1: Trait2` // `<<Self as Trait1>::Assoc1 as Trait2>::Assoc2: Foo` } trait Trait2 { type Assoc2; } trait Foo {} fn hello<T: Trait1>(x: <<T as Trait1>::Assoc1 as Trait2>::Assoc2) { fn is_foo(_: impl Foo) {} is_foo(x); // Currently fails with: // ERROR the trait bound `<<Self as Trait1>::Assoc1 as Trait2>::Assoc2: Foo` is not satisfied } ``` This has been a long-standing place of brokenness for ATBs, and is also part of the reason why ATBs currently desugar so differently in various positions (i.e. sometimes desugaring to param-env bounds, sometimes desugaring to RPITs, etc). For example, in RPIT and TAIT position, `impl Foo<Bar: Baz>` currently desugars to `impl Foo<Bar = impl Baz>` because we do not currently take advantage of these nested item bounds if we desugared them into a single set of item bounds on the opaque. This is obviously both strange and unnecessary if we just take advantage of these bounds as we should. ## Approach This PR repeatedly peels off each projection of a given goal's self type and tries to match its item bounds against a goal, repeating with the self type of the projection. This is pretty straightforward to implement in the new solver, only requiring us to loop on the self type of a rigid projection to discover inner rigid projections, and we also need to introduce an extra probe so we can normalize them. In the old solver, we can do essentially the same thing, however we rely on the fact that projections *should* be normalized already. This is obviously not always the case -- however, in the case that they are not fully normalized, such as a projection which has both infer vars and, we bail out with ambiguity if we hit an infer var for the self type. ## Caveats ⚠️ In the old solver, this has the side-effect of actually stalling some higher-ranked trait goals of the form `for<'a> <?0 as Tr<'a>>: Tr2`. Because we stall them, they no longer are eagerly treated as error -- this cause some existing `known-bug` tests to go from fail -> pass. I'm pretty unconvinced that this is a problem since we make code that we expect to pass in the *new* solver also pass in the *old* solver, though this obviously doesn't solve the *full* problem. ## And then also... We also adjust the desugaring of ATB to always desugar to a regular associated bound, rather than sometimes to an impl Trait **except** for when the ATB is present in a `dyn Trait`. We need to lower `dyn Trait<Assoc: Bar>` to `dyn Trait<Assoc = impl Bar>` because object types need all of their associated types specified. I would also be in favor of splitting out the ATB feature and/or removing support for object types in order to stabilize just the set of positions for which the ATB feature is consistent (i.e. always elaborates to a bound).
2 parents d44e3b9 + 7057188 commit 9ec287d

26 files changed

+307
-411
lines changed

Diff for: compiler/rustc_ast_lowering/src/lib.rs

+11-13
Original file line numberDiff line numberDiff line change
@@ -1092,24 +1092,22 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
10921092

10931093
// Piggy-back on the `impl Trait` context to figure out the correct behavior.
10941094
let desugar_kind = match itctx {
1095-
// We are in the return position:
1096-
//
1097-
// fn foo() -> impl Iterator<Item: Debug>
1098-
//
1099-
// so desugar to
1100-
//
1101-
// fn foo() -> impl Iterator<Item = impl Debug>
1102-
ImplTraitContext::ReturnPositionOpaqueTy { .. }
1103-
| ImplTraitContext::TypeAliasesOpaqueTy { .. } => DesugarKind::ImplTrait,
1104-
1105-
// We are in the argument position, but within a dyn type:
1095+
// in an argument, RPIT, or TAIT, if we are within a dyn type:
11061096
//
11071097
// fn foo(x: dyn Iterator<Item: Debug>)
11081098
//
1109-
// so desugar to
1099+
// then desugar to:
11101100
//
11111101
// fn foo(x: dyn Iterator<Item = impl Debug>)
1112-
ImplTraitContext::Universal if self.is_in_dyn_type => DesugarKind::ImplTrait,
1102+
//
1103+
// This is because dyn traits must have all of their associated types specified.
1104+
ImplTraitContext::ReturnPositionOpaqueTy { .. }
1105+
| ImplTraitContext::TypeAliasesOpaqueTy { .. }
1106+
| ImplTraitContext::Universal
1107+
if self.is_in_dyn_type =>
1108+
{
1109+
DesugarKind::ImplTrait
1110+
}
11131111

11141112
ImplTraitContext::Disallowed(position) if self.is_in_dyn_type => {
11151113
DesugarKind::Error(position)

Diff for: compiler/rustc_trait_selection/src/solve/assembly/mod.rs

+62-9
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,27 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
548548
goal: Goal<'tcx, G>,
549549
candidates: &mut Vec<Candidate<'tcx>>,
550550
) {
551-
let alias_ty = match goal.predicate.self_ty().kind() {
551+
let () = self.probe(|_| ProbeKind::NormalizedSelfTyAssembly).enter(|ecx| {
552+
ecx.assemble_alias_bound_candidates_recur(goal.predicate.self_ty(), goal, candidates);
553+
});
554+
}
555+
556+
/// For some deeply nested `<T>::A::B::C::D` rigid associated type,
557+
/// we should explore the item bounds for all levels, since the
558+
/// `associated_type_bounds` feature means that a parent associated
559+
/// type may carry bounds for a nested associated type.
560+
///
561+
/// If we have a projection, check that its self type is a rigid projection.
562+
/// If so, continue searching by recursively calling after normalization.
563+
// FIXME: This may recurse infinitely, but I can't seem to trigger it without
564+
// hitting another overflow error something. Add a depth parameter needed later.
565+
fn assemble_alias_bound_candidates_recur<G: GoalKind<'tcx>>(
566+
&mut self,
567+
self_ty: Ty<'tcx>,
568+
goal: Goal<'tcx, G>,
569+
candidates: &mut Vec<Candidate<'tcx>>,
570+
) {
571+
let (kind, alias_ty) = match *self_ty.kind() {
552572
ty::Bool
553573
| ty::Char
554574
| ty::Int(_)
@@ -573,23 +593,56 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
573593
| ty::Param(_)
574594
| ty::Placeholder(..)
575595
| ty::Infer(ty::IntVar(_) | ty::FloatVar(_))
576-
| ty::Alias(ty::Inherent, _)
577-
| ty::Alias(ty::Weak, _)
578596
| ty::Error(_) => return,
579-
ty::Infer(ty::TyVar(_) | ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_))
580-
| ty::Bound(..) => bug!("unexpected self type for `{goal:?}`"),
581-
// Excluding IATs and type aliases here as they don't have meaningful item bounds.
582-
ty::Alias(ty::Projection | ty::Opaque, alias_ty) => alias_ty,
597+
ty::Infer(ty::FreshTy(_) | ty::FreshIntTy(_) | ty::FreshFloatTy(_)) | ty::Bound(..) => {
598+
bug!("unexpected self type for `{goal:?}`")
599+
}
600+
601+
ty::Infer(ty::TyVar(_)) => {
602+
// If we hit infer when normalizing the self type of an alias,
603+
// then bail with ambiguity. We should never encounter this on
604+
// the *first* iteration of this recursive function.
605+
if let Ok(result) =
606+
self.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS)
607+
{
608+
candidates.push(Candidate { source: CandidateSource::AliasBound, result });
609+
}
610+
return;
611+
}
612+
613+
ty::Alias(kind @ (ty::Projection | ty::Opaque), alias_ty) => (kind, alias_ty),
614+
ty::Alias(ty::Inherent | ty::Weak, _) => {
615+
unreachable!("Weak and Inherent aliases should have been normalized away already")
616+
}
583617
};
584618

585619
for assumption in
586620
self.tcx().item_bounds(alias_ty.def_id).instantiate(self.tcx(), alias_ty.args)
587621
{
588622
match G::consider_alias_bound_candidate(self, goal, assumption) {
589623
Ok(result) => {
590-
candidates.push(Candidate { source: CandidateSource::AliasBound, result })
624+
candidates.push(Candidate { source: CandidateSource::AliasBound, result });
625+
}
626+
Err(NoSolution) => {}
627+
}
628+
}
629+
630+
if kind != ty::Projection {
631+
return;
632+
}
633+
634+
match self.try_normalize_ty(goal.param_env, alias_ty.self_ty()) {
635+
// Recurse on the self type of the projection.
636+
Some(next_self_ty) => {
637+
self.assemble_alias_bound_candidates_recur(next_self_ty, goal, candidates);
638+
}
639+
// Bail if we overflow when normalizing, adding an ambiguous candidate.
640+
None => {
641+
if let Ok(result) =
642+
self.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW)
643+
{
644+
candidates.push(Candidate { source: CandidateSource::AliasBound, result });
591645
}
592-
Err(NoSolution) => (),
593646
}
594647
}
595648
}

Diff for: compiler/rustc_trait_selection/src/traits/project.rs

+36-23
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ use rustc_middle::ty::{self, Term, ToPredicate, Ty, TyCtxt};
4040
use rustc_span::symbol::sym;
4141

4242
use std::collections::BTreeMap;
43+
use std::ops::ControlFlow;
4344

4445
pub use rustc_middle::traits::Reveal;
4546

@@ -1614,32 +1615,44 @@ fn assemble_candidates_from_trait_def<'cx, 'tcx>(
16141615
candidate_set: &mut ProjectionCandidateSet<'tcx>,
16151616
) {
16161617
debug!("assemble_candidates_from_trait_def(..)");
1618+
let mut ambiguous = false;
1619+
selcx.for_each_item_bound(
1620+
obligation.predicate.self_ty(),
1621+
|selcx, clause, _| {
1622+
let Some(clause) = clause.as_projection_clause() else {
1623+
return ControlFlow::Continue(());
1624+
};
16171625

1618-
let tcx = selcx.tcx();
1619-
// Check whether the self-type is itself a projection.
1620-
// If so, extract what we know from the trait and try to come up with a good answer.
1621-
let bounds = match *obligation.predicate.self_ty().kind() {
1622-
// Excluding IATs and type aliases here as they don't have meaningful item bounds.
1623-
ty::Alias(ty::Projection | ty::Opaque, ref data) => {
1624-
tcx.item_bounds(data.def_id).instantiate(tcx, data.args)
1625-
}
1626-
ty::Infer(ty::TyVar(_)) => {
1627-
// If the self-type is an inference variable, then it MAY wind up
1628-
// being a projected type, so induce an ambiguity.
1629-
candidate_set.mark_ambiguous();
1630-
return;
1631-
}
1632-
_ => return,
1633-
};
1626+
let is_match =
1627+
selcx.infcx.probe(|_| selcx.match_projection_projections(obligation, clause, true));
16341628

1635-
assemble_candidates_from_predicates(
1636-
selcx,
1637-
obligation,
1638-
candidate_set,
1639-
ProjectionCandidate::TraitDef,
1640-
bounds.iter(),
1641-
true,
1629+
match is_match {
1630+
ProjectionMatchesProjection::Yes => {
1631+
candidate_set.push_candidate(ProjectionCandidate::TraitDef(clause));
1632+
1633+
if !obligation.predicate.has_non_region_infer() {
1634+
// HACK: Pick the first trait def candidate for a fully
1635+
// inferred predicate. This is to allow duplicates that
1636+
// differ only in normalization.
1637+
return ControlFlow::Break(());
1638+
}
1639+
}
1640+
ProjectionMatchesProjection::Ambiguous => {
1641+
candidate_set.mark_ambiguous();
1642+
}
1643+
ProjectionMatchesProjection::No => {}
1644+
}
1645+
1646+
ControlFlow::Continue(())
1647+
},
1648+
// `ProjectionCandidateSet` is borrowed in the above closure,
1649+
// so just mark ambiguous outside of the closure.
1650+
|| ambiguous = true,
16421651
);
1652+
1653+
if ambiguous {
1654+
candidate_set.mark_ambiguous();
1655+
}
16431656
}
16441657

16451658
/// In the case of a trait object like

Diff for: compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs

+47-5
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
//!
77
//! [rustc dev guide]:https://rustc-dev-guide.rust-lang.org/traits/resolution.html#candidate-assembly
88
9+
use std::ops::ControlFlow;
10+
911
use hir::def_id::DefId;
1012
use hir::LangItem;
13+
use rustc_data_structures::fx::FxHashSet;
1114
use rustc_hir as hir;
1215
use rustc_infer::traits::ObligationCause;
1316
use rustc_infer::traits::{Obligation, PolyTraitObligation, SelectionError};
1417
use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams};
15-
use rustc_middle::ty::{self, Ty, TypeVisitableExt};
18+
use rustc_middle::ty::{self, ToPolyTraitRef, Ty, TypeVisitableExt};
1619

1720
use crate::traits;
1821
use crate::traits::query::evaluate_obligation::InferCtxtExt;
@@ -158,11 +161,50 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
158161
_ => return,
159162
}
160163

161-
let result = self
162-
.infcx
163-
.probe(|_| self.match_projection_obligation_against_definition_bounds(obligation));
164+
self.infcx.probe(|_| {
165+
let poly_trait_predicate = self.infcx.resolve_vars_if_possible(obligation.predicate);
166+
let placeholder_trait_predicate =
167+
self.infcx.enter_forall_and_leak_universe(poly_trait_predicate);
168+
debug!(?placeholder_trait_predicate);
169+
170+
// The bounds returned by `item_bounds` may contain duplicates after
171+
// normalization, so try to deduplicate when possible to avoid
172+
// unnecessary ambiguity.
173+
let mut distinct_normalized_bounds = FxHashSet::default();
174+
self.for_each_item_bound::<!>(
175+
placeholder_trait_predicate.self_ty(),
176+
|selcx, bound, idx| {
177+
let Some(bound) = bound.as_trait_clause() else {
178+
return ControlFlow::Continue(());
179+
};
180+
if bound.polarity() != placeholder_trait_predicate.polarity {
181+
return ControlFlow::Continue(());
182+
}
164183

165-
candidates.vec.extend(result.into_iter().map(|idx| ProjectionCandidate(idx)));
184+
selcx.infcx.probe(|_| {
185+
match selcx.match_normalize_trait_ref(
186+
obligation,
187+
bound.to_poly_trait_ref(),
188+
placeholder_trait_predicate.trait_ref,
189+
) {
190+
Ok(None) => {
191+
candidates.vec.push(ProjectionCandidate(idx));
192+
}
193+
Ok(Some(normalized_trait))
194+
if distinct_normalized_bounds.insert(normalized_trait) =>
195+
{
196+
candidates.vec.push(ProjectionCandidate(idx));
197+
}
198+
_ => {}
199+
}
200+
});
201+
202+
ControlFlow::Continue(())
203+
},
204+
// On ambiguity.
205+
|| candidates.ambiguous = true,
206+
);
207+
});
166208
}
167209

168210
/// Given an obligation like `<SomeTrait for T>`, searches the obligations that the caller

Diff for: compiler/rustc_trait_selection/src/traits/select/confirmation.rs

+18-11
Original file line numberDiff line numberDiff line change
@@ -162,20 +162,26 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
162162
self.infcx.enter_forall_and_leak_universe(trait_predicate).trait_ref;
163163
let placeholder_self_ty = placeholder_trait_predicate.self_ty();
164164
let placeholder_trait_predicate = ty::Binder::dummy(placeholder_trait_predicate);
165-
let (def_id, args) = match *placeholder_self_ty.kind() {
166-
// Excluding IATs and type aliases here as they don't have meaningful item bounds.
167-
ty::Alias(ty::Projection | ty::Opaque, ty::AliasTy { def_id, args, .. }) => {
168-
(def_id, args)
169-
}
170-
_ => bug!("projection candidate for unexpected type: {:?}", placeholder_self_ty),
171-
};
172165

173-
let candidate_predicate =
174-
tcx.item_bounds(def_id).map_bound(|i| i[idx]).instantiate(tcx, args);
166+
let candidate_predicate = self
167+
.for_each_item_bound(
168+
placeholder_self_ty,
169+
|_, clause, clause_idx| {
170+
if clause_idx == idx {
171+
ControlFlow::Break(clause)
172+
} else {
173+
ControlFlow::Continue(())
174+
}
175+
},
176+
|| unreachable!(),
177+
)
178+
.break_value()
179+
.expect("expected to index into clause that exists");
175180
let candidate = candidate_predicate
176181
.as_trait_clause()
177182
.expect("projection candidate is not a trait predicate")
178183
.map_bound(|t| t.trait_ref);
184+
179185
let mut obligations = Vec::new();
180186
let candidate = normalize_with_depth_to(
181187
self,
@@ -194,8 +200,9 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
194200
.map_err(|_| Unimplemented)
195201
})?);
196202

197-
if let ty::Alias(ty::Projection, ..) = placeholder_self_ty.kind() {
198-
let predicates = tcx.predicates_of(def_id).instantiate_own(tcx, args);
203+
// FIXME(compiler-errors): I don't think this is needed.
204+
if let ty::Alias(ty::Projection, alias_ty) = placeholder_self_ty.kind() {
205+
let predicates = tcx.predicates_of(alias_ty.def_id).instantiate_own(tcx, alias_ty.args);
199206
for (predicate, _) in predicates {
200207
let normalized = normalize_with_depth_to(
201208
self,

0 commit comments

Comments
 (0)