From 73476d8c52be4e6b1ff5487f03bd2026993f79fe Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 3 Dec 2025 22:50:48 +0000 Subject: [PATCH 1/4] move error.rs to error/mod.rs --- library/core/src/{error.rs => error/mod.rs} | 0 tests/ui/span/issue-71363.stderr | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename library/core/src/{error.rs => error/mod.rs} (100%) diff --git a/library/core/src/error.rs b/library/core/src/error/mod.rs similarity index 100% rename from library/core/src/error.rs rename to library/core/src/error/mod.rs diff --git a/tests/ui/span/issue-71363.stderr b/tests/ui/span/issue-71363.stderr index d2f780bdbcb43..fde9f332f54ac 100644 --- a/tests/ui/span/issue-71363.stderr +++ b/tests/ui/span/issue-71363.stderr @@ -10,7 +10,7 @@ help: the trait `std::fmt::Display` is not implemented for `MyError` 3 | struct MyError; | ^^^^^^^^^^^^^^ note: required by a bound in `std::error::Error` - --> $SRC_DIR/core/src/error.rs:LL:COL + --> $SRC_DIR/core/src/error/mod.rs:LL:COL error[E0277]: `MyError` doesn't implement `Debug` --> $DIR/issue-71363.rs:4:28 @@ -20,7 +20,7 @@ error[E0277]: `MyError` doesn't implement `Debug` | = note: add `#[derive(Debug)]` to `MyError` or manually `impl Debug for MyError` note: required by a bound in `std::error::Error` - --> $SRC_DIR/core/src/error.rs:LL:COL + --> $SRC_DIR/core/src/error/mod.rs:LL:COL help: consider annotating `MyError` with `#[derive(Debug)]` | 3 + #[derive(Debug)] From e7e68a865bfbf2e6c1d7bcbb4cd04d1fdcc6ea56 Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 3 Dec 2025 23:09:39 +0000 Subject: [PATCH 2/4] move the error provide code to its own module --- library/core/src/error/mod.rs | 697 +++--------------------------- library/core/src/error/provide.rs | 642 +++++++++++++++++++++++++++ 2 files changed, 697 insertions(+), 642 deletions(-) create mode 100644 library/core/src/error/provide.rs diff --git a/library/core/src/error/mod.rs b/library/core/src/error/mod.rs index 9ca91ee009ee9..b0bf5a4b3f0fb 100644 --- a/library/core/src/error/mod.rs +++ b/library/core/src/error/mod.rs @@ -1,8 +1,13 @@ -#![doc = include_str!("error.md")] +#![doc = include_str!("../error.md")] #![stable(feature = "error_in_core", since = "1.81.0")] use crate::any::TypeId; -use crate::fmt::{self, Debug, Display, Formatter}; +use crate::fmt::{Debug, Display}; + +mod provide; + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +pub use provide::{Request, request_ref, request_value}; /// `Error` is a trait representing the basic expectations for error values, /// i.e., values of type `E` in [`Result`]. @@ -153,12 +158,52 @@ pub trait Error: Debug + Display { /// Used in conjunction with [`Request::provide_value`] and [`Request::provide_ref`] to extract /// references to member variables from `dyn Error` trait objects. /// + /// Every individual error type can `provide` some types that contain programmatic + /// information about the error. The set of values `provide`d by a given error type + /// can normally change between different versions of that error type's library. However, + /// a library defining an error type can always make stronger backwards-compatibility + /// promises - for example, a library can declare that an error type always provides a + /// [`Location`](core::panic::Location) that provides a relevant source-code location. + /// + /// # Whether to provide by reference or by value + /// + /// [`Request::provide_value`] and [`Request::provide_ref`] are two different namespaces. + /// Therefore, when providing a type, it needs to be picked whether it will be provided + /// by reference or by value. + /// + /// If a type is provided by value, then a new copy of that type has to be created every + /// time it is provided, but if it is provided by reference, then the provided value has + /// to be stored somewhere within the error so that the reference can be returned. + /// + /// Some general rules: + /// + /// 1. If a type is [Copy], it is conventional to provide it by value. + /// 2. If a type is not [Copy] but also not computed at provide time, for example + /// backtrace types that are captured when the error is created, it is conventional + /// to provide it by reference. + /// + /// Provided types that are not [Copy] and computed at provide time are fairly rare in + /// practice. However, when using them, you should be using + /// [`Request::would_be_satisfied_by_value_of`] to avoid computing them when they + /// are not requested. + /// + /// # Common uses of `provide` + /// + /// 1. [`Location`](core::panic::Location), provided by value, to indicate a source-code + /// location relevant to the error. This allows following the [`Error::source`] + /// chain to generate a "logical" backtrace, even in the absence of debug information. + /// 2. A backtrace, provided by reference, that contains the backtrace of the error. + /// 3. Various exit code types, normally provided by value. For example, an HTTP framework + /// might request an HTTP status on an error, to allow error types to override the HTTP + /// status returned on an error (consult your framework for specific behavior). + /// /// # Example /// /// ```rust /// #![feature(error_generic_member_access)] /// use core::fmt; - /// use core::error::{request_ref, Request}; + /// use core::error::{request_ref, request_value, Request}; + /// use core::panic::Location; /// /// #[derive(Debug)] /// enum MyLittleTeaPot { @@ -180,6 +225,7 @@ pub trait Error: Debug + Display { /// #[derive(Debug)] /// struct Error { /// backtrace: MyBacktrace, + /// location: Location<'static>, /// } /// /// impl fmt::Display for Error { @@ -191,17 +237,21 @@ pub trait Error: Debug + Display { /// impl std::error::Error for Error { /// fn provide<'a>(&'a self, request: &mut Request<'a>) { /// request - /// .provide_ref::(&self.backtrace); + /// .provide_ref::(&self.backtrace) + /// .provide_value::>(self.location); /// } /// } /// /// fn main() { /// let backtrace = MyBacktrace::new(); - /// let error = Error { backtrace }; + /// let location = Location::caller(); + /// let error = Error { backtrace, location: *location }; /// let dyn_error = &error as &dyn std::error::Error; /// let backtrace_ref = request_ref::(dyn_error).unwrap(); + /// let location = request_value::>(dyn_error).unwrap(); /// /// assert!(core::ptr::eq(&error.backtrace, backtrace_ref)); + /// assert_eq!(error.location, location); /// assert!(request_ref::(dyn_error).is_none()); /// } /// ``` @@ -381,643 +431,6 @@ impl dyn Error { } } -/// Requests a value of type `T` from the given `impl Error`. -/// -/// # Examples -/// -/// Get a string value from an error. -/// -/// ```rust -/// #![feature(error_generic_member_access)] -/// use std::error::Error; -/// use core::error::request_value; -/// -/// fn get_string(err: &impl Error) -> String { -/// request_value::(err).unwrap() -/// } -/// ``` -#[unstable(feature = "error_generic_member_access", issue = "99301")] -pub fn request_value<'a, T>(err: &'a (impl Error + ?Sized)) -> Option -where - T: 'static, -{ - request_by_type_tag::<'a, tags::Value>(err) -} - -/// Requests a reference of type `T` from the given `impl Error`. -/// -/// # Examples -/// -/// Get a string reference from an error. -/// -/// ```rust -/// #![feature(error_generic_member_access)] -/// use core::error::Error; -/// use core::error::request_ref; -/// -/// fn get_str(err: &impl Error) -> &str { -/// request_ref::(err).unwrap() -/// } -/// ``` -#[unstable(feature = "error_generic_member_access", issue = "99301")] -pub fn request_ref<'a, T>(err: &'a (impl Error + ?Sized)) -> Option<&'a T> -where - T: 'static + ?Sized, -{ - request_by_type_tag::<'a, tags::Ref>>(err) -} - -/// Request a specific value by tag from the `Error`. -fn request_by_type_tag<'a, I>(err: &'a (impl Error + ?Sized)) -> Option -where - I: tags::Type<'a>, -{ - let mut tagged = Tagged { tag_id: TypeId::of::(), value: TaggedOption::<'a, I>(None) }; - err.provide(tagged.as_request()); - tagged.value.0 -} - -/////////////////////////////////////////////////////////////////////////////// -// Request and its methods -/////////////////////////////////////////////////////////////////////////////// - -/// `Request` supports generic, type-driven access to data. Its use is currently restricted to the -/// standard library in cases where trait authors wish to allow trait implementors to share generic -/// information across trait boundaries. The motivating and prototypical use case is -/// `core::error::Error` which would otherwise require a method per concrete type (eg. -/// `std::backtrace::Backtrace` instance that implementors want to expose to users). -/// -/// # Data flow -/// -/// To describe the intended data flow for Request objects, let's consider two conceptual users -/// separated by API boundaries: -/// -/// * Consumer - the consumer requests objects using a Request instance; eg a crate that offers -/// fancy `Error`/`Result` reporting to users wants to request a Backtrace from a given `dyn Error`. -/// -/// * Producer - the producer provides objects when requested via Request; eg. a library with an -/// an `Error` implementation that automatically captures backtraces at the time instances are -/// created. -/// -/// The consumer only needs to know where to submit their request and are expected to handle the -/// request not being fulfilled by the use of `Option` in the responses offered by the producer. -/// -/// * A Producer initializes the value of one of its fields of a specific type. (or is otherwise -/// prepared to generate a value requested). eg, `backtrace::Backtrace` or -/// `std::backtrace::Backtrace` -/// * A Consumer requests an object of a specific type (say `std::backtrace::Backtrace`). In the -/// case of a `dyn Error` trait object (the Producer), there are functions called `request_ref` and -/// `request_value` to simplify obtaining an `Option` for a given type. -/// * The Producer, when requested, populates the given Request object which is given as a mutable -/// reference. -/// * The Consumer extracts a value or reference to the requested type from the `Request` object -/// wrapped in an `Option`; in the case of `dyn Error` the aforementioned `request_ref` and ` -/// request_value` methods mean that `dyn Error` users don't have to deal with the `Request` type at -/// all (but `Error` implementors do). The `None` case of the `Option` suggests only that the -/// Producer cannot currently offer an instance of the requested type, not it can't or never will. -/// -/// # Examples -/// -/// The best way to demonstrate this is using an example implementation of `Error`'s `provide` trait -/// method: -/// -/// ``` -/// #![feature(error_generic_member_access)] -/// use core::fmt; -/// use core::error::Request; -/// use core::error::request_ref; -/// -/// #[derive(Debug)] -/// enum MyLittleTeaPot { -/// Empty, -/// } -/// -/// #[derive(Debug)] -/// struct MyBacktrace { -/// // ... -/// } -/// -/// impl MyBacktrace { -/// fn new() -> MyBacktrace { -/// // ... -/// # MyBacktrace {} -/// } -/// } -/// -/// #[derive(Debug)] -/// struct Error { -/// backtrace: MyBacktrace, -/// } -/// -/// impl fmt::Display for Error { -/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { -/// write!(f, "Example Error") -/// } -/// } -/// -/// impl std::error::Error for Error { -/// fn provide<'a>(&'a self, request: &mut Request<'a>) { -/// request -/// .provide_ref::(&self.backtrace); -/// } -/// } -/// -/// fn main() { -/// let backtrace = MyBacktrace::new(); -/// let error = Error { backtrace }; -/// let dyn_error = &error as &dyn std::error::Error; -/// let backtrace_ref = request_ref::(dyn_error).unwrap(); -/// -/// assert!(core::ptr::eq(&error.backtrace, backtrace_ref)); -/// assert!(request_ref::(dyn_error).is_none()); -/// } -/// ``` -/// -#[unstable(feature = "error_generic_member_access", issue = "99301")] -#[repr(transparent)] -pub struct Request<'a>(Tagged + 'a>); - -impl<'a> Request<'a> { - /// Provides a value or other type with only static lifetimes. - /// - /// # Examples - /// - /// Provides an `u8`. - /// - /// ```rust - /// #![feature(error_generic_member_access)] - /// - /// use core::error::Request; - /// - /// #[derive(Debug)] - /// struct SomeConcreteType { field: u8 } - /// - /// impl std::fmt::Display for SomeConcreteType { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "{} failed", self.field) - /// } - /// } - /// - /// impl std::error::Error for SomeConcreteType { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request.provide_value::(self.field); - /// } - /// } - /// ``` - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn provide_value(&mut self, value: T) -> &mut Self - where - T: 'static, - { - self.provide::>(value) - } - - /// Provides a value or other type with only static lifetimes computed using a closure. - /// - /// # Examples - /// - /// Provides a `String` by cloning. - /// - /// ```rust - /// #![feature(error_generic_member_access)] - /// - /// use core::error::Request; - /// - /// #[derive(Debug)] - /// struct SomeConcreteType { field: String } - /// - /// impl std::fmt::Display for SomeConcreteType { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "{} failed", self.field) - /// } - /// } - /// - /// impl std::error::Error for SomeConcreteType { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request.provide_value_with::(|| self.field.clone()); - /// } - /// } - /// ``` - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn provide_value_with(&mut self, fulfil: impl FnOnce() -> T) -> &mut Self - where - T: 'static, - { - self.provide_with::>(fulfil) - } - - /// Provides a reference. The referee type must be bounded by `'static`, - /// but may be unsized. - /// - /// # Examples - /// - /// Provides a reference to a field as a `&str`. - /// - /// ```rust - /// #![feature(error_generic_member_access)] - /// - /// use core::error::Request; - /// - /// #[derive(Debug)] - /// struct SomeConcreteType { field: String } - /// - /// impl std::fmt::Display for SomeConcreteType { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "{} failed", self.field) - /// } - /// } - /// - /// impl std::error::Error for SomeConcreteType { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request.provide_ref::(&self.field); - /// } - /// } - /// ``` - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn provide_ref(&mut self, value: &'a T) -> &mut Self { - self.provide::>>(value) - } - - /// Provides a reference computed using a closure. The referee type - /// must be bounded by `'static`, but may be unsized. - /// - /// # Examples - /// - /// Provides a reference to a field as a `&str`. - /// - /// ```rust - /// #![feature(error_generic_member_access)] - /// - /// use core::error::Request; - /// - /// #[derive(Debug)] - /// struct SomeConcreteType { business: String, party: String } - /// fn today_is_a_weekday() -> bool { true } - /// - /// impl std::fmt::Display for SomeConcreteType { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "{} failed", self.business) - /// } - /// } - /// - /// impl std::error::Error for SomeConcreteType { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// request.provide_ref_with::(|| { - /// if today_is_a_weekday() { - /// &self.business - /// } else { - /// &self.party - /// } - /// }); - /// } - /// } - /// ``` - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn provide_ref_with( - &mut self, - fulfil: impl FnOnce() -> &'a T, - ) -> &mut Self { - self.provide_with::>>(fulfil) - } - - /// Provides a value with the given `Type` tag. - fn provide(&mut self, value: I::Reified) -> &mut Self - where - I: tags::Type<'a>, - { - if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { - res.0 = Some(value); - } - self - } - - /// Provides a value with the given `Type` tag, using a closure to prevent unnecessary work. - fn provide_with(&mut self, fulfil: impl FnOnce() -> I::Reified) -> &mut Self - where - I: tags::Type<'a>, - { - if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { - res.0 = Some(fulfil()); - } - self - } - - /// Checks if the `Request` would be satisfied if provided with a - /// value of the specified type. If the type does not match or has - /// already been provided, returns false. - /// - /// # Examples - /// - /// Checks if a `u8` still needs to be provided and then provides - /// it. - /// - /// ```rust - /// #![feature(error_generic_member_access)] - /// - /// use core::error::Request; - /// use core::error::request_value; - /// - /// #[derive(Debug)] - /// struct Parent(Option); - /// - /// impl std::fmt::Display for Parent { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "a parent failed") - /// } - /// } - /// - /// impl std::error::Error for Parent { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// if let Some(v) = self.0 { - /// request.provide_value::(v); - /// } - /// } - /// } - /// - /// #[derive(Debug)] - /// struct Child { - /// parent: Parent, - /// } - /// - /// impl Child { - /// // Pretend that this takes a lot of resources to evaluate. - /// fn an_expensive_computation(&self) -> Option { - /// Some(99) - /// } - /// } - /// - /// impl std::fmt::Display for Child { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "child failed: \n because of parent: {}", self.parent) - /// } - /// } - /// - /// impl std::error::Error for Child { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// // In general, we don't know if this call will provide - /// // an `u8` value or not... - /// self.parent.provide(request); - /// - /// // ...so we check to see if the `u8` is needed before - /// // we run our expensive computation. - /// if request.would_be_satisfied_by_value_of::() { - /// if let Some(v) = self.an_expensive_computation() { - /// request.provide_value::(v); - /// } - /// } - /// - /// // The request will be satisfied now, regardless of if - /// // the parent provided the value or we did. - /// assert!(!request.would_be_satisfied_by_value_of::()); - /// } - /// } - /// - /// let parent = Parent(Some(42)); - /// let child = Child { parent }; - /// assert_eq!(Some(42), request_value::(&child)); - /// - /// let parent = Parent(None); - /// let child = Child { parent }; - /// assert_eq!(Some(99), request_value::(&child)); - /// - /// ``` - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn would_be_satisfied_by_value_of(&self) -> bool - where - T: 'static, - { - self.would_be_satisfied_by::>() - } - - /// Checks if the `Request` would be satisfied if provided with a - /// reference to a value of the specified type. - /// - /// If the type does not match or has already been provided, returns false. - /// - /// # Examples - /// - /// Checks if a `&str` still needs to be provided and then provides - /// it. - /// - /// ```rust - /// #![feature(error_generic_member_access)] - /// - /// use core::error::Request; - /// use core::error::request_ref; - /// - /// #[derive(Debug)] - /// struct Parent(Option); - /// - /// impl std::fmt::Display for Parent { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "a parent failed") - /// } - /// } - /// - /// impl std::error::Error for Parent { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// if let Some(v) = &self.0 { - /// request.provide_ref::(v); - /// } - /// } - /// } - /// - /// #[derive(Debug)] - /// struct Child { - /// parent: Parent, - /// name: String, - /// } - /// - /// impl Child { - /// // Pretend that this takes a lot of resources to evaluate. - /// fn an_expensive_computation(&self) -> Option<&str> { - /// Some(&self.name) - /// } - /// } - /// - /// impl std::fmt::Display for Child { - /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - /// write!(f, "{} failed: \n {}", self.name, self.parent) - /// } - /// } - /// - /// impl std::error::Error for Child { - /// fn provide<'a>(&'a self, request: &mut Request<'a>) { - /// // In general, we don't know if this call will provide - /// // a `str` reference or not... - /// self.parent.provide(request); - /// - /// // ...so we check to see if the `&str` is needed before - /// // we run our expensive computation. - /// if request.would_be_satisfied_by_ref_of::() { - /// if let Some(v) = self.an_expensive_computation() { - /// request.provide_ref::(v); - /// } - /// } - /// - /// // The request will be satisfied now, regardless of if - /// // the parent provided the reference or we did. - /// assert!(!request.would_be_satisfied_by_ref_of::()); - /// } - /// } - /// - /// let parent = Parent(Some("parent".into())); - /// let child = Child { parent, name: "child".into() }; - /// assert_eq!(Some("parent"), request_ref::(&child)); - /// - /// let parent = Parent(None); - /// let child = Child { parent, name: "child".into() }; - /// assert_eq!(Some("child"), request_ref::(&child)); - /// ``` - #[unstable(feature = "error_generic_member_access", issue = "99301")] - pub fn would_be_satisfied_by_ref_of(&self) -> bool - where - T: ?Sized + 'static, - { - self.would_be_satisfied_by::>>() - } - - fn would_be_satisfied_by(&self) -> bool - where - I: tags::Type<'a>, - { - matches!(self.0.downcast::(), Some(TaggedOption(None))) - } -} - -#[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a> Debug for Request<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.debug_struct("Request").finish_non_exhaustive() - } -} - -/////////////////////////////////////////////////////////////////////////////// -// Type tags -/////////////////////////////////////////////////////////////////////////////// - -pub(crate) mod tags { - //! Type tags are used to identify a type using a separate value. This module includes type tags - //! for some very common types. - //! - //! Currently type tags are not exposed to the user. But in the future, if you want to use the - //! Request API with more complex types (typically those including lifetime parameters), you - //! will need to write your own tags. - - use crate::marker::PhantomData; - - /// This trait is implemented by specific tag types in order to allow - /// describing a type which can be requested for a given lifetime `'a`. - /// - /// A few example implementations for type-driven tags can be found in this - /// module, although crates may also implement their own tags for more - /// complex types with internal lifetimes. - pub(crate) trait Type<'a>: Sized + 'static { - /// The type of values which may be tagged by this tag for the given - /// lifetime. - type Reified: 'a; - } - - /// Similar to the [`Type`] trait, but represents a type which may be unsized (i.e., has a - /// `?Sized` bound). E.g., `str`. - pub(crate) trait MaybeSizedType<'a>: Sized + 'static { - type Reified: 'a + ?Sized; - } - - impl<'a, T: Type<'a>> MaybeSizedType<'a> for T { - type Reified = T::Reified; - } - - /// Type-based tag for types bounded by `'static`, i.e., with no borrowed elements. - #[derive(Debug)] - pub(crate) struct Value(PhantomData); - - impl<'a, T: 'static> Type<'a> for Value { - type Reified = T; - } - - /// Type-based tag similar to [`Value`] but which may be unsized (i.e., has a `?Sized` bound). - #[derive(Debug)] - pub(crate) struct MaybeSizedValue(PhantomData); - - impl<'a, T: ?Sized + 'static> MaybeSizedType<'a> for MaybeSizedValue { - type Reified = T; - } - - /// Type-based tag for reference types (`&'a T`, where T is represented by - /// `>::Reified`. - #[derive(Debug)] - pub(crate) struct Ref(PhantomData); - - impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref { - type Reified = &'a I::Reified; - } -} - -/// An `Option` with a type tag `I`. -/// -/// Since this struct implements `Erased`, the type can be erased to make a dynamically typed -/// option. The type can be checked dynamically using `Tagged::tag_id` and since this is statically -/// checked for the concrete type, there is some degree of type safety. -#[repr(transparent)] -pub(crate) struct TaggedOption<'a, I: tags::Type<'a>>(pub Option); - -impl<'a, I: tags::Type<'a>> Tagged> { - pub(crate) fn as_request(&mut self) -> &mut Request<'a> { - let erased = self as &mut Tagged + 'a>; - // SAFETY: transmuting `&mut Tagged + 'a>` to `&mut Request<'a>` is safe since - // `Request` is repr(transparent). - unsafe { &mut *(erased as *mut Tagged> as *mut Request<'a>) } - } -} - -/// Represents a type-erased but identifiable object. -/// -/// This trait is exclusively implemented by the `TaggedOption` type. -unsafe trait Erased<'a>: 'a {} - -unsafe impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> {} - -struct Tagged { - tag_id: TypeId, - value: E, -} - -impl<'a> Tagged + 'a> { - /// Returns some reference to the dynamic value if it is tagged with `I`, - /// or `None` otherwise. - #[inline] - fn downcast(&self) -> Option<&TaggedOption<'a, I>> - where - I: tags::Type<'a>, - { - if self.tag_id == TypeId::of::() { - // SAFETY: Just checked whether we're pointing to an I. - Some(&unsafe { &*(self as *const Self).cast::>>() }.value) - } else { - None - } - } - - /// Returns some mutable reference to the dynamic value if it is tagged with `I`, - /// or `None` otherwise. - #[inline] - fn downcast_mut(&mut self) -> Option<&mut TaggedOption<'a, I>> - where - I: tags::Type<'a>, - { - if self.tag_id == TypeId::of::() { - Some( - // SAFETY: Just checked whether we're pointing to an I. - &mut unsafe { &mut *(self as *mut Self).cast::>>() } - .value, - ) - } else { - None - } - } -} - /// An iterator over an [`Error`] and its sources. /// /// If you want to omit the initial error and only process diff --git a/library/core/src/error/provide.rs b/library/core/src/error/provide.rs new file mode 100644 index 0000000000000..1aa03d3ed4206 --- /dev/null +++ b/library/core/src/error/provide.rs @@ -0,0 +1,642 @@ +//! Implementation of [Request] + +use crate::any::TypeId; +use crate::error::Error; +use crate::fmt::{self, Debug, Formatter}; + +/// Requests a value of type `T` from the given `impl Error`. +/// +/// # Examples +/// +/// Get a string value from an error. +/// +/// ```rust +/// #![feature(error_generic_member_access)] +/// use std::error::Error; +/// use core::error::request_value; +/// +/// fn get_string(err: &impl Error) -> String { +/// request_value::(err).unwrap() +/// } +/// ``` +#[unstable(feature = "error_generic_member_access", issue = "99301")] +pub fn request_value<'a, T>(err: &'a (impl Error + ?Sized)) -> Option +where + T: 'static, +{ + request_by_type_tag::<'a, tags::Value>(err) +} + +/// Requests a reference of type `T` from the given `impl Error`. +/// +/// # Examples +/// +/// Get a string reference from an error. +/// +/// ```rust +/// #![feature(error_generic_member_access)] +/// use core::error::Error; +/// use core::error::request_ref; +/// +/// fn get_str(err: &impl Error) -> &str { +/// request_ref::(err).unwrap() +/// } +/// ``` +#[unstable(feature = "error_generic_member_access", issue = "99301")] +pub fn request_ref<'a, T>(err: &'a (impl Error + ?Sized)) -> Option<&'a T> +where + T: 'static + ?Sized, +{ + request_by_type_tag::<'a, tags::Ref>>(err) +} + +/// Request a specific value by tag from the `Error`. +fn request_by_type_tag<'a, I>(err: &'a (impl Error + ?Sized)) -> Option +where + I: tags::Type<'a>, +{ + let mut tagged = Tagged { tag_id: TypeId::of::(), value: TaggedOption::<'a, I>(None) }; + err.provide(tagged.as_request()); + tagged.value.0 +} + +/////////////////////////////////////////////////////////////////////////////// +// Request and its methods +/////////////////////////////////////////////////////////////////////////////// + +/// `Request` supports generic, type-driven access to data. Its use is currently restricted to the +/// standard library in cases where trait authors wish to allow trait implementors to share generic +/// information across trait boundaries. The motivating and prototypical use case is +/// `core::error::Error` which would otherwise require a method per concrete type (eg. +/// `std::backtrace::Backtrace` instance that implementors want to expose to users). +/// +/// # Data flow +/// +/// To describe the intended data flow for Request objects, let's consider two conceptual users +/// separated by API boundaries: +/// +/// * Consumer - the consumer requests objects using a Request instance; eg a crate that offers +/// fancy `Error`/`Result` reporting to users wants to request a Backtrace from a given `dyn Error`. +/// +/// * Producer - the producer provides objects when requested via Request; eg. a library with an +/// an `Error` implementation that automatically captures backtraces at the time instances are +/// created. +/// +/// The consumer only needs to know where to submit their request and are expected to handle the +/// request not being fulfilled by the use of `Option` in the responses offered by the producer. +/// +/// * A Producer initializes the value of one of its fields of a specific type. (or is otherwise +/// prepared to generate a value requested). eg, `backtrace::Backtrace` or +/// `std::backtrace::Backtrace` +/// * A Consumer requests an object of a specific type (say `std::backtrace::Backtrace`). In the +/// case of a `dyn Error` trait object (the Producer), there are functions called `request_ref` and +/// `request_value` to simplify obtaining an `Option` for a given type. +/// * The Producer, when requested, populates the given Request object which is given as a mutable +/// reference. +/// * The Consumer extracts a value or reference to the requested type from the `Request` object +/// wrapped in an `Option`; in the case of `dyn Error` the aforementioned `request_ref` and ` +/// request_value` methods mean that `dyn Error` users don't have to deal with the `Request` type at +/// all (but `Error` implementors do). The `None` case of the `Option` suggests only that the +/// Producer cannot currently offer an instance of the requested type, not it can't or never will. +/// +/// # Examples +/// +/// The best way to demonstrate this is using an example implementation of `Error`'s `provide` trait +/// method: +/// +/// ``` +/// #![feature(error_generic_member_access)] +/// use core::fmt; +/// use core::error::Request; +/// use core::error::request_ref; +/// +/// #[derive(Debug)] +/// enum MyLittleTeaPot { +/// Empty, +/// } +/// +/// #[derive(Debug)] +/// struct MyBacktrace { +/// // ... +/// } +/// +/// impl MyBacktrace { +/// fn new() -> MyBacktrace { +/// // ... +/// # MyBacktrace {} +/// } +/// } +/// +/// #[derive(Debug)] +/// struct Error { +/// backtrace: MyBacktrace, +/// } +/// +/// impl fmt::Display for Error { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "Example Error") +/// } +/// } +/// +/// impl std::error::Error for Error { +/// fn provide<'a>(&'a self, request: &mut Request<'a>) { +/// request +/// .provide_ref::(&self.backtrace); +/// } +/// } +/// +/// fn main() { +/// let backtrace = MyBacktrace::new(); +/// let error = Error { backtrace }; +/// let dyn_error = &error as &dyn std::error::Error; +/// let backtrace_ref = request_ref::(dyn_error).unwrap(); +/// +/// assert!(core::ptr::eq(&error.backtrace, backtrace_ref)); +/// assert!(request_ref::(dyn_error).is_none()); +/// } +/// ``` +/// +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[repr(transparent)] +pub struct Request<'a>(Tagged + 'a>); + +impl<'a> Request<'a> { + /// Provides a value or other type with only static lifetimes. + /// + /// # Examples + /// + /// Provides an `u8`. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { field: u8 } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.field) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_value::(self.field); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_value(&mut self, value: T) -> &mut Self + where + T: 'static, + { + self.provide::>(value) + } + + /// Provides a value or other type with only static lifetimes computed using a closure. + /// + /// # Examples + /// + /// Provides a `String` by cloning. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { field: String } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.field) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_value_with::(|| self.field.clone()); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_value_with(&mut self, fulfil: impl FnOnce() -> T) -> &mut Self + where + T: 'static, + { + self.provide_with::>(fulfil) + } + + /// Provides a reference. The referee type must be bounded by `'static`, + /// but may be unsized. + /// + /// # Examples + /// + /// Provides a reference to a field as a `&str`. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { field: String } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.field) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_ref::(&self.field); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_ref(&mut self, value: &'a T) -> &mut Self { + self.provide::>>(value) + } + + /// Provides a reference computed using a closure. The referee type + /// must be bounded by `'static`, but may be unsized. + /// + /// # Examples + /// + /// Provides a reference to a field as a `&str`. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// + /// use core::error::Request; + /// + /// #[derive(Debug)] + /// struct SomeConcreteType { business: String, party: String } + /// fn today_is_a_weekday() -> bool { true } + /// + /// impl std::fmt::Display for SomeConcreteType { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed", self.business) + /// } + /// } + /// + /// impl std::error::Error for SomeConcreteType { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// request.provide_ref_with::(|| { + /// if today_is_a_weekday() { + /// &self.business + /// } else { + /// &self.party + /// } + /// }); + /// } + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn provide_ref_with( + &mut self, + fulfil: impl FnOnce() -> &'a T, + ) -> &mut Self { + self.provide_with::>>(fulfil) + } + + /// Provides a value with the given `Type` tag. + fn provide(&mut self, value: I::Reified) -> &mut Self + where + I: tags::Type<'a>, + { + if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { + res.0 = Some(value); + } + self + } + + /// Provides a value with the given `Type` tag, using a closure to prevent unnecessary work. + fn provide_with(&mut self, fulfil: impl FnOnce() -> I::Reified) -> &mut Self + where + I: tags::Type<'a>, + { + if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { + res.0 = Some(fulfil()); + } + self + } + + /// Checks if the `Request` would be satisfied if provided with a + /// value of the specified type. If the type does not match or has + /// already been provided, returns false. + /// + /// # Examples + /// + /// Checks if a `u8` still needs to be provided and then provides + /// it. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// + /// use core::error::Request; + /// use core::error::request_value; + /// + /// #[derive(Debug)] + /// struct Parent(Option); + /// + /// impl std::fmt::Display for Parent { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "a parent failed") + /// } + /// } + /// + /// impl std::error::Error for Parent { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// if let Some(v) = self.0 { + /// request.provide_value::(v); + /// } + /// } + /// } + /// + /// #[derive(Debug)] + /// struct Child { + /// parent: Parent, + /// } + /// + /// impl Child { + /// // Pretend that this takes a lot of resources to evaluate. + /// fn an_expensive_computation(&self) -> Option { + /// Some(99) + /// } + /// } + /// + /// impl std::fmt::Display for Child { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "child failed: \n because of parent: {}", self.parent) + /// } + /// } + /// + /// impl std::error::Error for Child { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// // In general, we don't know if this call will provide + /// // an `u8` value or not... + /// self.parent.provide(request); + /// + /// // ...so we check to see if the `u8` is needed before + /// // we run our expensive computation. + /// if request.would_be_satisfied_by_value_of::() { + /// if let Some(v) = self.an_expensive_computation() { + /// request.provide_value::(v); + /// } + /// } + /// + /// // The request will be satisfied now, regardless of if + /// // the parent provided the value or we did. + /// assert!(!request.would_be_satisfied_by_value_of::()); + /// } + /// } + /// + /// let parent = Parent(Some(42)); + /// let child = Child { parent }; + /// assert_eq!(Some(42), request_value::(&child)); + /// + /// let parent = Parent(None); + /// let child = Child { parent }; + /// assert_eq!(Some(99), request_value::(&child)); + /// + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn would_be_satisfied_by_value_of(&self) -> bool + where + T: 'static, + { + self.would_be_satisfied_by::>() + } + + /// Checks if the `Request` would be satisfied if provided with a + /// reference to a value of the specified type. + /// + /// If the type does not match or has already been provided, returns false. + /// + /// # Examples + /// + /// Checks if a `&str` still needs to be provided and then provides + /// it. + /// + /// ```rust + /// #![feature(error_generic_member_access)] + /// + /// use core::error::Request; + /// use core::error::request_ref; + /// + /// #[derive(Debug)] + /// struct Parent(Option); + /// + /// impl std::fmt::Display for Parent { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "a parent failed") + /// } + /// } + /// + /// impl std::error::Error for Parent { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// if let Some(v) = &self.0 { + /// request.provide_ref::(v); + /// } + /// } + /// } + /// + /// #[derive(Debug)] + /// struct Child { + /// parent: Parent, + /// name: String, + /// } + /// + /// impl Child { + /// // Pretend that this takes a lot of resources to evaluate. + /// fn an_expensive_computation(&self) -> Option<&str> { + /// Some(&self.name) + /// } + /// } + /// + /// impl std::fmt::Display for Child { + /// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + /// write!(f, "{} failed: \n {}", self.name, self.parent) + /// } + /// } + /// + /// impl std::error::Error for Child { + /// fn provide<'a>(&'a self, request: &mut Request<'a>) { + /// // In general, we don't know if this call will provide + /// // a `str` reference or not... + /// self.parent.provide(request); + /// + /// // ...so we check to see if the `&str` is needed before + /// // we run our expensive computation. + /// if request.would_be_satisfied_by_ref_of::() { + /// if let Some(v) = self.an_expensive_computation() { + /// request.provide_ref::(v); + /// } + /// } + /// + /// // The request will be satisfied now, regardless of if + /// // the parent provided the reference or we did. + /// assert!(!request.would_be_satisfied_by_ref_of::()); + /// } + /// } + /// + /// let parent = Parent(Some("parent".into())); + /// let child = Child { parent, name: "child".into() }; + /// assert_eq!(Some("parent"), request_ref::(&child)); + /// + /// let parent = Parent(None); + /// let child = Child { parent, name: "child".into() }; + /// assert_eq!(Some("child"), request_ref::(&child)); + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn would_be_satisfied_by_ref_of(&self) -> bool + where + T: ?Sized + 'static, + { + self.would_be_satisfied_by::>>() + } + + fn would_be_satisfied_by(&self) -> bool + where + I: tags::Type<'a>, + { + matches!(self.0.downcast::(), Some(TaggedOption(None))) + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a> Debug for Request<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + f.debug_struct("Request").finish_non_exhaustive() + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Type tags +/////////////////////////////////////////////////////////////////////////////// + +pub(crate) mod tags { + //! Type tags are used to identify a type using a separate value. This module includes type tags + //! for some very common types. + //! + //! Currently type tags are not exposed to the user. But in the future, if you want to use the + //! Request API with more complex types (typically those including lifetime parameters), you + //! will need to write your own tags. + + use crate::marker::PhantomData; + + /// This trait is implemented by specific tag types in order to allow + /// describing a type which can be requested for a given lifetime `'a`. + /// + /// A few example implementations for type-driven tags can be found in this + /// module, although crates may also implement their own tags for more + /// complex types with internal lifetimes. + pub(crate) trait Type<'a>: Sized + 'static { + /// The type of values which may be tagged by this tag for the given + /// lifetime. + type Reified: 'a; + } + + /// Similar to the [`Type`] trait, but represents a type which may be unsized (i.e., has a + /// `?Sized` bound). E.g., `str`. + pub(crate) trait MaybeSizedType<'a>: Sized + 'static { + type Reified: 'a + ?Sized; + } + + impl<'a, T: Type<'a>> MaybeSizedType<'a> for T { + type Reified = T::Reified; + } + + /// Type-based tag for types bounded by `'static`, i.e., with no borrowed elements. + #[derive(Debug)] + pub(crate) struct Value(PhantomData); + + impl<'a, T: 'static> Type<'a> for Value { + type Reified = T; + } + + /// Type-based tag similar to [`Value`] but which may be unsized (i.e., has a `?Sized` bound). + #[derive(Debug)] + pub(crate) struct MaybeSizedValue(PhantomData); + + impl<'a, T: ?Sized + 'static> MaybeSizedType<'a> for MaybeSizedValue { + type Reified = T; + } + + /// Type-based tag for reference types (`&'a T`, where T is represented by + /// `>::Reified`. + #[derive(Debug)] + pub(crate) struct Ref(PhantomData); + + impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref { + type Reified = &'a I::Reified; + } +} + +/// An `Option` with a type tag `I`. +/// +/// Since this struct implements `Erased`, the type can be erased to make a dynamically typed +/// option. The type can be checked dynamically using `Tagged::tag_id` and since this is statically +/// checked for the concrete type, there is some degree of type safety. +#[repr(transparent)] +pub(crate) struct TaggedOption<'a, I: tags::Type<'a>>(pub Option); + +impl<'a, I: tags::Type<'a>> Tagged> { + pub(crate) fn as_request(&mut self) -> &mut Request<'a> { + let erased = self as &mut Tagged + 'a>; + // SAFETY: transmuting `&mut Tagged + 'a>` to `&mut Request<'a>` is safe since + // `Request` is repr(transparent). + unsafe { &mut *(erased as *mut Tagged> as *mut Request<'a>) } + } +} + +/// Represents a type-erased but identifiable object. +/// +/// This trait is exclusively implemented by the `TaggedOption` type. +unsafe trait Erased<'a>: 'a {} + +unsafe impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> {} + +struct Tagged { + tag_id: TypeId, + value: E, +} + +impl<'a> Tagged + 'a> { + /// Returns some reference to the dynamic value if it is tagged with `I`, + /// or `None` otherwise. + #[inline] + fn downcast(&self) -> Option<&TaggedOption<'a, I>> + where + I: tags::Type<'a>, + { + if self.tag_id == TypeId::of::() { + // SAFETY: Just checked whether we're pointing to an I. + Some(&unsafe { &*(self as *const Self).cast::>>() }.value) + } else { + None + } + } + + /// Returns some mutable reference to the dynamic value if it is tagged with `I`, + /// or `None` otherwise. + #[inline] + fn downcast_mut(&mut self) -> Option<&mut TaggedOption<'a, I>> + where + I: tags::Type<'a>, + { + if self.tag_id == TypeId::of::() { + Some( + // SAFETY: Just checked whether we're pointing to an I. + &mut unsafe { &mut *(self as *mut Self).cast::>>() } + .value, + ) + } else { + None + } + } +} From 60a9e56203a206f008c01cd58534dc032a697b1a Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 3 Dec 2025 22:53:40 +0000 Subject: [PATCH 3/4] implement error multi request --- library/core/src/error/mod.rs | 6 +- library/core/src/error/provide.rs | 696 +++++++++++++++++- library/core/src/lib.rs | 1 + library/coretests/tests/error.rs | 70 +- .../crates/ide-db/src/generated/lints.rs | 12 + tests/codegen-llvm/error-provide.rs | 57 +- 6 files changed, 818 insertions(+), 24 deletions(-) diff --git a/library/core/src/error/mod.rs b/library/core/src/error/mod.rs index b0bf5a4b3f0fb..aee21cbf1526a 100644 --- a/library/core/src/error/mod.rs +++ b/library/core/src/error/mod.rs @@ -7,7 +7,11 @@ use crate::fmt::{Debug, Display}; mod provide; #[unstable(feature = "error_generic_member_access", issue = "99301")] -pub use provide::{Request, request_ref, request_value}; +pub use provide::{ + ChainRefMultiRequestBuilder, ChainValMultiRequestBuilder, EmptyMultiRequestBuilder, + IntoMultiRequest, MultiRequestBuilder, MultiResponse, MultiResponseChainRef, + MultiResponseChainVal, Request, request_ref, request_value, +}; /// `Error` is a trait representing the basic expectations for error values, /// i.e., values of type `E` in [`Result`]. diff --git a/library/core/src/error/provide.rs b/library/core/src/error/provide.rs index 1aa03d3ed4206..e0a73f6a7bbf0 100644 --- a/library/core/src/error/provide.rs +++ b/library/core/src/error/provide.rs @@ -3,6 +3,8 @@ use crate::any::TypeId; use crate::error::Error; use crate::fmt::{self, Debug, Formatter}; +use crate::marker::PhantomData; +use crate::ptr::NonNull; /// Requests a value of type `T` from the given `impl Error`. /// @@ -55,7 +57,7 @@ fn request_by_type_tag<'a, I>(err: &'a (impl Error + ?Sized)) -> Option, { - let mut tagged = Tagged { tag_id: TypeId::of::(), value: TaggedOption::<'a, I>(None) }; + let mut tagged = >>::new_concrete(); err.provide(tagged.as_request()); tagged.value.0 } @@ -308,9 +310,7 @@ impl<'a> Request<'a> { where I: tags::Type<'a>, { - if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { - res.0 = Some(value); - } + self.0.provide::(value); self } @@ -319,9 +319,7 @@ impl<'a> Request<'a> { where I: tags::Type<'a>, { - if let Some(res @ TaggedOption(None)) = self.0.downcast_mut::() { - res.0 = Some(fulfil()); - } + self.0.provide_with::(fulfil); self } @@ -504,7 +502,7 @@ impl<'a> Request<'a> { where I: tags::Type<'a>, { - matches!(self.0.downcast::(), Some(TaggedOption(None))) + self.0.would_be_satisfied_by::() } } @@ -515,6 +513,533 @@ impl<'a> Debug for Request<'a> { } } +/// Base case for [IntoMultiRequest]. +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[derive(Copy, Clone, Debug)] +pub struct EmptyMultiRequestBuilder; + +/// Case of [IntoMultiRequest] that retrieves a type by value. +/// +/// Create via [MultiRequestBuilder::with_value]. +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[derive(Copy, Clone, Debug)] +pub struct ChainValMultiRequestBuilder(PhantomData<(T, NEXT)>); + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[derive(Copy, Clone, Debug)] +/// Case of [IntoMultiRequest] that retrieves a type by value. +/// +/// Create via [MultiRequestBuilder::with_ref]. +pub struct ChainRefMultiRequestBuilder(PhantomData<(*const T, NEXT)>); + +/// Internal trait for types that represent a request for multiple provided +/// traits in parallel. +/// +/// There is no need to use this trait directly, use [MultiRequestBuilder] instead. +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[allow(private_bounds)] +pub trait IntoMultiRequest<'a>: private::IntoMultiRequestInner<'a> + 'static {} + +mod private { + #[unstable(feature = "error_generic_member_access", issue = "99301")] + #[allow(private_bounds)] + pub trait IntoMultiRequestInner<'a> { + #[unstable(feature = "error_generic_member_access", issue = "99301")] + type Request: super::Erased<'a> + MultiResponseInner<'a>; + #[unstable(feature = "error_generic_member_access", issue = "99301")] + fn get_request() -> Self::Request; + } + + #[unstable(feature = "error_generic_member_access", issue = "99301")] + #[allow(private_bounds)] + pub trait MultiResponseInner<'a> { + fn consume_with(&mut self, fulfil: impl FnOnce(I::Reified)) -> &mut Self + where + I: super::tags::Type<'a>; + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a> IntoMultiRequest<'a> for EmptyMultiRequestBuilder {} +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a> private::IntoMultiRequestInner<'a> for EmptyMultiRequestBuilder { + type Request = EmptyMultiResponse; + + fn get_request() -> Self::Request { + EmptyMultiResponse + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, T, NEXT> IntoMultiRequest<'a> for ChainValMultiRequestBuilder +where + T: 'static, + NEXT: IntoMultiRequest<'a>, +{ +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, T, NEXT> private::IntoMultiRequestInner<'a> for ChainValMultiRequestBuilder +where + T: 'static, + NEXT: IntoMultiRequest<'a>, +{ + type Request = MultiResponseChainVal<'a, T, NEXT::Request>; + + fn get_request() -> Self::Request { + MultiResponseChainVal { + inner: MultiResponseChain { cur: None, next: NEXT::get_request(), marker: PhantomData }, + } + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, T, NEXT> IntoMultiRequest<'a> for ChainRefMultiRequestBuilder +where + T: ?Sized + 'static, + NEXT: IntoMultiRequest<'a>, +{ +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, T, NEXT> private::IntoMultiRequestInner<'a> for ChainRefMultiRequestBuilder +where + T: ?Sized + 'static, + NEXT: IntoMultiRequest<'a>, +{ + type Request = MultiResponseChainRef<'a, T, NEXT::Request>; + + fn get_request() -> Self::Request { + MultiResponseChainRef { + inner: MultiResponseChain { cur: None, next: NEXT::get_request(), marker: PhantomData }, + } + } +} + +/// A response from an empty [MultiRequestBuilder::request] +#[unstable( + feature = "error_generic_member_access_internals", + reason = "implementation detail which may disappear or be replaced at any time", + issue = "none" +)] +#[derive(Debug)] +pub struct EmptyMultiResponse; + +#[derive(Debug)] +struct MultiResponseChain<'a, I, NEXT> +where + I: tags::Type<'a>, +{ + cur: Option, + next: NEXT, + // Lifetime is invariant because it is used in an associated type + marker: PhantomData<*mut &'a ()>, +} + +/// A response from a [MultiRequestBuilder::request] after calling [MultiRequestBuilder::with_value]. +#[unstable( + feature = "error_generic_member_access_internals", + reason = "implementation detail which may disappear or be replaced at any time", + issue = "none" +)] +#[derive(Debug)] +pub struct MultiResponseChainVal<'a, T, NEXT> +where + T: 'static, +{ + inner: MultiResponseChain<'a, tags::Value, NEXT>, +} + +/// A response from a [MultiRequestBuilder::request] after calling [MultiRequestBuilder::with_ref]. +#[unstable( + feature = "error_generic_member_access_internals", + reason = "implementation detail which may disappear or be replaced at any time", + issue = "none" +)] +#[derive(Debug)] +pub struct MultiResponseChainRef<'a, T, NEXT> +where + T: 'static + ?Sized, +{ + inner: MultiResponseChain<'a, tags::Ref>, NEXT>, +} + +/// A response from a [MultiRequestBuilder]. The types returned from +/// [MultiRequestBuilder::request] implement this trait. +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[allow(private_bounds)] +pub trait MultiResponse<'a> { + /// Retrieve a reference with the type `R` from this multi response, + /// + /// The reference will be passed to `fulfil` if present. This function + /// consumes the reference, so the next call to `retrieve_ref` + /// with the same type will not call `fulfil`. + /// + /// This function returns `self` to allow easy chained use. + /// + /// # Examples + /// + /// When requesting only a single type, it is better to use + /// [request_ref] - this is only an example. + /// + /// ``` + /// #![feature(error_generic_member_access)] + /// use core::error::{Error, MultiRequestBuilder, MultiResponse}; + /// + /// fn get_str(e: &dyn Error) -> Option<&str> { + /// let mut result = None; + /// MultiRequestBuilder::new() + /// .with_ref::() + /// .request(e) + /// .retrieve_ref(|res| result = Some(res)); + /// result + /// } + /// ``` + fn retrieve_ref(&mut self, fulfil: impl FnOnce(&'a R)) -> &mut Self + where + R: ?Sized + 'static; + + /// Retrieve a value with the type `V` from this multi response, + /// + /// The value will be passed to `fulfil` if present. This function + /// consumes the value, so the next call to `retrieve_value` + /// with the same type will not call `fulfil`. + /// + /// This function returns `self` to allow easy chained use. + /// + /// # Examples + /// + /// When requesting only a single type, it is better to use + /// [request_value] - this is only an example. + /// + /// ``` + /// #![feature(error_generic_member_access)] + /// use core::error::{Error, MultiRequestBuilder, MultiResponse}; + /// + /// fn get_string(e: &dyn Error) -> Option { + /// let mut result = None; + /// MultiRequestBuilder::new() + /// .with_value::() + /// .request(e) + /// .retrieve_value(|res| result = Some(res)); + /// result + /// } + /// ``` + fn retrieve_value(&mut self, fulfil: impl FnOnce(V)) -> &mut Self + where + V: 'static; +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, T: private::MultiResponseInner<'a>> MultiResponse<'a> for T { + fn retrieve_ref(&mut self, fulfil: impl FnOnce(&'a R)) -> &mut Self + where + R: ?Sized + 'static, + { + self.consume_with::>>(fulfil) + } + + fn retrieve_value(&mut self, fulfil: impl FnOnce(V)) -> &mut Self + where + V: 'static, + { + self.consume_with::>(fulfil) + } +} +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a> private::MultiResponseInner<'a> for EmptyMultiResponse { + #[allow(private_bounds)] + fn consume_with(&mut self, _fulfil: impl FnOnce(I::Reified)) -> &mut Self + where + I: tags::Type<'a>, + { + self + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, J, NEXT> private::MultiResponseInner<'a> for MultiResponseChain<'a, J, NEXT> +where + J: tags::Type<'a>, + NEXT: private::MultiResponseInner<'a>, +{ + fn consume_with(&mut self, fulfil: impl FnOnce(I::Reified)) -> &mut Self + where + I: tags::Type<'a>, + { + // SAFETY: cast is safe because type ids are equal implies types are equal + unsafe { + // this `if` is const. Equality is always decidable for tag types, but we can't prove that to the type system. + if TypeId::of::() == TypeId::of::() { + // cast is safe because type ids are equal + let cur = + &mut *(&mut self.cur as *mut Option as *mut Option); + if let Some(val) = cur.take() { + fulfil(val); + return self; + } + } + } + self.next.consume_with::(fulfil); + self + } +} +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, T, NEXT> private::MultiResponseInner<'a> for MultiResponseChainVal<'a, T, NEXT> +where + T: 'static, + NEXT: private::MultiResponseInner<'a>, +{ + #[allow(private_bounds)] + fn consume_with(&mut self, fulfil: impl FnOnce(I::Reified)) -> &mut Self + where + I: tags::Type<'a>, + { + self.inner.consume_with::(fulfil); + self + } +} +#[unstable(feature = "error_generic_member_access", issue = "99301")] +// SAFETY: delegates to inner impl +unsafe impl<'a, T, NEXT> Erased<'a> for MultiResponseChainVal<'a, T, NEXT> +where + T: 'static, + NEXT: Erased<'a>, +{ + unsafe fn consume_closure( + this: impl FnOnce() -> *const Self, + type_id: TypeId, + ) -> Option> { + // SAFETY: delegation + unsafe { MultiResponseChain::consume_closure(move || &raw const (*this()).inner, type_id) } + } + + unsafe fn consume(self: *const Self, type_id: TypeId) -> Option> { + // SAFETY: same safety conditions + unsafe { Self::consume_closure(move || self, type_id) } + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +impl<'a, T, NEXT> private::MultiResponseInner<'a> for MultiResponseChainRef<'a, T, NEXT> +where + T: 'static + ?Sized, + NEXT: private::MultiResponseInner<'a>, +{ + #[allow(private_bounds)] + fn consume_with(&mut self, fulfil: impl FnOnce(I::Reified)) -> &mut Self + where + I: tags::Type<'a>, + { + self.inner.consume_with::(fulfil); + self + } +} +#[unstable(feature = "error_generic_member_access", issue = "99301")] +// SAFETY: delegates to inner impl +unsafe impl<'a, T, NEXT> Erased<'a> for MultiResponseChainRef<'a, T, NEXT> +where + T: 'static + ?Sized, + NEXT: Erased<'a>, +{ + unsafe fn consume_closure( + this: impl FnOnce() -> *const Self, + type_id: TypeId, + ) -> Option> { + // SAFETY: delegation + unsafe { MultiResponseChain::consume_closure(move || &raw const (*this()).inner, type_id) } + } + + unsafe fn consume(self: *const Self, type_id: TypeId) -> Option> { + // SAFETY: same safety conditions + unsafe { Self::consume_closure(move || self, type_id) } + } +} + +unsafe impl<'a> Erased<'a> for EmptyMultiResponse { + unsafe fn consume_closure( + _this: impl FnOnce() -> *const Self, + _type_id: TypeId, + ) -> Option> { + None + } + + unsafe fn consume(self: *const Self, type_id: TypeId) -> Option> { + // SAFETY: same safety conditions + unsafe { Self::consume_closure(move || self, type_id) } + } +} + +unsafe impl<'a, I, NEXT> Erased<'a> for MultiResponseChain<'a, I, NEXT> +where + I: tags::Type<'a>, + NEXT: Erased<'a>, +{ + unsafe fn consume_closure( + this: impl FnOnce() -> *const Self, + type_id: TypeId, + ) -> Option> { + // SAFETY: dereferencing *this guaranteed to be valid. + unsafe { + if type_id == TypeId::of::() { + // SAFETY: returning an Option as requested + Some( + NonNull::new_unchecked((&raw const (*this()).cur) as *mut Option) + .cast(), + ) + } else { + // SAFETY: safe to delegate consume_closure + NEXT::consume_closure(move || &raw const (*this()).next, type_id) + } + } + } + + unsafe fn consume(self: *const Self, type_id: TypeId) -> Option> { + // SAFETY: same safety conditions + unsafe { Self::consume_closure(move || self, type_id) } + } +} + +#[unstable(feature = "error_generic_member_access", issue = "99301")] +#[derive(Copy, Clone, Debug)] +/// A [MultiRequestBuilder] is used to request multiple types from an [Error] at once. +/// +/// Requesting a type from an [Error] is fairly fast - normally faster than formatting +/// an error - but if you need to request many different error types, it is better +/// to use this API to request them at once. +/// +/// # Examples +/// +/// ``` +/// #![feature(error_generic_member_access)] +/// use core::fmt; +/// use core::error::{Error, MultiResponse, Request}; +/// +/// #[derive(Debug)] +/// struct MyError { +/// str_field: &'static str, +/// val_field: MyExitCode, +/// } +/// +/// #[derive(Copy, Clone, Debug, PartialEq, Eq)] +/// struct MyExitCode(u32); +/// +/// impl fmt::Display for MyError { +/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { +/// write!(f, "Example Error") +/// } +/// } +/// +/// impl Error for MyError { +/// fn provide<'a>(&'a self, request: &mut Request<'a>) { +/// request +/// .provide_ref::(self.str_field) +/// .provide_value::(self.val_field); +/// } +/// } +/// +/// fn main() { +/// let e = MyError { +/// str_field: "hello", +/// val_field: MyExitCode(3), +/// }; +/// +/// let mut str_val = None; +/// let mut exit_code_val = None; +/// let mut string_val = None; +/// let mut value = core::error::MultiRequestBuilder::new() +/// // request by reference +/// .with_ref::() +/// // and by value +/// .with_value::() +/// // and some type that isn't in the error +/// .with_value::() +/// .request(&e) +/// // The error has str by reference +/// .retrieve_ref::(|val| str_val = Some(val)) +/// // The error has MyExitCode by value +/// .retrieve_value::(|val| exit_code_val = Some(val)) +/// // The error does not have a string field, consume will not be called +/// .retrieve_value::(|val| string_val = Some(val)); +/// +/// assert_eq!(exit_code_val, Some(MyExitCode(3))); +/// assert_eq!(str_val, Some("hello")); +/// assert_eq!(string_val, None); +/// } +/// ``` +pub struct MultiRequestBuilder IntoMultiRequest<'a>> { + inner: PhantomData, +} + +impl MultiRequestBuilder { + /// Create a new [MultiRequestBuilder] + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn new() -> Self { + MultiRequestBuilder { inner: PhantomData } + } +} + +impl IntoMultiRequest<'a>> MultiRequestBuilder { + /// Create a [MultiRequestBuilder] that requests a value. + /// + /// # Examples + /// + /// ``` + /// #![feature(error_generic_member_access)] + /// use core::error::{Error, MultiRequestBuilder, MultiResponse}; + /// + /// fn get_string(e: &dyn Error) -> Option { + /// let mut result = None; + /// MultiRequestBuilder::new() + /// .with_value::() + /// .request(e) + /// .retrieve_value(|res| result = Some(res)); + /// result + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn with_value( + self, + ) -> MultiRequestBuilder> { + MultiRequestBuilder { inner: PhantomData } + } + + /// Create a [MultiRequestBuilder] that requests a reference. + /// + /// # Examples + /// + /// ``` + /// #![feature(error_generic_member_access)] + /// use core::error::{Error, MultiRequestBuilder, MultiResponse}; + /// + /// fn get_string(e: &dyn Error) -> Option { + /// let mut result = None; + /// MultiRequestBuilder::new() + /// .with_value::() + /// .request(e) + /// .retrieve_value(|res| result = Some(res)); + /// result + /// } + /// ``` + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn with_ref( + self, + ) -> MultiRequestBuilder> { + MultiRequestBuilder { inner: PhantomData } + } + + /// Request provided values from a given error. + #[unstable(feature = "error_generic_member_access", issue = "99301")] + pub fn request<'a>(self, err: &'a (impl Error + ?Sized)) -> impl MultiResponse<'a> { + let mut tagged = Tagged::new_virtual(INNER::get_request()); + err.provide(tagged.as_request()); + tagged.value + } +} + +// special type id, used to mark a `Tagged` where calls should be done virtually +struct ErasedMarker; + /////////////////////////////////////////////////////////////////////////////// // Type tags /////////////////////////////////////////////////////////////////////////////// @@ -527,7 +1052,9 @@ pub(crate) mod tags { //! Request API with more complex types (typically those including lifetime parameters), you //! will need to write your own tags. + use crate::any::TypeId; use crate::marker::PhantomData; + use crate::ptr::NonNull; /// This trait is implemented by specific tag types in order to allow /// describing a type which can be requested for a given lifetime `'a`. @@ -535,10 +1062,18 @@ pub(crate) mod tags { /// A few example implementations for type-driven tags can be found in this /// module, although crates may also implement their own tags for more /// complex types with internal lifetimes. - pub(crate) trait Type<'a>: Sized + 'static { + pub(crate) unsafe trait Type<'a>: Sized + 'static { /// The type of values which may be tagged by this tag for the given /// lifetime. type Reified: 'a; + + // This requires `sink` to be a valid pointer, and if `type_id == TypeId::of::`` and + // the function returns Some, returns a pointer with the same lifetime and + // mutability as `sink` to `Option<>::Reified>`. + unsafe fn consume( + sink: *const super::TaggedOption<'a, Self>, + type_id: TypeId, + ) -> Option>; } /// Similar to the [`Type`] trait, but represents a type which may be unsized (i.e., has a @@ -555,8 +1090,22 @@ pub(crate) mod tags { #[derive(Debug)] pub(crate) struct Value(PhantomData); - impl<'a, T: 'static> Type<'a> for Value { + unsafe impl<'a, T: 'static> Type<'a> for Value { type Reified = T; + + unsafe fn consume( + sink: *const super::TaggedOption<'a, Self>, + type_id: TypeId, + ) -> Option> { + // SAFETY: sink is a valid pointer + unsafe { + if (*sink).0.is_none() && type_id == TypeId::of::() { + Some(NonNull::new_unchecked(&raw const (*sink).0 as *mut Self::Reified).cast()) + } else { + None + } + } + } } /// Type-based tag similar to [`Value`] but which may be unsized (i.e., has a `?Sized` bound). @@ -572,8 +1121,25 @@ pub(crate) mod tags { #[derive(Debug)] pub(crate) struct Ref(PhantomData); - impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref { + unsafe impl<'a, I: MaybeSizedType<'a>> Type<'a> for Ref + where + I::Reified: 'static, + { type Reified = &'a I::Reified; + + unsafe fn consume( + sink: *const super::TaggedOption<'a, Self>, + type_id: TypeId, + ) -> Option> { + // SAFETY: sink is a valid pointer + unsafe { + if (*sink).0.is_none() && type_id == TypeId::of::() { + Some(NonNull::new_unchecked(&raw const (*sink).0 as *mut Self::Reified).cast()) + } else { + None + } + } + } } } @@ -586,6 +1152,18 @@ pub(crate) mod tags { pub(crate) struct TaggedOption<'a, I: tags::Type<'a>>(pub Option); impl<'a, I: tags::Type<'a>> Tagged> { + fn new_concrete() -> Self { + Tagged { tag_id: TypeId::of::(), value: TaggedOption::<'a, I>(None) } + } +} + +impl<'a, T: Erased<'a>> Tagged { + fn new_virtual(value: T) -> Self { + Tagged { tag_id: TypeId::of::(), value } + } +} + +impl<'a, T: Erased<'a>> Tagged { pub(crate) fn as_request(&mut self) -> &mut Request<'a> { let erased = self as &mut Tagged + 'a>; // SAFETY: transmuting `&mut Tagged + 'a>` to `&mut Request<'a>` is safe since @@ -597,9 +1175,39 @@ impl<'a, I: tags::Type<'a>> Tagged> { /// Represents a type-erased but identifiable object. /// /// This trait is exclusively implemented by the `TaggedOption` type. -unsafe trait Erased<'a>: 'a {} +unsafe trait Erased<'a>: 'a { + // This requires `self` to be a valid pointer, and if `type_id == TypeId::of::`` and + // the function returns Some, returns a pointer with the same lifetime and + // mutability as `self` to `Option<>::Reified>`. -unsafe impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> {} + // in consume_closure, `self = this()`. + // The optimizer does not do the branch table optimization if your function takes + // a self pointer, do the closure hack to work around it + unsafe fn consume_closure( + this: impl FnOnce() -> *const Self, + type_id: TypeId, + ) -> Option> + where + Self: Sized; + + unsafe fn consume(self: *const Self, type_id: TypeId) -> Option>; +} + +unsafe impl<'a, I: tags::Type<'a>> Erased<'a> for TaggedOption<'a, I> { + // This impl is not really used since TaggedOptions are not virtual, but leave it here + unsafe fn consume_closure( + this: impl FnOnce() -> *const Self, + type_id: TypeId, + ) -> Option> { + // SAFETY: this is a valid pointer + unsafe { I::consume(&*this(), type_id) } + } + + unsafe fn consume(self: *const Self, type_id: TypeId) -> Option> { + // SAFETY: same safety conditions + unsafe { Self::consume_closure(move || self, type_id) } + } +} struct Tagged { tag_id: TypeId, @@ -607,6 +1215,68 @@ struct Tagged { } impl<'a> Tagged + 'a> { + fn is_virtual(&self) -> bool { + self.tag_id == TypeId::of::() + } + + #[inline] + fn would_be_satisfied_by(&self) -> bool + where + I: tags::Type<'a>, + { + if self.is_virtual() { + // consume returns None if the space is not satisfied + // SAFETY: `&raw const self.value` is valid + unsafe { (&raw const self.value).consume(TypeId::of::()).is_some() } + } else { + matches!(self.downcast::(), Some(TaggedOption(None))) + } + } + + #[inline] + fn provide(&mut self, value: I::Reified) + where + I: tags::Type<'a>, + { + if self.is_virtual() { + // SAFETY: consume_mut is defined to return either None or Some(I::Reified) + unsafe { + if let Some(res) = (&raw const self.value).consume(TypeId::of::()) { + let mut ptr: NonNull> = res.cast(); + // cast is fine since consume_mut returns a pointer to an Option + // could use `ptr::write` here, but this is not expected to be important enough + *ptr.as_mut() = Some(value); + } + } + } else { + if let Some(res @ TaggedOption(None)) = self.downcast_mut::() { + res.0 = Some(value); + } + } + } + + #[inline] + fn provide_with(&mut self, fulfil: impl FnOnce() -> I::Reified) + where + I: tags::Type<'a>, + { + if self.is_virtual() { + // SAFETY: consume_mut is defined to return either None or Some(I::Reified) + unsafe { + if let Some(res) = (&raw const self.value).consume(TypeId::of::()) { + let mut ptr: NonNull> = res.cast(); + // cast is fine since consume_mut returns a pointer to an Option + // could use `ptr::write` here, but this is not expected to be important enough + *ptr.as_mut() = Some(fulfil()); + } + } + } else { + if let Some(res @ TaggedOption(None)) = self.downcast_mut::() { + res.0 = Some(fulfil()); + } + } + } + /// Returns some reference to the dynamic value if it is tagged with `I`, /// or `None` otherwise. #[inline] diff --git a/library/core/src/lib.rs b/library/core/src/lib.rs index a1c7990f2311d..0673e57d35e07 100644 --- a/library/core/src/lib.rs +++ b/library/core/src/lib.rs @@ -95,6 +95,7 @@ // // Library features: // tidy-alphabetical-start +#![feature(arbitrary_self_types_pointers)] #![feature(array_ptr_get)] #![feature(asm_experimental_arch)] #![feature(bigint_helper_methods)] diff --git a/library/coretests/tests/error.rs b/library/coretests/tests/error.rs index 996566d3848bd..645f9a989e366 100644 --- a/library/coretests/tests/error.rs +++ b/library/coretests/tests/error.rs @@ -12,12 +12,23 @@ impl std::fmt::Display for SomeConcreteType { } } +struct Invalid; +#[derive(Debug, PartialEq, Eq)] +struct Valid; + impl std::error::Error for SomeConcreteType { fn provide<'a>(&'a self, request: &mut Request<'a>) { request .provide_ref::(&self.some_string) .provide_ref::(&self.some_string) - .provide_value_with::(|| "bye".to_owned()); + .provide_value_with::(|| "bye".to_owned()) + .provide_value_with::(|| panic!("should not be called")); + if request.would_be_satisfied_by_ref_of::() { + panic!("should not be satisfied"); + } + if request.would_be_satisfied_by_ref_of::() { + request.provide_ref(&Valid); + } } } @@ -29,6 +40,7 @@ fn test_error_generic_member_access() { assert_eq!(request_ref::(&*obj).unwrap(), "hello"); assert_eq!(request_value::(&*obj).unwrap(), "bye"); assert_eq!(request_value::(&obj), None); + assert_eq!(request_ref::(&obj), Some(&Valid)); } // Test the Error.provide and request mechanisms with a by-reference trait object. @@ -39,6 +51,7 @@ fn test_request_constructor() { assert_eq!(request_ref::(&*obj).unwrap(), "hello"); assert_eq!(request_value::(&*obj).unwrap(), "bye"); assert_eq!(request_value::(&obj), None); + assert_eq!(request_ref::(&obj), Some(&Valid)); } // Test the Error.provide and request mechanisms with a boxed trait object. @@ -49,6 +62,7 @@ fn test_error_generic_member_access_boxed() { assert_eq!(request_ref::(&*obj).unwrap(), "hello"); assert_eq!(request_value::(&*obj).unwrap(), "bye"); + assert_eq!(request_ref::(&*obj), Some(&Valid)); // NOTE: Box only implements Error when E: Error + Sized, which means we can't pass a // Box to request_value. @@ -63,4 +77,58 @@ fn test_error_generic_member_access_concrete() { assert_eq!(request_ref::(&obj).unwrap(), "hello"); assert_eq!(request_value::(&obj).unwrap(), "bye"); assert_eq!(request_value::(&obj), None); + assert_eq!(request_ref::(&obj), Some(&Valid)); +} + +#[test] +fn test_error_combined_access_concrete() { + let obj = SomeConcreteType { some_string: "hello".to_owned() }; + + let mut string_val = None; + let mut string_ref = None; + let mut u8_val = None; + let mut valid_ref = None; + + MultiRequestBuilder::new() + .with_value::() + .with_ref::() + .with_value::() + .with_ref::() + .request(&obj) + .retrieve_value::(|val| string_val = Some(val)) + .retrieve_ref::(|val| string_ref = Some(val)) + .retrieve_value::(|val| u8_val = Some(val)) + .retrieve_ref::(|val| valid_ref = Some(val)); + + assert_eq!(string_ref.unwrap(), "hello"); + assert_eq!(string_val.unwrap(), "bye"); + assert_eq!(u8_val, None); + assert_eq!(valid_ref.unwrap(), Valid); +} + +#[test] +fn test_error_combined_access_dyn() { + let obj = SomeConcreteType { some_string: "hello".to_owned() }; + let obj: &dyn Error = &obj; + + let mut string_val = None; + let mut string_ref = None; + let mut u8_val = None; + let mut valid_ref = None; + + MultiRequestBuilder::new() + .with_value::() + .with_ref::() + .with_value::() + .with_ref::() + .request(&obj) + .retrieve_value::(|val| string_val = Some(val)) + .retrieve_ref::(|val| string_ref = Some(val)) + .retrieve_value::(|val| u8_val = Some(val)) + .retrieve_ref::(|val| valid_ref = Some(val)); + + assert_eq!(string_ref.unwrap(), "hello"); + assert_eq!(string_val.unwrap(), "bye"); + assert_eq!(u8_val, None); + assert_eq!(valid_ref.unwrap(), Valid); } diff --git a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs index 7f4dad873cd6f..440f9c0a8f60a 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs +++ b/src/tools/rust-analyzer/crates/ide-db/src/generated/lints.rs @@ -5871,6 +5871,18 @@ The tracking issue for this feature is: [#99301] [#99301]: https://github.com/rust-lang/rust/issues/99301 +------------------------ +"##, + default_severity: Severity::Allow, + warn_since: None, + deny_since: None, + }, + Lint { + label: "error_generic_member_access_internals", + description: r##"# `error_generic_member_access_internals` + +This feature has no tracking issue, and is therefore likely internal to the compiler, not being intended for general use. + ------------------------ "##, default_severity: Severity::Allow, diff --git a/tests/codegen-llvm/error-provide.rs b/tests/codegen-llvm/error-provide.rs index 7f091e34359e4..af984161b4ae9 100644 --- a/tests/codegen-llvm/error-provide.rs +++ b/tests/codegen-llvm/error-provide.rs @@ -3,6 +3,10 @@ //@ compile-flags: -Copt-level=3 #![crate_type = "lib"] #![feature(error_generic_member_access)] + +extern crate core; + +use core::error::MultiResponse; use std::error::Request; use std::fmt; @@ -30,17 +34,20 @@ impl fmt::Display for MyError { } impl std::error::Error for MyError { - // CHECK-LABEL: @provide #[no_mangle] fn provide<'a>(&'a self, request: &mut Request<'a>) { - // LLVM should be able to optimize multiple .provide_* calls into a switch table - // and eliminate redundant ones, rather than compare one-by-one. - - // CHECK-NEXT: start: - // CHECK-NEXT: %[[SCRUTINEE:[^ ]+]] = load i128, ptr - // CHECK-NEXT: switch i128 %[[SCRUTINEE]], label %{{.*}} [ - // CHECK-COUNT-3: i128 {{.*}}, label %{{.*}} - // CHECK-NEXT: ] + // In an ideal world, LLVM would be able to generate a jump table here. + // Currently it can not, mostly because it can't prove that the tag id + // is not modified. However, this shouldn't matter much since this + // API shouldn't be called many times - when requesting a large number + // of items, MultiRequestBuilder should be used. + // + // We could make a MIR optimization pass that flattens + // the reads of the tag id - this is sound, since it is mutably borrowed, + // but this has a fairly low cost/benefit ratio - `provide` should + // only be called O(1) times per error constructed, and it's already + // not much slower than constructing the error (and much faster + // if an allocation, backtrace or formatting is involved). request .provide_ref::(&self.backtrace1) .provide_ref::(&self.other) @@ -48,3 +55,35 @@ impl std::error::Error for MyError { .provide_ref::(&self.backtrace3); } } + +pub fn provide_multi( + e: &dyn std::error::Error, +) -> (Option<&[u8; 0]>, Option<&[u8; 1]>, Option<&[u8; 2]>) { + let (mut bt1, mut bt2, mut bt3) = (None, None, None); + let mut value = core::error::MultiRequestBuilder::new() + .with_ref::<[u8; 0]>() + .with_ref::<[u8; 1]>() + .with_ref::<[u8; 2]>() + .with_ref::<[u8; 3]>() + .with_ref::<[u8; 4]>() + .with_ref::<[u8; 5]>() + .with_ref::<[u8; 6]>() + .with_ref::<[u8; 7]>() + .request(e) + .retrieve_ref(|b| bt1 = Some(b)) + .retrieve_ref(|b| bt2 = Some(b)) + .retrieve_ref(|b| bt3 = Some(b)); + (bt1, bt2, bt3) +} + +// Check that the virtual function generated has a switch + +// CHECK: define {{.*}}4core5error7provide{{.*}}21MultiResponseChainRef +// CHECK-NEXT: start: +// CHECK-NEXT: %[[SCRUTINEE:[^ ]+]] = load i128, ptr +// CHECK-NEXT: switch i128 %[[SCRUTINEE]], label %{{.*}} [ +// The request we write has 8 arms. However, LLVM puts the "bottom-most" 3 arms in +// a separate branch, before it produces a switch. This still leaves us with +// O(log n) performance [LLVM generates a binary tree for ] +// CHECK-COUNT-5: i128 {{.*}}, label %{{.*}} +// CHECK-NEXT: ] From a3209a509cc1088d0a6658a12109b7f12d9af48c Mon Sep 17 00:00:00 2001 From: Ariel Ben-Yehuda Date: Wed, 3 Dec 2025 23:07:21 +0000 Subject: [PATCH 4/4] cleanups --- library/core/src/error/mod.rs | 6 +-- library/core/src/error/provide.rs | 80 +++++++++++++++-------------- tests/codegen-llvm/error-provide.rs | 2 +- 3 files changed, 45 insertions(+), 43 deletions(-) diff --git a/library/core/src/error/mod.rs b/library/core/src/error/mod.rs index aee21cbf1526a..1bec774ee6aea 100644 --- a/library/core/src/error/mod.rs +++ b/library/core/src/error/mod.rs @@ -8,9 +8,9 @@ mod provide; #[unstable(feature = "error_generic_member_access", issue = "99301")] pub use provide::{ - ChainRefMultiRequestBuilder, ChainValMultiRequestBuilder, EmptyMultiRequestBuilder, - IntoMultiRequest, MultiRequestBuilder, MultiResponse, MultiResponseChainRef, - MultiResponseChainVal, Request, request_ref, request_value, + ChainRefMultiRequestBuilder, ChainRefMultiResponse, ChainValMultiRequestBuilder, + ChainValMultiResponse, EmptyMultiRequestBuilder, IntoMultiRequest, MultiRequestBuilder, + MultiResponse, Request, request_ref, request_value, }; /// `Error` is a trait representing the basic expectations for error values, diff --git a/library/core/src/error/provide.rs b/library/core/src/error/provide.rs index e0a73f6a7bbf0..8525b7c7464fb 100644 --- a/library/core/src/error/provide.rs +++ b/library/core/src/error/provide.rs @@ -538,16 +538,18 @@ pub struct ChainRefMultiRequestBuilder(PhantomData<(*const T, N /// There is no need to use this trait directly, use [MultiRequestBuilder] instead. #[unstable(feature = "error_generic_member_access", issue = "99301")] #[allow(private_bounds)] -pub trait IntoMultiRequest<'a>: private::IntoMultiRequestInner<'a> + 'static {} +pub trait IntoMultiRequest: private::IntoMultiRequestInner + 'static {} mod private { #[unstable(feature = "error_generic_member_access", issue = "99301")] #[allow(private_bounds)] - pub trait IntoMultiRequestInner<'a> { + pub trait IntoMultiRequestInner { #[unstable(feature = "error_generic_member_access", issue = "99301")] - type Request: super::Erased<'a> + MultiResponseInner<'a>; + type Request<'a>: super::Erased<'a> + MultiResponseInner<'a> + where + Self: 'a; #[unstable(feature = "error_generic_member_access", issue = "99301")] - fn get_request() -> Self::Request; + fn get_request<'a>() -> Self::Request<'a>; } #[unstable(feature = "error_generic_member_access", issue = "99301")] @@ -560,58 +562,58 @@ mod private { } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a> IntoMultiRequest<'a> for EmptyMultiRequestBuilder {} +impl IntoMultiRequest for EmptyMultiRequestBuilder {} #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a> private::IntoMultiRequestInner<'a> for EmptyMultiRequestBuilder { - type Request = EmptyMultiResponse; +impl private::IntoMultiRequestInner for EmptyMultiRequestBuilder { + type Request<'a> = EmptyMultiResponse; - fn get_request() -> Self::Request { + fn get_request<'a>() -> Self::Request<'a> { EmptyMultiResponse } } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a, T, NEXT> IntoMultiRequest<'a> for ChainValMultiRequestBuilder +impl IntoMultiRequest for ChainValMultiRequestBuilder where T: 'static, - NEXT: IntoMultiRequest<'a>, + NEXT: IntoMultiRequest, { } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a, T, NEXT> private::IntoMultiRequestInner<'a> for ChainValMultiRequestBuilder +impl private::IntoMultiRequestInner for ChainValMultiRequestBuilder where T: 'static, - NEXT: IntoMultiRequest<'a>, + NEXT: IntoMultiRequest, { - type Request = MultiResponseChainVal<'a, T, NEXT::Request>; + type Request<'a> = ChainValMultiResponse<'a, T, NEXT::Request<'a>>; - fn get_request() -> Self::Request { - MultiResponseChainVal { - inner: MultiResponseChain { cur: None, next: NEXT::get_request(), marker: PhantomData }, + fn get_request<'a>() -> Self::Request<'a> { + ChainValMultiResponse { + inner: ChainMultiResponse { cur: None, next: NEXT::get_request(), marker: PhantomData }, } } } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a, T, NEXT> IntoMultiRequest<'a> for ChainRefMultiRequestBuilder +impl IntoMultiRequest for ChainRefMultiRequestBuilder where T: ?Sized + 'static, - NEXT: IntoMultiRequest<'a>, + NEXT: IntoMultiRequest, { } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a, T, NEXT> private::IntoMultiRequestInner<'a> for ChainRefMultiRequestBuilder +impl private::IntoMultiRequestInner for ChainRefMultiRequestBuilder where T: ?Sized + 'static, - NEXT: IntoMultiRequest<'a>, + NEXT: IntoMultiRequest, { - type Request = MultiResponseChainRef<'a, T, NEXT::Request>; + type Request<'a> = ChainRefMultiResponse<'a, T, NEXT::Request<'a>>; - fn get_request() -> Self::Request { - MultiResponseChainRef { - inner: MultiResponseChain { cur: None, next: NEXT::get_request(), marker: PhantomData }, + fn get_request<'a>() -> Self::Request<'a> { + ChainRefMultiResponse { + inner: ChainMultiResponse { cur: None, next: NEXT::get_request(), marker: PhantomData }, } } } @@ -626,7 +628,7 @@ where pub struct EmptyMultiResponse; #[derive(Debug)] -struct MultiResponseChain<'a, I, NEXT> +struct ChainMultiResponse<'a, I, NEXT> where I: tags::Type<'a>, { @@ -643,11 +645,11 @@ where issue = "none" )] #[derive(Debug)] -pub struct MultiResponseChainVal<'a, T, NEXT> +pub struct ChainValMultiResponse<'a, T, NEXT> where T: 'static, { - inner: MultiResponseChain<'a, tags::Value, NEXT>, + inner: ChainMultiResponse<'a, tags::Value, NEXT>, } /// A response from a [MultiRequestBuilder::request] after calling [MultiRequestBuilder::with_ref]. @@ -657,11 +659,11 @@ where issue = "none" )] #[derive(Debug)] -pub struct MultiResponseChainRef<'a, T, NEXT> +pub struct ChainRefMultiResponse<'a, T, NEXT> where T: 'static + ?Sized, { - inner: MultiResponseChain<'a, tags::Ref>, NEXT>, + inner: ChainMultiResponse<'a, tags::Ref>, NEXT>, } /// A response from a [MultiRequestBuilder]. The types returned from @@ -758,7 +760,7 @@ impl<'a> private::MultiResponseInner<'a> for EmptyMultiResponse { } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a, J, NEXT> private::MultiResponseInner<'a> for MultiResponseChain<'a, J, NEXT> +impl<'a, J, NEXT> private::MultiResponseInner<'a> for ChainMultiResponse<'a, J, NEXT> where J: tags::Type<'a>, NEXT: private::MultiResponseInner<'a>, @@ -785,7 +787,7 @@ where } } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a, T, NEXT> private::MultiResponseInner<'a> for MultiResponseChainVal<'a, T, NEXT> +impl<'a, T, NEXT> private::MultiResponseInner<'a> for ChainValMultiResponse<'a, T, NEXT> where T: 'static, NEXT: private::MultiResponseInner<'a>, @@ -801,7 +803,7 @@ where } #[unstable(feature = "error_generic_member_access", issue = "99301")] // SAFETY: delegates to inner impl -unsafe impl<'a, T, NEXT> Erased<'a> for MultiResponseChainVal<'a, T, NEXT> +unsafe impl<'a, T, NEXT> Erased<'a> for ChainValMultiResponse<'a, T, NEXT> where T: 'static, NEXT: Erased<'a>, @@ -811,7 +813,7 @@ where type_id: TypeId, ) -> Option> { // SAFETY: delegation - unsafe { MultiResponseChain::consume_closure(move || &raw const (*this()).inner, type_id) } + unsafe { ChainMultiResponse::consume_closure(move || &raw const (*this()).inner, type_id) } } unsafe fn consume(self: *const Self, type_id: TypeId) -> Option> { @@ -821,7 +823,7 @@ where } #[unstable(feature = "error_generic_member_access", issue = "99301")] -impl<'a, T, NEXT> private::MultiResponseInner<'a> for MultiResponseChainRef<'a, T, NEXT> +impl<'a, T, NEXT> private::MultiResponseInner<'a> for ChainRefMultiResponse<'a, T, NEXT> where T: 'static + ?Sized, NEXT: private::MultiResponseInner<'a>, @@ -837,7 +839,7 @@ where } #[unstable(feature = "error_generic_member_access", issue = "99301")] // SAFETY: delegates to inner impl -unsafe impl<'a, T, NEXT> Erased<'a> for MultiResponseChainRef<'a, T, NEXT> +unsafe impl<'a, T, NEXT> Erased<'a> for ChainRefMultiResponse<'a, T, NEXT> where T: 'static + ?Sized, NEXT: Erased<'a>, @@ -847,7 +849,7 @@ where type_id: TypeId, ) -> Option> { // SAFETY: delegation - unsafe { MultiResponseChain::consume_closure(move || &raw const (*this()).inner, type_id) } + unsafe { ChainMultiResponse::consume_closure(move || &raw const (*this()).inner, type_id) } } unsafe fn consume(self: *const Self, type_id: TypeId) -> Option> { @@ -870,7 +872,7 @@ unsafe impl<'a> Erased<'a> for EmptyMultiResponse { } } -unsafe impl<'a, I, NEXT> Erased<'a> for MultiResponseChain<'a, I, NEXT> +unsafe impl<'a, I, NEXT> Erased<'a> for ChainMultiResponse<'a, I, NEXT> where I: tags::Type<'a>, NEXT: Erased<'a>, @@ -967,7 +969,7 @@ where /// assert_eq!(string_val, None); /// } /// ``` -pub struct MultiRequestBuilder IntoMultiRequest<'a>> { +pub struct MultiRequestBuilder { inner: PhantomData, } @@ -979,7 +981,7 @@ impl MultiRequestBuilder { } } -impl IntoMultiRequest<'a>> MultiRequestBuilder { +impl MultiRequestBuilder { /// Create a [MultiRequestBuilder] that requests a value. /// /// # Examples diff --git a/tests/codegen-llvm/error-provide.rs b/tests/codegen-llvm/error-provide.rs index af984161b4ae9..ba84c5f2c8d33 100644 --- a/tests/codegen-llvm/error-provide.rs +++ b/tests/codegen-llvm/error-provide.rs @@ -78,7 +78,7 @@ pub fn provide_multi( // Check that the virtual function generated has a switch -// CHECK: define {{.*}}4core5error7provide{{.*}}21MultiResponseChainRef +// CHECK: define {{.*}}4core5error7provide{{.*}}21ChainRefMultiResponse // CHECK-NEXT: start: // CHECK-NEXT: %[[SCRUTINEE:[^ ]+]] = load i128, ptr // CHECK-NEXT: switch i128 %[[SCRUTINEE]], label %{{.*}} [