• 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 //! 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