/ compiler / passes / src / test_passes.rs
test_passes.rs
  1  // Copyright (C) 2019-2025 ADnet Contributors
  2  // This file is part of the ADL library.
  3  
  4  // The ADL library is free software: you can redistribute it and/or modify
  5  // it under the terms of the GNU General Public License as published by
  6  // the Free Software Foundation, either version 3 of the License, or
  7  // (at your option) any later version.
  8  
  9  // The ADL library is distributed in the hope that it will be useful,
 10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 12  // GNU General Public License for more details.
 13  
 14  // You should have received a copy of the GNU General Public License
 15  // along with the ADL library. If not, see <https://www.gnu.org/licenses/>.
 16  
 17  // Unit tests for individual compiler passes.
 18  // This module provides test infrastructure to verify the behavior of individual
 19  // compiler passes in isolation. Each pass can be tested by providing Leo source
 20  // code and verifying the AST output after the pass runs.
 21  
 22  /*!
 23  # Compiler Pass Test Runners
 24  
 25  This module provides automatically generated test runners for all compiler transform passes in the ADL compiler.
 26  
 27  ## Adding a New Compiler Pass Test
 28  
 29  To add a new compiler pass, you need to update this file and create the test directories:
 30  
 31  ### 1. Update this file
 32  
 33  1. Add a new entry to the `compiler_passes!` table:
 34  
 35  ```rust
 36  (runner_name, [(PassStruct, input), ...])
 37  ```
 38  
 39  - `runner_name` – the function name for this pass runner (snake_case).
 40  - `[(PassStruct, input), ...]` – a list of passes to run sequentially. Each entry is a tuple of `(pass_struct, input)`.
 41  - `input` – the argument to the pass. Can be `()` if none, or a struct literal like `(SsaFormingInput { rename_defs: true })`.
 42  
 43  Examples:
 44  
 45  ```rust
 46  // Single pass with typical prelude
 47  (new_pass_runner, [
 48      (PathResolution, ()),
 49      (SymbolTableCreation, ()),
 50      (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
 51      (NewPass, (NewPassInput { option: true }))
 52  ]),
 53  
 54  // Multiple passes run sequentially
 55  (multi_pass_runner, [
 56      (PathResolution, ()),
 57      (SymbolTableCreation, ()),
 58      (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
 59      (FirstPass, ()),
 60      (SecondPass, (SecondPassInput { value: NetworkName::AlphaTestnetV0 }))
 61  ]),
 62  
 63  // Pass without prelude (if prelude not needed)
 64  (no_prelude_runner, [
 65      (SomePass, ())
 66  ]),
 67  ```
 68  
 69  2. No other code needs to change — macros automatically generate:
 70     - The runner function
 71     - The test function (`#[test] fn new_pass_runner_test()`)
 72  
 73  ---
 74  
 75  ### 2. Create the test directories
 76  
 77  Each pass requires two directories in the Leo repository:
 78  
 79  1. **Source tests**: `leo/tests/tests/passes/<pass_name>`
 80     - Contains `.adl` files with source programs to test this pass.
 81  
 82  2. **Expected outputs**: `leo/tests/expectations/passes/<pass_name>`
 83     - Contains expected output files for each source test file.
 84  
 85  Example for `common_subexpression_elimination`:
 86  
 87  ```
 88  leo/tests/tests/passes/common_subexpression_elimination/
 89  leo/tests/expectations/passes/common_subexpression_elimination/
 90  ```
 91  
 92  - The runner will compare the output AST (or errors/warnings) against these expectations.
 93  
 94  ---
 95  
 96  This structure ensures that **adding a new compiler pass test is minimal**:
 97  - Add a single line to the `compiler_passes!` table.
 98  - Create the two directories for tests and expected outputs.
 99  - All runners and test functions are generated automatically.
100  */
101  
102  use crate::*;
103  use adl_ast::NetworkName;
104  use adl_errors::{BufferEmitter, Handler};
105  use adl_parser::parse_ast;
106  use adl_span::{create_session_if_not_set_then, source_map::FileName, with_session_globals};
107  use serial_test::serial;
108  
109  /// Table of all compiler passes and their runner names.
110  /// Each entry is a tuple of `(runner_name, [(pass_struct, input), ...])`
111  /// - `runner_name` – the function name for this pass runner (snake_case).
112  /// - `[(pass_struct, input), ...]` – a list of passes to run sequentially. Each entry is a tuple of `(pass_struct, input)`.
113  ///   Include the prelude passes (PathResolution, SymbolTableCreation, TypeChecking) at the beginning if needed.
114  /// - `input` – the argument to the pass. Can be `()` if none, or a struct literal like `(SsaFormingInput { rename_defs: true })`.
115  macro_rules! compiler_passes {
116      ($macro:ident) => {
117          $macro! {
118              (common_subexpression_elimination_runner, [
119                  (PathResolution, ()),
120                  (SymbolTableCreation, ()),
121                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
122                  (Disambiguate, ()),
123                  (CommonSubexpressionEliminating, ())
124              ]),
125              (const_prop_unroll_and_morphing_runner, [
126                  (PathResolution, ()),
127                  (SymbolTableCreation, ()),
128                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
129                  (Disambiguate, ()),
130                  (ConstPropUnrollAndMorphing, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0)))
131              ]),
132              (destructuring_runner, [
133                  (PathResolution, ()),
134                  (SymbolTableCreation, ()),
135                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
136                  (Disambiguate, ()),
137                  (Destructuring, ())
138              ]),
139              (dead_code_elimination_runner, [
140                  (PathResolution, ()),
141                  (SymbolTableCreation, ()),
142                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
143                  (Disambiguate, ()),
144                  (DeadCodeEliminating, ())
145              ]),
146              (flattening_runner, [
147                  (PathResolution, ()),
148                  (SymbolTableCreation, ()),
149                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
150                  (Disambiguate, ()),
151                  (Flattening, ())
152              ]),
153              (function_inlining_runner, [
154                  (PathResolution, ()),
155                  (SymbolTableCreation, ()),
156                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
157                  (Disambiguate, ()),
158                  (FunctionInlining, ())
159              ]),
160              (option_lowering_runner, [
161                  (PathResolution, ()),
162                  (SymbolTableCreation, ()),
163                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
164                  (Disambiguate, ()),
165                  (OptionLowering, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0)))
166              ]),
167              (processing_async_runner, [
168                  (PathResolution, ()),
169                  (SymbolTableCreation, ()),
170                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
171                  (Disambiguate, ()),
172                  (ProcessingAsync, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0)))
173              ]),
174              (processing_script_runner, [
175                  (PathResolution, ()),
176                  (SymbolTableCreation, ()),
177                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
178                  (Disambiguate, ()),
179                  (ProcessingScript, ())
180              ]),
181              (ssa_forming_runner, [
182                  (PathResolution, ()),
183                  (SymbolTableCreation, ()),
184                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
185                  (Disambiguate, ()),
186                  (SsaForming, (SsaFormingInput { rename_defs: true }))
187              ]),
188              (storage_lowering_runner, [
189                  (PathResolution, ()),
190                  (SymbolTableCreation, ()),
191                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
192                  (Disambiguate, ()),
193                  (StorageLowering, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0)))
194              ]),
195              (write_transforming_runner, [
196                  (PathResolution, ()),
197                  (SymbolTableCreation, ()),
198                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
199                  (Disambiguate, ()),
200                  (WriteTransforming, ())
201              ]),
202              (remove_unreachable_runner, [
203                  (RemoveUnreachable, ())
204              ]),
205              (ssa_const_propagation_runner, [
206                  (PathResolution, ()),
207                  (SymbolTableCreation, ()),
208                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
209                  (Disambiguate, ()),
210                  (SsaForming, (SsaFormingInput { rename_defs: true })),
211                  (SsaConstPropagation, ()),
212              ]),
213              (disambiguate_runner, [
214                  (PathResolution, ()),
215                  (SymbolTableCreation, ()),
216                  (TypeChecking, (TypeCheckingInput::new(NetworkName::AlphaTestnetV0))),
217                  (Disambiguate, ()),
218              ]),
219          }
220      };
221  }
222  
223  /// Macro to generate a single runner function for compiler passes.
224  ///
225  /// Each runner:
226  /// - Sets up a BufferEmitter and Handler for error/warning reporting.
227  /// - Parse the test into an AST.
228  /// - Runs the specified list of compiler passes sequentially.
229  /// - Returns the resulting AST or formatted errors/warnings.
230  macro_rules! make_runner {
231      ($runner_name:ident, [$(($pass:ident, $input:expr)),* $(,)?]) => {
232          fn $runner_name(source: &str) -> String {
233              let buf = BufferEmitter::new();
234              let handler = Handler::new(buf.clone());
235  
236              create_session_if_not_set_then(|_| {
237                  let mut state = CompilerState { handler: handler.clone(), ..Default::default() };
238  
239                  state.ast = match handler.extend_if_error(parse_ast(
240                      handler.clone(),
241                      &state.node_builder,
242                      &with_session_globals(|s| s.source_map.new_source(source, FileName::Custom("test".into()))),
243                      &[],
244                      NetworkName::AlphaTestnetV0,
245                  )) {
246                      Ok(ast) => ast,
247                      Err(()) => return format!("{}{}", buf.extract_errs(), buf.extract_warnings()),
248                  };
249  
250                  // Run the specified passes sequentially
251                  $(
252                      if handler.extend_if_error($pass::do_pass($input, &mut state)).is_err() {
253                          return format!("{}{}", buf.extract_errs(), buf.extract_warnings());
254                      }
255                  )*
256  
257                  // Success: return AST with any warnings
258                  format!("{}{}", buf.extract_warnings(), state.ast.ast)
259              })
260          }
261      };
262  }
263  
264  /// Macro to generate all runners from the compiler_passes table.
265  macro_rules! make_all_runners {
266      ($(($runner:ident, $passes:tt)),* $(,)?) => {
267          $(
268              make_runner!($runner, $passes);
269          )*
270      };
271  }
272  compiler_passes!(make_all_runners);
273  
274  /// Macro to generate `#[test]` functions for all compiler passes.
275  ///
276  /// Each test function:
277  /// - Uses the runner function generated above.
278  /// - Uses `adl_test_framework::run_tests` with a path derived from the last pass struct name (the actual pass being tested).
279  /// - Uses `paste::paste!` to safely concatenate identifiers.
280  macro_rules! make_all_tests {
281      ($(($runner:ident, [$(($pass:ident, $input:tt)),* $(,)?])),* $(,)?) => {
282          $(
283              paste::paste! {
284                  #[test]
285                  #[serial]
286                  fn [<$runner _test>]() {
287                      // Automatically derive the snake_case directory name from the last pass name (the actual pass being tested)
288                      // We need to extract the last pass from the list
289                      make_all_tests_inner!($runner, [$(($pass, $input)),*]);
290                  }
291              }
292          )*
293      };
294  }
295  
296  /// Helper macro to extract the last pass name from the list.
297  macro_rules! make_all_tests_inner {
298      ($runner:ident, [($pass:ident, $input:tt)]) => {
299          paste::paste! {
300              adl_test_framework::run_tests(
301                  concat!("passes/", stringify!([<$pass:snake>])),
302                  $runner,
303              );
304          }
305      };
306      ($runner:ident, [($pass:ident, $input:tt), $(($rest_pass:ident, $rest_input:tt)),+ $(,)?]) => {
307          make_all_tests_inner!($runner, [$(($rest_pass, $rest_input)),+]);
308      };
309  }
310  
311  compiler_passes!(make_all_tests);