Skip to content

Commit

Permalink
support arc new type
Browse files Browse the repository at this point in the history
Signed-off-by: Bugen Zhao <[email protected]>
  • Loading branch information
BugenZhao committed Nov 29, 2023
1 parent 5a33d1e commit 5613b53
Show file tree
Hide file tree
Showing 7 changed files with 173 additions and 87 deletions.
56 changes: 45 additions & 11 deletions derive/src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,28 @@ pub enum DeriveCtorType {
ContextInto,
}

pub fn derive_box(input: &DeriveInput) -> Result<TokenStream> {
pub enum DeriveNewType {
Box,
Arc,
}

impl DeriveNewType {
fn name(&self) -> &'static str {
match self {
DeriveNewType::Box => "Box",
DeriveNewType::Arc => "Arc",
}
}

fn ty_ident(&self) -> Ident {
match self {
DeriveNewType::Box => format_ident!("ErrorBox"),
DeriveNewType::Arc => format_ident!("ErrorArc"),
}
}
}

pub fn derive_new_type(input: &DeriveInput, ty: DeriveNewType) -> Result<TokenStream> {
let input_type = input.ident.clone();
let vis = &input.vis;

Expand All @@ -206,25 +227,41 @@ pub fn derive_box(input: &DeriveInput) -> Result<TokenStream> {
if impl_type == input_type {
return Err(syn::Error::new_spanned(
input,
"should specify a different type for `Box` derive with `#[thiserror_ext(type = <type>)]`",
format!("should specify a different type for `{}` derive with `#[thiserror_ext(type = <type>)]`", ty.name()),
));
}

let backtrace_type_param = if backtrace {
quote!(thiserror_ext::__private::MaybeBacktrace)
} else {
quote!(thiserror_ext::__private::NoBacktrace)
quote!(thiserror_ext::__private::NoExtraBacktrace)
};

let doc = format!("The `{}`-wrapped type of [`{}`].", ty.name(), input_type);
let new_type = ty.ty_ident();
let extra_derive = match ty {
DeriveNewType::Box => quote!(),
DeriveNewType::Arc => quote!(Clone),
};

let into_inner = match ty {
DeriveNewType::Box => quote!(
#[doc = "Consumes `self` and returns the inner error."]
#vis fn into_inner(self) -> #input_type {
self.0.into_inner()
}
),
DeriveNewType::Arc => quote!(),
};

let doc = format!("The boxed type of [`{}`].", input_type);
let generated = quote!(
#[doc = #doc]
#[derive(thiserror_ext::__private::thiserror::Error, Debug)]
#[derive(thiserror_ext::__private::thiserror::Error, Debug, #extra_derive)]
#[error(transparent)]
#vis struct #impl_type(
#[from]
#[backtrace]
thiserror_ext::__private::ErrorBox<
thiserror_ext::__private::#new_type<
#input_type,
#backtrace_type_param,
>,
Expand All @@ -236,7 +273,7 @@ pub fn derive_box(input: &DeriveInput) -> Result<TokenStream> {
E: Into<#input_type>,
{
fn from(error: E) -> Self {
Self(thiserror_ext::__private::ErrorBox::new(error.into()))
Self(thiserror_ext::__private::#new_type::new(error.into()))
}
}

Expand All @@ -246,10 +283,7 @@ pub fn derive_box(input: &DeriveInput) -> Result<TokenStream> {
self.0.inner()
}

#[doc = "Consumes `self` and returns the inner error."]
#vis fn into_inner(self) -> #input_type {
self.0.into_inner()
}
#into_inner
}
);

Expand Down
13 changes: 11 additions & 2 deletions derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use expand::DeriveCtorType;
use expand::{DeriveCtorType, DeriveNewType};
use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};

