From 97eeb45b8b8a8b2373b74c2c6be0d4fa14d6596d Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Fri, 4 Aug 2023 19:52:42 +0200 Subject: [PATCH] Replace DisplayAsDisplay and PathAsDisplay with AsDisplay trait MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rather than having separate traits implementing as_display method, replace DisplayAsDisplay and PathAsDisplay traits with a AsDisplay trait. The difference between those two traits is in the result returned by the as_display method. With AsDisplay trait this is captured by an associated type. The main motivation for the change is making it simpler to support no_std builds in the future. Previously, PathAsDisplay would have to be handled specially in such builds on the side of macro expansion. Now, thiserror-impl doesn’t need to be aware of any complications around AsDisplay since they are all contained in thiserror crate. --- impl/src/expand.rs | 32 +++++++++++------------ src/display.rs | 53 ++++++++++++++++++++++++++++---------- src/lib.rs | 2 +- tests/ui/no-display.stderr | 2 +- 4 files changed, 56 insertions(+), 33 deletions(-) diff --git a/impl/src/expand.rs b/impl/src/expand.rs index f6822b2..b0fa2e5 100644 --- a/impl/src/expand.rs +++ b/impl/src/expand.rs @@ -120,14 +120,7 @@ fn impl_struct(input: Struct) -> TokenStream { }) } else if let Some(display) = &input.attrs.display { display_implied_bounds = display.implied_bounds.clone(); - let use_as_display = if display.has_bonus_display { - Some(quote! { - #[allow(unused_imports)] - use thiserror::__private::{DisplayAsDisplay, PathAsDisplay}; - }) - } else { - None - }; + let use_as_display = impl_use_as_display(display.has_bonus_display); let pat = fields_pat(&input.fields); Some(quote! { #use_as_display @@ -351,19 +344,13 @@ fn impl_enum(input: Enum) -> TokenStream { let display_impl = if input.has_display() { let mut display_inferred_bounds = InferredBounds::new(); - let use_as_display = if input.variants.iter().any(|v| { + let use_as_display = input.variants.iter().any(|v| { v.attrs .display .as_ref() .map_or(false, |display| display.has_bonus_display) - }) { - Some(quote! { - #[allow(unused_imports)] - use thiserror::__private::{DisplayAsDisplay, PathAsDisplay}; - }) - } else { - None - }; + }); + let use_as_display = impl_use_as_display(use_as_display); let void_deref = if input.variants.is_empty() { Some(quote!(*)) } else { @@ -466,6 +453,17 @@ fn fields_pat(fields: &[Field]) -> TokenStream { } } +fn impl_use_as_display(needs_as_display: bool) -> Option { + if needs_as_display { + Some(quote! { + #[allow(unused_imports)] + use thiserror::__private::AsDisplay as _; + }) + } else { + None + } +} + fn from_initializer(from_field: &Field, backtrace_field: Option<&Field>) -> TokenStream { let from_member = &from_field.member; let some_source = if type_is_option(from_field.ty) { diff --git a/src/display.rs b/src/display.rs index da49051..ab7a5b6 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,30 +1,55 @@ use std::fmt::Display; -use std::path::{self, Path, PathBuf}; +use std::path::{Path, PathBuf}; #[doc(hidden)] -pub trait DisplayAsDisplay { - fn as_display(&self) -> Self; +pub trait AsDisplay { + type Target: Display + ?Sized; + + fn as_display(&self) -> &Self::Target; } -impl DisplayAsDisplay for &T { - fn as_display(&self) -> Self { +impl AsDisplay for &T { + type Target = T; + + fn as_display(&self) -> &Self::Target { self } } -#[doc(hidden)] -pub trait PathAsDisplay { - fn as_display(&self) -> path::Display<'_>; +impl AsDisplay for Path { + type Target = PathDisplay; + + #[inline] + fn as_display(&self) -> &Self::Target { + PathDisplay::new(self) + } } -impl PathAsDisplay for Path { - fn as_display(&self) -> path::Display<'_> { - self.display() +impl AsDisplay for PathBuf { + type Target = PathDisplay; + + #[inline] + fn as_display(&self) -> &Self::Target { + PathDisplay::new(self.as_path()) + } +} + +#[doc(hidden)] +#[repr(transparent)] +pub struct PathDisplay(Path); + +impl PathDisplay { + #[inline] + fn new(path: &Path) -> &Self { + // SAFETY: PathDisplay is repr(transparent) so casting pointers between + // it and its payload is safe. + unsafe { &*(path as *const Path as *const Self) } } } -impl PathAsDisplay for PathBuf { - fn as_display(&self) -> path::Display<'_> { - self.display() +impl Display for PathDisplay { + #[inline] + fn fmt(&self, fmtr: &mut std::fmt::Formatter) -> std::fmt::Result { + self.0.display().fmt(fmtr) } } diff --git a/src/lib.rs b/src/lib.rs index e5d2950..08b8d56 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -251,7 +251,7 @@ pub mod __private { #[doc(hidden)] pub use crate::aserror::AsDynError; #[doc(hidden)] - pub use crate::display::{DisplayAsDisplay, PathAsDisplay}; + pub use crate::display::AsDisplay; #[cfg(error_generic_member_access)] #[doc(hidden)] pub use crate::provide::ThiserrorProvide; diff --git a/tests/ui/no-display.stderr b/tests/ui/no-display.stderr index 76818e1..26dc842 100644 --- a/tests/ui/no-display.stderr +++ b/tests/ui/no-display.stderr @@ -9,7 +9,7 @@ error[E0599]: the method `as_display` exists for reference `&NoDisplay`, but its | = note: the following trait bounds were not satisfied: `NoDisplay: std::fmt::Display` - which is required by `&NoDisplay: DisplayAsDisplay` + which is required by `&NoDisplay: AsDisplay` note: the trait `std::fmt::Display` must be implemented --> $RUST/core/src/fmt/mod.rs |