blob: f0cd6165f62c0caeee71b41666eb1eae8473ca96 [file] [log] [blame]
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -07001// 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
Devin Jeanpierrec503d332023-04-26 11:28:11 -07005/// Like `token_stream_matchers::assert_cc_matches!`, but expects `IR` instance
6/// as input. The macro converts the instance to its corresponding struct
7/// expression and matches the pattern against that. See the documentation of
8/// `token_stream_matchers` for more information.
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -07009///
10/// Example:
11/// ```rust
Devin Jeanpierrec503d332023-04-26 11:28:11 -070012/// let ir = ir_from_cc(..., "struct SomeStruct {};').unwrap();
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -070013/// assert_ir_matches!(
14/// ir,
15/// quote! {
16/// Record {
17/// rs_name: "SomeStruct" ...
18/// }
19/// }
20/// );
21/// ```
22#[macro_export]
23macro_rules! assert_ir_matches {
24 ($ir:expr, $pattern:expr $(,)*) => {
25 $crate::internal::match_ir(&$ir, &$pattern)
26 .expect("input unexpectedly didn't match the pattern");
27 };
28}
29
30/// Like `assert_ir_matches`, but asserts that the pattern does not match the
31/// `IR`.
32#[macro_export]
33macro_rules! assert_ir_not_matches {
34 ($ir:expr, $pattern:expr $(,)*) => {
35 $crate::internal::mismatch_ir(&$ir, &$pattern).unwrap();
36 };
37}
38
39/// Like `assert_ir_matches!`, but expects a list of Items and a list of
40/// TokenStreams as input. The macro converts each Item to its struct expression
41/// and matches the corresponding pattern against that.
42/// The use case for this macro is to compare a list of Items to expected
43/// patterns, e.g when we want to confirm that the children items of an item
44/// appear in a certain order.
45#[macro_export]
46macro_rules! assert_items_match {
47 ($items:expr, $patterns:expr $(,)*) => {
48 assert_eq!($items.len(), $patterns.len());
Devin Jeanpierrec503d332023-04-26 11:28:11 -070049 for (idx, (item, pattern)) in $items.into_iter().zip($patterns).enumerate() {
50 $crate::internal::match_item(&item, &pattern).expect(&format!(
51 "input at position {} unexpectedly didn't match the pattern",
52 &idx
53 ));
54 }
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -070055 };
56}
57
58/// Only used to make stuff needed by exported macros available
59pub mod internal {
60
61 use anyhow::Result;
62 use ir::{Item, IR};
63 use itertools::Itertools;
64 pub use proc_macro2::TokenStream;
65 use quote::quote;
66 pub use token_stream_printer::{
Lukasz Anforowiczae9be082022-10-05 07:33:57 -070067 rs_tokens_to_formatted_string, rs_tokens_to_formatted_string_for_tests,
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -070068 };
69
70 pub fn match_ir(ir: &IR, pattern: &TokenStream) -> Result<()> {
71 token_stream_matchers::internal::match_tokens(
72 &ir_to_token_stream(ir)?,
73 pattern,
74 &ir_to_string,
75 )
76 }
77
78 pub fn mismatch_ir(ir: &IR, pattern: &TokenStream) -> Result<()> {
79 token_stream_matchers::internal::mismatch_tokens(
80 &ir_to_token_stream(ir)?,
81 pattern,
82 &ir_to_string,
83 )
84 }
85
86 fn ir_to_token_stream(ir: &IR) -> Result<TokenStream> {
87 // derived debug impl doesn't emit commas after the last element of a group,
88 // (for example `[a, b, c]`), but rustfmt automatically adds it (`[a, b,
89 // c,]`). We use rustfmt to format the failure messages. So to make the
90 // input token stream consistent with failure messages we format the
91 // input token stream with rustfmt as well.
92 Ok(ir_to_string(ir.flat_ir_debug_print().parse().unwrap())?.parse().unwrap())
93 }
94
95 fn ir_to_string(input: TokenStream) -> Result<String> {
96 // Rustfmt refuses to format some kinds of invalid Rust code. Let's put our IR
97 // struct expression into the body of a function, format it, and then remove
98 // the function.
99 let input_stream = quote! { fn make_rustfmt_happy() { #input } };
100 let formatted = rs_tokens_to_formatted_string_for_tests(input_stream)?;
101 let snippet = formatted
102 .strip_prefix("fn make_rustfmt_happy() {\n")
103 .unwrap()
104 .strip_suffix("}\n")
105 .unwrap()
106 .lines()
107 .map(|line| &line[4..])
108 .join("\n");
109 Ok(snippet)
110 }
111
112 pub fn match_item(item: &Item, pattern: &TokenStream) -> Result<()> {
113 token_stream_matchers::internal::match_tokens(
114 &item_to_token_stream(item)?,
115 pattern,
116 &ir_to_string,
117 )
118 }
119
120 fn item_to_token_stream(item: &Item) -> Result<TokenStream> {
121 // derived debug impl doesn't emit commas after the last element of a group,
122 // (for example `[a, b, c]`), but rustfmt automatically adds it (`[a, b,
123 // c,]`). We use rustfmt to format the failure messages. So to make the
124 // input token stream consistent with failure messages we format the
125 // input token stream with rustfmt as well.
126 Ok(ir_to_string(format! {"{:?}", item}.parse().unwrap())?.parse().unwrap())
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -0700133 use quote::quote;
134
Devin Jeanpierrec503d332023-04-26 11:28:11 -0700135 /// We aren't testing platform-specific details, just the matchers.
136 fn ir_from_cc(header: &str) -> arc_anyhow::Result<ir::IR> {
137 ir_testing::ir_from_cc(multiplatform_testing::Platform::X86Linux, header)
138 }
139
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -0700140 #[test]
141 fn test_optional_trailing_comma() {
Lukasz Anforowiczcca52ea2023-04-03 08:55:12 -0700142 assert_ir_matches!(ir_from_cc("").unwrap(), quote! { FlatIR { ... }});
143 assert_ir_matches!(ir_from_cc("").unwrap(), quote! { FlatIR { ... }},);
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -0700144
145 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir});
146 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir},);
147 }
148
149 #[test]
150 fn test_assert_ir_matches_assumes_trailing_commas_in_groups() {
Devin Jeanpierrec503d332023-04-26 11:28:11 -0700151 assert_ir_matches!(ir_from_cc("").unwrap(), quote! {{... , }});
Lukasz Anforowiczefd635d2022-09-22 10:12:49 -0700152 }
153
154 #[test]
155 fn test_assert_not_matches_accepts_not_matching_pattern() {
156 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir});
157 }
158
159 #[test]
160 #[should_panic(expected = "input:\n\n```\nFlatIR {")]
161 fn test_assert_ir_not_matches_panics_on_match() {
162 assert_ir_not_matches!(ir_from_cc("").unwrap(), quote! {items});
163 }
164
165 #[test]
166 #[should_panic]
167 fn test_assert_ir_matches_panics_on_mismatch() {
168 assert_ir_matches!(ir_from_cc("").unwrap(), quote! {this pattern is not in the ir});
169 }
170}