Expand Down Expand Up @@ -27,7 +27,16 @@ pub fn derive_context_into(input: TokenStream) -> TokenStream {
pub fn derive_box(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

expand::derive_box(&input)
expand::derive_new_type(&input, DeriveNewType::Box)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

#[proc_macro_derive(Arc, attributes(thiserror_ext))]
pub fn derive_arc(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

expand::derive_new_type(&input, DeriveNewType::Arc)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
Expand Down
4 changes: 2 additions & 2 deletions src/backtrace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ pub trait WithBacktrace {
}

/// Do not capture extra backtrace.
pub struct NoBacktrace;
pub struct NoExtraBacktrace;

/// Capture backtrace if the error does not already have one.
pub struct MaybeBacktrace(Option<Backtrace>);

impl WithBacktrace for NoBacktrace {
impl WithBacktrace for NoExtraBacktrace {
fn capture(_inner: &dyn std::error::Error) -> Self {
Self
}
Expand Down
69 changes: 0 additions & 69 deletions src/error_box.rs

This file was deleted.

6 changes: 3 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

mod as_dyn;
mod backtrace;
mod error_box;
mod ptr;
mod report;

pub use as_dyn::AsDyn;
Expand All @@ -11,8 +11,8 @@ pub use thiserror_ext_derive::*;

#[doc(hidden)]
pub mod __private {
pub use crate::backtrace::{MaybeBacktrace, NoBacktrace};
pub use crate::error_box::ErrorBox;
pub use crate::backtrace::{MaybeBacktrace, NoExtraBacktrace};
pub use crate::ptr::{ErrorArc, ErrorBox};
pub use thiserror;
}

Expand Down
89 changes: 89 additions & 0 deletions src/ptr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use std::sync::Arc;

use crate::backtrace::WithBacktrace;

/// Workaround for https://github.com/rust-lang/rust/issues/117432.
#[derive(Clone)]
#[repr(transparent)]
pub struct ErrorBox<T, B>(Box<(T, B)>);

impl<T, B> ErrorBox<T, B> {
pub fn inner_mut(&mut self) -> &mut T {
&mut self.0.as_mut().0
}

pub fn into_inner(self) -> T {
(*self.0).0
}
}

impl<T, B> std::ops::DerefMut for ErrorBox<T, B> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner_mut()
}
}

#[repr(transparent)]
pub struct ErrorArc<T, B>(Arc<(T, B)>);

impl<T, B> Clone for ErrorArc<T, B> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}

macro_rules! impl_methods {
($ty:ident) => {
impl<T: std::error::Error, B: WithBacktrace> $ty<T, B> {
pub fn new(t: T) -> Self {
let backtrace = B::capture(&t);
Self((t, backtrace).into())
}
}

impl<T, B> $ty<T, B> {
fn backtrace(&self) -> &B {
&self.0.as_ref().1
}

pub fn inner(&self) -> &T {
&self.0.as_ref().0
}
}

impl<T, B> std::ops::Deref for $ty<T, B> {
type Target = T;

fn deref(&self) -> &Self::Target {
self.inner()
}
}

impl<T: std::fmt::Display, B> std::fmt::Display for $ty<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner().fmt(f)
}
}

impl<T: std::fmt::Debug, B> std::fmt::Debug for $ty<T, B> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner().fmt(f)
}
}

impl<T: std::error::Error, B: WithBacktrace> std::error::Error for $ty<T, B> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
T::source(self.inner())
}

// https://github.com/rust-lang/rust/issues/117432
fn provide<'a>(&'a self, request: &mut std::error::Request<'a>) {
self.backtrace().provide(request);
T::provide(self.inner(), request);
}
}
};
}

impl_methods!(ErrorBox);
impl_methods!(ErrorArc);
23 changes: 23 additions & 0 deletions tests/arc_new_type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#![feature(error_generic_member_access)]

use std::{error::Error, num::ParseIntError};

use thiserror::*;
use thiserror_ext::*;

#[derive(Error, Debug, Arc, Construct)]
#[thiserror_ext(type = SharedMyError)]
pub enum MyErrorInner {
#[error("foo: {foo}")]
Foo { source: ParseIntError, foo: String },
}

#[test]
fn test() {
let error = SharedMyError::foo("nope".parse::<i32>().unwrap_err(), "hello".to_owned());
let error2 = error.clone();

// Test source preserved.
let source = error2.source().unwrap();
assert_eq!(source.to_string(), "invalid digit found in string");
}

0 comments on commit 5613b53

Please sign in to comment.