Skip to content

Commit 5832ebf

Browse files
committed
Add derive macro Throwable.
1 parent dc75ec7 commit 5832ebf

File tree

4 files changed

+182
-26
lines changed

4 files changed

+182
-26
lines changed

phper-macros/src/derives.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
use proc_macro::TokenStream;
2+
use proc_macro2::TokenStream as TokenStream2;
3+
use quote::quote;
4+
use syn::{Attribute, Data, DeriveInput, Fields, Meta};
5+
6+
pub(crate) fn derive_throwable(input: DeriveInput) -> syn::Result<TokenStream> {
7+
let crate_ident = parse_throwable_crate_ident(&input);
8+
let exception = parse_throwable_attrs(&input)?;
9+
parse_throwable_input(&input, crate_ident, exception)
10+
}
11+
12+
fn parse_throwable_crate_ident(input: &DeriveInput) -> TokenStream2 {
13+
let has_throwable_crate = attributes_find_ident(&input.attrs, "throwable_crate");
14+
let crate_ident = if has_throwable_crate.is_some() {
15+
quote! { crate }
16+
} else {
17+
quote! { phper }
18+
};
19+
crate_ident
20+
}
21+
22+
fn parse_throwable_attrs(input: &DeriveInput) -> syn::Result<TokenStream2> {
23+
let attr = attributes_find_ident(&input.attrs, "throwable");
24+
attr.map(|attr| {
25+
attr.parse_args::<Meta>().and_then(|meta| match meta {
26+
Meta::NameValue(name_value) => {
27+
if !name_value.path.is_ident("class") {
28+
Err(syn::Error::new_spanned(
29+
&attr,
30+
"now only support #[throwable(error = ?)] for enum",
31+
))
32+
} else {
33+
let lit = name_value.lit;
34+
Ok(quote! { #lit })
35+
}
36+
}
37+
_ => Err(syn::Error::new_spanned(
38+
&attr,
39+
"now only support #[throwable(error = ?)] for enum",
40+
)),
41+
})
42+
})
43+
.unwrap_or_else(|| Ok(quote! { "Exception" }))
44+
}
45+
46+
fn parse_throwable_input(
47+
input: &DeriveInput,
48+
crate_ident: TokenStream2,
49+
exception: TokenStream2,
50+
) -> syn::Result<TokenStream> {
51+
let input_ident = &input.ident;
52+
53+
match &input.data {
54+
Data::Enum(e) => {
55+
let mut transparent_idents = Vec::new();
56+
57+
for variant in &e.variants {
58+
let attr = attributes_find_ident(&variant.attrs, "throwable");
59+
match attr {
60+
Some(attr) => {
61+
if attr.tokens.to_string() != "(transparent)" {
62+
return Err(syn::Error::new_spanned(
63+
&attr,
64+
"now only support #[throwable(transparent)] for variant",
65+
));
66+
}
67+
match &variant.fields {
68+
Fields::Unnamed(f) if f.unnamed.len() == 1 => {
69+
transparent_idents.push(variant.ident.clone());
70+
}
71+
_ => {
72+
return Err(syn::Error::new_spanned(
73+
&variant,
74+
"now only support unnamed field with one item mark attribute #[throwable]",
75+
));
76+
}
77+
}
78+
}
79+
None => continue,
80+
}
81+
}
82+
83+
let mut class_entry_arms = transparent_idents
84+
.iter()
85+
.map(|i| {
86+
quote! { Self::#i(e) => #crate_ident::errors::Throwable::class_entry(e), }
87+
})
88+
.collect::<Vec<_>>();
89+
class_entry_arms.push(quote! { _ => ClassEntry::from_globals(#exception).unwrap(), });
90+
91+
let mut code_arms = transparent_idents
92+
.iter()
93+
.map(|i| {
94+
quote! { Self::#i(e) => #crate_ident::errors::Throwable::code(e), }
95+
})
96+
.collect::<Vec<_>>();
97+
code_arms.push(quote! { _ => 0, });
98+
99+
let mut message_arms = transparent_idents
100+
.iter()
101+
.map(|i| {
102+
quote! { Self::#i(e) => #crate_ident::errors::Throwable::message(e), }
103+
})
104+
.collect::<Vec<_>>();
105+
message_arms.push(quote! { _ => std::string::ToString::to_string(&self), });
106+
107+
Ok((quote! {
108+
impl #crate_ident::errors::Throwable for #input_ident {
109+
fn class_entry(&self) -> &#crate_ident::classes::StatelessClassEntry {
110+
match self {
111+
#(#class_entry_arms)*
112+
}
113+
}
114+
115+
fn code(&self) -> u64 {
116+
match self {
117+
#(#code_arms)*
118+
}
119+
}
120+
121+
fn message(&self) -> std::string::String {
122+
match self {
123+
#(#message_arms)*
124+
}
125+
}
126+
}
127+
})
128+
.into())
129+
}
130+
Data::Struct(_) => Err(syn::Error::new_spanned(
131+
&input,
132+
"struct auto derive Throwable is not supported",
133+
)),
134+
Data::Union(_) => Err(syn::Error::new_spanned(
135+
&input,
136+
"union auto derive Throwable is not supported",
137+
)),
138+
}
139+
}
140+
141+
fn attributes_find_ident<'a>(attrs: &'a [Attribute], ident: &'a str) -> Option<&'a Attribute> {
142+
attrs.iter().find(|attr| attr.path.is_ident(ident))
143+
}

phper-macros/src/lib.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,13 @@ The proc-macros for [phper](https://crates.io/crates/phper).
1111
// TODO Write a bridge macro for easy usage about register functions and classes, like `cxx`.
1212

1313
mod alloc;
14+
mod derives;
1415
mod inner;
1516
mod log;
1617
mod utils;
1718

1819
use proc_macro::TokenStream;
20+
use syn::{parse_macro_input, DeriveInput};
1921

2022
/// C style string end with '\0'.
2123
///
@@ -70,3 +72,27 @@ pub fn c_str_ptr(input: TokenStream) -> TokenStream {
7072
pub fn php_get_module(attr: TokenStream, input: TokenStream) -> TokenStream {
7173
inner::php_get_module(attr, input)
7274
}
75+
76+
/// Auto derive for [phper::errors::Throwable].
77+
///
78+
/// # Examples
79+
///
80+
/// ```no_test
81+
/// #[derive(thiserror::Error, crate::Throwable, Debug)]
82+
/// #[throwable(class = "Exception")]
83+
/// pub enum Error {
84+
/// #[error(transparent)]
85+
/// Io(#[from] std::io::Error),
86+
///
87+
/// #[error(transparent)]
88+
/// #[throwable(transparent)]
89+
/// My(#[from] MyError),
90+
/// }
91+
/// ```
92+
///
93+
/// TODO Support struct, attbiute `throwable` with `code` and `message`.
94+
#[proc_macro_derive(Throwable, attributes(throwable, throwable_crate))]
95+
pub fn derive_throwable(input: TokenStream) -> TokenStream {
96+
let input = parse_macro_input!(input as DeriveInput);
97+
derives::derive_throwable(input).unwrap_or_else(|e| e.into_compile_error().into())
98+
}

phper/src/errors.rs

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ pub type Result<T> = std::result::Result<T, self::Error>;
3535
/// Crate level Error, which also can become an exception in php.
3636
///
3737
/// As a php exception, will throw `ErrorException` when the item not implement [Throwable].
38-
#[derive(thiserror::Error, Debug)]
38+
#[derive(thiserror::Error, crate::Throwable, Debug)]
39+
#[throwable(class = "ErrorException")]
40+
#[throwable_crate]
3941
pub enum Error {
4042
#[error(transparent)]
4143
Io(#[from] io::Error),
@@ -46,20 +48,23 @@ pub enum Error {
4648
#[error(transparent)]
4749
FromBytesWithNul(#[from] FromBytesWithNulError),
4850

51+
#[error(transparent)]
52+
Other(#[from] anyhow::Error),
53+
4954
#[error(transparent)]
5055
Type(#[from] TypeError),
5156

5257
#[error(transparent)]
58+
#[throwable(transparent)]
5359
ClassNotFound(#[from] ClassNotFoundError),
5460

5561
#[error(transparent)]
62+
#[throwable(transparent)]
5663
ArgumentCount(#[from] ArgumentCountError),
5764

5865
#[error(transparent)]
66+
#[throwable(transparent)]
5967
StateType(#[from] StateTypeError),
60-
61-
#[error(transparent)]
62-
Other(#[from] anyhow::Error),
6368
}
6469

6570
impl Error {
@@ -70,27 +75,6 @@ impl Error {
7075
}
7176
}
7277

73-
// TODO Add message() implement.
74-
impl Throwable for Error {
75-
fn class_entry(&self) -> &StatelessClassEntry {
76-
match self {
77-
Self::Type(e) => e.class_entry(),
78-
Self::ClassNotFound(e) => e.class_entry(),
79-
Self::ArgumentCount(e) => e.class_entry(),
80-
_ => ClassEntry::from_globals("ErrorException").unwrap(),
81-
}
82-
}
83-
84-
fn code(&self) -> u64 {
85-
match self {
86-
Self::Type(e) => e.code(),
87-
Self::ClassNotFound(e) => e.code(),
88-
Self::ArgumentCount(e) => e.code(),
89-
_ => 0,
90-
}
91-
}
92-
}
93-
9478
#[derive(thiserror::Error, Debug)]
9579
#[error("type error: {message}")]
9680
pub struct TypeError {

phper/src/modules.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ impl Module {
118118
self.module_shutdown = Some(Box::new(func));
119119
}
120120

121-
pub fn on_request_init(&mut self, func: impl Fn(ModuleContext) -> bool + Send + Sync + 'static) {
121+
pub fn on_request_init(
122+
&mut self,
123+
func: impl Fn(ModuleContext) -> bool + Send + Sync + 'static,
124+
) {
122125
self.request_init = Some(Box::new(func));
123126
}
124127

0 commit comments

Comments
 (0)