v / vlib / v2 / transformer / post_pass_to_flat_test.v
180 lines · 169 sloc · 5.85 KB · f3c5760b8838272e4789c38a1be9e03f9ceac351
Raw
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.
12module transformer
13
14import v2.ast
15import v2.pref as vpref
16import v2.types
17
18// Local helper — `v test` compiles each `_test.v` file in isolation.
19fn 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
34fn 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
50fn 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.
67fn 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.
84fn 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.
138fn 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