From 5c25d30f6fe9996f815a96f4b328e62c452cc3e3 Mon Sep 17 00:00:00 2001 From: Ben Reeves Date: Thu, 24 Mar 2022 19:24:40 -0500 Subject: [PATCH] Allow specialized const trait impls. Fixes #95186. Fixes #95187. --- .../src/impl_wf_check/min_specialization.rs | 65 +++++++++++++------ .../const-default-const-specialized.rs | 38 +++++++++++ .../const-default-non-const-specialized.rs | 37 +++++++++++ ...const-default-non-const-specialized.stderr | 37 +++++++++++ .../specialization/default-keyword.rs | 14 ++++ .../issue-95186-specialize-on-tilde-const.rs | 34 ++++++++++ ...87-same-trait-bound-different-constness.rs | 28 ++++++++ .../non-const-default-const-specialized.rs | 34 ++++++++++ ...non-const-default-const-specialized.stderr | 20 ++++++ 9 files changed, 286 insertions(+), 21 deletions(-) create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-const-specialized.rs create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.rs create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.stderr create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/default-keyword.rs create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95186-specialize-on-tilde-const.rs create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/issue-95187-same-trait-bound-different-constness.rs create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.rs create mode 100644 src/test/ui/rfc-2632-const-trait-impl/specialization/non-const-default-const-specialized.stderr 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_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 const Tr for T { } +/// impl 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() -> u32 { + T::value() +} + +impl 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::(); + +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() -> u32 { + T::value() + //~^ ERROR any use of this value will cause an error [const_err] + //~| WARNING this was previously accepted +} + +impl 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::(); // 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 `::value` + | inside `get_value::` 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 + +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 `::value` + | inside `get_value::` 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 + 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 const Foo for T {} + +impl const Foo for T +where + T: ~const Specialize, +{} + +trait Bar {} + +impl const Bar for T +where + T: ~const Foo, +{} + +impl 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 const Bar for T +where + T: ~const Foo, +{} + +impl 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() -> u32 { + T::value() +} + +impl 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::(); + +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() -> u32 { + | ^^^^^^^^^^^^ required by this bound in `get_value` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0277`.