about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBen Reeves <benwolverine2019@gmail.com>2022-03-24 19:24:40 -0500
committerBen Reeves <benwolverine2019@gmail.com>2022-11-10 12:37:06 -0600
commit5c25d30f6fe9996f815a96f4b328e62c452cc3e3 (patch)
tree98352a30c7f1a10d302c2be70e56d43de7f2ca95
parent01a6f30324deb8f9c9ec4a70c53690c5d073a244 (diff)
Allow specialized const trait impls.
Fixes #95186.
Fixes #95187.
-rw-r--r--compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs65
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-const-specialized.rs38
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.rs37
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.stderr37
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/default-keyword.rs14
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95186-specialize-on-tilde-const.rs34
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95187-same-trait-bound-different-constness.rs28
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.rs34
-rw-r--r--src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.stderr20
9 files changed, 286 insertions, 21 deletions
diff --git a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs
index 267077cdab4..f65760b9c98 100644
--- a/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs
+++ b/compiler/rustc_hir_analysis/src/impl_wf_check/min_specialization.rs
@@ -80,6 +80,7 @@ use rustc_span::Span;
 use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
 use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _;
 use rustc_trait_selection::traits::{self, translate_substs, wf, ObligationCtxt};
+use tracing::instrument;
 
 pub(super) fn check_min_specialization(tcx: TyCtxt<'_>, impl_def_id: LocalDefId) {
     if let Some(node) = parent_specialization_node(tcx, impl_def_id) {
@@ -103,13 +104,11 @@ fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId) -> Opti
 }
 
 /// Check that `impl1` is a sound specialization
+#[instrument(level = "debug", skip(tcx))]
 fn check_always_applicable(tcx: TyCtxt<'_>, impl1_def_id: LocalDefId, impl2_node: Node) {
     if let Some((impl1_substs, impl2_substs)) = get_impl_substs(tcx, impl1_def_id, impl2_node) {
         let impl2_def_id = impl2_node.def_id();
-        debug!(
-            "check_always_applicable(\nimpl1_def_id={:?},\nimpl2_def_id={:?},\nimpl2_substs={:?}\n)",
-            impl1_def_id, impl2_def_id, impl2_substs
-        );
+        debug!(?impl2_def_id, ?impl2_substs);
 
         let parent_substs = if impl2_node.is_from_trait() {
             impl2_substs.to_vec()
@@ -280,13 +279,13 @@ fn check_static_lifetimes<'tcx>(
 ///
 /// Each predicate `P` must be:
 ///
-/// * global (not reference any parameters)
-/// * `T: Tr` predicate where `Tr` is an always-applicable trait
-/// * on the base `impl impl2`
-///     * Currently this check is done using syntactic equality, which is
-///       conservative but generally sufficient.
-/// * a well-formed predicate of a type argument of the trait being implemented,
+/// * Global (not reference any parameters).
+/// * A `T: Tr` predicate where `Tr` is an always-applicable trait.
+/// * Present on the base impl `impl2`.
+///     * This check is done using the `trait_predicates_eq` function below.
+/// * A well-formed predicate of a type argument of the trait being implemented,
 ///   including the `Self`-type.
+#[instrument(level = "debug", skip(tcx))]
 fn check_predicates<'tcx>(
     tcx: TyCtxt<'tcx>,
     impl1_def_id: LocalDefId,
@@ -322,10 +321,7 @@ fn check_predicates<'tcx>(
         .map(|obligation| obligation.predicate)
         .collect()
     };
-    debug!(
-        "check_always_applicable(\nimpl1_predicates={:?},\nimpl2_predicates={:?}\n)",
-        impl1_predicates, impl2_predicates,
-    );
+    debug!(?impl1_predicates, ?impl2_predicates);
 
     // Since impls of always applicable traits don't get to assume anything, we
     // can also assume their supertraits apply.
@@ -373,25 +369,52 @@ fn check_predicates<'tcx>(
     );
 
     for (predicate, span) in impl1_predicates {
-        if !impl2_predicates.contains(&predicate) {
+        if !impl2_predicates.iter().any(|pred2| trait_predicates_eq(predicate, *pred2)) {
             check_specialization_on(tcx, predicate, span)
         }
     }
 }
 
+/// Checks whether two predicates are the same for the purposes of specialization.
+///
+/// This is slightly more complicated than simple syntactic equivalence, since
+/// we want to equate `T: Tr` with `T: ~const Tr` so this can work:
+///
+/// #[rustc_specialization_trait]
+/// trait Specialize { }
+///
+/// impl<T: ~const Bound> const Tr for T { }
+/// impl<T: Bound + Specialize> Tr for T { }
+fn trait_predicates_eq<'tcx>(
+    predicate1: ty::Predicate<'tcx>,
+    predicate2: ty::Predicate<'tcx>,
+) -> bool {
+    let predicate_kind_without_constness = |kind: ty::PredicateKind<'tcx>| match kind {
+        ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity }) => {
+            ty::PredicateKind::Trait(ty::TraitPredicate {
+                trait_ref,
+                constness: ty::BoundConstness::NotConst,
+                polarity,
+            })
+        }
+        _ => kind,
+    };
+
+    let pred1_kind_not_const = predicate1.kind().map_bound(predicate_kind_without_constness);
+    let pred2_kind_not_const = predicate2.kind().map_bound(predicate_kind_without_constness);
+
+    pred1_kind_not_const == pred2_kind_not_const
+}
+
+#[instrument(level = "debug", skip(tcx))]
 fn check_specialization_on<'tcx>(tcx: TyCtxt<'tcx>, predicate: ty::Predicate<'tcx>, span: Span) {
