]> git.proxmox.com Git - perlmod.git/commitdiff
macro: support tuple return types in functions
authorWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 16 Feb 2021 09:39:36 +0000 (10:39 +0100)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Tue, 16 Feb 2021 09:39:37 +0000 (10:39 +0100)
These are now *list* return types in perl.
However, currently there is no support for 'wantarray' or
lists of dynamic length.

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
perlmod-macro/src/function.rs
perlmod-test/src/bless.rs

index 34d5e83046f2c63a5fbd89100a81d69bec36aedb..7e6d8e11108446264b558fad3fddfe73ca243244 100644 (file)
@@ -43,6 +43,17 @@ impl ArgumentAttrs {
     }
 }
 
+enum Return {
+    /// Return nothing. (This is different from returning an implicit undef!)
+    None(bool),
+
+    /// Return a single element.
+    Single(bool),
+
+    /// We support tuple return types. They act like "list" return types in perl.
+    Tuple(bool, usize),
+}
+
 pub fn handle_function(
     attr: FunctionAttrs,
     mut func: syn::ItemFn,
@@ -57,10 +68,13 @@ pub fn handle_function(
     }
 
     let name = func.sig.ident.clone();
-    let xs_name = attr.xs_name.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()),
-    });
+    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()),
+        });
     let impl_xs_name = Ident::new(&format!("impl_xs_{}", name), name.span());
 
     let mut extract_arguments = TokenStream::new();
@@ -150,10 +164,11 @@ pub fn handle_function(
     }
 
     let has_return_value = match &func.sig.output {
-        syn::ReturnType::Default => false,
-        syn::ReturnType::Type(_arrow, ty) => match &**ty {
-            syn::Type::Tuple(tuple) => !tuple.elems.is_empty(),
-            _ => true,
+        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),
         },
     };
 
@@ -166,76 +181,18 @@ pub fn handle_function(
         Span::call_site(),
     );
 
-    let mut handle_return;
-    let return_type;
-    let wrapper_func;
-    if has_return_value {
-        return_type = quote! { *mut ::perlmod::ffi::SV };
-
-        handle_return = quote! {
-            let result = match #name(#passed_arguments) {
-                Ok(output) => output,
-                Err(err) => {
-                    return Err(::perlmod::Value::new_string(&err.to_string())
-                        .into_mortal()
-                        .into_raw());
-                }
-            };
-        };
-
-        if attr.raw_return {
-            handle_return.extend(quote! {
-                Ok(result.into_mortal().into_raw())
-            });
-        } else {
-            handle_return.extend(quote! {
-                match ::perlmod::to_value(&result) {
-                    Ok(value) => Ok(value.into_mortal().into_raw()),
-                    Err(err) => Err(::perlmod::Value::new_string(&err.to_string())
-                        .into_mortal()
-                        .into_raw()),
-                }
-            });
-        };
-
-        wrapper_func = quote! {
-            #[no_mangle]
-            #[doc(hidden)]
-            pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
-                unsafe {
-                    match #impl_xs_name(cv) {
-                        Ok(sv) => ::perlmod::ffi::stack_push_raw(sv),
-                        Err(sv) => ::perlmod::ffi::croak(sv),
-                    }
-                }
-            }
-        };
-    } else {
-        return_type = quote! { () };
-
-        if attr.raw_return {
-            bail!(&attr.raw_return => "raw_return attribute is illegal without a return value");
-        }
-
-        handle_return = quote! {
-            #name(#passed_arguments);
-
-            Ok(())
-        };
-
-        wrapper_func = quote! {
-            #[no_mangle]
-            #[doc(hidden)]
-            pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
-                unsafe {
-                    match #impl_xs_name(cv) {
-                        Ok(()) => (),
-                        Err(sv) => ::perlmod::ffi::croak(sv),
-                    }
-                }
-            }
-        };
-    }
+    let ReturnHandling {
+        return_type,
+        handle_return,
+        wrapper_func,
+    } = handle_return_kind(
+        &attr,
+        has_return_value,
+        &name,
+        &xs_name,
+        &impl_xs_name,
+        passed_arguments,
+    )?;
 
     let tokens = quote! {
         #func
@@ -277,3 +234,253 @@ pub fn handle_function(
         tokens,
     })
 }
