Skip to content

Commit c14d912

Browse files
authored
Rollup merge of #110673 - compiler-errors:alias-bounds-2, r=lcnr
Make alias bounds sound in the new solver (take 2) Make alias bounds sound in the new solver (in a way that does not require coinduction) by only considering them for projection types whose corresponding trait refs come from a param-env candidate. That is, given `<T as Trait>::Assoc: Bound`, we only *really* need to consider the alias bound if `T: Trait` is satisfied via a param-env candidate. If it's instead satisfied, e.g., via an user provided impl candidate or a , then that impl should have a concrete type to which we could otherwise normalize `<T as Trait>::Assoc`, and that concrete type is then responsible to prove the `Bound` on it. Similar consideration is given to opaque types, since we only need to consider alias bounds if we're *not* in reveal-all mode, since similarly we'd be able to reveal the opaque types and prove any bounds that way. This does not remove that hacky "eager projection replacement" logic from object bounds, which are somewhat like alias bounds. But removing this eager normalization behavior (added in #108333) would require full coinduction to be enabled. Compare to #110628, which does remove this object-bound custom logic but requires coinduction to be sound. r? `@lcnr`
2 parents 65dfca8 + 3a863e5 commit c14d912

File tree

6 files changed

+231
-93
lines changed

6 files changed

+231
-93
lines changed

compiler/rustc_trait_selection/src/solve/assembly/mod.rs

+154-4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_data_structures::fx::FxIndexSet;
88
use rustc_hir::def_id::DefId;
99
use rustc_infer::traits::query::NoSolution;
1010
use rustc_infer::traits::util::elaborate;
11+
use rustc_infer::traits::Reveal;
1112
use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, MaybeCause, QueryResult};
1213
use rustc_middle::ty::fast_reject::TreatProjections;
1314
use rustc_middle::ty::TypeFoldable;
@@ -87,7 +88,9 @@ pub(super) enum CandidateSource {
8788
}
8889

8990
/// Methods used to assemble candidates for either trait or projection goals.
90-
pub(super) trait GoalKind<'tcx>: TypeFoldable<TyCtxt<'tcx>> + Copy + Eq {
91+
pub(super) trait GoalKind<'tcx>:
92+
TypeFoldable<TyCtxt<'tcx>> + Copy + Eq + std::fmt::Display
93+
{
9194
fn self_ty(self) -> Ty<'tcx>;
9295

9396
fn trait_ref(self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx>;
@@ -96,6 +99,17 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<TyCtxt<'tcx>> + Copy + Eq {
9699

97100
fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId;
98101

102+
// Try equating an assumption predicate against a goal's predicate. If it
103+
// holds, then execute the `then` callback, which should do any additional
104+
// work, then produce a response (typically by executing
105+
// [`EvalCtxt::evaluate_added_goals_and_make_canonical_response`]).
106+
fn probe_and_match_goal_against_assumption(
107+
ecx: &mut EvalCtxt<'_, 'tcx>,
108+
goal: Goal<'tcx, Self>,
109+
assumption: ty::Predicate<'tcx>,
110+
then: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> QueryResult<'tcx>,
111+
) -> QueryResult<'tcx>;
112+
99113
// Consider a clause, which consists of a "assumption" and some "requirements",
100114
// to satisfy a goal. If the requirements hold, then attempt to satisfy our
101115
// goal by equating it with the assumption.
@@ -104,7 +118,26 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<TyCtxt<'tcx>> + Copy + Eq {
104118
goal: Goal<'tcx, Self>,
105119
assumption: ty::Predicate<'tcx>,
106120
requirements: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
107-
) -> QueryResult<'tcx>;
121+
) -> QueryResult<'tcx> {
122+
Self::probe_and_match_goal_against_assumption(ecx, goal, assumption, |ecx| {
123+
ecx.add_goals(requirements);
124+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
125+
})
126+
}
127+
128+
/// Consider a bound originating from the item bounds of an alias. For this we
129+
/// require that the well-formed requirements of the self type of the goal
130+
/// are "satisfied from the param-env".
131+
/// See [`EvalCtxt::validate_alias_bound_self_from_param_env`].
132+
fn consider_alias_bound_candidate(
133+
ecx: &mut EvalCtxt<'_, 'tcx>,
134+
goal: Goal<'tcx, Self>,
135+
assumption: ty::Predicate<'tcx>,
136+
) -> QueryResult<'tcx> {
137+
Self::probe_and_match_goal_against_assumption(ecx, goal, assumption, |ecx| {
138+
ecx.validate_alias_bound_self_from_param_env(goal)
139+
})
140+
}
108141

