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 //! Syntax trees for the arguments to the `call_method!` series of proc macros.
16
17 use syn::{
18 parse::{Parse, ParseStream},
19 punctuated::Punctuated,
20 Expr, LitStr, Token,
21 };
22
23 /// The trailing method arguments
24 type Args = Punctuated<Expr, Token![,]>;
25
26 /// See [`crate::call_constructor!`].
27 pub struct ConstructorArgs {
28 pub env: Expr,
29 pub cls: Expr,
30 pub sig: LitStr,
31 pub args: Args,
32 }
33
34 impl Parse for ConstructorArgs {
parse(stream: ParseStream<'_>) -> syn::Result<Self>35 fn parse(stream: ParseStream<'_>) -> syn::Result<Self> {
36 let env = CommaSep::parse(stream)?.node;
37 let cls = CommaSep::parse(stream)?.node;
38 let sig = stream.parse()?;
39 let args = parse_args(stream)?;
40
41 Ok(Self {
42 env,
43 cls,
44 sig,
45 args,
46 })
47 }
48 }
49
50 /// See [`crate::call_static_method!`].
51 pub struct StaticArgs {
52 pub env: Expr,
53 pub cls: Expr,
54 pub name: Expr,
55 pub sig: LitStr,
56 pub args: Args,
57 }
58
59 impl Parse for StaticArgs {
parse(stream: ParseStream<'_>) -> syn::Result<Self>60 fn parse(stream: ParseStream<'_>) -> syn::Result<Self> {
61 let env = CommaSep::parse(stream)?.node;
62 let cls = CommaSep::parse(stream)?.node;
63 let name = CommaSep::parse(stream)?.node;
64 let sig = stream.parse()?;
65 let args = parse_args(stream)?;
66
67 Ok(Self {
68 env,
69 cls,
70 name,
71 sig,
72 args,
73 })
74 }
75 }
76
77 /// See [`crate::call_method!`].
78 pub struct InstanceArgs {
79 pub env: Expr,
80 pub cls: Expr,
81 pub name: Expr,
82 pub sig: LitStr,
83 pub this: Expr,
84 pub args: Args,
85 }
86
87 impl Parse for InstanceArgs {
parse(stream: ParseStream<'_>) -> syn::Result<Self>88 fn parse(stream: ParseStream<'_>) -> syn::Result<Self> {
89 let env = CommaSep::parse(stream)?.node;
90 let cls = CommaSep::parse(stream)?.node;
91 let name = CommaSep::parse(stream)?.node;
92 let sig = CommaSep::parse(stream)?.node;
93 let this = stream.parse()?;
94 let args = parse_args(stream)?;
95
96 Ok(Self {
97 env,
98 cls,
99 name,
100 sig,
101 this,
102 args,
103 })
104 }
105 }
106
107 /// Parses the variable number of arguments to the method. A leading comma is required when there
108 /// are arguments being passed.
parse_args(stream: ParseStream<'_>) -> syn::Result<Args>109 fn parse_args(stream: ParseStream<'_>) -> syn::Result<Args> {
110 Ok(if stream.is_empty() {
111 Punctuated::new()
112 } else {
113 let _ = stream.parse::<Token![,]>()?;
114 Punctuated::parse_terminated(stream)?
115 })
116 }
117
118 /// A syntax node followed by a comma.
119 #[allow(dead_code)]
120 pub struct CommaSep<T> {
121 pub node: T,
122 pub comma: Token![,],
123 }
124
125 impl<T: Parse> Parse for CommaSep<T> {
parse(stream: ParseStream<'_>) -> syn::Result<Self>126 fn parse(stream: ParseStream<'_>) -> syn::Result<Self> {
127 Ok(Self {
128 node: stream.parse()?,
129 comma: stream.parse()?,
130 })
131 }
132 }
133
134 #[cfg(test)]
135 #[allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
136 mod test {
137 use super::*;
138 use quote::quote;
139 use syn::parse::Parser;
140
141 #[test]
comma_sep_correct()142 fn comma_sep_correct() {
143 let ret: CommaSep<syn::Ident> = syn::parse2(quote![abc,]).unwrap();
144
145 assert_eq!(ret.node, "abc");
146 }
147
148 #[test]
comma_sep_incorrect()149 fn comma_sep_incorrect() {
150 assert!(syn::parse2::<CommaSep<syn::Ident>>(quote![abc]).is_err());
151 assert!(syn::parse2::<CommaSep<syn::Ident>>(quote![,abc]).is_err());
152 assert!(syn::parse2::<CommaSep<syn::Ident>>(quote![,]).is_err());
153 }
154
155 #[test]
parse_args_no_args()156 fn parse_args_no_args() {
157 let args = parse_args.parse2(quote![]).unwrap();
158
159 assert_eq!(0, args.len());
160 }
161
162 #[test]
parse_args_no_args_extra_comma()163 fn parse_args_no_args_extra_comma() {
164 let args = parse_args.parse2(quote![,]).unwrap();
165
166 assert_eq!(0, args.len());
167 }
168
169 #[test]
parse_args_single_no_trailing()170 fn parse_args_single_no_trailing() {
171 let args = parse_args.parse2(quote![, foo]).unwrap();
172
173 assert_eq!(1, args.len());
174 }
175
176 #[test]
parse_args_single_trailing()177 fn parse_args_single_trailing() {
178 let args = parse_args.parse2(quote![, foo,]).unwrap();
179
180 assert_eq!(1, args.len());
181 }
182
183 #[test]
parse_args_multi_no_trailing()184 fn parse_args_multi_no_trailing() {
185 let args = parse_args.parse2(quote![, one, two, three]).unwrap();
186
187 assert_eq!(3, args.len());
188 }
189
190 #[test]
parse_args_multi_trailing()191 fn parse_args_multi_trailing() {
192 let args = parse_args.parse2(quote![, one, two, three,]).unwrap();
193
194 assert_eq!(3, args.len());
195 }
196
197 #[test]
parse_args_error_two_commas()198 fn parse_args_error_two_commas() {
199 let res = parse_args.parse2(quote![, ,]);
200
201 assert!(res.is_err());
202 }
203
204 #[test]
parse_constructor_args()205 fn parse_constructor_args() {
206 let tests = [
207 quote![&mut env, &CLS, "()V"],
208 quote![&mut env, &CLS, "()V",],
209 quote![&mut env, &CLS, "(I)V", 123],
210 quote![&mut env, &CLS, "(I)V", 123,],
211 quote![&mut env, &CLS, "(ILjava/lang/String;)V", 123, &some_str],
212 quote![&mut env, &CLS, "(ILjava/lang/String;)V", 123, &some_str,],
213 ];
214
215 for valid in tests {
216 assert!(
217 syn::parse2::<ConstructorArgs>(valid.clone()).is_ok(),
218 "test: {valid}"
219 );
220 }
221 }
222
223 #[test]
parse_static_method_args()224 fn parse_static_method_args() {
225 let tests = [
226 quote![&mut env, &CLS, "methodName", "()V"],
227 quote![&mut env, &CLS, "methodName", "()V",],
228 quote![&mut env, &CLS, "methodName", "(I)V", 123],
229 quote![&mut env, &CLS, "methodName", "(I)V", 123,],
230 quote![
231 &mut env,
232 &CLS,
233 "methodName",
234 "(ILjava/lang/String;)V",
235 123,
236 &some_str
237 ],
238 quote![
239 &mut env,
240 &CLS,
241 "methodName",
242 "(ILjava/lang/String;)V",
243 123,
244 &some_str,
245 ],
246 ];
247
248 for valid in tests {
249 assert!(
250 syn::parse2::<StaticArgs>(valid.clone()).is_ok(),
251 "test: {valid}"
252 );
253 }
254 }
255
256 #[test]
parse_method_args()257 fn parse_method_args() {
258 let tests = [
259 quote![&mut env, &CLS, "methodName", "()V", &this_obj],
260 quote![&mut env, &CLS, "methodName", "()V", &this_obj,],
261 quote![&mut env, &CLS, "methodName", "(I)V", &this_obj, 123],
262 quote![&mut env, &CLS, "methodName", "(I)V", &this_obj, 123,],
263 quote![
264 &mut env,
265 &CLS,
266 "methodName",
267 "(ILjava/lang/String;)V",
268 &this_obj,
269 123,
270 &some_str
271 ],
272 quote![
273 &mut env,
274 &CLS,
275 "methodName",
276 "(ILjava/lang/String;)V",
277 &this_obj,
278 123,
279 &some_str,
280 ],
281 ];
282
283 for valid in tests {
284 assert!(
285 syn::parse2::<InstanceArgs>(valid.clone()).is_ok(),
286 "test: {valid}"
287 );
288 }
289 }
290 }
291