+
+struct ReturnHandling {
+    return_type: TokenStream,
+    handle_return: TokenStream,
+    wrapper_func: TokenStream,
+}
+
+fn handle_return_kind(
+    attr: &FunctionAttrs,
+    ret: Return,
+    name: &Ident,
+    xs_name: &Ident,
+    impl_xs_name: &Ident,
+    passed_arguments: TokenStream,
+) -> Result<ReturnHandling, Error> {
+    let return_type;
+    let mut handle_return;
+    let wrapper_func;
+
+    match ret {
+        Return::None(result) => {
+            return_type = quote! { () };
+
+            if attr.raw_return {
+                bail!(&attr.raw_return => "raw_return attribute is illegal without a return value");
+            }
+
+            if result {
+                handle_return = quote! {
+                    match #name(#passed_arguments) {
+                        Ok(()) => (),
+                        Err(err) => {
+                            return Err(::perlmod::Value::new_string(&err.to_string())
+                                .into_mortal()
+                                .into_raw());
+                        }
+                    }
+
+                    Ok(())
+                };
+            } else {
+                handle_return = quote! {
+                    #name(#passed_arguments);
+
+                    Ok(())
+                };
+            }
+
+            wrapper_func = quote! {
+                #[no_mangle]
+                #[doc(hidden)]
+                pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
+                    unsafe {
+                        match #impl_xs_name(cv) {
+                            Ok(()) => (),
+                            Err(sv) => ::perlmod::ffi::croak(sv),
+                        }
+                    }
+                }
+            };
+        }
+        Return::Single(result) => {
+            return_type = quote! { *mut ::perlmod::ffi::SV };
+
+            if result {
+                handle_return = quote! {
+                    let result = match #name(#passed_arguments) {
+                        Ok(output) => output,
+                        Err(err) => {
+                            return Err(::perlmod::Value::new_string(&err.to_string())
+                                .into_mortal()
+                                .into_raw());
+                        }
+                    };
+                };
+            } else {
+                handle_return = quote! {
+                    let result = #name(#passed_arguments);
+                };
+            }
+
+            if attr.raw_return {
+                handle_return.extend(quote! {
+                    Ok(result.into_mortal().into_raw())
+                });
+            } else {
+                handle_return.extend(quote! {
+                    match ::perlmod::to_value(&result) {
+                        Ok(value) => Ok(value.into_mortal().into_raw()),
+                        Err(err) => Err(::perlmod::Value::new_string(&err.to_string())
+                            .into_mortal()
+                            .into_raw()),
+                    }
+                });
+            };
+
+            wrapper_func = quote! {
+                #[no_mangle]
+                #[doc(hidden)]
+                pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
+                    unsafe {
+                        match #impl_xs_name(cv) {
+                            Ok(sv) => ::perlmod::ffi::stack_push_raw(sv),
+                            Err(sv) => ::perlmod::ffi::croak(sv),
+                        }
+                    }
+                }
+            };
+        }
+        Return::Tuple(result, count) => {
+            return_type = {
+                let mut rt = TokenStream::new();
+                for _ in 0..count {
+                    rt.extend(quote! { *mut ::perlmod::ffi::SV, });
+                }
+                quote! { (#rt) }
+            };
+
+            if result {
+                handle_return = quote! {
+                    let result = match #name(#passed_arguments) {
+                        Ok(output) => output,
+                        Err(err) => {
+                            return Err(::perlmod::Value::new_string(&err.to_string())
+                                .into_mortal()
+                                .into_raw());
+                        }
+                    };
+                };
+            } else {
+                handle_return = quote! {
+                    let result = match #name(#passed_arguments);
+                };
+            }
+
+            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! {
+                        match ::perlmod::to_value(&result.#i) {
+                            Ok(value) => value.into_mortal().into_raw(),
+                            Err(err) => return Err(::perlmod::Value::new_string(&err.to_string())
+                                .into_mortal()
+                                .into_raw()),
+                        },
+                    });
+                }
+                handle_return.extend(quote! {
+                    Ok((#rt))
+                });
+            }
+
+            let icount = simple_usize(count, Span::call_site());
+            let sp_offset = simple_usize(count - 1, Span::call_site());
+            let mut push = quote! {
+                ::perlmod::ffi::RSPL_stack_resize_by(#icount);
+                let mut sp = ::perlmod::ffi::RSPL_stack_sp().sub(#sp_offset);
+                *sp = sv.0;
+            };
+
+            for i in 1..count {
+                let i = simple_usize(i, Span::call_site());
+                push.extend(quote! {
+                    sp = sp.add(1);
+                    *sp = sv.#i;
+                });
+            }
+            //let mut push = TokenStream::new();
+            //for i in 0..count {
+            //    let i = simple_usize(i, Span::call_site());
+            //    push.extend(quote! {
+            //        ::perlmod::ffi::stack_push_raw(sv.#i);
+            //    });
+            //}
+
+            wrapper_func = quote! {
+                #[no_mangle]
+                #[doc(hidden)]
+                pub extern "C" fn #xs_name(cv: &::perlmod::ffi::CV) {
+                    unsafe {
+                        match #impl_xs_name(cv) {
+                            Ok(sv) => { #push },
+                            Err(sv) => ::perlmod::ffi::croak(sv),
+                        }
+                    }
+                }
+            };
+        }
+    }
+
+    Ok(ReturnHandling {
+        return_type,
+        handle_return,
+        wrapper_func,
+    })
+}
+
+/// Note that we cannot handle renamed imports at all here...
+pub fn is_result_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_result = match segs.len() {
+            1 => segs.last().unwrap().ident == "Result",
+            2 => segs.first().unwrap().ident == "std" && segs.last().unwrap().ident == "Result",
+            _ => false,
+        };
+        if !is_result {
+            return None;
+        }
+
+        if let syn::PathArguments::AngleBracketed(generic) = &segs.last().unwrap().arguments {
+            // We allow aliased Result types with an implicit Error:
+            if generic.args.len() != 1 && generic.args.len() != 2 {
+                return None;
+            }
+
+            if let syn::GenericArgument::Type(ty) = generic.args.first().unwrap() {
+                return Some(ty);
+            }
+        }
+    }
+    None
+}
+
+/// If the type is a Result type, return the contained Ok type, otherwise return the type itself.
+/// Also return whether or not it actually was a Result.
+pub fn get_result_type(ty: &syn::Type) -> (&syn::Type, bool) {
+    match is_result_type(ty) {
+        Some(ty) => (ty, true),
+        None => (ty, false),
+    }
+}
+
+/// Get a non-suffixed integer from an usize.
+fn simple_usize(i: usize, span: Span) -> syn::LitInt {
+    syn::LitInt::new(&format!("{}", i), span)
+}
index 310393cdd1af9d7f0dd9001ce0d0cf5e43e4e59d..0a5ca30371309cff5c566f2b2941214eb4eb36d3 100644 (file)
@@ -45,6 +45,11 @@ mod export {
         perlmod::destructor!(this, Bless: CLASSNAME);
     }
 
+    #[export]
+    fn multi_return(#[raw] _this: Value) -> Result<(u32, u32), std::convert::Infallible> {
+        Ok((17, 32))
+    }
+
     impl<'a> TryFrom<&'a Value> for &'a Bless {
         type Error = Error;