109142
// Consider a clause specifically for a `dyn Trait` self type. This requires
110143
// additionally checking all of the supertraits and object bounds to hold,
@@ -113,7 +146,25 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<TyCtxt<'tcx>> + Copy + Eq {
113146
ecx: &mut EvalCtxt<'_, 'tcx>,
114147
goal: Goal<'tcx, Self>,
115148
assumption: ty::Predicate<'tcx>,
116-
) -> QueryResult<'tcx>;
149+
) -> QueryResult<'tcx> {
150+
Self::probe_and_match_goal_against_assumption(ecx, goal, assumption, |ecx| {
151+
let tcx = ecx.tcx();
152+
let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else {
153+
bug!("expected object type in `consider_object_bound_candidate`");
154+
};
155+
ecx.add_goals(
156+
structural_traits::predicates_for_object_candidate(
157+
&ecx,
158+
goal.param_env,
159+
goal.predicate.trait_ref(tcx),
160+
bounds,
161+
)
162+
.into_iter()
163+
.map(|pred| goal.with(tcx, pred)),
164+
);
165+
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
166+
})
167+
}
117168

118169
fn consider_impl_candidate(
119170
ecx: &mut EvalCtxt<'_, 'tcx>,
@@ -463,7 +514,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
463514

464515
for assumption in self.tcx().item_bounds(alias_ty.def_id).subst(self.tcx(), alias_ty.substs)
465516
{
466-
match G::consider_implied_clause(self, goal, assumption, []) {
517+
match G::consider_alias_bound_candidate(self, goal, assumption) {
467518
Ok(result) => {
468519
candidates.push(Candidate { source: CandidateSource::AliasBound, result })
469520
}
@@ -472,6 +523,105 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
472523
}
473524
}
474525

526+
/// Check that we are allowed to use an alias bound originating from the self
527+
/// type of this goal. This means something different depending on the self type's
528+
/// alias kind.
529+
///
530+
/// * Projection: Given a goal with a self type such as `<Ty as Trait>::Assoc`,
531+
/// we require that the bound `Ty: Trait` can be proven using either a nested alias
532+
/// bound candidate, or a param-env candidate.
533+
///
534+
/// * Opaque: The param-env must be in `Reveal::UserFacing` mode. Otherwise,
535+
/// the goal should be proven by using the hidden type instead.
536+
#[instrument(level = "debug", skip(self), ret)]
537+
pub(super) fn validate_alias_bound_self_from_param_env<G: GoalKind<'tcx>>(
538+
&mut self,
539+
goal: Goal<'tcx, G>,
540+
) -> QueryResult<'tcx> {
541+
match *goal.predicate.self_ty().kind() {
542+
ty::Alias(ty::Projection, projection_ty) => {
543+
let mut param_env_candidates = vec![];
544+
let self_trait_ref = projection_ty.trait_ref(self.tcx());
545+
546+
if self_trait_ref.self_ty().is_ty_var() {
547+
return self
548+
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
549+
}
550+
551+
let trait_goal: Goal<'_, ty::TraitPredicate<'tcx>> = goal.with(
552+
self.tcx(),
553+
ty::TraitPredicate {
554+
trait_ref: self_trait_ref,
555+
constness: ty::BoundConstness::NotConst,
556+
polarity: ty::ImplPolarity::Positive,
557+
},
558+
);
559+
560+
self.assemble_param_env_candidates(trait_goal, &mut param_env_candidates);
561+
// FIXME: We probably need some sort of recursion depth check here.
562+
// Can't come up with an example yet, though, and the worst case
563+
// we can have is a compiler stack overflow...
564+
self.assemble_alias_bound_candidates(trait_goal, &mut param_env_candidates);
565+
566+
// FIXME: We must also consider alias-bound candidates for a peculiar
567+
// class of built-in candidates that I'll call "defaulted" built-ins.
568+
//
569+
// For example, we always know that `T: Pointee` is implemented, but
570+
// we do not always know what `<T as Pointee>::Metadata` actually is,
571+
// similar to if we had a user-defined impl with a `default type ...`.
572+
// For these traits, since we're not able to always normalize their
573+
// associated types to a concrete type, we must consider their alias bounds
574+
// instead, so we can prove bounds such as `<T as Pointee>::Metadata: Copy`.
575+
self.assemble_alias_bound_candidates_for_builtin_impl_default_items(
576+
trait_goal,
577+
&mut param_env_candidates,
578+
);
579+
580+
self.merge_candidates(param_env_candidates)
581+
}
582+
ty::Alias(ty::Opaque, _opaque_ty) => match goal.param_env.reveal() {
583+
Reveal::UserFacing => {
584+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
585+
}
586+
Reveal::All => return Err(NoSolution),
587+
},
588+
_ => bug!("only expected to be called on alias tys"),
589+
}
590+
}
591+
592+
/// Assemble a subset of builtin impl candidates for a class of candidates called
593+
/// "defaulted" built-in traits.
594+
///
595+
/// For example, we always know that `T: Pointee` is implemented, but we do not
596+
/// always know what `<T as Pointee>::Metadata` actually is! See the comment in
597+
/// [`EvalCtxt::validate_alias_bound_self_from_param_env`] for more detail.
598+
#[instrument(level = "debug", skip_all)]
599+
fn assemble_alias_bound_candidates_for_builtin_impl_default_items<G: GoalKind<'tcx>>(
600+
&mut self,
601+
goal: Goal<'tcx, G>,
602+
candidates: &mut Vec<Candidate<'tcx>>,
603+
) {
604+
let lang_items = self.tcx().lang_items();
605+
let trait_def_id = goal.predicate.trait_def_id(self.tcx());
606+
607+
// You probably shouldn't add anything to this list unless you
608+
// know what you're doing.
609+
let result = if lang_items.pointee_trait() == Some(trait_def_id) {
610+
G::consider_builtin_pointee_candidate(self, goal)
611+
} else if lang_items.discriminant_kind_trait() == Some(trait_def_id) {
612+
G::consider_builtin_discriminant_kind_candidate(self, goal)
613+
} else {
614+
Err(NoSolution)
615+
};
616+
617+
match result {
618+
Ok(result) => {
619+
candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result })
620+
}
621+
Err(NoSolution) => (),
622+
}
623+
}
624+
475625
#[instrument(level = "debug", skip_all)]
476626
fn assemble_object_bound_candidates<G: GoalKind<'tcx>>(
477627
&mut self,

