v / vlib / v2 / transformer / generated_fns_parts_from_flat_test.v
223 lines · 211 sloc · 7.03 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 s166: `generated_fns_parts_from_flat` must produce
7// a `GeneratedFnsParts` whose buckets contain the same fn names in the
8// same order as the legacy `generated_fns_parts([]ast.File)`. The two
9// flat-input helpers (`explicit_str_method_fn_names_from_flat` from s165
10// and `generated_fn_module_from_flat` from s164) are individually pinned;
11// this test pins their assembly.
12module transformer
13
14import v2.ast
15import v2.pref as vpref
16import v2.types
17
18fn create_parts_from_flat_test_transformer() &Transformer {
19 env := &types.Environment{}
20 return &Transformer{
21 pref: &vpref.Preferences{}
22 env: unsafe { env }
23 needed_clone_fns: map[string]string{}
24 needed_array_contains_fns: map[string]ArrayMethodInfo{}
25 needed_array_index_fns: map[string]ArrayMethodInfo{}
26 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
27 local_decl_types: map[string]types.Type{}
28 }
29}
30
31fn fn_names_from_stmts(stmts []ast.Stmt) []string {
32 mut names := []string{cap: stmts.len}
33 for s in stmts {
34 if s is ast.FnDecl {
35 names << s.name
36 }
37 }
38 return names
39}
40
41fn assert_parts_equal(legacy GeneratedFnsParts, fromflat GeneratedFnsParts) {
42 legacy_core := fn_names_from_stmts(legacy.core_fns)
43 flat_core := fn_names_from_stmts(fromflat.core_fns)
44 assert legacy_core == flat_core
45
46 legacy_user := fn_names_from_stmts(legacy.user_fns)
47 flat_user := fn_names_from_stmts(fromflat.user_fns)
48 assert legacy_user == flat_user
49
50 assert legacy.module_fns.len == fromflat.module_fns.len
51 for mod, _ in legacy.module_fns {
52 legacy_mod := fn_names_from_stmts(legacy.module_fns[mod])
53 flat_mod := fn_names_from_stmts(fromflat.module_fns[mod])
54 assert legacy_mod == flat_mod
55 }
56}
57
58// none branch: no needed_* maps populated → both return `none`.
59fn test_generated_fns_parts_from_flat_none_when_no_generators_fire() {
60 files := [ast.File{
61 name: 'main.v'
62 mod: 'main'
63 }]
64 flat := ast.flatten_files(files)
65 mut t_legacy := create_parts_from_flat_test_transformer()
66 mut t_flat := create_parts_from_flat_test_transformer()
67 legacy := t_legacy.generated_fns_parts(files) or {
68 assert true
69 assert t_flat.generated_fns_parts_from_flat(&flat) == none
70 return
71 }
72 _ = legacy
73 assert false, 'expected none'
74}
75
76// str-fn routed to a registered module: Duration__str → 'time' bucket.
77fn test_generated_fns_parts_from_flat_str_fn_routed_to_module() {
78 // Two files: main + time. Both register `time__Duration__str` as needed.
79 files := [
80 ast.File{
81 name: 'main.v'
82 mod: 'main'
83 },
84 ast.File{
85 name: 'time.v'
86 mod: 'time'
87 },
88 ]
89 flat := ast.flatten_files(files)
90 mut t_legacy := create_parts_from_flat_test_transformer()
91 mut t_flat := create_parts_from_flat_test_transformer()
92 t_legacy.needed_str_fns['time__Duration__str'] = ''
93 t_flat.needed_str_fns['time__Duration__str'] = ''
94 legacy := t_legacy.generated_fns_parts(files) or {
95 assert false, 'legacy returned none'
96 return
97 }
98 fromflat := t_flat.generated_fns_parts_from_flat(&flat) or {
99 assert false, 'flat returned none'
100 return
101 }
102 assert legacy.module_fns['time'].len == 1
103 assert fromflat.module_fns['time'].len == 1
104 assert_parts_equal(legacy, fromflat)
105}
106
107// core_fn (int_str) routed to core bucket regardless of files.
108fn test_generated_fns_parts_from_flat_core_fn_routed_to_core() {
109 files := [ast.File{
110 name: 'main.v'
111 mod: 'main'
112 }]
113 flat := ast.flatten_files(files)
114 mut t_legacy := create_parts_from_flat_test_transformer()
115 mut t_flat := create_parts_from_flat_test_transformer()
116 // int__str is core (is_core_generated_fn matches it). Use a typed
117 // non-existent str entry that gets generated but goes to the core bucket
118 // via the prefix list. We rely on the existing `int__str` path.
119 t_legacy.needed_str_fns['int__str'] = ''
120 t_flat.needed_str_fns['int__str'] = ''
121 legacy := t_legacy.generated_fns_parts(files) or {
122 // int__str may short-circuit to skip generation; if so just assert
123 // both paths agree by returning none.
124 assert t_flat.generated_fns_parts_from_flat(&flat) == none
125 return
126 }
127 fromflat := t_flat.generated_fns_parts_from_flat(&flat) or {
128 assert false, 'flat returned none while legacy returned parts'
129 return
130 }
131 assert_parts_equal(legacy, fromflat)
132}
133
134// user_fn (Array_int_str) routed to user bucket (not core, no module prefix).
135fn test_generated_fns_parts_from_flat_array_str_fn_routed_to_user() {
136 files := [ast.File{
137 name: 'main.v'
138 mod: 'main'
139 }]
140 flat := ast.flatten_files(files)
141 mut t_legacy := create_parts_from_flat_test_transformer()
142 mut t_flat := create_parts_from_flat_test_transformer()
143 t_legacy.needed_str_fns['Array_int_str'] = 'int'
144 t_flat.needed_str_fns['Array_int_str'] = 'int'
145 legacy := t_legacy.generated_fns_parts(files) or {
146 assert false, 'legacy returned none'
147 return
148 }
149 fromflat := t_flat.generated_fns_parts_from_flat(&flat) or {
150 assert false, 'flat returned none'
151 return
152 }
153 assert_parts_equal(legacy, fromflat)
154}
155
156// mixed buckets: time__Duration__str → 'time' module, Array_int_str → user.
157fn test_generated_fns_parts_from_flat_mixed_buckets_match() {
158 files := [
159 ast.File{
160 name: 'main.v'
161 mod: 'main'
162 },
163 ast.File{
164 name: 'time.v'
165 mod: 'time'
166 },
167 ]
168 flat := ast.flatten_files(files)
169 mut t_legacy := create_parts_from_flat_test_transformer()
170 mut t_flat := create_parts_from_flat_test_transformer()
171 t_legacy.needed_str_fns['time__Duration__str'] = ''
172 t_legacy.needed_str_fns['Array_int_str'] = 'int'
173 t_flat.needed_str_fns['time__Duration__str'] = ''
174 t_flat.needed_str_fns['Array_int_str'] = 'int'
175 legacy := t_legacy.generated_fns_parts(files) or {
176 assert false, 'legacy returned none'
177 return
178 }
179 fromflat := t_flat.generated_fns_parts_from_flat(&flat) or {
180 assert false, 'flat returned none'
181 return
182 }
183 assert_parts_equal(legacy, fromflat)
184}
185
186// explicit str method skips generation: file has `fn (d Duration) str()` for
187// time module, and `needed_str_fns` requests `time__Duration__str` — both
188// paths must skip the generator (explicit_str_fns contains the name).
189fn test_generated_fns_parts_from_flat_explicit_str_method_skips_generation() {
190 files := [
191 ast.File{
192 name: 'time.v'
193 mod: 'time'
194 stmts: [
195 ast.Stmt(ast.FnDecl{
196 name: 'str'
197 is_method: true
198 receiver: ast.Parameter{
199 name: 'd'
200 typ: ast.Expr(ast.Ident{
201 name: 'Duration'
202 })
203 }
204 typ: ast.FnType{
205 return_type: ast.Ident{
206 name: 'string'
207 }
208 }
209 }),
210 ]
211 },
212 ]
213 flat := ast.flatten_files(files)
214 mut t_legacy := create_parts_from_flat_test_transformer()
215 mut t_flat := create_parts_from_flat_test_transformer()
216 t_legacy.needed_str_fns['time__Duration__str'] = ''
217 t_flat.needed_str_fns['time__Duration__str'] = ''
218 // Generator should skip because explicit fn exists; both paths return none.
219 legacy_opt := t_legacy.generated_fns_parts(files)
220 flat_opt := t_flat.generated_fns_parts_from_flat(&flat)
221 assert legacy_opt == none
222 assert flat_opt == none
223}
224