feat: allow skipping env/class in args

also removed a bit of clones but added more...
This commit is contained in:
əlemi 2024-09-21 23:03:02 +02:00
parent e855659e1a
commit 4daaad5faf
Signed by: alemi
GPG key ID: A4895B84D311642C
4 changed files with 130 additions and 37 deletions

View file

@ -1,12 +1,18 @@
use proc_macro2::{Span, TokenStream};
use quote::TokenStreamExt;
use syn::Ident;
pub(crate) struct ArgumentOptions {
pub(crate) incoming: TokenStream,
pub(crate) forwarding: TokenStream,
pub(crate) env: Ident,
}
fn unpack_pat(pat: syn::Pat) -> Result<TokenStream, syn::Error> {
match pat {
syn::Pat::Ident(i) => {
let ident = i.ident;
Ok(quote::quote!( #ident ,))
Ok(quote::quote!( #ident ))
},
syn::Pat::Reference(r) => {
unpack_pat(*r.pat)
@ -15,16 +21,110 @@ fn unpack_pat(pat: syn::Pat) -> Result<TokenStream, syn::Error> {
}
}
pub(crate) fn parse_args(fn_item: syn::ItemFn) -> Result<(TokenStream, TokenStream), syn::Error> {
let mut incoming = TokenStream::new();
let mut forwarding = TokenStream::new();
for arg in fn_item.sig.inputs {
fn type_equals(ty: Box<syn::Type>, search: impl AsRef<str>) -> bool {
match *ty {
syn::Type::Array(_) => false,
syn::Type::BareFn(_) => false,
syn::Type::ImplTrait(_) => false,
syn::Type::Infer(_) => false,
syn::Type::Macro(_) => false,
syn::Type::Never(_) => false,
syn::Type::Ptr(_) => false,
syn::Type::Slice(_) => false,
syn::Type::TraitObject(_) => false,
syn::Type::Tuple(_) => false,
syn::Type::Verbatim(_) => false,
syn::Type::Group(g) => type_equals(g.elem, search),
syn::Type::Paren(p) => type_equals(p.elem, search),
syn::Type::Reference(r) => type_equals(r.elem, search),
syn::Type::Path(ty) => {
ty.path.segments
.last()
.map_or(false, |e| e.ident == search.as_ref())
},
_ => false,
}
}
impl ArgumentOptions {
pub(crate) fn parse_args(fn_item: &syn::ItemFn) -> Result<Self, syn::Error> {
let mut arguments = Vec::new();
let mut pass_env = false;
let mut pass_class = false;
for arg in fn_item.sig.inputs.iter() {
let syn::FnArg::Typed(ty) = arg else {
return Err(syn::Error::new(Span::call_site(), "#[jni] macro doesn't work on methods"));
};
incoming.append_all(quote::quote!( #ty , ));
let pat = unpack_pat(*ty.pat)?;
forwarding.append_all(pat);
let pat = unpack_pat(*ty.pat.clone())?;
if type_equals(ty.ty.clone(), "JNIEnv") { pass_env = true };
if type_equals(ty.ty.clone(), "JClass") { pass_class = true };
arguments.push(SingleArgument {
pat: syn::Ident::new(&pat.to_string(), Span::call_site()),
ty: ty.ty.clone(),
});
// if env.is_none() {
// if type_equals(ty.ty.clone(), "JNIEnv") {
// env = Some(syn::Ident::new(&pat.to_string(), Span::call_site()));
// } else {
// let envv = ;
// incoming.append_all(quote::quote!( #envv: jni::JNIEnv<'local>,));
// env = Some(envv);
// }
// }
// if class.is_none() && !type_equals(ty.ty.clone(), "JNIEnv") {
// if type_equals(ty.ty.clone(), "JClass") {
// class = Some(syn::Ident::new(&pat.to_string(), Span::call_site()));
// } else {
// let classs = syn::Ident::new("class", Span::call_site());
// incoming.append_all(quote::quote!( #classs: jni::objects::JClass<'local>,));
// class = Some(classs);
// }
// }
// incoming.append_all(quote::quote!( #ty , ));
// forwarding.append_all(quote::quote! ( #pat, ));
}
Ok((incoming, forwarding))
let mut incoming = TokenStream::new();
let mut forwarding = TokenStream::new();
let env = if pass_env {
arguments.first()
.ok_or_else(|| syn::Error::new(Span::call_site(), "missing env parameter"))?
.pat
.clone()
} else {
syn::Ident::new("env", Span::call_site())
};
let mut args_iter = arguments.into_iter();
if pass_env {
if let Some(arg) = args_iter.next() {
let pat = arg.pat;
let ty = arg.ty;
incoming.append_all(quote::quote!( mut #pat: #ty,));
forwarding.append_all(quote::quote!( #pat,));
}
} else {
incoming.append_all(quote::quote!( mut #env: jni::JNIEnv<'local>,));
}
if !pass_class {
incoming.append_all(quote::quote!( _class: jni::objects::JClass<'local>,));
}
for arg in args_iter {
let pat = arg.pat;
let ty = arg.ty;
incoming.append_all(quote::quote!( mut #pat: #ty,));
forwarding.append_all(quote::quote!( #pat,));
}
Ok(Self { incoming, forwarding, env })
}
}
struct SingleArgument {
pat: syn::Ident,
ty: Box<syn::Type>,
}

View file

@ -5,8 +5,6 @@ pub(crate) struct AttrsOptions {
pub(crate) class: String,
pub(crate) exception: Option<String>,
pub(crate) return_pointer: bool,
pub(crate) without_env: bool,
pub(crate) without_class: bool,
}
impl AttrsOptions {
@ -18,8 +16,6 @@ impl AttrsOptions {
let mut class = None;
let mut exception = None;
let mut return_pointer = false;
let mut without_env = false;
let mut without_class = false;
for attr in attrs {
match what_next {
@ -30,8 +26,6 @@ impl AttrsOptions {
"class" => what_next = WhatNext::Class,
"exception" => what_next = WhatNext::Exception,
"ptr" => return_pointer = true,
"no_env" => without_env = true,
"no_class" => without_class = true,
_ => return Err(syn::Error::new(Span::call_site(), "unexpected attribute on macro: {attr}")),
}
}
@ -60,7 +54,7 @@ impl AttrsOptions {
let Some(package) = package else { return Err(syn::Error::new(Span::call_site(), "missing required attribute 'package'")) };
let Some(class) = class else { return Err(syn::Error::new(Span::call_site(), "missing required attribute 'class'")) };
Ok(Self { package, class, exception, return_pointer, without_class, without_env })
Ok(Self { package, class, exception, return_pointer })
}
}

View file

@ -9,11 +9,11 @@ pub(crate) struct ReturnOptions {
}
impl ReturnOptions {
pub(crate) fn parse_signature(ret: ReturnType) -> Result<Self, syn::Error> {
pub(crate) fn parse_signature(ret: &ReturnType) -> Result<Self, syn::Error> {
match ret {
syn::ReturnType::Default => Ok(Self { ty: None, result: false }),
syn::ReturnType::Type(_tok, ty) => match *ty {
syn::Type::Path(ref path) => {
syn::ReturnType::Type(_tok, ty) => match *ty.clone() {
syn::Type::Path(path) => {
let Some(last) = path.path.segments.last() else {
return Err(syn::Error::new(Span::call_site(), "empty Result type is not valid"));
};
@ -39,9 +39,9 @@ impl ReturnOptions {
}
}
pub(crate) fn tokens(self) -> TokenStream {
match self.ty {
Some(t) => ReturnType::Type(syn::Token![->](Span::call_site()), t).to_token_stream(),
pub(crate) fn tokens(&self) -> TokenStream {
match &self.ty { // TODO why do we need to invoke syn::Token! macro ???
Some(t) => ReturnType::Type(syn::Token![->](Span::call_site()), t.clone()).to_token_stream(),
None => ReturnType::Default.to_token_stream(),
}
}

View file

@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream};
use quote::TokenStreamExt;
use syn::Item;
use crate::{args::parse_args, attrs::AttrsOptions, ret::ReturnOptions};
use crate::{args::ArgumentOptions, attrs::AttrsOptions, ret::ReturnOptions};
pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
let mut out = TokenStream::new();
@ -12,24 +12,25 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re
};
let attrs = AttrsOptions::parse_attr(attrs)?;
let ret = ReturnOptions::parse_signature(fn_item.sig.output.clone())?;
let args = ArgumentOptions::parse_args(&fn_item)?;
let ret = ReturnOptions::parse_signature(&fn_item.sig.output)?;
let return_type = ret.clone().tokens();
let return_type = ret.tokens();
let name = fn_item.sig.ident.to_string();
let name_jni = name.replace("_", "_1");
let fn_name_inner = syn::Ident::new(&name, Span::call_site());
let fn_name = syn::Ident::new(&format!("Java_{}_{}_{name_jni}", attrs.package, attrs.class), Span::call_site());
let (incoming, forwarding) = parse_args(fn_item)?;
let incoming = args.incoming;
// V----------------------------------V
let header = quote::quote! {
#[no_mangle]
#[allow(unused_mut)]
pub extern "system" fn #fn_name<'local>(#incoming) #return_type
};
// ^----------------------------------^
let return_expr = if attrs.return_pointer {
quote::quote!( std::ptr::null_mut() )
@ -37,13 +38,11 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re
quote::quote!( 0 )
};
let Some(env_ident) = forwarding.clone().into_iter().next() else {
return Err(syn::Error::new(Span::call_site(), "missing JNIEnv argument"));
};
let env_ident = args.env;
let forwarding = args.forwarding;
let body = if ret.result { // wrap errors
if let Some(exception) = attrs.exception {
// V----------------------------------V
quote::quote! {
{
use jni_toolbox::JniToolboxError;
@ -62,7 +61,7 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re
quote::quote! {
{
use jni_toolbox::JniToolboxError;
// NOTE: this is SAFE! the cloned env reference lives less than the actual one, we just lack a
// NOTE: this should be SAFE! the cloned env reference lives less than the actual one, we just lack a
// way to get it back from the called function and thus resort to unsafe cloning
let mut env_copy = unsafe { #env_ident.unsafe_clone() };
match #fn_name_inner(#forwarding) {