blob: 62e13333b7eba048904bfdc2fa2f5635406fda22 [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 $(,)*) => {
Googlerfdd4e562022-01-11 12:44:55 +000043 $crate::internal::match_tokens(&$input, &$pattern, &$crate::internal::tokens_to_string)
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000044 .expect("input unexpectedly didn't match the pattern");
45 };
46}
47
48/// Like `assert_cc_matches!`, but also formats the input in the error message
49/// using rustfmt.
50#[macro_export]
51macro_rules! assert_rs_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +000052 ($input:expr, $pattern:expr $(,)*) => {
Marcel Hlopko9fc69402021-12-10 13:12:34 +000053 $crate::internal::match_tokens(
54 &$input,
55 &$pattern,
Googlerfdd4e562022-01-11 12:44:55 +000056 &$crate::internal::rs_tokens_to_formatted_string,
Marcel Hlopko9fc69402021-12-10 13:12:34 +000057 )
58 .expect("input unexpectedly didn't match the pattern");
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000059 };
60}
61
62/// Asserts that the `input` does not contain the `pattern`.
63///
64/// Pattern can use `...` wildcard. See `assert_cc_matches` for details.
65#[macro_export]
66macro_rules! assert_cc_not_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +000067 ($input:expr, $pattern:expr $(,)*) => {
Googlerfdd4e562022-01-11 12:44:55 +000068 $crate::internal::mismatch_tokens(&$input, &$pattern, &$crate::internal::tokens_to_string)
69 .unwrap();
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000070 };
71}
72
73/// Like `assert_cc_not_matches!`, but also formats the input in the error
74/// message using rustfmt.
75#[macro_export]
76macro_rules! assert_rs_not_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +000077 ($input:expr, $pattern:expr $(,)*) => {
Googlerfdd4e562022-01-11 12:44:55 +000078 $crate::internal::mismatch_tokens(
Marcel Hlopko9fc69402021-12-10 13:12:34 +000079 &$input,
80 &$pattern,
Googlerfdd4e562022-01-11 12:44:55 +000081 &$crate::internal::rs_tokens_to_formatted_string,
Marcel Hlopko9fc69402021-12-10 13:12:34 +000082 )
Googlerfdd4e562022-01-11 12:44:55 +000083 .unwrap();
Marcel Hlopkoca84ff42021-12-09 14:15:14 +000084 };
85}
86
Marcel Hlopko016cab02021-12-16 14:29:35 +000087/// Like `assert_cc_matches!`, but expects `IR` instance as input. The macro
88/// converts the instance to its corresponding struct expression and matches the
89/// pattern against that.
90#[macro_export]
91macro_rules! assert_ir_matches {
92 ($ir:expr, $pattern:expr $(,)*) => {
93 $crate::internal::match_ir(&$ir, &$pattern)
94 .expect("input unexpectedly didn't match the pattern");
95 };
96}
97
98/// Like `assert_ir_matches`, but asserts that the pattern does not match the
99/// `IR`.
100#[macro_export]
101macro_rules! assert_ir_not_matches {
102 ($ir:expr, $pattern:expr $(,)*) => {
Googlerfdd4e562022-01-11 12:44:55 +0000103 $crate::internal::mismatch_ir(&$ir, &$pattern).unwrap();
Marcel Hlopko016cab02021-12-16 14:29:35 +0000104 };
105}
106
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000107/// Only used to make stuff needed by exported macros available
108pub mod internal {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000109
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000110 use anyhow::{anyhow, Result};
Marcel Hlopko016cab02021-12-16 14:29:35 +0000111 use ir::IR;
112 use itertools::Itertools;
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000113 pub use proc_macro2::TokenStream;
Marcel Hlopko803696f2021-12-14 10:07:45 +0000114 use proc_macro2::TokenTree;
Marcel Hlopko016cab02021-12-16 14:29:35 +0000115 use quote::quote;
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000116 use std::iter;
117 pub use token_stream_printer::{rs_tokens_to_formatted_string, tokens_to_string};
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000118
Marcel Hlopko016cab02021-12-16 14:29:35 +0000119 pub fn match_ir(ir: &IR, pattern: &TokenStream) -> Result<()> {
Googlerfdd4e562022-01-11 12:44:55 +0000120 match_tokens(&ir_to_token_stream(ir)?, pattern, &ir_to_string)
121 }
122
123 pub fn mismatch_ir(ir: &IR, pattern: &TokenStream) -> Result<()> {
124 mismatch_tokens(&ir_to_token_stream(ir)?, pattern, &ir_to_string)
125 }
126
127 fn ir_to_token_stream(ir: &IR) -> Result<TokenStream> {
Marcel Hlopko016cab02021-12-16 14:29:35 +0000128 // derived debug impl doesn't emit commas after the last element of a group,
129 // (for example `[a, b, c]`), but rustfmt automatically adds it (`[a, b,
130 // c,]`). We use rustfmt to format the failure messages. So to make the
131 // input token stream consistent with failure messages we format the
132 // input token stream with rustfmt as well.
Googlerfdd4e562022-01-11 12:44:55 +0000133 Ok(ir_to_string(ir.flat_ir_debug_print().parse().unwrap())?.parse().unwrap())
Marcel Hlopko016cab02021-12-16 14:29:35 +0000134 }
135
136 fn ir_to_string(input: TokenStream) -> Result<String> {
137 // Rustfmt refuses to format some kinds of invalid Rust code. Let's put our IR
138 // struct expression into the body of a function, format it, and then remove
139 // the function.
140 let input_stream = quote! { fn make_rustfmt_happy() { #input } };
141 let formatted = rs_tokens_to_formatted_string(input_stream)?;
142 let snippet = formatted
143 .strip_prefix("fn make_rustfmt_happy() {\n")
144 .unwrap()
145 .strip_suffix("}\n")
146 .unwrap()
147 .lines()
148 .map(|line| &line[4..])
149 .join("\n");
150 Ok(snippet)
151 }
152
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000153 #[derive(Debug)]
154 enum MatchInfo {
155 // Successful match with the suffix of the `input` stream that follows the match.
156 Match { input_suffix: TokenStream },
157 Mismatch(Mismatch),
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000158 }
159
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000160 #[derive(Debug)]
161 struct Mismatch {
162 match_length: usize,
163 messages: Vec<String>,
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000164 }
165
Marcel Hlopko803696f2021-12-14 10:07:45 +0000166 impl Mismatch {
167 fn for_no_partial_match() -> Self {
168 Mismatch {
169 match_length: 0,
170 messages: vec![
171 "not even a partial match of the pattern throughout the input".to_string(),
172 ],
173 }
174 }
175
176 fn for_input_ended(
177 match_length: usize,
178 pattern_suffix: TokenStream,
179 pattern: TokenStream,
180 input: TokenStream,
181 ) -> Self {
182 Mismatch {
183 match_length,
184 messages: vec![
185 format!("expected '{}' but the input already ended", pattern_suffix),
186 format!("expected '{}' got '{}'", pattern, input),
187 ],
188 }
189 }
190 }
191
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000192 pub fn match_tokens<ToStringFn>(
193 input: &TokenStream,
194 pattern: &TokenStream,
Googlerfdd4e562022-01-11 12:44:55 +0000195 to_string_fn: &ToStringFn,
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000196 ) -> Result<()>
197 where
198 ToStringFn: Fn(TokenStream) -> Result<String>,
199 {
200 let iter = input.clone().into_iter();
Marcel Hlopko803696f2021-12-14 10:07:45 +0000201 let mut best_mismatch = Mismatch::for_no_partial_match();
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000202
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000203 let mut stack = vec![iter];
204 while let Some(mut iter) = stack.pop() {
205 loop {
206 match match_prefix(iter.clone(), pattern.clone()) {
207 MatchInfo::Match { input_suffix: _ } => return Ok(()),
208 MatchInfo::Mismatch(mismatch) => {
209 if best_mismatch.match_length < mismatch.match_length {
210 best_mismatch = mismatch
211 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000212 }
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000213 };
214 if let Some(next) = iter.next() {
215 if let TokenTree::Group(ref group) = next {
216 stack.push(group.stream().into_iter());
217 };
218 } else {
219 break;
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000220 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000221 }
222 }
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000223
224 assert!(!best_mismatch.messages.is_empty());
225 let input_string = to_string_fn(input.clone())?;
226 let mut error = anyhow!(format!("input:\n\n```\n{}\n```", input_string));
227 for msg in best_mismatch.messages.into_iter().rev() {
228 error = error.context(msg);
229 }
230 Err(error)
231 }
232
Googlerfdd4e562022-01-11 12:44:55 +0000233 pub fn mismatch_tokens<ToStringFn>(
234 input: &TokenStream,
235 pattern: &TokenStream,
236 to_string_fn: &ToStringFn,
237 ) -> Result<()>
238 where
239 ToStringFn: Fn(TokenStream) -> Result<String>,
240 {
241 if match_tokens(input, pattern, to_string_fn).is_ok() {
242 let input_string = to_string_fn(input.clone())?;
243 Err(anyhow!(format!(
244 "input unexpectedly matched the pattern. input:\n\n```\n{}\n```",
245 input_string
246 )))
247 } else {
248 Ok(())
249 }
250 }
251
Marcel Hlopko803696f2021-12-14 10:07:45 +0000252 // This implementation uses naive backtracking algorithm that is in the worst
253 // case O(2^n) in the number of wildcards. In practice this is not so bad
254 // because wildcards only match their current group, they don't descend into
255 // subtrees and they don't match outside. Still, it may be possible to
256 // reimplement this using NFA and end up with simpler, more regular code
257 // while still providing reasonable error messages on mismatch.
258 // TODO(hlopko): Try to reimplement matching using NFA.
259 fn match_prefix(
260 input: impl Iterator<Item = TokenTree> + Clone,
261 pattern: TokenStream,
262 ) -> MatchInfo {
263 let mut input_iter = input.clone();
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000264 let mut pattern_iter = pattern.clone().into_iter().peekable();
265 let mut match_counter = 0;
Marcel Hlopko803696f2021-12-14 10:07:45 +0000266 let mut best_mismatch = Mismatch::for_no_partial_match();
267 let mut update_best_mismatch = |mismatch: Mismatch| {
268 if mismatch.match_length > best_mismatch.match_length {
269 best_mismatch = mismatch;
270 }
271 };
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000272 while let Some(actual_token) = input_iter.next() {
273 if is_newline_token(&actual_token) {
274 continue;
275 }
276
Marcel Hlopko803696f2021-12-14 10:07:45 +0000277 if starts_with_wildcard(to_stream(&pattern_iter)) {
278 // branch off to matching the token after the wildcard
279 match match_after_wildcard(
280 reinsert_token(input_iter.clone(), actual_token).into_iter(),
281 input.clone(),
282 skip_wildcard(pattern_iter.clone()),
283 ) {
284 MatchInfo::Mismatch(mut mismatch) => {
285 mismatch.match_length += match_counter;
286 update_best_mismatch(mismatch);
287 }
288 match_info => {
289 return match_info;
290 }
291 }
292 // and if that didn't work, consume one more token by the wildcard
293 continue;
294 }
295
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000296 if let Some(pattern_token) = pattern_iter.next() {
297 if let MatchInfo::Mismatch(mut mismatch) = match_tree(&actual_token, &pattern_token)
298 {
299 mismatch.messages.push(format!(
300 "expected '{}' got '{}'",
301 pattern,
302 input.collect::<TokenStream>()
303 ));
304 mismatch.match_length += match_counter;
305 return MatchInfo::Mismatch(mismatch);
306 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000307 } else {
Marcel Hlopko803696f2021-12-14 10:07:45 +0000308 return MatchInfo::Match { input_suffix: reinsert_token(input_iter, actual_token) };
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000309 }
310 match_counter += 1;
311 }
312
Marcel Hlopko803696f2021-12-14 10:07:45 +0000313 if pattern_iter.peek().is_none() {
314 return MatchInfo::Match { input_suffix: TokenStream::new() };
315 }
316 if is_wildcard(to_stream(&pattern_iter)) {
317 return MatchInfo::Match { input_suffix: TokenStream::new() };
318 }
319
320 update_best_mismatch(Mismatch::for_input_ended(
321 match_counter,
322 to_stream(&pattern_iter),
323 pattern,
324 to_stream(&input),
325 ));
326 MatchInfo::Mismatch(best_mismatch)
327 }
328
329 fn match_after_wildcard(
330 input_iter: impl Iterator<Item = TokenTree> + Clone,
331 input: impl Iterator<Item = TokenTree> + Clone,
332 pattern: TokenStream,
333 ) -> MatchInfo {
334 match match_prefix(input_iter.clone(), pattern.clone()) {
335 MatchInfo::Match { input_suffix } if input_suffix.is_empty() => {
336 MatchInfo::Match { input_suffix }
337 }
338 MatchInfo::Match { input_suffix } => {
339 let match_input_length = input_iter.count() + 1;
340 let suffix_length = input_suffix.into_iter().count();
341 MatchInfo::Mismatch(Mismatch::for_input_ended(
342 match_input_length - suffix_length,
343 pattern.clone(),
344 pattern,
345 to_stream(&input),
346 ))
347 }
348 mismatch => mismatch,
349 }
350 }
351
352 fn to_stream(iter: &(impl Iterator<Item = TokenTree> + Clone)) -> TokenStream {
353 iter.clone().collect::<TokenStream>()
354 }
355
356 fn reinsert_token(
357 iter: impl Iterator<Item = TokenTree> + Clone,
358 token: TokenTree,
359 ) -> TokenStream {
360 iter::once(token).chain(iter).collect::<TokenStream>()
361 }
362
363 fn is_newline_token(token: &TokenTree) -> bool {
364 matches!(token, TokenTree::Ident(ref id) if id == "__NEWLINE__")
365 }
366
367 fn is_wildcard(pattern: TokenStream) -> bool {
368 format!("{}", pattern) == "..."
369 }
370
371 fn starts_with_wildcard(pattern: TokenStream) -> bool {
372 format!("{}", pattern).starts_with("...")
373 }
374
375 fn skip_wildcard(pattern: impl Iterator<Item = TokenTree> + Clone) -> TokenStream {
376 assert!(starts_with_wildcard(to_stream(&pattern)));
377 pattern.skip(3).collect::<TokenStream>()
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000378 }
379
380 fn match_tree(actual_token: &TokenTree, pattern_token: &TokenTree) -> MatchInfo {
381 match (actual_token, pattern_token) {
382 (TokenTree::Group(ref actual_group), TokenTree::Group(ref pattern_group)) => {
383 if actual_group.delimiter() != pattern_group.delimiter() {
384 return MatchInfo::Mismatch(Mismatch {
385 match_length: 0,
386 messages: vec![format!(
387 "expected delimiter {:?} for group '{}' but got {:?} for group '{}'",
388 pattern_group.delimiter(),
389 Into::<TokenStream>::into(pattern_token.clone()),
390 actual_group.delimiter(),
391 Into::<TokenStream>::into(actual_token.clone()),
392 )],
393 });
394 }
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000395 let match_info =
396 match_prefix(actual_group.stream().into_iter(), pattern_group.stream());
397 match match_info {
398 MatchInfo::Match { input_suffix } => {
399 if input_suffix
400 .clone()
401 .into_iter()
402 .filter(|token| !is_newline_token(token))
403 .count()
404 != 0
405 {
406 MatchInfo::Mismatch(Mismatch {
407 match_length: 0,
408 messages: vec![format!(
409 "matched the entire pattern but the input still contained '{}'",
410 input_suffix
411 )],
412 })
413 } else {
414 MatchInfo::Match { input_suffix: TokenStream::new() }
415 }
416 }
417 mismatch => mismatch,
418 }
419 }
420 (ref actual, ref pattern) => {
421 let actual_src = format!("{}", actual);
422 let pattern_src = format!("{}", pattern);
423 if actual_src == pattern_src {
424 MatchInfo::Match { input_suffix: TokenStream::new() }
425 } else {
426 MatchInfo::Mismatch(Mismatch {
427 match_length: 0,
428 messages: vec![format!("expected '{}' but got '{}'", pattern, actual)],
429 })
430 }
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000431 }
432 }
433 }
434}
435
436#[cfg(test)]
437mod tests {
Marcel Hlopko9fc69402021-12-10 13:12:34 +0000438 use super::internal::*;
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000439 use super::*;
Marcel Hlopko016cab02021-12-16 14:29:35 +0000440 use ir_testing::ir_from_cc;
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000441 use quote::quote;
442
443 macro_rules! assert_rs_cc_matches {
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000444 ($input:expr, $pattern:expr $(,)*) => {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000445 $crate::assert_cc_matches!($input, $pattern);
446 $crate::assert_rs_matches!($input, $pattern);
447 };
448 }
449
450 #[test]
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000451 fn test_optional_trailing_comma() {
452 assert_rs_matches!(quote! {x}, quote! {x});
453 assert_rs_matches!(quote! {x}, quote! {x},);
Marcel Hlopko016cab02021-12-16 14:29:35 +0000454
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000455 assert_cc_matches!(quote! {x}, quote! {x});
456 assert_cc_matches!(quote! {x}, quote! {x},);
Marcel Hlopko016cab02021-12-16 14:29:35 +0000457
458 assert_ir_matches!(ir_from_cc("").unwrap(), quote! {});
459 assert_ir_matches!(ir_from_cc("").unwrap(), quote! {},);
460
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000461 assert_rs_not_matches!(quote! {x}, quote! {y});
462 assert_rs_not_matches!(quote! {x}, quote! {y},);
Marcel Hlopko803696f2021-12-14 10:07:45 +0000463
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000464 assert_cc_not_matches!(quote! {x}, quote! {y});
465 assert_cc_not_matches!(quote! {x}, quote! {y},);
Marcel Hlopko016cab02021-12-16 14:29:35 +0000466
467 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir});
468 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir},);
Marcel Hlopko62ac72f2021-12-10 09:14:58 +0000469 }
470
471 #[test]
Marcel Hlopko016cab02021-12-16 14:29:35 +0000472 fn test_assert_ir_matches_assumes_trailing_commas_in_groups() {
473 assert_ir_matches!(ir_from_cc("").unwrap(), quote! {{... items: [...],}});
474 }
475
476 #[test]
477 fn test_assert_not_matches_accepts_not_matching_pattern() {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000478 assert_cc_not_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
479 assert_rs_not_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
Marcel Hlopko016cab02021-12-16 14:29:35 +0000480 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir});
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000481 }
482
483 #[test]
Rosica Dejanovskada9105d2022-03-30 09:38:16 -0700484 #[should_panic(expected = "input:\n\n```\nfn foo(){ }\n")]
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000485 fn test_assert_cc_not_matches_panics_on_match() {
486 assert_cc_not_matches!(quote! { fn foo() {} }, quote! { fn foo() {} });
487 }
488
489 #[test]
Googlerfdd4e562022-01-11 12:44:55 +0000490 #[should_panic(expected = "input:\n\n```\nfn foo() {}\n\n```")]
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000491 fn test_assert_rs_not_matches_panics_on_match() {
492 assert_rs_not_matches!(quote! { fn foo() {} }, quote! { fn foo() {} });
493 }
494
495 #[test]
Googlerfdd4e562022-01-11 12:44:55 +0000496 #[should_panic(expected = "input:\n\n```\nFlatIR {")]
Marcel Hlopko016cab02021-12-16 14:29:35 +0000497 fn test_assert_ir_not_matches_panics_on_match() {
498 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {items});
499 }
500
501 #[test]
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000502 fn test_assert_cc_matches_accepts_matching_pattern() {
503 assert_rs_cc_matches!(quote! { fn foo() {} }, quote! { fn foo() {} });
504 }
505
506 #[test]
507 #[should_panic]
508 fn test_assert_cc_matches_panics_on_mismatch() {
509 assert_cc_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
510 }
511
512 #[test]
513 #[should_panic]
514 fn test_assert_rs_matches_panics_on_mismatch() {
515 assert_rs_matches!(quote! { fn foo() {} }, quote! { fn bar() {} });
516 }
517
518 #[test]
Marcel Hlopko016cab02021-12-16 14:29:35 +0000519 #[should_panic]
520 fn test_assert_ir_matches_panics_on_mismatch() {
521 assert_ir_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir});
522 }
523
524 #[test]
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000525 fn test_accept_siblings() {
526 assert_rs_cc_matches!(quote! {a b c d}, quote! {a b c d});
527 assert_rs_cc_matches!(quote! {a b c d}, quote! {a b});
528 assert_rs_cc_matches!(quote! {a b c d}, quote! {b c});
529 assert_rs_cc_matches!(quote! {a b c d}, quote! {c d});
530 }
531
532 #[test]
533 fn test_accept_subtrees() {
534 assert_rs_cc_matches!(quote! {impl SomeStruct { fn foo() {} }}, quote! {fn foo() {}});
535 }
536
537 #[test]
538 #[should_panic]
539 fn test_cc_reject_partial_subtree() {
540 assert_cc_matches!(quote! {fn foo() {a(); b();}}, quote! {fn foo() { a(); }});
541 }
542
543 #[test]
544 #[should_panic]
545 fn test_rs_reject_partial_subtree() {
546 assert_rs_matches!(quote! {fn foo() {a(); b();}}, quote! {fn foo() { a(); }});
547 }
548
549 #[test]
550 fn test_cc_error_message() {
551 assert_eq!(
552 format!(
553 "{:?}",
554 match_tokens(
555 &quote! {struct A { int a; int b; };},
556 &quote! {struct B},
Googlerfdd4e562022-01-11 12:44:55 +0000557 &tokens_to_string
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000558 )
559 .expect_err("unexpected match")
560 ),
561 "expected 'B' but got 'A'
562
563Caused by:
564 0: expected 'struct B' got 'struct A { int a ; int b ; } ;'
565 1: input:\n \n ```
Rosica Dejanovskada9105d2022-03-30 09:38:16 -0700566 struct A{ int a;int b; };
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000567 ```"
568 );
569 }
570
571 #[test]
572 fn test_rustfmt_in_rs_error_message() {
573 assert_eq!(
574 format!(
575 "{:?}",
576 match_tokens(
577 &quote! {struct A { a: i64, b: i64 }},
578 &quote! {struct B},
Googlerfdd4e562022-01-11 12:44:55 +0000579 &rs_tokens_to_formatted_string
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000580 )
581 .expect_err("unexpected match")
582 ),
583 "expected 'B' but got 'A'
584
585Caused by:
586 0: expected 'struct B' got 'struct A { a : i64 , b : i64 }'
587 1: input:\n \n ```
588 struct A {
589 a: i64,
590 b: i64,
591 }\n \n ```"
592 );
593 }
594
595 #[test]
596 fn test_reject_unfinished_pattern() {
597 assert_eq!(
598 format!(
599 "{:#}",
600 match_tokens(
601 &quote! {fn foo() {}},
602 &quote! {fn foo() {} struct X {}},
Googlerfdd4e562022-01-11 12:44:55 +0000603 &tokens_to_string
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000604 )
605 .expect_err("unexpected match")
606 ),
607 "expected 'struct X { }' but the input already ended: \
608 expected 'fn foo () { } struct X { }' got 'fn foo () { }': \
Rosica Dejanovskada9105d2022-03-30 09:38:16 -0700609 input:\n\n```\nfn foo(){ }\n```"
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000610 );
611 }
612
613 #[test]
614 fn test_reject_different_delimiters() {
615 assert_eq!(
616 format!(
617 "{:#}",
Googlerfdd4e562022-01-11 12:44:55 +0000618 match_tokens(&quote! {fn foo() ()}, &quote! {fn foo() {}}, &tokens_to_string)
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000619 .expect_err("unexpected match")
620 ),
621 "expected delimiter Brace for group '{ }' but got Parenthesis for group '()': \
622 expected 'fn foo () { }' got 'fn foo () ()': \
623 input:\n\n```\nfn foo()()\n```"
624 );
625 }
626
627 #[test]
628 fn test_reject_mismatch_inside_group() {
629 assert_eq!(
630 format!(
631 "{:#}",
632 match_tokens(
633 &quote! {fn foo() { a: i64, b: i64 }},
634 &quote! {fn foo() { a: i64, c: i64 }},
Googlerfdd4e562022-01-11 12:44:55 +0000635 &tokens_to_string
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000636 )
637 .expect_err("unexpected match")
638 ),
639 "expected 'c' but got 'b': \
640 expected 'a : i64 , c : i64' got 'a : i64 , b : i64': \
641 expected 'fn foo () { a : i64 , c : i64 }' got 'fn foo () { a : i64 , b : i64 }': \
Rosica Dejanovskada9105d2022-03-30 09:38:16 -0700642 input:\n\n```\nfn foo(){ a:i64,b:i64 }\n```"
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000643 );
644 }
645
646 #[test]
647 fn test_accept_wildcard_in_group() {
648 assert_rs_cc_matches!(
649 quote! {fn foo() -> bool { return false; }},
650 quote! {fn foo() -> bool {...}}
651 );
652 }
653
654 #[test]
655 fn test_ignore_newlines() {
656 assert_rs_cc_matches!(
657 quote! {__NEWLINE__ fn __NEWLINE__ foo __NEWLINE__ (
658 __NEWLINE__ a: __NEWLINE__ usize) {}},
659 quote! {fn foo(a: usize) {}}
660 );
661 }
662
663 #[test]
664 fn test_reject_unfinished_input_inside_group() {
665 assert_eq!(
666 format!(
667 "{:#}",
668 match_tokens(
669 &quote! {impl Drop { fn drop(&mut self) { drop_impl(); }}},
670 &quote! {fn drop(&mut self) {}},
Googlerfdd4e562022-01-11 12:44:55 +0000671 &tokens_to_string
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000672 )
673 .expect_err("unexpected match")
674 ),
675 "matched the entire pattern but the input still contained 'drop_impl () ;': \
676 expected 'fn drop (& mut self) { }' got 'fn drop (& mut self) { drop_impl () ; }': \
Rosica Dejanovskada9105d2022-03-30 09:38:16 -0700677 input:\n\n```\nimpl Drop{ fn drop(&mut self){ drop_impl(); } }\n```"
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000678 );
679 assert_eq!(
680 format!(
681 "{:#}",
682 match_tokens(
683 &quote! {impl Drop { fn drop(&mut self) { drop_impl1(); drop_impl2(); }}},
684 &quote! {fn drop(&mut self) { drop_impl1(); }},
Googlerfdd4e562022-01-11 12:44:55 +0000685 &tokens_to_string
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000686 )
687 .expect_err("unexpected match")
688 ),
689 "matched the entire pattern but the input still contained 'drop_impl2 () ;': \
690 expected 'fn drop (& mut self) { drop_impl1 () ; }' \
691 got 'fn drop (& mut self) { drop_impl1 () ; drop_impl2 () ; }': \
Rosica Dejanovskada9105d2022-03-30 09:38:16 -0700692 input:\n\n```\nimpl Drop{ fn drop(&mut self){ \
693 drop_impl1();drop_impl2(); } }\n```"
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000694 );
695 }
696
697 #[test]
698 fn test_accept_unfinished_input_with_only_newlines() {
699 assert_rs_cc_matches!(quote! {fn foo() { __NEWLINE__ }}, quote! {fn foo() {}});
700 assert_rs_cc_matches!(quote! {fn foo() { a(); __NEWLINE__ }}, quote! {fn foo() { a(); }});
701 }
702
703 #[test]
Marcel Hlopko803696f2021-12-14 10:07:45 +0000704 fn test_wildcard_in_the_beginning_of_the_group() {
705 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a ... ] });
706 assert_rs_cc_matches!(quote! { [ a a b b c c ] }, quote! { [ a a ... ] });
707 }
708 #[test]
709 fn test_wildcard_in_the_middle_of_the_group() {
710 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a ... c ] });
711 assert_rs_cc_matches!(quote! { [ a a b b c c ] }, quote! { [ a a ... c c ] });
712 }
713 #[test]
714 fn test_wildcard_in_the_end_of_the_group() {
715 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ ... c ] });
716 assert_rs_cc_matches!(quote! { [ a a b b c c ] }, quote! { [ ... c c ] });
717 }
718
719 #[test]
720 fn test_wildcard_not_consuming_anything_in_group() {
721 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ ... a b c ] });
722 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a ... b c ] });
723 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a b ... c ] });
724 assert_rs_cc_matches!(quote! { [ a b c ] }, quote! { [ a b c ... ] });
725 }
726
727 #[test]
728 fn test_error_message_shows_the_longest_match_with_wildcards() {
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000729 assert_eq!(
730 format!(
731 "{:#}",
Googlerfdd4e562022-01-11 12:44:55 +0000732 match_tokens(&quote! {[ a b b ]}, &quote! { [ a ... c ]}, &tokens_to_string)
Marcel Hlopko803696f2021-12-14 10:07:45 +0000733 .expect_err("unexpected match")
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000734 ),
Marcel Hlopko803696f2021-12-14 10:07:45 +0000735 // the error message shows "longer match" with more tokens consumed by the wildcard
736 "expected 'c' but got 'b': \
737 expected 'c' got 'b b': \
738 expected '[a ... c]' got '[a b b]': \
739 input:\n\n```\n[a b b]\n```"
740 );
741 assert_eq!(
742 format!(
743 "{:#}",
Googlerfdd4e562022-01-11 12:44:55 +0000744 match_tokens(&quote! {[ a b b ]}, &quote! { [ a ... b c ]}, &tokens_to_string)
Marcel Hlopko803696f2021-12-14 10:07:45 +0000745 .expect_err("unexpected match")
746 ),
747 // the error message shows "longer match" with branching off the wildcard earlier
748 "expected 'c' but got 'b': \
749 expected 'b c' got 'b b': \
750 expected '[a ... b c]' got '[a b b]': \
751 input:\n\n```\n[a b b]\n```"
Marcel Hlopkoca84ff42021-12-09 14:15:14 +0000752 );
753 }
754}