about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_trait_selection/src/solve/assembly/mod.rs116
-rw-r--r--compiler/rustc_trait_selection/src/solve/project_goals.rs24
-rw-r--r--compiler/rustc_trait_selection/src/solve/trait_goals.rs24
-rw-r--r--tests/ui/traits/new-solver/alias-bound-unsound.rs27
-rw-r--r--tests/ui/traits/new-solver/alias-bound-unsound.stderr24
-rw-r--r--tests/ui/traits/new-solver/nested-alias-bound.rs20
6 files changed, 233 insertions, 2 deletions
diff --git a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
index bacb0e32efc..0f42d5f1262 100644
--- a/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
+++ b/compiler/rustc_trait_selection/src/solve/assembly/mod.rs
@@ -8,6 +8,7 @@ use rustc_data_structures::fx::FxIndexSet;
 use rustc_hir::def_id::DefId;
 use rustc_infer::traits::query::NoSolution;
 use rustc_infer::traits::util::elaborate;
+use rustc_infer::traits::Reveal;
 use rustc_middle::traits::solve::{CanonicalResponse, Certainty, Goal, MaybeCause, QueryResult};
 use rustc_middle::ty::fast_reject::TreatProjections;
 use rustc_middle::ty::TypeFoldable;
@@ -87,7 +88,9 @@ pub(super) enum CandidateSource {
 }
 
 /// 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 trait_ref(self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx>;
@@ -106,6 +109,16 @@ pub(super) trait GoalKind<'tcx>: TypeFoldable<TyCtxt<'tcx>> + Copy + Eq {
         requirements: impl IntoIterator<Item = Goal<'tcx, ty::Predicate<'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
     // additionally checking all of the supertraits and object bounds to hold,
     // since they're not implied by the well-formedness of the object type.
@@ -463,7 +476,7 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
 
         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) => {
                     candidates.push(Candidate { source: CandidateSource::AliasBound, result })
                 }
@@ -472,6 +485,105 @@ impl<'tcx> EvalCtxt<'_, '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)]
     fn assemble_object_bound_candidates<G: GoalKind<'tcx>>(
         &mut self,
diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs
index e5d51064c8d..85ac7f15a2f 100644
--- a/compiler/rustc_trait_selection/src/solve/project_goals.rs
+++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs
@@ -83,6 +83,30 @@ impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> {
         }
     }
 
+    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(
         ecx: &mut EvalCtxt<'_, 'tcx>,
         goal: Goal<'tcx, Self>,
diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
index 04b38edc126..0f6fd4059b4 100644
--- a/compiler/rustc_trait_selection/src/solve/trait_goals.rs
+++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs
@@ -105,6 +105,30 @@ impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> {
         }
     }
 
+    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(
         ecx: &mut EvalCtxt<'_, 'tcx>,
         goal: Goal<'tcx, Self>,
diff --git a/tests/ui/traits/new-solver/alias-bound-unsound.rs b/tests/ui/traits/new-solver/alias-bound-unsound.rs
new file mode 100644
index 00000000000..00294c708f1
--- /dev/null
+++ b/tests/ui/traits/new-solver/alias-bound-unsound.rs
@@ -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}");
+}
diff --git a/tests/ui/traits/new-solver/alias-bound-unsound.stderr b/tests/ui/traits/new-solver/alias-bound-unsound.stderr
new file mode 100644
index 00000000000..9a43d2a6639
--- /dev/null
+++ b/tests/ui/traits/new-solver/alias-bound-unsound.stderr
@@ -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`.
diff --git a/tests/ui/traits/new-solver/nested-alias-bound.rs b/tests/ui/traits/new-solver/nested-alias-bound.rs
new file mode 100644
index 00000000000..c365902dbe5
--- /dev/null
+++ b/tests/ui/traits/new-solver/nested-alias-bound.rs
@@ -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() {}