| 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.prepend_to_fn_body`. The helper is the seed primitive |
| 7 | // for the remaining prepend-style post_pass mutations |
| 8 | // (`inject_main_runtime_const_init_calls`, `inject_live_reload`): it re-emits |
| 9 | // a stmt_fn_decl with extras prepended to its body, returning the new |
| 10 | // FlatNodeId so callers can rewire the surrounding file/stmt list. |
| 11 | // |
| 12 | // We compare via `subtree_signature(fn_id)` rather than the whole-file |
| 13 | // `signature()` because the file root's `extra=intern(mod)` slot would leak |
| 14 | // intern order between the reference (which builds extras before file mod |
| 15 | // gets re-interned) and subject (which interns extras AFTER file mod) paths. |
| 16 | |
| 17 | fn make_fn_decl_with_body(name string, body []Stmt) Stmt { |
| 18 | return Stmt(FnDecl{ |
| 19 | name: name |
| 20 | stmts: body |
| 21 | }) |
| 22 | } |
| 23 | |
| 24 | fn make_const_stmt(name string, value string) Stmt { |
| 25 | return Stmt(ConstDecl{ |
| 26 | fields: [ |
| 27 | FieldInit{ |
| 28 | name: name |
| 29 | value: Expr(BasicLiteral{ |
| 30 | kind: .number |
| 31 | value: value |
| 32 | }) |
| 33 | }, |
| 34 | ] |
| 35 | }) |
| 36 | } |
| 37 | |
| 38 | // Reference: emit a FnDecl whose body is already `[extras + old]`. |
| 39 | fn build_reference_fn_with_prepended() (FlatAst, FlatNodeId) { |
| 40 | mut b := new_flat_builder() |
| 41 | fn_id := b.emit_stmt(make_fn_decl_with_body('foo', [ |
| 42 | make_const_stmt('A', '1'), |
| 43 | make_const_stmt('B', '2'), |
| 44 | make_const_stmt('C', '3'), |
| 45 | ])) |
| 46 | return b.flat, fn_id |
| 47 | } |
| 48 | |
| 49 | // Subject: emit a FnDecl whose body is `[old]`, emit extras separately, |
| 50 | // then prepend via the primitive under test. |
| 51 | fn build_subject_fn_with_prepended() (FlatAst, FlatNodeId) { |
| 52 | mut b := new_flat_builder() |
| 53 | base_fn_id := b.emit_stmt(make_fn_decl_with_body('foo', [ |
| 54 | make_const_stmt('C', '3'), |
| 55 | ])) |
| 56 | a_id := b.emit_stmt(make_const_stmt('A', '1')) |
| 57 | b_id := b.emit_stmt(make_const_stmt('B', '2')) |
| 58 | new_fn_id := b.prepend_to_fn_body(base_fn_id, [a_id, b_id]) |
| 59 | return b.flat, new_fn_id |
| 60 | } |
| 61 | |
| 62 | fn test_prepend_to_fn_body_signature_matches_reference() { |
| 63 | ref_flat, ref_fn_id := build_reference_fn_with_prepended() |
| 64 | sub_flat, sub_fn_id := build_subject_fn_with_prepended() |
| 65 | ref_sig := ref_flat.subtree_signature(ref_fn_id) |
| 66 | sub_sig := sub_flat.subtree_signature(sub_fn_id) |
| 67 | assert ref_sig == sub_sig |
| 68 | } |
| 69 | |
| 70 | fn test_prepend_to_fn_body_zero_extras_returns_existing_id() { |
| 71 | mut b := new_flat_builder() |
| 72 | original_id := b.emit_stmt(make_fn_decl_with_body('foo', [ |
| 73 | make_const_stmt('C', '3'), |
| 74 | ])) |
| 75 | returned_id := b.prepend_to_fn_body(original_id, []) |
| 76 | assert returned_id == original_id |
| 77 | } |
| 78 | |
| 79 | fn test_prepend_to_fn_body_invalid_id_returns_invalid() { |
| 80 | mut b := new_flat_builder() |
| 81 | a_id := b.emit_stmt(make_const_stmt('A', '1')) |
| 82 | returned_id := b.prepend_to_fn_body(FlatNodeId(-1), [a_id]) |
| 83 | assert returned_id == invalid_flat_node_id |
| 84 | } |
| 85 | |
| 86 | fn test_prepend_to_fn_body_non_fn_decl_returns_invalid() { |
| 87 | // Pass a non-FnDecl node id (a ConstDecl) — primitive must refuse. |
| 88 | mut b := new_flat_builder() |
| 89 | const_id := b.emit_stmt(make_const_stmt('C', '3')) |
| 90 | extra_id := b.emit_stmt(make_const_stmt('A', '1')) |
| 91 | returned_id := b.prepend_to_fn_body(const_id, [extra_id]) |
| 92 | assert returned_id == invalid_flat_node_id |
| 93 | } |
| 94 | |
| 95 | fn test_prepend_to_fn_body_empty_original_body() { |
| 96 | // FnDecl with no original body stmts; prepending should still produce a |
| 97 | // new FnDecl whose body is exactly the extras. |
| 98 | ref_flat, ref_fn_id := fn () (FlatAst, FlatNodeId) { |
| 99 | mut b := new_flat_builder() |
| 100 | fn_id := b.emit_stmt(make_fn_decl_with_body('foo', [make_const_stmt('A', '1')])) |
| 101 | return b.flat, fn_id |
| 102 | }() |
| 103 | |
| 104 | mut sb := new_flat_builder() |
| 105 | base_fn_id := sb.emit_stmt(make_fn_decl_with_body('foo', [])) |
| 106 | a_id := sb.emit_stmt(make_const_stmt('A', '1')) |
| 107 | new_fn_id := sb.prepend_to_fn_body(base_fn_id, [a_id]) |
| 108 | |
| 109 | assert ref_flat.subtree_signature(ref_fn_id) == sb.flat.subtree_signature(new_fn_id) |
| 110 | } |
| 111 | |