diff --git a/macro/macro.rs b/macro/macro.rs deleted file mode 100644 index 24a866d..0000000 --- a/macro/macro.rs +++ /dev/null @@ -1,224 +0,0 @@ -#[proc_macro_attribute] -pub fn jni( - attrs: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - generate_jni_wrapper( - syn::parse_macro_input!(attrs), - syn::parse_macro_input!(input), - ) - .unwrap() - .into() -} - - - - -use proc_macro2::{Span, TokenStream, TokenTree}; -use quote::TokenStreamExt; -use syn::{FnArg, Item, ReturnType, Type}; - -fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result { - let mut out = TokenStream::new(); - - let Item::Fn(fn_item) = syn::parse2(input.clone())? else { - return Err(syn::Error::new(Span::call_site(), "#[jni] is only supported on functions")); - }; - - let mut what_next = WhatNext::Nothing; - - let mut package = None; - let mut clazz = None; - let mut exception = None; - let mut return_pointer = false; - - for attr in attrs { - match what_next { - WhatNext::Nothing => { - if let TokenTree::Ident(ref i) = attr { - match i.to_string().as_ref() { - "package" => what_next = WhatNext::Package, - "class" => what_next = WhatNext::Class, - "exception" => what_next = WhatNext::Exception, - "ptr" => return_pointer = true, - _ => return Err(syn::Error::new(Span::call_site(), "unexpected attribute on macro: {attr}")), - } - } - }, - WhatNext::Class => { - if let TokenTree::Literal(i) = attr { - let raw = i.to_string().replace('"', ""); - clazz = Some(syn::Ident::new(&raw, Span::call_site())); - what_next = WhatNext::Nothing; - } - }, - WhatNext::Package => { - if let TokenTree::Literal(i) = attr { - let raw = i.to_string().replace('"', "").replace(".", "_"); - package = Some(syn::Ident::new(&raw, Span::call_site())); - what_next = WhatNext::Nothing; - } - }, - WhatNext::Exception => { - if let TokenTree::Literal(i) = attr { - let raw = i.to_string().replace('"', "").replace(".", "_"); - exception = Some(raw); - what_next = WhatNext::Nothing; - } - } - } - } - - let Some(package) = package else { return Err(syn::Error::new(Span::call_site(), "missing attribute 'package'")) }; - let Some(clazz) = clazz else { return Err(syn::Error::new(Span::call_site(), "missing attribute 'class'")) }; - - let (could_error, ret_type) = match fn_item.sig.output { - syn::ReturnType::Default => (false, fn_item.sig.output), - syn::ReturnType::Type(_tok, ty) => match *ty { - syn::Type::Path(ref path) => { - let Some(last) = path.path.segments.last() else { - return Err(syn::Error::new(Span::call_site(), "empty Result type is not valid")); - }; - - // TODO this is terrible, macro returns a function and we call it?? there must be a - // better way!!! - let mut out = ( - false, - ReturnType::Type(syn::Token![->](Span::call_site()), Box::new(Type::Path(path.clone()))) - ); - - if last.ident == "Result" { - match &last.arguments { - syn::PathArguments::None => return Err(syn::Error::new(Span::call_site(), "Result without generics is not valid")), - syn::PathArguments::Parenthesized(_) => return Err(syn::Error::new(Span::call_site(), "Parenthesized Result is not valid")), - syn::PathArguments::AngleBracketed(ref generics) => for generic in generics.args.iter() { - match generic { - syn::GenericArgument::Lifetime(_) => continue, - syn::GenericArgument::Type(ty) => { - out = (true, ReturnType::Type(syn::Token![->](Span::call_site()), Box::new(ty.clone()))); - break; - }, - _ => return Err(syn::Error::new(Span::call_site(), "unexpected type in Result")), - } - } - } - } - - out - }, - _ => return Err(syn::Error::new(Span::call_site(), "unsupported return type")), - }, - }; - - - let mut incoming = TokenStream::new(); - let mut forwarding = TokenStream::new(); - - for arg in fn_item.sig.inputs { - let 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 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_{package}_{clazz}_{name_jni}"), Span::call_site()); - - let Some(env_ident) = forwarding.clone().into_iter().next() else { - return Err(syn::Error::new(Span::call_site(), "missing JNIEnv argument")); - }; - - let return_expr = if return_pointer { - quote::quote!( std::ptr::null_mut() ) - } else { - quote::quote!( 0 ) - }; - - let wrapped = if could_error { - if let Some(exception) = exception { - // V----------------------------------V - quote::quote! { - #[no_mangle] - #[allow(unused_mut)] - pub extern "system" fn #fn_name<'local>(#incoming) #ret_type { - use jni_toolbox::JniToolboxError; - match #fn_name_inner(#forwarding) { - Ok(ret) => ret, - Err(e) => match #env_ident.throw_new(#exception, format!("{e:?}")) { - Ok(_) => return #return_expr, - Err(e) => panic!("error throwing java exception: {e}"), - } - } - } - } - // ^----------------------------------^ - } else { - // V----------------------------------V - quote::quote! { - #[no_mangle] - #[allow(unused_mut)] - pub extern "system" fn #fn_name<'local>(#incoming) #ret_type { - use jni_toolbox::JniToolboxError; - // NOTE: this is 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) { - Err(e) => match env_copy.find_class(e.jclass()) { - Err(e) => panic!("error throwing Java exception -- failed resolving error class: {e}"), - Ok(class) => match env_copy.new_string(format!("{e:?}")) { - Err(e) => panic!("error throwing Java exception -- failed creating error string: {e}"), - Ok(msg) => match env_copy.new_object(class, "(Ljava/lang/String;)V", &[jni::objects::JValueGen::Object(&msg)]) { - Err(e) => panic!("error throwing Java exception -- failed creating object: {e}"), - Ok(obj) => match env_copy.throw(jni::objects::JThrowable::from(obj)) { - Err(e) => panic!("error throwing Java exception -- failed throwing: {e}"), - Ok(_) => return #return_expr, - }, - }, - }, - } - Ok(ret) => ret, - } - } - } - // ^----------------------------------^ - } - } else { - // V----------------------------------V - quote::quote! { - #[no_mangle] - #[allow(unused_mut)] - pub extern "system" fn #fn_name<'local>(#incoming) #ret_type { - #fn_name_inner(#forwarding) - } - } - // ^----------------------------------^ - }; - - out.append_all(input); - out.append_all(wrapped); - Ok(out) -} - -enum WhatNext { - Nothing, - Package, - Class, - Exception, -} - -fn unpack_pat(pat: syn::Pat) -> Result { - match pat { - syn::Pat::Ident(i) => { - let ident = i.ident; - Ok(quote::quote!( #ident ,)) - }, - syn::Pat::Reference(r) => { - unpack_pat(*r.pat) - }, - _ => Err(syn::Error::new(Span::call_site(), "unsupported argument type")), - } -} diff --git a/macro/src/args.rs b/macro/src/args.rs new file mode 100644 index 0000000..c308d6a --- /dev/null +++ b/macro/src/args.rs @@ -0,0 +1,30 @@ +use proc_macro2::{Span, TokenStream}; +use quote::TokenStreamExt; + + +fn unpack_pat(pat: syn::Pat) -> Result { + match pat { + syn::Pat::Ident(i) => { + let ident = i.ident; + Ok(quote::quote!( #ident ,)) + }, + syn::Pat::Reference(r) => { + unpack_pat(*r.pat) + }, + _ => Err(syn::Error::new(Span::call_site(), "unsupported argument type")), + } +} + +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); + } + Ok((incoming, forwarding)) +} diff --git a/macro/src/lib.rs b/macro/src/lib.rs index 175a485..0d07be7 100644 --- a/macro/src/lib.rs +++ b/macro/src/lib.rs @@ -1,5 +1,7 @@ mod attrs; mod wrapper; +mod args; +mod ret; /// wrap this function in in a JNI exported fn diff --git a/macro/src/ret.rs b/macro/src/ret.rs new file mode 100644 index 0000000..d9babd9 --- /dev/null +++ b/macro/src/ret.rs @@ -0,0 +1,48 @@ +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; +use syn::{ReturnType, Type}; + +#[derive(Clone)] +pub(crate) struct ReturnOptions { + pub(crate) ty: Option>, + pub(crate) result: bool, +} + +impl ReturnOptions { + 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) => { + let Some(last) = path.path.segments.last() else { + return Err(syn::Error::new(Span::call_site(), "empty Result type is not valid")); + }; + + if last.ident == "Result" { + match &last.arguments { + syn::PathArguments::None => return Err(syn::Error::new(Span::call_site(), "Result without generics is not valid")), + syn::PathArguments::Parenthesized(_) => return Err(syn::Error::new(Span::call_site(), "Parenthesized Result is not valid")), + syn::PathArguments::AngleBracketed(ref generics) => for generic in generics.args.iter() { + match generic { + syn::GenericArgument::Lifetime(_) => continue, + syn::GenericArgument::Type(ty) => return Ok(Self { ty: Some(Box::new(ty.clone())), result: true }), + _ => return Err(syn::Error::new(Span::call_site(), "unexpected type in Result")), + } + } + } + } + + Ok(Self { ty: Some(Box::new(Type::Path(path.clone()))), result: false }) + }, + _ => Err(syn::Error::new(Span::call_site(), "unsupported return type")), + }, + } + } + + pub(crate) fn tokens(self) -> TokenStream { + match self.ty { + Some(t) => ReturnType::Type(syn::Token![->](Span::call_site()), t).to_token_stream(), + None => ReturnType::Default.to_token_stream(), + } + } +} diff --git a/macro/src/wrapper.rs b/macro/src/wrapper.rs index f2f3b7d..769a05f 100644 --- a/macro/src/wrapper.rs +++ b/macro/src/wrapper.rs @@ -1,8 +1,8 @@ use proc_macro2::{Span, TokenStream}; use quote::TokenStreamExt; -use syn::{FnArg, Item, ReturnType, Type}; +use syn::Item; -use crate::attrs::AttrsOptions; +use crate::{args::parse_args, attrs::AttrsOptions, ret::ReturnOptions}; pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result { let mut out = TokenStream::new(); @@ -12,65 +12,23 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re }; let attrs = AttrsOptions::parse_attr(attrs)?; - - let (could_error, ret_type) = match fn_item.sig.output { - syn::ReturnType::Default => (false, fn_item.sig.output), - syn::ReturnType::Type(_tok, ty) => match *ty { - syn::Type::Path(ref path) => { - let Some(last) = path.path.segments.last() else { - return Err(syn::Error::new(Span::call_site(), "empty Result type is not valid")); - }; - - // TODO this is terrible, macro returns a function and we call it?? there must be a - // better way!!! - let mut out = ( - false, - ReturnType::Type(syn::Token![->](Span::call_site()), Box::new(Type::Path(path.clone()))) - ); - - if last.ident == "Result" { - match &last.arguments { - syn::PathArguments::None => return Err(syn::Error::new(Span::call_site(), "Result without generics is not valid")), - syn::PathArguments::Parenthesized(_) => return Err(syn::Error::new(Span::call_site(), "Parenthesized Result is not valid")), - syn::PathArguments::AngleBracketed(ref generics) => for generic in generics.args.iter() { - match generic { - syn::GenericArgument::Lifetime(_) => continue, - syn::GenericArgument::Type(ty) => { - out = (true, ReturnType::Type(syn::Token![->](Span::call_site()), Box::new(ty.clone()))); - break; - }, - _ => return Err(syn::Error::new(Span::call_site(), "unexpected type in Result")), - } - } - } - } - - out - }, - _ => return Err(syn::Error::new(Span::call_site(), "unsupported return type")), - }, - }; + let ret = ReturnOptions::parse_signature(fn_item.sig.output.clone())?; - let mut incoming = TokenStream::new(); - let mut forwarding = TokenStream::new(); - - for arg in fn_item.sig.inputs { - let 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 return_type = ret.clone().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 Some(env_ident) = forwarding.clone().into_iter().next() else { - return Err(syn::Error::new(Span::call_site(), "missing JNIEnv argument")); + let (incoming, forwarding) = parse_args(fn_item)?; + + 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 { @@ -79,13 +37,15 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re quote::quote!( 0 ) }; - let wrapped = if could_error { + let Some(env_ident) = forwarding.clone().into_iter().next() else { + return Err(syn::Error::new(Span::call_site(), "missing JNIEnv argument")); + }; + + + let body = if ret.result { // wrap errors if let Some(exception) = attrs.exception { - // V----------------------------------V quote::quote! { - #[no_mangle] - #[allow(unused_mut)] - pub extern "system" fn #fn_name<'local>(#incoming) #ret_type { + { use jni_toolbox::JniToolboxError; match #fn_name_inner(#forwarding) { Ok(ret) => ret, @@ -100,9 +60,7 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re } else { // V----------------------------------V quote::quote! { - #[no_mangle] - #[allow(unused_mut)] - pub extern "system" fn #fn_name<'local>(#incoming) #ret_type { + { use jni_toolbox::JniToolboxError; // NOTE: this is 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 @@ -125,14 +83,11 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re } } } - // ^----------------------------------^ } } else { // V----------------------------------V quote::quote! { - #[no_mangle] - #[allow(unused_mut)] - pub extern "system" fn #fn_name<'local>(#incoming) #ret_type { + { #fn_name_inner(#forwarding) } } @@ -140,19 +95,7 @@ pub(crate) fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Re }; out.append_all(input); - out.append_all(wrapped); + out.append_all(header); + out.append_all(body); Ok(out) } - -fn unpack_pat(pat: syn::Pat) -> Result { - match pat { - syn::Pat::Ident(i) => { - let ident = i.ident; - Ok(quote::quote!( #ident ,)) - }, - syn::Pat::Reference(r) => { - unpack_pat(*r.pat) - }, - _ => Err(syn::Error::new(Span::call_site(), "unsupported argument type")), - } -}