use std::{iter::once, sync::Arc};
use proc_macro2::{Ident, Literal, TokenStream};
use quote::quote_spanned;
use syn::spanned::Spanned;
use crate::{
argument::MethodSelector,
class_info::{self, ClassInfo, Method, Type},
reflect::{MethodIndex, Reflector},
signature::Signature,
};
pub fn java_function(selector: MethodSelector, input: syn::ItemFn) -> syn::Result<TokenStream> {
let span = selector.span();
let mut reflector = Reflector::default();
let (class_info, method_index) = reflected_method(&selector, &mut reflector)?;
let driver = Driver {
selector: &selector,
class_info: &class_info,
method_info: &class_info.methods[method_index],
input: &input,
};
let java_fn_name = driver.java_name();
let input_fn_name = &input.sig.ident;
let (
Argument {
name: env_name,
ty: env_ty,
},
Argument {
name: this_name,
ty: this_ty,
},
) = driver.default_arguments()?;
let user_arguments = driver.user_arguments()?;
let user_argument_names: Vec<_> = user_arguments.iter().map(|ua| &ua.name).collect();
let user_argument_tys: Vec<_> = user_arguments.iter().map(|ua| &ua.ty).collect();
let rust_arguments = driver.rust_arguments(&this_name, &user_argument_names)?;
let rust_invocation = quote_spanned!(span =>
#input_fn_name(
#(#rust_arguments),*
)
);
let (return_ty, rust_invocation) = driver.return_ty_and_expr(rust_invocation, &env_name)?;
let vis = &input.vis;
let rust_this_ty = driver.convert_ty(&class_info.this_ref().into())?;
let method_name_literal = Literal::string(&selector.method_name());
let signature_literal = Literal::string(
&driver
.method_info
.descriptor(&class_info.as_ref().generics_scope()),
);
let tokens = quote_spanned!(span =>
#[allow(unused_variables, nonstandard_style)]
const _: () = {
#[no_mangle]
fn #java_fn_name(
#env_name: #env_ty,
#this_name: #this_ty,
#(#user_argument_names: #user_argument_tys,)*
) -> #return_ty {
unsafe {
#rust_invocation
}
}
impl duchess::plumbing::JavaFn for #input_fn_name {
fn java_fn() -> duchess::plumbing::JavaFunction {
unsafe {
duchess::plumbing::JavaFunction::new(
#method_name_literal,
#signature_literal,
std::ptr::NonNull::new_unchecked(#java_fn_name as *mut ()),
<#rust_this_ty as duchess::JavaObject>::class,
)
}
}
}
};
#[allow(non_camel_case_types)]
#vis struct #input_fn_name { _private: ::core::convert::Infallible }
#input
);
crate::debug_tokens(
format!("{}::{}", selector.class_name(), selector.method_name()),
&tokens,
);
Ok(tokens)
}
fn reflected_method(
selector: &MethodSelector,
reflector: &mut Reflector,
) -> syn::Result<(Arc<ClassInfo>, MethodIndex)> {
let reflected_method = reflector.reflect_method(selector)?;
match reflected_method {
crate::reflect::ReflectedMethod::Constructor(_, _) => Err(syn::Error::new(
selector.span(),
format!("cannot have a native class constructor"),
)),
crate::reflect::ReflectedMethod::Method(class_info, index) => Ok((class_info, index)),
}
}
struct Driver<'a> {
selector: &'a MethodSelector,
class_info: &'a ClassInfo,
method_info: &'a Method,
input: &'a syn::ItemFn,
}
struct Argument {
name: syn::Ident,
ty: TokenStream,
}
impl Driver<'_> {
fn java_name(&self) -> syn::Ident {
let class_name = self.selector.class_name();
let class = class_name.to_jni_class_name();
let package = class_name.to_jni_package();
let method_name = self.selector.method_name().replace("_", "_1");
let symbol_name: String = once("Java")
.chain(once(&package[..]))
.chain(once(&class[..]))
.chain(once(&method_name[..]))
.collect::<Vec<_>>()
.join("_");
syn::Ident::new(&symbol_name, self.selector.span())
}
fn default_arguments(&self) -> syn::Result<(Argument, Argument)> {
let span = self.selector.span();
let env_arg = Argument {
name: syn::Ident::new("jni_env", span),
ty: quote_spanned!(span => duchess::plumbing::EnvPtr<'_>),
};
let this_ty = if self.method_info.flags.is_static {
quote_spanned!(span => duchess::plumbing::jni_sys::jclass)
} else {
let rust_this_ty = self.convert_ty(&self.class_info.this_ref().into())?;
quote_spanned!(span => &#rust_this_ty)
};
let this_arg = Argument {
name: syn::Ident::new("this", span),
ty: this_ty,
};
Ok((env_arg, this_arg))
}
fn convert_ty(&self, ty: &Type) -> syn::Result<TokenStream> {
Ok(Signature::new(
&self.method_info.name,
self.selector.span(),
&self.class_info.generics,
)
.forbid_capture(|sig| sig.java_ty(ty))?)
}
fn user_arguments(&self) -> syn::Result<Vec<Argument>> {
let span = self.selector.span();
let mut arguments = vec![];
for (argument_ty, index) in self.method_info.argument_tys.iter().zip(0..) {
let name = syn::Ident::new(&format!("arg{index}"), span);
let java_ty = self.convert_ty(argument_ty)?;
let ty = match argument_ty {
class_info::Type::Ref(_) | class_info::Type::Repeat(_) => {
quote_spanned!(span => &#java_ty)
}
class_info::Type::Scalar(_) => java_ty,
};
arguments.push(Argument { name, ty })
}
Ok(arguments)
}
fn rust_arguments(
&self,
this_name: &Ident,
user_names: &[&Ident],
) -> syn::Result<Vec<TokenStream>> {
let mut input_rust_arguments = vec![];
for fn_arg in &self.input.sig.inputs {
match fn_arg {
syn::FnArg::Receiver(r) => {
return Err(syn::Error::new(
r.span(),
"Rust methods cannot be mapped to Java native functions",
));
}
syn::FnArg::Typed(t) => {
input_rust_arguments.push(t);
}
}
}
let expected_num_rust_arguments = if self.method_info.flags.is_static {
0
} else {
1 } + user_names.len();
if input_rust_arguments.len() > expected_num_rust_arguments {
let extra_span = input_rust_arguments[expected_num_rust_arguments].span();
return Err(syn::Error::new(
extra_span,
&format!(
"extra argument(s) on Rust function, only {} argument(s) are expected",
expected_num_rust_arguments
),
));
} else if input_rust_arguments.len() < expected_num_rust_arguments {
if !self.method_info.flags.is_static && input_rust_arguments.len() == 0 {
return Err(syn::Error::new(
self.input.sig.ident.span(),
&format!(
"Rust function should have {} argument(s); don't forget about `this`",
expected_num_rust_arguments
),
));
} else if !self.method_info.flags.is_static {
return Err(syn::Error::new(
self.input.sig.ident.span(),
&format!(
"Rust function should have {} argument(s); don't forget about `this`",
expected_num_rust_arguments
),
));
} else {
return Err(syn::Error::new(
self.input.sig.ident.span(),
&format!(
"Rust function should have {} argument(s)",
expected_num_rust_arguments
),
));
}
}
let mut output = vec![];
let mut inputs = input_rust_arguments.iter();
if !self.method_info.flags.is_static {
output.push(self.rust_argument(this_name, false, inputs.next().unwrap())?);
}
for (user_name, argument_ty) in user_names.iter().zip(&self.method_info.argument_tys) {
output.push(self.rust_argument(
user_name,
argument_ty.is_scalar(),
inputs.next().unwrap(),
)?);
}
Ok(output)
}
fn rust_argument(
&self,
arg_name: &Ident,
java_ty_is_scalar: bool,
rust_ty: &syn::PatType,
) -> syn::Result<TokenStream> {
if let syn::Type::Reference(_) = &*rust_ty.ty {
if java_ty_is_scalar {
return Err(syn::Error::new(
rust_ty.ty.span(),
&format!("unexpected Rust reference; Java function declares a scalar type for this argument"),
));
}
return Ok(quote_spanned!(rust_ty.span() => #arg_name));
}
if java_ty_is_scalar {
return Ok(quote_spanned!(rust_ty.span() => #arg_name));
}
Ok(quote_spanned!(rust_ty.span() => duchess::JvmOp::to_rust(#arg_name).execute()))
}
fn return_ty_and_expr(
&self,
return_expr: TokenStream,
env_name: &Ident,
) -> syn::Result<(TokenStream, TokenStream)> {
let span = self.selector.span();
match &self.method_info.return_ty {
Some(ty) => match ty {
class_info::Type::Scalar(ty) => {
let output_rust_ty = ty.to_tokens(span);
Ok((
output_rust_ty.clone(),
quote_spanned!(span => duchess::plumbing::native_function_returning_scalar::<#output_rust_ty, _>(#env_name, || #return_expr)),
))
}
class_info::Type::Ref(_) | class_info::Type::Repeat(_) => {
let output_java_ty = self.convert_ty(ty)?;
Ok((
quote_spanned!(span => duchess::plumbing::jni_sys::jobject),
quote_spanned!(span => duchess::plumbing::native_function_returning_object::<#output_java_ty, _>(#env_name, || #return_expr)),
))
}
},
None => Ok((quote_spanned!(span => ()), return_expr)),
}
}
}