-    debug!("can_specialize_on(predicate = {:?})", predicate);
     match predicate.kind().skip_binder() {
         // Global predicates are either always true or always false, so we
         // are fine to specialize on.
         _ if predicate.is_global() => (),
         // We allow specializing on explicitly marked traits with no associated
         // items.
-        ty::PredicateKind::Trait(ty::TraitPredicate {
-            trait_ref,
-            constness: ty::BoundConstness::NotConst,
-            polarity: _,
-        }) => {
+        ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity: _ }) => {
             if !matches!(
                 trait_predicate_kind(tcx, predicate),
                 Some(TraitSpecializationKind::Marker)
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-const-specialized.rs b/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-const-specialized.rs
new file mode 100644
index 00000000000..1eddfbf50f3
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-const-specialized.rs
@@ -0,0 +1,38 @@
+// Tests that a const default trait impl can be specialized by another const
+// trait impl and that the specializing impl will be used during const-eval.
+
+// run-pass
+
+#![feature(const_trait_impl)]
+#![feature(min_specialization)]
+
+trait Value {
+    fn value() -> u32;
+}
+
+const fn get_value<T: ~const Value>() -> u32 {
+    T::value()
+}
+
+impl<T> const Value for T {
+    default fn value() -> u32 {
+        0
+    }
+}
+
+struct FortyTwo;
+
+impl const Value for FortyTwo {
+    fn value() -> u32 {
+        42
+    }
+}
+
+const ZERO: u32 = get_value::<()>();
+
+const FORTY_TWO: u32 = get_value::<FortyTwo>();
+
+fn main() {
+    assert_eq!(ZERO, 0);
+    assert_eq!(FORTY_TWO, 42);
+}
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.rs b/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.rs
new file mode 100644
index 00000000000..31de6fadeb7
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.rs
@@ -0,0 +1,37 @@
+// Tests that a const default trait impl can be specialized by a non-const trait
+// impl, but that the specializing impl cannot be used in a const context.
+
+#![feature(const_trait_impl)]
+#![feature(min_specialization)]
+
+trait Value {
+    fn value() -> u32;
+}
+
+const fn get_value<T: ~const Value>() -> u32 {
+    T::value()
+    //~^ ERROR any use of this value will cause an error [const_err]
+    //~| WARNING this was previously accepted
+}
+
+impl<T> const Value for T {
+    default fn value() -> u32 {
+        0
+    }
+}
+
+struct FortyTwo;
+
+impl Value for FortyTwo {
+    fn value() -> u32 {
+        println!("You can't do that (constly)");
+        42
+    }
+}
+
+const ZERO: u32 = get_value::<()>();
+
+const FORTY_TWO: u32 =
+    get_value::<FortyTwo>(); // This is the line that causes the error, but it gets reported above
+
+fn main() {}
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.stderr b/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.stderr
new file mode 100644
index 00000000000..7dfd489ea65
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.stderr
@@ -0,0 +1,37 @@
+error: any use of this value will cause an error
+  --> $DIR/const-default-non-const-specialized.rs:12:5
+   |
+LL |     T::value()
+   |     ^^^^^^^^^^
+   |     |
+   |     calling non-const function `<FortyTwo as Value>::value`
+   |     inside `get_value::<FortyTwo>` at $DIR/const-default-non-const-specialized.rs:12:5
+   |     inside `FORTY_TWO` at $DIR/const-default-non-const-specialized.rs:35:5
+...
+LL | const FORTY_TWO: u32 =
+   | --------------------
+   |
+   = note: `#[deny(const_err)]` on by default
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
+
+error: aborting due to previous error
+
+Future incompatibility report: Future breakage diagnostic:
+error: any use of this value will cause an error
+  --> $DIR/const-default-non-const-specialized.rs:12:5
+   |
+LL |     T::value()
+   |     ^^^^^^^^^^
+   |     |
+   |     calling non-const function `<FortyTwo as Value>::value`
+   |     inside `get_value::<FortyTwo>` at $DIR/const-default-non-const-specialized.rs:12:5
+   |     inside `FORTY_TWO` at $DIR/const-default-non-const-specialized.rs:35:5
+...
+LL | const FORTY_TWO: u32 =
+   | --------------------
+   |
+   = note: `#[deny(const_err)]` on by default
+   = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
+   = note: for more information, see issue #71800 <https://github.com/rust-lang/rust/issues/71800>
+
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/default-keyword.rs b/src/test/ui/rfc-2632-const-trait-impl/specialization/default-keyword.rs
new file mode 100644
index 00000000000..c03b0a0d19c
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/default-keyword.rs
@@ -0,0 +1,14 @@
+// check-pass
+
+#![feature(const_trait_impl)]
+#![feature(min_specialization)]
+
+trait Foo {
+    fn foo();
+}
+
+impl const Foo for u32 {
+    default fn foo() {}
+}
+
+fn main() {}
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95186-specialize-on-tilde-const.rs b/src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95186-specialize-on-tilde-const.rs
new file mode 100644
index 00000000000..1f7f47879d7
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95186-specialize-on-tilde-const.rs
@@ -0,0 +1,34 @@
+// Tests that `~const` trait bounds can be used to specialize const trait impls.
+
+// check-pass
+
+#![feature(const_trait_impl)]
+#![feature(rustc_attrs)]
+#![feature(min_specialization)]
+
+#[rustc_specialization_trait]
+trait Specialize {}
+
+trait Foo {}
+
+impl<T> const Foo for T {}
+
+impl<T> const Foo for T
+where
+    T: ~const Specialize,
+{}
+
+trait Bar {}
+
+impl<T> const Bar for T
+where
+    T: ~const Foo,
+{}
+
+impl<T> const Bar for T
+where
+    T: ~const Foo,
+    T: ~const Specialize,
+{}
+
+fn main() {}
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95187-same-trait-bound-different-constness.rs b/src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95187-same-trait-bound-different-constness.rs
new file mode 100644
index 00000000000..f6daba5595a
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95187-same-trait-bound-different-constness.rs
@@ -0,0 +1,28 @@
+// Tests that `T: ~const Foo` and `T: Foo` are treated as equivalent for the
+// purposes of min_specialization.
+
+// check-pass
+
+#![feature(rustc_attrs)]
+#![feature(min_specialization)]
+#![feature(const_trait_impl)]
+
+#[rustc_specialization_trait]
+trait Specialize {}
+
+trait Foo {}
+
+trait Bar {}
+
+impl<T> const Bar for T
+where
+    T: ~const Foo,
+{}
+
+impl<T> Bar for T
+where
+    T: Foo,
+    T: Specialize,
+{}
+
+fn main() {}
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.rs b/src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.rs
new file mode 100644
index 00000000000..cf6c292e8a4
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.rs
@@ -0,0 +1,34 @@
+// Tests that a non-const default impl can be specialized by a const trait impl,
+// but that the default impl cannot be used in a const context.
+
+#![feature(const_trait_impl)]
+#![feature(min_specialization)]
+
+trait Value {
+    fn value() -> u32;
+}
+
+const fn get_value<T: ~const Value>() -> u32 {
+    T::value()
+}
+
+impl<T> Value for T {
+    default fn value() -> u32 {
+        println!("You can't do that (constly)");
+        0
+    }
+}
+
+struct FortyTwo;
+
+impl const Value for FortyTwo {
+    fn value() -> u32 {
+        42
+    }
+}
+
+const ZERO: u32 = get_value::<()>(); //~ ERROR the trait bound `(): ~const Value` is not satisfied
+
+const FORTY_TWO: u32 = get_value::<FortyTwo>();
+
+fn main() {}
diff --git a/src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.stderr b/src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.stderr
new file mode 100644
index 00000000000..1065009c891
--- /dev/null
+++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.stderr
@@ -0,0 +1,20 @@
+error[E0277]: the trait bound `(): ~const Value` is not satisfied
+  --> $DIR/non-const-default-const-specialized.rs:30:31
+   |
+LL | const ZERO: u32 = get_value::<()>();
+   |                               ^^ the trait `~const Value` is not implemented for `()`
+   |
+note: the trait `Value` is implemented for `()`, but that implementation is not `const`
+  --> $DIR/non-const-default-const-specialized.rs:30:31
+   |
+LL | const ZERO: u32 = get_value::<()>();
+   |                               ^^
+note: required by a bound in `get_value`
+  --> $DIR/non-const-default-const-specialized.rs:11:23
+   |
+LL | const fn get_value<T: ~const Value>() -> u32 {
+   |                       ^^^^^^^^^^^^ required by this bound in `get_value`
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0277`.