| 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 | module ast |
| 5 | |
| 6 | // Tests for `FlatBuilder.replace_file_stmt`. The companion primitive to |
| 7 | // `prepend_to_fn_body`: when a caller swaps a stmt for a re-emitted variant |
| 8 | // (e.g. the new FnDecl returned by `prepend_to_fn_body`), `replace_file_stmt` |
| 9 | // rebuilds the enclosing file root so the file's stmts list references the |
| 10 | // new id. Together they support the prepend-style post_pass mutations |
| 11 | // (`inject_main_runtime_const_init_calls`, s155). |
| 12 | |
| 13 | fn make_minimal_file_with_three_consts() File { |
| 14 | return File{ |
| 15 | name: 'inline.v' |
| 16 | mod: 'foo' |
| 17 | stmts: [ |
| 18 | Stmt(ModuleStmt{ |
| 19 | name: 'foo' |
| 20 | }), |
| 21 | Stmt(ConstDecl{ |
| 22 | fields: [ |
| 23 | FieldInit{ |
| 24 | name: 'A' |
| 25 | value: Expr(BasicLiteral{ |
| 26 | kind: .number |
| 27 | value: '1' |
| 28 | }) |
| 29 | }, |
| 30 | ] |
| 31 | }), |
| 32 | Stmt(ConstDecl{ |
| 33 | fields: [ |
| 34 | FieldInit{ |
| 35 | name: 'B' |
| 36 | value: Expr(BasicLiteral{ |
| 37 | kind: .number |
| 38 | value: '2' |
| 39 | }) |
| 40 | }, |
| 41 | ] |
| 42 | }), |
| 43 | Stmt(ConstDecl{ |
| 44 | fields: [ |
| 45 | FieldInit{ |
| 46 | name: 'C' |
| 47 | value: Expr(BasicLiteral{ |
| 48 | kind: .number |
| 49 | value: '3' |
| 50 | }) |
| 51 | }, |
| 52 | ] |
| 53 | }), |
| 54 | ] |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | fn make_replacement_const_stmt() Stmt { |
| 59 | return Stmt(ConstDecl{ |
| 60 | fields: [ |
| 61 | FieldInit{ |
| 62 | name: 'Z' |
| 63 | value: Expr(BasicLiteral{ |
| 64 | kind: .number |
| 65 | value: '99' |
| 66 | }) |
| 67 | }, |
| 68 | ] |
| 69 | }) |
| 70 | } |
| 71 | |
| 72 | fn make_const_stmt_named(name string, value string) Stmt { |
| 73 | return Stmt(ConstDecl{ |
| 74 | fields: [ |
| 75 | FieldInit{ |
| 76 | name: name |
| 77 | value: Expr(BasicLiteral{ |
| 78 | kind: .number |
| 79 | value: value |
| 80 | }) |
| 81 | }, |
| 82 | ] |
| 83 | }) |
| 84 | } |
| 85 | |
| 86 | // Reference: build the file with stmt index 2 (the `B` const) already |
| 87 | // replaced by the `Z` const at construction time. |
| 88 | fn build_reference_with_replacement() FlatAst { |
| 89 | mut b := new_flat_builder() |
| 90 | b.append_file(File{ |
| 91 | name: 'inline.v' |
| 92 | mod: 'foo' |
| 93 | stmts: [ |
| 94 | Stmt(ModuleStmt{ |
| 95 | name: 'foo' |
| 96 | }), |
| 97 | make_const_stmt_named('A', '1'), |
| 98 | make_replacement_const_stmt(), |
| 99 | make_const_stmt_named('C', '3'), |
| 100 | ] |
| 101 | }) |
| 102 | return b.flat |
| 103 | } |
| 104 | |
| 105 | // Subject: build the bare three-const file, emit the replacement stmt |
| 106 | // separately, then splice it in via `replace_file_stmt(0, 2, z_id)`. |
| 107 | fn build_subject_with_replacement() FlatAst { |
| 108 | mut b := new_flat_builder() |
| 109 | b.append_file(make_minimal_file_with_three_consts()) |
| 110 | z_id := b.emit_stmt(make_replacement_const_stmt()) |
| 111 | b.replace_file_stmt(0, 2, z_id) |
| 112 | return b.flat |
| 113 | } |
| 114 | |
| 115 | fn test_replace_file_stmt_signature_matches_reference() { |
| 116 | ref_sig := build_reference_with_replacement().signature() |
| 117 | sub_sig := build_subject_with_replacement().signature() |
| 118 | assert ref_sig == sub_sig |
| 119 | } |
| 120 | |
| 121 | fn test_replace_file_stmt_invalid_file_idx_returns_invalid() { |
| 122 | mut b := new_flat_builder() |
| 123 | b.append_file(make_minimal_file_with_three_consts()) |
| 124 | z_id := b.emit_stmt(make_replacement_const_stmt()) |
| 125 | returned_id := b.replace_file_stmt(-1, 0, z_id) |
| 126 | assert returned_id == invalid_flat_node_id |
| 127 | } |
| 128 | |
| 129 | fn test_replace_file_stmt_invalid_stmt_idx_returns_invalid() { |
| 130 | mut b := new_flat_builder() |
| 131 | b.append_file(make_minimal_file_with_three_consts()) |
| 132 | z_id := b.emit_stmt(make_replacement_const_stmt()) |
| 133 | // File has 4 stmts (module + 3 consts); idx 99 is out of range. |
| 134 | returned_id := b.replace_file_stmt(0, 99, z_id) |
| 135 | assert returned_id == invalid_flat_node_id |
| 136 | } |
| 137 | |
| 138 | fn test_replace_file_stmt_invalid_new_id_returns_invalid() { |
| 139 | mut b := new_flat_builder() |
| 140 | b.append_file(make_minimal_file_with_three_consts()) |
| 141 | returned_id := b.replace_file_stmt(0, 0, FlatNodeId(-1)) |
| 142 | assert returned_id == invalid_flat_node_id |
| 143 | } |
| 144 | |
| 145 | fn test_replace_file_stmt_updates_files_table() { |
| 146 | mut b := new_flat_builder() |
| 147 | original_file_id := b.append_file(make_minimal_file_with_three_consts()) |
| 148 | z_id := b.emit_stmt(make_replacement_const_stmt()) |
| 149 | new_file_id := b.replace_file_stmt(0, 1, z_id) |
| 150 | // The flat.files[0].file_id must point at the new file root, not the old. |
| 151 | assert new_file_id != original_file_id |
| 152 | assert b.flat.files[0].file_id == new_file_id |
| 153 | } |
| 154 | |