introduce try_from_ref argument attribute

and document attributes in the #[export] macro documentation

Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
This commit is contained in:
Wolfgang Bumiller 2020-11-27 15:30:07 +01:00
parent 63af6eebc9
commit 7de9fdf07b
3 changed files with 72 additions and 15 deletions

View File

@ -13,6 +13,36 @@ pub struct XSub {
pub tokens: TokenStream,
}
#[derive(Default)]
struct ArgumentAttrs {
raw: bool,
try_from_ref: bool,
}
impl ArgumentAttrs {
fn handle_path(&mut self, path: &syn::Path) -> bool {
if path.is_ident("raw") {
self.raw = true;
} else if path.is_ident("try_from_ref") {
self.try_from_ref = true;
} else {
return false;
}
true
}
fn validate(&self, span: Span) -> Result<(), Error> {
if self.raw && self.try_from_ref {
bail!(
span,
"`raw` and `try_from_ref` attributes are mutually exclusive"
);
}
Ok(())
}
}
pub fn handle_function(
attr: FunctionAttrs,
mut func: syn::ItemFn,
@ -37,19 +67,15 @@ pub fn handle_function(
let mut deserialized_arguments = TokenStream::new();
let mut passed_arguments = TokenStream::new();
for arg in &mut func.sig.inputs {
let mut raw_arg = false;
let mut argument_attrs = ArgumentAttrs::default();
let pat_ty = match arg {
syn::FnArg::Receiver(_) => bail!(arg => "cannot export self-taking methods as xsubs"),
syn::FnArg::Typed(ref mut pt) => {
pt.attrs.retain(|attr| {
if attr.path.is_ident("raw") {
raw_arg = true;
false
} else {
true
}
});
pt.attrs
.retain(|attr| !argument_attrs.handle_path(&attr.path));
use syn::spanned::Spanned;
argument_attrs.validate(pt.span())?;
&*pt
}
};
@ -86,10 +112,22 @@ pub fn handle_function(
};
});
if raw_arg {
if argument_attrs.raw {
deserialized_arguments.extend(quote! {
let #deserialized_name = #extracted_name;
});
} else if argument_attrs.try_from_ref {
deserialized_arguments.extend(quote! {
let #deserialized_name: #arg_type =
match ::std::convert::TryFrom::try_from(&#extracted_name) {
Ok(arg) => arg,
Err(err) => {
return Err(::perlmod::Value::new_string(&err.to_string())
.into_mortal()
.into_raw());
}
};
});
} else {
deserialized_arguments.extend(quote! {
let #deserialized_name: #arg_type =

View File

@ -42,4 +42,23 @@ pub use value::Value;
#[cfg(feature = "exporter")]
#[doc(inline)]
pub use perlmod_macro::{export, package};
pub use perlmod_macro::package;
#[cfg(feature = "exporter")]
#[doc(inline)]
/// Attribute to export a function so that it can be installed as an `xsub` in perl. See the
/// [`package!`](macro@package) macro for a usage example.
///
/// This macro can optionally take a `raw_return` argument specifying that the return type, which
/// must be a [`Value`], will be returned as is, and not go through serialization.
///
/// Additionally, function parameters can also use the following attributes:
///
/// * `#[raw]` with a parameter of type [`Value`]: The parameter will be passed as
/// is and not go through deserialization.
/// * `#[try_from_ref]`: Instead of regular deserialization, `TryFrom::try_from(&Value)` will be
/// used.
///
/// Implementing the `TryFrom` trait accordingly can make using blessed references more
/// convenient, but at the cost of hiding underlying `unsafe` code.
pub use perlmod_macro::export;

View File

@ -232,7 +232,7 @@ impl Value {
///
/// This is mainly a helper to be used for blessed values. This only checks that the value
/// itself is any kind of reference, then assumes it contains something resembling a pointer
/// (see [`Value::pv_raw`]), and if so, simply casts it to `T`.
/// (see [`ScalarRef::pv_raw`](ScalarRef::pv_raw())), and if so, simply casts it to `T`.
pub unsafe fn from_ref_box<T>(&self) -> Result<&T, Error> {
let ptr = self
.dereference()
@ -246,9 +246,9 @@ impl Value {
///
/// # Safety
///
/// See [`Value::from_ref_box`]. This additionally uses [`Value::reftype`] to check that the
/// passed value was indeed blessed into the provided `package` name. Other than that, it
/// cannot verify the the contained pointer is truly a `T`.
/// See [`Value::from_ref_box`]. This additionally uses [`reftype`](ScalarRef::reftype) to
/// check that the passed value was indeed blessed into the provided `package` name. Other than
/// that, it cannot verify the the contained pointer is truly a `T`.
pub unsafe fn from_blessed_box<'a, T>(&'a self, package: &'_ str) -> Result<&'a T, Error> {
let ptr = self
.dereference()