blob: 85e8c1adf0b20104339353cab8fdb0208a4c023d [file] [log] [blame]
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::Span;
use syn::{
parse::{Parse, ParseStream},
Expr, LitStr, Token,
};
use super::meta_arg::MetaArg;
use super::substitutions::{substitute_class_chars, substitute_package_chars};
pub struct JniMethodMeta {
/// The class descriptor in [export_name format](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names)
pub class_desc: String,
/// The method name in Java. Use the Rust method name if not specified
pub method_name: Option<LitStr>,
/// The expression to run when a panic is encountered. The value of this expression will be
/// returned by the annotated function.
pub panic_returns: Option<Expr>,
}
impl Parse for JniMethodMeta {
fn parse(stream: ParseStream<'_>) -> syn::Result<Self> {
let mut package = None;
let mut class = None;
let mut method_name = None;
let mut panic_returns = None;
fn set_once<T>(opt: &mut Option<T>, value: T, span: Span, field: &str) -> syn::Result<()> {
if let Some(_old) = opt.replace(value) {
return Err(syn::Error::new(
span,
format!("`{field}` should not be specified twice"),
));
}
Ok(())
}
type Structure = syn::punctuated::Punctuated<MetaArg, Token![,]>;
for arg in Structure::parse_terminated(stream)? {
match arg {
MetaArg::Package {
package_token,
value,
..
} => {
set_once(&mut package, value, package_token.span, "package")?;
}
MetaArg::Class {
class_token, value, ..
} => {
set_once(&mut class, value, class_token.span, "class")?;
}
MetaArg::MethodName {
method_name_token,
value,
..
} => {
set_once(
&mut method_name,
value,
method_name_token.span,
"method_name",
)?;
}
MetaArg::PanicReturns {
panic_returns_token,
value,
..
} => {
set_once(
&mut panic_returns,
value,
panic_returns_token.span,
"panic_returns",
)?;
}
}
}
let Some(package) = package else {
return Err(syn::Error::new(stream.span(), "`package` is required"));
};
let Some(class) = class else {
return Err(syn::Error::new(stream.span(), "`class` is required"));
};
let package = {
let package_str = package.value();
if package_str.contains('/') {
return Err(syn::Error::new(
stream.span(),
"`package` should use '.' as a separator",
));
}
substitute_package_chars(&package_str)
};
let class = substitute_class_chars(&class.value());
let class_desc = format!("{package}_{class}");
Ok(Self {
class_desc,
method_name,
panic_returns,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use syn::parse_quote;
#[test]
fn can_parse() {
let meta: JniMethodMeta = parse_quote! {
package = "com.example",
class = "Foo.Inner",
panic_returns = false,
};
assert_eq!("com_example_Foo_00024Inner", &meta.class_desc);
assert!(meta.panic_returns.is_some());
}
#[test]
fn can_parse_missing_panic_returns() {
let meta: JniMethodMeta = parse_quote! {
package = "com.example",
class = "Foo.Inner",
};
assert_eq!("com_example_Foo_00024Inner", &meta.class_desc);
assert!(meta.panic_returns.is_none());
}
#[test]
#[should_panic]
fn double_package() {
let _: JniMethodMeta = parse_quote! {
package = "com.example",
package = "com.example",
class = "Foo.Inner",
panic_returns = false,
};
}
#[test]
#[should_panic]
fn package_uses_slashes() {
let _: JniMethodMeta = parse_quote! {
package = "com/example",
class = "Foo.Inner",
panic_returns = false,
};
}
#[test]
#[should_panic]
fn missing_package() {
let _: JniMethodMeta = parse_quote! {
class = "Foo.Inner",
panic_returns = false,
};
}
#[test]
#[should_panic]
fn double_class() {
let _: JniMethodMeta = parse_quote! {
package = "com.example",
class = "Foo.Inner",
class = "Foo.Inner",
panic_returns = false,
};
}
#[test]
#[should_panic]
fn missing_class() {
let _: JniMethodMeta = parse_quote! {
package = "com.example",
panic_returns = false,
};
}
#[test]
#[should_panic]
fn double_panic_returns() {
let _: JniMethodMeta = parse_quote! {
package = "com.example",
class = "Foo.Inner",
panic_returns = false,
panic_returns = false,
};
}
}