1 // Copyright 2024 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 use proc_macro2::Span; 16 use syn::{ 17 parse::{Parse, ParseStream}, 18 Expr, LitStr, Token, 19 }; 20 21 use super::meta_arg::MetaArg; 22 use super::substitutions::{substitute_class_chars, substitute_package_chars}; 23 24 pub struct JniMethodMeta { 25 /// The class descriptor in [export_name format](https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#resolving_native_method_names) 26 pub class_desc: String, 27 /// The method name in Java. Use the Rust method name if not specified 28 pub method_name: Option<LitStr>, 29 /// The expression to run when a panic is encountered. The value of this expression will be 30 /// returned by the annotated function. 31 pub panic_returns: Option<Expr>, 32 } 33 34 impl Parse for JniMethodMeta { parse(stream: ParseStream<'_>) -> syn::Result<Self>35 fn parse(stream: ParseStream<'_>) -> syn::Result<Self> { 36 let mut package = None; 37 let mut class = None; 38 let mut method_name = None; 39 let mut panic_returns = None; 40 41 fn set_once<T>(opt: &mut Option<T>, value: T, span: Span, field: &str) -> syn::Result<()> { 42 if let Some(_old) = opt.replace(value) { 43 return Err(syn::Error::new( 44 span, 45 format!("`{field}` should not be specified twice"), 46 )); 47 } 48 Ok(()) 49 } 50 51 type Structure = syn::punctuated::Punctuated<MetaArg, Token![,]>; 52 53 for arg in Structure::parse_terminated(stream)? { 54 match arg { 55 MetaArg::Package { 56 package_token, 57 value, 58 .. 59 } => { 60 set_once(&mut package, value, package_token.span, "package")?; 61 } 62 MetaArg::Class { 63 class_token, value, .. 64 } => { 65 set_once(&mut class, value, class_token.span, "class")?; 66 } 67 MetaArg::MethodName { 68 method_name_token, 69 value, 70 .. 71 } => { 72 set_once( 73 &mut method_name, 74 value, 75 method_name_token.span, 76 "method_name", 77 )?; 78 } 79 MetaArg::PanicReturns { 80 panic_returns_token, 81 value, 82 .. 83 } => { 84 set_once( 85 &mut panic_returns, 86 value, 87 panic_returns_token.span, 88 "panic_returns", 89 )?; 90 } 91 } 92 } 93 94 let Some(package) = package else { 95 return Err(syn::Error::new(stream.span(), "`package` is required")); 96 }; 97 98 let Some(class) = class else { 99 return Err(syn::Error::new(stream.span(), "`class` is required")); 100 }; 101 102 let package = { 103 let package_str = package.value(); 104 if package_str.contains('/') { 105 return Err(syn::Error::new( 106 stream.span(), 107 "`package` should use '.' as a separator", 108 )); 109 } 110 substitute_package_chars(&package_str) 111 }; 112 let class = substitute_class_chars(&class.value()); 113 let class_desc = format!("{package}_{class}"); 114 115 Ok(Self { 116 class_desc, 117 method_name, 118 panic_returns, 119 }) 120 } 121 } 122 123 #[cfg(test)] 124 mod tests { 125 use super::*; 126 use syn::parse_quote; 127 128 #[test] can_parse()129 fn can_parse() { 130 let meta: JniMethodMeta = parse_quote! { 131 package = "com.example", 132 class = "Foo.Inner", 133 panic_returns = false, 134 }; 135 assert_eq!("com_example_Foo_00024Inner", &meta.class_desc); 136 assert!(meta.panic_returns.is_some()); 137 } 138 139 #[test] can_parse_missing_panic_returns()140 fn can_parse_missing_panic_returns() { 141 let meta: JniMethodMeta = parse_quote! { 142 package = "com.example", 143 class = "Foo.Inner", 144 }; 145 assert_eq!("com_example_Foo_00024Inner", &meta.class_desc); 146 assert!(meta.panic_returns.is_none()); 147 } 148 149 #[test] 150 #[should_panic] double_package()151 fn double_package() { 152 let _: JniMethodMeta = parse_quote! { 153 package = "com.example", 154 package = "com.example", 155 class = "Foo.Inner", 156 panic_returns = false, 157 }; 158 } 159 160 #[test] 161 #[should_panic] package_uses_slashes()162 fn package_uses_slashes() { 163 let _: JniMethodMeta = parse_quote! { 164 package = "com/example", 165 class = "Foo.Inner", 166 panic_returns = false, 167 }; 168 } 169 170 #[test] 171 #[should_panic] missing_package()172 fn missing_package() { 173 let _: JniMethodMeta = parse_quote! { 174 class = "Foo.Inner", 175 panic_returns = false, 176 }; 177 } 178 179 #[test] 180 #[should_panic] double_class()181 fn double_class() { 182 let _: JniMethodMeta = parse_quote! { 183 package = "com.example", 184 class = "Foo.Inner", 185 class = "Foo.Inner", 186 panic_returns = false, 187 }; 188 } 189 190 #[test] 191 #[should_panic] missing_class()192 fn missing_class() { 193 let _: JniMethodMeta = parse_quote! { 194 package = "com.example", 195 panic_returns = false, 196 }; 197 } 198 199 #[test] 200 #[should_panic] double_panic_returns()201 fn double_panic_returns() { 202 let _: JniMethodMeta = parse_quote! { 203 package = "com.example", 204 class = "Foo.Inner", 205 panic_returns = false, 206 panic_returns = false, 207 }; 208 } 209 } 210