blob: 540fdd6a013b3635ae7364b4f58647d0bea28049 [file] [log] [blame]
Marcel Hlopkoca84ff42021-12-09 14:15:14 +00001// Part of the Crubit project, under the Apache License v2.0 with LLVM
2// Exceptions. See /LICENSE for license information.
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
4
Marcel Hlopkoca84ff42021-12-09 14:15:14 +00005/// Asserts that the `input` contains the `pattern` as a subtree.
6///
7/// Pattern can use `...` wildcard in a group, then any content of the
Marcel Hlopko803696f2021-12-14 10:07:45 +00008/// `proc_macro2::Group` will match the pattern. Wildcards cannot match group
9/// delimiters, and that therefore the tokens matched by a wildcard cannot
10/// straddle a group boundary. If the wildcard is mixed with regular tokens the
11/// wildcard can match 0 or many tokens and the matcher will backtrack and try
12/// to find any possible match. Order of regular tokens is significant.
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000013///
14/// Examples where matching succeeds:
15/// ```rust
16/// assert_cc_matches!(
17/// quote!{ void foo() {} },
18/// quote!{ void foo() {} });
19/// assert_cc_matches!(
20/// quote!{ void foo() {} },
21/// quote!{ foo() });
Marcel Hlopko1e448aa2021-12-10 07:37:51 +000022/// assert_cc_matches!(
Marcel Hlopko803696f2021-12-14 10:07:45 +000023/// quote!{ void foo() { bar(); baz(); qux(); } },
24/// quote!{ void foo() { bar(); ... qux(); } });
25/// // "backtracking example"
26/// assert_cc_matches!(
27/// quote!{ void foo() { a(); b(); c(); d(); c(); a(); },
28/// quote!{ { a(); ... c(); a(); } });
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000029/// ```
30///
31/// Example where matching fails:
32/// ```rust
33/// assert_cc_matches!(
34/// quote!{ void foo() { bar(); baz(); } },
Marcel Hlopko1e448aa2021-12-10 07:37:51 +000035/// quote!{ void foo() { bar(); } });
Marcel Hlopko803696f2021-12-14 10:07:45 +000036/// assert_cc_matches!(
37/// quote!{ void foo() { bar(); } },
38/// quote!{ void ... bar() });
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000039/// ```
40#[macro_export]
41macro_rules! assert_cc_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +000042 ($input:expr, $pattern:expr $(,)*) => {
Lukasz Anforowiczae9be082022-10-05 07:33:57 -070043 $crate::internal::match_tokens(
44 &$input,
45 &$pattern,
46 &$crate::internal::cc_tokens_to_formatted_string)
47 .expect("input unexpectedly didn't match the pattern");
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000048 };
49}
50
51/// Like `assert_cc_matches!`, but also formats the input in the error message
52/// using rustfmt.
53#[macro_export]
54macro_rules! assert_rs_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +000055 ($input:expr, $pattern:expr $(,)*) => {
Marcel Hlopko9fc69402021-12-10 13:12:34 +000056 $crate::internal::match_tokens(
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -070057 &$input,
58 &$pattern,
59 &$crate::internal::rs_tokens_to_formatted_string_for_tests,
60 )
61 .expect("input unexpectedly didn't match the pattern");
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000062 };
63}
64
65/// Asserts that the `input` does not contain the `pattern`.
66///
67/// Pattern can use `...` wildcard. See `assert_cc_matches` for details.
68#[macro_export]
69macro_rules! assert_cc_not_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +000070 ($input:expr, $pattern:expr $(,)*) => {
Lukasz Anforowiczae9be082022-10-05 07:33:57 -070071 $crate::internal::mismatch_tokens(
72 &$input,
73 &$pattern,
74 &$crate::internal::cc_tokens_to_formatted_string)
75 .unwrap();
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000076 };
77}
78
79/// Like `assert_cc_not_matches!`, but also formats the input in the error
80/// message using rustfmt.
81#[macro_export]
82macro_rules! assert_rs_not_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +000083 ($input:expr, $pattern:expr $(,)*) => {
Googlerfdd4e562022-01-11 12:44:55 +000084 $crate::internal::mismatch_tokens(
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -070085 &$input,
86 &$pattern,
87 &$crate::internal::rs_tokens_to_formatted_string_for_tests,
88 )
89 .unwrap();
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000090 };
91}
92
Marcel Hlopko9fc69402021-12-10 13:12:34 +000093/// Only used to make stuff needed by exported macros available
94pub mod internal {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000095
Lukasz Anforowiczdd44d982022-09-22 10:09:37 -070096 use anyhow::{anyhow, Result};
Marcel Hlopko9fc69402021-12-10 13:12:34 +000097 pub use proc_macro2::TokenStream;
Marcel Hlopko803696f2021-12-14 10:07:45 +000098 use proc_macro2::TokenTree;
Marcel Hlopko9fc69402021-12-10 13:12:34 +000099 use std::iter;
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -0700100 pub use token_stream_printer::{
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700101 cc_tokens_to_formatted_string, rs_tokens_to_formatted_string,
102 rs_tokens_to_formatted_string_for_tests,
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -0700103 };
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000104
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000105 #[derive(Debug)]
106 enum MatchInfo {
107 // Successful match with the suffix of the `input` stream that follows the match.
108 Match { input_suffix: TokenStream },
109 Mismatch(Mismatch),
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000110 }
111
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000112 #[derive(Debug)]
113 struct Mismatch {
114 match_length: usize,
115 messages: Vec<String>,
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000116 }
117
Marcel Hlopko803696f2021-12-14 10:07:45 +0000118 impl Mismatch {
119 fn for_no_partial_match() -> Self {
120 Mismatch {
121 match_length: 0,
122 messages: vec![
123 "not even a partial match of the pattern throughout the input".to_string(),
124 ],
125 }
126 }
127
128 fn for_input_ended(
129 match_length: usize,
130 pattern_suffix: TokenStream,
131 pattern: TokenStream,
132 input: TokenStream,
133 ) -> Self {
134 Mismatch {
135 match_length,
136 messages: vec![
137 format!("expected '{}' but the input already ended", pattern_suffix),
138 format!("expected '{}' got '{}'", pattern, input),
139 ],
140 }
141 }
142 }
143
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000144 pub fn match_tokens<ToStringFn>(
145 input: &TokenStream,
146 pattern: &TokenStream,
Googlerfdd4e562022-01-11 12:44:55 +0000147 to_string_fn: &ToStringFn,
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000148 ) -> Result<()>
149 where
150 ToStringFn: Fn(TokenStream) -> Result<String>,
151 {
152 let iter = input.clone().into_iter();
Marcel Hlopko803696f2021-12-14 10:07:45 +0000153 let mut best_mismatch = Mismatch::for_no_partial_match();
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000154
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000155 let mut stack = vec![iter];
156 while let Some(mut iter) = stack.pop() {
157 loop {
158 match match_prefix(iter.clone(), pattern.clone()) {
159 MatchInfo::Match { input_suffix: _ } => return Ok(()),
160 MatchInfo::Mismatch(mismatch) => {
161 if best_mismatch.match_length < mismatch.match_length {
162 best_mismatch = mismatch
163 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000164 }
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000165 };
166 if let Some(next) = iter.next() {
167 if let TokenTree::Group(ref group) = next {
168 stack.push(group.stream().into_iter());
169 };
170 } else {
171 break;
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000172 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000173 }
174 }
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000175
176 assert!(!best_mismatch.messages.is_empty());
177 let input_string = to_string_fn(input.clone())?;
178 let mut error = anyhow!(format!("input:\n\n```\n{}\n```", input_string));
179 for msg in best_mismatch.messages.into_iter().rev() {
180 error = error.context(msg);
181 }
182 Err(error)
183 }
184
Googlerfdd4e562022-01-11 12:44:55 +0000185 pub fn mismatch_tokens<ToStringFn>(
186 input: &TokenStream,
187 pattern: &TokenStream,
188 to_string_fn: &ToStringFn,
189 ) -> Result<()>
190 where
191 ToStringFn: Fn(TokenStream) -> Result<String>,
192 {
193 if match_tokens(input, pattern, to_string_fn).is_ok() {
194 let input_string = to_string_fn(input.clone())?;
195 Err(anyhow!(format!(
196 "input unexpectedly matched the pattern. input:\n\n```\n{}\n```",
197 input_string
198 )))
199 } else {
200 Ok(())
201 }
202 }
203
Marcel Hlopko803696f2021-12-14 10:07:45 +0000204 // This implementation uses naive backtracking algorithm that is in the worst
205 // case O(2^n) in the number of wildcards. In practice this is not so bad
206 // because wildcards only match their current group, they don't descend into
207 // subtrees and they don't match outside. Still, it may be possible to
208 // reimplement this using NFA and end up with simpler, more regular code
209 // while still providing reasonable error messages on mismatch.
210 // TODO(hlopko): Try to reimplement matching using NFA.
211 fn match_prefix(
212 input: impl Iterator<Item = TokenTree> + Clone,
213 pattern: TokenStream,
214 ) -> MatchInfo {
215 let mut input_iter = input.clone();
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000216 let mut pattern_iter = pattern.clone().into_iter().peekable();
217 let mut match_counter = 0;
Marcel Hlopko803696f2021-12-14 10:07:45 +0000218 let mut best_mismatch = Mismatch::for_no_partial_match();
219 let mut update_best_mismatch = |mismatch: Mismatch| {
220 if mismatch.match_length > best_mismatch.match_length {
221 best_mismatch = mismatch;
222 }
223 };
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000224 while let Some(actual_token) = input_iter.next() {
Devin Jeanpierre2b4182b2022-04-19 08:23:50 -0700225 if is_whitespace_token(&actual_token) {
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000226 continue;
227 }
228
Marcel Hlopko803696f2021-12-14 10:07:45 +0000229 if starts_with_wildcard(to_stream(&pattern_iter)) {
230 // branch off to matching the token after the wildcard
231 match match_after_wildcard(
232 reinsert_token(input_iter.clone(), actual_token).into_iter(),
233 input.clone(),
234 skip_wildcard(pattern_iter.clone()),
235 ) {
236 MatchInfo::Mismatch(mut mismatch) => {
237 mismatch.match_length += match_counter;
238 update_best_mismatch(mismatch);
239 }
240 match_info => {
241 return match_info;
242 }
243 }
244 // and if that didn't work, consume one more token by the wildcard
245 continue;
246 }
247
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000248 if let Some(pattern_token) = pattern_iter.next() {
249 if let MatchInfo::Mismatch(mut mismatch) = match_tree(&actual_token, &pattern_token)
250 {
251 mismatch.messages.push(format!(
252 "expected '{}' got '{}'",
253 pattern,
254 input.collect::<TokenStream>()
255 ));
256 mismatch.match_length += match_counter;
257 return MatchInfo::Mismatch(mismatch);
258 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000259 } else {
Marcel Hlopko803696f2021-12-14 10:07:45 +0000260 return MatchInfo::Match { input_suffix: reinsert_token(input_iter, actual_token) };
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000261 }
262 match_counter += 1;
263 }
264
Marcel Hlopko803696f2021-12-14 10:07:45 +0000265 if pattern_iter.peek().is_none() {
266 return MatchInfo::Match { input_suffix: TokenStream::new() };
267 }
268 if is_wildcard(to_stream(&pattern_iter)) {
269 return MatchInfo::Match { input_suffix: TokenStream::new() };
270 }
271
272 update_best_mismatch(Mismatch::for_input_ended(
273 match_counter,
274 to_stream(&pattern_iter),
275 pattern,
276 to_stream(&input),
277 ));
278 MatchInfo::Mismatch(best_mismatch)
279 }
280
281 fn match_after_wildcard(
282 input_iter: impl Iterator<Item = TokenTree> + Clone,
283 input: impl Iterator<Item = TokenTree> + Clone,
284 pattern: TokenStream,
285 ) -> MatchInfo {
286 match match_prefix(input_iter.clone(), pattern.clone()) {
287 MatchInfo::Match { input_suffix } if input_suffix.is_empty() => {
288 MatchInfo::Match { input_suffix }
289 }
290 MatchInfo::Match { input_suffix } => {
291 let match_input_length = input_iter.count() + 1;
292 let suffix_length = input_suffix.into_iter().count();
293 MatchInfo::Mismatch(Mismatch::for_input_ended(
294 match_input_length - suffix_length,
295 pattern.clone(),
296 pattern,
297 to_stream(&input),
298 ))
299 }
300 mismatch => mismatch,
301 }
302 }
303
304 fn to_stream(iter: &(impl Iterator<Item = TokenTree> + Clone)) -> TokenStream {
305 iter.clone().collect::<TokenStream>()
306 }
307
308 fn reinsert_token(
309 iter: impl Iterator<Item = TokenTree> + Clone,
310 token: TokenTree,
311 ) -> TokenStream {
312 iter::once(token).chain(iter).collect::<TokenStream>()
313 }
314
Devin Jeanpierre2b4182b2022-04-19 08:23:50 -0700315 fn is_whitespace_token(token: &TokenTree) -> bool {
316 matches!(token, TokenTree::Ident(id) if id == "__NEWLINE__" || id == "__SPACE__")
Marcel Hlopko803696f2021-12-14 10:07:45 +0000317 }
318
319 fn is_wildcard(pattern: TokenStream) -> bool {
320 format!("{}", pattern) == "..."
321 }
322
323 fn starts_with_wildcard(pattern: TokenStream) -> bool {
324 format!("{}", pattern).starts_with("...")
325 }
326
327 fn skip_wildcard(pattern: impl Iterator<Item = TokenTree> + Clone) -> TokenStream {
328 assert!(starts_with_wildcard(to_stream(&pattern)));
329 pattern.skip(3).collect::<TokenStream>()
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000330 }
331
332 fn match_tree(actual_token: &TokenTree, pattern_token: &TokenTree) -> MatchInfo {
333 match (actual_token, pattern_token) {
334 (TokenTree::Group(ref actual_group), TokenTree::Group(ref pattern_group)) => {
335 if actual_group.delimiter() != pattern_group.delimiter() {
336 return MatchInfo::Mismatch(Mismatch {
337 match_length: 0,
338 messages: vec![format!(
339 "expected delimiter {:?} for group '{}' but got {:?} for group '{}'",
340 pattern_group.delimiter(),
341 Into::<TokenStream>::into(pattern_token.clone()),
342 actual_group.delimiter(),
343 Into::<TokenStream>::into(actual_token.clone()),
344 )],
345 });
346 }
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000347 let match_info =
348 match_prefix(actual_group.stream().into_iter(), pattern_group.stream());
349 match match_info {
350 MatchInfo::Match { input_suffix } => {
351 if input_suffix
352 .clone()
353 .into_iter()
Devin Jeanpierre2b4182b2022-04-19 08:23:50 -0700354 .filter(|token| !is_whitespace_token(token))
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000355 .count()
356 != 0
357 {
358 MatchInfo::Mismatch(Mismatch {
359 match_length: 0,
360 messages: vec![format!(
361 "matched the entire pattern but the input still contained '{}'",
362 input_suffix
363 )],
364 })
365 } else {
366 MatchInfo::Match { input_suffix: TokenStream::new() }
367 }
368 }
369 mismatch => mismatch,
370 }
371 }
372 (ref actual, ref pattern) => {
373 let actual_src = format!("{}", actual);
374 let pattern_src = format!("{}", pattern);
375 if actual_src == pattern_src {
376 MatchInfo::Match { input_suffix: TokenStream::new() }
377 } else {
378 MatchInfo::Mismatch(Mismatch {
379 match_length: 0,
380 messages: vec![format!("expected '{}' but got '{}'", pattern, actual)],
381 })
382 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000383 }
384 }
385 }
386}
387
388#[cfg(test)]
389mod tests {
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000390 use super::internal::*;
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000391 use super::*;
392 use quote::quote;
393
394 macro_rules! assert_rs_cc_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000395 ($input:expr, $pattern:expr $(,)*) => {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000396 $crate::assert_cc_matches!($input, $pattern);
397 $crate::assert_rs_matches!($input, $pattern);
398 };
399 }
400
401 #[test]
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000402 fn test_optional_trailing_comma() {
403 assert_rs_matches!(quote! {x}, quote! {x});
404 assert_rs_matches!(quote! {x}, quote! {x},);
Marcel Hlopko016cab02021-12-16 14:29:35 +0000405
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000406 assert_cc_matches!(quote! {x}, quote! {x});
407 assert_cc_matches!(quote! {x}, quote! {x},);
Marcel Hlopko016cab02021-12-16 14:29:35 +0000408
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000409 assert_rs_not_matches!(quote! {x}, quote! {y});
410 assert_rs_not_matches!(quote! {x}, quote! {y},);
Marcel Hlopko803696f2021-12-14 10:07:45 +0000411
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000412 assert_cc_not_matches!(quote! {x}, quote! {y});
413 assert_cc_not_matches!(quote! {x}, quote! {y},);
Marcel Hlopko016cab02021-12-16 14:29:35 +0000414 }
415
416 #[test]
417 fn test_assert_not_matches_accepts_not_matching_pattern() {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000418 assert_cc_not_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
419 assert_rs_not_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
420 }
421
422 #[test]
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700423 #[should_panic(expected =
424r#"input unexpectedly matched the pattern. input:
425
426```
427fn foo() {}
428```"#)]
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000429 fn test_assert_cc_not_matches_panics_on_match() {
430 assert_cc_not_matches!(quote! { fn foo() {} }, quote! { fn foo() {} });
431 }
432
433 #[test]
Googlerfdd4e562022-01-11 12:44:55 +0000434 #[should_panic(expected = "input:\n\n```\nfn foo() {}\n\n```")]
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000435 fn test_assert_rs_not_matches_panics_on_match() {
436 assert_rs_not_matches!(quote! { fn foo() {} }, quote! { fn foo() {} });
437 }
438
439 #[test]
440 fn test_assert_cc_matches_accepts_matching_pattern() {
441 assert_rs_cc_matches!(quote! { fn foo() {} }, quote! { fn foo() {} });
442 }
443
444 #[test]
445 #[should_panic]
446 fn test_assert_cc_matches_panics_on_mismatch() {
447 assert_cc_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
448 }
449
450 #[test]
451 #[should_panic]
452 fn test_assert_rs_matches_panics_on_mismatch() {
453 assert_rs_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
454 }
455
456 #[test]
457 fn test_accept_siblings() {
458 assert_rs_cc_matches!(quote! {a b c d}, quote! {a b c d});
459 assert_rs_cc_matches!(quote! {a b c d}, quote! {a b});
460 assert_rs_cc_matches!(quote! {a b c d}, quote! {b c});
461 assert_rs_cc_matches!(quote! {a b c d}, quote! {c d});
462 }
463
464 #[test]
465 fn test_accept_subtrees() {
466 assert_rs_cc_matches!(quote! {impl SomeStruct { fn foo() {} }}, quote! {fn foo() {}});
467 }
468
469 #[test]
470 #[should_panic]
471 fn test_cc_reject_partial_subtree() {
472 assert_cc_matches!(quote! {fn foo() {a(); b();}}, quote! {fn foo() { a(); }});
473 }
474
475 #[test]
476 #[should_panic]
477 fn test_rs_reject_partial_subtree() {
478 assert_rs_matches!(quote! {fn foo() {a(); b();}}, quote! {fn foo() { a(); }});
479 }
480
481 #[test]
482 fn test_cc_error_message() {
483 assert_eq!(
484 format!(
485 "{:?}",
486 match_tokens(
487 &quote! {struct A { int a; int b; };},
488 &quote! {struct B},
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700489 &cc_tokens_to_formatted_string
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000490 )
491 .expect_err("unexpected match")
492 ),
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700493 r#"expected 'B' but got 'A'
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000494
495Caused by:
496 0: expected 'struct B' got 'struct A { int a ; int b ; } ;'
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700497 1: input:
498
499 ```
500 struct A {
501 int a;
502 int b;
503 };
504 ```"#
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000505 );
506 }
507
508 #[test]
509 fn test_rustfmt_in_rs_error_message() {
510 assert_eq!(
511 format!(
512 "{:?}",
513 match_tokens(
514 &quote! {struct A { a: i64, b: i64 }},
515 &quote! {struct B},
Lukasz Anforowicz54ff3182022-05-06 07:17:58 -0700516 &rs_tokens_to_formatted_string_for_tests,
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000517 )
518 .expect_err("unexpected match")
519 ),
520 "expected 'B' but got 'A'
521
522Caused by:
523 0: expected 'struct B' got 'struct A { a : i64 , b : i64 }'
524 1: input:\n \n ```
525 struct A {
526 a: i64,
527 b: i64,
528 }\n \n ```"
529 );
530 }
531
532 #[test]
533 fn test_reject_unfinished_pattern() {
534 assert_eq!(
535 format!(
536 "{:#}",
537 match_tokens(
538 &quote! {fn foo() {}},
539 &quote! {fn foo() {} struct X {}},
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700540 &rs_tokens_to_formatted_string_for_tests
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000541 )
542 .expect_err("unexpected match")
543 ),
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700544 r#"expected 'struct X { }' but the input already ended: expected 'fn foo () { } struct X { }' got 'fn foo () { }': input:
545
546```
547fn foo() {}
548
549```"#
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000550 );
551 }
552
553 #[test]
554 fn test_reject_different_delimiters() {
555 assert_eq!(
556 format!(
557 "{:#}",
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700558 match_tokens(&quote! {fn foo() {}},
559 &quote! {fn foo() ()},
560 &rs_tokens_to_formatted_string_for_tests)
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000561 .expect_err("unexpected match")
562 ),
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700563 r#"expected delimiter Parenthesis for group '()' but got Brace for group '{ }': expected 'fn foo () ()' got 'fn foo () { }': input:
564
565```
566fn foo() {}
567
568```"#
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000569 );
570 }
571
572 #[test]
573 fn test_reject_mismatch_inside_group() {
574 assert_eq!(
575 format!(
576 "{:#}",
577 match_tokens(
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700578 &quote! {fn foo() { let a = 1; let b = 2; }},
579 &quote! {fn foo() { let a = 1; let c = 2; }},
580 &rs_tokens_to_formatted_string_for_tests
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000581 )
582 .expect_err("unexpected match")
583 ),
584 "expected 'c' but got 'b': \
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700585 expected 'let a = 1 ; let c = 2 ;' got 'let a = 1 ; let b = 2 ;': \
586 expected 'fn foo () { let a = 1 ; let c = 2 ; }' \
587 got 'fn foo () { let a = 1 ; let b = 2 ; }': \
588 input:\n\n```\nfn foo() {\n let a = 1;\n let b = 2;\n}\n\n```"
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000589 );
590 }
591
592 #[test]
593 fn test_accept_wildcard_in_group() {
594 assert_rs_cc_matches!(
595 quote! {fn foo() -> bool { return false; }},
596 quote! {fn foo() -> bool {...}}
597 );
598 }
599
600 #[test]
601 fn test_ignore_newlines() {
602 assert_rs_cc_matches!(
603 quote! {__NEWLINE__ fn __NEWLINE__ foo __NEWLINE__ (
Devin Jeanpierre2b4182b2022-04-19 08:23:50 -0700604 __NEWLINE__ a __NEWLINE__ : __NEWLINE__ usize __NEWLINE__) {}},
605 quote! {fn foo(a: usize) {}}
606 );
607 }
608
609 #[test]
610 fn test_ignore_space() {
611 assert_rs_cc_matches!(
612 quote! {__SPACE__ fn __SPACE__ foo __SPACE__ (
613 __SPACE__ a __SPACE__ : __SPACE__ usize __SPACE__) {}},
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000614 quote! {fn foo(a: usize) {}}
615 );
616 }
617
618 #[test]
619 fn test_reject_unfinished_input_inside_group() {
620 assert_eq!(
621 format!(
622 "{:#}",
623 match_tokens(
624 &quote! {impl Drop { fn drop(&mut self) { drop_impl(); }}},
625 &quote! {fn drop(&mut self) {}},
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700626 &rs_tokens_to_formatted_string_for_tests
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000627 )
628 .expect_err("unexpected match")
629 ),
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700630 r#"matched the entire pattern but the input still contained 'drop_impl () ;': expected 'fn drop (& mut self) { }' got 'fn drop (& mut self) { drop_impl () ; }': input:
631
632```
633impl Drop {
634 fn drop(&mut self) {
635 drop_impl();
636 }
637}
638
639```"#
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000640 );
641 assert_eq!(
642 format!(
643 "{:#}",
644 match_tokens(
645 &quote! {impl Drop { fn drop(&mut self) { drop_impl1(); drop_impl2(); }}},
646 &quote! {fn drop(&mut self) { drop_impl1(); }},
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700647 &rs_tokens_to_formatted_string_for_tests
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000648 )
649 .expect_err("unexpected match")
650 ),
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700651 r#"matched the entire pattern but the input still contained 'drop_impl2 () ;': expected 'fn drop (& mut self) { drop_impl1 () ; }' got 'fn drop (& mut self) { drop_impl1 () ; drop_impl2 () ; }': input:
652
653```
654impl Drop {
655 fn drop(&mut self) {
656 drop_impl1();
657 drop_impl2();
658 }
659}
660
661```"#
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000662 );
663 }
664
665 #[test]
666 fn test_accept_unfinished_input_with_only_newlines() {
667 assert_rs_cc_matches!(quote! {fn foo() { __NEWLINE__ }}, quote! {fn foo() {}});
668 assert_rs_cc_matches!(quote! {fn foo() { a(); __NEWLINE__ }}, quote! {fn foo() { a(); }});
669 }
670
671 #[test]
Marcel Hlopko803696f2021-12-14 10:07:45 +0000672 fn test_wildcard_in_the_beginning_of_the_group() {
673 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a ... ] });
674 assert_rs_cc_matches!(quote! { [ a a b b c c ] }, quote! { [ a a ... ] });
675 }
676 #[test]
677 fn test_wildcard_in_the_middle_of_the_group() {
678 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a ... c ] });
679 assert_rs_cc_matches!(quote! { [ a a b b c c ] }, quote! { [ a a ... c c ] });
680 }
681 #[test]
682 fn test_wildcard_in_the_end_of_the_group() {
683 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ ... c ] });
684 assert_rs_cc_matches!(quote! { [ a a b b c c ] }, quote! { [ ... c c ] });
685 }
686
687 #[test]
688 fn test_wildcard_not_consuming_anything_in_group() {
689 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ ... a b c ] });
690 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a ... b c ] });
691 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a b ... c ] });
692 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a b c ... ] });
693 }
694
695 #[test]
696 fn test_error_message_shows_the_longest_match_with_wildcards() {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000697 assert_eq!(
698 format!(
699 "{:#}",
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700700 match_tokens(&quote! { [ a b b ] },
701 &quote! { [ a ... c ]},
702 &|tokens: TokenStream| Ok(tokens.to_string()))
Marcel Hlopko803696f2021-12-14 10:07:45 +0000703 .expect_err("unexpected match")
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000704 ),
Marcel Hlopko803696f2021-12-14 10:07:45 +0000705 // the error message shows "longer match" with more tokens consumed by the wildcard
706 "expected 'c' but got 'b': \
707 expected 'c' got 'b b': \
708 expected '[a ... c]' got '[a b b]': \
709 input:\n\n```\n[a b b]\n```"
710 );
711 assert_eq!(
712 format!(
713 "{:#}",
Lukasz Anforowiczae9be082022-10-05 07:33:57 -0700714 match_tokens(&quote! {[ a b b ]},
715 &quote! { [ a ... b c ]},
716 &|tokens: TokenStream| Ok(tokens.to_string()))
Marcel Hlopko803696f2021-12-14 10:07:45 +0000717 .expect_err("unexpected match")
718 ),
719 // the error message shows "longer match" with branching off the wildcard earlier
720 "expected 'c' but got 'b': \
721 expected 'b c' got 'b b': \
722 expected '[a ... b c]' got '[a b b]': \
723 input:\n\n```\n[a b b]\n```"
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000724 );
725 }
726}