]> git.proxmox.com Git - perlmod.git/blobdiff - perlmod-macro/src/function.rs
add ability to set the errno value
[perlmod.git] / perlmod-macro / src / function.rs
index 14372f77904fa04e9331d74d00e7bee02f49926b..52aec1f1a6e97eb90db8b571890aeb428821abeb 100644 (file)
@@ -1,6 +1,7 @@
 use proc_macro2::{Ident, Span, TokenStream};
 
 use quote::quote;
+use syn::spanned::Spanned;
 use syn::Error;
 
 use crate::attribs::FunctionAttrs;
@@ -15,7 +16,13 @@ pub struct XSub {
 
 #[derive(Default)]
 struct ArgumentAttrs {
+    /// This is the `CV` pointer.
+    cv: Option<Span>,
+
+    /// Skip the deserializer for this argument.
     raw: bool,
+
+    /// Call `TryFrom<&Value>::try_from` for this argument instead of deserializing it.
     try_from_ref: bool,
 }
 
@@ -25,6 +32,8 @@ impl ArgumentAttrs {
             self.raw = true;
         } else if path.is_ident("try_from_ref") {
             self.try_from_ref = true;
+        } else if path.is_ident("cv") {
+            self.cv = Some(path.span());
         } else {
             return false;
         }
@@ -33,25 +42,30 @@ impl ArgumentAttrs {
     }
 
     fn validate(&self, span: Span) -> Result<(), Error> {
-        if self.raw && self.try_from_ref {
+        if self.raw as usize + self.try_from_ref as usize + self.cv.is_some() as usize > 1 {
             bail!(
                 span,
-                "`raw` and `try_from_ref` attributes are mutually exclusive"
+                "`raw` and `try_from_ref` and `cv` attributes are mutually exclusive"
             );
         }
         Ok(())
     }
 }
 
-enum Return {
+struct Return {
+    result: bool,
+    value: ReturnValue,
+}
+
+enum ReturnValue {
     /// Return nothing. (This is different from returning an implicit undef!)
-    None(bool),
+    None,
 
     /// Return a single element.
-    Single(bool),
+    Single,
 
     /// We support tuple return types. They act like "list" return types in perl.
-    Tuple(bool, usize),
+    Tuple(usize),
 }
 
 pub fn handle_function(
@@ -69,19 +83,21 @@ pub fn handle_function(
     }
 
     let name = func.sig.ident.clone();
+    let export_public = export_public.then_some(&func.vis);
     let xs_name = attr
         .xs_name
         .clone()
         .unwrap_or_else(|| match mangled_package_name {
-            None => Ident::new(&format!("xs_{}", name), name.span()),
-            Some(prefix) => Ident::new(&format!("xs_{}_{}", prefix, name), name.span()),
+            None => Ident::new(&format!("xs_{name}"), name.span()),
+            Some(prefix) => Ident::new(&format!("xs_{prefix}_{name}"), name.span()),
         });
-    let impl_xs_name = Ident::new(&format!("impl_xs_{}", name), name.span());
+    let impl_xs_name = Ident::new(&format!("impl_xs_{name}"), name.span());
 
     let mut trailing_options = 0;
     let mut extract_arguments = TokenStream::new();
     let mut deserialized_arguments = TokenStream::new();
     let mut passed_arguments = TokenStream::new();
+    let mut cv_arg_param = TokenStream::new();
     for arg in &mut func.sig.inputs {
         let mut argument_attrs = ArgumentAttrs::default();
 
@@ -90,7 +106,6 @@ pub fn handle_function(
             syn::FnArg::Typed(ref mut pt) => {
                 pt.attrs
                     .retain(|attr| !argument_attrs.handle_path(&attr.path));
-                use syn::spanned::Spanned;
                 argument_attrs.validate(pt.span())?;
                 &*pt
             }
@@ -111,12 +126,25 @@ pub fn handle_function(
 
         let arg_type = &*pat_ty.ty;
 
-        let extracted_name = Ident::new(&format!("extracted_arg_{}", arg_name), arg_name.span());
+        if let Some(cv_span) = argument_attrs.cv {
+            if !cv_arg_param.is_empty() {
+                bail!(cv_span, "only 1 'cv' parameter allowed");
+            }
+            cv_arg_param = quote! { #arg_name: #arg_type };
+            if passed_arguments.is_empty() {
+                passed_arguments.extend(quote! { #arg_name });
+            } else {
+                passed_arguments.extend(quote! {, #arg_name });
+            }
+            continue;
+        }
+
+        let extracted_name = Ident::new(&format!("extracted_arg_{arg_name}"), arg_name.span());
         let deserialized_name =
-            Ident::new(&format!("deserialized_arg_{}", arg_name), arg_name.span());
+            Ident::new(&format!("deserialized_arg_{arg_name}"), arg_name.span());
 
         let missing_message = syn::LitStr::new(
-            &format!("missing required parameter: '{}'\n", arg_name),
+            &format!("missing required parameter: '{arg_name}'\n"),
             arg_name.span(),
         );
 
@@ -180,11 +208,23 @@ pub fn handle_function(
     }
 
     let has_return_value = match &func.sig.output {
-        syn::ReturnType::Default => Return::None(false),
-        syn::ReturnType::Type(_arrow, ty) => match get_result_type(&**ty) {
-            (syn::Type::Tuple(tuple), result) if tuple.elems.is_empty() => Return::None(result),
-            (syn::Type::Tuple(tuple), result) => Return::Tuple(result, tuple.elems.len()),
-            (_, result) => Return::Single(result),
+        syn::ReturnType::Default => Return {
+            result: false,
+            value: ReturnValue::None,
+        },
+        syn::ReturnType::Type(_arrow, ty) => match get_result_type(ty) {
+            (syn::Type::Tuple(tuple), result) if tuple.elems.is_empty() => Return {
+                result,
+                value: ReturnValue::None,
+            },
+            (syn::Type::Tuple(tuple), result) => Return {
+                result,
+                value: ReturnValue::Tuple(tuple.elems.len()),
+            },
+            (_, result) => Return {
+                result,
+                value: ReturnValue::Single,
+            },
         },
     };
 
@@ -192,7 +232,7 @@ pub fn handle_function(
         &format!(
             "too many parameters for function '{}', (expected {})\n",
             name,
-            func.sig.inputs.len()
+            func.sig.inputs.len() - (!cv_arg_param.is_empty()) as usize
         ),
         Span::call_site(),
     );
@@ -209,6 +249,7 @@ pub fn handle_function(
         &impl_xs_name,
         passed_arguments,
         export_public,
+        !cv_arg_param.is_empty(),
     )?;
 
     let tokens = quote! {
@@ -218,7 +259,7 @@ pub fn handle_function(
 
         #[inline(never)]
         #[allow(non_snake_case)]
-        fn #impl_xs_name() -> Result<#return_type, *mut ::perlmod::ffi::SV> {
+        fn #impl_xs_name(#cv_arg_param) -> Result<#return_type, *mut ::perlmod::ffi::SV> {
             let argmark = unsafe { ::perlmod::ffi::pop_arg_mark() };
             let mut args = argmark.iter();
 
@@ -230,7 +271,7 @@ pub fn handle_function(
                     .into_raw());
             }
 
-            drop(args);
+            //drop(args);
 
             #deserialized_arguments
 
@@ -283,30 +324,70 @@ fn handle_return_kind(
     xs_name: &Ident,
     impl_xs_name: &Ident,
     passed_arguments: TokenStream,
-    export_public: bool,
+    export_public: Option<&syn::Visibility>,
+    cv_arg: bool,
 ) -> Result<ReturnHandling, Error> {
     let return_type;
     let mut handle_return;
     let wrapper_func;
 
+    let vis = match export_public {
+        Some(vis) => quote! { #[no_mangle] #vis },
+        None => quote! { #[allow(non_snake_case)] },
+    };
+
+    let (cv_arg_name, cv_arg_passed) = if cv_arg {
+        (
+            quote! { cv },
+            quote! { ::perlmod::Value::from_raw_ref(cv as *mut ::perlmod::ffi::SV) },
+        )
+    } else {
+        (quote! { _cv }, TokenStream::new())
+    };
+
+    let return_error = if ret.result {
+        if attr.serialize_error {
+            quote! {
+                match ::perlmod::to_value(&err) {
+                    Ok(err) => return Err(err.into_mortal().into_raw()),
+                    Err(err) => {
+                        return Err(::perlmod::Value::new_string(&format!("{}\n", err))
+                            .into_mortal()
+                            .into_raw());
+                    }
+                }
+            }
+        } else {
+            quote! {
+                return Err(::perlmod::Value::new_string(&format!("{}\n", err))
+                    .into_mortal()
+                    .into_raw());
+            }
+        }
+    } else {
+        TokenStream::new()
+    };
+
+    let copy_errno = if attr.errno {
+        quote! { ::perlmod::error::copy_errno_to_libc(); }
+    } else {
+        TokenStream::new()
+    };
+
     let pthx = crate::pthx_param();
-    match ret {
-        Return::None(result) => {
+    match ret.value {
+        ReturnValue::None => {
             return_type = quote! { () };
 
             if attr.raw_return {
                 bail!(&attr.raw_return => "raw_return attribute is illegal without a return value");
             }
 
-            if result {
+            if ret.result {
                 handle_return = quote! {
                     match #name(#passed_arguments) {
                         Ok(()) => (),
-                        Err(err) => {
-                            return Err(::perlmod::Value::new_string(&format!("{}\n", err))
-                                .into_mortal()
-                                .into_raw());
-                        }
+                        Err(err) => { #return_error }
                     }
 
                     Ok(())
@@ -319,17 +400,13 @@ fn handle_return_kind(
                 };
             }
 
-            let vis = if export_public {
-                quote! { #[no_mangle] pub }
-            } else {
-                quote! { #[allow(non_snake_case)] }
-            };
-
             wrapper_func = quote! {
                 #[doc(hidden)]
-                #vis extern "C" fn #xs_name(#pthx _cv: &::perlmod::ffi::CV) {
+                #vis extern "C" fn #xs_name(#pthx #cv_arg_name: *mut ::perlmod::ffi::CV) {
                     unsafe {
-                        match #impl_xs_name() {
+                        let res = #impl_xs_name(#cv_arg_passed);
+                        #copy_errno
+                        match res {
                             Ok(()) => (),
                             Err(sv) => ::perlmod::ffi::croak(sv),
                         }
@@ -337,18 +414,14 @@ fn handle_return_kind(
                 }
             };
         }
-        Return::Single(result) => {
+        ReturnValue::Single => {
             return_type = quote! { *mut ::perlmod::ffi::SV };
 
-            if result {
+            if ret.result {
                 handle_return = quote! {
                     let result = match #name(#passed_arguments) {
                         Ok(output) => output,
-                        Err(err) => {
-                            return Err(::perlmod::Value::new_string(&format!("{}\n", err))
-                                .into_mortal()
-                                .into_raw());
-                        }
+                        Err(err) => { #return_error }
                     };
                 };
             } else {
@@ -373,11 +446,12 @@ fn handle_return_kind(
             };
 
             wrapper_func = quote! {
-                #[no_mangle]
                 #[doc(hidden)]
-                pub extern "C" fn #xs_name(#pthx _cv: &::perlmod::ffi::CV) {
+                #vis extern "C" fn #xs_name(#pthx #cv_arg_name: *mut ::perlmod::ffi::CV) {
                     unsafe {
-                        match #impl_xs_name() {
+                        let res = #impl_xs_name(#cv_arg_passed);
+                        #copy_errno
+                        match res {
                             Ok(sv) => ::perlmod::ffi::stack_push_raw(sv),
                             Err(sv) => ::perlmod::ffi::croak(sv),
                         }
@@ -385,7 +459,7 @@ fn handle_return_kind(
                 }
             };
         }
-        Return::Tuple(result, count) => {
+        ReturnValue::Tuple(count) => {
             return_type = {
                 let mut rt = TokenStream::new();
                 for _ in 0..count {
@@ -394,20 +468,16 @@ fn handle_return_kind(
                 quote! { (#rt) }
             };
 
-            if result {
+            if ret.result {
                 handle_return = quote! {
                     let result = match #name(#passed_arguments) {
                         Ok(output) => output,
-                        Err(err) => {
-                            return Err(::perlmod::Value::new_string(&format!("{}\n", err))
-                                .into_mortal()
-                                .into_raw());
-                        }
+                        Err(err) => { #return_error }
                     };
                 };
             } else {
                 handle_return = quote! {
-                    let result = match #name(#passed_arguments);
+                    let result = #name(#passed_arguments);
                 };
             }
 
@@ -460,11 +530,12 @@ fn handle_return_kind(
             //}
 
             wrapper_func = quote! {
-                #[no_mangle]
                 #[doc(hidden)]
-                pub extern "C" fn #xs_name(#pthx _cv: &::perlmod::ffi::CV) {
+                #vis extern "C" fn #xs_name(#pthx #cv_arg_name: *mut ::perlmod::ffi::CV) {
                     unsafe {
-                        match #impl_xs_name() {
+                        let res = #impl_xs_name(#cv_arg_passed);
+                        #copy_errno
+                        match res {
                             Ok(sv) => { #push },
                             Err(sv) => ::perlmod::ffi::croak(sv),
                         }
@@ -522,7 +593,7 @@ pub fn get_result_type(ty: &syn::Type) -> (&syn::Type, bool) {
 
 /// Get a non-suffixed integer from an usize.
 fn simple_usize(i: usize, span: Span) -> syn::LitInt {
-    syn::LitInt::new(&format!("{}", i), span)
+    syn::LitInt::new(&format!("{i}"), span)
 }
 
 /// Note that we cannot handle renamed imports at all here...