| 1 | // Copyright (c) 2026 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | // vtest build: macos |
| 5 | // |
| 6 | // Bit-equality pin for s161: the `post_pass_to_flat` driver must run |
| 7 | // the six flat-aware post_pass steps in the same order as legacy |
| 8 | // `post_pass`, with the same gating, producing a FlatAst whose |
| 9 | // `signature()` matches what legacy `post_pass` + `flatten_files` |
| 10 | // produces. Pins the driver assembly that wires together |
| 11 | // s151/s152/s153/s155/s159/s160's `_to_flat` variants. |
| 12 | module transformer |
| 13 | |
| 14 | import v2.ast |
| 15 | import v2.pref as vpref |
| 16 | import v2.types |
| 17 | |
| 18 | // Local helper — `v test` compiles each `_test.v` file in isolation. |
| 19 | fn create_post_pass_to_flat_test_transformer() &Transformer { |
| 20 | env := &types.Environment{} |
| 21 | return &Transformer{ |
| 22 | pref: &vpref.Preferences{} |
| 23 | env: unsafe { env } |
| 24 | needed_clone_fns: map[string]string{} |
| 25 | needed_array_contains_fns: map[string]ArrayMethodInfo{} |
| 26 | needed_array_index_fns: map[string]ArrayMethodInfo{} |
| 27 | needed_array_last_index_fns: map[string]ArrayMethodInfo{} |
| 28 | local_decl_types: map[string]types.Type{} |
| 29 | runtime_const_inits_by_mod: map[string][]RuntimeConstInit{} |
| 30 | runtime_const_init_fn_name: map[string]string{} |
| 31 | } |
| 32 | } |
| 33 | |
| 34 | fn make_minimal_main_file_with_main_fn() ast.File { |
| 35 | return ast.File{ |
| 36 | name: 'main.v' |
| 37 | mod: 'main' |
| 38 | stmts: [ |
| 39 | ast.Stmt(ast.ModuleStmt{ |
| 40 | name: 'main' |
| 41 | }), |
| 42 | ast.Stmt(ast.FnDecl{ |
| 43 | name: 'main' |
| 44 | stmts: []ast.Stmt{} |
| 45 | }), |
| 46 | ] |
| 47 | } |
| 48 | } |
| 49 | |
| 50 | fn make_mymod_file_for_driver() ast.File { |
| 51 | return ast.File{ |
| 52 | name: 'mymod.v' |
| 53 | mod: 'mymod' |
| 54 | stmts: [ |
| 55 | ast.Stmt(ast.ModuleStmt{ |
| 56 | name: 'mymod' |
| 57 | }), |
| 58 | ] |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | // All gates off → driver is a near-no-op (only `inject_runtime_const_init_fns_to_flat` |
| 63 | // and `inject_main_runtime_const_init_to_flat` and `inject_test_main_to_flat` |
| 64 | // have unconditional entry, but they short-circuit because state is empty). |
| 65 | // Bit-equality with legacy `post_pass + flatten_files` proves the driver |
| 66 | // preserves the post_pass control-flow tree even when all steps no-op. |
| 67 | fn test_post_pass_to_flat_all_gates_off_matches_legacy() { |
| 68 | mut ref_files := [make_minimal_main_file_with_main_fn()] |
| 69 | mut t_ref := create_post_pass_to_flat_test_transformer() |
| 70 | t_ref.post_pass(mut ref_files) |
| 71 | ref_sig := ast.flatten_files(ref_files).signature() |
| 72 | |
| 73 | mut b := ast.new_flat_builder() |
| 74 | b.append_file(make_minimal_main_file_with_main_fn()) |
| 75 | mut t_sub := create_post_pass_to_flat_test_transformer() |
| 76 | t_sub.post_pass_to_flat(mut b, none) |
| 77 | assert b.flat.signature() == ref_sig |
| 78 | } |
| 79 | |
| 80 | // Active runtime-const-init gate exercises s160 (generate the |
| 81 | // `__v_init_consts_mymod` fn into the mymod file) AND s155 (prepend init |
| 82 | // call to main's body). Two-mod fixture leaks intern order through |
| 83 | // `.file.extra=intern(mod)`, so compare per-file via subtree_signature. |
| 84 | fn test_post_pass_to_flat_runtime_const_init_matches_legacy() { |
| 85 | mut t_ref := create_post_pass_to_flat_test_transformer() |
| 86 | t_ref.runtime_const_modules = ['mymod'] |
| 87 | t_ref.runtime_const_inits_by_mod['mymod'] = [ |
| 88 | RuntimeConstInit{ |
| 89 | name: 'mymod__answer' |
| 90 | expr: ast.Expr(ast.BasicLiteral{ |
| 91 | kind: .number |
| 92 | value: '42' |
| 93 | }) |
| 94 | }, |
| 95 | ] |
| 96 | mut ref_files := [make_minimal_main_file_with_main_fn(), make_mymod_file_for_driver()] |
| 97 | t_ref.post_pass(mut ref_files) |
| 98 | ref_flat := ast.flatten_files(ref_files) |
| 99 | // File 0 (main): edge 2 = stmts list — main's body should be prepended |
| 100 | // with init call. |
| 101 | ref_main_stmts := ref_flat.child_at(ref_flat.files[0].file_id, 2) |
| 102 | ref_main_sub_sig := ref_flat.subtree_signature(ref_main_stmts) |
| 103 | // File 1 (mymod): edge 2 = stmts list — should contain the generated |
| 104 | // `__v_init_consts_mymod` fn. |
| 105 | ref_mymod_stmts := ref_flat.child_at(ref_flat.files[1].file_id, 2) |
| 106 | ref_mymod_sub_sig := ref_flat.subtree_signature(ref_mymod_stmts) |
| 107 | |
| 108 | mut t_sub := create_post_pass_to_flat_test_transformer() |
| 109 | t_sub.runtime_const_modules = ['mymod'] |
| 110 | t_sub.runtime_const_inits_by_mod['mymod'] = [ |
| 111 | RuntimeConstInit{ |
| 112 | name: 'mymod__answer' |
| 113 | expr: ast.Expr(ast.BasicLiteral{ |
| 114 | kind: .number |
| 115 | value: '42' |
| 116 | }) |
| 117 | }, |
| 118 | ] |
| 119 | mut b := ast.new_flat_builder() |
| 120 | b.append_file(make_minimal_main_file_with_main_fn()) |
| 121 | b.append_file(make_mymod_file_for_driver()) |
| 122 | t_sub.post_pass_to_flat(mut b, none) |
| 123 | sub_main_stmts := b.flat.child_at(b.flat.files[0].file_id, 2) |
| 124 | sub_main_sub_sig := b.flat.subtree_signature(sub_main_stmts) |
| 125 | sub_mymod_stmts := b.flat.child_at(b.flat.files[1].file_id, 2) |
| 126 | sub_mymod_sub_sig := b.flat.subtree_signature(sub_mymod_stmts) |
| 127 | |
| 128 | assert ref_main_sub_sig == sub_main_sub_sig |
| 129 | assert ref_mymod_sub_sig == sub_mymod_sub_sig |
| 130 | } |
| 131 | |
| 132 | // Backend gating: cleanc must skip inject_test_main, eval must skip |
| 133 | // inject_runtime_const_init_fns + inject_main_runtime_const_init_calls. |
| 134 | // We don't fully exercise eval backend here (no test_ fns, no native |
| 135 | // backend), but cleanc gating is observable: with a `test_*` fn, default |
| 136 | // backend produces `fn main()` (synthesised), cleanc skips that. The |
| 137 | // driver must match legacy on both. |
| 138 | fn test_post_pass_to_flat_cleanc_skips_test_main() { |
| 139 | mut t_ref := create_post_pass_to_flat_test_transformer() |
| 140 | t_ref.pref = &vpref.Preferences{ |
| 141 | backend: .cleanc |
| 142 | } |
| 143 | test_file := ast.File{ |
| 144 | name: 'foo_test.v' |
| 145 | mod: 'main' |
| 146 | stmts: [ |
| 147 | ast.Stmt(ast.ModuleStmt{ |
| 148 | name: 'main' |
| 149 | }), |
| 150 | ast.Stmt(ast.FnDecl{ |
| 151 | name: 'test_one' |
| 152 | stmts: []ast.Stmt{} |
| 153 | }), |
| 154 | ] |
| 155 | } |
| 156 | mut ref_files := [test_file] |
| 157 | t_ref.post_pass(mut ref_files) |
| 158 | ref_sig := ast.flatten_files(ref_files).signature() |
| 159 | |
| 160 | mut t_sub := create_post_pass_to_flat_test_transformer() |
| 161 | t_sub.pref = &vpref.Preferences{ |
| 162 | backend: .cleanc |
| 163 | } |
| 164 | mut b := ast.new_flat_builder() |
| 165 | b.append_file(ast.File{ |
| 166 | name: 'foo_test.v' |
| 167 | mod: 'main' |
| 168 | stmts: [ |
| 169 | ast.Stmt(ast.ModuleStmt{ |
| 170 | name: 'main' |
| 171 | }), |
| 172 | ast.Stmt(ast.FnDecl{ |
| 173 | name: 'test_one' |
| 174 | stmts: []ast.Stmt{} |
| 175 | }), |
| 176 | ] |
| 177 | }) |
| 178 | t_sub.post_pass_to_flat(mut b, none) |
| 179 | assert b.flat.signature() == ref_sig |
| 180 | } |
| 181 | |