diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 48ca321b73746..d213702f1f37d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1132,6 +1132,7 @@ symbols! { repr_transparent, residual, result, + result_tracing, rhs, rintf32, rintf64, diff --git a/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs b/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs index 6cef3e9d9409e..771361221ed3c 100644 --- a/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs +++ b/compiler/rustc_typeck/src/impl_wf_check/min_specialization.rs @@ -77,6 +77,7 @@ use rustc_middle::ty::trait_def::TraitSpecializationKind; use rustc_middle::ty::{self, TyCtxt, TypeFoldable}; use rustc_span::Span; use rustc_trait_selection::traits::{self, translate_substs, wf}; +use tracing::instrument; pub(super) fn check_min_specialization(tcx: TyCtxt<'_>, impl_def_id: DefId, span: Span) { if let Some(node) = parent_specialization_node(tcx, impl_def_id) { @@ -102,6 +103,7 @@ fn parent_specialization_node(tcx: TyCtxt<'_>, impl1_def_id: DefId) -> Option, impl1_def_id: DefId, @@ -112,10 +114,8 @@ fn check_always_applicable( get_impl_substs(infcx, impl1_def_id, impl2_node, span) { 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_def_id:?}"); + debug!("impl2_substs={impl2_substs:?}"); let tcx = infcx.tcx; @@ -278,10 +278,10 @@ fn check_static_lifetimes<'tcx>( /// * 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. +/// * 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(infcx))] fn check_predicates<'tcx>( infcx: &InferCtxt<'_, 'tcx>, impl1_def_id: LocalDefId, @@ -313,10 +313,8 @@ 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={impl1_predicates:?}"); + debug!("impl2_predicates={impl2_predicates:?}"); // Since impls of always applicable traits don't get to assume anything, we // can also assume their supertraits apply. @@ -362,25 +360,52 @@ fn check_predicates<'tcx>( ); for predicate 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) @@ -409,13 +434,10 @@ fn trait_predicate_kind<'tcx>( predicate: ty::Predicate<'tcx>, ) -> Option { match predicate.kind().skip_binder() { - ty::PredicateKind::Trait(ty::TraitPredicate { - trait_ref, - constness: ty::BoundConstness::NotConst, - polarity: _, - }) => Some(tcx.trait_def(trait_ref.def_id).specialization_kind), - ty::PredicateKind::Trait(_) - | ty::PredicateKind::RegionOutlives(_) + ty::PredicateKind::Trait(ty::TraitPredicate { trait_ref, constness: _, polarity: _ }) => { + Some(tcx.trait_def(trait_ref.def_id).specialization_kind) + } + ty::PredicateKind::RegionOutlives(_) | ty::PredicateKind::TypeOutlives(_) | ty::PredicateKind::Projection(_) | ty::PredicateKind::WellFormed(_) diff --git a/library/core/src/result.rs b/library/core/src/result.rs index afd0c8572291d..6ed4703c8acab 100644 --- a/library/core/src/result.rs +++ b/library/core/src/result.rs @@ -492,7 +492,7 @@ use crate::iter::{self, FromIterator, FusedIterator, TrustedLen}; use crate::marker::Destruct; use crate::ops::{self, ControlFlow, Deref, DerefMut}; -use crate::{convert, fmt, hint}; +use crate::{convert, fmt, hint, panic}; /// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]). /// @@ -2101,14 +2101,76 @@ impl> const ops::FromResidual) -> Self { + default fn from_residual(residual: Result) -> Self { match residual { Err(e) => Err(From::from(e)), } } } +// FIXME(bgr360): how to properly add this change that depends on compiler +// functionality that's not yet in the beta? +#[cfg(not(bootstrap))] +#[unstable(feature = "result_tracing", issue = "none")] +#[rustc_const_unstable(feature = "const_convert", issue = "88674")] +impl const ops::FromResidual> for Result +where + F: ~const From + ~const Trace, +{ + #[inline] + #[track_caller] + fn from_residual(residual: Result) -> Self { + match residual { + Err(e) => { + let mut traced = F::from(e); + traced.trace(panic::Location::caller()); + Err(traced) + } + } + } +} + #[unstable(feature = "try_trait_v2_residual", issue = "91285")] impl ops::Residual for Result { type TryType = Result; } + +/// A trait that enables try-tracing for [`Err`] variants of [`Result`]. +/// +/// Implementing this trait on your error type will notify you every time the +/// try operator (`?`) is invoked on your result, providing you with the source +/// code location of the `?` invocation. +/// +/// # Example Usage +/// +/// Given the following code: +/// +/// ``` +/// #![feature(result_tracing)] +/// #![feature(min_specialization)] +/// +/// struct MyError; +/// +/// impl std::result::Trace for MyError { +/// fn trace(&mut self, location: &'static std::panic::Location<'static>) { +/// println!("`?` invoked at {}", location); +/// } +/// } +/// +/// # fn foo() -> Result<(), MyError> { +/// Err(MyError)? +/// # } +/// # foo().ok(); +/// ``` +/// +/// Output like the following would be produced: +/// +/// ```txt +/// `?` invoked at main.rs:LL:CC +/// ``` +#[unstable(feature = "result_tracing", issue = "none")] +#[rustc_specialization_trait] +pub trait Trace { + /// Called during `?` with the source code location of the `?` invocation. + fn trace(&mut self, location: &'static panic::Location<'static>); +} diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 5c861236e86f1..338e0830232e3 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -76,6 +76,7 @@ #![feature(never_type)] #![feature(unwrap_infallible)] #![feature(result_into_ok_or_err)] +#![feature(result_tracing)] #![feature(portable_simd)] #![feature(ptr_metadata)] #![feature(once_cell)] diff --git a/library/core/tests/result.rs b/library/core/tests/result.rs index 98b870512b046..ace04f919954f 100644 --- a/library/core/tests/result.rs +++ b/library/core/tests/result.rs @@ -425,3 +425,74 @@ fn result_try_trait_v2_branch() { assert_eq!(Ok::(one).branch(), Continue(one)); assert_eq!(Err::(()).branch(), Break(Err(()))); } + +mod result_tracing { + use core::panic; + use core::result::Trace; + + #[derive(Debug, PartialEq, Eq)] + struct TracedError(pub u32); + + impl From for TracedError { + fn from(i: u32) -> Self { + Self(i) + } + } + + impl Trace for TracedError { + // Increment by 1 on every `?` invocation. + fn trace(&mut self, _location: &'static panic::Location<'static>) { + self.0 += 1; + } + } + + #[test] + fn try_operator_calls_trace() { + fn foo() -> Result<(), TracedError> { + Err(TracedError(0)) + } + + fn bar() -> Result<(), TracedError> { + Ok(foo()?) + } + + fn baz() -> Result<(), TracedError> { + Ok(bar()?) + } + + let result = baz(); + assert_eq!(result, Err(TracedError(2))); + } + + #[test] + fn try_operator_converts_into_traced_type_and_calls_trace() { + fn foo() -> Result<(), TracedError> { + Err(0)? + } + + let result = foo(); + assert_eq!(result, Err(TracedError(1))); + } + + #[test] + fn try_operator_provides_correct_location() { + #[derive(Default)] + struct TheLocation(pub Option<&'static panic::Location<'static>>); + + impl Trace for TheLocation { + fn trace(&mut self, location: &'static panic::Location<'static>) { + self.0 = Some(location); + } + } + + let two_lines_before = panic::Location::caller(); + fn foo() -> Result<(), TheLocation> { + Err(TheLocation::default())? + } + + let result = foo(); + let location = result.unwrap_err().0.unwrap(); + assert_eq!(location.file(), two_lines_before.file()); + assert_eq!(location.line(), two_lines_before.line() + 2); + } +} 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 0000000000000..1eddfbf50f386 --- /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 0000000000000..48cab388e582a --- /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 { + // Ideally this error would show up at the call to `get_value`, not here. + T::value() + //~^ ERROR any use of this value will cause an error + //~| 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::(); + +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 0000000000000..c382781bb50ff --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/specialization/const-default-non-const-specialized.stderr @@ -0,0 +1,19 @@ +error: any use of this value will cause an error + --> $DIR/const-default-non-const-specialized.rs:13:5 + | +LL | T::value() + | ^^^^^^^^^^ + | | + | calling non-const function `::value` + | inside `get_value::` at $DIR/const-default-non-const-specialized.rs:13:5 + | inside `FORTY_TWO` at $DIR/const-default-non-const-specialized.rs:35:24 +... +LL | const FORTY_TWO: u32 = get_value::(); + | ----------------------------------------------- + | + = 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 + 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 0000000000000..c03b0a0d19ca5 --- /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 0000000000000..1f7f47879d78b --- /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 0000000000000..f6daba5595a8d --- /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 0000000000000..cf6c292e8a465 --- /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 0000000000000..1065009c8910e --- /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`.