From c231bf43cbc1a6d6f9cae85cbe42a72bfd874b27 Mon Sep 17 00:00:00 2001 From: sup39 Date: Fri, 12 Apr 2024 04:28:51 +0900 Subject: [PATCH] feat (macro-rs): convert Result return type to napi::Result --- Cargo.lock | 2 + packages/macro-rs/Cargo.toml | 5 ++ packages/macro-rs/src/lib.rs | 148 ++++++++++++++++++++++++++++++++--- 3 files changed, 145 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c55a538f75..4d33664b5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1217,9 +1217,11 @@ name = "macro_rs" version = "0.0.0" dependencies = [ "convert_case", + "napi", "proc-macro2", "quote", "syn 2.0.58", + "thiserror", ] [[package]] diff --git a/packages/macro-rs/Cargo.toml b/packages/macro-rs/Cargo.toml index c165bc00ac..25fa8e74bf 100644 --- a/packages/macro-rs/Cargo.toml +++ b/packages/macro-rs/Cargo.toml @@ -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 } diff --git a/packages/macro-rs/src/lib.rs b/packages/macro-rs/src/lib.rs index 54dd378ec9..a0eb14f00f 100644 --- a/packages/macro-rs/src/lib.rs +++ b/packages/macro-rs/src/lib.rs @@ -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` and `Result` are converted to `napi::Result`. +/// 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` 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 { +/// 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 { +/// # 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 { +/// 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::::new(); // append "_napi" to function name item_fn_sig.ident = syn::parse_str(&format!("{}_napi", &ident)).unwrap(); + // convert return type `...::Result` to `napi::Result` + 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 from Result + 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 = 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 { + 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 { + integer_divide(dividend, divisor) + .map_err(|err| napi::Error::from_reason(err.to_string())) + } + } + ); + } }