]> git.proxmox.com Git - perlmod.git/blobdiff - perlmod-macro/src/function.rs
macro: allow optional option parameters
[perlmod.git] / perlmod-macro / src / function.rs
index 6e3bc4f5261c63c5607383c1974984cee6f9e27c..22f529706306e8c3d1bacc34d5083bac1c82ebd5 100644 (file)
@@ -1,8 +1,7 @@
-use anyhow::Error;
-
 use proc_macro2::{Ident, Span, TokenStream};
 
 use quote::quote;
+use syn::Error;
 
 use crate::attribs::FunctionAttrs;
 
@@ -58,6 +57,7 @@ pub fn handle_function(
     attr: FunctionAttrs,
     mut func: syn::ItemFn,
     mangled_package_name: Option<&str>,
+    export_public: bool,
 ) -> Result<XSub, Error> {
     if !func.sig.generics.params.is_empty() {
         bail!(&func.sig.generics => "generic functions cannot be exported as xsubs");
@@ -113,17 +113,27 @@ pub fn handle_function(
         let deserialized_name =
             Ident::new(&format!("deserialized_arg_{}", arg_name), arg_name.span());
 
-        let missing_message =
-            syn::LitStr::new("missing required parameter: '{}'\n", arg_name.span());
+        let missing_message = syn::LitStr::new(
+            &format!("missing required parameter: '{}'\n", arg_name),
+            arg_name.span(),
+        );
 
-        extract_arguments.extend(quote! {
-            let #extracted_name: ::perlmod::Value = match args.next() {
-                Some(arg) => ::perlmod::Value::from(arg),
-                None => {
+        let none_handling = if is_option_type(arg_type).is_some() {
+            quote! { ::perlmod::Value::new_undef(), }
+        } else {
+            quote! {
+                {
                     return Err(::perlmod::Value::new_string(#missing_message)
                         .into_mortal()
                         .into_raw());
                 }
+            }
+        };
+
+        extract_arguments.extend(quote! {
+            let #extracted_name: ::perlmod::Value = match args.next() {
+                Some(arg) => ::perlmod::Value::from(arg),
+                None => #none_handling
             };
         });
 
@@ -193,6 +203,7 @@ pub fn handle_function(
         &xs_name,
         &impl_xs_name,
         passed_arguments,
+        export_public,
     )?;
 
     let tokens = quote! {
@@ -202,9 +213,7 @@ pub fn handle_function(
 
         #[inline(never)]
         #[allow(non_snake_case)]
-        fn #impl_xs_name(
-            _cv: &::perlmod::ffi::CV,
-        ) -> Result<#return_type, *mut ::perlmod::ffi::SV> {
+        fn #impl_xs_name() -> Result<#return_type, *mut ::perlmod::ffi::SV> {
             let argmark = unsafe { ::perlmod::ffi::pop_arg_mark() };
             let mut args = argmark.iter();
 
@@ -249,11 +258,13 @@ fn handle_return_kind(
     xs_name: &Ident,
     impl_xs_name: &Ident,
     passed_arguments: TokenStream,
+    export_public: bool,
 ) -> Result<ReturnHandling, Error> {
     let return_type;
     let mut handle_return;
     let wrapper_func;
 
+    let pthx = crate::pthx_param();
     match ret {
         Return::None(result) => {
             return_type = quote! { () };
@@ -283,12 +294,17 @@ fn handle_return_kind(
                 };
             }
 
+            let vis = if export_public {
+                quote! { #[no_mangle] pub }
+            } else {
+                quote! { #[allow(non_snake_case)] }
+            };
+
             wrapper_func = quote! {
-                #[no_mangle]
                 #[doc(hidden)]
-                pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
+                #vis extern "C" fn #xs_name(#pthx _cv: &::perlmod::ffi::CV) {
                     unsafe {
-                        match #impl_xs_name(cv) {
+                        match #impl_xs_name() {
                             Ok(()) => (),
                             Err(sv) => ::perlmod::ffi::croak(sv),
                         }
@@ -334,9 +350,9 @@ fn handle_return_kind(
             wrapper_func = quote! {
                 #[no_mangle]
                 #[doc(hidden)]
-                pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
+                pub extern "C" fn #xs_name(#pthx _cv: &::perlmod::ffi::CV) {
                     unsafe {
-                        match #impl_xs_name(cv) {
+                        match #impl_xs_name() {
                             Ok(sv) => ::perlmod::ffi::stack_push_raw(sv),
                             Err(sv) => ::perlmod::ffi::croak(sv),
                         }
@@ -370,17 +386,13 @@ fn handle_return_kind(
                 };
             }
 
+            let mut rt = TokenStream::new();
             if attr.raw_return {
-                let mut rt = TokenStream::new();
                 for i in 0..count {
                     let i = simple_usize(i, Span::call_site());
                     rt.extend(quote! { (result.#i).into_mortal().into_raw(), });
                 }
-                handle_return.extend(quote! {
-                    Ok((#rt))
-                });
             } else {
-                let mut rt = TokenStream::new();
                 for i in 0..count {
                     let i = simple_usize(i, Span::call_site());
                     rt.extend(quote! {
@@ -393,10 +405,11 @@ fn handle_return_kind(
                         },
                     });
                 }
-                handle_return.extend(quote! {
-                    Ok((#rt))
-                });
             }
+            handle_return.extend(quote! {
+                Ok((#rt))
+            });
+            drop(rt);
 
             let icount = simple_usize(count, Span::call_site());
             let sp_offset = simple_usize(count - 1, Span::call_site());
@@ -424,9 +437,9 @@ fn handle_return_kind(
             wrapper_func = quote! {
                 #[no_mangle]
                 #[doc(hidden)]
-                pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
+                pub extern "C" fn #xs_name(#pthx _cv: &::perlmod::ffi::CV) {
                     unsafe {
-                        match #impl_xs_name(cv) {
+                        match #impl_xs_name() {
                             Ok(sv) => { #push },
                             Err(sv) => ::perlmod::ffi::croak(sv),
                         }
@@ -451,8 +464,8 @@ pub fn is_result_type(ty: &syn::Type) -> Option<&syn::Type> {
         }
         let segs = &p.path.segments;
         let is_result = match segs.len() {
-            1 => segs.last().unwrap().ident == "Result",
-            2 => segs.first().unwrap().ident == "std" && segs.last().unwrap().ident == "Result",
+            1 => segs[0].ident == "Result",
+            3 => segs[0].ident == "std" && segs[1].ident == "result" && segs[2].ident == "Result",
             _ => false,
         };
         if !is_result {
@@ -486,3 +499,32 @@ pub fn get_result_type(ty: &syn::Type) -> (&syn::Type, bool) {
 fn simple_usize(i: usize, span: Span) -> syn::LitInt {
     syn::LitInt::new(&format!("{}", i), span)
 }
+
+/// Note that we cannot handle renamed imports at all here...
+pub fn is_option_type(ty: &syn::Type) -> Option<&syn::Type> {
+    if let syn::Type::Path(p) = ty {
+        if p.qself.is_some() {
+            return None;
+        }
+        let segs = &p.path.segments;
+        let is_option = match segs.len() {
+            1 => segs[0].ident == "Option",
+            3 => segs[0].ident == "std" && segs[1].ident == "option" && segs[2].ident == "Option",
+            _ => false,
+        };
+        if !is_option {
+            return None;
+        }
+
+        if let syn::PathArguments::AngleBracketed(generic) = &segs.last().unwrap().arguments {
+            if generic.args.len() != 1 {
+                return None;
+            }
+
+            if let syn::GenericArgument::Type(ty) = generic.args.first().unwrap() {
+                return Some(ty);
+            }
+        }
+    }
+    None
+}