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);