Lukasz Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 1 | // 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 Jeanpierre | c503d33 | 2023-04-26 11:28:11 -0700 | [diff] [blame] | 5 | /// 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 Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 9 | /// |
| 10 | /// Example: |
| 11 | /// ```rust |
Devin Jeanpierre | c503d33 | 2023-04-26 11:28:11 -0700 | [diff] [blame] | 12 | /// let ir = ir_from_cc(..., "struct SomeStruct {};').unwrap(); |
Lukasz Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 13 | /// assert_ir_matches!( |
| 14 | /// ir, |
| 15 | /// quote! { |
| 16 | /// Record { |
| 17 | /// rs_name: "SomeStruct" ... |
| 18 | /// } |
| 19 | /// } |
| 20 | /// ); |
| 21 | /// ``` |
| 22 | #[macro_export] |
| 23 | macro_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] |
| 33 | macro_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] |
| 46 | macro_rules! assert_items_match { |
| 47 | ($items:expr, $patterns:expr $(,)*) => { |
| 48 | assert_eq!($items.len(), $patterns.len()); |
Devin Jeanpierre | c503d33 | 2023-04-26 11:28:11 -0700 | [diff] [blame] | 49 | 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 Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 55 | }; |
| 56 | } |
| 57 | |
| 58 | /// Only used to make stuff needed by exported macros available |
| 59 | pub 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 Anforowicz | ae9be08 | 2022-10-05 07:33:57 -0700 | [diff] [blame] | 67 | rs_tokens_to_formatted_string, rs_tokens_to_formatted_string_for_tests, |
Lukasz Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 68 | }; |
| 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)] |
| 131 | mod tests { |
| 132 | use super::*; |
Lukasz Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 133 | use quote::quote; |
| 134 | |
Devin Jeanpierre | c503d33 | 2023-04-26 11:28:11 -0700 | [diff] [blame] | 135 | /// 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 Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 140 | #[test] |
| 141 | fn test_optional_trailing_comma() { |
Lukasz Anforowicz | cca52ea | 2023-04-03 08:55:12 -0700 | [diff] [blame] | 142 | assert_ir_matches!(ir_from_cc("").unwrap(), quote! { FlatIR { ... }}); |
| 143 | assert_ir_matches!(ir_from_cc("").unwrap(), quote! { FlatIR { ... }},); |
Lukasz Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 144 | |
| 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 Jeanpierre | c503d33 | 2023-04-26 11:28:11 -0700 | [diff] [blame] | 151 | assert_ir_matches!(ir_from_cc("").unwrap(), quote! {{... , }}); |
Lukasz Anforowicz | efd635d | 2022-09-22 10:12:49 -0700 | [diff] [blame] | 152 | } |
| 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 | } |