• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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