vxx / vlib / v2_toberemoved / transformer / transformer_test.v
9756 lines · 9066 sloc · 234.16 KB
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
5module transformer
6
7import os
8import v2.ast
9import v2.parser
10import v2.pref as vpref
11import v2.token
12import v2.types
13
14// Helper to create a minimal transformer for testing
15fn create_test_transformer() &Transformer {
16 env := &types.Environment{}
17 return &Transformer{
18 pref: &vpref.Preferences{}
19 env: unsafe { env }
20 needed_clone_fns: map[string]string{}
21 needed_array_contains_fns: map[string]ArrayMethodInfo{}
22 needed_array_index_fns: map[string]ArrayMethodInfo{}
23 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
24 local_decl_types: map[string]types.Type{}
25 }
26}
27
28// Helper to create a transformer with a scope containing variable types
29fn create_transformer_with_vars(vars map[string]types.Type) &Transformer {
30 env := &types.Environment{}
31 mut scope := types.new_scope(unsafe { nil })
32 for name, typ in vars {
33 scope.insert(name, value_object_from_type(typ))
34 }
35 return &Transformer{
36 pref: &vpref.Preferences{}
37 env: unsafe { env }
38 scope: scope
39 needed_clone_fns: map[string]string{}
40 needed_array_contains_fns: map[string]ArrayMethodInfo{}
41 needed_array_index_fns: map[string]ArrayMethodInfo{}
42 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
43 local_decl_types: map[string]types.Type{}
44 }
45}
46
47fn test_value_object_from_type_stores_value_symbol_type() {
48 obj := value_object_from_type(types.Type(types.int_))
49 assert obj is types.TypeObject
50 assert obj.typ() is types.Primitive
51}
52
53fn test_worker_clone_owns_mutable_maps() {
54 prefs := &vpref.Preferences{}
55 env := types.Environment.new()
56 mut transformer := Transformer.new_with_pref(env, prefs)
57 transformer.synth_pos_counter = -73
58 scope := types.new_scope(unsafe { nil })
59 transformer.elided_fns = {
60 'main__skipped': true
61 }
62 transformer.cached_scopes = {
63 'main': scope
64 }
65 transformer.cached_fn_scopes = {
66 'main__known': scope
67 }
68 transformer.cached_method_keys = ['main__Foo']
69 transformer.generic_fn_value_names = {
70 'handler': true
71 }
72
73 mut worker := transformer.new_worker_clone(1)
74 assert worker.next_synth_pos().id == -100073
75 worker.elided_fns['main__worker_skipped'] = true
76 worker.cached_scopes['worker'] = scope
77 worker.cached_fn_scopes['main__worker'] = scope
78 worker.cached_method_keys << 'main__Bar'
79 worker.generic_fn_value_names['worker_handler'] = true
80
81 assert 'main__worker_skipped' !in transformer.elided_fns
82 assert 'worker' !in transformer.cached_scopes
83 assert 'main__worker' !in transformer.cached_fn_scopes
84 assert transformer.cached_method_keys == ['main__Foo']
85 assert 'worker_handler' !in transformer.generic_fn_value_names
86
87 transformer.merge_worker(worker)
88 assert transformer.elided_fns['main__worker_skipped']
89 assert transformer.cached_fn_scopes['main__worker'] == scope
90}
91
92fn transform_code_for_test(code string) []ast.File {
93 prefs := &vpref.Preferences{
94 backend: .cleanc
95 no_parallel: true
96 }
97 return transform_code_with_prefs_for_test(code, prefs)
98}
99
100fn transform_code_with_prefs_for_test(code string, prefs &vpref.Preferences) []ast.File {
101 tmp_file := '/tmp/v2_transformer_test_${os.getpid()}.v'
102 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
103 defer {
104 os.rm(tmp_file) or {}
105 }
106 mut file_set := token.FileSet.new()
107 mut par := parser.Parser.new(prefs)
108 files := par.parse_files([tmp_file], mut file_set)
109 mut env := types.Environment.new()
110 mut checker := types.Checker.new(prefs, file_set, env)
111 checker.check_files(files)
112 mut transformer := Transformer.new_with_pref(env, prefs)
113 return transformer.transform_files(files)
114}
115
116fn transform_code_with_env_for_test(code string) (&types.Environment, []ast.File) {
117 tmp_file := '/tmp/v2_transformer_env_test_${os.getpid()}.v'
118 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
119 defer {
120 os.rm(tmp_file) or {}
121 }
122 prefs := &vpref.Preferences{
123 backend: .cleanc
124 no_parallel: true
125 }
126 mut file_set := token.FileSet.new()
127 mut par := parser.Parser.new(prefs)
128 files := par.parse_files([tmp_file], mut file_set)
129 mut env := types.Environment.new()
130 mut checker := types.Checker.new(prefs, file_set, env)
131 checker.check_files(files)
132 mut transformer := Transformer.new_with_pref(env, prefs)
133 transformed := transformer.transform_files(files)
134 return env, transformed
135}
136
137struct TestSource {
138 rel string
139 code string
140}
141
142fn transform_sources_for_test(sources []TestSource) []ast.File {
143 tmp_dir := os.join_path(os.temp_dir(), 'v2_transformer_sources_${os.getpid()}')
144 os.rmdir_all(tmp_dir) or {}
145 os.mkdir_all(tmp_dir) or { panic(err) }
146 defer {
147 os.rmdir_all(tmp_dir) or {}
148 }
149 mut paths := []string{cap: sources.len}
150 for source in sources {
151 path := os.join_path(tmp_dir, source.rel)
152 os.mkdir_all(os.dir(path)) or { panic(err) }
153 os.write_file(path, source.code) or { panic('failed to write ${path}') }
154 paths << path
155 }
156 prefs := &vpref.Preferences{
157 backend: .cleanc
158 no_parallel: true
159 }
160 mut file_set := token.FileSet.new()
161 mut par := parser.Parser.new(prefs)
162 files := par.parse_files(paths, mut file_set)
163 mut env := types.Environment.new()
164 mut checker := types.Checker.new(prefs, file_set, env)
165 checker.check_files(files)
166 mut transformer := Transformer.new_with_pref(env, prefs)
167 return transformer.transform_files(files)
168}
169
170fn transform_sources_with_env_for_test(sources []TestSource) (&types.Environment, []ast.File) {
171 tmp_dir := os.join_path(os.temp_dir(), 'v2_transformer_sources_env_${os.getpid()}')
172 os.rmdir_all(tmp_dir) or {}
173 os.mkdir_all(tmp_dir) or { panic(err) }
174 defer {
175 os.rmdir_all(tmp_dir) or {}
176 }
177 mut paths := []string{cap: sources.len}
178 for source in sources {
179 path := os.join_path(tmp_dir, source.rel)
180 os.mkdir_all(os.dir(path)) or { panic(err) }
181 os.write_file(path, source.code) or { panic('failed to write ${path}') }
182 paths << path
183 }
184 prefs := &vpref.Preferences{
185 backend: .cleanc
186 no_parallel: true
187 }
188 mut file_set := token.FileSet.new()
189 mut par := parser.Parser.new(prefs)
190 files := par.parse_files(paths, mut file_set)
191 mut env := types.Environment.new()
192 mut checker := types.Checker.new(prefs, file_set, env)
193 checker.check_files(files)
194 mut transformer := Transformer.new_with_pref(env, prefs)
195 transformed := transformer.transform_files(files)
196 return env, transformed
197}
198
199fn ident_name_from_expr_for_test(expr ast.Expr) ?string {
200 if expr is ast.Ident {
201 return expr.name
202 }
203 if expr is ast.ModifierExpr && expr.expr is ast.Ident {
204 return expr.expr.name
205 }
206 return none
207}
208
209fn test_sort_selector_path_after_root_keeps_nested_fields() {
210 t := create_test_transformer()
211 expr := ast.Expr(ast.SelectorExpr{
212 lhs: ast.Expr(ast.SelectorExpr{
213 lhs: ast.Expr(ast.Ident{
214 name: 'a'
215 })
216 rhs: ast.Ident{
217 name: 'path'
218 }
219 })
220 rhs: ast.Ident{
221 name: 'len'
222 }
223 })
224 path := t.selector_path_after_root(expr, 'a') or { panic('missing selector path') }
225
226 assert path.len == 2
227 assert path[0] == 'path'
228 assert path[1] == 'len'
229}
230
231fn test_get_expr_type_selector_prefers_struct_field_over_stale_pos_type() {
232 mut env := types.Environment.new()
233 env.set_expr_type(91, types.string_)
234 value_kind_type := types.Type(types.Enum{
235 name: 'json2__ValueKind'
236 })
237 value_info_type := types.Type(types.Struct{
238 name: 'json2__ValueInfo'
239 fields: [
240 types.Field{
241 name: 'value_kind'
242 typ: value_kind_type
243 },
244 ]
245 })
246 mut scope := types.new_scope(unsafe { nil })
247 scope.insert('struct_info', value_object_from_type(value_info_type))
248 mut t := Transformer{
249 pref: &vpref.Preferences{}
250 env: unsafe { env }
251 scope: scope
252 local_decl_types: map[string]types.Type{}
253 }
254 typ := t.get_expr_type(ast.SelectorExpr{
255 lhs: ast.Ident{
256 name: 'struct_info'
257 }
258 rhs: ast.Ident{
259 name: 'value_kind'
260 }
261 pos: token.Pos{
262 id: 91
263 }
264 }) or { panic('missing selector type') }
265 assert typ is types.Enum
266 assert (typ as types.Enum).name == 'json2__ValueKind'
267}
268
269fn test_get_expr_type_selector_prefers_struct_field_over_stale_synth_type() {
270 value_kind_type := types.Type(types.Enum{
271 name: 'json2__ValueKind'
272 })
273 value_info_type := types.Type(types.Struct{
274 name: 'json2__ValueInfo'
275 fields: [
276 types.Field{
277 name: 'value_kind'
278 typ: value_kind_type
279 },
280 ]
281 })
282 mut scope := types.new_scope(unsafe { nil })
283 scope.insert('struct_info', value_object_from_type(value_info_type))
284 mut t := Transformer{
285 pref: &vpref.Preferences{}
286 env: unsafe { types.Environment.new() }
287 scope: scope
288 local_decl_types: map[string]types.Type{}
289 }
290 pos := token.Pos{
291 id: 92
292 }
293 t.register_synth_type(pos, types.Type(types.string_))
294 typ := t.get_expr_type(ast.SelectorExpr{
295 lhs: ast.Ident{
296 name: 'struct_info'
297 }
298 rhs: ast.Ident{
299 name: 'value_kind'
300 }
301 pos: pos
302 }) or { panic('missing selector type') }
303 assert typ is types.Enum
304 assert (typ as types.Enum).name == 'json2__ValueKind'
305}
306
307fn test_string_interpolation_prefers_declared_selector_type_over_stale_string_pos() {
308 value_kind_type := types.Type(types.Enum{
309 name: 'json2__ValueKind'
310 })
311 value_info_type := types.Type(types.Struct{
312 name: 'json2__ValueInfo'
313 fields: [
314 types.Field{
315 name: 'value_kind'
316 typ: value_kind_type
317 },
318 ]
319 })
320 mut env := types.Environment.new()
321 env.set_expr_type(93, types.Type(types.string_))
322 mut scope := types.new_scope(unsafe { nil })
323 scope.insert('struct_info', value_object_from_type(value_info_type))
324 mut t := Transformer{
325 pref: &vpref.Preferences{}
326 env: unsafe { env }
327 scope: scope
328 local_decl_types: map[string]types.Type{}
329 needed_str_fns: map[string]string{}
330 needed_enum_str_fns: map[string]types.Enum{}
331 synth_types: map[int]types.Type{}
332 }
333 inter_expr := ast.Expr(ast.SelectorExpr{
334 lhs: ast.Ident{
335 name: 'struct_info'
336 }
337 rhs: ast.Ident{
338 name: 'value_kind'
339 }
340 pos: token.Pos{
341 id: 93
342 }
343 })
344 inter := ast.StringInter{
345 expr: inter_expr
346 }
347 assert t.resolve_sprintf_format(inter) == '%s'
348 arg := t.transform_sprintf_arg(inter)
349 assert arg is ast.SelectorExpr
350 call := (arg as ast.SelectorExpr).lhs
351 assert call is ast.CallExpr
352 lhs := (call as ast.CallExpr).lhs
353 assert lhs is ast.Ident
354 assert (lhs as ast.Ident).name == 'json2__ValueKind__str'
355
356 stale_arg := ast.Expr(ast.SelectorExpr{
357 lhs: ast.Expr(ast.CallExpr{
358 lhs: ast.Ident{
359 name: 'string__str'
360 }
361 args: [inter_expr]
362 pos: token.Pos{
363 id: 94
364 }
365 })
366 rhs: ast.Ident{
367 name: 'str'
368 }
369 pos: token.Pos{
370 id: 95
371 }
372 })
373 lit := t.transform_string_inter_literal(ast.StringInterLiteral{
374 values: ["'Expected object, but got ", "'"]
375 inters: [
376 ast.StringInter{
377 expr: stale_arg
378 },
379 ]
380 })
381 assert lit is ast.StringInterLiteral
382 repaired := (lit as ast.StringInterLiteral).inters[0].expr
383 assert repaired is ast.SelectorExpr
384 repaired_call := (repaired as ast.SelectorExpr).lhs
385 assert repaired_call is ast.CallExpr
386 repaired_lhs := (repaired_call as ast.CallExpr).lhs
387 assert repaired_lhs is ast.Ident
388 assert (repaired_lhs as ast.Ident).name == 'json2__ValueKind__str'
389
390 cloned := t.clone_expr_with_bindings_and_fields(ast.Expr(ast.StringInterLiteral{
391 values: ["'Expected object, but got ", "'"]
392 inters: [
393 ast.StringInter{
394 expr: stale_arg
395 },
396 ]
397 }), map[string]types.Type{}, []CloneComptimeFieldCtx{})
398 assert cloned is ast.StringInterLiteral
399 cloned_arg := (cloned as ast.StringInterLiteral).inters[0].expr
400 assert cloned_arg is ast.SelectorExpr
401 cloned_call := (cloned_arg as ast.SelectorExpr).lhs
402 assert cloned_call is ast.CallExpr
403 cloned_lhs := (cloned_call as ast.CallExpr).lhs
404 assert cloned_lhs is ast.Ident
405 assert (cloned_lhs as ast.Ident).name == 'json2__ValueKind__str'
406}
407
408fn collect_call_names_from_stmt(stmt ast.Stmt, mut names []string) {
409 match stmt {
410 ast.AssignStmt {
411 for expr in stmt.lhs {
412 collect_call_names_from_expr(expr, mut names)
413 }
414 for expr in stmt.rhs {
415 collect_call_names_from_expr(expr, mut names)
416 }
417 }
418 ast.AssertStmt {
419 collect_call_names_from_expr(stmt.expr, mut names)
420 collect_call_names_from_expr(stmt.extra, mut names)
421 }
422 ast.BlockStmt {
423 for nested in stmt.stmts {
424 collect_call_names_from_stmt(nested, mut names)
425 }
426 }
427 ast.ComptimeStmt {
428 collect_call_names_from_stmt(stmt.stmt, mut names)
429 }
430 ast.DeferStmt {
431 for nested in stmt.stmts {
432 collect_call_names_from_stmt(nested, mut names)
433 }
434 }
435 ast.ExprStmt {
436 collect_call_names_from_expr(stmt.expr, mut names)
437 }
438 ast.ForInStmt {
439 collect_call_names_from_expr(stmt.key, mut names)
440 collect_call_names_from_expr(stmt.value, mut names)
441 collect_call_names_from_expr(stmt.expr, mut names)
442 }
443 ast.ForStmt {
444 collect_call_names_from_stmt(stmt.init, mut names)
445 collect_call_names_from_expr(stmt.cond, mut names)
446 collect_call_names_from_stmt(stmt.post, mut names)
447 for nested in stmt.stmts {
448 collect_call_names_from_stmt(nested, mut names)
449 }
450 }
451 ast.LabelStmt {
452 collect_call_names_from_stmt(stmt.stmt, mut names)
453 }
454 ast.ReturnStmt {
455 for expr in stmt.exprs {
456 collect_call_names_from_expr(expr, mut names)
457 }
458 }
459 else {}
460 }
461}
462
463fn collect_call_names_from_expr(expr ast.Expr, mut names []string) {
464 match expr {
465 ast.ArrayInitExpr {
466 collect_call_names_from_expr(expr.init, mut names)
467 collect_call_names_from_expr(expr.cap, mut names)
468 collect_call_names_from_expr(expr.len, mut names)
469 for item in expr.exprs {
470 collect_call_names_from_expr(item, mut names)
471 }
472 }
473 ast.AsCastExpr {
474 collect_call_names_from_expr(expr.expr, mut names)
475 }
476 ast.AssocExpr {
477 collect_call_names_from_expr(expr.expr, mut names)
478 for field in expr.fields {
479 collect_call_names_from_expr(field.value, mut names)
480 }
481 }
482 ast.CallExpr {
483 if expr.lhs is ast.Ident {
484 names << expr.lhs.name
485 }
486 collect_call_names_from_expr(expr.lhs, mut names)
487 for arg in expr.args {
488 collect_call_names_from_expr(arg, mut names)
489 }
490 }
491 ast.CallOrCastExpr {
492 collect_call_names_from_expr(expr.lhs, mut names)
493 collect_call_names_from_expr(expr.expr, mut names)
494 }
495 ast.CastExpr {
496 collect_call_names_from_expr(expr.expr, mut names)
497 }
498 ast.FieldInit {
499 collect_call_names_from_expr(expr.value, mut names)
500 }
501 ast.FnLiteral {
502 for nested in expr.stmts {
503 collect_call_names_from_stmt(nested, mut names)
504 }
505 }
506 ast.GenericArgOrIndexExpr {
507 collect_call_names_from_expr(expr.lhs, mut names)
508 collect_call_names_from_expr(expr.expr, mut names)
509 }
510 ast.GenericArgs {
511 collect_call_names_from_expr(expr.lhs, mut names)
512 for arg in expr.args {
513 collect_call_names_from_expr(arg, mut names)
514 }
515 }
516 ast.IfExpr {
517 collect_call_names_from_expr(expr.cond, mut names)
518 for nested in expr.stmts {
519 collect_call_names_from_stmt(nested, mut names)
520 }
521 collect_call_names_from_expr(expr.else_expr, mut names)
522 }
523 ast.IndexExpr {
524 collect_call_names_from_expr(expr.lhs, mut names)
525 collect_call_names_from_expr(expr.expr, mut names)
526 }
527 ast.InfixExpr {
528 collect_call_names_from_expr(expr.lhs, mut names)
529 collect_call_names_from_expr(expr.rhs, mut names)
530 }
531 ast.InitExpr {
532 for field in expr.fields {
533 collect_call_names_from_expr(field.value, mut names)
534 }
535 }
536 ast.LambdaExpr {
537 collect_call_names_from_expr(expr.expr, mut names)
538 }
539 ast.LockExpr {
540 for nested in expr.stmts {
541 collect_call_names_from_stmt(nested, mut names)
542 }
543 }
544 ast.MapInitExpr {
545 for key in expr.keys {
546 collect_call_names_from_expr(key, mut names)
547 }
548 for val in expr.vals {
549 collect_call_names_from_expr(val, mut names)
550 }
551 }
552 ast.MatchExpr {
553 collect_call_names_from_expr(expr.expr, mut names)
554 for branch in expr.branches {
555 for cond in branch.cond {
556 collect_call_names_from_expr(cond, mut names)
557 }
558 for nested in branch.stmts {
559 collect_call_names_from_stmt(nested, mut names)
560 }
561 }
562 }
563 ast.ModifierExpr {
564 collect_call_names_from_expr(expr.expr, mut names)
565 }
566 ast.OrExpr {
567 collect_call_names_from_expr(expr.expr, mut names)
568 for nested in expr.stmts {
569 collect_call_names_from_stmt(nested, mut names)
570 }
571 }
572 ast.ParenExpr {
573 collect_call_names_from_expr(expr.expr, mut names)
574 }
575 ast.PostfixExpr {
576 collect_call_names_from_expr(expr.expr, mut names)
577 }
578 ast.PrefixExpr {
579 collect_call_names_from_expr(expr.expr, mut names)
580 }
581 ast.RangeExpr {
582 collect_call_names_from_expr(expr.start, mut names)
583 collect_call_names_from_expr(expr.end, mut names)
584 }
585 ast.SelectExpr {
586 collect_call_names_from_stmt(expr.stmt, mut names)
587 for nested in expr.stmts {
588 collect_call_names_from_stmt(nested, mut names)
589 }
590 collect_call_names_from_expr(expr.next, mut names)
591 }
592 ast.SelectorExpr {
593 collect_call_names_from_expr(expr.lhs, mut names)
594 }
595 ast.StringInterLiteral {
596 for inter in expr.inters {
597 collect_call_names_from_expr(inter.expr, mut names)
598 collect_call_names_from_expr(inter.format_expr, mut names)
599 }
600 }
601 ast.Tuple {
602 for item in expr.exprs {
603 collect_call_names_from_expr(item, mut names)
604 }
605 }
606 ast.UnsafeExpr {
607 for nested in expr.stmts {
608 collect_call_names_from_stmt(nested, mut names)
609 }
610 }
611 else {}
612 }
613}
614
615fn call_names_for_fn(files []ast.File, fn_name string) []string {
616 mut names := []string{}
617 for file in files {
618 for stmt in file.stmts {
619 if stmt is ast.FnDecl && stmt.name == fn_name {
620 for nested in stmt.stmts {
621 collect_call_names_from_stmt(nested, mut names)
622 }
623 }
624 }
625 }
626 return names
627}
628
629fn test_println_rune_lowers_to_rune_str_call() {
630 mut t := create_transformer_with_vars({
631 'r': types.Type(types.rune_)
632 })
633 out := t.transform_call_expr(ast.CallExpr{
634 lhs: ast.Ident{
635 name: 'println'
636 }
637 args: [ast.Expr(ast.Ident{
638 name: 'r'
639 })]
640 })
641 assert out is ast.CallExpr
642 call := out as ast.CallExpr
643 assert call.args.len == 1
644 assert call.args[0] is ast.CallExpr
645 str_call := call.args[0] as ast.CallExpr
646 assert str_call.lhs is ast.Ident
647 assert (str_call.lhs as ast.Ident).name == 'rune__str'
648 assert 'rune__str' in t.needed_str_fns
649}
650
651fn test_println_i64_call_or_cast_lowers_to_i64_str_call() {
652 mut t := create_test_transformer()
653 out := t.transform_call_expr(ast.CallExpr{
654 lhs: ast.Ident{
655 name: 'println'
656 }
657 args: [
658 ast.Expr(ast.CallOrCastExpr{
659 lhs: ast.Ident{
660 name: 'i64'
661 }
662 expr: ast.BasicLiteral{
663 kind: .number
664 value: '4294967296'
665 }
666 }),
667 ]
668 })
669 assert out is ast.CallExpr
670 call := out as ast.CallExpr
671 assert call.args.len == 1
672 assert call.args[0] is ast.CallExpr
673 str_call := call.args[0] as ast.CallExpr
674 assert str_call.lhs is ast.Ident
675 assert (str_call.lhs as ast.Ident).name == 'i64__str'
676 assert 'i64__str' in t.needed_str_fns
677}
678
679fn call_names_for_fn_suffix(files []ast.File, fn_suffix string) []string {
680 mut names := []string{}
681 for file in files {
682 for stmt in file.stmts {
683 if stmt is ast.FnDecl && stmt.name.ends_with(fn_suffix) {
684 for nested in stmt.stmts {
685 collect_call_names_from_stmt(nested, mut names)
686 }
687 }
688 }
689 }
690 return names
691}
692
693fn test_transform_generic_call_uses_sumtype_match_smartcast_for_inference() {
694 files := transform_code_for_test('
695struct Point {
696 x int
697}
698
699type Primitive = []int | []string | []Point | bool | int | string | Point
700
701fn wrap_first[T](values []T) Primitive {
702 return Primitive(values[0])
703}
704
705fn check(value Primitive) {
706 match value {
707 []int {
708 _ := wrap_first(value)
709 }
710 []string {
711 _ := wrap_first(value)
712 }
713 []Point {
714 _ := wrap_first(value)
715 }
716 else {}
717 }
718}
719')
720 names := call_names_for_fn(files, 'check')
721 assert 'wrap_first_T_int' in names
722 assert 'wrap_first_T_string' in names
723 assert 'wrap_first_T_Point' in names
724 assert 'wrap_first' !in names
725}
726
727fn test_transform_generic_receiver_methods_use_concrete_specializations() {
728 files := transform_sources_for_test([
729 TestSource{
730 rel: 'x/json2/decode.v'
731 code: '
732module json2
733
734struct ValueInfo {
735 x int
736}
737
738struct Node[T] {
739mut:
740 value T
741 next &Node[T] = unsafe { nil }
742}
743
744struct Decoder {
745mut:
746 values_info LinkedList[ValueInfo]
747}
748
749struct LinkedList[T] {
750mut:
751 head &Node[T] = unsafe { nil }
752 tail &Node[T] = unsafe { nil }
753 len int
754}
755
756fn (mut list LinkedList[T]) push(value T) {
757 _ = value
758}
759
760fn (list &LinkedList[T]) last() &T {
761 return &list.tail.value
762}
763'
764 },
765 TestSource{
766 rel: 'x/json2/check.v'
767 code: '
768module json2
769
770fn (mut checker Decoder) check() {
771 mut actual_value_info_pointer := unsafe { nil }
772 checker.values_info.push(ValueInfo{})
773 actual_value_info_pointer = checker.values_info.last()
774 _ = actual_value_info_pointer
775}
776'
777 },
778 ])
779 names := call_names_for_fn(files, 'check')
780 assert names.any(it.contains('json2__LinkedList__push_T_') && it.contains('json2_ValueInfo')), 'expected specialized push call, got ${names}'
781
782 assert names.any(it.contains('json2__LinkedList__last_T_') && it.contains('json2_ValueInfo')), 'expected specialized last call, got ${names}'
783
784 assert 'LinkedList__push' !in names
785 assert 'LinkedList__last' !in names
786 assert 'json2__LinkedList__push' !in names
787 assert 'json2__LinkedList__last' !in names
788}
789
790fn test_transform_nested_generic_receiver_method_call_in_monomorphized_clone() {
791 files := transform_sources_for_test([
792 TestSource{
793 rel: 'x/json2/encode.v'
794 code: '
795module json2
796
797pub struct EncoderOptions {}
798
799pub struct Any {}
800
801struct Encoder {}
802
803pub fn encode[T](val T, config EncoderOptions) string {
804 _ = config
805 mut encoder := Encoder{}
806 encoder.encode_value[T](val)
807 return ""
808}
809
810fn (mut encoder Encoder) encode_value[T](val T) {
811 _ = encoder
812 _ = val
813}
814'
815 },
816 TestSource{
817 rel: 'main.v'
818 code: '
819module main
820
821import json2
822
823fn use() {
824 _ = json2.encode(json2.Any{}, json2.EncoderOptions{})
825}
826'
827 },
828 ])
829 mut fn_names := []string{}
830 for file in files {
831 for stmt in file.stmts {
832 if stmt is ast.FnDecl {
833 fn_names << stmt.name
834 }
835 }
836 }
837 assert 'json2__encode_T_json2_Any' in fn_names
838 assert 'encode_value_T_json2_Any' in fn_names
839 call_names := call_names_for_fn(files, 'json2__encode_T_json2_Any')
840 assert 'json2__Encoder__encode_value_T_json2_Any' in call_names
841 assert 'encode_value_T_json2_Any' !in call_names
842}
843
844fn test_transform_eventbus_receiver_generic_method_call_is_monomorphized() {
845 files := transform_sources_for_test([
846 TestSource{
847 rel: 'eventbus/eventbus.v'
848 code: '
849module eventbus
850
851pub type EventHandlerFn = fn (receiver voidptr, args voidptr, sender voidptr)
852
853pub struct Subscriber[T] {}
854
855pub fn new_subscriber[T]() Subscriber[T] {
856 return Subscriber[T]{}
857}
858
859pub fn (mut s Subscriber[T]) subscribe_method(name T, handler EventHandlerFn, receiver voidptr) {
860 _ = s
861 _ = name
862 _ = handler
863 _ = receiver
864}
865'
866 },
867 TestSource{
868 rel: 'main.v'
869 code: "
870module main
871
872import eventbus
873
874fn handler(receiver voidptr, args voidptr, sender voidptr) {
875 _ = receiver
876 _ = args
877 _ = sender
878}
879
880fn use() {
881 mut subscriber := eventbus.new_subscriber[string]()
882 subscriber.subscribe_method('ready', handler, unsafe { nil })
883}
884"
885 },
886 ])
887 mut fn_names := []string{}
888 for file in files {
889 for stmt in file.stmts {
890 if stmt is ast.FnDecl {
891 fn_names << stmt.name
892 }
893 }
894 }
895 assert 'subscribe_method_T_string' in fn_names
896
897 call_names := call_names_for_fn(files, 'use')
898 assert 'eventbus__Subscriber__subscribe_method_T_string' in call_names
899 assert 'eventbus__Subscriber__subscribe_method' !in call_names
900}
901
902fn test_transform_embedded_method_promotion_keeps_owner_method_precedence() {
903 mut t := create_test_transformer()
904 t.collect_declared_method_fns([
905 ast.File{
906 mod: 'main'
907 stmts: [
908 ast.Stmt(ast.FnDecl{
909 name: 'redirect_to_index'
910 is_method: true
911 receiver: ast.Parameter{
912 name: 'ctx'
913 typ: ast.Expr(ast.Ident{
914 name: 'Context'
915 })
916 }
917 }),
918 ]
919 },
920 ])
921 recv_type := types.Type(types.Struct{
922 name: 'Context'
923 })
924 resolved := t.resolve_cached_method_fn_name_for_type(recv_type, 'redirect_to_index', 'Context') or {
925 panic('missing declared owner method')
926 }
927 assert resolved == 'Context__redirect_to_index'
928 assert 'Context__redirect_to_index' in t.declared_method_fns
929}
930
931fn test_generic_receiver_bindings_prefer_declared_selector_over_stale_wrapper_type() {
932 value_info_type := types.Type(types.Struct{
933 name: 'json2__ValueInfo'
934 })
935 template_type := types.Type(types.Struct{
936 name: 'json2__LinkedList'
937 generic_params: ['T']
938 fields: [
939 types.Field{
940 name: 'item'
941 typ: types.Type(types.NamedType('T'))
942 },
943 ]
944 })
945 concrete_type := types.Type(types.Struct{
946 name: 'json2__LinkedList'
947 fields: [
948 types.Field{
949 name: 'item'
950 typ: value_info_type
951 },
952 ]
953 })
954 decoder_type := types.Type(types.Struct{
955 name: 'json2__Decoder'
956 fields: [
957 types.Field{
958 name: 'values_info'
959 typ: concrete_type
960 },
961 ]
962 })
963 mut env := types.Environment.new()
964 env.set_expr_type(701, template_type)
965 mut scope := types.new_scope(unsafe { nil })
966 scope.insert('checker', value_object_from_type(decoder_type))
967 scope.insert_type('LinkedList', template_type)
968 scope.insert_type('json2__LinkedList', template_type)
969 mut t := Transformer{
970 pref: &vpref.Preferences{}
971 env: unsafe { env }
972 scope: scope
973 cur_module: 'json2'
974 cached_scopes: {
975 'json2': scope
976 }
977 local_decl_types: map[string]types.Type{}
978 }
979 receiver := ast.Expr(ast.ParenExpr{
980 expr: ast.Expr(ast.SelectorExpr{
981 lhs: ast.Ident{
982 name: 'checker'
983 }
984 rhs: ast.Ident{
985 name: 'values_info'
986 }
987 })
988 pos: token.Pos{
989 id: 701
990 }
991 })
992 decl := ast.FnDecl{
993 name: 'last'
994 is_method: true
995 receiver: ast.Parameter{
996 name: 'list'
997 typ: ast.Expr(ast.PrefixExpr{
998 op: .amp
999 expr: ast.Expr(ast.GenericArgOrIndexExpr{
1000 lhs: ast.Expr(ast.Ident{
1001 name: 'LinkedList'
1002 })
1003 expr: ast.Expr(ast.Ident{
1004 name: 'T'
1005 })
1006 })
1007 })
1008 }
1009 }
1010 bindings := t.generic_bindings_from_method_receiver(decl, receiver, 'json2__LinkedList__last') or {
1011 panic('missing receiver bindings')
1012 }
1013 binding := bindings['T'] or { panic('missing T binding') }
1014 assert binding is types.Struct
1015 assert (binding as types.Struct).name == 'json2__ValueInfo'
1016}
1017
1018fn test_decl_assign_rune_arithmetic_keeps_rune_storage_type() {
1019 mut t := create_transformer_with_vars({
1020 'unicode_point': types.builtin_type('rune') or { rune_type() }
1021 'unicode_point2': types.builtin_type('rune') or { rune_type() }
1022 })
1023 mut env := types.Environment.new()
1024 env.set_expr_type(93, types.Type(types.int_))
1025 env.set_expr_type(94, types.Type(types.int_))
1026 env.set_expr_type(95, types.Type(types.int_))
1027 t.env = unsafe { env }
1028 lhs := ast.Expr(ast.Ident{
1029 name: 'final_unicode_point'
1030 pos: token.Pos{
1031 id: 93
1032 }
1033 })
1034 cast_rhs := ast.Expr(ast.CastExpr{
1035 typ: ast.Expr(ast.Ident{
1036 name: 'rune'
1037 })
1038 expr: ast.Expr(ast.BasicLiteral{
1039 kind: .number
1040 value: '0'
1041 })
1042 })
1043 cast_typ := t.decl_assign_storage_type(lhs, cast_rhs) or { panic('missing cast type') }
1044 assert cast_typ is types.Rune
1045 rhs := ast.Expr(ast.InfixExpr{
1046 op: .plus
1047 lhs: ast.Expr(ast.InfixExpr{
1048 op: .amp
1049 lhs: ast.Expr(ast.Ident{
1050 name: 'unicode_point2'
1051 pos: token.Pos{
1052 id: 94
1053 }
1054 })
1055 rhs: ast.Expr(ast.BasicLiteral{
1056 kind: .number
1057 value: '0x3FF'
1058 })
1059 })
1060 rhs: ast.Expr(ast.InfixExpr{
1061 op: .left_shift
1062 lhs: ast.Expr(ast.InfixExpr{
1063 op: .amp
1064 lhs: ast.Expr(ast.Ident{
1065 name: 'unicode_point'
1066 pos: token.Pos{
1067 id: 95
1068 }
1069 })
1070 rhs: ast.Expr(ast.BasicLiteral{
1071 kind: .number
1072 value: '0x3FF'
1073 })
1074 })
1075 rhs: ast.Expr(ast.BasicLiteral{
1076 kind: .number
1077 value: '10'
1078 })
1079 })
1080 })
1081 typ := t.decl_assign_storage_type(lhs, rhs) or { panic('missing rune arithmetic type') }
1082 assert typ is types.Rune
1083}
1084
1085fn test_runtime_const_init_main_calls_run_main_consts_after_module_inits() {
1086 mut t := create_test_transformer()
1087 t.runtime_const_modules << 'main'
1088 t.runtime_const_init_fn_name['main'] = '__v_init_consts_main'
1089 files := [
1090 ast.File{
1091 mod: 'main'
1092 stmts: [
1093 ast.Stmt(ast.FnDecl{
1094 name: 'main'
1095 }),
1096 ]
1097 },
1098 ast.File{
1099 mod: 'rand'
1100 stmts: [
1101 ast.Stmt(ast.FnDecl{
1102 name: 'init'
1103 }),
1104 ]
1105 },
1106 ]
1107 calls := t.runtime_const_init_main_calls_parts(files)
1108 mut names := []string{}
1109 for call_stmt in calls {
1110 collect_call_names_from_stmt(call_stmt, mut names)
1111 }
1112 assert names == ['rand__init', '__v_init_consts_main']
1113}
1114
1115fn stmt_has_assign_op(stmt ast.Stmt, op token.Token) bool {
1116 match stmt {
1117 ast.AssignStmt {
1118 return stmt.op == op
1119 }
1120 ast.BlockStmt {
1121 for nested in stmt.stmts {
1122 if stmt_has_assign_op(nested, op) {
1123 return true
1124 }
1125 }
1126 }
1127 ast.ExprStmt {
1128 return expr_has_assign_op(stmt.expr, op)
1129 }
1130 ast.ForStmt {
1131 if stmt_has_assign_op(stmt.init, op) || stmt_has_assign_op(stmt.post, op) {
1132 return true
1133 }
1134 for nested in stmt.stmts {
1135 if stmt_has_assign_op(nested, op) {
1136 return true
1137 }
1138 }
1139 }
1140 ast.LabelStmt {
1141 return stmt_has_assign_op(stmt.stmt, op)
1142 }
1143 else {}
1144 }
1145
1146 return false
1147}
1148
1149fn expr_has_assign_op(expr ast.Expr, op token.Token) bool {
1150 match expr {
1151 ast.IfExpr {
1152 for stmt in expr.stmts {
1153 if stmt_has_assign_op(stmt, op) {
1154 return true
1155 }
1156 }
1157 return expr_has_assign_op(expr.else_expr, op)
1158 }
1159 ast.MatchExpr {
1160 for branch in expr.branches {
1161 for stmt in branch.stmts {
1162 if stmt_has_assign_op(stmt, op) {
1163 return true
1164 }
1165 }
1166 }
1167 }
1168 ast.UnsafeExpr {
1169 for stmt in expr.stmts {
1170 if stmt_has_assign_op(stmt, op) {
1171 return true
1172 }
1173 }
1174 }
1175 else {}
1176 }
1177
1178 return false
1179}
1180
1181fn fn_has_assign_op(files []ast.File, fn_name string, op token.Token) bool {
1182 for file in files {
1183 for stmt in file.stmts {
1184 if stmt is ast.FnDecl && stmt.name == fn_name {
1185 for nested in stmt.stmts {
1186 if stmt_has_assign_op(nested, op) {
1187 return true
1188 }
1189 }
1190 }
1191 }
1192 }
1193 return false
1194}
1195
1196fn count_label_name_in_files(files []ast.File, fn_name string, label string) int {
1197 mut count := 0
1198 for file in files {
1199 for stmt in file.stmts {
1200 if stmt is ast.FnDecl && stmt.name == fn_name {
1201 count += count_label_name_in_stmts(stmt.stmts, label)
1202 }
1203 }
1204 }
1205 return count
1206}
1207
1208fn count_label_name_in_stmts(stmts []ast.Stmt, label string) int {
1209 mut count := 0
1210 for stmt in stmts {
1211 count += count_label_name_in_stmt(stmt, label)
1212 }
1213 return count
1214}
1215
1216fn count_label_name_in_stmt(stmt ast.Stmt, label string) int {
1217 match stmt {
1218 ast.BlockStmt {
1219 return count_label_name_in_stmts(stmt.stmts, label)
1220 }
1221 ast.ComptimeStmt {
1222 return count_label_name_in_stmt(stmt.stmt, label)
1223 }
1224 ast.DeferStmt {
1225 return count_label_name_in_stmts(stmt.stmts, label)
1226 }
1227 ast.ExprStmt {
1228 return count_label_name_in_expr(stmt.expr, label)
1229 }
1230 ast.ForStmt {
1231 return count_label_name_in_stmt(stmt.init, label) +
1232 count_label_name_in_stmt(stmt.post, label) +
1233 count_label_name_in_expr(stmt.cond, label) +
1234 count_label_name_in_stmts(stmt.stmts, label)
1235 }
1236 ast.LabelStmt {
1237 mut count := if stmt.name == label { 1 } else { 0 }
1238 count += count_label_name_in_stmt(stmt.stmt, label)
1239 return count
1240 }
1241 ast.ReturnStmt {
1242 mut count := 0
1243 for expr in stmt.exprs {
1244 count += count_label_name_in_expr(expr, label)
1245 }
1246 return count
1247 }
1248 else {
1249 return 0
1250 }
1251 }
1252}
1253
1254fn count_label_name_in_expr(expr ast.Expr, label string) int {
1255 match expr {
1256 ast.IfExpr {
1257 return count_label_name_in_expr(expr.cond, label) +
1258 count_label_name_in_stmts(expr.stmts, label) +
1259 count_label_name_in_expr(expr.else_expr, label)
1260 }
1261 ast.MatchExpr {
1262 mut count := 0
1263 for branch in expr.branches {
1264 count += count_label_name_in_stmts(branch.stmts, label)
1265 }
1266 return count
1267 }
1268 ast.UnsafeExpr {
1269 return count_label_name_in_stmts(expr.stmts, label)
1270 }
1271 else {
1272 return 0
1273 }
1274 }
1275}
1276
1277fn find_call_with_lhs_suffix_in_stmts(stmts []ast.Stmt, suffix string) ?ast.CallExpr {
1278 for stmt in stmts {
1279 if call := find_call_with_lhs_suffix_in_stmt(stmt, suffix) {
1280 return call
1281 }
1282 }
1283 return none
1284}
1285
1286fn find_call_with_lhs_suffix_in_stmt(stmt ast.Stmt, suffix string) ?ast.CallExpr {
1287 match stmt {
1288 ast.AssignStmt {
1289 for expr in stmt.lhs {
1290 if call := find_call_with_lhs_suffix_in_expr(expr, suffix) {
1291 return call
1292 }
1293 }
1294 for expr in stmt.rhs {
1295 if call := find_call_with_lhs_suffix_in_expr(expr, suffix) {
1296 return call
1297 }
1298 }
1299 }
1300 ast.BlockStmt {
1301 return find_call_with_lhs_suffix_in_stmts(stmt.stmts, suffix)
1302 }
1303 ast.ExprStmt {
1304 return find_call_with_lhs_suffix_in_expr(stmt.expr, suffix)
1305 }
1306 ast.ForInStmt {
1307 if call := find_call_with_lhs_suffix_in_expr(stmt.expr, suffix) {
1308 return call
1309 }
1310 }
1311 ast.ForStmt {
1312 if call := find_call_with_lhs_suffix_in_stmt(stmt.init, suffix) {
1313 return call
1314 }
1315 if call := find_call_with_lhs_suffix_in_expr(stmt.cond, suffix) {
1316 return call
1317 }
1318 if call := find_call_with_lhs_suffix_in_stmt(stmt.post, suffix) {
1319 return call
1320 }
1321 return find_call_with_lhs_suffix_in_stmts(stmt.stmts, suffix)
1322 }
1323 ast.FnDecl {
1324 return find_call_with_lhs_suffix_in_stmts(stmt.stmts, suffix)
1325 }
1326 ast.ReturnStmt {
1327 for expr in stmt.exprs {
1328 if call := find_call_with_lhs_suffix_in_expr(expr, suffix) {
1329 return call
1330 }
1331 }
1332 }
1333 else {}
1334 }
1335
1336 return none
1337}
1338
1339fn find_call_with_lhs_suffix_in_expr(expr ast.Expr, suffix string) ?ast.CallExpr {
1340 match expr {
1341 ast.ArrayInitExpr {
1342 for item in expr.exprs {
1343 if call := find_call_with_lhs_suffix_in_expr(item, suffix) {
1344 return call
1345 }
1346 }
1347 }
1348 ast.CallExpr {
1349 if expr.lhs is ast.Ident && (expr.lhs as ast.Ident).name.ends_with(suffix) {
1350 return expr
1351 }
1352 if call := find_call_with_lhs_suffix_in_expr(expr.lhs, suffix) {
1353 return call
1354 }
1355 for arg in expr.args {
1356 if call := find_call_with_lhs_suffix_in_expr(arg, suffix) {
1357 return call
1358 }
1359 }
1360 }
1361 ast.CastExpr {
1362 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1363 }
1364 ast.FieldInit {
1365 return find_call_with_lhs_suffix_in_expr(expr.value, suffix)
1366 }
1367 ast.IfExpr {
1368 if call := find_call_with_lhs_suffix_in_expr(expr.cond, suffix) {
1369 return call
1370 }
1371 if call := find_call_with_lhs_suffix_in_stmts(expr.stmts, suffix) {
1372 return call
1373 }
1374 return find_call_with_lhs_suffix_in_expr(expr.else_expr, suffix)
1375 }
1376 ast.InfixExpr {
1377 if call := find_call_with_lhs_suffix_in_expr(expr.lhs, suffix) {
1378 return call
1379 }
1380 return find_call_with_lhs_suffix_in_expr(expr.rhs, suffix)
1381 }
1382 ast.InitExpr {
1383 for field in expr.fields {
1384 if call := find_call_with_lhs_suffix_in_expr(field.value, suffix) {
1385 return call
1386 }
1387 }
1388 }
1389 ast.MatchExpr {
1390 if call := find_call_with_lhs_suffix_in_expr(expr.expr, suffix) {
1391 return call
1392 }
1393 for branch in expr.branches {
1394 if call := find_call_with_lhs_suffix_in_stmts(branch.stmts, suffix) {
1395 return call
1396 }
1397 }
1398 }
1399 ast.ModifierExpr {
1400 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1401 }
1402 ast.ParenExpr {
1403 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1404 }
1405 ast.PostfixExpr {
1406 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1407 }
1408 ast.PrefixExpr {
1409 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1410 }
1411 ast.SelectorExpr {
1412 return find_call_with_lhs_suffix_in_expr(expr.lhs, suffix)
1413 }
1414 ast.UnsafeExpr {
1415 return find_call_with_lhs_suffix_in_stmts(expr.stmts, suffix)
1416 }
1417 else {}
1418 }
1419
1420 return none
1421}
1422
1423// string_type returns the builtin v2 string type.
1424fn string_type() types.Type {
1425 return types.string_
1426}
1427
1428fn test_transform_decl_assign_keeps_explicit_as_cast_type_with_stale_lhs_name_scope() {
1429 env := &types.Environment{}
1430 mut scope := types.new_scope(unsafe { nil })
1431 scope.insert('inner', types.Type(types.Struct{
1432 name: 'ast__Expr'
1433 }))
1434 mut t := &Transformer{
1435 pref: &vpref.Preferences{}
1436 env: unsafe { env }
1437 scope: scope
1438 needed_clone_fns: map[string]string{}
1439 needed_array_contains_fns: map[string]ArrayMethodInfo{}
1440 needed_array_index_fns: map[string]ArrayMethodInfo{}
1441 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
1442 }
1443 result := t.transform_assign_stmt(ast.AssignStmt{
1444 op: .decl_assign
1445 lhs: [
1446 ast.Expr(ast.Ident{
1447 name: 'inner'
1448 }),
1449 ]
1450 rhs: [
1451 ast.Expr(ast.AsCastExpr{
1452 expr: ast.Expr(ast.Ident{
1453 name: 'node'
1454 })
1455 typ: ast.Expr(ast.SelectorExpr{
1456 lhs: ast.Expr(ast.Ident{
1457 name: 'ast'
1458 })
1459 rhs: ast.Ident{
1460 name: 'PrefixExpr'
1461 }
1462 })
1463 }),
1464 ]
1465 })
1466 assert result.rhs[0] is ast.AsCastExpr
1467}
1468
1469fn test_transform_decl_assign_does_not_wrap_rhs_with_stale_lhs_sumtype_scope() {
1470 env := &types.Environment{}
1471 type_sum := types.Type(types.SumType{
1472 name: 'types__Type'
1473 variants: [string_type()]
1474 })
1475 mut scope := types.new_scope(unsafe { nil })
1476 scope.insert('receiver_type', type_sum)
1477 mut types_scope := types.new_scope(unsafe { nil })
1478 types_scope.insert('Type', type_sum)
1479 mut t := &Transformer{
1480 pref: &vpref.Preferences{}
1481 env: unsafe { env }
1482 scope: scope
1483 cur_module: 'transformer'
1484 cached_scopes: {
1485 'types': types_scope
1486 }
1487 needed_clone_fns: map[string]string{}
1488 needed_array_contains_fns: map[string]ArrayMethodInfo{}
1489 needed_array_index_fns: map[string]ArrayMethodInfo{}
1490 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
1491 }
1492 result := t.transform_assign_stmt(ast.AssignStmt{
1493 op: .decl_assign
1494 lhs: [
1495 ast.Expr(ast.Ident{
1496 name: 'receiver_type'
1497 }),
1498 ]
1499 rhs: [
1500 ast.Expr(ast.StringLiteral{
1501 value: 'flag'
1502 kind: .v
1503 }),
1504 ]
1505 })
1506 assert result.rhs[0] is ast.StringLiteral
1507}
1508
1509fn test_transform_decl_assign_registers_smartcast_selector_type() {
1510 env := &types.Environment{}
1511 mut ast_scope := types.new_scope(unsafe { nil })
1512 ast_scope.insert('Interface', types.Type(types.Struct{
1513 name: 'ast__Interface'
1514 }))
1515 mut fn_scope := types.new_scope(unsafe { nil })
1516 mut t := &Transformer{
1517 pref: &vpref.Preferences{}
1518 env: unsafe { env }
1519 scope: fn_scope
1520 fn_root_scope: fn_scope
1521 cur_module: 'checker'
1522 cached_scopes: {
1523 'ast': ast_scope
1524 }
1525 needed_clone_fns: map[string]string{}
1526 needed_array_contains_fns: map[string]ArrayMethodInfo{}
1527 needed_array_index_fns: map[string]ArrayMethodInfo{}
1528 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
1529 }
1530 t.push_smartcast_full('parent_sym.info', 'ast__Interface', 'ast__Interface', 'ast__TypeInfo')
1531 _ := t.transform_assign_stmt(ast.AssignStmt{
1532 op: .decl_assign
1533 lhs: [ast.Expr(ast.Ident{
1534 name: 'generic_info'
1535 })]
1536 rhs: [
1537 ast.Expr(ast.SelectorExpr{
1538 lhs: ast.Expr(ast.Ident{
1539 name: 'parent_sym'
1540 })
1541 rhs: ast.Ident{
1542 name: 'info'
1543 }
1544 }),
1545 ]
1546 })
1547 typ := fn_scope.lookup_var_type('generic_info') or {
1548 assert false, 'generic_info type was not registered'
1549 return
1550 }
1551 assert typ is types.Struct
1552 assert (typ as types.Struct).name == 'ast__Interface'
1553}
1554
1555fn test_smartcast_method_call_prefers_variant_method_over_sumtype_method() {
1556 files := transform_code_for_test('
1557struct Primitive {}
1558
1559struct Enum {
1560 name string
1561}
1562
1563type Type = Primitive | Enum
1564
1565fn (t Type) name() string {
1566 _ = t
1567 return "type"
1568}
1569
1570fn (t Primitive) name() string {
1571 _ = t
1572 return "primitive"
1573}
1574
1575fn (t Enum) name() string {
1576 return t.name
1577}
1578
1579fn type_name(t Type) string {
1580 return match t {
1581 Primitive, Enum { t.name() }
1582 }
1583}
1584')
1585 call_names := call_names_for_fn(files, 'type_name')
1586 assert 'Primitive__name' in call_names
1587 assert 'Enum__name' in call_names
1588 assert 'Type__name' !in call_names
1589}
1590
1591fn test_flag_enum_set_on_local_variable_lowers_to_assignment() {
1592 files := transform_code_for_test('
1593@[flag]
1594enum Flags {
1595 empty
1596 enabled
1597}
1598
1599fn set_flag() Flags {
1600 mut attrs := Flags.empty
1601 attrs.set(.enabled)
1602 return attrs
1603}
1604')
1605 call_names := call_names_for_fn(files, 'set_flag')
1606 assert 'Flags__set' !in call_names
1607 assert fn_has_assign_op(files, 'set_flag', .or_assign)
1608}
1609
1610fn test_flag_enum_set_inside_statement_match_lowers_to_assignment() {
1611 files := transform_code_for_test('
1612@[flag]
1613enum Flags {
1614 empty
1615 enabled
1616}
1617
1618fn set_flag(names []string) Flags {
1619 mut attrs := Flags.empty
1620 for name in names {
1621 match name {
1622 "enabled" { attrs.set(.enabled) }
1623 else {}
1624 }
1625 }
1626 return attrs
1627}
1628')
1629 call_names := call_names_for_fn(files, 'set_flag')
1630 assert 'Flags__set' !in call_names
1631 assert fn_has_assign_op(files, 'set_flag', .or_assign)
1632}
1633
1634fn test_embedded_struct_selector_uses_embedded_method_owner() {
1635 files := transform_code_for_test('
1636struct Request {}
1637struct Response {}
1638
1639struct SilentStreamingDownloader {}
1640
1641fn (mut d SilentStreamingDownloader) on_finish(request &Request, response &Response) ! {
1642 _ = request
1643 _ = response
1644}
1645
1646struct TerminalStreamingDownloader {
1647 SilentStreamingDownloader
1648}
1649
1650fn finish(mut d TerminalStreamingDownloader, request &Request, response &Response) ! {
1651 d.SilentStreamingDownloader.on_finish(request, response)!
1652}
1653')
1654 call_names := call_names_for_fn(files, 'finish')
1655 assert 'SilentStreamingDownloader__on_finish' in call_names
1656 assert 'int__on_finish' !in call_names
1657}
1658
1659fn test_promoted_embedded_generic_method_call_lowers_to_concrete_method() {
1660 files := transform_code_for_test('
1661struct Options[T] {
1662 handler fn (mut ctx T) bool
1663}
1664
1665struct Middleware[T] {}
1666
1667fn (mut m Middleware[T]) use(options Options[T]) {
1668 _ = options
1669}
1670
1671struct Context {}
1672
1673struct App {
1674 Middleware[Context]
1675}
1676
1677fn (mut app App) before_request(mut ctx Context) bool {
1678 _ = ctx
1679 return true
1680}
1681
1682fn main() {
1683 mut app := App{}
1684 app.use(handler: app.before_request)
1685}
1686')
1687 mut fn_names := []string{}
1688 for file in files {
1689 for stmt in file.stmts {
1690 if stmt is ast.FnDecl {
1691 fn_names << stmt.name
1692 }
1693 }
1694 }
1695 call_names := call_names_for_fn(files, 'main')
1696 assert 'use_T_Context' in fn_names
1697 assert 'Middleware__use_T_Context' in call_names
1698 assert 'app.use' !in call_names
1699}
1700
1701fn test_promoted_embedded_generic_method_uses_live_struct_metadata() {
1702 files := transform_code_for_test('
1703struct Result {}
1704
1705struct BaseContext {}
1706
1707fn (mut ctx BaseContext) json[T](j T) Result {
1708 _ = j
1709 return Result{}
1710}
1711
1712struct Context {
1713 BaseContext
1714}
1715
1716struct App {}
1717
1718fn (mut app App) index(mut ctx Context) Result {
1719 _ = app
1720 return ctx.json("ok")
1721}
1722
1723fn main() {
1724 mut app := App{}
1725 mut ctx := Context{}
1726 _ = app.index(mut ctx)
1727}
1728')
1729 call_names := call_names_for_fn(files, 'index')
1730 assert 'BaseContext__json_T_string' in call_names
1731 assert 'ctx.json' !in call_names
1732}
1733
1734fn test_alias_receiver_method_is_resolved_before_base_container_method() {
1735 files := transform_code_for_test('
1736type Builder = []u8
1737
1738fn (mut b Builder) str() string {
1739 _ = b
1740 return ""
1741}
1742
1743fn builder_str() string {
1744 mut b := Builder([]u8{})
1745 return b.str()
1746}
1747')
1748 call_names := call_names_for_fn(files, 'builder_str')
1749 assert 'Builder__str' in call_names
1750 assert 'array__str' !in call_names
1751}
1752
1753fn test_string_interpolation_does_not_generate_default_over_explicit_str_method() {
1754 files := transform_code_for_test('
1755module globset
1756
1757pub struct ErrorKind {}
1758
1759pub fn (kind ErrorKind) str() string {
1760 _ = kind
1761 return "custom"
1762}
1763
1764pub fn message(kind ErrorKind) string {
1765 return "\${kind}"
1766}
1767')
1768 mut explicit_methods := 0
1769 mut generated_defaults := 0
1770 for file in files {
1771 for stmt in file.stmts {
1772 if stmt is ast.FnDecl {
1773 if stmt.is_method && stmt.name == 'str' {
1774 explicit_methods++
1775 }
1776 if !stmt.is_method && stmt.name == 'globset__ErrorKind__str' {
1777 generated_defaults++
1778 }
1779 }
1780 }
1781 }
1782 call_names := call_names_for_fn(files, 'message')
1783 assert explicit_methods == 1
1784 assert generated_defaults == 0
1785 assert 'globset__ErrorKind__str' in call_names
1786}
1787
1788fn test_array_str_call_uses_element_specific_helper() {
1789 files := transform_code_for_test('
1790module globset
1791
1792pub struct Token {}
1793
1794pub fn (tok Token) str() string {
1795 _ = tok
1796 return "Token"
1797}
1798
1799pub fn show(tokens []Token) string {
1800 return tokens.str()
1801}
1802')
1803 call_names := call_names_for_fn(files, 'show')
1804 assert 'Array_globset__Token_str' in call_names
1805 assert 'array__str' !in call_names
1806}
1807
1808fn test_string_array_str_call_keeps_builtin_method() {
1809 files := transform_code_for_test('
1810module builtin
1811
1812fn (a []string) str() string {
1813 _ = a
1814 return ""
1815}
1816
1817fn show(values []string) string {
1818 return values.str()
1819}
1820')
1821 call_names := call_names_for_fn(files, 'show')
1822 assert 'Array_string__str' in call_names
1823 assert 'Array_string_str' !in call_names
1824}
1825
1826fn test_qualified_alias_receiver_uses_concrete_base_method_with_same_short_name() {
1827 files := transform_sources_for_test([
1828 TestSource{
1829 rel: 'base/base.v'
1830 code: 'module base
1831
1832pub struct SSLConn {}
1833
1834pub fn (mut s SSLConn) connect() {
1835 _ = s
1836}
1837'
1838 },
1839 TestSource{
1840 rel: 'ssl/ssl.v'
1841 code: 'module ssl
1842
1843import base
1844
1845pub type SSLConn = base.SSLConn
1846'
1847 },
1848 TestSource{
1849 rel: 'main.v'
1850 code: 'module main
1851
1852import ssl
1853
1854fn call(mut s ssl.SSLConn) {
1855 s.connect()
1856}
1857'
1858 },
1859 ])
1860 call_names := call_names_for_fn(files, 'call')
1861 assert 'base__SSLConn__connect' in call_names, 'expected base method owner, got ${call_names}'
1862 assert 'ssl__SSLConn__connect' !in call_names
1863}
1864
1865fn test_imported_global_receiver_method_uses_global_type() {
1866 files := transform_sources_for_test([
1867 TestSource{
1868 rel: 'ast/ast.v'
1869 code: 'module ast
1870
1871pub type Type = u32
1872
1873pub struct Table {}
1874
1875pub __global global_table = &Table(unsafe { nil })
1876
1877pub fn (t &Table) type_to_str(typ Type) string {
1878 _ = t
1879 _ = typ
1880 return ""
1881}
1882'
1883 },
1884 TestSource{
1885 rel: 'main.v'
1886 code: 'module transformer
1887
1888import ast
1889
1890struct Param {
1891 typ ast.Type
1892}
1893
1894struct FnDecl {
1895 receiver Param
1896}
1897
1898fn call(node &FnDecl) string {
1899 return ast.global_table.type_to_str(node.receiver.typ)
1900}
1901'
1902 },
1903 ])
1904 call_names := call_names_for_fn(files, 'call')
1905 assert 'ast__Table__type_to_str' in call_names, 'expected ast.Table method owner, got ${call_names}'
1906 assert 'int__type_to_str' !in call_names
1907}
1908
1909fn test_sumtype_type_name_on_imported_struct_field_is_lowered() {
1910 files := transform_sources_for_test([
1911 TestSource{
1912 rel: 'astx/astx.v'
1913 code: 'module astx
1914
1915pub type Expr = Ident | Number
1916
1917pub struct Ident {}
1918
1919pub struct Number {}
1920
1921pub struct CallExpr {
1922pub:
1923 lhs Expr
1924}
1925'
1926 },
1927 TestSource{
1928 rel: 'main.v'
1929 code: 'module main
1930
1931import astx
1932
1933fn call_target_kind(expr astx.CallExpr) string {
1934 return expr.lhs.type_name()
1935}
1936'
1937 },
1938 ])
1939 call_names := call_names_for_fn(files, 'call_target_kind')
1940 assert 'astx__Expr__type_name' !in call_names
1941 assert 'Expr__type_name' !in call_names
1942}
1943
1944fn test_map_alias_field_clone_lowers_to_builtin_map_clone() {
1945 files := transform_code_for_test('
1946type TypeID = int
1947
1948struct TypeStore {
1949 cache map[string]TypeID
1950}
1951
1952struct Module {
1953 type_store TypeStore
1954}
1955
1956fn clone_cache(m Module) map[string]TypeID {
1957 return m.type_store.cache.clone()
1958}
1959')
1960 call_names := call_names_for_fn(files, 'clone_cache')
1961 assert 'map__clone' in call_names
1962 assert 'Map_string_TypeID__clone' !in call_names
1963 assert 'Map_string_main__TypeID__clone' !in call_names
1964}
1965
1966fn test_string_index_byte_methods_on_nested_receiver_fields_use_u8_receiver() {
1967 files := transform_code_for_test('
1968struct Scanner {
1969 src string
1970 offset int
1971}
1972
1973struct Parser {
1974 scanner &Scanner
1975}
1976
1977fn (c u8) is_space() bool {
1978 return true
1979}
1980
1981fn (c u8) is_letter() bool {
1982 return true
1983}
1984
1985fn (p &Parser) peek_dollar_keyword() string {
1986 if p.scanner.offset >= p.scanner.src.len {
1987 return ""
1988 }
1989 mut idx := p.scanner.offset
1990 for idx < p.scanner.src.len && p.scanner.src[idx].is_space() {
1991 idx++
1992 }
1993 start := idx
1994 for idx < p.scanner.src.len && p.scanner.src[idx].is_letter() {
1995 idx++
1996 }
1997 return p.scanner.src[start..idx]
1998}
1999')
2000 call_names := call_names_for_fn(files, 'peek_dollar_keyword')
2001 assert 'u8__is_space' in call_names
2002 assert 'u8__is_letter' in call_names
2003 assert 'int__is_space' !in call_names
2004 assert 'int__is_letter' !in call_names
2005}
2006
2007fn test_smartcast_call_arg_keeps_original_sumtype_when_param_expects_sumtype() {
2008 files := transform_code_for_test('
2009struct Table {}
2010
2011struct Checker {
2012 table Table
2013}
2014
2015struct GlobalField {}
2016struct Var {}
2017
2018type ScopeObject = GlobalField | Var
2019
2020fn (t Table) is_interface_var(obj ScopeObject) bool {
2021 _ = t
2022 _ = obj
2023 return true
2024}
2025
2026fn uses_scope_object(mut c Checker, mut obj ScopeObject) bool {
2027 match mut obj {
2028 Var {
2029 return c.table.is_interface_var(obj)
2030 }
2031 else {}
2032 }
2033 return false
2034}
2035')
2036 for file in files {
2037 for stmt in file.stmts {
2038 if stmt is ast.FnDecl && stmt.name == 'uses_scope_object' {
2039 call := find_call_with_lhs_suffix_in_stmts(stmt.stmts, '__is_interface_var') or {
2040 assert false, 'expected transformed is_interface_var call'
2041 return
2042 }
2043 assert call.args.len > 0
2044 arg := call.args[call.args.len - 1]
2045 assert arg is ast.Ident, 'sumtype call arg was wrapped as ${arg.type_name()}'
2046 assert (arg as ast.Ident).name == 'obj'
2047 return
2048 }
2049 }
2050 }
2051 assert false, 'uses_scope_object was not found'
2052}
2053
2054fn test_smartcast_method_call_arg_keeps_original_sumtype_when_param_expects_sumtype() {
2055 files := transform_code_for_test('
2056struct Holder {}
2057struct Gen {}
2058
2059type Type = Holder | int
2060
2061fn (mut g Gen) use_type(value Type, holder Holder) {
2062 _ = g
2063 _ = value
2064 _ = holder
2065}
2066
2067fn use_smartcasted_type(mut g Gen, concrete Type) {
2068 if concrete is Holder {
2069 g.use_type(concrete, concrete as Holder)
2070 }
2071}
2072')
2073 for file in files {
2074 for stmt in file.stmts {
2075 if stmt is ast.FnDecl && stmt.name == 'use_smartcasted_type' {
2076 call := find_call_with_lhs_suffix_in_stmts(stmt.stmts, 'Gen__use_type') or {
2077 assert false, 'expected transformed Gen__use_type call'
2078 return
2079 }
2080 assert call.args.len == 3
2081 arg := call.args[1]
2082 assert arg is ast.Ident, 'sumtype method call arg was wrapped as ${arg.type_name()}'
2083 assert (arg as ast.Ident).name == 'concrete'
2084 return
2085 }
2086 }
2087 }
2088 assert false, 'use_smartcasted_type was not found'
2089}
2090
2091fn test_map_index_or_decl_keeps_declared_sumtype_for_later_smartcast_call_arg() {
2092 files := transform_code_for_test('
2093struct Holder {}
2094struct Gen {}
2095
2096type Type = Holder | int
2097
2098fn (mut g Gen) use_type(value Type, holder Holder) {
2099 _ = g
2100 _ = value
2101 _ = holder
2102}
2103
2104fn use_map_or_smartcasted_type(mut g Gen, active map[string]Type, name string) {
2105 concrete := active[name] or { return }
2106 if concrete !is Holder {
2107 return
2108 }
2109 g.use_type(concrete, concrete as Holder)
2110}
2111')
2112 for file in files {
2113 for stmt in file.stmts {
2114 if stmt is ast.FnDecl && stmt.name == 'use_map_or_smartcasted_type' {
2115 call := find_call_with_lhs_suffix_in_stmts(stmt.stmts, 'Gen__use_type') or {
2116 assert false, 'expected transformed Gen__use_type call'
2117 return
2118 }
2119 assert call.args.len == 3
2120 arg := call.args[1]
2121 assert arg is ast.Ident, 'sumtype method call arg was wrapped as ${arg.type_name()}'
2122 assert (arg as ast.Ident).name == 'concrete'
2123 return
2124 }
2125 }
2126 }
2127 assert false, 'use_map_or_smartcasted_type was not found'
2128}
2129
2130fn test_sumtype_call_arg_uses_declared_local_type_when_current_type_is_narrowed() {
2131 global_field_type := types.Type(types.Struct{
2132 name: 'GlobalField'
2133 })
2134 var_type := types.Type(types.Struct{
2135 name: 'Var'
2136 })
2137 scope_object_type := types.Type(types.SumType{
2138 name: 'ScopeObject'
2139 variants: [global_field_type, var_type]
2140 })
2141 mut scope := types.new_scope(unsafe { nil })
2142 scope.insert('GlobalField', global_field_type)
2143 scope.insert('Var', var_type)
2144 scope.insert('ScopeObject', scope_object_type)
2145 scope.insert_or_update('obj', var_type)
2146 mut env := types.Environment.new()
2147 obj_pos := token.Pos{
2148 id: 8901
2149 }
2150 env.set_expr_type(obj_pos.id, var_type)
2151 mut t := &Transformer{
2152 pref: &vpref.Preferences{}
2153 env: unsafe { env }
2154 scope: scope
2155 fn_root_scope: scope
2156 cached_scopes: {
2157 'main': scope
2158 }
2159 local_decl_types: {
2160 'obj': scope_object_type
2161 }
2162 needed_clone_fns: map[string]string{}
2163 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2164 needed_array_index_fns: map[string]ArrayMethodInfo{}
2165 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2166 smartcast_expr_counts: map[string]int{}
2167 }
2168 arg := ast.Expr(ast.Ident{
2169 name: 'obj'
2170 pos: obj_pos
2171 })
2172 out := t.transform_call_arg_with_sumtype_check(arg, CallFnInfo{
2173 param_types: [scope_object_type]
2174 }, 0)
2175 assert out is ast.Ident, 'declared sumtype local was wrapped as ${out.type_name()}'
2176 assert (out as ast.Ident).name == 'obj'
2177}
2178
2179fn test_is_pointer_type_handles_unresolved_alias_base_type() {
2180 t := create_test_transformer()
2181 assert !t.is_pointer_type(types.Type(types.Alias{
2182 name: 'UnresolvedAlias'
2183 }))
2184}
2185
2186fn test_expr_to_string_handles_no_arg_selector_call_for_smartcasts() {
2187 t := create_test_transformer()
2188 call := ast.Expr(ast.CallExpr{
2189 lhs: ast.Expr(ast.SelectorExpr{
2190 lhs: ast.Expr(ast.SelectorExpr{
2191 lhs: ast.Expr(ast.Ident{
2192 name: 'branch'
2193 })
2194 rhs: ast.Ident{
2195 name: 'stmts'
2196 }
2197 })
2198 rhs: ast.Ident{
2199 name: 'last'
2200 }
2201 })
2202 })
2203 assert t.expr_to_string(call) == 'branch.stmts.last()'
2204}
2205
2206fn test_expr_to_string_handles_indexed_selector_for_smartcasts() {
2207 t := create_test_transformer()
2208 expr := ast.Expr(ast.SelectorExpr{
2209 lhs: ast.Expr(ast.IndexExpr{
2210 lhs: ast.Expr(ast.SelectorExpr{
2211 lhs: ast.Expr(ast.Ident{
2212 name: 'node'
2213 })
2214 rhs: ast.Ident{
2215 name: 'branches'
2216 }
2217 })
2218 expr: ast.Expr(ast.BasicLiteral{
2219 kind: .number
2220 value: '0'
2221 })
2222 })
2223 rhs: ast.Ident{
2224 name: 'cond'
2225 }
2226 })
2227 assert t.expr_to_string(expr) == 'node.branches[0].cond'
2228}
2229
2230fn test_smartcast_context_from_lowered_tag_check_uses_sumtype_metadata() {
2231 variants := [
2232 types.Type(types.Struct{
2233 name: 'ast__Ident'
2234 }),
2235 types.Type(types.Struct{
2236 name: 'ast__BasicLiteral'
2237 }),
2238 ]
2239 sum_type := types.Type(types.SumType{
2240 name: 'ast__Expr'
2241 variants: variants
2242 })
2243 mut ast_scope := types.new_scope(unsafe { nil })
2244 ast_scope.insert('Expr', sum_type)
2245 ast_scope.insert('Ident', variants[0])
2246 mut fn_scope := types.new_scope(unsafe { nil })
2247 fn_scope.insert('node', sum_type)
2248 env := &types.Environment{}
2249 t := &Transformer{
2250 pref: &vpref.Preferences{}
2251 env: unsafe { env }
2252 scope: fn_scope
2253 cur_module: 'checker'
2254 cached_scopes: {
2255 'ast': ast_scope
2256 }
2257 needed_clone_fns: map[string]string{}
2258 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2259 needed_array_index_fns: map[string]ArrayMethodInfo{}
2260 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2261 }
2262 ctx := t.smartcast_context_from_condition_term(ast.InfixExpr{
2263 op: .eq
2264 lhs: ast.Expr(ast.SelectorExpr{
2265 lhs: ast.Expr(ast.Ident{
2266 name: 'node'
2267 })
2268 rhs: ast.Ident{
2269 name: '_tag'
2270 }
2271 })
2272 rhs: ast.Expr(ast.BasicLiteral{
2273 kind: .number
2274 value: '0'
2275 })
2276 }) or {
2277 assert false, 'lowered tag check did not recover smartcast context'
2278 return
2279 }
2280 assert ctx.expr == 'node'
2281 assert ctx.variant == 'ast__Ident'
2282 assert ctx.variant_full == 'ast__Ident'
2283 assert ctx.sumtype == 'ast__Expr'
2284}
2285
2286fn test_if_mut_selector_smartcast_rewrites_body_selector() {
2287 expr_sum_type := types.Type(types.SumType{
2288 name: 'ast__Expr'
2289 variants: [
2290 types.Type(types.Struct{
2291 name: 'ast__Ident'
2292 fields: [
2293 types.Field{
2294 name: 'language'
2295 typ: types.Type(types.Enum{
2296 name: 'ast__Language'
2297 })
2298 },
2299 ]
2300 }),
2301 types.Type(types.Struct{
2302 name: 'ast__BasicLiteral'
2303 }),
2304 ]
2305 })
2306 mut ast_scope := types.new_scope(unsafe { nil })
2307 ast_scope.insert('Expr', expr_sum_type)
2308 ast_scope.insert('Ident', types.Type(types.Struct{
2309 name: 'ast__Ident'
2310 fields: [
2311 types.Field{
2312 name: 'language'
2313 typ: types.Type(types.Enum{
2314 name: 'ast__Language'
2315 })
2316 },
2317 ]
2318 }))
2319 ast_scope.insert('EnumField', types.Type(types.Struct{
2320 name: 'ast__EnumField'
2321 fields: [
2322 types.Field{
2323 name: 'expr'
2324 typ: expr_sum_type
2325 },
2326 ]
2327 }))
2328 mut fn_scope := types.new_scope(unsafe { nil })
2329 fn_scope.insert('field', types.Type(types.Pointer{
2330 base_type: types.Type(types.Struct{
2331 name: 'ast__EnumField'
2332 })
2333 }))
2334 env := &types.Environment{}
2335 mut t := &Transformer{
2336 pref: &vpref.Preferences{}
2337 env: unsafe { env }
2338 scope: fn_scope
2339 cur_module: 'checker'
2340 cached_scopes: {
2341 'ast': ast_scope
2342 }
2343 needed_clone_fns: map[string]string{}
2344 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2345 needed_array_index_fns: map[string]ArrayMethodInfo{}
2346 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2347 synth_types: map[int]types.Type{}
2348 smartcast_expr_counts: map[string]int{}
2349 }
2350 field_expr := ast.Expr(ast.SelectorExpr{
2351 lhs: ast.Expr(ast.Ident{
2352 name: 'field'
2353 })
2354 rhs: ast.Ident{
2355 name: 'expr'
2356 }
2357 })
2358 result := t.transform_expr(ast.Expr(ast.IfExpr{
2359 cond: ast.Expr(ast.InfixExpr{
2360 op: .key_is
2361 lhs: ast.Expr(ast.ModifierExpr{
2362 kind: .key_mut
2363 expr: field_expr
2364 })
2365 rhs: ast.Expr(ast.SelectorExpr{
2366 lhs: ast.Expr(ast.Ident{
2367 name: 'ast'
2368 })
2369 rhs: ast.Ident{
2370 name: 'Ident'
2371 }
2372 })
2373 })
2374 stmts: [
2375 ast.Stmt(ast.ExprStmt{
2376 expr: ast.Expr(ast.SelectorExpr{
2377 lhs: field_expr
2378 rhs: ast.Ident{
2379 name: 'language'
2380 }
2381 })
2382 }),
2383 ]
2384 }))
2385 assert result is ast.IfExpr
2386 if_expr := result as ast.IfExpr
2387 assert if_expr.stmts.len == 1
2388 assert if_expr.stmts[0] is ast.ExprStmt
2389 stmt := if_expr.stmts[0] as ast.ExprStmt
2390 assert stmt.expr is ast.SelectorExpr
2391 selector := stmt.expr as ast.SelectorExpr
2392 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2393 cast := selector.lhs as ast.CastExpr
2394 assert cast.typ.name() == 'ast__Ident*'
2395}
2396
2397fn test_for_mut_selector_smartcast_rewrites_assignment_rhs_selector() {
2398 type_info_type := types.Type(types.SumType{
2399 name: 'ast__TypeInfo'
2400 variants: [
2401 types.Type(types.Struct{
2402 name: 'ast__Array'
2403 fields: [
2404 types.Field{
2405 name: 'elem_type'
2406 typ: types.Type(types.Struct{
2407 name: 'ast__Type'
2408 })
2409 },
2410 ]
2411 }),
2412 types.Type(types.Struct{
2413 name: 'ast__Struct'
2414 }),
2415 ]
2416 })
2417 mut ast_scope := types.new_scope(unsafe { nil })
2418 ast_scope.insert('TypeInfo', type_info_type)
2419 ast_scope.insert('Array', types.Type(types.Struct{
2420 name: 'ast__Array'
2421 fields: [
2422 types.Field{
2423 name: 'elem_type'
2424 typ: types.Type(types.Struct{
2425 name: 'ast__Type'
2426 })
2427 },
2428 ]
2429 }))
2430 ast_scope.insert('TypeSymbol', types.Type(types.Struct{
2431 name: 'ast__TypeSymbol'
2432 fields: [
2433 types.Field{
2434 name: 'info'
2435 typ: type_info_type
2436 },
2437 ]
2438 }))
2439 type_type := types.Type(types.Struct{
2440 name: 'ast__Type'
2441 })
2442 mut fn_scope := types.new_scope(unsafe { nil })
2443 fn_scope.insert('elem_sym', types.Type(types.Pointer{
2444 base_type: types.Type(types.Struct{
2445 name: 'ast__TypeSymbol'
2446 })
2447 }))
2448 fn_scope.insert('elem_type', type_type)
2449 env := &types.Environment{}
2450 mut t := &Transformer{
2451 pref: &vpref.Preferences{}
2452 env: unsafe { env }
2453 scope: fn_scope
2454 cur_module: 'ast'
2455 cached_scopes: {
2456 'ast': ast_scope
2457 }
2458 needed_clone_fns: map[string]string{}
2459 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2460 needed_array_index_fns: map[string]ArrayMethodInfo{}
2461 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2462 synth_types: map[int]types.Type{}
2463 smartcast_expr_counts: map[string]int{}
2464 }
2465 info_expr := ast.Expr(ast.SelectorExpr{
2466 lhs: ast.Expr(ast.Ident{
2467 name: 'elem_sym'
2468 })
2469 rhs: ast.Ident{
2470 name: 'info'
2471 }
2472 })
2473 result := t.transform_stmt(ast.Stmt(ast.ForStmt{
2474 cond: ast.Expr(ast.InfixExpr{
2475 op: .key_is
2476 lhs: ast.Expr(ast.ModifierExpr{
2477 kind: .key_mut
2478 expr: info_expr
2479 })
2480 rhs: ast.Expr(ast.Ident{
2481 name: 'Array'
2482 })
2483 })
2484 stmts: [
2485 ast.Stmt(ast.AssignStmt{
2486 op: .assign
2487 lhs: [
2488 ast.Expr(ast.Ident{
2489 name: 'elem_type'
2490 }),
2491 ]
2492 rhs: [
2493 ast.Expr(ast.SelectorExpr{
2494 lhs: info_expr
2495 rhs: ast.Ident{
2496 name: 'elem_type'
2497 }
2498 }),
2499 ]
2500 }),
2501 ]
2502 }))
2503 assert result is ast.ForStmt
2504 for_stmt := result as ast.ForStmt
2505 assert for_stmt.stmts.len == 1
2506 assert for_stmt.stmts[0] is ast.AssignStmt
2507 assign_stmt := for_stmt.stmts[0] as ast.AssignStmt
2508 assert assign_stmt.rhs[0] is ast.SelectorExpr
2509 selector := assign_stmt.rhs[0] as ast.SelectorExpr
2510 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2511 cast := selector.lhs as ast.CastExpr
2512 assert cast.typ.name() == 'ast__Array*'
2513}
2514
2515fn test_checked_for_mut_selector_smartcast_rewrites_assignment_rhs_selector() {
2516 files := transform_code_for_test('
2517module main
2518
2519struct Type {}
2520
2521struct Array {
2522 elem_type Type
2523}
2524
2525struct Struct {}
2526
2527type TypeInfo = Array | Struct
2528
2529struct TypeSymbol {
2530mut:
2531 info TypeInfo
2532}
2533
2534fn f(mut elem_sym TypeSymbol) {
2535 mut elem_type := Type{}
2536 for mut elem_sym.info is Array {
2537 elem_type = elem_sym.info.elem_type
2538 }
2539}
2540')
2541 for file in files {
2542 for stmt in file.stmts {
2543 if stmt is ast.FnDecl && stmt.name == 'f' {
2544 for nested in stmt.stmts {
2545 if nested is ast.ForStmt {
2546 assert nested.stmts.len == 1
2547 assert nested.stmts[0] is ast.AssignStmt
2548 assign_stmt := nested.stmts[0] as ast.AssignStmt
2549 assert assign_stmt.rhs[0] is ast.SelectorExpr
2550 selector := assign_stmt.rhs[0] as ast.SelectorExpr
2551 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2552 cast := selector.lhs as ast.CastExpr
2553 assert cast.typ.name() == 'Array*'
2554 return
2555 }
2556 }
2557 }
2558 }
2559 }
2560 assert false, 'function f loop was not found'
2561}
2562
2563fn test_label_detection_ignores_empty_ast_slots() {
2564 assert !transformer_expr_contains_label_stmt(ast.empty_expr)
2565 assert !transformer_stmt_contains_label_stmt(ast.empty_stmt)
2566 assert !transformer_expr_contains_label_stmt(ast.IfExpr{
2567 cond: ast.BasicLiteral{
2568 kind: .key_true
2569 value: 'true'
2570 }
2571 stmts: []
2572 })
2573}
2574
2575fn test_lowered_tag_check_selector_smartcast_rewrites_body_selector() {
2576 expr_sum_type := types.Type(types.SumType{
2577 name: 'ast__Expr'
2578 variants: [
2579 types.Type(types.Struct{
2580 name: 'ast__Ident'
2581 fields: [
2582 types.Field{
2583 name: 'language'
2584 typ: types.Type(types.Enum{
2585 name: 'ast__Language'
2586 })
2587 },
2588 ]
2589 }),
2590 types.Type(types.Struct{
2591 name: 'ast__BasicLiteral'
2592 }),
2593 ]
2594 })
2595 mut ast_scope := types.new_scope(unsafe { nil })
2596 ast_scope.insert('Expr', expr_sum_type)
2597 ast_scope.insert('Ident', types.Type(types.Struct{
2598 name: 'ast__Ident'
2599 fields: [
2600 types.Field{
2601 name: 'language'
2602 typ: types.Type(types.Enum{
2603 name: 'ast__Language'
2604 })
2605 },
2606 ]
2607 }))
2608 ast_scope.insert('EnumField', types.Type(types.Struct{
2609 name: 'ast__EnumField'
2610 fields: [
2611 types.Field{
2612 name: 'expr'
2613 typ: expr_sum_type
2614 },
2615 ]
2616 }))
2617 mut fn_scope := types.new_scope(unsafe { nil })
2618 fn_scope.insert('field', types.Type(types.Pointer{
2619 base_type: types.Type(types.Struct{
2620 name: 'ast__EnumField'
2621 })
2622 }))
2623 env := &types.Environment{}
2624 mut t := &Transformer{
2625 pref: &vpref.Preferences{}
2626 env: unsafe { env }
2627 scope: fn_scope
2628 cur_module: 'checker'
2629 cached_scopes: {
2630 'ast': ast_scope
2631 }
2632 needed_clone_fns: map[string]string{}
2633 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2634 needed_array_index_fns: map[string]ArrayMethodInfo{}
2635 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2636 synth_types: map[int]types.Type{}
2637 smartcast_expr_counts: map[string]int{}
2638 }
2639 field_expr := ast.Expr(ast.SelectorExpr{
2640 lhs: ast.Expr(ast.Ident{
2641 name: 'field'
2642 })
2643 rhs: ast.Ident{
2644 name: 'expr'
2645 }
2646 })
2647 result := t.transform_expr(ast.Expr(ast.IfExpr{
2648 cond: ast.Expr(ast.InfixExpr{
2649 op: .eq
2650 lhs: ast.Expr(ast.SelectorExpr{
2651 lhs: field_expr
2652 rhs: ast.Ident{
2653 name: '_tag'
2654 }
2655 })
2656 rhs: ast.Expr(ast.BasicLiteral{
2657 kind: .number
2658 value: '0'
2659 })
2660 })
2661 stmts: [
2662 ast.Stmt(ast.ExprStmt{
2663 expr: ast.Expr(ast.SelectorExpr{
2664 lhs: field_expr
2665 rhs: ast.Ident{
2666 name: 'language'
2667 }
2668 })
2669 }),
2670 ]
2671 }))
2672 assert result is ast.IfExpr
2673 if_expr := result as ast.IfExpr
2674 assert if_expr.stmts.len == 1
2675 assert if_expr.stmts[0] is ast.ExprStmt
2676 stmt := if_expr.stmts[0] as ast.ExprStmt
2677 assert stmt.expr is ast.SelectorExpr
2678 selector := stmt.expr as ast.SelectorExpr
2679 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2680 cast := selector.lhs as ast.CastExpr
2681 assert cast.typ.name() == 'ast__Ident*'
2682}
2683
2684fn test_assignment_rhs_call_or_cast_lhs_preserves_nested_smartcast() {
2685 stmt_sum_type := types.Type(types.SumType{
2686 name: 'ast__Stmt'
2687 variants: [
2688 types.Type(types.Struct{
2689 name: 'ast__ExprStmt'
2690 }),
2691 ]
2692 })
2693 expr_sum_type := types.Type(types.SumType{
2694 name: 'ast__Expr'
2695 variants: [
2696 types.Type(types.Struct{
2697 name: 'ast__CallExpr'
2698 }),
2699 types.Type(types.Struct{
2700 name: 'ast__InfixExpr'
2701 }),
2702 ]
2703 })
2704 or_expr_type := types.Type(types.Struct{
2705 name: 'ast__OrExpr'
2706 fields: [
2707 types.Field{
2708 name: 'scope'
2709 typ: types.Type(types.Struct{
2710 name: 'ast__Scope'
2711 })
2712 },
2713 types.Field{
2714 name: 'err_used'
2715 typ: types.Type(types.bool_)
2716 },
2717 ]
2718 })
2719 mut ast_scope := types.new_scope(unsafe { nil })
2720 ast_scope.insert('Stmt', stmt_sum_type)
2721 ast_scope.insert('Expr', expr_sum_type)
2722 ast_scope.insert('ExprStmt', types.Type(types.Struct{
2723 name: 'ast__ExprStmt'
2724 fields: [
2725 types.Field{
2726 name: 'expr'
2727 typ: expr_sum_type
2728 },
2729 ]
2730 }))
2731 ast_scope.insert('InfixExpr', types.Type(types.Struct{
2732 name: 'ast__InfixExpr'
2733 fields: [
2734 types.Field{
2735 name: 'or_block'
2736 typ: or_expr_type
2737 },
2738 types.Field{
2739 name: 'right'
2740 typ: expr_sum_type
2741 },
2742 ]
2743 }))
2744 ast_scope.insert('CallExpr', types.Type(types.Struct{
2745 name: 'ast__CallExpr'
2746 }))
2747 mut fn_scope := types.new_scope(unsafe { nil })
2748 fn_scope.insert('node', stmt_sum_type)
2749 env := &types.Environment{}
2750 mut t := &Transformer{
2751 pref: &vpref.Preferences{}
2752 env: unsafe { env }
2753 scope: fn_scope
2754 cur_module: 'checker'
2755 cached_scopes: {
2756 'ast': ast_scope
2757 }
2758 needed_clone_fns: map[string]string{}
2759 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2760 needed_array_index_fns: map[string]ArrayMethodInfo{}
2761 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2762 synth_types: map[int]types.Type{}
2763 smartcast_expr_counts: map[string]int{}
2764 }
2765 t.push_smartcast_full('node', 'ast__ExprStmt', 'ast__ExprStmt', 'ast__Stmt')
2766 t.push_smartcast_full('node.expr', 'ast__InfixExpr', 'ast__InfixExpr', 'ast__Expr')
2767 t.push_smartcast_full('node.expr.right', 'ast__CallExpr', 'ast__CallExpr', 'ast__Expr')
2768 node_ident := ast.Expr(ast.Ident{
2769 name: 'node'
2770 })
2771 node_expr := ast.Expr(ast.SelectorExpr{
2772 lhs: node_ident
2773 rhs: ast.Ident{
2774 name: 'expr'
2775 }
2776 })
2777 node_or_block := ast.Expr(ast.SelectorExpr{
2778 lhs: node_expr
2779 rhs: ast.Ident{
2780 name: 'or_block'
2781 }
2782 })
2783 node_scope := ast.Expr(ast.SelectorExpr{
2784 lhs: node_or_block
2785 rhs: ast.Ident{
2786 name: 'scope'
2787 }
2788 })
2789 transformed := t.transform_assign_stmt(ast.AssignStmt{
2790 op: .assign
2791 lhs: [
2792 ast.Expr(ast.SelectorExpr{
2793 lhs: node_or_block
2794 rhs: ast.Ident{
2795 name: 'err_used'
2796 }
2797 }),
2798 ]
2799 rhs: [
2800 ast.Expr(ast.CallOrCastExpr{
2801 lhs: ast.Expr(ast.SelectorExpr{
2802 lhs: node_scope
2803 rhs: ast.Ident{
2804 name: 'known_var'
2805 }
2806 })
2807 expr: ast.Expr(ast.StringLiteral{
2808 kind: .v
2809 value: "'err'"
2810 })
2811 }),
2812 ]
2813 })
2814 assert transformed.rhs[0] is ast.CallExpr
2815 call := transformed.rhs[0] as ast.CallExpr
2816 assert call.lhs is ast.SelectorExpr
2817 call_lhs := call.lhs as ast.SelectorExpr
2818 assert call_lhs.lhs is ast.SelectorExpr
2819 scope_sel := call_lhs.lhs as ast.SelectorExpr
2820 assert scope_sel.lhs is ast.SelectorExpr
2821 or_block_sel := scope_sel.lhs as ast.SelectorExpr
2822 assert or_block_sel.lhs is ast.CastExpr, 'rhs call lhs lost node.expr smartcast: ${or_block_sel.lhs.type_name()}'
2823}
2824
2825fn test_transform_folds_string_literal_concat() {
2826 mut t := create_test_transformer()
2827 result := t.transform_expr(ast.InfixExpr{
2828 op: .plus
2829 lhs: ast.Expr(ast.StringLiteral{
2830 kind: .v
2831 value: "'left-'"
2832 })
2833 rhs: ast.Expr(ast.StringLiteral{
2834 kind: .v
2835 value: "'right'"
2836 })
2837 })
2838
2839 assert result is ast.StringLiteral, 'expected StringLiteral, got ${result.type_name()}'
2840 lit := result as ast.StringLiteral
2841 assert lit.kind == .v
2842 assert lit.value == 'left-right'
2843}
2844
2845fn test_transform_keeps_generic_array_elem_equality_as_infix() {
2846 mut t := create_transformer_with_vars({
2847 'a': types.Type(types.Array{
2848 elem_type: types.Type(types.NamedType('T'))
2849 })
2850 'e': types.Type(types.NamedType('T'))
2851 })
2852 t.cur_fn_generic_params = ['T']
2853 t.generic_var_type_params = {
2854 'a': 'T'
2855 }
2856 t.env.set_expr_type(101, types.string_)
2857 t.env.set_expr_type(102, types.string_)
2858
2859 result := t.transform_infix_expr(ast.InfixExpr{
2860 op: .eq
2861 lhs: ast.Expr(ast.IndexExpr{
2862 lhs: ast.Expr(ast.Ident{
2863 name: 'a'
2864 })
2865 expr: ast.Expr(ast.Ident{
2866 name: 'idx'
2867 })
2868 pos: token.Pos{
2869 id: 101
2870 }
2871 })
2872 rhs: ast.Expr(ast.Ident{
2873 pos: token.Pos{
2874 id: 102
2875 }
2876 name: 'e'
2877 })
2878 })
2879
2880 assert result is ast.InfixExpr, 'generic equality should stay for specialized codegen'
2881}
2882
2883fn test_transform_generic_module_call_uses_specialized_name() {
2884 mut t := create_test_transformer()
2885 mut scope := types.new_scope(unsafe { nil })
2886 scope.insert('json', types.Module{
2887 name: 'x.json2'
2888 })
2889 t.scope = scope
2890
2891 result := t.transform_expr(ast.CallExpr{
2892 lhs: ast.Expr(ast.GenericArgOrIndexExpr{
2893 lhs: ast.Expr(ast.SelectorExpr{
2894 lhs: ast.Expr(ast.Ident{
2895 name: 'json'
2896 })
2897 rhs: ast.Ident{
2898 name: 'decode'
2899 }
2900 })
2901 expr: ast.Expr(ast.Ident{
2902 name: 'GitHubRepoInfo'
2903 })
2904 })
2905 args: [
2906 ast.Expr(ast.Ident{
2907 name: 'body'
2908 }),
2909 ]
2910 })
2911
2912 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
2913 call := result as ast.CallExpr
2914 assert call.lhs is ast.Ident
2915 assert (call.lhs as ast.Ident).name == 'json2__decode_T_GitHubRepoInfo'
2916 assert call.args.len == 1
2917}
2918
2919fn test_transform_generic_module_call_or_cast_uses_specialized_name() {
2920 mut t := create_test_transformer()
2921 mut scope := types.new_scope(unsafe { nil })
2922 scope.insert('json', types.Module{
2923 name: 'x.json2'
2924 })
2925 t.scope = scope
2926
2927 result := t.transform_expr(ast.CallOrCastExpr{
2928 lhs: ast.Expr(ast.GenericArgOrIndexExpr{
2929 lhs: ast.Expr(ast.SelectorExpr{
2930 lhs: ast.Expr(ast.Ident{
2931 name: 'json'
2932 })
2933 rhs: ast.Ident{
2934 name: 'decode'
2935 }
2936 })
2937 expr: ast.Expr(ast.Ident{
2938 name: 'GitHubRepoInfo'
2939 })
2940 })
2941 expr: ast.Expr(ast.SelectorExpr{
2942 lhs: ast.Expr(ast.Ident{
2943 name: 'resp'
2944 })
2945 rhs: ast.Ident{
2946 name: 'body'
2947 }
2948 })
2949 })
2950
2951 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
2952 call := result as ast.CallExpr
2953 assert call.lhs is ast.Ident
2954 assert (call.lhs as ast.Ident).name == 'json2__decode_T_GitHubRepoInfo'
2955 assert call.args.len == 1
2956}
2957
2958fn test_transform_generic_module_call_or_cast_uses_array_specialized_name() {
2959 mut t := create_test_transformer()
2960 mut scope := types.new_scope(unsafe { nil })
2961 scope.insert('json', types.Module{
2962 name: 'x.json2'
2963 })
2964 t.scope = scope
2965
2966 result := t.transform_expr(ast.CallOrCastExpr{
2967 lhs: ast.Expr(ast.GenericArgOrIndexExpr{
2968 lhs: ast.Expr(ast.SelectorExpr{
2969 lhs: ast.Expr(ast.Ident{
2970 name: 'json'
2971 })
2972 rhs: ast.Ident{
2973 name: 'decode'
2974 }
2975 })
2976 expr: ast.Expr(ast.Type(ast.ArrayType{
2977 elem_type: ast.Expr(ast.Ident{
2978 name: 'GitHubContributor'
2979 })
2980 }))
2981 })
2982 expr: ast.Expr(ast.SelectorExpr{
2983 lhs: ast.Expr(ast.Ident{
2984 name: 'resp'
2985 })
2986 rhs: ast.Ident{
2987 name: 'body'
2988 }
2989 })
2990 })
2991
2992 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
2993 call := result as ast.CallExpr
2994 assert call.lhs is ast.Ident
2995 assert (call.lhs as ast.Ident).name == 'json2__decode_T_Array_GitHubContributor'
2996 assert call.args.len == 1
2997}
2998
2999fn test_transform_monomorphizes_imported_generic_module_call() {
3000 files := transform_sources_for_test([
3001 TestSource{
3002 rel: 'x/json2/decode.v'
3003 code: '
3004module json2
3005
3006struct Decoder {}
3007
3008fn (mut decoder Decoder) decode_value[T](mut value T) ! {
3009 _ = value
3010}
3011
3012pub fn decode[T]() !T {
3013 mut decoder := Decoder{}
3014 mut result := T{}
3015 decoder.decode_value(mut result)!
3016 return result
3017}
3018'
3019 },
3020 TestSource{
3021 rel: 'main.v'
3022 code: '
3023module main
3024
3025import x.json2
3026
3027struct Payload {
3028 value int
3029}
3030
3031fn main() {
3032 _ := json2.decode[Payload]() or { Payload{} }
3033}
3034'
3035 },
3036 ])
3037 call_names := call_names_for_fn(files, 'main')
3038 assert 'json2__decode_T_Payload' in call_names
3039 assert 'decode_T_Payload' !in call_names
3040
3041 mut found_prefixed_clone := false
3042 mut found_unprefixed_clone := false
3043 mut found_method_clone := false
3044 mut found_generic_decl := false
3045 for file in files {
3046 for stmt in file.stmts {
3047 if stmt is ast.FnDecl {
3048 if stmt.name == 'json2__decode_T_Payload' {
3049 found_prefixed_clone = true
3050 assert stmt.typ.generic_params.len == 0
3051 }
3052 if stmt.name == 'decode_T_Payload' {
3053 found_unprefixed_clone = true
3054 }
3055 if stmt.name == 'decode_value_T_Payload' {
3056 found_method_clone = true
3057 assert stmt.typ.generic_params.len == 0
3058 }
3059 if stmt.name == 'decode' && decl_generic_param_names(stmt).len > 0 {
3060 found_generic_decl = true
3061 }
3062 }
3063 }
3064 }
3065 assert found_prefixed_clone
3066 assert !found_unprefixed_clone
3067 assert found_method_clone
3068 assert !found_generic_decl
3069}
3070
3071fn test_transform_nested_module_selector_respects_local_shadow() {
3072 mut t := create_test_transformer()
3073 mut checker_scope := types.new_scope(unsafe { nil })
3074 checker_scope.insert('checker', types.Module{
3075 name: 'checker'
3076 })
3077 mut fn_scope := types.new_scope(checker_scope)
3078 fn_scope.insert('checker', types.Type(types.Struct{
3079 name: 'checker__Checker'
3080 }))
3081 t.cur_module = 'checker'
3082 t.scope = fn_scope
3083 t.cached_scopes = {
3084 'checker': checker_scope
3085 'type_resolver': types.new_scope(unsafe { nil })
3086 }
3087
3088 result := t.transform_expr(ast.SelectorExpr{
3089 lhs: ast.Expr(ast.SelectorExpr{
3090 lhs: ast.Expr(ast.Ident{
3091 name: 'checker'
3092 })
3093 rhs: ast.Ident{
3094 name: 'type_resolver'
3095 }
3096 })
3097 rhs: ast.Ident{
3098 name: 'info'
3099 }
3100 })
3101
3102 assert result is ast.SelectorExpr, 'local field chain must not become a module symbol'
3103 outer := result as ast.SelectorExpr
3104 assert outer.lhs is ast.SelectorExpr
3105 inner := outer.lhs as ast.SelectorExpr
3106 assert inner.lhs is ast.Ident
3107 assert (inner.lhs as ast.Ident).name == 'checker'
3108 assert inner.rhs.name == 'type_resolver'
3109 assert outer.rhs.name == 'info'
3110}
3111
3112fn test_expr_to_string_keeps_as_cast_selector_paths_distinct() {
3113 t := create_test_transformer()
3114 node_right := ast.Expr(ast.SelectorExpr{
3115 lhs: ast.Expr(ast.Ident{
3116 name: 'node'
3117 })
3118 rhs: ast.Ident{
3119 name: 'right'
3120 }
3121 })
3122 ast_as_cast := ast.Expr(ast.SelectorExpr{
3123 lhs: ast.Expr(ast.Ident{
3124 name: 'ast'
3125 })
3126 rhs: ast.Ident{
3127 name: 'AsCast'
3128 }
3129 })
3130 ast_par_expr := ast.Expr(ast.SelectorExpr{
3131 lhs: ast.Expr(ast.Ident{
3132 name: 'ast'
3133 })
3134 rhs: ast.Ident{
3135 name: 'ParExpr'
3136 }
3137 })
3138 as_cast_payload_expr := ast.Expr(ast.SelectorExpr{
3139 lhs: ast.Expr(ast.AsCastExpr{
3140 expr: ast.Expr(ast.SelectorExpr{
3141 lhs: node_right
3142 rhs: ast.Ident{
3143 name: 'expr'
3144 }
3145 })
3146 typ: ast_as_cast
3147 })
3148 rhs: ast.Ident{
3149 name: 'expr'
3150 }
3151 })
3152 par_expr_payload_expr := ast.Expr(ast.SelectorExpr{
3153 lhs: ast.Expr(ast.AsCastExpr{
3154 expr: node_right
3155 typ: ast_par_expr
3156 })
3157 rhs: ast.Ident{
3158 name: 'expr'
3159 }
3160 })
3161
3162 assert t.expr_to_string(as_cast_payload_expr) == '(node.right.expr as ast__AsCast).expr'
3163 assert t.expr_to_string(par_expr_payload_expr) == '(node.right as ast__ParExpr).expr'
3164 assert t.expr_to_string(as_cast_payload_expr) != t.expr_to_string(par_expr_payload_expr)
3165}
3166
3167fn test_transform_bare_generic_call_uses_specialized_name() {
3168 mut t := create_test_transformer()
3169 result := t.transform_expr(ast.CallExpr{
3170 lhs: ast.Expr(ast.GenericArgs{
3171 lhs: ast.Expr(ast.Ident{
3172 name: 'run_new'
3173 })
3174 args: [
3175 ast.Expr(ast.Ident{
3176 name: 'A'
3177 }),
3178 ast.Expr(ast.Ident{
3179 name: 'X'
3180 }),
3181 ]
3182 })
3183 args: [
3184 ast.Expr(ast.Ident{
3185 name: 'app'
3186 }),
3187 ast.Expr(ast.Ident{
3188 name: 'params'
3189 }),
3190 ]
3191 })
3192
3193 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
3194 call := result as ast.CallExpr
3195 assert call.lhs is ast.Ident
3196 assert (call.lhs as ast.Ident).name == 'run_new_T_A_X'
3197 assert call.args.len == 2
3198}
3199
3200fn test_transform_transitive_generic_call_in_clone_emits_callee_clone() {
3201 files := transform_sources_for_test([
3202 TestSource{
3203 rel: 'webx/webx.v'
3204 code: 'module webx
3205
3206pub fn run_at[A, X]() ! {
3207 run_new[A, X]()!
3208}
3209
3210pub fn run_new[A, X]() ! {}
3211'
3212 },
3213 TestSource{
3214 rel: 'main.v'
3215 code: 'module main
3216
3217import webx
3218
3219struct App {}
3220struct Context {}
3221
3222fn boot() {
3223 webx.run_at[App, Context]() or {}
3224}
3225'
3226 },
3227 ])
3228 mut fn_names := []string{}
3229 mut webx_fn_names := []string{}
3230 mut main_fn_names := []string{}
3231 for file in files {
3232 for stmt in file.stmts {
3233 if stmt is ast.FnDecl {
3234 fn_names << stmt.name
3235 if file.mod == 'webx' {
3236 webx_fn_names << stmt.name
3237 }
3238 if file.mod == 'main' {
3239 main_fn_names << stmt.name
3240 }
3241 }
3242 }
3243 }
3244 assert 'webx__run_at_T_App_Context' in fn_names
3245 assert 'webx__run_new_T_App_Context' in fn_names
3246 assert 'webx__run_at_T_App_Context' in main_fn_names
3247 assert 'webx__run_new_T_App_Context' in main_fn_names
3248 assert 'webx__run_at_T_App_Context' !in webx_fn_names
3249 assert 'webx__run_new_T_App_Context' !in webx_fn_names
3250}
3251
3252fn test_transform_transitive_generic_call_with_mut_arg_in_clone_emits_callee_clone() {
3253 files := transform_sources_for_test([
3254 TestSource{
3255 rel: 'webx/webx.v'
3256 code: 'module webx
3257
3258pub struct RunParams {}
3259
3260pub fn run_at[A, X](mut global_app A, params RunParams) ! {
3261 run_new[A, X](mut global_app, params)!
3262}
3263
3264pub fn run_new[A, X](mut global_app A, params RunParams) ! {}
3265'
3266 },
3267 TestSource{
3268 rel: 'main.v'
3269 code: 'module main
3270
3271import webx
3272
3273struct App {}
3274struct Context {}
3275
3276fn boot() {
3277 mut app := App{}
3278 webx.run_at[App, Context](mut app, webx.RunParams{}) or {}
3279}
3280'
3281 },
3282 ])
3283 mut fn_names := []string{}
3284 mut webx_fn_names := []string{}
3285 mut main_fn_names := []string{}
3286 for file in files {
3287 for stmt in file.stmts {
3288 if stmt is ast.FnDecl {
3289 fn_names << stmt.name
3290 if file.mod == 'webx' {
3291 webx_fn_names << stmt.name
3292 }
3293 if file.mod == 'main' {
3294 main_fn_names << stmt.name
3295 }
3296 }
3297 }
3298 }
3299 assert 'webx__run_at_T_App_Context' in fn_names
3300 assert 'webx__run_new_T_App_Context' in fn_names
3301 assert 'webx__run_at_T_App_Context' in main_fn_names
3302 assert 'webx__run_new_T_App_Context' in main_fn_names
3303 assert 'webx__run_at_T_App_Context' !in webx_fn_names
3304 assert 'webx__run_new_T_App_Context' !in webx_fn_names
3305}
3306
3307fn test_transform_transitive_generic_function_value_in_clone_emits_full_callee_clone() {
3308 files := transform_sources_for_test([
3309 TestSource{
3310 rel: 'webx/webx.v'
3311 code: 'module webx
3312
3313pub struct ServerConfig {
3314 handler fn (req int) int
3315}
3316
3317pub struct Context {}
3318
3319pub fn run_at[A, X]() {
3320 run_new[A, X]()
3321}
3322
3323pub fn run_new[A, X]() {
3324 _ := ServerConfig{
3325 handler: parallel_request_handler[A, X]
3326 }
3327}
3328
3329fn parallel_request_handler[A, X](req int) int {
3330 return route[A, X](req)
3331}
3332
3333fn route[A, X](req int) int {
3334 return req
3335}
3336'
3337 },
3338 TestSource{
3339 rel: 'main.v'
3340 code: 'module main
3341
3342import webx
3343
3344struct App {}
3345struct Context {}
3346
3347fn boot() {
3348 webx.run_at[App, Context]()
3349}
3350'
3351 },
3352 ])
3353 mut main_fn_names := []string{}
3354 mut handler_name := ''
3355 for file in files {
3356 if file.mod != 'main' {
3357 continue
3358 }
3359 for stmt in file.stmts {
3360 if stmt is ast.FnDecl {
3361 main_fn_names << stmt.name
3362 if stmt.name == 'webx__run_new_T_App_Context' {
3363 assign := stmt.stmts[0] as ast.AssignStmt
3364 init := assign.rhs[0] as ast.InitExpr
3365 if init.fields[0].value is ast.Ident {
3366 handler_name = (init.fields[0].value as ast.Ident).name
3367 } else {
3368 handler_name = init.fields[0].value.type_name()
3369 }
3370 }
3371 }
3372 }
3373 }
3374 assert 'webx__run_new_T_App_Context' in main_fn_names
3375 assert handler_name == 'webx__parallel_request_handler_T_App_Context'
3376 assert 'webx__parallel_request_handler_T_App_Context' in main_fn_names
3377 assert 'webx__route_T_App_Context' in main_fn_names
3378}
3379
3380fn test_transform_imported_clone_substituted_init_type_keeps_declaring_module_context() {
3381 files := transform_sources_for_test([
3382 TestSource{
3383 rel: 'webx/webx.v'
3384 code: 'module webx
3385
3386pub struct Context {}
3387
3388pub fn make[A, X]() {
3389 ctx := &Context{}
3390 mut user_context := X{
3391 Context: ctx
3392 }
3393 route[A, X](mut user_context)
3394}
3395
3396fn route[A, X](mut user_context X) {
3397 _ = user_context
3398}
3399'
3400 },
3401 TestSource{
3402 rel: 'main.v'
3403 code: 'module main
3404
3405import webx
3406
3407struct App {}
3408
3409struct Context {
3410 webx.Context
3411}
3412
3413fn boot() {
3414 webx.make[App, Context]()
3415}
3416'
3417 },
3418 ])
3419 mut init_type_name := ''
3420 mut main_fn_names := []string{}
3421 for file in files {
3422 if file.mod != 'main' {
3423 continue
3424 }
3425 for stmt in file.stmts {
3426 if stmt is ast.FnDecl && stmt.name == 'webx__make_T_App_Context' {
3427 main_fn_names << stmt.name
3428 assign := stmt.stmts[1] as ast.AssignStmt
3429 init := assign.rhs[0] as ast.InitExpr
3430 init_type := init.typ as ast.Ident
3431 init_type_name = init_type.name
3432 } else if stmt is ast.FnDecl {
3433 main_fn_names << stmt.name
3434 }
3435 }
3436 }
3437 assert init_type_name == 'Context'
3438 assert 'webx__route_T_App_Context' in main_fn_names
3439}
3440
3441fn test_transform_generic_struct_clone_qualifies_module_type_arg_in_fn_field() {
3442 files := transform_sources_for_test([
3443 TestSource{
3444 rel: 'veb/veb.v'
3445 code: 'module veb
3446
3447pub struct Context {}
3448
3449pub struct MiddlewareOptions[T] {
3450 handler fn (mut ctx T) bool
3451}
3452
3453pub fn make[T]() MiddlewareOptions[T] {
3454 return MiddlewareOptions[T]{}
3455}
3456'
3457 },
3458 TestSource{
3459 rel: 'main.v'
3460 code: 'module main
3461
3462import veb
3463
3464struct Context {}
3465
3466fn boot() {
3467 _ := veb.make[veb.Context]()
3468}
3469'
3470 },
3471 ])
3472 mut clone_name := ''
3473 mut handler_param_name := ''
3474 for file in files {
3475 if file.mod != 'veb' {
3476 continue
3477 }
3478 for stmt in file.stmts {
3479 if stmt is ast.StructDecl && stmt.name == 'MiddlewareOptions_T_veb_Context' {
3480 clone_name = stmt.name
3481 field_typ := stmt.fields[0].typ as ast.Type
3482 fn_typ := field_typ as ast.FnType
3483 param_ident := fn_typ.params[0].typ as ast.Ident
3484 handler_param_name = param_ident.name
3485 }
3486 }
3487 }
3488 assert clone_name == 'MiddlewareOptions_T_veb_Context'
3489 assert handler_param_name == 'veb__Context'
3490}
3491
3492fn test_transform_embedded_generic_method_options_do_not_emit_bare_import_context() {
3493 files := transform_sources_for_test([
3494 TestSource{
3495 rel: 'veb/veb.v'
3496 code: 'module veb
3497
3498pub struct Context {}
3499
3500pub struct Middleware[T] {}
3501
3502@[params]
3503pub struct MiddlewareOptions[T] {
3504 handler fn (mut ctx T) bool
3505}
3506
3507pub fn (mut m Middleware[T]) use(options MiddlewareOptions[T]) {
3508 _ = options
3509}
3510'
3511 },
3512 TestSource{
3513 rel: 'main.v'
3514 code: 'module main
3515
3516import veb
3517
3518struct App {
3519 veb.Middleware[Context]
3520}
3521
3522struct Context {
3523 veb.Context
3524}
3525
3526fn (app &App) before_request(mut ctx Context) bool {
3527 return true
3528}
3529
3530fn boot() {
3531 mut app := App{}
3532 app.use(handler: app.before_request)
3533}
3534'
3535 },
3536 ])
3537 mut import_handler_param := ''
3538 mut main_handler_param := ''
3539 for file in files {
3540 for stmt in file.stmts {
3541 match stmt {
3542 ast.StructDecl {
3543 if !stmt.name.contains('MiddlewareOptions_T') {
3544 continue
3545 }
3546 field_typ := stmt.fields[0].typ as ast.Type
3547 fn_typ := field_typ as ast.FnType
3548 param_ident := fn_typ.params[0].typ as ast.Ident
3549 if file.mod == 'veb' && stmt.name == 'MiddlewareOptions_T_veb_Context' {
3550 import_handler_param = param_ident.name
3551 }
3552 if file.mod == 'main' && stmt.name == 'veb__MiddlewareOptions_T_Context' {
3553 main_handler_param = param_ident.name
3554 }
3555 }
3556 else {}
3557 }
3558 }
3559 }
3560 assert import_handler_param == '' || import_handler_param == 'veb__Context', 'import_handler_param=${import_handler_param}, main_handler_param=${main_handler_param}'
3561
3562 assert main_handler_param == '' || main_handler_param == 'Context', 'import_handler_param=${import_handler_param}, main_handler_param=${main_handler_param}'
3563}
3564
3565fn test_transform_imported_generic_struct_with_local_type_stays_in_call_file() {
3566 files := transform_sources_for_test([
3567 TestSource{
3568 rel: 'json2/json2.v'
3569 code: 'module json2
3570
3571pub struct StructKeyDecodeResult[T] {
3572 value T
3573}
3574
3575pub fn decode[T]() StructKeyDecodeResult[T] {
3576 return StructKeyDecodeResult[T]{}
3577}
3578'
3579 },
3580 TestSource{
3581 rel: 'main.v'
3582 code: 'module main
3583
3584import json2
3585
3586struct LocalResponse {}
3587
3588fn boot() {
3589 _ := json2.decode[LocalResponse]()
3590}
3591'
3592 },
3593 ])
3594 mut import_struct_found := false
3595 mut import_fn_found := false
3596 mut main_struct_found := false
3597 mut main_fn_found := false
3598 mut main_field_typ := ''
3599 for file in files {
3600 for stmt in file.stmts {
3601 match stmt {
3602 ast.StructDecl {
3603 if stmt.name == 'json2__StructKeyDecodeResult_T_LocalResponse'
3604 || stmt.name == 'StructKeyDecodeResult_T_LocalResponse' {
3605 if file.mod == 'json2' {
3606 import_struct_found = true
3607 }
3608 if file.mod == 'main'
3609 && stmt.name == 'json2__StructKeyDecodeResult_T_LocalResponse' {
3610 main_struct_found = true
3611 field_typ := stmt.fields[0].typ as ast.Ident
3612 main_field_typ = field_typ.name
3613 }
3614 }
3615 }
3616 ast.FnDecl {
3617 if stmt.name == 'json2__decode_T_LocalResponse'
3618 || stmt.name == 'decode_T_LocalResponse' {
3619 if file.mod == 'json2' {
3620 import_fn_found = true
3621 }
3622 if file.mod == 'main' && stmt.name == 'json2__decode_T_LocalResponse' {
3623 main_fn_found = true
3624 }
3625 }
3626 }
3627 else {}
3628 }
3629 }
3630 }
3631 assert !import_struct_found
3632 assert !import_fn_found
3633 assert main_struct_found
3634 assert main_fn_found
3635 assert main_field_typ == 'LocalResponse'
3636}
3637
3638fn test_transform_generic_struct_emits_module_clone_after_file_local_clone() {
3639 files := transform_sources_for_test([
3640 TestSource{
3641 rel: 'other/other.v'
3642 code: 'module other
3643
3644pub struct Type {}
3645'
3646 },
3647 TestSource{
3648 rel: 'm/m.v'
3649 code: 'module m
3650
3651import other
3652
3653pub struct Box[T] {
3654 value T
3655}
3656
3657pub fn use_external() Box[other.Type] {
3658 return Box[other.Type]{}
3659}
3660
3661pub fn use_int() Box[int] {
3662 return Box[int]{}
3663}
3664'
3665 },
3666 TestSource{
3667 rel: 'main.v'
3668 code: 'module main
3669
3670import m
3671
3672fn boot() {
3673 _ := m.use_external()
3674 _ := m.use_int()
3675}
3676'
3677 },
3678 ])
3679 mut m_structs := []string{}
3680 for file in files {
3681 if file.mod != 'm' {
3682 continue
3683 }
3684 for stmt in file.stmts {
3685 if stmt is ast.StructDecl {
3686 m_structs << stmt.name
3687 }
3688 }
3689 }
3690 assert 'Box_T_other_Type' in m_structs
3691 assert 'Box_T_int' in m_structs
3692}
3693
3694fn test_transform_moved_generic_struct_qualifies_source_module_field_types() {
3695 files := transform_sources_for_test([
3696 TestSource{
3697 rel: 'boxlib/boxlib.v'
3698 code: 'module boxlib
3699
3700pub struct Helper {}
3701
3702pub struct Box[T] {
3703 Helper
3704 helper Helper
3705 values []Helper
3706 value T
3707}
3708
3709pub fn make[T]() Box[T] {
3710 return Box[T]{}
3711}
3712'
3713 },
3714 TestSource{
3715 rel: 'main.v'
3716 code: 'module main
3717
3718import boxlib
3719
3720struct LocalResponse {}
3721
3722fn boot() {
3723 _ := boxlib.make[LocalResponse]()
3724}
3725'
3726 },
3727 ])
3728 mut moved_struct_found := false
3729 mut embedded_typ := ''
3730 mut helper_typ := ''
3731 mut values_elem_typ := ''
3732 mut value_typ := ''
3733 for file in files {
3734 if file.mod != 'main' {
3735 continue
3736 }
3737 for stmt in file.stmts {
3738 if stmt is ast.StructDecl && stmt.name == 'boxlib__Box_T_LocalResponse' {
3739 moved_struct_found = true
3740 if stmt.embedded.len > 0 {
3741 embedded_ident := stmt.embedded[0] as ast.Ident
3742 embedded_typ = embedded_ident.name
3743 }
3744 for field in stmt.fields {
3745 match field.name {
3746 'helper' {
3747 helper_ident := field.typ as ast.Ident
3748 helper_typ = helper_ident.name
3749 }
3750 'values' {
3751 array_typ := field.typ as ast.Type
3752 values_array := array_typ as ast.ArrayType
3753 values_elem_ident := values_array.elem_type as ast.Ident
3754 values_elem_typ = values_elem_ident.name
3755 }
3756 'value' {
3757 value_ident := field.typ as ast.Ident
3758 value_typ = value_ident.name
3759 }
3760 else {}
3761 }
3762 }
3763 }
3764 }
3765 }
3766 assert moved_struct_found
3767 assert embedded_typ == 'boxlib__Helper'
3768 assert helper_typ == 'boxlib__Helper'
3769 assert values_elem_typ == 'boxlib__Helper'
3770 assert value_typ == 'LocalResponse'
3771}
3772
3773fn test_transform_transitive_imported_clone_substitutes_nested_generic_route_context() {
3774 env, files := transform_sources_with_env_for_test([
3775 TestSource{
3776 rel: 'webx/webx.v'
3777 code: 'module webx
3778
3779pub struct Request {
3780 route int
3781}
3782
3783pub struct ServerConfig {
3784 handler fn (req Request) &Context
3785}
3786
3787pub struct Context {
3788 current_path string
3789}
3790
3791pub fn run_new[A, X](mut global_app A) {
3792 _ := ServerConfig{
3793 handler: parallel_request_handler[A, X]
3794 }
3795 _ = global_app
3796}
3797
3798fn parallel_request_handler[A, X](req Request) &Context {
3799 mut app := A{}
3800 return handle_request_and_route[A, X](mut app, req)
3801}
3802
3803fn handle_request_and_route[A, X](mut app A, req Request) &Context {
3804 mut ctx := &Context{}
3805 mut user_context := X{
3806 Context: ctx
3807 }
3808 handle_route[A, X](mut app, mut user_context, req.route)
3809 return ctx
3810}
3811
3812fn handle_route[A, X](mut app A, mut user_context X, route int) {
3813 _ = app
3814 _ = user_context
3815 _ = route
3816}
3817'
3818 },
3819 TestSource{
3820 rel: 'main.v'
3821 code: 'module main
3822
3823import webx
3824
3825struct App {}
3826
3827struct Context {
3828 webx.Context
3829}
3830
3831fn boot() {
3832 mut app := App{}
3833 webx.run_new[App, Context](mut app)
3834}
3835'
3836 },
3837 ])
3838 mut main_fn_names := []string{}
3839 mut init_type_name := ''
3840 mut route_call_name := ''
3841 for file in files {
3842 if file.mod != 'main' {
3843 continue
3844 }
3845 for stmt in file.stmts {
3846 if stmt is ast.FnDecl {
3847 main_fn_names << stmt.name
3848 if stmt.name == 'webx__handle_request_and_route_T_App_Context' {
3849 for inner in stmt.stmts {
3850 if inner is ast.AssignStmt && inner.lhs.len == 1 && inner.rhs.len == 1 {
3851 lhs_name := ident_name_from_expr_for_test(inner.lhs[0]) or { '' }
3852 if lhs_name == 'user_context' && inner.rhs[0] is ast.InitExpr {
3853 init := inner.rhs[0] as ast.InitExpr
3854 init_type := init.typ as ast.Ident
3855 init_type_name = init_type.name
3856 }
3857 }
3858 if inner is ast.ExprStmt && inner.expr is ast.CallExpr {
3859 call := inner.expr as ast.CallExpr
3860 if call.lhs is ast.Ident {
3861 route_call_name = (call.lhs as ast.Ident).name
3862 } else {
3863 route_call_name = call.lhs.type_name()
3864 }
3865 }
3866 }
3867 }
3868 }
3869 }
3870 }
3871 assert 'webx__parallel_request_handler_T_App_Context' in main_fn_names
3872 assert 'webx__handle_request_and_route_T_App_Context' in main_fn_names
3873 assert 'webx__handle_route_T_App_Context' in main_fn_names
3874 assert init_type_name == 'Context'
3875 assert route_call_name == 'webx__handle_route_T_App_Context'
3876 route_scope := env.get_fn_scope('main', 'webx__handle_route_T_App_Context') or {
3877 panic('missing imported route clone scope')
3878 }
3879 user_context_type := route_scope.lookup_var_type('user_context') or {
3880 panic('missing user_context type')
3881 }
3882 assert user_context_type.name() == 'Context'
3883}
3884
3885fn test_transform_inferred_generic_method_call_uses_specialized_name() {
3886 files := transform_code_for_test('
3887module main
3888
3889struct Job {}
3890
3891struct Item {
3892 value int
3893}
3894
3895struct Out {
3896mut:
3897 value int
3898}
3899
3900fn fill(item Item, mut out Out) {
3901 out.value = item.value
3902}
3903
3904fn (job &Job) apply[K, D, F](items []K, mut out []D, f F) {
3905 _ = job
3906 if items.len > 0 && out.len > 0 {
3907 f(items[0], mut out[0])
3908 }
3909}
3910
3911fn use_apply() int {
3912 job := Job{}
3913 items := [Item{
3914 value: 7
3915 }]
3916 mut out := []Out{len: 1}
3917 job.apply(items, mut out, fill)
3918 return out[0].value
3919}
3920')
3921 call_names := call_names_for_fn(files, 'use_apply')
3922 assert 'Job__apply_T_Item_Out_fn_item_Item_out_Out_void' in call_names
3923 assert 'Job__apply' !in call_names
3924
3925 mut found_clone := false
3926 for file in files {
3927 for stmt in file.stmts {
3928 if stmt is ast.FnDecl && stmt.name == 'apply_T_Item_Out_fn_item_Item_out_Out_void' {
3929 found_clone = true
3930 assert stmt.typ.generic_params.len == 0
3931 assert stmt.typ.params.len == 3
3932 assert stmt.typ.params[2].typ is ast.Type
3933 fn_param_type := stmt.typ.params[2].typ as ast.Type
3934 assert fn_param_type is ast.FnType
3935 }
3936 }
3937 }
3938 assert found_clone
3939}
3940
3941fn test_transform_non_generic_method_does_not_match_generic_method_short_name() {
3942 files := transform_code_for_test('
3943module main
3944
3945struct Vec3[T] {
3946 x T
3947 y T
3948 z T
3949}
3950
3951fn (v Vec3[T]) div(u Vec3[T]) Vec3[T] {
3952 _ = u
3953 return v
3954}
3955
3956struct Float3 {
3957 x f32
3958 y f32
3959 z f32
3960}
3961
3962fn (a Float3) div(s f32) Float3 {
3963 _ = s
3964 return a
3965}
3966
3967fn use_float(a Float3) {
3968 _ = a.div(f32(2))
3969}
3970')
3971 call_names := call_names_for_fn(files, 'use_float')
3972 assert 'Float3__div' in call_names
3973 assert 'Float3__div_T_f32' !in call_names
3974}
3975
3976fn test_transform_inferred_generic_fn_call_uses_specialized_fn_arg_name() {
3977 files := transform_code_for_test('
3978module main
3979
3980struct Alpha {}
3981
3982fn use_alpha(mut a Alpha) {
3983 _ = a
3984}
3985
3986fn call_cb[T, F](mut value T, cb F) {
3987 cb(mut value)
3988}
3989
3990fn main() {
3991 mut a := Alpha{}
3992 call_cb(mut a, use_alpha)
3993}
3994')
3995 call_names := call_names_for_fn(files, 'main')
3996 assert 'call_cb_T_Alpha_fn_a_Alpha_void' in call_names
3997 assert 'call_cb' !in call_names
3998 assert 'call_cb_T_Alpha_voidptr' !in call_names
3999}
4000
4001fn test_transform_records_inferred_generic_binding_before_monomorphize() {
4002 files := transform_code_for_test('
4003module main
4004
4005fn clamp[T](a T, x T, b T) T {
4006 mut min := T(0)
4007 if x < b {
4008 min = x
4009 } else {
4010 min = b
4011 }
4012 return if min < a { a } else { min }
4013}
4014
4015fn main() {
4016 ratio := f32(0.5)
4017 _ = clamp(f32(0), ratio, 1.0)
4018}
4019')
4020 call_names := call_names_for_fn(files, 'main')
4021 assert 'clamp_T_f32' in call_names
4022 mut found_clone := false
4023 for file in files {
4024 for stmt in file.stmts {
4025 if stmt is ast.FnDecl && stmt.name == 'clamp_T_f32' {
4026 found_clone = true
4027 assert stmt.typ.generic_params.len == 0
4028 assert stmt.stmts.len > 0
4029 }
4030 }
4031 }
4032 assert found_clone
4033}
4034
4035fn stmts_have_defer(stmts []ast.Stmt) bool {
4036 for stmt in stmts {
4037 match stmt {
4038 ast.BlockStmt {
4039 if stmts_have_defer(stmt.stmts) {
4040 return true
4041 }
4042 }
4043 ast.ComptimeStmt {
4044 if stmts_have_defer([stmt.stmt]) {
4045 return true
4046 }
4047 }
4048 ast.DeferStmt {
4049 return true
4050 }
4051 ast.ExprStmt {
4052 if stmt.expr is ast.IfExpr {
4053 if stmts_have_defer(stmt.expr.stmts) {
4054 return true
4055 }
4056 }
4057 }
4058 ast.ForStmt {
4059 if stmts_have_defer(stmt.stmts) {
4060 return true
4061 }
4062 }
4063 else {}
4064 }
4065 }
4066 return false
4067}
4068
4069fn test_lower_defer_stmts_lowers_defer_inside_comptime_stmt() {
4070 mut t := create_test_transformer()
4071 cleanup_stmt := ast.Stmt(ast.ExprStmt{
4072 expr: ast.Expr(ast.Ident{
4073 name: 'cleanup'
4074 })
4075 })
4076 body_stmt := ast.Stmt(ast.ExprStmt{
4077 expr: ast.Expr(ast.Ident{
4078 name: 'body'
4079 })
4080 })
4081 lowered := t.lower_defer_stmts([
4082 ast.Stmt(ast.ComptimeStmt{
4083 stmt: ast.Stmt(ast.ForStmt{
4084 stmts: [
4085 body_stmt,
4086 ast.Stmt(ast.DeferStmt{
4087 stmts: [cleanup_stmt]
4088 }),
4089 ]
4090 })
4091 }),
4092 ], false, types.Type(types.void_))
4093
4094 assert !stmts_have_defer(lowered)
4095 assert lowered.len == 1
4096 assert lowered[0] is ast.ComptimeStmt
4097 comptime_stmt := lowered[0] as ast.ComptimeStmt
4098 assert comptime_stmt.stmt is ast.ForStmt
4099 for_stmt := comptime_stmt.stmt as ast.ForStmt
4100 assert for_stmt.stmts.len == 2
4101 assert for_stmt.stmts[1] is ast.ExprStmt
4102 assert (for_stmt.stmts[1] as ast.ExprStmt).expr.name() == 'cleanup'
4103}
4104
4105fn test_lower_defer_fn_inside_loop_runs_on_function_exit() {
4106 mut t := create_test_transformer()
4107 cleanup_stmt := ast.Stmt(ast.ExprStmt{
4108 expr: ast.Expr(ast.Ident{
4109 name: 'cleanup'
4110 })
4111 })
4112 body_stmt := ast.Stmt(ast.ExprStmt{
4113 expr: ast.Expr(ast.Ident{
4114 name: 'body'
4115 })
4116 })
4117 lowered := t.lower_defer_stmts([
4118 ast.Stmt(ast.ForStmt{
4119 stmts: [
4120 ast.Stmt(ast.DeferStmt{
4121 mode: .function
4122 stmts: [cleanup_stmt]
4123 }),
4124 body_stmt,
4125 ]
4126 }),
4127 ast.Stmt(ast.ReturnStmt{
4128 exprs: [
4129 ast.Expr(ast.Ident{
4130 name: 'x'
4131 }),
4132 ]
4133 }),
4134 ], true, types.Type(types.int_))
4135
4136 assert !stmts_have_defer(lowered)
4137 assert lowered.len == 5
4138 assert lowered[0] is ast.AssignStmt
4139 flag_decl := lowered[0] as ast.AssignStmt
4140 assert flag_decl.op == .decl_assign
4141 assert flag_decl.lhs[0] is ast.Ident
4142 flag_name := (flag_decl.lhs[0] as ast.Ident).name
4143
4144 assert lowered[1] is ast.ForStmt
4145 for_stmt := lowered[1] as ast.ForStmt
4146 assert for_stmt.stmts.len == 2
4147 assert for_stmt.stmts[0] is ast.AssignStmt
4148 flag_assign := for_stmt.stmts[0] as ast.AssignStmt
4149 assert flag_assign.op == .assign
4150 assert flag_assign.lhs[0] is ast.Ident
4151 assert (flag_assign.lhs[0] as ast.Ident).name == flag_name
4152 assert for_stmt.stmts[1] is ast.ExprStmt
4153 assert (for_stmt.stmts[1] as ast.ExprStmt).expr.name() == 'body'
4154
4155 assert lowered[3] is ast.ExprStmt
4156 guard_stmt := lowered[3] as ast.ExprStmt
4157 assert guard_stmt.expr is ast.IfExpr
4158 guard := guard_stmt.expr as ast.IfExpr
4159 assert guard.cond is ast.Ident
4160 assert (guard.cond as ast.Ident).name == flag_name
4161 assert guard.stmts.len == 1
4162 assert guard.stmts[0] is ast.ExprStmt
4163 assert (guard.stmts[0] as ast.ExprStmt).expr.name() == 'cleanup'
4164}
4165
4166fn test_defer_return_temp_uses_value_type_for_pointer_expr_returning_value() {
4167 mut scope := types.new_scope(unsafe { nil })
4168 mut env := types.Environment.new()
4169 stmt_type := types.Type(types.SumType{
4170 name: 'ast__Stmt'
4171 })
4172 ptr_stmt_type := types.Type(types.Pointer{
4173 base_type: stmt_type
4174 })
4175 pos := token.Pos{
4176 id: 99101
4177 }
4178 env.set_expr_type(pos.id, ptr_stmt_type)
4179 mut t := &Transformer{
4180 pref: &vpref.Preferences{}
4181 env: unsafe { env }
4182 scope: scope
4183 fn_root_scope: scope
4184 needed_clone_fns: map[string]string{}
4185 needed_array_contains_fns: map[string]ArrayMethodInfo{}
4186 needed_array_index_fns: map[string]ArrayMethodInfo{}
4187 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
4188 local_decl_types: map[string]types.Type{}
4189 }
4190 t.register_defer_return_temp('_defer_t1', ast.Expr(ast.Ident{
4191 name: 'node'
4192 pos: pos
4193 }), stmt_type, map[string]types.Type{})
4194 obj := scope.lookup_parent('_defer_t1', 0) or { panic('missing defer temp') }
4195 assert obj.typ().name() == 'ast__Stmt'
4196}
4197
4198fn test_defer_return_temp_uses_sum_return_type_for_smartcast_variant() {
4199 temp_type := defer_return_temp_type(types.Type(types.Struct{
4200 name: 'ast__ForStmt'
4201 }), types.Type(types.SumType{
4202 name: 'ast__Stmt'
4203 variants: [
4204 types.Type(types.Struct{
4205 name: 'ast__ForCStmt'
4206 }),
4207 types.Type(types.Struct{
4208 name: 'ast__ForInStmt'
4209 }),
4210 types.Type(types.Struct{
4211 name: 'ast__ForStmt'
4212 }),
4213 ]
4214 }))
4215 assert temp_type.name() == 'ast__Stmt'
4216}
4217
4218fn test_transform_persists_defer_return_temp_scope_for_codegen() {
4219 env, _ := transform_code_with_env_for_test('
4220type Stmt = EmptyStmt | ForInStmt
4221
4222struct EmptyStmt {}
4223
4224struct ForInStmt {
4225 val int
4226}
4227
4228struct G {
4229mut:
4230 values map[string]int
4231}
4232
4233fn cleanup() {}
4234
4235fn (mut g G) stmt(node Stmt) Stmt {
4236 if node is ForInStmt {
4237 defer(fn) {
4238 cleanup()
4239 }
4240 mut new_node := ForInStmt{
4241 ...node
4242 }
4243 return Stmt(new_node)
4244 }
4245 return node
4246}
4247')
4248 fn_scope := env.get_fn_scope('main', 'G__stmt') or { panic('missing transformed fn scope') }
4249 mut found_defer_temp := false
4250 for name, obj in fn_scope.objects {
4251 if !name.starts_with('_defer_t') {
4252 continue
4253 }
4254 found_defer_temp = true
4255 assert obj.typ() !is types.Pointer
4256 assert obj.typ().name().ends_with('Stmt')
4257 }
4258 assert found_defer_temp
4259}
4260
4261fn test_scoped_defer_return_err_temp_uses_ierror_type() {
4262 mut scope := types.new_scope(unsafe { nil })
4263 mut t := create_test_transformer()
4264 t.scope = scope
4265 t.fn_root_scope = scope
4266 t.set_synth_pos_counter(-1)
4267 err_type := types.Type(types.Struct{
4268 name: 'IError'
4269 })
4270 t.register_defer_return_temp('_defer_t_err', ast.Expr(ast.Ident{
4271 name: 'err'
4272 }), types.Type(types.ResultType{
4273 base_type: types.Type(types.SumType{
4274 name: 'ast__Expr'
4275 })
4276 }), {
4277 'err': err_type
4278 })
4279 mut found_ierror_temp := false
4280 mut temp_types := []string{}
4281 for name, obj in scope.objects {
4282 if !name.starts_with('_defer_t') {
4283 continue
4284 }
4285 temp_types << '${name}:${obj.typ().name()}'
4286 if obj.typ().name() == 'IError' {
4287 found_ierror_temp = true
4288 }
4289 }
4290 assert found_ierror_temp, temp_types.str()
4291}
4292
4293fn test_defer_decl_tracking_prefers_rhs_type_for_err_decl() {
4294 mut scope := types.new_scope(unsafe { nil })
4295 scope.insert_or_update('err', types.Type(types.Struct{
4296 name: 'ast__NodeError'
4297 }))
4298 mut t := create_test_transformer()
4299 t.scope = scope
4300 t.set_synth_pos_counter(-1)
4301 t.synth_types = map[int]types.Type{}
4302 or_tmp := ast.Expr(ast.Ident{
4303 name: '_or_t1'
4304 })
4305 mut decls := map[string]types.Type{}
4306 t.add_decl_types_from_stmt(mut decls, ast.Stmt(ast.AssignStmt{
4307 op: .decl_assign
4308 lhs: [ast.Expr(ast.Ident{
4309 name: 'err'
4310 })]
4311 rhs: [
4312 t.synth_selector(or_tmp, 'err', types.Type(types.Struct{
4313 name: 'IError'
4314 })),
4315 ]
4316 }))
4317 err_type := decls['err'] or {
4318 assert false, 'err declaration type was not tracked'
4319 return
4320 }
4321 assert err_type.name() == 'IError'
4322}
4323
4324fn test_transform_string_inter_smartcast_temp_uses_variant_type() {
4325 env, _ := transform_code_with_env_for_test('
4326type Value = int | string
4327
4328struct Entry {
4329 key Value
4330}
4331
4332fn entry_string(entry Entry) string {
4333 key_text := if entry.key is string {
4334 "\${entry.key}"
4335 } else {
4336 "other"
4337 }
4338 return key_text
4339}
4340')
4341 fn_scope := env.get_fn_scope('main', 'entry_string') or {
4342 panic('missing transformed fn scope')
4343 }
4344 mut found_string_temp := false
4345 for name, obj in fn_scope.objects {
4346 if !name.starts_with('_or_t') {
4347 continue
4348 }
4349 if obj.typ().name() == 'string' {
4350 found_string_temp = true
4351 }
4352 assert obj.typ().name() != 'Value'
4353 }
4354 assert found_string_temp
4355}
4356
4357fn test_lower_defer_fn_captures_block_local_values() {
4358 mut t := create_transformer_with_vars({
4359 'old': types.Type(types.bool_)
4360 })
4361 old_decl := ast.Stmt(ast.AssignStmt{
4362 op: .decl_assign
4363 lhs: [ast.Expr(ast.Ident{
4364 name: 'old'
4365 })]
4366 rhs: [ast.Expr(ast.BasicLiteral{
4367 kind: .key_false
4368 value: 'false'
4369 })]
4370 })
4371 defer_stmt := ast.Stmt(ast.DeferStmt{
4372 mode: .function
4373 stmts: [
4374 ast.Stmt(ast.AssignStmt{
4375 op: .assign
4376 lhs: [
4377 ast.Expr(ast.SelectorExpr{
4378 lhs: ast.Ident{
4379 name: 'g'
4380 }
4381 rhs: ast.Ident{
4382 name: 'inside_smartcast'
4383 }
4384 }),
4385 ]
4386 rhs: [
4387 ast.Expr(ast.Ident{
4388 name: 'old'
4389 }),
4390 ]
4391 }),
4392 ]
4393 })
4394 lowered := t.lower_defer_stmts([
4395 ast.Stmt(ast.BlockStmt{
4396 stmts: [old_decl, defer_stmt]
4397 }),
4398 ], false, types.Type(types.void_))
4399
4400 assert lowered.len == 4
4401 assert lowered[1] is ast.AssignStmt
4402 capture_decl := lowered[1] as ast.AssignStmt
4403 assert capture_decl.lhs[0] is ast.Ident
4404 capture_name := (capture_decl.lhs[0] as ast.Ident).name
4405 assert capture_name.starts_with('_defer_cap')
4406
4407 assert lowered[2] is ast.BlockStmt
4408 block_stmt := lowered[2] as ast.BlockStmt
4409 assert block_stmt.stmts.len == 3
4410 assert block_stmt.stmts[1] is ast.AssignStmt
4411 capture_assign := block_stmt.stmts[1] as ast.AssignStmt
4412 assert capture_assign.lhs[0] is ast.Ident
4413 assert (capture_assign.lhs[0] as ast.Ident).name == capture_name
4414
4415 assert lowered[3] is ast.ExprStmt
4416 guard := (lowered[3] as ast.ExprStmt).expr as ast.IfExpr
4417 restore := guard.stmts[0] as ast.AssignStmt
4418 assert restore.rhs[0] is ast.Ident
4419 assert (restore.rhs[0] as ast.Ident).name == capture_name
4420}
4421
4422fn test_lower_defer_fn_captures_block_local_values_in_string_interpolation() {
4423 mut t := create_transformer_with_vars({
4424 'old': types.Type(types.string_)
4425 })
4426 old_decl := ast.Stmt(ast.AssignStmt{
4427 op: .decl_assign
4428 lhs: [ast.Expr(ast.Ident{
4429 name: 'old'
4430 })]
4431 rhs: [ast.Expr(ast.StringLiteral{
4432 kind: .v
4433 value: 'old'
4434 })]
4435 })
4436 defer_stmt := ast.Stmt(ast.DeferStmt{
4437 mode: .function
4438 stmts: [
4439 ast.Stmt(ast.ExprStmt{
4440 expr: ast.Expr(ast.CallExpr{
4441 lhs: ast.Ident{
4442 name: 'cleanup'
4443 }
4444 args: [
4445 ast.Expr(ast.StringInterLiteral{
4446 kind: .v
4447 values: ['value=', '']
4448 inters: [ast.StringInter{
4449 expr: ast.Expr(ast.Ident{
4450 name: 'old'
4451 })
4452 }]
4453 }),
4454 ]
4455 })
4456 }),
4457 ]
4458 })
4459 lowered := t.lower_defer_stmts([
4460 ast.Stmt(ast.BlockStmt{
4461 stmts: [old_decl, defer_stmt]
4462 }),
4463 ], false, types.Type(types.void_))
4464
4465 assert lowered.len == 4
4466 capture_decl := lowered[1] as ast.AssignStmt
4467 capture_name := (capture_decl.lhs[0] as ast.Ident).name
4468 assert capture_name.starts_with('_defer_cap')
4469
4470 block_stmt := lowered[2] as ast.BlockStmt
4471 capture_assign := block_stmt.stmts[1] as ast.AssignStmt
4472 assert (capture_assign.lhs[0] as ast.Ident).name == capture_name
4473
4474 guard := (lowered[3] as ast.ExprStmt).expr as ast.IfExpr
4475 cleanup := (guard.stmts[0] as ast.ExprStmt).expr as ast.CallExpr
4476 inter_lit := cleanup.args[0] as ast.StringInterLiteral
4477 assert inter_lit.inters[0].expr is ast.Ident
4478 assert (inter_lit.inters[0].expr as ast.Ident).name == capture_name
4479}
4480
4481fn test_lower_defer_fn_captures_parent_block_values_from_nested_if() {
4482 mut t := create_test_transformer()
4483 old_decl := ast.Stmt(ast.AssignStmt{
4484 op: .decl_assign
4485 lhs: [ast.Expr(ast.Ident{
4486 name: 'old'
4487 })]
4488 rhs: [ast.Expr(ast.BasicLiteral{
4489 kind: .key_false
4490 value: 'false'
4491 })]
4492 })
4493 defer_stmt := ast.Stmt(ast.DeferStmt{
4494 mode: .function
4495 stmts: [
4496 ast.Stmt(ast.AssignStmt{
4497 op: .assign
4498 lhs: [
4499 ast.Expr(ast.SelectorExpr{
4500 lhs: ast.Ident{
4501 name: 'g'
4502 }
4503 rhs: ast.Ident{
4504 name: 'inside_smartcast'
4505 }
4506 }),
4507 ]
4508 rhs: [
4509 ast.Expr(ast.Ident{
4510 name: 'old'
4511 }),
4512 ]
4513 }),
4514 ]
4515 })
4516 lowered := t.lower_defer_stmts([
4517 old_decl,
4518 ast.Stmt(ast.ExprStmt{
4519 expr: ast.Expr(ast.IfExpr{
4520 cond: ast.Expr(ast.BasicLiteral{
4521 kind: .key_true
4522 value: 'true'
4523 })
4524 stmts: [defer_stmt]
4525 })
4526 }),
4527 ], false, types.Type(types.void_))
4528
4529 assert lowered.len == 5
4530 capture_decl := lowered[1] as ast.AssignStmt
4531 capture_name := (capture_decl.lhs[0] as ast.Ident).name
4532 assert capture_name.starts_with('_defer_cap')
4533
4534 if_stmt := lowered[3] as ast.ExprStmt
4535 if_expr := if_stmt.expr as ast.IfExpr
4536 assert if_expr.stmts.len == 2
4537 capture_assign := if_expr.stmts[0] as ast.AssignStmt
4538 assert (capture_assign.lhs[0] as ast.Ident).name == capture_name
4539
4540 guard := (lowered[4] as ast.ExprStmt).expr as ast.IfExpr
4541 restore := guard.stmts[0] as ast.AssignStmt
4542 assert restore.rhs[0] is ast.Ident
4543 assert (restore.rhs[0] as ast.Ident).name == capture_name
4544}
4545
4546fn test_transform_defer_fn_in_range_for_captures_loop_block_literals() {
4547 files := transform_code_for_test("
4548fn cleanup(s string) {}
4549
4550fn main() {
4551 for i in 0 .. 2 {
4552 mut str_add_rhs_tmp := ''
4553 mut str_add_rhs_needs_free := false
4554 if i == 1 {
4555 str_add_rhs_tmp = 'x'
4556 str_add_rhs_needs_free = true
4557 defer(fn) {
4558 if str_add_rhs_needs_free {
4559 cleanup('\${str_add_rhs_tmp}')
4560 }
4561 }
4562 }
4563 }
4564}
4565")
4566 mut main_fn := ast.FnDecl{}
4567 mut found_main := false
4568 for file in files {
4569 for stmt in file.stmts {
4570 if stmt is ast.FnDecl && stmt.name == 'main' {
4571 main_fn = stmt
4572 found_main = true
4573 }
4574 }
4575 }
4576 assert found_main
4577 mut capture_decl_count := 0
4578 for stmt in main_fn.stmts {
4579 if stmt is ast.AssignStmt && stmt.op == .decl_assign && stmt.lhs.len > 0
4580 && stmt.lhs[0] is ast.Ident && (stmt.lhs[0] as ast.Ident).name.starts_with('_defer_cap') {
4581 capture_decl_count++
4582 }
4583 }
4584 assert capture_decl_count == 2
4585}
4586
4587fn test_transform_lowers_static_method_struct_shorthand_args() {
4588 files := transform_code_for_test('
4589struct Walker {
4590 table int
4591 count int
4592}
4593
4594fn Walker.new(params Walker) &Walker {
4595 _ = params
4596 return &Walker{}
4597}
4598
4599fn main() {
4600 table := 1
4601 _ = Walker.new(table: table, count: 2)
4602}
4603')
4604 mut found := false
4605 for file in files {
4606 for stmt in file.stmts {
4607 if stmt is ast.FnDecl && stmt.name == 'main' {
4608 for body_stmt in stmt.stmts {
4609 if body_stmt is ast.AssignStmt && body_stmt.rhs.len == 1
4610 && body_stmt.rhs[0] is ast.CallExpr {
4611 call := body_stmt.rhs[0] as ast.CallExpr
4612 if call.lhs is ast.Ident && (call.lhs as ast.Ident).name == 'Walker__new' {
4613 found = true
4614 assert call.args.len == 1
4615 assert call.args[0] is ast.InitExpr
4616 }
4617 }
4618 }
4619 }
4620 }
4621 }
4622 assert found
4623}
4624
4625fn test_explicit_as_cast_survives_smartcasted_selector_call_expr() {
4626 files := transform_code_for_test('
4627type Expr = ArrayDecompose | Ident
4628
4629struct ArrayDecompose {}
4630
4631struct Ident {}
4632
4633struct CallArg {
4634 expr Expr
4635}
4636
4637fn last(args []CallArg) CallArg {
4638 return args[0]
4639}
4640
4641fn f(args []CallArg) {
4642 if last(args).expr is ArrayDecompose {
4643 array_decompose := last(args).expr as ArrayDecompose
4644 _ = array_decompose
4645 }
4646}
4647')
4648 mut found := false
4649 for file in files {
4650 for stmt in file.stmts {
4651 if stmt is ast.FnDecl && stmt.name == 'f' {
4652 for body_stmt in stmt.stmts {
4653 if body_stmt is ast.ExprStmt && body_stmt.expr is ast.IfExpr {
4654 if_expr := body_stmt.expr as ast.IfExpr
4655 for inner in if_expr.stmts {
4656 if inner is ast.AssignStmt && inner.op == .decl_assign
4657 && inner.lhs.len == 1 && inner.lhs[0] is ast.Ident
4658 && (inner.lhs[0] as ast.Ident).name == 'array_decompose' {
4659 found = true
4660 assert inner.rhs.len == 1
4661 assert inner.rhs[0] is ast.AsCastExpr
4662 }
4663 }
4664 }
4665 }
4666 }
4667 }
4668 }
4669 assert found
4670}
4671
4672fn test_transform_lowers_index_parsed_generic_method_call() {
4673 files := transform_code_for_test('
4674struct File {}
4675
4676struct PoolProcessor {}
4677
4678fn (p &PoolProcessor) get_item[T](idx int) T {
4679 _ = p
4680 _ = idx
4681 return T(0)
4682}
4683
4684fn main() {
4685 p := &PoolProcessor{}
4686 file := p.get_item[&File](0)
4687 _ = file
4688}
4689')
4690 mut found := false
4691 for file in files {
4692 for stmt in file.stmts {
4693 if stmt is ast.FnDecl && stmt.name == 'main' {
4694 for body_stmt in stmt.stmts {
4695 if body_stmt is ast.AssignStmt && body_stmt.rhs.len == 1
4696 && body_stmt.rhs[0] is ast.CallExpr {
4697 call := body_stmt.rhs[0] as ast.CallExpr
4698 if call.lhs is ast.Ident
4699 && (call.lhs as ast.Ident).name == 'PoolProcessor__get_item_T_Fileptr' {
4700 found = true
4701 assert call.args.len == 2
4702 assert call.args[0].name() == 'p'
4703 assert call.args[1].name() == '0'
4704 }
4705 }
4706 }
4707 }
4708 }
4709 }
4710 assert found
4711}
4712
4713// Create a rune-like type that returns 'rune' from name()
4714fn rune_type() types.Type {
4715 return types.Alias{
4716 name: 'rune'
4717 }
4718}
4719
4720fn test_transform_comptime_embed_file_call_or_cast_expr_to_init_expr() {
4721 raw_dir := os.join_path(os.temp_dir(), 'v2_transformer_embed_file_${os.getpid()}')
4722 os.mkdir_all(raw_dir) or { panic(err) }
4723 tmp_dir := os.real_path(raw_dir)
4724 defer {
4725 os.rmdir_all(tmp_dir) or {}
4726 }
4727 source_path := os.join_path(tmp_dir, 'main.v')
4728 asset_path := os.join_path(tmp_dir, 'asset.txt')
4729 os.write_file(source_path, 'fn main() {}') or { panic(err) }
4730 os.write_file(asset_path, 'hello') or { panic(err) }
4731
4732 mut t := create_test_transformer()
4733 t.cur_file_name = source_path
4734
4735 result := t.transform_comptime_expr(ast.ComptimeExpr{
4736 expr: ast.Expr(ast.CallOrCastExpr{
4737 lhs: ast.Expr(ast.Ident{
4738 name: 'embed_file'
4739 })
4740 expr: ast.Expr(ast.StringLiteral{
4741 kind: .v
4742 value: "'asset.txt'"
4743 })
4744 })
4745 })
4746
4747 assert t.needed_embed_file_helper
4748 assert result is ast.InitExpr, 'expected InitExpr, got ${result.type_name()}'
4749 init := result as ast.InitExpr
4750 assert init.typ is ast.Ident
4751 assert (init.typ as ast.Ident).name == embed_file_helper_type_name
4752 assert init.fields.len == 4
4753 assert init.fields[0].name == '_data'
4754 assert init.fields[0].value is ast.StringLiteral
4755 assert (init.fields[0].value as ast.StringLiteral).value == "'hello'"
4756 assert init.fields[1].name == 'len'
4757 assert init.fields[1].value is ast.BasicLiteral
4758 assert (init.fields[1].value as ast.BasicLiteral).value == '5'
4759 assert init.fields[2].name == 'path'
4760 assert init.fields[3].name == 'apath'
4761 assert init.fields[3].value is ast.StringLiteral
4762 assert (init.fields[3].value as ast.StringLiteral).value == quote_v_string_literal(asset_path)
4763}
4764
4765fn test_transform_comptime_embed_file_chained_method_call() {
4766 raw_dir := os.join_path(os.temp_dir(), 'v2_transformer_embed_file_chain_${os.getpid()}')
4767 os.mkdir_all(raw_dir) or { panic(err) }
4768 tmp_dir := os.real_path(raw_dir)
4769 defer {
4770 os.rmdir_all(tmp_dir) or {}
4771 }
4772 source_path := os.join_path(tmp_dir, 'main.v')
4773 asset_path := os.join_path(tmp_dir, 'asset.txt')
4774 os.write_file(source_path, 'fn main() {}') or { panic(err) }
4775 os.write_file(asset_path, 'hello') or { panic(err) }
4776
4777 mut t := create_test_transformer()
4778 t.cur_file_name = source_path
4779
4780 result := t.transform_comptime_expr(ast.ComptimeExpr{
4781 expr: ast.Expr(ast.CallExpr{
4782 lhs: ast.Expr(ast.SelectorExpr{
4783 lhs: ast.Expr(ast.CallOrCastExpr{
4784 lhs: ast.Expr(ast.Ident{
4785 name: 'embed_file'
4786 })
4787 expr: ast.Expr(ast.StringLiteral{
4788 kind: .v
4789 value: "'asset.txt'"
4790 })
4791 })
4792 rhs: ast.Ident{
4793 name: 'to_bytes'
4794 }
4795 })
4796 args: []
4797 })
4798 })
4799
4800 assert t.needed_embed_file_helper
4801 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
4802 call := result as ast.CallExpr
4803 assert call.lhs is ast.SelectorExpr
4804 sel := call.lhs as ast.SelectorExpr
4805 assert sel.lhs is ast.InitExpr
4806 init := sel.lhs as ast.InitExpr
4807 assert init.typ is ast.Ident
4808 assert (init.typ as ast.Ident).name == embed_file_helper_type_name
4809 assert sel.rhs.name == 'to_bytes'
4810 assert init.fields.len == 4
4811 assert init.fields[0].value is ast.StringLiteral
4812 assert (init.fields[0].value as ast.StringLiteral).value == "'hello'"
4813 assert init.fields[3].value is ast.StringLiteral
4814 assert (init.fields[3].value as ast.StringLiteral).value == quote_v_string_literal(asset_path)
4815}
4816
4817fn test_inject_embed_file_helper_adds_builtin_helper_once() {
4818 mut t := create_test_transformer()
4819 mut files := [
4820 ast.File{
4821 mod: 'builtin'
4822 name: 'builtin.v'
4823 stmts: [
4824 ast.Stmt(ast.StructDecl{
4825 name: 'Existing'
4826 }),
4827 ]
4828 },
4829 ast.File{
4830 mod: 'main'
4831 name: 'main.v'
4832 },
4833 ]
4834
4835 t.inject_embed_file_helper(mut files)
4836 t.inject_embed_file_helper(mut files)
4837
4838 assert files[0].stmts.len == 7
4839 assert files[0].stmts[1] is ast.StructDecl
4840 assert (files[0].stmts[1] as ast.StructDecl).name == embed_file_helper_type_name
4841 mut helper_count := 0
4842 for stmt in files[0].stmts {
4843 if stmt is ast.StructDecl && stmt.name == embed_file_helper_type_name {
4844 helper_count++
4845 }
4846 }
4847 assert helper_count == 1
4848}
4849
4850fn test_transform_ident_vmodroot_to_string_literal() {
4851 mut t := create_test_transformer()
4852 t.comptime_vmodroot = '/tmp/v'
4853 result := t.transform_expr(ast.Ident{
4854 name: '@VMODROOT'
4855 })
4856 assert result is ast.StringLiteral, 'expected StringLiteral, got ${result.type_name()}'
4857 lit := result as ast.StringLiteral
4858 assert lit.kind == .v
4859 assert lit.value == "'/tmp/v'"
4860}
4861
4862fn test_transform_ident_vmodroot_empty_root() {
4863 mut t := create_test_transformer()
4864 t.comptime_vmodroot = ''
4865 result := t.transform_expr(ast.Ident{
4866 name: '@VMODROOT'
4867 })
4868 assert result is ast.StringLiteral, 'expected StringLiteral, got ${result.type_name()}'
4869 lit := result as ast.StringLiteral
4870 assert lit.kind == .v
4871 assert lit.value == "''"
4872}
4873
4874fn test_transform_autogenerates_clone_helpers_for_iclone_structs() {
4875 files := transform_code_for_test('
4876interface IClone {}
4877
4878struct Inner implements IClone {
4879 name string
4880}
4881
4882struct Outer implements IClone {
4883 inner Inner
4884 nums []int
4885}
4886
4887fn use_clone(value Outer) Outer {
4888 return value.clone()
4889}
4890')
4891 assert files.len == 1
4892 file := files[0]
4893 mut saw_inner_clone := false
4894 mut saw_outer_clone := false
4895 mut saw_lowered_call := false
4896 for stmt in file.stmts {
4897 if stmt is ast.FnDecl && stmt.name == 'use_clone' {
4898 assert stmt.stmts.len == 1
4899 ret := stmt.stmts[0] as ast.ReturnStmt
4900 assert ret.exprs.len == 1
4901 assert ret.exprs[0] is ast.CallExpr
4902 call := ret.exprs[0] as ast.CallExpr
4903 assert call.lhs is ast.Ident
4904 assert (call.lhs as ast.Ident).name == 'Outer__clone'
4905 saw_lowered_call = true
4906 }
4907 if stmt is ast.FnDecl && stmt.name == 'Inner__clone' {
4908 saw_inner_clone = true
4909 }
4910 if stmt is ast.FnDecl && stmt.name == 'Outer__clone' {
4911 saw_outer_clone = true
4912 assert stmt.stmts.len == 1
4913 ret := stmt.stmts[0] as ast.ReturnStmt
4914 assert ret.exprs.len == 1
4915 assert ret.exprs[0] is ast.InitExpr
4916 init := ret.exprs[0] as ast.InitExpr
4917 assert init.fields.len == 2
4918 assert init.fields[0].name == 'inner'
4919 assert init.fields[0].value is ast.CallExpr
4920 inner_call := init.fields[0].value as ast.CallExpr
4921 assert inner_call.lhs is ast.Ident
4922 assert (inner_call.lhs as ast.Ident).name == 'Inner__clone'
4923 assert init.fields[1].name == 'nums'
4924 assert init.fields[1].value is ast.CallExpr
4925 nums_call := init.fields[1].value as ast.CallExpr
4926 assert nums_call.lhs is ast.Ident
4927 assert (nums_call.lhs as ast.Ident).name == 'array__clone'
4928 }
4929 }
4930 assert saw_lowered_call
4931 assert saw_inner_clone
4932 assert saw_outer_clone
4933}
4934
4935fn test_transform_autogenerates_chained_clone_helpers_from_snapshot() {
4936 files := transform_code_for_test('
4937interface IClone {}
4938
4939struct Leaf implements IClone {
4940 name string
4941}
4942
4943struct Branch implements IClone {
4944 leaf Leaf
4945}
4946
4947struct Root implements IClone {
4948 branch Branch
4949}
4950
4951fn use_clone(value Root) Root {
4952 return value.clone()
4953}
4954')
4955 assert files.len == 1
4956 file := files[0]
4957 mut clone_names := map[string]bool{}
4958 for stmt in file.stmts {
4959 if stmt is ast.FnDecl {
4960 clone_names[stmt.name] = true
4961 }
4962 }
4963 assert clone_names['Root__clone']
4964 assert clone_names['Branch__clone']
4965 assert clone_names['Leaf__clone']
4966}
4967
4968fn test_array_comparison_eq() {
4969 // Set up variable types so get_array_type_str can detect them
4970 mut t := create_transformer_with_vars({
4971 'arr1': types.Type(types.Array{ elem_type: types.int_ })
4972 'arr2': types.Type(types.Array{
4973 elem_type: types.int_
4974 })
4975 })
4976
4977 // Create: arr1 == arr2
4978 expr := ast.InfixExpr{
4979 op: .eq
4980 lhs: ast.Ident{
4981 name: 'arr1'
4982 }
4983 rhs: ast.Ident{
4984 name: 'arr2'
4985 }
4986 }
4987
4988 result := t.transform_infix_expr(expr)
4989
4990 // Should be transformed to: array__eq(arr1, arr2)
4991 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
4992 call := result as ast.CallExpr
4993 assert call.lhs is ast.Ident
4994 call_name := (call.lhs as ast.Ident).name
4995 assert call_name == 'array__eq', 'expected array__eq, got ${call_name}'
4996 assert call.args.len == 2
4997}
4998
4999fn test_array_comparison_ne() {
5000 mut t := create_transformer_with_vars({
5001 'arr1': types.Type(types.Array{ elem_type: types.int_ })
5002 'arr2': types.Type(types.Array{
5003 elem_type: types.int_
5004 })
5005 })
5006
5007 // Create: arr1 != arr2
5008 expr := ast.InfixExpr{
5009 op: .ne
5010 lhs: ast.Ident{
5011 name: 'arr1'
5012 }
5013 rhs: ast.Ident{
5014 name: 'arr2'
5015 }
5016 }
5017
5018 result := t.transform_infix_expr(expr)
5019
5020 // Should be transformed to: !array__eq(arr1, arr2)
5021 assert result is ast.PrefixExpr, 'expected PrefixExpr, got ${result.type_name()}'
5022 prefix := result as ast.PrefixExpr
5023 assert prefix.op == .not
5024 assert prefix.expr is ast.CallExpr
5025 call := prefix.expr as ast.CallExpr
5026 call_name := (call.lhs as ast.Ident).name
5027 assert call_name == 'array__eq', 'expected array__eq, got ${call_name}'
5028}
5029
5030fn test_array_comparison_non_array_passthrough() {
5031 // Variables with non-array types
5032 mut t := create_transformer_with_vars({
5033 'x': types.Type(types.int_)
5034 'y': types.Type(types.int_)
5035 })
5036
5037 // Create: x == y (non-array comparison)
5038 expr := ast.InfixExpr{
5039 op: .eq
5040 lhs: ast.Ident{
5041 name: 'x'
5042 }
5043 rhs: ast.Ident{
5044 name: 'y'
5045 }
5046 }
5047
5048 result := t.transform_infix_expr(expr)
5049
5050 // Should remain as InfixExpr (not transformed)
5051 assert result is ast.InfixExpr, 'expected InfixExpr for non-array comparison'
5052}
5053
5054fn test_transform_index_expr_string_slice_lowered() {
5055 mut t := create_transformer_with_vars({
5056 's': types.Type(string_type())
5057 })
5058
5059 expr := ast.IndexExpr{
5060 lhs: ast.Ident{
5061 name: 's'
5062 }
5063 expr: ast.RangeExpr{
5064 op: .dotdot
5065 start: ast.BasicLiteral{
5066 kind: .number
5067 value: '1'
5068 }
5069 end: ast.BasicLiteral{
5070 kind: .number
5071 value: '3'
5072 }
5073 }
5074 }
5075
5076 result := t.transform_expr(expr)
5077 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5078 call := result as ast.CallExpr
5079 assert call.lhs is ast.Ident
5080 assert (call.lhs as ast.Ident).name == 'string__substr'
5081 assert call.args.len == 3
5082}
5083
5084fn test_transform_index_expr_nested_selector_string_slice_lowered() {
5085 scanner_type := types.Type(types.Struct{
5086 name: 'scanner__Scanner'
5087 fields: [
5088 types.Field{
5089 name: 'src'
5090 typ: types.Type(string_type())
5091 },
5092 ]
5093 })
5094 parser_type := types.Type(types.Struct{
5095 name: 'parser__Parser'
5096 fields: [
5097 types.Field{
5098 name: 'scanner'
5099 typ: types.Type(types.Pointer{
5100 base_type: scanner_type
5101 })
5102 },
5103 ]
5104 })
5105 mut t := create_transformer_with_vars({
5106 'p': types.Type(types.Pointer{
5107 base_type: parser_type
5108 })
5109 })
5110
5111 expr := ast.IndexExpr{
5112 lhs: ast.SelectorExpr{
5113 lhs: ast.SelectorExpr{
5114 lhs: ast.Ident{
5115 name: 'p'
5116 }
5117 rhs: ast.Ident{
5118 name: 'scanner'
5119 }
5120 }
5121 rhs: ast.Ident{
5122 name: 'src'
5123 }
5124 }
5125 expr: ast.RangeExpr{
5126 op: .dotdot
5127 start: ast.BasicLiteral{
5128 kind: .number
5129 value: '1'
5130 }
5131 end: ast.BasicLiteral{
5132 kind: .number
5133 value: '3'
5134 }
5135 }
5136 }
5137
5138 result := t.transform_expr(expr)
5139 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5140 call := result as ast.CallExpr
5141 assert call.lhs is ast.Ident
5142 assert (call.lhs as ast.Ident).name == 'string__substr'
5143 assert call.args.len == 3
5144}
5145
5146fn test_transform_index_expr_imported_nested_selector_string_slice_lowered() {
5147 files := transform_sources_for_test([
5148 TestSource{
5149 rel: 'v2/scanner/scanner.v'
5150 code: 'module scanner
5151
5152pub struct Scanner {
5153pub mut:
5154 src string
5155 pos int
5156}
5157'
5158 },
5159 TestSource{
5160 rel: 'v2/parser/parser.v'
5161 code: 'module parser
5162
5163import v2.scanner
5164
5165pub struct Parser {
5166mut:
5167 scanner &scanner.Scanner
5168}
5169
5170fn (mut p Parser) directive() string {
5171 start := 0
5172 end := p.scanner.src.len
5173 return p.scanner.src[start..end]
5174}
5175'
5176 },
5177 ])
5178 mut found_directive := false
5179 for file in files {
5180 for stmt in file.stmts {
5181 if stmt is ast.FnDecl && stmt.name == 'directive' {
5182 found_directive = true
5183 assert find_call_with_lhs_suffix_in_stmts(stmt.stmts, 'string__substr') != none
5184 assert find_call_with_lhs_suffix_in_stmts(stmt.stmts, 'array__slice') == none
5185 }
5186 }
5187 }
5188 assert found_directive
5189}
5190
5191fn test_transform_selector_expr_on_nested_selector_string_slice_lowers_slice_lhs() {
5192 scanner_type := types.Type(types.Struct{
5193 name: 'scanner__Scanner'
5194 fields: [
5195 types.Field{
5196 name: 'src'
5197 typ: types.Type(string_type())
5198 },
5199 ]
5200 })
5201 parser_type := types.Type(types.Struct{
5202 name: 'parser__Parser'
5203 fields: [
5204 types.Field{
5205 name: 'scanner'
5206 typ: types.Type(types.Pointer{
5207 base_type: scanner_type
5208 })
5209 },
5210 ]
5211 })
5212 mut t := create_transformer_with_vars({
5213 'p': types.Type(types.Pointer{
5214 base_type: parser_type
5215 })
5216 })
5217
5218 expr := ast.SelectorExpr{
5219 lhs: ast.IndexExpr{
5220 lhs: ast.SelectorExpr{
5221 lhs: ast.SelectorExpr{
5222 lhs: ast.Ident{
5223 name: 'p'
5224 }
5225 rhs: ast.Ident{
5226 name: 'scanner'
5227 }
5228 }
5229 rhs: ast.Ident{
5230 name: 'src'
5231 }
5232 }
5233 expr: ast.RangeExpr{
5234 op: .dotdot
5235 start: ast.BasicLiteral{
5236 kind: .number
5237 value: '1'
5238 }
5239 end: ast.BasicLiteral{
5240 kind: .number
5241 value: '3'
5242 }
5243 }
5244 }
5245 rhs: ast.Ident{
5246 name: 'trim_space'
5247 }
5248 }
5249
5250 result := t.transform_expr(expr)
5251 assert result is ast.SelectorExpr, 'expected SelectorExpr, got ${result.type_name()}'
5252 sel := result as ast.SelectorExpr
5253 assert sel.lhs is ast.CallExpr, 'expected transformed slice CallExpr, got ${sel.lhs.type_name()}'
5254 call := sel.lhs as ast.CallExpr
5255 assert call.lhs is ast.Ident
5256 assert (call.lhs as ast.Ident).name == 'string__substr'
5257}
5258
5259fn test_transform_selector_expr_string_slice_uses_fn_root_scope_fallback() {
5260 full_scanner_type := types.Type(types.Struct{
5261 name: 'scanner__Scanner'
5262 fields: [
5263 types.Field{
5264 name: 'src'
5265 typ: types.Type(string_type())
5266 },
5267 ]
5268 })
5269 stale_scanner_type := types.Type(types.Struct{
5270 name: 'scanner__Scanner'
5271 })
5272 parser_type := types.Type(types.Struct{
5273 name: 'parser__Parser'
5274 fields: [
5275 types.Field{
5276 name: 'scanner'
5277 typ: types.Type(types.Pointer{
5278 base_type: stale_scanner_type
5279 })
5280 },
5281 ]
5282 })
5283 mut root_scope := types.new_scope(unsafe { nil })
5284 root_scope.insert('p', types.Type(types.Pointer{
5285 base_type: parser_type
5286 }))
5287 mut scanner_scope := types.new_scope(unsafe { nil })
5288 scanner_scope.insert('Scanner', types.TypeObject{
5289 typ: full_scanner_type
5290 })
5291 mut t := create_test_transformer()
5292 t.fn_root_scope = root_scope
5293 t.scope = types.new_scope(unsafe { nil })
5294 t.cached_scopes = {
5295 'scanner': scanner_scope
5296 }
5297
5298 expr := ast.SelectorExpr{
5299 lhs: ast.IndexExpr{
5300 lhs: ast.SelectorExpr{
5301 lhs: ast.SelectorExpr{
5302 lhs: ast.Ident{
5303 name: 'p'
5304 }
5305 rhs: ast.Ident{
5306 name: 'scanner'
5307 }
5308 }
5309 rhs: ast.Ident{
5310 name: 'src'
5311 }
5312 }
5313 expr: ast.RangeExpr{
5314 op: .dotdot
5315 start: ast.BasicLiteral{
5316 kind: .number
5317 value: '1'
5318 }
5319 end: ast.BasicLiteral{
5320 kind: .number
5321 value: '3'
5322 }
5323 }
5324 }
5325 rhs: ast.Ident{
5326 name: 'trim_space'
5327 }
5328 }
5329
5330 result := t.transform_expr(expr)
5331 assert result is ast.SelectorExpr, 'expected SelectorExpr, got ${result.type_name()}'
5332 sel := result as ast.SelectorExpr
5333 assert sel.lhs is ast.CallExpr, 'expected transformed slice CallExpr, got ${sel.lhs.type_name()}'
5334 call := sel.lhs as ast.CallExpr
5335 assert call.lhs is ast.Ident
5336 assert (call.lhs as ast.Ident).name == 'string__substr'
5337}
5338
5339fn test_transform_index_expr_string_call_slice_open_ended_uses_max_int() {
5340 mut env := &types.Environment{}
5341 env.set_expr_type(1, types.string_)
5342 mut t := &Transformer{
5343 pref: &vpref.Preferences{}
5344 env: unsafe { env }
5345 needed_array_contains_fns: map[string]ArrayMethodInfo{}
5346 needed_array_index_fns: map[string]ArrayMethodInfo{}
5347 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
5348 }
5349
5350 expr := ast.IndexExpr{
5351 lhs: ast.CallExpr{
5352 lhs: ast.Ident{
5353 name: 'get_type'
5354 }
5355 pos: token.Pos{
5356 id: 1
5357 }
5358 }
5359 expr: ast.RangeExpr{
5360 op: .dotdot
5361 start: ast.BasicLiteral{
5362 kind: .number
5363 value: '2'
5364 }
5365 end: ast.empty_expr
5366 }
5367 }
5368
5369 result := t.transform_expr(expr)
5370 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5371 call := result as ast.CallExpr
5372 assert call.lhs is ast.Ident
5373 assert (call.lhs as ast.Ident).name == 'string__substr'
5374 assert call.args.len == 3
5375 assert call.args[0] is ast.CallExpr
5376 assert call.args[2] is ast.BasicLiteral
5377 end := call.args[2] as ast.BasicLiteral
5378 assert end.kind == .number
5379 assert end.value == '2147483647'
5380}
5381
5382fn test_transform_index_expr_array_slice_lowered() {
5383 mut t := create_transformer_with_vars({
5384 'arr': types.Type(types.Array{ elem_type: types.int_ })
5385 })
5386
5387 expr := ast.IndexExpr{
5388 lhs: ast.Ident{
5389 name: 'arr'
5390 }
5391 expr: ast.RangeExpr{
5392 op: .ellipsis
5393 start: ast.BasicLiteral{
5394 kind: .number
5395 value: '0'
5396 }
5397 end: ast.BasicLiteral{
5398 kind: .number
5399 value: '4'
5400 }
5401 }
5402 }
5403
5404 result := t.transform_expr(expr)
5405 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5406 call := result as ast.CallExpr
5407 assert call.lhs is ast.Ident
5408 assert (call.lhs as ast.Ident).name == 'array__slice'
5409 assert call.args.len == 3
5410 // Inclusive range `...` should become end + 1.
5411 assert call.args[2] is ast.InfixExpr
5412}
5413
5414fn test_transform_index_expr_open_ended_array_slice_uses_slice_ni() {
5415 mut t := create_transformer_with_vars({
5416 'arr': types.Type(types.Array{ elem_type: types.string_ })
5417 })
5418
5419 expr := ast.IndexExpr{
5420 lhs: ast.Ident{
5421 name: 'arr'
5422 }
5423 expr: ast.RangeExpr{
5424 op: .dotdot
5425 start: ast.BasicLiteral{
5426 kind: .number
5427 value: '1'
5428 }
5429 end: ast.empty_expr
5430 }
5431 }
5432
5433 result := t.transform_expr(expr)
5434 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5435 call := result as ast.CallExpr
5436 assert call.lhs is ast.Ident
5437 assert (call.lhs as ast.Ident).name == 'array__slice_ni'
5438 assert call.args.len == 3
5439 assert call.args[2] is ast.SelectorExpr
5440}
5441
5442fn test_transform_index_expr_fixed_array_slice_uses_start_address_and_length() {
5443 mut t := create_transformer_with_vars({
5444 'f': types.Type(types.ArrayFixed{
5445 len: 5
5446 elem_type: types.int_
5447 })
5448 })
5449
5450 expr := ast.IndexExpr{
5451 lhs: ast.Ident{
5452 name: 'f'
5453 }
5454 expr: ast.RangeExpr{
5455 op: .dotdot
5456 start: ast.BasicLiteral{
5457 kind: .number
5458 value: '1'
5459 }
5460 end: ast.BasicLiteral{
5461 kind: .number
5462 value: '4'
5463 }
5464 }
5465 }
5466
5467 result := t.transform_expr(expr)
5468 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5469 call := result as ast.CallExpr
5470 assert call.lhs is ast.Ident
5471 assert (call.lhs as ast.Ident).name == 'new_array_from_c_array'
5472 assert call.args.len == 4
5473 assert call.args[0] is ast.InfixExpr
5474 len_expr := call.args[0] as ast.InfixExpr
5475 assert len_expr.op == .minus
5476 assert len_expr.lhs is ast.BasicLiteral
5477 assert (len_expr.lhs as ast.BasicLiteral).value == '4'
5478 assert len_expr.rhs is ast.BasicLiteral
5479 assert (len_expr.rhs as ast.BasicLiteral).value == '1'
5480 assert call.args[1] is ast.InfixExpr
5481 assert call.args[3] is ast.PrefixExpr
5482 ptr_expr := call.args[3] as ast.PrefixExpr
5483 assert ptr_expr.op == .amp
5484 assert ptr_expr.expr is ast.IndexExpr
5485 index_expr := ptr_expr.expr as ast.IndexExpr
5486 assert index_expr.lhs is ast.Ident
5487 assert (index_expr.lhs as ast.Ident).name == 'f'
5488 assert index_expr.expr is ast.BasicLiteral
5489 assert (index_expr.expr as ast.BasicLiteral).value == '1'
5490}
5491
5492fn test_transform_call_or_cast_expr_array_contains_fixed_array() {
5493 mut t := create_transformer_with_vars({
5494 'a': types.Type(types.ArrayFixed{
5495 len: 3
5496 elem_type: types.int_
5497 })
5498 })
5499 expr := ast.CallOrCastExpr{
5500 lhs: ast.SelectorExpr{
5501 lhs: ast.Ident{
5502 name: 'a'
5503 }
5504 rhs: ast.Ident{
5505 name: 'contains'
5506 }
5507 }
5508 expr: ast.BasicLiteral{
5509 kind: .number
5510 value: '2'
5511 }
5512 }
5513 result := t.transform_call_or_cast_expr(expr)
5514 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5515 call := result as ast.CallExpr
5516 assert call.lhs is ast.Ident
5517 assert (call.lhs as ast.Ident).name == 'Array_fixed_int_3_contains'
5518 assert call.args.len == 2
5519 assert call.args[0] is ast.Ident
5520 assert (call.args[0] as ast.Ident).name == 'a'
5521 assert 'Array_fixed_int_3_contains' in t.needed_array_contains_fns
5522}
5523
5524fn test_transform_prefers_declared_array_receiver_contains_method() {
5525 files := transform_code_for_test('
5526struct Attr {
5527 name string
5528}
5529
5530fn (attrs []Attr) contains(str string) bool {
5531 _ = attrs
5532 _ = str
5533 return true
5534}
5535
5536fn has_typedef(attrs []Attr) bool {
5537 return attrs.contains("typedef")
5538}
5539')
5540 mut found := false
5541 for file in files {
5542 for stmt in file.stmts {
5543 if stmt is ast.FnDecl && stmt.name == 'has_typedef' {
5544 assert stmt.stmts.len == 1
5545 assert stmt.stmts[0] is ast.ReturnStmt
5546 ret := stmt.stmts[0] as ast.ReturnStmt
5547 assert ret.exprs.len == 1
5548 assert ret.exprs[0] is ast.CallExpr
5549 call := ret.exprs[0] as ast.CallExpr
5550 assert call.lhs is ast.Ident
5551 assert (call.lhs as ast.Ident).name == 'Array_Attr__contains'
5552 assert call.args.len == 2
5553 found = true
5554 }
5555 }
5556 }
5557 assert found
5558}
5559
5560fn test_transform_call_expr_array_contains_fixed_array() {
5561 mut t := create_transformer_with_vars({
5562 'a': types.Type(types.ArrayFixed{
5563 len: 3
5564 elem_type: types.int_
5565 })
5566 })
5567 expr := ast.CallExpr{
5568 lhs: ast.Ident{
5569 name: 'array__contains'
5570 }
5571 args: [
5572 ast.Expr(ast.Ident{
5573 name: 'a'
5574 }),
5575 ast.Expr(ast.PrefixExpr{
5576 op: .amp
5577 expr: ast.BasicLiteral{
5578 kind: .number
5579 value: '2'
5580 }
5581 }),
5582 ]
5583 }
5584 result := t.transform_call_expr(expr)
5585 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5586 call := result as ast.CallExpr
5587 assert call.lhs is ast.Ident
5588 assert (call.lhs as ast.Ident).name == 'Array_fixed_int_3_contains'
5589 assert call.args.len == 2
5590 assert call.args[1] is ast.BasicLiteral
5591 assert 'Array_fixed_int_3_contains' in t.needed_array_contains_fns
5592}
5593
5594fn test_generate_array_method_elem_expr_registers_elem_type() {
5595 mut t := create_test_transformer()
5596 elem_expr := t.generate_array_method_elem_expr(ArrayMethodInfo{
5597 array_type: 'Array_string'
5598 elem_type: 'string'
5599 }, ast.Expr(ast.Ident{
5600 name: 'i'
5601 }))
5602 assert elem_expr is ast.IndexExpr
5603 assert elem_expr.pos().id < 0
5604 elem_type := t.synth_types[elem_expr.pos().id] or {
5605 assert false, 'expected synthesized element type for array helper index expr'
5606 return
5607 }
5608 assert t.type_to_c_name(elem_type) == 'string'
5609}
5610
5611fn test_type_to_c_decl_name_preserves_float_primitives() {
5612 t := create_test_transformer()
5613 assert t.type_to_c_decl_name(types.Type(types.Primitive{
5614 props: .float
5615 size: 32
5616 })) == 'f32'
5617 assert t.type_to_c_decl_name(types.Type(types.f64_)) == 'f64'
5618 assert t.type_to_c_decl_name(types.Type(types.Pointer{
5619 base_type: types.Type(types.f64_)
5620 })) == 'f64*'
5621}
5622
5623fn test_resolve_expr_with_expected_type_resolves_enum_shorthand() {
5624 mut t := create_test_transformer()
5625 resolved := t.resolve_expr_with_expected_type(ast.Expr(ast.SelectorExpr{
5626 lhs: ast.empty_expr
5627 rhs: ast.Ident{
5628 name: 'v'
5629 }
5630 }), types.Type(types.Enum{
5631 name: 'ast__StringLiteralKind'
5632 }))
5633 assert resolved is ast.Ident
5634 assert (resolved as ast.Ident).name == 'ast__StringLiteralKind__v'
5635}
5636
5637fn test_resolve_expr_with_expected_type_casts_none_to_option() {
5638 mut t := create_test_transformer()
5639 expected := types.Type(types.OptionType{
5640 base_type: types.Type(types.Array{
5641 elem_type: types.Type(types.u8_)
5642 })
5643 })
5644 resolved := t.resolve_expr_with_expected_type(ast.Expr(ast.Type(ast.NoneType{})), expected)
5645 assert resolved is ast.CastExpr
5646 cast := resolved as ast.CastExpr
5647 assert cast.expr is ast.Type
5648 assert cast.typ is ast.Type
5649 assert cast.typ as ast.Type is ast.OptionType
5650}
5651
5652fn test_transform_return_match_branch_resolves_enum_shorthand_to_return_type() {
5653 enum_typ := types.Type(types.Enum{
5654 name: 'CompletionType'
5655 })
5656 mut scope := types.new_scope(unsafe { nil })
5657 scope.insert('CompletionType', enum_typ)
5658 mut t := create_test_transformer()
5659 t.cur_module = 'main'
5660 t.cur_fn_ret_type_name = 'CompletionType'
5661 t.preserve_match_branch_value = true
5662 t.cached_scopes = {
5663 'main': scope
5664 }
5665 stmts := [
5666 ast.Stmt(ast.ExprStmt{
5667 expr: ast.Expr(ast.SelectorExpr{
5668 lhs: ast.empty_expr
5669 rhs: ast.Ident{
5670 name: 'encoding'
5671 }
5672 })
5673 }),
5674 ]
5675 transformed := t.transform_match_branch_stmts(stmts)
5676 assert transformed.len == 1
5677 assert transformed[0] is ast.ExprStmt
5678 expr := (transformed[0] as ast.ExprStmt).expr
5679 assert expr is ast.Ident
5680 assert (expr as ast.Ident).name == 'CompletionType__encoding'
5681}
5682
5683fn test_transform_selector_enum_uses_declared_parent_owner() {
5684 enum_typ := types.Type(types.Enum{
5685 name: 'StrIntpType'
5686 })
5687 mut builtin_scope := types.new_scope(unsafe { nil })
5688 builtin_scope.insert('StrIntpType', enum_typ)
5689 mut c_scope := types.new_scope(builtin_scope)
5690 mut t := create_test_transformer()
5691 t.cur_module = 'c'
5692 t.cached_scopes = {
5693 'builtin': builtin_scope
5694 'c': c_scope
5695 }
5696 result := t.transform_expr(ast.SelectorExpr{
5697 lhs: ast.Expr(ast.Ident{
5698 name: 'StrIntpType'
5699 })
5700 rhs: ast.Ident{
5701 name: 'si_no_str'
5702 }
5703 })
5704 assert result is ast.Ident
5705 assert (result as ast.Ident).name == 'StrIntpType__si_no_str'
5706}
5707
5708fn test_resolve_enum_shorthand_uses_declared_parent_owner() {
5709 enum_typ := types.Type(types.Enum{
5710 name: 'ChanState'
5711 })
5712 mut builtin_scope := types.new_scope(unsafe { nil })
5713 builtin_scope.insert('ChanState', enum_typ)
5714 mut sync_scope := types.new_scope(builtin_scope)
5715 mut t := create_test_transformer()
5716 t.cur_module = 'sync'
5717 t.cached_scopes = {
5718 'builtin': builtin_scope
5719 'sync': sync_scope
5720 }
5721 resolved := t.resolve_enum_shorthand(ast.Expr(ast.SelectorExpr{
5722 lhs: ast.empty_expr
5723 rhs: ast.Ident{
5724 name: 'closed'
5725 }
5726 }), 'sync__ChanState')
5727 assert resolved is ast.Ident
5728 assert (resolved as ast.Ident).name == 'ChanState__closed'
5729}
5730
5731fn test_transform_selector_enum_keeps_direct_module_owner() {
5732 enum_typ := types.Type(types.Enum{
5733 name: 'Token'
5734 })
5735 mut token_scope := types.new_scope(unsafe { nil })
5736 token_scope.insert('Token', enum_typ)
5737 mut t := create_test_transformer()
5738 t.cur_module = 'token'
5739 t.cached_scopes = {
5740 'token': token_scope
5741 }
5742 result := t.transform_expr(ast.SelectorExpr{
5743 lhs: ast.Expr(ast.Ident{
5744 name: 'Token'
5745 })
5746 rhs: ast.Ident{
5747 name: 'name'
5748 }
5749 })
5750 assert result is ast.Ident
5751 assert (result as ast.Ident).name == 'token__Token__name'
5752}
5753
5754fn test_transform_init_expr_resolves_imported_enum_shorthand() {
5755 env := &types.Environment{}
5756 mut ast_scope := types.new_scope(unsafe { nil })
5757 enum_typ := types.Type(types.Enum{
5758 name: 'ast__StringLiteralKind'
5759 })
5760 ast_scope.insert('StringLiteralKind', enum_typ)
5761 ast_scope.insert('StringLiteral', types.Type(types.Struct{
5762 name: 'ast__StringLiteral'
5763 fields: [
5764 types.Field{
5765 name: 'kind'
5766 typ: enum_typ
5767 },
5768 types.Field{
5769 name: 'value'
5770 typ: types.string_
5771 },
5772 ]
5773 }))
5774 lock env.scopes {
5775 env.scopes['ast'] = ast_scope
5776 }
5777 mut t := &Transformer{
5778 pref: &vpref.Preferences{}
5779 env: unsafe { env }
5780 cur_module: 'main'
5781 cached_scopes: {
5782 'ast': ast_scope
5783 }
5784 needed_array_contains_fns: map[string]ArrayMethodInfo{}
5785 needed_array_index_fns: map[string]ArrayMethodInfo{}
5786 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
5787 }
5788 result := t.transform_init_expr(ast.InitExpr{
5789 typ: ast.Expr(ast.SelectorExpr{
5790 lhs: ast.Expr(ast.Ident{
5791 name: 'ast'
5792 })
5793 rhs: ast.Ident{
5794 name: 'StringLiteral'
5795 }
5796 })
5797 fields: [
5798 ast.FieldInit{
5799 name: 'kind'
5800 value: ast.Expr(ast.SelectorExpr{
5801 lhs: ast.empty_expr
5802 rhs: ast.Ident{
5803 name: 'v'
5804 }
5805 })
5806 },
5807 ]
5808 })
5809 assert result is ast.InitExpr
5810 init := result as ast.InitExpr
5811 mut found_kind := false
5812 for field in init.fields {
5813 if field.name == 'kind' {
5814 found_kind = true
5815 assert field.value is ast.Ident
5816 assert (field.value as ast.Ident).name == 'ast__StringLiteralKind__v'
5817 break
5818 }
5819 }
5820 assert found_kind
5821}
5822
5823fn test_transform_map_init_expr_non_empty_lowers_to_runtime_ctor() {
5824 mut t := create_test_transformer()
5825
5826 expr := ast.MapInitExpr{
5827 keys: [
5828 ast.Expr(ast.StringLiteral{
5829 value: 'foo'
5830 }),
5831 ast.Expr(ast.StringLiteral{
5832 value: 'bar'
5833 }),
5834 ]
5835 vals: [
5836 ast.Expr(ast.BasicLiteral{
5837 kind: .number
5838 value: '1'
5839 }),
5840 ast.Expr(ast.BasicLiteral{
5841 kind: .number
5842 value: '2'
5843 }),
5844 ]
5845 }
5846
5847 result := t.transform_map_init_expr(expr)
5848
5849 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5850 call := result as ast.CallExpr
5851 assert call.lhs is ast.Ident
5852 assert (call.lhs as ast.Ident).name == 'new_map_init_noscan_value'
5853 assert call.args.len == 9, 'expected 9 args for map constructor, got ${call.args.len}'
5854 assert call.args[7] is ast.ArrayInitExpr, 'expected key array arg'
5855 assert call.args[8] is ast.ArrayInitExpr, 'expected value array arg'
5856}
5857
5858fn test_transform_map_init_expr_empty_lowers_to_new_map() {
5859 mut t := create_test_transformer()
5860
5861 expr := ast.MapInitExpr{
5862 typ: ast.Expr(ast.Type(ast.MapType{
5863 key_type: ast.Ident{
5864 name: 'string'
5865 }
5866 value_type: ast.Ident{
5867 name: 'int'
5868 }
5869 }))
5870 }
5871
5872 result := t.transform_map_init_expr(expr)
5873 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
5874 call := result as ast.CallExpr
5875 assert call.lhs is ast.Ident
5876 assert (call.lhs as ast.Ident).name == 'new_map'
5877 assert call.args.len == 6
5878 assert call.args[2] is ast.PrefixExpr
5879 assert (call.args[2] as ast.PrefixExpr).op == .amp
5880 assert (call.args[2] as ast.PrefixExpr).expr is ast.Ident
5881 assert ((call.args[2] as ast.PrefixExpr).expr as ast.Ident).name == 'map_hash_string'
5882}
5883
5884fn test_transform_struct_init_pointer_field_keeps_pointer_value() {
5885 files := transform_code_for_test('
5886type BytePtr = &u8
5887
5888struct Dense {
5889 keys BytePtr = unsafe { nil }
5890}
5891
5892fn make_ptr() BytePtr {
5893 return unsafe { nil }
5894}
5895
5896fn new_dense() Dense {
5897 return Dense{
5898 keys: make_ptr()
5899 }
5900}
5901')
5902 mut found_field := false
5903 for stmt in files[0].stmts {
5904 if stmt is ast.FnDecl && stmt.name == 'new_dense' {
5905 for inner in stmt.stmts {
5906 if inner is ast.ReturnStmt && inner.exprs.len == 1 && inner.exprs[0] is ast.InitExpr {
5907 init := inner.exprs[0] as ast.InitExpr
5908 for field in init.fields {
5909 if field.name == 'keys' {
5910 found_field = true
5911 assert field.value !is ast.PrefixExpr
5912 }
5913 }
5914 }
5915 }
5916 }
5917 }
5918 assert found_field
5919}
5920
5921fn test_transform_index_expr_map_read_lowers_to_map_get() {
5922 mut t := create_transformer_with_vars({
5923 'm': types.Type(types.Map{
5924 key_type: string_type()
5925 value_type: types.int_
5926 })
5927 })
5928
5929 expr := ast.IndexExpr{
5930 lhs: ast.Ident{
5931 name: 'm'
5932 }
5933 expr: ast.StringLiteral{
5934 kind: .v
5935 value: "'foo'"
5936 }
5937 }
5938
5939 result := t.transform_index_expr(expr)
5940 assert result is ast.UnsafeExpr, 'expected UnsafeExpr, got ${result.type_name()}'
5941 unsafe_expr := result as ast.UnsafeExpr
5942 assert unsafe_expr.stmts.len > 0
5943 last := unsafe_expr.stmts[unsafe_expr.stmts.len - 1]
5944 assert last is ast.ExprStmt
5945 last_expr := (last as ast.ExprStmt).expr
5946 assert last_expr is ast.ParenExpr
5947 paren := last_expr as ast.ParenExpr
5948 assert paren.expr is ast.PrefixExpr
5949 pref := paren.expr as ast.PrefixExpr
5950 assert pref.op == .mul
5951 assert pref.expr is ast.CastExpr
5952 cast := pref.expr as ast.CastExpr
5953 assert cast.expr is ast.CallExpr
5954 call := cast.expr as ast.CallExpr
5955 assert call.lhs is ast.Ident
5956 assert (call.lhs as ast.Ident).name == 'map__get'
5957 assert call.args.len == 3
5958}
5959
5960fn test_transform_map_index_push_lowers_to_map_get_and_set() {
5961 mut t := create_transformer_with_vars({
5962 'lists': types.Type(types.Map{
5963 key_type: types.int_
5964 value_type: types.Type(types.Array{
5965 elem_type: types.int_
5966 })
5967 })
5968 })
5969
5970 result := t.try_transform_map_index_push(ast.ExprStmt{
5971 expr: ast.InfixExpr{
5972 op: .left_shift
5973 lhs: ast.IndexExpr{
5974 lhs: ast.Ident{
5975 name: 'lists'
5976 }
5977 expr: ast.BasicLiteral{
5978 kind: .number
5979 value: '2'
5980 }
5981 }
5982 rhs: ast.BasicLiteral{
5983 kind: .number
5984 value: '1'
5985 }
5986 }
5987 }) or {
5988 assert false, 'expected map index push to be transformed'
5989 return
5990 }
5991
5992 assert result is ast.ExprStmt
5993 expr_stmt := result as ast.ExprStmt
5994 assert expr_stmt.expr is ast.CallExpr
5995 push_call := expr_stmt.expr as ast.CallExpr
5996 assert push_call.lhs is ast.Ident
5997 assert (push_call.lhs as ast.Ident).name == 'array__push_noscan'
5998 assert push_call.args.len == 2
5999 assert push_call.args[0] is ast.CastExpr
6000 arr_ptr := push_call.args[0] as ast.CastExpr
6001 assert arr_ptr.expr is ast.CallExpr
6002 map_call := arr_ptr.expr as ast.CallExpr
6003 assert map_call.lhs is ast.Ident
6004 assert (map_call.lhs as ast.Ident).name == 'map__get_and_set'
6005 assert map_call.args.len == 3
6006}
6007
6008fn test_transform_map_index_push_generic_arg_or_index_lowers_to_map_get_and_set() {
6009 mut t := create_transformer_with_vars({
6010 'lists': types.Type(types.Map{
6011 key_type: types.int_
6012 value_type: types.Type(types.Array{
6013 elem_type: types.int_
6014 })
6015 })
6016 })
6017
6018 result := t.try_transform_map_index_push(ast.ExprStmt{
6019 expr: ast.InfixExpr{
6020 op: .left_shift
6021 lhs: ast.GenericArgOrIndexExpr{
6022 lhs: ast.Ident{
6023 name: 'lists'
6024 }
6025 expr: ast.BasicLiteral{
6026 kind: .number
6027 value: '2'
6028 }
6029 }
6030 rhs: ast.BasicLiteral{
6031 kind: .number
6032 value: '1'
6033 }
6034 }
6035 }) or {
6036 assert false, 'expected ambiguous map index push to be transformed'
6037 return
6038 }
6039
6040 assert result is ast.ExprStmt
6041 expr_stmt := result as ast.ExprStmt
6042 assert expr_stmt.expr is ast.CallExpr
6043 mut call_names := []string{}
6044 collect_call_names_from_expr(expr_stmt.expr, mut call_names)
6045 assert 'map__get_and_set' in call_names
6046}
6047
6048fn test_transform_map_index_push_generic_args_lowers_to_map_get_and_set() {
6049 mut t := create_transformer_with_vars({
6050 'lists': types.Type(types.Map{
6051 key_type: types.int_
6052 value_type: types.Type(types.Array{
6053 elem_type: types.int_
6054 })
6055 })
6056 })
6057
6058 result := t.try_transform_map_index_push(ast.ExprStmt{
6059 expr: ast.InfixExpr{
6060 op: .left_shift
6061 lhs: ast.GenericArgs{
6062 lhs: ast.Ident{
6063 name: 'lists'
6064 }
6065 args: [
6066 ast.Expr(ast.BasicLiteral{
6067 kind: .number
6068 value: '2'
6069 }),
6070 ]
6071 }
6072 rhs: ast.BasicLiteral{
6073 kind: .number
6074 value: '1'
6075 }
6076 }
6077 }) or {
6078 assert false, 'expected generic-args map index push to be transformed'
6079 return
6080 }
6081
6082 assert result is ast.ExprStmt
6083 expr_stmt := result as ast.ExprStmt
6084 assert expr_stmt.expr is ast.CallExpr
6085 mut call_names := []string{}
6086 collect_call_names_from_expr(expr_stmt.expr, mut call_names)
6087 assert 'map__get_and_set' in call_names
6088}
6089
6090fn test_transform_map_index_push_refuses_pointer_map_value() {
6091 array_int_type := types.Type(types.Array{
6092 elem_type: types.int_
6093 })
6094 mut t := create_transformer_with_vars({
6095 'lists': types.Type(types.Map{
6096 key_type: types.int_
6097 value_type: types.Type(types.Pointer{
6098 base_type: array_int_type
6099 })
6100 })
6101 })
6102
6103 if _ := t.try_transform_map_index_push(ast.ExprStmt{
6104 expr: ast.InfixExpr{
6105 op: .left_shift
6106 lhs: ast.IndexExpr{
6107 lhs: ast.Ident{
6108 name: 'lists'
6109 }
6110 expr: ast.BasicLiteral{
6111 kind: .number
6112 value: '2'
6113 }
6114 }
6115 rhs: ast.BasicLiteral{
6116 kind: .number
6117 value: '1'
6118 }
6119 }
6120 })
6121 {
6122 assert false, 'expected pointer map value push not to be transformed'
6123 }
6124}
6125
6126fn test_transform_map_index_selector_postfix_lowers_to_map_get_and_set() {
6127 fn_type := types.Type(types.Struct{
6128 name: 'Fn'
6129 fields: [
6130 types.Field{
6131 name: 'usages'
6132 typ: types.int_
6133 },
6134 ]
6135 })
6136 mut t := create_transformer_with_vars({
6137 'fns': types.Type(types.Map{
6138 key_type: string_type()
6139 value_type: fn_type
6140 })
6141 })
6142
6143 result := t.try_transform_map_index_postfix(ast.ExprStmt{
6144 expr: ast.PostfixExpr{
6145 op: .inc
6146 expr: ast.SelectorExpr{
6147 lhs: ast.Expr(ast.IndexExpr{
6148 lhs: ast.Ident{
6149 name: 'fns'
6150 }
6151 expr: ast.StringLiteral{
6152 kind: .v
6153 value: "'main'"
6154 }
6155 })
6156 rhs: ast.Ident{
6157 name: 'usages'
6158 }
6159 }
6160 }
6161 }) or {
6162 assert false, 'expected map index selector postfix to be transformed'
6163 return
6164 }
6165
6166 assert result is ast.AssignStmt
6167 assign_stmt := result as ast.AssignStmt
6168 assert assign_stmt.op == .plus_assign
6169 assert assign_stmt.lhs.len == 1
6170 assert assign_stmt.lhs[0] is ast.SelectorExpr
6171 lhs := assign_stmt.lhs[0] as ast.SelectorExpr
6172 assert lhs.rhs.name == 'usages'
6173 mut call_names := []string{}
6174 collect_call_names_from_expr(lhs.lhs, mut call_names)
6175 assert 'map__get_and_set' in call_names
6176}
6177
6178fn test_transform_map_index_assign_uses_temp_for_const_key() {
6179 files := transform_code_for_test('
6180const int_type_idx = 8
6181
6182fn set_print_type(mut print_types map[int]bool) {
6183 print_types[int_type_idx] = true
6184}
6185')
6186 call := find_call_with_lhs_suffix_in_stmts(files[0].stmts, 'map__set') or {
6187 assert false, 'expected map__set call'
6188 return
6189 }
6190 assert call.args.len == 3
6191 assert call.args[1] is ast.CastExpr
6192 key_cast := call.args[1] as ast.CastExpr
6193 assert key_cast.expr is ast.PrefixExpr
6194 key_addr := key_cast.expr as ast.PrefixExpr
6195 assert key_addr.expr is ast.Ident
6196 assert (key_addr.expr as ast.Ident).name != 'int_type_idx'
6197}
6198
6199fn test_transform_map_index_assign_uses_temp_for_module_const_key() {
6200 files := transform_sources_for_test([
6201 TestSource{
6202 rel: 'ast/types.v'
6203 code: 'module ast
6204pub const int_type_idx = 8
6205'
6206 },
6207 TestSource{
6208 rel: 'main.v'
6209 code: 'module main
6210import ast
6211
6212fn set_print_type(mut print_types map[int]bool) {
6213 print_types[ast.int_type_idx] = true
6214}
6215'
6216 },
6217 ])
6218 mut found := false
6219 for file in files {
6220 if file.mod != 'main' {
6221 continue
6222 }
6223 call := find_call_with_lhs_suffix_in_stmts(file.stmts, 'map__set') or { continue }
6224 assert call.args.len == 3
6225 assert call.args[1] is ast.CastExpr
6226 key_cast := call.args[1] as ast.CastExpr
6227 assert key_cast.expr is ast.PrefixExpr
6228 key_addr := key_cast.expr as ast.PrefixExpr
6229 assert key_addr.expr is ast.Ident
6230 assert (key_addr.expr as ast.Ident).name != 'ast__int_type_idx'
6231 found = true
6232 }
6233 assert found
6234}
6235
6236fn test_addr_of_prefix_temp_materializes_selector_from_call_result() {
6237 mut t := create_test_transformer()
6238 mut prefix_stmts := []ast.Stmt{}
6239 addr := t.addr_of_with_prefix_temp(ast.Expr(ast.SelectorExpr{
6240 lhs: ast.Expr(ast.CallExpr{
6241 lhs: ast.Ident{
6242 name: 'pos'
6243 }
6244 })
6245 rhs: ast.Ident{
6246 name: 'pos'
6247 }
6248 }), types.int_, mut prefix_stmts)
6249 assert prefix_stmts.len == 1
6250 assert prefix_stmts[0] is ast.AssignStmt
6251 tmp_decl := prefix_stmts[0] as ast.AssignStmt
6252 assert tmp_decl.lhs[0] is ast.Ident
6253 assert addr is ast.PrefixExpr
6254 addr_expr := addr as ast.PrefixExpr
6255 assert addr_expr.expr is ast.Ident
6256 assert (addr_expr.expr as ast.Ident).name == (tmp_decl.lhs[0] as ast.Ident).name
6257}
6258
6259fn test_transform_addr_of_call_or_cast_selector_lowers_to_pointer_cast_selector() {
6260 mut t := create_test_transformer()
6261 result := t.transform_expr(ast.Expr(ast.PrefixExpr{
6262 op: .amp
6263 expr: ast.Expr(ast.SelectorExpr{
6264 lhs: ast.Expr(ast.CallOrCastExpr{
6265 lhs: ast.Expr(ast.Ident{
6266 name: 'int'
6267 })
6268 expr: ast.Expr(ast.Ident{
6269 name: 'raw'
6270 })
6271 })
6272 rhs: ast.Ident{
6273 name: 'field'
6274 }
6275 })
6276 }))
6277 assert result is ast.SelectorExpr, 'expected SelectorExpr, got ${result.type_name()}'
6278 selector := result as ast.SelectorExpr
6279 assert selector.lhs is ast.CastExpr, 'selector lhs was not reduced: ${selector.lhs.type_name()}'
6280 cast := selector.lhs as ast.CastExpr
6281 assert cast.typ is ast.PrefixExpr
6282 cast_typ := cast.typ as ast.PrefixExpr
6283 assert cast_typ.op == .amp
6284 assert cast_typ.expr is ast.Ident
6285 assert (cast_typ.expr as ast.Ident).name == 'int'
6286 assert cast.expr is ast.Ident
6287 assert (cast.expr as ast.Ident).name == 'raw'
6288 assert selector.rhs.name == 'field'
6289}
6290
6291fn test_transform_map_index_assign_with_or_rhs_lowers_to_map_set() {
6292 files := transform_code_for_test('
6293fn maybe_int() ?int {
6294 return 1
6295}
6296
6297fn set_value(mut values map[string]int) {
6298 values["c"] = maybe_int() or { 0 }
6299}
6300')
6301 call := find_call_with_lhs_suffix_in_stmts(files[0].stmts, 'map__set') or {
6302 assert false, 'expected map__set call'
6303 return
6304 }
6305 assert call.args.len == 3
6306}
6307
6308fn test_map_value_temp_expr_keeps_existing_sumtype_value() {
6309 sum_type := types.Type(types.SumType{
6310 name: 'types.Type'
6311 variants: [types.Type(types.string_), types.Type(types.int_)]
6312 })
6313 mut t := create_transformer_with_vars({
6314 'v': sum_type
6315 })
6316 expr := t.map_value_temp_expr(ast.Expr(ast.Ident{
6317 name: 'v'
6318 }), sum_type)
6319 assert expr is ast.Ident
6320 assert (expr as ast.Ident).name == 'v'
6321}
6322
6323fn test_map_value_temp_expr_keeps_short_named_sumtype_value() {
6324 sum_type := types.Type(types.SumType{
6325 name: 'types.Type'
6326 variants: [types.Type(types.string_), types.Type(types.int_)]
6327 })
6328 mut t := create_transformer_with_vars({
6329 'v': types.Type(types.NamedType('Type'))
6330 })
6331 expr := t.map_value_temp_expr(ast.Expr(ast.Ident{
6332 name: 'v'
6333 }), sum_type)
6334 assert expr is ast.Ident
6335 assert (expr as ast.Ident).name == 'v'
6336}
6337
6338fn test_for_in_var_registration_replaces_reused_loop_value_type() {
6339 sum_type := types.Type(types.SumType{
6340 name: 'types.Type'
6341 variants: [types.Type(types.string_), types.Type(types.int_)]
6342 })
6343 mut t := create_transformer_with_vars({})
6344 t.register_for_in_var_type('v', types.Type(types.string_))
6345 t.register_for_in_var_type('v', sum_type)
6346 expr := t.map_value_temp_expr(ast.Expr(ast.Ident{
6347 name: 'v'
6348 }), sum_type)
6349 assert expr is ast.Ident
6350 assert (expr as ast.Ident).name == 'v'
6351}
6352
6353fn test_decl_assign_index_expr_records_lhs_type() {
6354 mut t := create_transformer_with_vars({
6355 'values': types.Type(types.Array{
6356 elem_type: types.int_
6357 })
6358 })
6359 lhs_pos := token.Pos{
6360 id: 4242
6361 }
6362 index_pos := token.Pos{
6363 id: 4243
6364 }
6365 t.env.set_expr_type(index_pos.id, types.Type(types.f64_))
6366 transformed := t.transform_assign_stmt(ast.AssignStmt{
6367 op: .decl_assign
6368 lhs: [ast.Expr(ast.Ident{
6369 name: 'first'
6370 pos: lhs_pos
6371 })]
6372 rhs: [
6373 ast.Expr(ast.IndexExpr{
6374 lhs: ast.Expr(ast.Ident{
6375 name: 'values'
6376 })
6377 expr: ast.Expr(ast.BasicLiteral{
6378 kind: .number
6379 value: '0'
6380 })
6381 pos: index_pos
6382 }),
6383 ]
6384 })
6385 assert transformed.op == .decl_assign
6386 local_type := t.lookup_local_decl_type('first') or {
6387 assert false, 'missing local declaration type for index expression'
6388 return
6389 }
6390 assert t.type_to_c_name(local_type) == 'int'
6391 synth_type := t.get_synth_type(lhs_pos) or {
6392 assert false, 'missing synth type for index declaration lhs'
6393 return
6394 }
6395 assert t.type_to_c_name(synth_type) == 'int'
6396}
6397
6398fn test_unsafe_cast_deref_prefers_cast_target_over_stale_env_type() {
6399 mut t := create_test_transformer()
6400 unsafe_pos := token.Pos{
6401 id: 4250
6402 }
6403 deref_pos := token.Pos{
6404 id: 4251
6405 }
6406 t.env.set_expr_type(unsafe_pos.id, types.Type(types.f64_))
6407 t.env.set_expr_type(deref_pos.id, types.Type(types.f64_))
6408 typ := t.get_expr_type(ast.UnsafeExpr{
6409 stmts: [
6410 ast.Stmt(ast.ExprStmt{
6411 expr: ast.Expr(ast.PrefixExpr{
6412 op: .mul
6413 expr: ast.Expr(ast.PrefixExpr{
6414 op: .amp
6415 expr: ast.Expr(ast.CallOrCastExpr{
6416 lhs: ast.Expr(ast.Ident{
6417 name: 'int'
6418 })
6419 expr: ast.Expr(ast.Ident{
6420 name: 'raw'
6421 })
6422 })
6423 })
6424 pos: deref_pos
6425 })
6426 }),
6427 ]
6428 pos: unsafe_pos
6429 }) or {
6430 assert false, 'missing unsafe cast deref type'
6431 return
6432 }
6433 assert t.type_to_c_name(typ) == 'int'
6434}
6435
6436fn test_decl_assign_unsafe_cast_deref_records_lhs_type() {
6437 mut t := create_test_transformer()
6438 lhs_pos := token.Pos{
6439 id: 4260
6440 }
6441 unsafe_pos := token.Pos{
6442 id: 4261
6443 }
6444 deref_pos := token.Pos{
6445 id: 4262
6446 }
6447 t.env.set_expr_type(unsafe_pos.id, types.Type(types.f64_))
6448 t.env.set_expr_type(deref_pos.id, types.Type(types.f64_))
6449 transformed := t.transform_assign_stmt(ast.AssignStmt{
6450 op: .decl_assign
6451 lhs: [ast.Expr(ast.Ident{
6452 name: 'x'
6453 pos: lhs_pos
6454 })]
6455 rhs: [
6456 ast.Expr(ast.UnsafeExpr{
6457 stmts: [
6458 ast.Stmt(ast.ExprStmt{
6459 expr: ast.Expr(ast.PrefixExpr{
6460 op: .mul
6461 expr: ast.Expr(ast.PrefixExpr{
6462 op: .amp
6463 expr: ast.Expr(ast.CallOrCastExpr{
6464 lhs: ast.Expr(ast.Ident{
6465 name: 'int'
6466 })
6467 expr: ast.Expr(ast.Ident{
6468 name: 'raw'
6469 })
6470 })
6471 })
6472 pos: deref_pos
6473 })
6474 }),
6475 ]
6476 pos: unsafe_pos
6477 }),
6478 ]
6479 })
6480 assert transformed.op == .decl_assign
6481 local_type := t.lookup_local_decl_type('x') or {
6482 assert false, 'missing local declaration type for unsafe cast deref'
6483 return
6484 }
6485 assert t.type_to_c_name(local_type) == 'int'
6486 synth_type := t.get_synth_type(lhs_pos) or {
6487 assert false, 'missing synth type for unsafe cast deref declaration lhs'
6488 return
6489 }
6490 assert t.type_to_c_name(synth_type) == 'int'
6491}
6492
6493fn test_is_enum_rvalue_stops_on_unresolved_alias() {
6494 t := create_test_transformer()
6495 unresolved_alias := types.Type(types.Alias{
6496 name: 'Unresolved'
6497 })
6498 assert !t.is_enum_rvalue(ast.Expr(ast.Ident{
6499 name: 'value'
6500 }), unresolved_alias)
6501}
6502
6503fn test_transform_map_index_push_resolves_named_array_alias_value() {
6504 mut t := create_transformer_with_vars({
6505 'cleanups': types.Type(types.Map{
6506 key_type: types.string_
6507 value_type: types.Type(types.NamedType('strings.Builder'))
6508 })
6509 'cleanup': types.Type(types.NamedType('strings.Builder'))
6510 })
6511 mut strings_scope := types.new_scope(unsafe { nil })
6512 strings_scope.insert_type('Builder', types.Type(types.Alias{
6513 name: 'strings.Builder'
6514 base_type: types.Type(types.Array{
6515 elem_type: types.int_
6516 })
6517 }))
6518 t.cached_scopes = {
6519 'strings': strings_scope
6520 }
6521
6522 result := t.try_transform_map_index_push(ast.ExprStmt{
6523 expr: ast.InfixExpr{
6524 op: .left_shift
6525 lhs: ast.IndexExpr{
6526 lhs: ast.Ident{
6527 name: 'cleanups'
6528 }
6529 expr: ast.StringLiteral{
6530 kind: .v
6531 value: "'main'"
6532 }
6533 }
6534 rhs: ast.Ident{
6535 name: 'cleanup'
6536 }
6537 }
6538 }) or {
6539 assert false, 'expected named alias map index push to be transformed'
6540 return
6541 }
6542
6543 assert result is ast.ExprStmt
6544 expr_stmt := result as ast.ExprStmt
6545 assert expr_stmt.expr is ast.CallExpr
6546 push_call := expr_stmt.expr as ast.CallExpr
6547 assert push_call.lhs is ast.Ident
6548 assert (push_call.lhs as ast.Ident).name == 'array__push_many'
6549 assert push_call.args[0] is ast.CastExpr
6550 arr_ptr := push_call.args[0] as ast.CastExpr
6551 assert arr_ptr.expr is ast.CallExpr
6552 map_call := arr_ptr.expr as ast.CallExpr
6553 assert map_call.lhs is ast.Ident
6554 assert (map_call.lhs as ast.Ident).name == 'map__get_and_set'
6555}
6556
6557fn test_transform_array_append_selector_array_field_uses_push_many() {
6558 ast_type := types.Type(types.NamedType('ast.Type'))
6559 array_ast_type := types.Type(types.Array{
6560 elem_type: ast_type
6561 })
6562 mut t := create_transformer_with_vars({
6563 'smartcasts': array_ast_type
6564 'field': types.Type(types.Pointer{
6565 base_type: types.Type(types.Struct{
6566 name: 'ast.ScopeStructField'
6567 fields: [
6568 types.Field{
6569 name: 'smartcasts'
6570 typ: array_ast_type
6571 },
6572 ]
6573 })
6574 })
6575 })
6576
6577 result := t.transform_expr(ast.InfixExpr{
6578 op: .left_shift
6579 lhs: ast.Ident{
6580 name: 'smartcasts'
6581 }
6582 rhs: ast.SelectorExpr{
6583 lhs: ast.Ident{
6584 name: 'field'
6585 }
6586 rhs: ast.Ident{
6587 name: 'smartcasts'
6588 }
6589 }
6590 })
6591
6592 assert result is ast.CallExpr
6593 call := result as ast.CallExpr
6594 assert call.lhs is ast.Ident
6595 assert (call.lhs as ast.Ident).name == 'array__push_many'
6596 assert call.args.len == 3
6597}
6598
6599fn test_transform_array_append_after_array_decl_uses_declared_array_type() {
6600 array_int_type := types.Type(types.Array{
6601 elem_type: types.int_
6602 })
6603 mut t := create_transformer_with_vars({
6604 'field': types.Type(types.Pointer{
6605 base_type: types.Type(types.Struct{
6606 name: 'Field'
6607 fields: [
6608 types.Field{
6609 name: 'values'
6610 typ: array_int_type
6611 },
6612 ]
6613 })
6614 })
6615 })
6616 _ = t.transform_assign_stmt(ast.AssignStmt{
6617 op: .decl_assign
6618 lhs: [ast.Expr(ast.Ident{
6619 name: 'values'
6620 })]
6621 rhs: [
6622 ast.Expr(ast.ArrayInitExpr{
6623 typ: ast.Expr(ast.Type(ast.ArrayType{
6624 elem_type: ast.Ident{
6625 name: 'int'
6626 }
6627 }))
6628 }),
6629 ]
6630 })
6631
6632 result := t.transform_expr(ast.InfixExpr{
6633 op: .left_shift
6634 lhs: ast.Ident{
6635 name: 'values'
6636 }
6637 rhs: ast.SelectorExpr{
6638 lhs: ast.Ident{
6639 name: 'field'
6640 }
6641 rhs: ast.Ident{
6642 name: 'values'
6643 }
6644 }
6645 })
6646
6647 assert result is ast.CallExpr
6648 call := result as ast.CallExpr
6649 assert call.lhs is ast.Ident
6650 assert (call.lhs as ast.Ident).name == 'array__push_many'
6651}
6652
6653fn test_transform_array_alias_mut_receiver_append_uses_receiver_pointer() {
6654 files := transform_code_for_test('
6655type Builder = []u8
6656
6657fn (mut b Builder) add_zero() {
6658 b << u8(0)
6659}
6660
6661fn main() {
6662 mut b := Builder([]u8{})
6663 b.add_zero()
6664}
6665')
6666 mut found := false
6667 for file in files {
6668 for stmt in file.stmts {
6669 if stmt is ast.FnDecl && stmt.name == 'add_zero' {
6670 assert stmt.stmts.len == 1
6671 assert stmt.stmts[0] is ast.ExprStmt
6672 expr_stmt := stmt.stmts[0] as ast.ExprStmt
6673 assert expr_stmt.expr is ast.CallExpr
6674 call := expr_stmt.expr as ast.CallExpr
6675 assert call.lhs is ast.Ident
6676 assert (call.lhs as ast.Ident).name == 'builtin__array_push_noscan'
6677 assert call.args.len == 2
6678 assert call.args[0] is ast.CastExpr
6679 arr_ptr := call.args[0] as ast.CastExpr
6680 assert arr_ptr.expr is ast.Ident
6681 assert (arr_ptr.expr as ast.Ident).name == 'b'
6682 found = true
6683 }
6684 }
6685 }
6686 assert found
6687}
6688
6689fn test_array_elem_type_uses_checker_ident_metadata() {
6690 env := types.Environment.new()
6691 mut scope := types.new_scope(unsafe { nil })
6692 mut t := &Transformer{
6693 pref: &vpref.Preferences{}
6694 env: env
6695 scope: scope
6696 needed_clone_fns: map[string]string{}
6697 needed_array_contains_fns: map[string]ArrayMethodInfo{}
6698 needed_array_index_fns: map[string]ArrayMethodInfo{}
6699 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
6700 }
6701 mut env_mut := unsafe { env }
6702 env_mut.set_expr_type(42, types.Type(types.Array{
6703 elem_type: types.Type(types.NamedType('ast.Type'))
6704 }))
6705
6706 elem_type := t.get_array_elem_type_str(ast.Expr(ast.Ident{
6707 name: 'smartcasts'
6708 pos: token.Pos{
6709 id: 42
6710 }
6711 })) or {
6712 assert false, 'expected array element type from checker metadata'
6713 return
6714 }
6715 assert elem_type == 'ast__Type'
6716}
6717
6718fn test_transform_array_append_preserves_alias_element_type_for_push_many() {
6719 files := transform_code_for_test('
6720type Type = u32
6721
6722struct ScopeStructField {
6723 smartcasts []Type
6724}
6725
6726fn f(field &ScopeStructField) {
6727 mut smartcasts := []Type{}
6728 smartcasts << field.smartcasts
6729}
6730')
6731 mut found := false
6732 for file in files {
6733 for stmt in file.stmts {
6734 if stmt is ast.FnDecl && stmt.name == 'f' {
6735 assert stmt.stmts.len == 2
6736 assert stmt.stmts[1] is ast.ExprStmt
6737 expr_stmt := stmt.stmts[1] as ast.ExprStmt
6738 assert expr_stmt.expr is ast.CallExpr
6739 call := expr_stmt.expr as ast.CallExpr
6740 assert call.lhs is ast.Ident
6741 assert (call.lhs as ast.Ident).name == 'array__push_many'
6742 found = true
6743 }
6744 }
6745 }
6746 assert found
6747}
6748
6749fn test_transform_nested_array_append_literal_pushes_single_array_value() {
6750 files := transform_code_for_test('
6751fn f(column_name string) {
6752 mut groups := [][]string{}
6753 groups << [column_name]
6754}
6755')
6756 mut found := false
6757 for file in files {
6758 for stmt in file.stmts {
6759 if stmt is ast.FnDecl && stmt.name == 'f' {
6760 assert stmt.stmts.len == 3
6761 assert stmt.stmts[1] is ast.AssignStmt
6762 tmp_assign := stmt.stmts[1] as ast.AssignStmt
6763 assert tmp_assign.op == .decl_assign
6764 assert tmp_assign.lhs.len == 1
6765 assert tmp_assign.lhs[0] is ast.Ident
6766 tmp_name := (tmp_assign.lhs[0] as ast.Ident).name
6767 assert tmp_name.starts_with('_ap_t')
6768 assert tmp_assign.rhs.len == 1
6769 assert tmp_assign.rhs[0] is ast.ArrayInitExpr
6770 assert stmt.stmts[2] is ast.ExprStmt
6771 expr_stmt := stmt.stmts[2] as ast.ExprStmt
6772 assert expr_stmt.expr is ast.CallExpr
6773 call := expr_stmt.expr as ast.CallExpr
6774 assert call.lhs is ast.Ident
6775 assert (call.lhs as ast.Ident).name == 'builtin__array_push_noscan'
6776 assert call.args.len == 2
6777 assert call.args[1] is ast.ArrayInitExpr
6778 outer_arr := call.args[1] as ast.ArrayInitExpr
6779 assert outer_arr.typ is ast.Type
6780 assert outer_arr.typ as ast.Type is ast.ArrayType
6781 outer_type := outer_arr.typ as ast.Type
6782 outer_array_type := outer_type as ast.ArrayType
6783 assert outer_array_type.elem_type is ast.Ident
6784 assert (outer_array_type.elem_type as ast.Ident).name == 'Array_string'
6785 assert outer_arr.exprs.len == 1
6786 assert outer_arr.exprs[0] is ast.Ident
6787 assert (outer_arr.exprs[0] as ast.Ident).name == tmp_name
6788 found = true
6789 }
6790 }
6791 }
6792 assert found
6793}
6794
6795fn test_transform_nested_array_append_call_result_uses_temp() {
6796 files := transform_code_for_test('
6797fn make_items() []string {
6798 return ["x"]
6799}
6800
6801fn f() {
6802 mut groups := [][]string{}
6803 groups << make_items()
6804}
6805')
6806 mut found := false
6807 for file in files {
6808 for stmt in file.stmts {
6809 if stmt is ast.FnDecl && stmt.name == 'f' {
6810 assert stmt.stmts.len == 3
6811 assert stmt.stmts[1] is ast.AssignStmt
6812 tmp_assign := stmt.stmts[1] as ast.AssignStmt
6813 assert tmp_assign.op == .decl_assign
6814 assert tmp_assign.lhs.len == 1
6815 assert tmp_assign.lhs[0] is ast.Ident
6816 tmp_name := (tmp_assign.lhs[0] as ast.Ident).name
6817 assert tmp_name.starts_with('_ap_t')
6818 assert tmp_assign.rhs.len == 1
6819 assert tmp_assign.rhs[0] is ast.CallExpr
6820 rhs_call := tmp_assign.rhs[0] as ast.CallExpr
6821 assert rhs_call.lhs is ast.Ident
6822 assert (rhs_call.lhs as ast.Ident).name == 'make_items'
6823 assert stmt.stmts[2] is ast.ExprStmt
6824 expr_stmt := stmt.stmts[2] as ast.ExprStmt
6825 assert expr_stmt.expr is ast.CallExpr
6826 call := expr_stmt.expr as ast.CallExpr
6827 assert call.lhs is ast.Ident
6828 assert (call.lhs as ast.Ident).name == 'builtin__array_push_noscan'
6829 assert call.args.len == 2
6830 assert call.args[1] is ast.ArrayInitExpr
6831 outer_arr := call.args[1] as ast.ArrayInitExpr
6832 assert outer_arr.exprs.len == 1
6833 assert outer_arr.exprs[0] is ast.Ident
6834 assert (outer_arr.exprs[0] as ast.Ident).name == tmp_name
6835 found = true
6836 }
6837 }
6838 }
6839 assert found
6840}
6841
6842fn assert_array_append_arg_is_addressed_ident(arg ast.Expr, name string) {
6843 assert arg is ast.CastExpr
6844 arr_ptr := arg as ast.CastExpr
6845 assert arr_ptr.expr is ast.PrefixExpr
6846 prefix := arr_ptr.expr as ast.PrefixExpr
6847 assert prefix.op == .amp
6848 assert prefix.expr is ast.Ident
6849 assert (prefix.expr as ast.Ident).name == name
6850}
6851
6852fn test_transform_array_append_mut_array_param_uses_local_storage_address() {
6853 files := transform_code_for_test('
6854fn add_seen(mut seen []string, name string) {
6855 seen << name
6856}
6857
6858fn push_path(mut patterns [][]string, path []string) {
6859 patterns << path
6860}
6861
6862fn push_many_paths(mut patterns [][]string, paths [][]string) {
6863 patterns << paths
6864}
6865')
6866 mut found_seen := false
6867 mut found_path := false
6868 mut found_many := false
6869 for file in files {
6870 for stmt in file.stmts {
6871 if stmt is ast.FnDecl && stmt.name in ['add_seen', 'push_path', 'push_many_paths'] {
6872 assert stmt.stmts.len == 1
6873 assert stmt.stmts[0] is ast.ExprStmt
6874 expr_stmt := stmt.stmts[0] as ast.ExprStmt
6875 assert expr_stmt.expr is ast.CallExpr
6876 call := expr_stmt.expr as ast.CallExpr
6877 assert call.lhs is ast.Ident
6878 call_name := (call.lhs as ast.Ident).name
6879 assert call.args.len >= 2
6880 if stmt.name == 'add_seen' {
6881 assert call_name == 'builtin__array_push_noscan'
6882 assert_array_append_arg_is_addressed_ident(call.args[0], 'seen')
6883 found_seen = true
6884 } else if stmt.name == 'push_path' {
6885 assert call_name == 'builtin__array_push_noscan'
6886 assert_array_append_arg_is_addressed_ident(call.args[0], 'patterns')
6887 found_path = true
6888 } else if stmt.name == 'push_many_paths' {
6889 assert call_name == 'array__push_many'
6890 assert_array_append_arg_is_addressed_ident(call.args[0], 'patterns')
6891 found_many = true
6892 }
6893 }
6894 }
6895 }
6896 assert found_seen
6897 assert found_path
6898 assert found_many
6899}
6900
6901fn test_transform_nested_array_literal_single_push_uses_temp_and_param_storage_address() {
6902 files := transform_code_for_test('
6903fn push_literal_path(mut patterns [][]string, path []string) {
6904 patterns << [path]
6905}
6906')
6907 mut found := false
6908 for file in files {
6909 for stmt in file.stmts {
6910 if stmt is ast.FnDecl && stmt.name == 'push_literal_path' {
6911 assert stmt.stmts.len == 1
6912 assert stmt.stmts[0] is ast.ExprStmt
6913 expr_stmt := stmt.stmts[0] as ast.ExprStmt
6914 assert expr_stmt.expr is ast.CallExpr
6915 call := expr_stmt.expr as ast.CallExpr
6916 assert call.lhs is ast.Ident
6917 assert (call.lhs as ast.Ident).name == 'builtin__array_push_noscan'
6918 assert call.args.len == 2
6919 assert_array_append_arg_is_addressed_ident(call.args[0], 'patterns')
6920 assert call.args[1] is ast.ArrayInitExpr
6921 outer_arr := call.args[1] as ast.ArrayInitExpr
6922 assert outer_arr.exprs.len == 1
6923 assert outer_arr.exprs[0] is ast.Ident
6924 assert (outer_arr.exprs[0] as ast.Ident).name == 'path'
6925 found = true
6926 }
6927 }
6928 }
6929 assert found
6930}
6931
6932fn test_transform_for_in_nested_array_value_uses_array_index() {
6933 files := transform_code_for_test('
6934fn f(groups [][]string) []int {
6935 mut counts := []int{}
6936 for group in groups {
6937 counts << group.len
6938 }
6939 return counts
6940}
6941')
6942 mut found := false
6943 for file in files {
6944 for stmt in file.stmts {
6945 if stmt is ast.FnDecl && stmt.name == 'f' {
6946 assert stmt.stmts.len >= 2
6947 assert stmt.stmts[1] is ast.ForStmt
6948 for_stmt := stmt.stmts[1] as ast.ForStmt
6949 assert for_stmt.stmts.len > 0
6950 assert for_stmt.stmts[0] is ast.AssignStmt
6951 value_assign := for_stmt.stmts[0] as ast.AssignStmt
6952 assert value_assign.lhs.len == 1
6953 assert value_assign.lhs[0] is ast.Ident
6954 assert (value_assign.lhs[0] as ast.Ident).name == 'group'
6955 assert value_assign.rhs.len == 1
6956 assert value_assign.rhs[0] is ast.IndexExpr
6957 index_expr := value_assign.rhs[0] as ast.IndexExpr
6958 assert index_expr.lhs !is ast.CastExpr
6959 found = true
6960 }
6961 }
6962 }
6963 assert found
6964}
6965
6966fn test_transform_for_in_prefers_scope_iter_type_over_position_type() {
6967 mut env := types.Environment.new()
6968 iter_pos := token.Pos{
6969 id: 42
6970 }
6971 env.set_expr_type(iter_pos.id, types.Type(types.Array{
6972 elem_type: types.Type(types.int_)
6973 }))
6974 mut scope := types.new_scope(unsafe { nil })
6975 scope.insert('s', value_object_from_type(types.Type(types.string_)))
6976 mut t := &Transformer{
6977 pref: &vpref.Preferences{}
6978 env: env
6979 scope: scope
6980 needed_clone_fns: map[string]string{}
6981 needed_array_contains_fns: map[string]ArrayMethodInfo{}
6982 needed_array_index_fns: map[string]ArrayMethodInfo{}
6983 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
6984 local_decl_types: map[string]types.Type{}
6985 }
6986 result := t.transform_for_stmt(ast.ForStmt{
6987 init: ast.Stmt(ast.ForInStmt{
6988 value: ast.Expr(ast.Ident{
6989 name: 'ch'
6990 })
6991 expr: ast.Expr(ast.ParenExpr{
6992 expr: ast.Expr(ast.Ident{
6993 name: 's'
6994 pos: iter_pos
6995 })
6996 })
6997 })
6998 })
6999 assert result.stmts.len > 0
7000 assert result.stmts[0] is ast.AssignStmt
7001 value_assign := result.stmts[0] as ast.AssignStmt
7002 assert value_assign.rhs.len == 1
7003 assert value_assign.rhs[0] is ast.IndexExpr
7004 index_expr := value_assign.rhs[0] as ast.IndexExpr
7005 mut index_lhs := index_expr.lhs
7006 for _ in 0 .. 4 {
7007 if index_lhs is ast.ParenExpr {
7008 index_lhs = index_lhs.expr
7009 continue
7010 }
7011 break
7012 }
7013 assert index_lhs is ast.Ident
7014 assert (index_lhs as ast.Ident).name == 's'
7015}
7016
7017fn test_transform_for_in_fixed_array_value_uses_array_index() {
7018 files := transform_code_for_test('
7019fn f(pairs [][2]int) []int {
7020 mut out := []int{}
7021 for pair in pairs {
7022 out << pair[1]
7023 }
7024 return out
7025}
7026')
7027 mut found := false
7028 for file in files {
7029 for stmt in file.stmts {
7030 if stmt is ast.FnDecl && stmt.name == 'f' {
7031 assert stmt.stmts.len >= 2
7032 assert stmt.stmts[1] is ast.ForStmt
7033 for_stmt := stmt.stmts[1] as ast.ForStmt
7034 assert for_stmt.stmts.len > 0
7035 assert for_stmt.stmts[0] is ast.AssignStmt
7036 value_assign := for_stmt.stmts[0] as ast.AssignStmt
7037 assert value_assign.lhs.len == 1
7038 assert value_assign.lhs[0] is ast.Ident
7039 assert (value_assign.lhs[0] as ast.Ident).name == 'pair'
7040 assert value_assign.rhs.len == 1
7041 assert value_assign.rhs[0] is ast.IndexExpr
7042 index_expr := value_assign.rhs[0] as ast.IndexExpr
7043 assert index_expr.lhs !is ast.CastExpr
7044 found = true
7045 }
7046 }
7047 }
7048 assert found
7049}
7050
7051fn test_transform_for_in_struct_value_uses_typed_array_index() {
7052 files := transform_code_for_test('
7053struct Field {
7054 typ int
7055}
7056
7057fn f(fields []Field) int {
7058 mut out := 0
7059 for field in fields {
7060 out += field.typ
7061 }
7062 return out
7063}
7064')
7065 mut found := false
7066 for file in files {
7067 for stmt in file.stmts {
7068 if stmt is ast.FnDecl && stmt.name == 'f' {
7069 assert stmt.stmts.len >= 2
7070 assert stmt.stmts[1] is ast.ForStmt
7071 for_stmt := stmt.stmts[1] as ast.ForStmt
7072 assert for_stmt.stmts.len > 0
7073 assert for_stmt.stmts[0] is ast.AssignStmt
7074 value_assign := for_stmt.stmts[0] as ast.AssignStmt
7075 assert value_assign.lhs.len == 1
7076 assert value_assign.lhs[0] is ast.Ident
7077 assert (value_assign.lhs[0] as ast.Ident).name == 'field'
7078 assert value_assign.rhs.len == 1
7079 assert value_assign.rhs[0] is ast.IndexExpr
7080 index_expr := value_assign.rhs[0] as ast.IndexExpr
7081 assert index_expr.lhs !is ast.CastExpr
7082 found = true
7083 }
7084 }
7085 }
7086 assert found
7087}
7088
7089fn test_transform_for_in_smartcast_variant_array_field_keeps_element_type() {
7090 field_type := types.Type(types.Struct{
7091 name: 'Field'
7092 fields: [
7093 types.Field{
7094 name: 'name'
7095 typ: types.Type(types.string_)
7096 },
7097 ]
7098 })
7099 enum_type := types.Type(types.Struct{
7100 name: 'Enum'
7101 fields: [
7102 types.Field{
7103 name: 'fields'
7104 typ: types.Type(types.Array{
7105 elem_type: field_type
7106 })
7107 },
7108 ]
7109 })
7110 sum_type := types.Type(types.SumType{
7111 name: 'Type'
7112 variants: [enum_type]
7113 })
7114 mut local_scope := types.new_scope(unsafe { nil })
7115 local_scope.insert('typ', value_object_from_type(sum_type))
7116 mut module_scope := types.new_scope(unsafe { nil })
7117 module_scope.insert_type('Enum', enum_type)
7118 module_scope.insert_type('Type', sum_type)
7119 mut t := create_test_transformer()
7120 t.scope = local_scope
7121 t.cur_module = 'main'
7122 t.cached_scopes = {
7123 'main': module_scope
7124 }
7125 t.push_smartcast_full('typ', 'Enum', 'Enum', 'Type')
7126 result := t.transform_for_stmt(ast.ForStmt{
7127 init: ast.Stmt(ast.ForInStmt{
7128 value: ast.Expr(ast.Ident{
7129 name: 'field'
7130 })
7131 expr: ast.Expr(ast.SelectorExpr{
7132 lhs: ast.Expr(ast.Ident{
7133 name: 'typ'
7134 })
7135 rhs: ast.Ident{
7136 name: 'fields'
7137 }
7138 })
7139 })
7140 })
7141 assert result.stmts.len > 0
7142 assert result.stmts[0] is ast.AssignStmt
7143 value_assign := result.stmts[0] as ast.AssignStmt
7144 assert value_assign.lhs.len == 1
7145 assert value_assign.lhs[0] is ast.Ident
7146 value_ident := value_assign.lhs[0] as ast.Ident
7147 value_typ := t.get_synth_type(value_ident.pos) or {
7148 panic('missing smartcast for-in value type')
7149 }
7150 assert value_typ.name() == 'Field'
7151 assert value_assign.rhs.len == 1
7152 assert value_assign.rhs[0] is ast.IndexExpr
7153 index_expr := value_assign.rhs[0] as ast.IndexExpr
7154 index_typ := t.get_synth_type(index_expr.pos) or {
7155 panic('missing smartcast for-in index type')
7156 }
7157 assert index_typ.name() == 'Field'
7158}
7159
7160fn test_transform_for_in_nested_fixed_array_value_registers_decl_type() {
7161 elem_type := types.Type(types.ArrayFixed{
7162 len: 3
7163 elem_type: types.Type(types.f32_)
7164 })
7165 iter_type := types.Type(types.ArrayFixed{
7166 len: 4
7167 elem_type: elem_type
7168 })
7169 mut t := create_transformer_with_vars({
7170 'corners': iter_type
7171 })
7172 result := t.transform_for_stmt(ast.ForStmt{
7173 init: ast.Stmt(ast.ForInStmt{
7174 value: ast.Expr(ast.Ident{
7175 name: 'c'
7176 })
7177 expr: ast.Expr(ast.Ident{
7178 name: 'corners'
7179 })
7180 })
7181 stmts: [
7182 ast.Stmt(ast.ExprStmt{
7183 expr: ast.Expr(ast.IndexExpr{
7184 lhs: ast.Expr(ast.Ident{
7185 name: 'c'
7186 })
7187 expr: ast.Expr(ast.BasicLiteral{
7188 kind: .number
7189 value: '0'
7190 })
7191 })
7192 }),
7193 ]
7194 })
7195 assert result.stmts.len > 0
7196 assert result.stmts[0] is ast.AssignStmt
7197 value_assign := result.stmts[0] as ast.AssignStmt
7198 assert value_assign.lhs.len == 1
7199 assert value_assign.lhs[0] is ast.Ident
7200 value_ident := value_assign.lhs[0] as ast.Ident
7201 assert value_ident.name == 'c'
7202 assert value_ident.pos.id != 0
7203 value_decl_type := t.get_synth_type(value_ident.pos) or {
7204 assert false, 'missing for-in value declaration type'
7205 return
7206 }
7207 assert value_decl_type == elem_type
7208}
7209
7210fn test_transform_fixed_array_of_array_literals_keeps_inner_arrays_dynamic() {
7211 env, files := transform_code_with_env_for_test('
7212fn f() {
7213 corners := [[f32(1), 2, 3], [f32(4), 5, 6]]!
7214 for c in corners {
7215 _ = c[0]
7216 }
7217}
7218')
7219 mut found_decl := false
7220 mut found_loop := false
7221 for file in files {
7222 for stmt in file.stmts {
7223 if stmt is ast.FnDecl && stmt.name == 'f' {
7224 assert stmt.stmts.len >= 2
7225 assert stmt.stmts[0] is ast.AssignStmt
7226 corners_decl := stmt.stmts[0] as ast.AssignStmt
7227 assert corners_decl.rhs.len == 1
7228 assert corners_decl.rhs[0] is ast.ArrayInitExpr
7229 corners_init := corners_decl.rhs[0] as ast.ArrayInitExpr
7230 assert corners_init.typ is ast.Type
7231 assert corners_init.typ as ast.Type is ast.ArrayFixedType
7232 corners_type := corners_init.typ as ast.Type
7233 fixed_type := corners_type as ast.ArrayFixedType
7234 assert fixed_type.elem_type is ast.Type
7235 assert fixed_type.elem_type as ast.Type is ast.ArrayType
7236 assert corners_init.exprs.len == 2
7237 assert corners_init.exprs[0] is ast.CallExpr
7238 assert corners_init.exprs[1] is ast.CallExpr
7239 found_decl = true
7240
7241 assert stmt.stmts[1] is ast.ForStmt
7242 for_stmt := stmt.stmts[1] as ast.ForStmt
7243 assert for_stmt.stmts.len > 0
7244 assert for_stmt.stmts[0] is ast.AssignStmt
7245 value_assign := for_stmt.stmts[0] as ast.AssignStmt
7246 assert value_assign.lhs.len == 1
7247 assert value_assign.lhs[0] is ast.Ident
7248 value_ident := value_assign.lhs[0] as ast.Ident
7249 value_type := env.get_expr_type(value_ident.pos.id) or {
7250 assert false, 'missing loop value type'
7251 return
7252 }
7253 assert value_type is types.Array
7254 found_loop = true
7255 }
7256 }
7257 }
7258 assert found_decl
7259 assert found_loop
7260}
7261
7262fn test_transform_for_in_mixed_float_array_uses_float_data_cast() {
7263 files := transform_code_for_test('
7264fn f() {
7265 for _, i in [8.0, 1000, 175_616] {
7266 _ = i
7267 }
7268}
7269')
7270 mut found := false
7271 for file in files {
7272 for stmt in file.stmts {
7273 if stmt is ast.FnDecl && stmt.name == 'f' {
7274 assert stmt.stmts.len == 1
7275 assert stmt.stmts[0] is ast.ForStmt
7276 for_stmt := stmt.stmts[0] as ast.ForStmt
7277 assert for_stmt.stmts.len > 0
7278 assert for_stmt.stmts[0] is ast.AssignStmt
7279 value_assign := for_stmt.stmts[0] as ast.AssignStmt
7280 assert value_assign.rhs.len == 1
7281 assert value_assign.rhs[0] is ast.IndexExpr
7282 index_expr := value_assign.rhs[0] as ast.IndexExpr
7283 assert index_expr.lhs is ast.CastExpr
7284 cast_expr := index_expr.lhs as ast.CastExpr
7285 assert cast_expr.typ is ast.Ident
7286 assert (cast_expr.typ as ast.Ident).name == 'f64*'
7287 found = true
7288 }
7289 }
7290 }
7291 assert found
7292}
7293
7294fn test_transform_selector_field_index_method_receiver_stays_index_expr() {
7295 files := transform_code_for_test('
7296struct EmbeddedFile {
7297 bytes []u8
7298}
7299
7300fn (b u8) hex() string {
7301 return "x"
7302}
7303
7304fn f(emfile EmbeddedFile) string {
7305 return emfile.bytes[0].hex()
7306}
7307')
7308 mut found := false
7309 for file in files {
7310 for stmt in file.stmts {
7311 if stmt is ast.FnDecl && stmt.name == 'f' {
7312 call := find_call_with_lhs_suffix_in_stmts(stmt.stmts, 'u8__hex') or {
7313 assert false, 'missing transformed u8.hex call'
7314 return
7315 }
7316 assert call.args.len == 1
7317 assert call.args[0] is ast.IndexExpr, 'method receiver should stay an index expression, got ${call.args[0].type_name()}'
7318 found = true
7319 }
7320 }
7321 }
7322 assert found
7323}
7324
7325fn test_map_index_or_assign_uses_selector_field_map_type() {
7326 map_type := types.Type(types.Map{
7327 key_type: string_type()
7328 value_type: string_type()
7329 })
7330 handler_type := types.Type(types.Struct{
7331 name: 'Handler'
7332 fields: [
7333 types.Field{
7334 name: 'files'
7335 typ: map_type
7336 },
7337 ]
7338 })
7339 mut module_scope := types.new_scope(unsafe { nil })
7340 module_scope.insert('Handler', handler_type)
7341 mut t := create_transformer_with_vars({
7342 'handler': handler_type
7343 })
7344 t.cur_module = 'main'
7345 t.cached_scopes = {
7346 'main': module_scope
7347 }
7348
7349 or_expr := ast.OrExpr{
7350 expr: ast.IndexExpr{
7351 lhs: ast.SelectorExpr{
7352 lhs: ast.Ident{
7353 name: 'handler'
7354 }
7355 rhs: ast.Ident{
7356 name: 'files'
7357 }
7358 }
7359 expr: ast.StringLiteral{
7360 kind: .v
7361 value: "'index.html'"
7362 }
7363 }
7364 stmts: [
7365 ast.Stmt(ast.ReturnStmt{
7366 exprs: [
7367 ast.Expr(ast.StringLiteral{
7368 kind: .v
7369 value: "''"
7370 }),
7371 ]
7372 }),
7373 ]
7374 }
7375 result := t.try_expand_map_index_or_assign(ast.AssignStmt{
7376 op: .decl_assign
7377 lhs: [ast.Expr(ast.Ident{
7378 name: 'file'
7379 })]
7380 rhs: [ast.Expr(or_expr)]
7381 }, or_expr) or {
7382 assert false, 'expected selector-field map or assignment to be expanded'
7383 return
7384 }
7385
7386 assert result.len == 3
7387 temp_assign := result[0]
7388 assert temp_assign is ast.AssignStmt
7389 temp_rhs := (temp_assign as ast.AssignStmt).rhs[0]
7390 assert temp_rhs is ast.CallExpr
7391 temp_call := temp_rhs as ast.CallExpr
7392 assert temp_call.lhs is ast.Ident
7393 assert (temp_call.lhs as ast.Ident).name == 'map__get_check'
7394}
7395
7396fn test_transform_init_expr_empty_typed_map_lowers_to_new_map() {
7397 mut t := create_test_transformer()
7398
7399 expr := ast.InitExpr{
7400 typ: ast.Expr(ast.Type(ast.MapType{
7401 key_type: ast.Ident{
7402 name: 'string'
7403 }
7404 value_type: ast.Ident{
7405 name: 'int'
7406 }
7407 }))
7408 }
7409
7410 result := t.transform_init_expr(expr)
7411 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
7412 call := result as ast.CallExpr
7413 assert call.lhs is ast.Ident
7414 assert (call.lhs as ast.Ident).name == 'new_map'
7415 assert call.args.len == 6
7416}
7417
7418fn test_transform_string_index_or_assign_expands_to_bounds_check() {
7419 mut t := create_transformer_with_vars({
7420 's': types.Type(types.string_)
7421 'i': types.Type(types.int_)
7422 })
7423 or_expr := ast.OrExpr{
7424 expr: ast.IndexExpr{
7425 lhs: ast.Ident{
7426 name: 's'
7427 }
7428 expr: ast.Ident{
7429 name: 'i'
7430 }
7431 }
7432 stmts: [
7433 ast.Stmt(ast.ReturnStmt{
7434 exprs: [
7435 ast.Expr(ast.BasicLiteral{
7436 kind: .number
7437 value: '0'
7438 }),
7439 ]
7440 }),
7441 ]
7442 }
7443 result := t.try_expand_array_index_or_assign(ast.AssignStmt{
7444 op: .decl_assign
7445 lhs: [ast.Expr(ast.Ident{
7446 name: 'ch'
7447 })]
7448 rhs: [ast.Expr(or_expr)]
7449 }, or_expr) or {
7450 assert false, 'expected string index or assignment to be expanded'
7451 return
7452 }
7453 assert result.len == 2
7454 assert result[0] is ast.AssignStmt
7455 if_stmt := (result[1] as ast.ExprStmt).expr as ast.IfExpr
7456 assert if_stmt.cond is ast.InfixExpr
7457 cond := if_stmt.cond as ast.InfixExpr
7458 assert cond.op == .lt
7459 assert cond.rhs is ast.SelectorExpr
7460 assert (cond.rhs as ast.SelectorExpr).rhs.name == 'len'
7461}
7462
7463fn test_array_index_or_assign_with_void_fallback_does_not_assign_void() {
7464 mut t := create_transformer_with_vars({
7465 'items': types.Type(types.Array{
7466 elem_type: types.string_
7467 })
7468 'i': types.Type(types.int_)
7469 'path': types.Type(types.string_)
7470 })
7471 or_expr := ast.OrExpr{
7472 expr: ast.IndexExpr{
7473 lhs: ast.Ident{
7474 name: 'items'
7475 }
7476 expr: ast.Ident{
7477 name: 'i'
7478 }
7479 }
7480 stmts: [
7481 ast.Stmt(ast.ExprStmt{
7482 expr: ast.CallExpr{
7483 lhs: ast.Ident{
7484 name: 'eprintln_exit'
7485 }
7486 }
7487 }),
7488 ]
7489 }
7490 result := t.try_expand_array_index_or_assign(ast.AssignStmt{
7491 op: .assign
7492 lhs: [ast.Expr(ast.Ident{
7493 name: 'path'
7494 })]
7495 rhs: [ast.Expr(or_expr)]
7496 }, or_expr) or {
7497 assert false, 'expected array index or assignment to be expanded'
7498 return
7499 }
7500 if_stmt := (result[0] as ast.ExprStmt).expr as ast.IfExpr
7501 else_if := if_stmt.else_expr as ast.IfExpr
7502 assert else_if.stmts.len == 1
7503 assert else_if.stmts[0] is ast.ExprStmt
7504 assert else_if.stmts[0] !is ast.AssignStmt
7505}
7506
7507fn test_is_string_expr_string_literal() {
7508 mut t := create_test_transformer()
7509
7510 expr := ast.StringLiteral{
7511 value: 'hello'
7512 }
7513
7514 assert t.is_string_expr(expr), 'StringLiteral should be detected as string'
7515}
7516
7517fn test_is_string_expr_basic_literal_string() {
7518 mut t := create_test_transformer()
7519
7520 expr := ast.BasicLiteral{
7521 value: 'hello'
7522 kind: .string
7523 }
7524
7525 assert t.is_string_expr(expr), 'BasicLiteral with .string kind should be detected as string'
7526}
7527
7528fn test_is_string_expr_cast_to_string() {
7529 mut t := create_test_transformer()
7530
7531 // Create: (string){...}
7532 expr := ast.CastExpr{
7533 typ: ast.Ident{
7534 name: 'string'
7535 }
7536 expr: ast.BasicLiteral{
7537 value: 'test'
7538 kind: .string
7539 }
7540 }
7541
7542 assert t.is_string_expr(expr), 'CastExpr to string should be detected as string'
7543}
7544
7545fn test_is_string_expr_method_call() {
7546 mut t := create_test_transformer()
7547
7548 // Create: s.to_upper()
7549 expr := ast.CallExpr{
7550 lhs: ast.SelectorExpr{
7551 lhs: ast.Ident{
7552 name: 's'
7553 }
7554 rhs: ast.Ident{
7555 name: 'to_upper'
7556 }
7557 }
7558 args: []
7559 }
7560
7561 assert t.is_string_expr(expr), 'method call to_upper() should be detected as string'
7562}
7563
7564fn test_is_string_returning_method() {
7565 t := create_test_transformer()
7566
7567 // Test various string-returning methods
7568 assert t.is_string_returning_method('str')
7569 assert t.is_string_returning_method('string')
7570 assert t.is_string_returning_method('to_upper')
7571 assert t.is_string_returning_method('to_lower')
7572 assert t.is_string_returning_method('substr')
7573 assert t.is_string_returning_method('hex')
7574 assert t.is_string_returning_method('join')
7575
7576 // Non-string methods
7577 assert !t.is_string_returning_method('len')
7578 assert !t.is_string_returning_method('push')
7579}
7580
7581// --- OrExpr expansion tests ---
7582
7583fn test_expand_single_or_expr_defaults_to_result() {
7584 // When type lookup fails (empty environment), expand_single_or_expr
7585 // should default to Result expansion instead of returning OrExpr unchanged.
7586 mut t := create_test_transformer()
7587
7588 // Build: some_call() or { 0 }
7589 or_expr := ast.OrExpr{
7590 expr: ast.CallExpr{
7591 lhs: ast.Ident{
7592 name: 'some_call'
7593 }
7594 args: []
7595 }
7596 stmts: [
7597 ast.Stmt(ast.ExprStmt{
7598 expr: ast.BasicLiteral{
7599 kind: .number
7600 value: '0'
7601 }
7602 }),
7603 ]
7604 }
7605
7606 mut prefix_stmts := []ast.Stmt{}
7607 result := t.expand_single_or_expr(or_expr, mut prefix_stmts)
7608
7609 // Should generate prefix statements (temp assign + if-check)
7610 assert prefix_stmts.len == 2, 'expected 2 prefix stmts (assign + if), got ${prefix_stmts.len}'
7611
7612 // First prefix stmt: _or_t1 := some_call()
7613 assert prefix_stmts[0] is ast.AssignStmt
7614 assign := prefix_stmts[0] as ast.AssignStmt
7615 assert assign.op == .decl_assign
7616 assert assign.lhs.len == 1
7617 temp_ident := assign.lhs[0]
7618 assert temp_ident is ast.Ident
7619 temp_name := (temp_ident as ast.Ident).name
7620 assert temp_name.starts_with('_or_t'), 'expected temp name starting with _or_t, got ${temp_name}'
7621
7622 // Second prefix stmt: if _or_t1.is_error { ... } (Result pattern, not Option pattern)
7623 assert prefix_stmts[1] is ast.ExprStmt
7624 if_stmt := (prefix_stmts[1] as ast.ExprStmt).expr
7625 assert if_stmt is ast.IfExpr
7626 if_expr := if_stmt as ast.IfExpr
7627 // For Result: condition is _or_t1.is_error (SelectorExpr)
7628 assert if_expr.cond is ast.SelectorExpr, 'expected SelectorExpr (Result pattern), got ${if_expr.cond.type_name()}'
7629 sel := if_expr.cond as ast.SelectorExpr
7630 assert sel.rhs.name == 'is_error', 'expected is_error selector for Result, got ${sel.rhs.name}'
7631
7632 // base_type is unknown (empty env), defaults to 'int' => not void => returns data access
7633 assert result is ast.SelectorExpr, 'expected SelectorExpr (.data access) for default base type, got ${result.type_name()}'
7634}
7635
7636fn test_expand_single_or_expr_with_return_in_or_block() {
7637 // Or-block with return statement (control flow pattern):
7638 // some_call() or { return }
7639 mut t := create_test_transformer()
7640
7641 or_expr := ast.OrExpr{
7642 expr: ast.CallExpr{
7643 lhs: ast.Ident{
7644 name: 'some_call'
7645 }
7646 args: []
7647 }
7648 stmts: [ast.Stmt(ast.FlowControlStmt{
7649 op: .key_return
7650 })]
7651 }
7652
7653 mut prefix_stmts := []ast.Stmt{}
7654 result := t.expand_single_or_expr(or_expr, mut prefix_stmts)
7655
7656 // Should still generate prefix statements
7657 assert prefix_stmts.len == 2, 'expected 2 prefix stmts, got ${prefix_stmts.len}'
7658
7659 // The if-block body should contain only the return statement (err not used, so no err assign)
7660 if_stmt := (prefix_stmts[1] as ast.ExprStmt).expr as ast.IfExpr
7661 assert if_stmt.stmts.len == 2, 'expected 2 stmt in if body (return only, err not used), got ${if_stmt.stmts.len}'
7662
7663 // base_type is unknown => defaults to 'int' => not void => returns data access
7664 assert result is ast.SelectorExpr, 'expected SelectorExpr (.data access) for default base type'
7665}
7666
7667fn test_transform_expr_or_expr_wraps_in_unsafe() {
7668 // transform_expr with OrExpr should wrap in UnsafeExpr (compound expression)
7669 mut t := create_test_transformer()
7670
7671 or_expr := ast.OrExpr{
7672 expr: ast.CallExpr{
7673 lhs: ast.Ident{
7674 name: 'get_value'
7675 }
7676 args: []
7677 }
7678 stmts: [
7679 ast.Stmt(ast.ExprStmt{
7680 expr: ast.BasicLiteral{
7681 kind: .number
7682 value: '42'
7683 }
7684 }),
7685 ]
7686 }
7687
7688 result := t.transform_expr(or_expr)
7689
7690 // Should be wrapped in UnsafeExpr (GCC compound expression)
7691 assert result is ast.UnsafeExpr, 'expected UnsafeExpr wrapper, got ${result.type_name()}'
7692 unsafe_expr := result as ast.UnsafeExpr
7693 // Stmts: temp assign, if-check, and the result ExprStmt
7694 assert unsafe_expr.stmts.len == 3, 'expected 3 stmts in UnsafeExpr, got ${unsafe_expr.stmts.len}'
7695
7696 // First stmt: temp assign
7697 assert unsafe_expr.stmts[0] is ast.AssignStmt
7698
7699 // Second stmt: if-check with is_error (Result pattern)
7700 assert unsafe_expr.stmts[1] is ast.ExprStmt
7701 if_check := (unsafe_expr.stmts[1] as ast.ExprStmt).expr
7702 assert if_check is ast.IfExpr
7703
7704 // Last stmt should be ExprStmt with the result expression
7705 last := unsafe_expr.stmts[2]
7706 assert last is ast.ExprStmt
7707}
7708
7709fn test_get_or_block_stmts_and_value_ignores_trailing_empty_stmt() {
7710 mut t := create_test_transformer()
7711
7712 stmts := [
7713 ast.Stmt(ast.ExprStmt{
7714 expr: ast.Tuple{
7715 exprs: [
7716 ast.Expr(ast.BasicLiteral{
7717 kind: .number
7718 value: '1'
7719 }),
7720 ast.Expr(ast.BasicLiteral{
7721 kind: .number
7722 value: '2'
7723 }),
7724 ]
7725 }
7726 }),
7727 ast.empty_stmt,
7728 ]
7729
7730 side_effects, value := t.get_or_block_stmts_and_value(stmts)
7731
7732 assert side_effects.len == 0
7733 assert value is ast.Tuple
7734 assert (value as ast.Tuple).exprs.len == 2
7735}
7736
7737// --- IfGuardExpr expansion tests ---
7738
7739fn test_transform_expr_if_guard_standalone_evaluates_rhs() {
7740 // Standalone IfGuardExpr in transform_expr should just evaluate RHS
7741 mut t := create_test_transformer()
7742
7743 guard := ast.IfGuardExpr{
7744 stmt: ast.AssignStmt{
7745 op: .decl_assign
7746 lhs: [ast.Expr(ast.Ident{
7747 name: 'x'
7748 })]
7749 rhs: [
7750 ast.Expr(ast.CallExpr{
7751 lhs: ast.Ident{
7752 name: 'some_func'
7753 }
7754 args: []
7755 }),
7756 ]
7757 }
7758 }
7759
7760 result := t.transform_expr(guard)
7761
7762 // Should evaluate to the RHS (the call expression)
7763 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
7764 call := result as ast.CallExpr
7765 assert call.lhs is ast.Ident
7766 assert (call.lhs as ast.Ident).name == 'some_func'
7767}
7768
7769fn test_transform_expr_if_guard_empty_rhs() {
7770 // IfGuardExpr with empty RHS should pass through
7771 mut t := create_test_transformer()
7772
7773 guard := ast.IfGuardExpr{
7774 stmt: ast.AssignStmt{
7775 op: .decl_assign
7776 lhs: [ast.Expr(ast.Ident{
7777 name: 'x'
7778 })]
7779 rhs: []
7780 }
7781 }
7782
7783 result := t.transform_expr(guard)
7784
7785 // With empty RHS, should return the guard as-is
7786 assert result is ast.IfGuardExpr
7787}
7788
7789fn test_transform_if_expr_with_if_guard_result_uses_temp_var() {
7790 // IfExpr with IfGuardExpr condition for Result type should use temp var pattern
7791 // Since env is empty, type lookups fail, so this hits the non-option path
7792 // which does map/array/simple transformation
7793 mut t := create_test_transformer()
7794
7795 // Build: if x := result_call() { body } else { else_body }
7796 if_expr := ast.IfExpr{
7797 cond: ast.IfGuardExpr{
7798 stmt: ast.AssignStmt{
7799 op: .decl_assign
7800 lhs: [ast.Expr(ast.Ident{
7801 name: 'x'
7802 })]
7803 rhs: [
7804 ast.Expr(ast.CallExpr{
7805 lhs: ast.Ident{
7806 name: 'result_call'
7807 }
7808 args: []
7809 }),
7810 ]
7811 }
7812 }
7813 stmts: [ast.Stmt(ast.ExprStmt{
7814 expr: ast.Ident{
7815 name: 'x'
7816 }
7817 })]
7818 else_expr: ast.BasicLiteral{
7819 kind: .number
7820 value: '0'
7821 }
7822 }
7823
7824 result := t.transform_if_expr(if_expr)
7825
7826 // The IfGuardExpr should be expanded - result should NOT contain IfGuardExpr
7827 // It should be a regular IfExpr with a non-guard condition
7828 assert result is ast.IfExpr, 'expected IfExpr, got ${result.type_name()}'
7829 result_if := result as ast.IfExpr
7830 assert result_if.cond !is ast.IfGuardExpr, 'IfGuardExpr should have been expanded'
7831}
7832
7833fn test_transform_if_expr_with_if_guard_blank_lhs() {
7834 // if _ := some_call() { body } — blank LHS should skip variable assignment
7835 mut t := create_test_transformer()
7836
7837 if_expr := ast.IfExpr{
7838 cond: ast.IfGuardExpr{
7839 stmt: ast.AssignStmt{
7840 op: .decl_assign
7841 lhs: [ast.Expr(ast.Ident{
7842 name: '_'
7843 })]
7844 rhs: [
7845 ast.Expr(ast.CallExpr{
7846 lhs: ast.Ident{
7847 name: 'some_call'
7848 }
7849 args: []
7850 }),
7851 ]
7852 }
7853 }
7854 stmts: [
7855 ast.Stmt(ast.ExprStmt{
7856 expr: ast.BasicLiteral{
7857 kind: .number
7858 value: '1'
7859 }
7860 }),
7861 ]
7862 else_expr: ast.BasicLiteral{
7863 kind: .number
7864 value: '0'
7865 }
7866 }
7867
7868 result := t.transform_if_expr(if_expr)
7869
7870 assert result is ast.IfExpr, 'expected IfExpr, got ${result.type_name()}'
7871 result_if := result as ast.IfExpr
7872 assert result_if.cond !is ast.IfGuardExpr, 'IfGuardExpr should have been expanded'
7873 // With blank LHS, the body should NOT have a guard assignment prepended
7874 // Body should just have the original statement (transformed)
7875 assert result_if.stmts.len == 1, 'expected 1 stmt in body (no guard assign for blank), got ${result_if.stmts.len}'
7876}
7877
7878fn test_if_guard_option_payload_assignment_uses_unwrapped_lhs_type() {
7879 opt_string := types.Type(types.OptionType{
7880 base_type: types.string_
7881 })
7882 mut env := types.Environment.new()
7883 call_pos := token.Pos{
7884 id: 1001
7885 }
7886 guard_pos := token.Pos{
7887 id: 1002
7888 }
7889 if_pos := token.Pos{
7890 id: 1003
7891 }
7892 env.set_expr_type(call_pos.id, opt_string)
7893 env.set_expr_type(guard_pos.id, opt_string)
7894 env.set_expr_type(if_pos.id, types.Type(types.Array{
7895 elem_type: types.string_
7896 }))
7897 mut scope := types.new_scope(unsafe { nil })
7898 scope.insert('resolved_mod', opt_string)
7899 mut t := &Transformer{
7900 pref: &vpref.Preferences{}
7901 env: unsafe { env }
7902 scope: scope
7903 fn_root_scope: scope
7904 needed_clone_fns: map[string]string{}
7905 needed_array_contains_fns: map[string]ArrayMethodInfo{}
7906 needed_array_index_fns: map[string]ArrayMethodInfo{}
7907 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
7908 synth_types: map[int]types.Type{}
7909 }
7910 stmt := ast.ExprStmt{
7911 expr: ast.Expr(ast.IfExpr{
7912 pos: if_pos
7913 cond: ast.Expr(ast.IfGuardExpr{
7914 stmt: ast.AssignStmt{
7915 op: .decl_assign
7916 lhs: [
7917 ast.Expr(ast.Ident{
7918 name: 'resolved_mod'
7919 pos: guard_pos
7920 }),
7921 ]
7922 rhs: [
7923 ast.Expr(ast.CallExpr{
7924 lhs: ast.Expr(ast.Ident{
7925 name: 'resolve_mod'
7926 })
7927 pos: call_pos
7928 }),
7929 ]
7930 }
7931 })
7932 stmts: [
7933 ast.Stmt(ast.ExprStmt{
7934 expr: ast.Expr(ast.Ident{
7935 name: 'resolved_mod'
7936 })
7937 }),
7938 ]
7939 })
7940 }
7941 expanded := t.try_expand_if_guard_stmt(stmt) or {
7942 assert false, 'if-guard did not expand'
7943 return
7944 }
7945 assert expanded.len == 2
7946 assert expanded[0] is ast.AssignStmt
7947 temp_assign := expanded[0] as ast.AssignStmt
7948 assert temp_assign.lhs[0] is ast.Ident
7949 temp_ident := temp_assign.lhs[0] as ast.Ident
7950 temp_type := t.synth_types[temp_ident.pos.id] or {
7951 assert false, 'guard temp type was not registered'
7952 return
7953 }
7954 assert temp_type.name() == '?string'
7955 assert expanded[1] is ast.ExprStmt
7956 if_stmt := (expanded[1] as ast.ExprStmt).expr as ast.IfExpr
7957 assert if_stmt.stmts[0] is ast.AssignStmt
7958 guard_assign := if_stmt.stmts[0] as ast.AssignStmt
7959 assert guard_assign.rhs[0] is ast.SelectorExpr, 'payload assignment was wrapped in ${guard_assign.rhs[0].type_name()}'
7960 payload := guard_assign.rhs[0] as ast.SelectorExpr
7961 assert payload.rhs.name == 'data'
7962 lhs_type := t.lookup_var_type('resolved_mod') or {
7963 assert false, 'guard payload variable type was not registered'
7964 return
7965 }
7966 assert lhs_type.name() == 'string'
7967}
7968
7969fn test_if_guard_map_lookup_temp_keeps_pointer_type() {
7970 map_type := types.Type(types.Map{
7971 key_type: types.Type(types.string_)
7972 value_type: types.Type(types.string_)
7973 })
7974 mut t := create_transformer_with_vars({
7975 'import_aliases': map_type
7976 'key': types.Type(types.string_)
7977 })
7978 t.synth_types = map[int]types.Type{}
7979 if_pos := token.Pos{
7980 id: 9101
7981 }
7982 t.env.set_expr_type(if_pos.id, types.Type(types.string_))
7983 stmt := ast.ExprStmt{
7984 expr: ast.Expr(ast.IfExpr{
7985 pos: if_pos
7986 cond: ast.Expr(ast.IfGuardExpr{
7987 stmt: ast.AssignStmt{
7988 op: .decl_assign
7989 lhs: [
7990 ast.Expr(ast.Ident{
7991 name: 'alias'
7992 }),
7993 ]
7994 rhs: [
7995 ast.Expr(ast.IndexExpr{
7996 lhs: ast.Expr(ast.Ident{
7997 name: 'import_aliases'
7998 })
7999 expr: ast.Expr(ast.Ident{
8000 name: 'key'
8001 })
8002 }),
8003 ]
8004 }
8005 })
8006 stmts: [
8007 ast.Stmt(ast.ExprStmt{