feat (macro-rs): convert Result<T, E> return type to napi::Result<T>
This commit is contained in:
parent
5d2238a266
commit
c231bf43cb
3 changed files with 145 additions and 10 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1217,9 +1217,11 @@ name = "macro_rs"
|
|||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"napi",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.58",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -12,3 +12,8 @@ convert_case = { workspace = true }
|
|||
proc-macro2 = { workspace = true }
|
||||
quote = { workspace = true }
|
||||
syn = { workspace = true, features = ["full", "extra-traits"] }
|
||||
|
||||
[dev-dependencies]
|
||||
thiserror = { workspace = true }
|
||||
napi = { workspace = true }
|
||||
#napi-derive = { workspace = true }
|
||||
|
|
|
@ -2,6 +2,16 @@ use convert_case::{Case, Casing};
|
|||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
|
||||
// FIXME
|
||||
/// For doctest only
|
||||
#[proc_macro_attribute]
|
||||
pub fn dummy_macro(
|
||||
_attr: proc_macro::TokenStream,
|
||||
item: proc_macro::TokenStream,
|
||||
) -> proc_macro::TokenStream {
|
||||
item
|
||||
}
|
||||
|
||||
/// Creates extra wrapper function for napi.
|
||||
///
|
||||
/// The types of the function arguments is converted with following rules:
|
||||
|
@ -9,9 +19,13 @@ use quote::{quote, ToTokens};
|
|||
/// - `&T` and `&mut T` are converted to `T`
|
||||
/// - Other `T` remains `T`
|
||||
///
|
||||
/// In addition, return type `Result<T>` and `Result<T, E>` are converted to `napi::Result<T>`.
|
||||
/// Note that `E` must implement `std::string::ToString` trait.
|
||||
///
|
||||
/// # Examples
|
||||
/// ## Example with `i32` argument
|
||||
/// ```rust
|
||||
/// ```
|
||||
/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME
|
||||
/// #[macro_rs::napi]
|
||||
/// pub fn add_one(x: i32) -> i32 {
|
||||
/// x + 1
|
||||
|
@ -20,8 +34,11 @@ use quote::{quote, ToTokens};
|
|||
///
|
||||
/// generates
|
||||
///
|
||||
/// ```rust
|
||||
/// #[cfg(feature = "napi")]
|
||||
/// ```
|
||||
/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME
|
||||
/// # pub fn add_one(x: i32) -> i32 {
|
||||
/// # x + 1
|
||||
/// # }
|
||||
/// #[napi_derive::napi(js_name = "addOne")]
|
||||
/// pub fn add_one_napi(x: i32) -> i32 {
|
||||
/// add_one(x)
|
||||
|
@ -29,7 +46,8 @@ use quote::{quote, ToTokens};
|
|||
/// ```
|
||||
///
|
||||
/// ## Example with `&str` argument
|
||||
/// ```rust
|
||||
/// ```
|
||||
/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME
|
||||
/// #[macro_rs::napi]
|
||||
/// pub fn concatenate_string(str1: &str, str2: &str) -> String {
|
||||
/// str1.to_owned() + str2
|
||||
|
@ -38,14 +56,66 @@ use quote::{quote, ToTokens};
|
|||
///
|
||||
/// generates
|
||||
///
|
||||
/// ```rust
|
||||
/// #[cfg(feature = "napi")]
|
||||
/// ```
|
||||
/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME
|
||||
/// # pub fn concatenate_string(str1: &str, str2: &str) -> String {
|
||||
/// # str1.to_owned() + str2
|
||||
/// # }
|
||||
/// #[napi_derive::napi(js_name = "concatenateString")]
|
||||
/// pub fn concatenate_string_napi(str1: String, str2: String) -> String {
|
||||
/// concatenate_string(&str1, &str2)
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Example with `Result<T, E>` return type
|
||||
/// ```
|
||||
/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME
|
||||
/// #[derive(thiserror::Error, Debug)]
|
||||
/// pub enum IntegerDivisionError {
|
||||
/// #[error("Divided by zero")]
|
||||
/// DividedByZero,
|
||||
/// #[error("Not divisible with remainder = {0}")]
|
||||
/// NotDivisible(i64),
|
||||
/// }
|
||||
///
|
||||
/// #[macro_rs::napi]
|
||||
/// pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
|
||||
/// match divisor {
|
||||
/// 0 => Err(IntegerDivisionError::DividedByZero),
|
||||
/// _ => match dividend % divisor {
|
||||
/// 0 => Ok(dividend / divisor),
|
||||
/// remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
||||
/// },
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// generates
|
||||
///
|
||||
/// ```
|
||||
/// # mod napi_derive { pub use macro_rs::dummy_macro as napi; } // FIXME
|
||||
/// # #[derive(thiserror::Error, Debug)]
|
||||
/// # pub enum IntegerDivisionError {
|
||||
/// # #[error("Divided by zero")]
|
||||
/// # DividedByZero,
|
||||
/// # #[error("Not divisible with remainder = {0}")]
|
||||
/// # NotDivisible(i64),
|
||||
/// # }
|
||||
/// # pub fn integer_divide(dividend: i64, divisor: i64) -> Result<i64, IntegerDivisionError> {
|
||||
/// # match divisor {
|
||||
/// # 0 => Err(IntegerDivisionError::DividedByZero),
|
||||
/// # _ => match dividend % divisor {
|
||||
/// # 0 => Ok(dividend / divisor),
|
||||
/// # remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
||||
/// # },
|
||||
/// # }
|
||||
/// # }
|
||||
/// #[napi_derive::napi(js_name = "integerDivide")]
|
||||
/// pub fn integer_divide_napi(dividend: i64, divisor: i64) -> napi::Result<i64> {
|
||||
/// integer_divide(dividend, divisor).map_err(|err| napi::Error::from_reason(err.to_string()))
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// TODO: macro attributes are ignored
|
||||
#[proc_macro_attribute]
|
||||
pub fn napi(
|
||||
|
@ -71,10 +141,41 @@ fn napi_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||
let item_fn_attrs = &item_fn.attrs;
|
||||
let item_fn_vis = &item_fn.vis;
|
||||
let mut item_fn_sig = item_fn.sig.clone();
|
||||
let mut function_call_modifiers = Vec::<TokenStream>::new();
|
||||
|
||||
// append "_napi" to function name
|
||||
item_fn_sig.ident = syn::parse_str(&format!("{}_napi", &ident)).unwrap();
|
||||
|
||||
// convert return type `...::Result<T, ...>` to `napi::Result<T>`
|
||||
if let syn::ReturnType::Type(_, ref mut return_type) = item_fn_sig.output {
|
||||
if let Some(result_generic_type) = (|| {
|
||||
let syn::Type::Path(return_type_path) = &**return_type else {
|
||||
return None;
|
||||
};
|
||||
// match a::b::c::Result
|
||||
let last_segment = return_type_path.path.segments.last()?;
|
||||
if last_segment.ident != "Result" {
|
||||
return None;
|
||||
};
|
||||
// extract <T, ...> from Result<T, ...>
|
||||
let syn::PathArguments::AngleBracketed(generic_arguments) = &last_segment.arguments
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
// return T only
|
||||
generic_arguments.args.first()
|
||||
})() {
|
||||
// modify return type
|
||||
*return_type = syn::parse_quote! {
|
||||
napi::Result<#result_generic_type>
|
||||
};
|
||||
// add modifier to function call result
|
||||
function_call_modifiers.push(quote! {
|
||||
.map_err(|err| napi::Error::from_reason(err.to_string()))
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// arguments in function call
|
||||
let called_args: Vec<TokenStream> = item_fn_sig
|
||||
.inputs
|
||||
|
@ -138,11 +239,11 @@ fn napi_impl(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||
quote! {
|
||||
#item_fn
|
||||
|
||||
#[cfg(feature = "napi")]
|
||||
#[napi_derive::napi(js_name = #js_name)]
|
||||
#(#item_fn_attrs)*
|
||||
#item_fn_vis #item_fn_sig {
|
||||
#ident(#(#called_args),*)
|
||||
#(#function_call_modifiers)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +271,6 @@ mod tests {
|
|||
}
|
||||
},
|
||||
quote! {
|
||||
#[cfg(feature = "napi")]
|
||||
#[napi_derive::napi(js_name = "addOne")]
|
||||
pub fn add_one_napi(x: i32) -> i32 {
|
||||
add_one(x)
|
||||
|
@ -188,7 +288,6 @@ mod tests {
|
|||
}
|
||||
},
|
||||
quote! {
|
||||
#[cfg(feature = "napi")]
|
||||
#[napi_derive::napi(js_name = "concatenateString")]
|
||||
pub fn concatenate_string_napi(str1: String, str2: String) -> String {
|
||||
concatenate_string(&str1, &str2)
|
||||
|
@ -210,7 +309,6 @@ mod tests {
|
|||
}
|
||||
},
|
||||
quote! {
|
||||
#[cfg(feature = "napi")]
|
||||
#[napi_derive::napi(js_name = "appendStringAndClone")]
|
||||
pub fn append_string_and_clone_napi(
|
||||
mut base_str: String,
|
||||
|
@ -221,4 +319,34 @@ mod tests {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_return_type() {
|
||||
test_macro!(
|
||||
quote! {
|
||||
pub fn integer_divide(
|
||||
dividend: i64,
|
||||
divisor: i64,
|
||||
) -> Result<i64, IntegerDivisionError> {
|
||||
match divisor {
|
||||
0 => Err(IntegerDivisionError::DividedByZero),
|
||||
_ => match dividend % divisor {
|
||||
0 => Ok(dividend / divisor),
|
||||
remainder => Err(IntegerDivisionError::NotDivisible(remainder)),
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
quote! {
|
||||
#[napi_derive::napi(js_name = "integerDivide")]
|
||||
pub fn integer_divide_napi(
|
||||
dividend: i64,
|
||||
divisor: i64,
|
||||
) -> napi::Result<i64> {
|
||||
integer_divide(dividend, divisor)
|
||||
.map_err(|err| napi::Error::from_reason(err.to_string()))
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue