From 4daaad5faf95d961fa78566a7260f6672b6867f2 Mon Sep 17 00:00:00 2001 From: alemi Date: Sat, 21 Sep 2024 23:03:02 +0200 Subject: [PATCH] feat: allow skipping env/class in args also removed a bit of clones but added more... --- macro/src/args.rs | 124 ++++++++++++++++++++++++++++++++++++++----- macro/src/attrs.rs | 8 +-- macro/src/ret.rs | 12 ++--- macro/src/wrapper.rs | 23 ++++---- 4 files changed, 130 insertions(+), 37 deletions(-) diff --git a/macro/src/args.rs b/macro/src/args.rs index c308d6a..7a926bb 100644 --- a/macro/src/args.rs +++ b/macro/src/args.rs @@ -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 { 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 { } } -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 { - 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); +fn type_equals(ty: Box, search: impl AsRef) -> 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, } - Ok((incoming, forwarding)) +} + +impl ArgumentOptions { + pub(crate) fn parse_args(fn_item: &syn::ItemFn) -> Result { + 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")); + }; + 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, )); + } + + 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, } diff --git a/macro/src/attrs.rs b/macro/src/attrs.rs index bf66bf8..f4ff2d2 100644 --- a/macro/src/attrs.rs +++ b/macro/src/attrs.rs @@ -5,8 +5,6 @@ pub(crate) struct AttrsOptions { pub(crate) class: String, pub(crate) exception: Option, 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 }) } } diff --git a/macro/src/ret.rs b/macro/src/ret.rs index d9babd9..6605d2e 100644 --- a/macro/src/ret.rs +++ b/macro/src/ret.rs @@ -9,11 +9,11 @@ pub(crate) struct ReturnOptions { } impl ReturnOptions { - pub(crate) fn parse_signature(ret: ReturnType) -> Result { + pub(crate) fn parse_signature(ret: &ReturnType) -> Result { 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(), } } diff --git a/macro/src/wrapper.rs b/macro/src/wrapper.rs index 769a05f..6fb38f6 100644 --- a/macro/src/wrapper.rs +++ b/macro/src/wrapper.rs @@ -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 { 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) {