compiler/rustc_trait_selection/src/solve/project_goals.rs

+3-45
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
5656
self.trait_def_id(tcx)
5757
}
5858

59-
fn consider_implied_clause(
59+
fn probe_and_match_goal_against_assumption(
6060
ecx: &mut EvalCtxt<'_, 'tcx>,
6161
goal: Goal<'tcx, Self>,
6262
assumption: ty::Predicate<'tcx>,
63-
requirements: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
63+
then: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> QueryResult<'tcx>,
6464
) -> QueryResult<'tcx> {
6565
if let Some(poly_projection_pred) = assumption.to_opt_poly_projection_pred()
6666
&& poly_projection_pred.projection_def_id() == goal.predicate.def_id()
@@ -75,49 +75,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
7575
)?;
7676
ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)
7777
.expect("expected goal term to be fully unconstrained");
78-
ecx.add_goals(requirements);
79-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
80-
})
81-
} else {
82-
Err(NoSolution)
83-
}
84-
}
85-
86-
fn consider_object_bound_candidate(
87-
ecx: &mut EvalCtxt<'_, 'tcx>,
88-
goal: Goal<'tcx, Self>,
89-
assumption: ty::Predicate<'tcx>,
90-
) -> QueryResult<'tcx> {
91-
if let Some(poly_projection_pred) = assumption.to_opt_poly_projection_pred()
92-
&& poly_projection_pred.projection_def_id() == goal.predicate.def_id()
93-
{
94-
ecx.probe(|ecx| {
95-
let tcx = ecx.tcx();
96-
97-
let assumption_projection_pred =
98-
ecx.instantiate_binder_with_infer(poly_projection_pred);
99-
ecx.eq(
100-
goal.param_env,
101-
goal.predicate.projection_ty,
102-
assumption_projection_pred.projection_ty,
103-
)?;
104-
105-
let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else {
106-
bug!("expected object type in `consider_object_bound_candidate`");
107-
};
108-
ecx.add_goals(
109-
structural_traits::predicates_for_object_candidate(
110-
&ecx,
111-
goal.param_env,
112-
goal.predicate.projection_ty.trait_ref(tcx),
113-
bounds,
114-
)
115-
.into_iter()
116-
.map(|pred| goal.with(tcx, pred)),
117-
);
118-
ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)
119-
.expect("expected goal term to be fully unconstrained");
120-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
78+
then(ecx)
12179
})
12280
} else {
12381
Err(NoSolution)

compiler/rustc_trait_selection/src/solve/trait_goals.rs

+3-44
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,11 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
7878
})
7979
}
8080

