use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
+use syn::spanned::Spanned;
use syn::Error;
use crate::attribs::FunctionAttrs;
#[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,
}
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;
}
}
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(
}
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();
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
}
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(),
);
}
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,
+ },
},
};
&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(),
);
&impl_xs_name,
passed_arguments,
export_public,
+ !cv_arg_param.is_empty(),
)?;
let tokens = quote! {
#[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();
.into_raw());
}
- drop(args);
+ //drop(args);
#deserialized_arguments
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(())
};
}
- 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),
}
}
};
}
- 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 {
};
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),
}
}
};
}
- Return::Tuple(result, count) => {
+ ReturnValue::Tuple(count) => {
return_type = {
let mut rt = TokenStream::new();
for _ in 0..count {
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);
};
}
//}
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),
}
/// 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...