| 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 the fifth flat-aware post_pass port (s159): |
| 7 | // `inject_live_reload_to_flat` must produce a FlatAst whose signature |
| 8 | // matches what the legacy `inject_live_reload` + `flatten_files` pair |
| 9 | // produces. Uses all six FlatBuilder primitives needed by live_reload: |
| 10 | // `replace_fn_body_stmts` (s158), `replace_file_stmt` (s155), |
| 11 | // `prepend_file_stmts` (s156), plus the legacy `inject_live_into_stmts` |
| 12 | // helper after decoding only the matching FnDecls from cursors. |
| 13 | module transformer |
| 14 | |
| 15 | import v2.ast |
| 16 | import v2.pref as vpref |
| 17 | import v2.types |
| 18 | |
| 19 | // Local helper — `v test` compiles each `_test.v` file in isolation so the |
| 20 | // shared transformer-test scaffold isn't visible here. Seeded with two |
| 21 | // optional @[live] fn entries (controlled by `with_live`) and a stable |
| 22 | // source_file path so both paths see identical LiveReloadParts. |
| 23 | fn create_live_reload_to_flat_test_transformer(with_live bool) &Transformer { |
| 24 | env := &types.Environment{} |
| 25 | mut t := &Transformer{ |
| 26 | pref: &vpref.Preferences{} |
| 27 | env: unsafe { env } |
| 28 | needed_clone_fns: map[string]string{} |
| 29 | needed_array_contains_fns: map[string]ArrayMethodInfo{} |
| 30 | needed_array_index_fns: map[string]ArrayMethodInfo{} |
| 31 | needed_array_last_index_fns: map[string]ArrayMethodInfo{} |
| 32 | local_decl_types: map[string]types.Type{} |
| 33 | live_source_file: '/tmp/_live_reload_to_flat_test.v' |
| 34 | } |
| 35 | if with_live { |
| 36 | t.live_fns << LiveFn{ |
| 37 | decl_name: 'tick' |
| 38 | mangled_name: 'tick' |
| 39 | is_method: false |
| 40 | } |
| 41 | } |
| 42 | return t |
| 43 | } |
| 44 | |
| 45 | fn make_main_file_with_main_and_for() ast.File { |
| 46 | return ast.File{ |
| 47 | name: 'main_live.v' |
| 48 | mod: 'main' |
| 49 | stmts: [ |
| 50 | ast.Stmt(ast.ModuleStmt{ |
| 51 | name: 'main' |
| 52 | }), |
| 53 | ast.Stmt(ast.FnDecl{ |
| 54 | name: 'tick' |
| 55 | stmts: [ |
| 56 | ast.Stmt(ast.AssertStmt{ |
| 57 | expr: ast.Expr(ast.BasicLiteral{ |
| 58 | kind: .number |
| 59 | value: '1' |
| 60 | }) |
| 61 | }), |
| 62 | ] |
| 63 | }), |
| 64 | ast.Stmt(ast.FnDecl{ |
| 65 | name: 'main' |
| 66 | stmts: [ |
| 67 | ast.Stmt(ast.ForStmt{ |
| 68 | cond: ast.Expr(ast.BasicLiteral{ |
| 69 | kind: .number |
| 70 | value: '1' |
| 71 | }) |
| 72 | stmts: [ |
| 73 | ast.Stmt(ast.ExprStmt{ |
| 74 | expr: ast.Expr(ast.CallExpr{ |
| 75 | lhs: ast.Expr(ast.Ident{ |
| 76 | name: 'tick' |
| 77 | }) |
| 78 | }) |
| 79 | }), |
| 80 | ] |
| 81 | }), |
| 82 | ] |
| 83 | }), |
| 84 | ] |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | fn test_inject_live_reload_to_flat_empty_live_fns_is_noop() { |
| 89 | mut b := ast.new_flat_builder() |
| 90 | b.append_file(make_main_file_with_main_and_for()) |
| 91 | baseline_sig := b.flat.signature() |
| 92 | mut t := create_live_reload_to_flat_test_transformer(false) |
| 93 | t.inject_live_reload_to_flat(mut b) |
| 94 | assert b.flat.signature() == baseline_sig |
| 95 | } |
| 96 | |
| 97 | fn test_inject_live_reload_to_flat_no_main_fn_is_noop() { |
| 98 | // File with no main fn — locator returns none even though live_fns is |
| 99 | // populated; signature must be unchanged. |
| 100 | module_only_file := ast.File{ |
| 101 | name: 'lib_live.v' |
| 102 | mod: 'main' |
| 103 | stmts: [ |
| 104 | ast.Stmt(ast.ModuleStmt{ |
| 105 | name: 'main' |
| 106 | }), |
| 107 | ast.Stmt(ast.FnDecl{ |
| 108 | name: 'tick' |
| 109 | stmts: []ast.Stmt{} |
| 110 | }), |
| 111 | ] |
| 112 | } |
| 113 | mut b := ast.new_flat_builder() |
| 114 | b.append_file(module_only_file) |
| 115 | baseline_sig := b.flat.signature() |
| 116 | mut t := create_live_reload_to_flat_test_transformer(true) |
| 117 | t.inject_live_reload_to_flat(mut b) |
| 118 | assert b.flat.signature() == baseline_sig |
| 119 | } |
| 120 | |
| 121 | fn test_inject_live_reload_to_flat_signature_matches_legacy() { |
| 122 | // Populated live_fns + main fn — splice happens. Compare the stmts-list |
| 123 | // subtree of the spliced file in both paths via `subtree_signature` on |
| 124 | // the file root's edge 2. |
| 125 | // |
| 126 | // Why subtree-of-stmts-list instead of full signature: the .file node's |
| 127 | // `extra` slot stores `intern(mod)` as a raw index. The legacy path |
| 128 | // flattens an already-mutated file (c_decls + global_decls + body |
| 129 | // emitted in declaration order → 'main' interned LATE) vs the flat |
| 130 | // path (file appended first → 'main' interned at slot 0 → splice |
| 131 | // re-emits the file root reusing saved mod_idx). Comparing the stmts |
| 132 | // list subtree skips the leaky `extra` field. |
| 133 | mut ref_files := [make_main_file_with_main_and_for()] |
| 134 | mut t_ref := create_live_reload_to_flat_test_transformer(true) |
| 135 | t_ref.inject_live_reload(mut ref_files) |
| 136 | ref_flat := ast.flatten_files(ref_files) |
| 137 | ref_stmts_list_id := ref_flat.child_at(ref_flat.files[0].file_id, 2) |
| 138 | ref_sub_sig := ref_flat.subtree_signature(ref_stmts_list_id) |
| 139 | |
| 140 | mut b := ast.new_flat_builder() |
| 141 | b.append_file(make_main_file_with_main_and_for()) |
| 142 | mut t_sub := create_live_reload_to_flat_test_transformer(true) |
| 143 | t_sub.inject_live_reload_to_flat(mut b) |
| 144 | sub_stmts_list_id := b.flat.child_at(b.flat.files[0].file_id, 2) |
| 145 | sub_sub_sig := b.flat.subtree_signature(sub_stmts_list_id) |
| 146 | |
| 147 | assert ref_sub_sig == sub_sub_sig |
| 148 | } |
| 149 | |