81-
fn consider_implied_clause(
81+
fn probe_and_match_goal_against_assumption(
8282
ecx: &mut EvalCtxt<'_, 'tcx>,
8383
goal: Goal<'tcx, Self>,
8484
assumption: ty::Predicate<'tcx>,
85-
requirements: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
85+
then: impl FnOnce(&mut EvalCtxt<'_, 'tcx>) -> QueryResult<'tcx>,
8686
) -> QueryResult<'tcx> {
8787
if let Some(poly_trait_pred) = assumption.to_opt_poly_trait_pred()
8888
&& poly_trait_pred.def_id() == goal.predicate.def_id()
@@ -97,48 +97,7 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
9797
goal.predicate.trait_ref,
9898
assumption_trait_pred.trait_ref,
9999
)?;
100-
ecx.add_goals(requirements);
101-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
102-
})
103-
} else {
104-
Err(NoSolution)
105-
}
106-
}
107-
108-
fn consider_object_bound_candidate(
109-
ecx: &mut EvalCtxt<'_, 'tcx>,
110-
goal: Goal<'tcx, Self>,
111-
assumption: ty::Predicate<'tcx>,
112-
) -> QueryResult<'tcx> {
113-
if let Some(poly_trait_pred) = assumption.to_opt_poly_trait_pred()
114-
&& poly_trait_pred.def_id() == goal.predicate.def_id()
115-
&& poly_trait_pred.polarity() == goal.predicate.polarity
116-
{
117-
// FIXME: Constness and polarity
118-
ecx.probe(|ecx| {
119-
let assumption_trait_pred =
120-
ecx.instantiate_binder_with_infer(poly_trait_pred);
121-
ecx.eq(
122-
goal.param_env,
123-
goal.predicate.trait_ref,
124-
assumption_trait_pred.trait_ref,
125-
)?;
126-
127-
let tcx = ecx.tcx();
128-
let ty::Dynamic(bounds, _, _) = *goal.predicate.self_ty().kind() else {
129-
bug!("expected object type in `consider_object_bound_candidate`");
130-
};
131-
ecx.add_goals(
132-
structural_traits::predicates_for_object_candidate(
133-
&ecx,
134-
goal.param_env,
135-
goal.predicate.trait_ref,
136-
bounds,
137-
)
138-
.into_iter()
139-
.map(|pred| goal.with(tcx, pred)),
140-
);
141-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
100+
then(ecx)
142101
})
143102
} else {
144103
Err(NoSolution)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// compile-flags: -Ztrait-solver=next
2+
3+
// Makes sure that alias bounds are not unsound!
4+
5+
#![feature(trivial_bounds)]
6+
7+
trait Foo {
8+
type Item: Copy
9+
where
10+
<Self as Foo>::Item: Copy;
11+
12+
fn copy_me(x: &Self::Item) -> Self::Item {
13+
*x
14+
}
15+
}
16+
17+
impl Foo for () {
18+
type Item = String where String: Copy;
19+
}
20+
21+
fn main() {
22+
let x = String::from("hello, world");
23+
drop(<() as Foo>::copy_me(&x));
24+
//~^ ERROR `<() as Foo>::Item: Copy` is not satisfied
25+
//~| ERROR `<() as Foo>::Item` is not well-formed
26+
println!("{x}");
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error[E0277]: the trait bound `<() as Foo>::Item: Copy` is not satisfied
2+
--> $DIR/alias-bound-unsound.rs:23:10
3+
|
4+
LL | drop(<() as Foo>::copy_me(&x));
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `<() as Foo>::Item`
6+
|
7+
note: required by a bound in `Foo::Item`
8+
--> $DIR/alias-bound-unsound.rs:10:30
9+
|
10+
LL | type Item: Copy
11+
| ---- required by a bound in this associated type
12+
LL | where
13+
LL | <Self as Foo>::Item: Copy;
14+
| ^^^^ required by this bound in `Foo::Item`
15+
16+
error: the type `<() as Foo>::Item` is not well-formed
17+
--> $DIR/alias-bound-unsound.rs:23:10
18+
|
19+
LL | drop(<() as Foo>::copy_me(&x));
20+
| ^^^^^^^^^^^^^^^^^^^^^^^^
21+
22+
error: aborting due to 2 previous errors
23+
24+
For more information about this error, try `rustc --explain E0277`.

0 commit comments

Comments
 (0)