Make alias bounds sound in the new solver
This commit is contained in:
parent
3a37c2f052
commit
0dbaae4165
@ -8,6 +8,7 @@
|
|||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
use rustc_infer::traits::query::NoSolution;
|
use rustc_infer::traits::query::NoSolution;
|
||||||
use rustc_infer::traits::util::elaborate;
|
use rustc_infer::traits::util::elaborate;
|
||||||
|
use rustc_infer::traits::Reveal;
|
||||||
use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, MaybeCause, QueryResult};
|
use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, MaybeCause, QueryResult};
|
||||||
use rustc_middle::ty::fast_reject::TreatProjections;
|
use rustc_middle::ty::fast_reject::TreatProjections;
|
||||||
use rustc_middle::ty::TypeFoldable;
|
use rustc_middle::ty::TypeFoldable;
|
||||||
@ -87,7 +88,9 @@ pub(super) enum CandidateSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Methods used to assemble candidates for either trait or projection goals.
|
/// Methods used to assemble candidates for either trait or projection goals.
|
||||||
pub(super) trait GoalKind<'tcx>: TypeFoldable<TyCtxt<'tcx>> + Copy + Eq {
|
pub(super) trait GoalKind<'tcx>:
|
||||||
|
TypeFoldable<TyCtxt<'tcx>> + Copy + Eq + std::fmt::Display
|
||||||
|
{
|
||||||
fn self_ty(self) -> Ty<'tcx>;
|
fn self_ty(self) -> Ty<'tcx>;
|
||||||
|
|
||||||
fn trait_ref(self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx>;
|
fn trait_ref(self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx>;
|
||||||
@ -106,6 +109,16 @@ fn consider_implied_clause(
|
|||||||
requirements: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
|
requirements: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'tcx>>>,
|
||||||
) -> QueryResult<'tcx>;
|
) -> QueryResult<'tcx>;
|
||||||
|
|
||||||
|
/// Consider a bound originating from the item bounds of an alias. For this we
|
||||||
|
/// require that the well-formed requirements of the self type of the goal
|
||||||
|
/// are "satisfied from the param-env".
|
||||||
|
/// See [`EvalCtxt::validate_alias_bound_self_from_param_env`].
|
||||||
|
fn consider_alias_bound_candidate(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
assumption: ty::Predicate<'tcx>,
|
||||||
|
) -> QueryResult<'tcx>;
|
||||||
|
|
||||||
// Consider a clause specifically for a `dyn Trait` self type. This requires
|
// Consider a clause specifically for a `dyn Trait` self type. This requires
|
||||||
// additionally checking all of the supertraits and object bounds to hold,
|
// additionally checking all of the supertraits and object bounds to hold,
|
||||||
// since they're not implied by the well-formedness of the object type.
|
// since they're not implied by the well-formedness of the object type.
|
||||||
@ -463,7 +476,7 @@ fn assemble_alias_bound_candidates<G: GoalKind<'tcx>>(
|
|||||||
|
|
||||||
for assumption in self.tcx().item_bounds(alias_ty.def_id).subst(self.tcx(), alias_ty.substs)
|
for assumption in self.tcx().item_bounds(alias_ty.def_id).subst(self.tcx(), alias_ty.substs)
|
||||||
{
|
{
|
||||||
match G::consider_implied_clause(self, goal, assumption, []) {
|
match G::consider_alias_bound_candidate(self, goal, assumption) {
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
candidates.push(Candidate { source: CandidateSource::AliasBound, result })
|
candidates.push(Candidate { source: CandidateSource::AliasBound, result })
|
||||||
}
|
}
|
||||||
@ -472,6 +485,105 @@ fn assemble_alias_bound_candidates<G: GoalKind<'tcx>>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check that we are allowed to use an alias bound originating from the self
|
||||||
|
/// type of this goal. This means something different depending on the self type's
|
||||||
|
/// alias kind.
|
||||||
|
///
|
||||||
|
/// * Projection: Given a goal with a self type such as `<Ty as Trait>::Assoc`,
|
||||||
|
/// we require that the bound `Ty: Trait` can be proven using either a nested alias
|
||||||
|
/// bound candidate, or a param-env candidate.
|
||||||
|
///
|
||||||
|
/// * Opaque: The param-env must be in `Reveal::UserFacing` mode. Otherwise,
|
||||||
|
/// the goal should be proven by using the hidden type instead.
|
||||||
|
#[instrument(level = "debug", skip(self), ret)]
|
||||||
|
pub(super) fn validate_alias_bound_self_from_param_env<G: GoalKind<'tcx>>(
|
||||||
|
&mut self,
|
||||||
|
goal: Goal<'tcx, G>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
match *goal.predicate.self_ty().kind() {
|
||||||
|
ty::Alias(ty::Projection, projection_ty) => {
|
||||||
|
let mut param_env_candidates = vec![];
|
||||||
|
let self_trait_ref = projection_ty.trait_ref(self.tcx());
|
||||||
|
|
||||||
|
if self_trait_ref.self_ty().is_ty_var() {
|
||||||
|
return self
|
||||||
|
.evaluate_added_goals_and_make_canonical_response(Certainty::AMBIGUOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
let trait_goal: Goal<'_, ty::TraitPredicate<'tcx>> = goal.with(
|
||||||
|
self.tcx(),
|
||||||
|
ty::TraitPredicate {
|
||||||
|
trait_ref: self_trait_ref,
|
||||||
|
constness: ty::BoundConstness::NotConst,
|
||||||
|
polarity: ty::ImplPolarity::Positive,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
self.assemble_param_env_candidates(trait_goal, &mut param_env_candidates);
|
||||||
|
// FIXME: We probably need some sort of recursion depth check here.
|
||||||
|
// Can't come up with an example yet, though, and the worst case
|
||||||
|
// we can have is a compiler stack overflow...
|
||||||
|
self.assemble_alias_bound_candidates(trait_goal, &mut param_env_candidates);
|
||||||
|
|
||||||
|
// FIXME: We must also consider alias-bound candidates for a peculiar
|
||||||
|
// class of built-in candidates that I'll call "defaulted" built-ins.
|
||||||
|
//
|
||||||
|
// For example, we always know that `T: Pointee` is implemented, but
|
||||||
|
// we do not always know what `<T as Pointee>::Metadata` actually is,
|
||||||
|
// similar to if we had a user-defined impl with a `default type ...`.
|
||||||
|
// For these traits, since we're not able to always normalize their
|
||||||
|
// associated types to a concrete type, we must consider their alias bounds
|
||||||
|
// instead, so we can prove bounds such as `<T as Pointee>::Metadata: Copy`.
|
||||||
|
self.assemble_alias_bound_candidates_for_builtin_impl_default_items(
|
||||||
|
trait_goal,
|
||||||
|
&mut param_env_candidates,
|
||||||
|
);
|
||||||
|
|
||||||
|
self.merge_candidates(param_env_candidates)
|
||||||
|
}
|
||||||
|
ty::Alias(ty::Opaque, _opaque_ty) => match goal.param_env.reveal() {
|
||||||
|
Reveal::UserFacing => {
|
||||||
|
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
|
||||||
|
}
|
||||||
|
Reveal::All => return Err(NoSolution),
|
||||||
|
},
|
||||||
|
_ => bug!("only expected to be called on alias tys"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Assemble a subset of builtin impl candidates for a class of candidates called
|
||||||
|
/// "defaulted" built-in traits.
|
||||||
|
///
|
||||||
|
/// For example, we always know that `T: Pointee` is implemented, but we do not
|
||||||
|
/// always know what `<T as Pointee>::Metadata` actually is! See the comment in
|
||||||
|
/// [`EvalCtxt::validate_alias_bound_self_from_param_env`] for more detail.
|
||||||
|
#[instrument(level = "debug", skip_all)]
|
||||||
|
fn assemble_alias_bound_candidates_for_builtin_impl_default_items<G: GoalKind<'tcx>>(
|
||||||
|
&mut self,
|
||||||
|
goal: Goal<'tcx, G>,
|
||||||
|
candidates: &mut Vec<Candidate<'tcx>>,
|
||||||
|
) {
|
||||||
|
let lang_items = self.tcx().lang_items();
|
||||||
|
let trait_def_id = goal.predicate.trait_def_id(self.tcx());
|
||||||
|
|
||||||
|
// You probably shouldn't add anything to this list unless you
|
||||||
|
// know what you're doing.
|
||||||
|
let result = if lang_items.pointee_trait() == Some(trait_def_id) {
|
||||||
|
G::consider_builtin_pointee_candidate(self, goal)
|
||||||
|
} else if lang_items.discriminant_kind_trait() == Some(trait_def_id) {
|
||||||
|
G::consider_builtin_discriminant_kind_candidate(self, goal)
|
||||||
|
} else {
|
||||||
|
Err(NoSolution)
|
||||||
|
};
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(result) => {
|
||||||
|
candidates.push(Candidate { source: CandidateSource::BuiltinImpl, result })
|
||||||
|
}
|
||||||
|
Err(NoSolution) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[instrument(level = "debug", skip_all)]
|
#[instrument(level = "debug", skip_all)]
|
||||||
fn assemble_object_bound_candidates<G: GoalKind<'tcx>>(
|
fn assemble_object_bound_candidates<G: GoalKind<'tcx>>(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -83,6 +83,30 @@ fn consider_implied_clause(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn consider_alias_bound_candidate(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
assumption: ty::Predicate<'tcx>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
if let Some(poly_projection_pred) = assumption.to_opt_poly_projection_pred()
|
||||||
|
&& poly_projection_pred.projection_def_id() == goal.predicate.def_id()
|
||||||
|
{
|
||||||
|
ecx.probe(|ecx| {
|
||||||
|
let assumption_projection_pred =
|
||||||
|
ecx.instantiate_binder_with_infer(poly_projection_pred);
|
||||||
|
ecx.eq(
|
||||||
|
goal.param_env,
|
||||||
|
goal.predicate.projection_ty,
|
||||||
|
assumption_projection_pred.projection_ty,
|
||||||
|
)?;
|
||||||
|
ecx.eq(goal.param_env, goal.predicate.term, assumption_projection_pred.term)?;
|
||||||
|
ecx.validate_alias_bound_self_from_param_env(goal)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(NoSolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn consider_object_bound_candidate(
|
fn consider_object_bound_candidate(
|
||||||
ecx: &mut EvalCtxt<'_, 'tcx>,
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
goal: Goal<'tcx, Self>,
|
goal: Goal<'tcx, Self>,
|
||||||
|
@ -105,6 +105,30 @@ fn consider_implied_clause(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn consider_alias_bound_candidate(
|
||||||
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
|
goal: Goal<'tcx, Self>,
|
||||||
|
assumption: ty::Predicate<'tcx>,
|
||||||
|
) -> QueryResult<'tcx> {
|
||||||
|
if let Some(poly_trait_pred) = assumption.to_opt_poly_trait_pred()
|
||||||
|
&& poly_trait_pred.def_id() == goal.predicate.def_id()
|
||||||
|
{
|
||||||
|
// FIXME: Constness and polarity
|
||||||
|
ecx.probe(|ecx| {
|
||||||
|
let assumption_trait_pred =
|
||||||
|
ecx.instantiate_binder_with_infer(poly_trait_pred);
|
||||||
|
ecx.eq(
|
||||||
|
goal.param_env,
|
||||||
|
goal.predicate.trait_ref,
|
||||||
|
assumption_trait_pred.trait_ref,
|
||||||
|
)?;
|
||||||
|
ecx.validate_alias_bound_self_from_param_env(goal)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(NoSolution)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn consider_object_bound_candidate(
|
fn consider_object_bound_candidate(
|
||||||
ecx: &mut EvalCtxt<'_, 'tcx>,
|
ecx: &mut EvalCtxt<'_, 'tcx>,
|
||||||
goal: Goal<'tcx, Self>,
|
goal: Goal<'tcx, Self>,
|
||||||
|
27
tests/ui/traits/new-solver/alias-bound-unsound.rs
Normal file
27
tests/ui/traits/new-solver/alias-bound-unsound.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// compile-flags: -Ztrait-solver=next
|
||||||
|
|
||||||
|
// Makes sure that alias bounds are not unsound!
|
||||||
|
|
||||||
|
#![feature(trivial_bounds)]
|
||||||
|
|
||||||
|
trait Foo {
|
||||||
|
type Item: Copy
|
||||||
|
where
|
||||||
|
<Self as Foo>::Item: Copy;
|
||||||
|
|
||||||
|
fn copy_me(x: &Self::Item) -> Self::Item {
|
||||||
|
*x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Foo for () {
|
||||||
|
type Item = String where String: Copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let x = String::from("hello, world");
|
||||||
|
drop(<() as Foo>::copy_me(&x));
|
||||||
|
//~^ ERROR `<() as Foo>::Item: Copy` is not satisfied
|
||||||
|
//~| ERROR `<() as Foo>::Item` is not well-formed
|
||||||
|
println!("{x}");
|
||||||
|
}
|
24
tests/ui/traits/new-solver/alias-bound-unsound.stderr
Normal file
24
tests/ui/traits/new-solver/alias-bound-unsound.stderr
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
error[E0277]: the trait bound `<() as Foo>::Item: Copy` is not satisfied
|
||||||
|
--> $DIR/alias-bound-unsound.rs:23:10
|
||||||
|
|
|
||||||
|
LL | drop(<() as Foo>::copy_me(&x));
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Copy` is not implemented for `<() as Foo>::Item`
|
||||||
|
|
|
||||||
|
note: required by a bound in `Foo::Item`
|
||||||
|
--> $DIR/alias-bound-unsound.rs:10:30
|
||||||
|
|
|
||||||
|
LL | type Item: Copy
|
||||||
|
| ---- required by a bound in this associated type
|
||||||
|
LL | where
|
||||||
|
LL | <Self as Foo>::Item: Copy;
|
||||||
|
| ^^^^ required by this bound in `Foo::Item`
|
||||||
|
|
||||||
|
error: the type `<() as Foo>::Item` is not well-formed
|
||||||
|
--> $DIR/alias-bound-unsound.rs:23:10
|
||||||
|
|
|
||||||
|
LL | drop(<() as Foo>::copy_me(&x));
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0277`.
|
20
tests/ui/traits/new-solver/nested-alias-bound.rs
Normal file
20
tests/ui/traits/new-solver/nested-alias-bound.rs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// compile-flags: -Ztrait-solver=next
|
||||||
|
// check-pass
|
||||||
|
|
||||||
|
trait A {
|
||||||
|
type A: B;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait B {
|
||||||
|
type B: C;
|
||||||
|
}
|
||||||
|
|
||||||
|
trait C {}
|
||||||
|
|
||||||
|
fn needs_c<T: C>() {}
|
||||||
|
|
||||||
|
fn test<T: A>() {
|
||||||
|
needs_c::<<T::A as B>::B>();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {}
|
Loading…
Reference in New Issue
Block a user