v / vlib / v2 / transformer / auto_str_generation_test.v
537 lines · 491 sloc · 14.59 KB · d358576851079d4716834cc86627307d28acd1ae
Raw
1module transformer
2
3import os
4import v2.ast
5import v2.parser
6import v2.pref as vpref
7import v2.token
8import v2.types
9
10fn auto_str_parse_files_for_test(label string, source string) ([]ast.File, ast.FlatAst, &types.Environment, &vpref.Preferences) {
11 tmp_file := os.join_path(os.vtmp_dir(), 'v2_auto_str_generation_${label}_${os.getpid()}.v')
12 os.write_file(tmp_file, source) or { panic('failed to write temp file') }
13 defer {
14 os.rm(tmp_file) or {}
15 }
16 mut prefs := &vpref.Preferences{
17 backend: .x64
18 no_parallel: true
19 }
20 mut file_set := token.FileSet.new()
21 mut par := parser.Parser.new(prefs)
22 files := par.parse_files([tmp_file], mut file_set)
23 mut env := types.Environment.new()
24 mut checker := types.Checker.new(prefs, file_set, env)
25 checker.check_files(files)
26 flat := ast.flatten_files(files)
27 return files, flat, env, prefs
28}
29
30fn auto_str_legacy_generated_files(label string, source string) []ast.File {
31 files, _, env, prefs := auto_str_parse_files_for_test(label, source)
32 mut trans := Transformer.new_with_pref(env, prefs)
33 return trans.transform_files(files)
34}
35
36fn auto_str_flat_generated_files(label string, source string) []ast.File {
37 files, flat, env, prefs := auto_str_parse_files_for_test(label, source)
38 mut trans := Transformer.new_with_pref(env, prefs)
39 transformed_flat, _ := trans.transform_files_to_flat_via_driver(&flat, files)
40 return transformed_flat.to_files()
41}
42
43fn auto_str_generated_fn_names(files []ast.File) []string {
44 mut names := []string{}
45 for file in files {
46 for stmt in file.stmts {
47 if stmt is ast.FnDecl && stmt.name.ends_with('__str') {
48 names << stmt.name
49 }
50 }
51 }
52 names.sort()
53 return names
54}
55
56fn assert_auto_str_names(files []ast.File, expected []string) {
57 names := auto_str_generated_fn_names(files)
58 for name in expected {
59 assert name in names, '${name} missing from generated names ${names}'
60 }
61}
62
63fn auto_str_generated_fn(files []ast.File, name string) ?ast.FnDecl {
64 for file in files {
65 for stmt in file.stmts {
66 if stmt is ast.FnDecl && stmt.name == name {
67 return stmt
68 }
69 }
70 }
71 return none
72}
73
74fn auto_str_collect_stmt_string_literals(stmt ast.Stmt, mut values []string) {
75 match stmt {
76 ast.AssignStmt {
77 for expr in stmt.lhs {
78 auto_str_collect_expr_string_literals(expr, mut values)
79 }
80 for expr in stmt.rhs {
81 auto_str_collect_expr_string_literals(expr, mut values)
82 }
83 }
84 ast.ExprStmt {
85 auto_str_collect_expr_string_literals(stmt.expr, mut values)
86 }
87 ast.ReturnStmt {
88 for expr in stmt.exprs {
89 auto_str_collect_expr_string_literals(expr, mut values)
90 }
91 }
92 else {}
93 }
94}
95
96fn auto_str_collect_expr_string_literals(expr ast.Expr, mut values []string) {
97 match expr {
98 ast.AsCastExpr {
99 auto_str_collect_expr_string_literals(expr.expr, mut values)
100 auto_str_collect_expr_string_literals(expr.typ, mut values)
101 }
102 ast.CallExpr {
103 for arg in expr.args {
104 auto_str_collect_expr_string_literals(arg, mut values)
105 }
106 }
107 ast.CastExpr {
108 auto_str_collect_expr_string_literals(expr.expr, mut values)
109 auto_str_collect_expr_string_literals(expr.typ, mut values)
110 }
111 ast.IfExpr {
112 auto_str_collect_expr_string_literals(expr.cond, mut values)
113 for nested in expr.stmts {
114 auto_str_collect_stmt_string_literals(nested, mut values)
115 }
116 auto_str_collect_expr_string_literals(expr.else_expr, mut values)
117 }
118 ast.InfixExpr {
119 auto_str_collect_expr_string_literals(expr.lhs, mut values)
120 auto_str_collect_expr_string_literals(expr.rhs, mut values)
121 }
122 ast.ModifierExpr {
123 auto_str_collect_expr_string_literals(expr.expr, mut values)
124 }
125 ast.ParenExpr {
126 auto_str_collect_expr_string_literals(expr.expr, mut values)
127 }
128 ast.PrefixExpr {
129 auto_str_collect_expr_string_literals(expr.expr, mut values)
130 }
131 ast.SelectorExpr {
132 auto_str_collect_expr_string_literals(expr.lhs, mut values)
133 }
134 ast.StringLiteral {
135 values << expr.value
136 }
137 else {}
138 }
139}
140
141fn auto_str_fn_string_literals(files []ast.File, fn_name string) []string {
142 fn_decl := auto_str_generated_fn(files, fn_name) or {
143 assert false, 'missing generated function ${fn_name}'
144 return []string{}
145 }
146 mut values := []string{}
147 for stmt in fn_decl.stmts {
148 auto_str_collect_stmt_string_literals(stmt, mut values)
149 }
150 return values
151}
152
153fn auto_str_has_literal_prefix(values []string, prefix string) bool {
154 for value in values {
155 if value.starts_with(prefix) {
156 return true
157 }
158 }
159 return false
160}
161
162fn auto_str_collect_stmt_call_names(stmt ast.Stmt, mut names []string) {
163 match stmt {
164 ast.AssignStmt {
165 for expr in stmt.lhs {
166 auto_str_collect_expr_call_names(expr, mut names)
167 }
168 for expr in stmt.rhs {
169 auto_str_collect_expr_call_names(expr, mut names)
170 }
171 }
172 ast.ExprStmt {
173 auto_str_collect_expr_call_names(stmt.expr, mut names)
174 }
175 ast.ForStmt {
176 auto_str_collect_stmt_call_names(stmt.init, mut names)
177 auto_str_collect_expr_call_names(stmt.cond, mut names)
178 auto_str_collect_stmt_call_names(stmt.post, mut names)
179 for nested in stmt.stmts {
180 auto_str_collect_stmt_call_names(nested, mut names)
181 }
182 }
183 ast.ReturnStmt {
184 for expr in stmt.exprs {
185 auto_str_collect_expr_call_names(expr, mut names)
186 }
187 }
188 else {}
189 }
190}
191
192fn auto_str_collect_expr_call_names(expr ast.Expr, mut names []string) {
193 match expr {
194 ast.AsCastExpr {
195 auto_str_collect_expr_call_names(expr.expr, mut names)
196 auto_str_collect_expr_call_names(expr.typ, mut names)
197 }
198 ast.CallExpr {
199 if expr.lhs is ast.Ident {
200 lhs := expr.lhs as ast.Ident
201 names << lhs.name
202 }
203 for arg in expr.args {
204 auto_str_collect_expr_call_names(arg, mut names)
205 }
206 }
207 ast.CastExpr {
208 auto_str_collect_expr_call_names(expr.expr, mut names)
209 auto_str_collect_expr_call_names(expr.typ, mut names)
210 }
211 ast.IfExpr {
212 auto_str_collect_expr_call_names(expr.cond, mut names)
213 for nested in expr.stmts {
214 auto_str_collect_stmt_call_names(nested, mut names)
215 }
216 auto_str_collect_expr_call_names(expr.else_expr, mut names)
217 }
218 ast.InfixExpr {
219 auto_str_collect_expr_call_names(expr.lhs, mut names)
220 auto_str_collect_expr_call_names(expr.rhs, mut names)
221 }
222 ast.ModifierExpr {
223 auto_str_collect_expr_call_names(expr.expr, mut names)
224 }
225 ast.ParenExpr {
226 auto_str_collect_expr_call_names(expr.expr, mut names)
227 }
228 ast.PrefixExpr {
229 auto_str_collect_expr_call_names(expr.expr, mut names)
230 }
231 ast.SelectorExpr {
232 auto_str_collect_expr_call_names(expr.lhs, mut names)
233 }
234 ast.StringInterLiteral {
235 for inter in expr.inters {
236 auto_str_collect_expr_call_names(inter.expr, mut names)
237 auto_str_collect_expr_call_names(inter.format_expr, mut names)
238 }
239 }
240 else {}
241 }
242}
243
244fn auto_str_fn_call_names(files []ast.File, fn_name string) []string {
245 fn_decl := auto_str_generated_fn(files, fn_name) or {
246 assert false, 'missing generated function ${fn_name}'
247 return []string{}
248 }
249 mut names := []string{}
250 for stmt in fn_decl.stmts {
251 auto_str_collect_stmt_call_names(stmt, mut names)
252 }
253 return names
254}
255
256fn test_array_str_generation_uses_helper_name_for_elem_type_when_metadata_is_stale() {
257 env := types.Environment.new()
258 mut trans := Transformer.new_with_pref(env, &vpref.Preferences{})
259 stmt := trans.generate_array_str_fn('Array_int_str', 'ast__KeywordOperator__str')
260 if stmt is ast.FnDecl {
261 mut calls := []string{}
262 for body_stmt in stmt.stmts {
263 auto_str_collect_stmt_call_names(body_stmt, mut calls)
264 }
265 assert 'int__str' in calls, calls.str()
266 assert 'ast__KeywordOperator__str__str' !in calls, calls.str()
267 return
268 }
269 assert false, 'expected generated Array_int_str function'
270}
271
272fn auto_str_collect_stmt_selector_names(stmt ast.Stmt, mut names []string) {
273 match stmt {
274 ast.AssignStmt {
275 for expr in stmt.lhs {
276 auto_str_collect_expr_selector_names(expr, mut names)
277 }
278 for expr in stmt.rhs {
279 auto_str_collect_expr_selector_names(expr, mut names)
280 }
281 }
282 ast.ExprStmt {
283 auto_str_collect_expr_selector_names(stmt.expr, mut names)
284 }
285 ast.ReturnStmt {
286 for expr in stmt.exprs {
287 auto_str_collect_expr_selector_names(expr, mut names)
288 }
289 }
290 else {}
291 }
292}
293
294fn auto_str_collect_expr_selector_names(expr ast.Expr, mut names []string) {
295 match expr {
296 ast.AsCastExpr {
297 auto_str_collect_expr_selector_names(expr.expr, mut names)
298 auto_str_collect_expr_selector_names(expr.typ, mut names)
299 }
300 ast.CallExpr {
301 for arg in expr.args {
302 auto_str_collect_expr_selector_names(arg, mut names)
303 }
304 }
305 ast.CastExpr {
306 auto_str_collect_expr_selector_names(expr.expr, mut names)
307 auto_str_collect_expr_selector_names(expr.typ, mut names)
308 }
309 ast.IfExpr {
310 auto_str_collect_expr_selector_names(expr.cond, mut names)
311 for nested in expr.stmts {
312 auto_str_collect_stmt_selector_names(nested, mut names)
313 }
314 auto_str_collect_expr_selector_names(expr.else_expr, mut names)
315 }
316 ast.InfixExpr {
317 auto_str_collect_expr_selector_names(expr.lhs, mut names)
318 auto_str_collect_expr_selector_names(expr.rhs, mut names)
319 }
320 ast.ModifierExpr {
321 auto_str_collect_expr_selector_names(expr.expr, mut names)
322 }
323 ast.ParenExpr {
324 auto_str_collect_expr_selector_names(expr.expr, mut names)
325 }
326 ast.PrefixExpr {
327 auto_str_collect_expr_selector_names(expr.expr, mut names)
328 }
329 ast.SelectorExpr {
330 names << expr.rhs.name
331 auto_str_collect_expr_selector_names(expr.lhs, mut names)
332 }
333 ast.StringInterLiteral {
334 for inter in expr.inters {
335 auto_str_collect_expr_selector_names(inter.expr, mut names)
336 auto_str_collect_expr_selector_names(inter.format_expr, mut names)
337 }
338 }
339 else {}
340 }
341}
342
343fn auto_str_fn_selector_names(files []ast.File, fn_name string) []string {
344 fn_decl := auto_str_generated_fn(files, fn_name) or {
345 assert false, 'missing generated function ${fn_name}'
346 return []string{}
347 }
348 mut names := []string{}
349 for stmt in fn_decl.stmts {
350 auto_str_collect_stmt_selector_names(stmt, mut names)
351 }
352 return names
353}
354
355fn assert_auto_str_simple_point_body(files []ast.File) {
356 literals := auto_str_fn_string_literals(files, 'Point__str')
357 calls := auto_str_fn_call_names(files, 'Point__str')
358 assert 'Point{}' !in literals
359 assert auto_str_has_literal_prefix(literals, 'Point{')
360 assert ' x: ' in literals
361 assert ' y: ' in literals
362 assert 'strings__Builder__write_string' in calls
363 assert 'strings__Builder__str' in calls
364}
365
366fn assert_auto_str_recursive_node_body(files []ast.File) {
367 literals := auto_str_fn_string_literals(files, 'Node__str')
368 calls := auto_str_fn_call_names(files, 'Node__str')
369 assert 'Node{}' !in literals
370 assert auto_str_has_literal_prefix(literals, 'Node{')
371 assert ' value: ' in literals
372 assert ' left: ' in literals
373 assert ' right: ' in literals
374 assert 'Tree__str' in calls
375 assert 'string__replace' in calls
376}
377
378fn assert_auto_str_tree_sumtype_body(files []ast.File) {
379 literals := auto_str_fn_string_literals(files, 'Tree__str')
380 calls := auto_str_fn_call_names(files, 'Tree__str')
381 selectors := auto_str_fn_selector_names(files, 'Tree__str')
382 assert 'Tree(' in literals
383 assert ')' in literals
384 assert 'Tree{}' in literals
385 assert 'Empty__str' in calls
386 assert 'Node__str' in calls
387 assert '_tag' in selectors
388}
389
390fn assert_auto_str_main_uses_node_str(files []ast.File) {
391 calls := auto_str_fn_call_names(files, 'main')
392 assert 'Node__str' in calls
393}
394
395fn test_auto_struct_str_generates_field_struct_helper_from_legacy_and_flat() {
396 source := '
397module main
398
399struct Point {
400 x int
401 y int
402}
403
404fn show(point Point) string {
405 return "\${point}"
406}
407'
408 legacy := auto_str_legacy_generated_files('simple_struct_legacy', source)
409 flat := auto_str_flat_generated_files('simple_struct_flat', source)
410 assert_auto_str_names(legacy, ['Point__str'])
411 assert_auto_str_names(flat, ['Point__str'])
412 assert_auto_str_simple_point_body(legacy)
413 assert_auto_str_simple_point_body(flat)
414}
415
416fn test_auto_sumtype_str_generates_wrapper_and_variant_helpers_from_legacy_and_flat() {
417 source := '
418module main
419
420type Tree = Empty | Node
421
422struct Empty {}
423
424struct Node {
425 value int
426}
427
428fn show(tree Tree) string {
429 return "\${tree}"
430}
431'
432 legacy := auto_str_legacy_generated_files('sumtype_legacy', source)
433 flat := auto_str_flat_generated_files('sumtype_flat', source)
434 assert_auto_str_names(legacy, ['Empty__str', 'Node__str', 'Tree__str'])
435 assert_auto_str_names(flat, ['Empty__str', 'Node__str', 'Tree__str'])
436 assert_auto_str_tree_sumtype_body(legacy)
437 assert_auto_str_tree_sumtype_body(flat)
438}
439
440fn test_auto_recursive_struct_sumtype_str_generates_helpers_from_legacy_and_flat() {
441 source := '
442module main
443
444type Tree = Empty | Node
445
446struct Empty {}
447
448struct Node {
449 value int
450 left Tree
451 right Tree
452}
453
454fn show(node Node) string {
455 return "\${node}"
456}
457'
458 legacy := auto_str_legacy_generated_files('recursive_legacy', source)
459 flat := auto_str_flat_generated_files('recursive_flat', source)
460 assert_auto_str_names(legacy, ['Empty__str', 'Node__str', 'Tree__str'])
461 assert_auto_str_names(flat, ['Empty__str', 'Node__str', 'Tree__str'])
462 assert_auto_str_recursive_node_body(legacy)
463 assert_auto_str_recursive_node_body(flat)
464 assert_auto_str_tree_sumtype_body(legacy)
465 assert_auto_str_tree_sumtype_body(flat)
466}
467
468fn test_auto_recursive_struct_sumtype_str_from_call_arg_interpolation_legacy_and_flat() {
469 source := '
470module main
471
472type Tree = Empty | Node
473
474struct Empty {}
475
476struct Node {
477 value int
478 left Tree
479 right Tree
480}
481
482fn sink(s string) {
483 _ = s
484}
485
486fn main() {
487 leaf := Node{7, Empty{}, Empty{}}
488 root := Node{9, leaf, Empty{}}
489 sink("\${root}")
490}
491'
492 legacy := auto_str_legacy_generated_files('recursive_call_arg_legacy', source)
493 flat := auto_str_flat_generated_files('recursive_call_arg_flat', source)
494 assert_auto_str_names(legacy, ['Empty__str', 'Node__str', 'Tree__str'])
495 assert_auto_str_names(flat, ['Empty__str', 'Node__str', 'Tree__str'])
496 assert_auto_str_main_uses_node_str(legacy)
497 assert_auto_str_main_uses_node_str(flat)
498 assert_auto_str_recursive_node_body(legacy)
499 assert_auto_str_recursive_node_body(flat)
500 assert_auto_str_tree_sumtype_body(legacy)
501 assert_auto_str_tree_sumtype_body(flat)
502}
503
504fn test_auto_recursive_struct_sumtype_str_from_implicit_main_call_arg_legacy_and_flat() {
505 source := '
506type Tree = Empty | Node
507
508struct Empty {}
509
510struct Node {
511 value int
512 left Tree
513 right Tree
514}
515
516fn sink(s string) {
517 _ = s
518}
519
520fn main() {
521 node1 := Node{30, Empty{}, Empty{}}
522 node2 := Node{20, Empty{}, Empty{}}
523 tree := Node{10, node1, node2}
524 sink("\${tree}")
525}
526'
527 legacy := auto_str_legacy_generated_files('recursive_implicit_main_call_arg_legacy', source)
528 flat := auto_str_flat_generated_files('recursive_implicit_main_call_arg_flat', source)
529 assert_auto_str_names(legacy, ['Empty__str', 'Node__str', 'Tree__str'])
530 assert_auto_str_names(flat, ['Empty__str', 'Node__str', 'Tree__str'])
531 assert_auto_str_main_uses_node_str(legacy)
532 assert_auto_str_main_uses_node_str(flat)
533 assert_auto_str_recursive_node_body(legacy)
534 assert_auto_str_recursive_node_body(flat)
535 assert_auto_str_tree_sumtype_body(legacy)
536 assert_auto_str_tree_sumtype_body(flat)
537}
538