feat: return compiler errors, throw java err

no more random panicing
This commit is contained in:
əlemi 2024-09-21 17:36:51 +02:00
parent 1f0bb05ab0
commit 5e560d2f3d
Signed by: alemi
GPG key ID: A4895B84D311642C
2 changed files with 24 additions and 15 deletions

View file

@ -15,10 +15,12 @@ fn your_function_name(arg: i32) -> Result<(), String> {
by specifying package and class, this crate will write an appropriate wrapper with the right function name. If inner function returns a `Result<>`, wrapper will also handle it. (currently just panics, soon will throw exceptions!) by specifying package and class, this crate will write an appropriate wrapper with the right function name. If inner function returns a `Result<>`, wrapper will also handle it. (currently just panics, soon will throw exceptions!)
note that input/output arguments must be natively FFI safe: there will be no hidden translations! you will have to un-marshal strings yourself
## examples ## examples
the following function: the following function:
```rust ```rust
#[java_easy_jni::jni(package = "mp.code", class = "Client")] #[jni_macro::jni(package = "mp.code", class = "Client")]
fn connect(env: JNIEnv, cacca: JString) -> Result<(), ConnectionError> { fn connect(env: JNIEnv, cacca: JString) -> Result<(), ConnectionError> {
let config = codemp::api::Config::new("asd".into(), "dsa".into()); let config = codemp::api::Config::new("asd".into(), "dsa".into());
tokio().block_on(codemp::Client::connect(config))?; tokio().block_on(codemp::Client::connect(config))?;

View file

@ -21,10 +21,8 @@ use syn::{FnArg, Item, ReturnType, Type};
fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> { fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, syn::Error> {
let mut out = TokenStream::new(); let mut out = TokenStream::new();
let item = syn::parse2(input.clone())?; 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 Item::Fn(fn_item) = item else {
panic!("can only be applied to functions"); // TODO throw err instead of panic
}; };
let mut what_next = WhatNext::Nothing; let mut what_next = WhatNext::Nothing;
@ -39,7 +37,7 @@ fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenS
match i.to_string().as_ref() { match i.to_string().as_ref() {
"package" => what_next = WhatNext::Package, "package" => what_next = WhatNext::Package,
"class" => what_next = WhatNext::Class, "class" => what_next = WhatNext::Class,
_ => panic!("unexpected keyword in attrs: {}", attr), _ => return Err(syn::Error::new(Span::call_site(), "unexpected attribute on macro: {attr}")),
} }
} }
}, },
@ -60,15 +58,15 @@ fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenS
} }
} }
let package = package.expect("missing attribute 'package'"); let Some(package) = package else { return Err(syn::Error::new(Span::call_site(), "missing attribute 'package'")) };
let clazz = clazz.expect("missing attribute 'class'"); 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 { let (could_error, ret_type) = match fn_item.sig.output {
syn::ReturnType::Default => (false, fn_item.sig.output), syn::ReturnType::Default => (false, fn_item.sig.output),
syn::ReturnType::Type(_tok, ty) => match *ty { syn::ReturnType::Type(_tok, ty) => match *ty {
syn::Type::Path(ref path) => { syn::Type::Path(ref path) => {
let Some(last) = path.path.segments.last() else { let Some(last) = path.path.segments.last() else {
panic!("empty type path"); 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 // TODO this is terrible, macro returns a function and we call it?? there must be a
@ -80,8 +78,8 @@ fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenS
if last.ident == "Result" { if last.ident == "Result" {
match &last.arguments { match &last.arguments {
syn::PathArguments::None => panic!("result without generics is not valid"), syn::PathArguments::None => return Err(syn::Error::new(Span::call_site(), "Result without generics is not valid")),
syn::PathArguments::Parenthesized(_) => panic!("parenthesized result 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() { syn::PathArguments::AngleBracketed(ref generics) => for generic in generics.args.iter() {
match generic { match generic {
syn::GenericArgument::Lifetime(_) => continue, syn::GenericArgument::Lifetime(_) => continue,
@ -89,7 +87,7 @@ fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenS
out = (true, ReturnType::Type(syn::Token![->](Span::call_site()), Box::new(ty.clone()))); out = (true, ReturnType::Type(syn::Token![->](Span::call_site()), Box::new(ty.clone())));
break; break;
}, },
_ => panic!("unexpected type in Result generic"), _ => return Err(syn::Error::new(Span::call_site(), "unexpected type in Result")),
} }
} }
} }
@ -97,7 +95,7 @@ fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenS
out out
}, },
_ => panic!("unsupported return type"), _ => return Err(syn::Error::new(Span::call_site(), "unsupported return type")),
}, },
}; };
@ -107,7 +105,7 @@ fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenS
for arg in fn_item.sig.inputs { for arg in fn_item.sig.inputs {
let FnArg::Typed(ty) = arg else { let FnArg::Typed(ty) = arg else {
panic!("jni macro doesn't work on methods"); return Err(syn::Error::new(Span::call_site(), "#[jni] macro doesn't work on methods"));
}; };
incoming.append_all(quote::quote!( #ty , )); incoming.append_all(quote::quote!( #ty , ));
let pat = ty.pat; let pat = ty.pat;
@ -119,13 +117,22 @@ fn generate_jni_wrapper(attrs: TokenStream, input: TokenStream) -> Result<TokenS
let fn_name_inner = syn::Ident::new(&name, Span::call_site()); 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 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 wrapped = if could_error { let wrapped = if could_error {
quote::quote! { quote::quote! {
#[no_mangle] #[no_mangle]
pub extern "system" fn #fn_name<'local>(#incoming) #ret_type { pub extern "system" fn #fn_name<'local>(#incoming) #ret_type {
match #fn_name_inner(#forwarding) { match #fn_name_inner(#forwarding) {
Ok(x) => x, Ok(x) => x,
Err(e) => panic!("error in JNI!"), // TODO throw java exc Err(e) => {
#env_ident
.throw_new("java/lang/RuntimeException", format!("{e:?}"))
.expect("failed throwing Java exception from native call");
return 0;
}
} }
} }
} }