vxx2 / vlib / v2_toberemoved / transformer / transformer_test.v
9756 lines · 9066 sloc · 234.16 KB · c0624b274a458fe3e487d89ae9554c2e8c25bdb6
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{
8008 expr: ast.Expr(ast.Ident{
8009 name: 'alias'
8010 })
8011 }),
8012 ]
8013 else_expr: ast.Expr(ast.StringLiteral{
8014 kind: .v
8015 value: 'missing'
8016 })
8017 })
8018 }
8019 expanded := t.try_expand_if_guard_stmt(stmt) or {
8020 assert false, 'map if-guard did not expand'
8021 return
8022 }
8023 assert expanded.len == 2
8024 assert expanded[0] is ast.AssignStmt
8025 temp_assign := expanded[0] as ast.AssignStmt
8026 assert temp_assign.lhs[0] is ast.Ident
8027 temp_ident := temp_assign.lhs[0] as ast.Ident
8028 temp_type := t.synth_types[temp_ident.pos.id] or {
8029 assert false, 'map guard temp type was not registered'
8030 return
8031 }
8032 assert temp_type is types.Pointer
8033 temp_pointer := temp_type as types.Pointer
8034 assert temp_pointer.base_type.name() == 'string'
8035 assert expanded[1] is ast.ExprStmt
8036 if_stmt := (expanded[1] as ast.ExprStmt).expr as ast.IfExpr
8037 assert if_stmt.pos.id != temp_ident.pos.id
8038 if_type := t.synth_types[if_stmt.pos.id] or {
8039 assert false, 'rewritten if expression type was not registered'
8040 return
8041 }
8042 assert if_type.name() == 'string'
8043}
8044
8045fn test_native_smartcast_alias_variant_uses_direct_data_cast() {
8046 mut ast_scope := types.new_scope(unsafe { nil })
8047 ast_scope.insert('EmptyExpr', types.Type(types.Alias{
8048 name: 'EmptyExpr'
8049 base_type: types.Type(types.Primitive{
8050 props: .integer | .unsigned
8051 size: 8
8052 })
8053 }))
8054 mut t := create_test_transformer()
8055 t.pref.backend = .arm64
8056 t.cached_scopes['ast'] = ast_scope
8057 ctx := SmartcastContext{
8058 expr: 'else_if.cond'
8059 variant: 'ast__EmptyExpr'
8060 variant_full: 'ast__EmptyExpr'
8061 sumtype: 'ast__Expr'
8062 }
8063 out := t.apply_smartcast_direct_ctx(ast.Expr(ast.SelectorExpr{
8064 lhs: ast.Expr(ast.Ident{
8065 name: 'else_if'
8066 })
8067 rhs: ast.Ident{
8068 name: 'cond'
8069 }
8070 }), ctx)
8071 assert out is ast.ParenExpr
8072 cast := (out as ast.ParenExpr).expr
8073 assert cast is ast.CastExpr, 'direct-data alias smartcast must not dereference _data'
8074 assert (cast as ast.CastExpr).typ is ast.Ident
8075 assert ((cast as ast.CastExpr).typ as ast.Ident).name == 'ast__EmptyExpr'
8076}
8077
8078fn test_sumtype_alias_variant_init_uses_direct_data() {
8079 empty_expr_type := types.Type(types.Alias{
8080 name: 'EmptyExpr'
8081 base_type: types.Type(types.Primitive{
8082 props: .integer | .unsigned
8083 size: 8
8084 })
8085 })
8086 mut ast_scope := types.new_scope(unsafe { nil })
8087 ast_scope.insert('EmptyExpr', empty_expr_type)
8088 ast_scope.insert('FnType', types.Type(types.Struct{
8089 name: 'FnType'
8090 }))
8091 ast_scope.insert('Expr', types.Type(types.SumType{
8092 name: 'Expr'
8093 variants: [empty_expr_type]
8094 }))
8095 mut t := create_test_transformer()
8096 t.cur_module = 'ast'
8097 t.cached_scopes['ast'] = ast_scope
8098 assert t.sumtype_variant_init_data_is_direct('EmptyExpr')
8099 assert !t.sumtype_variant_init_data_is_direct('FnType')
8100 wrapped := t.build_sumtype_init(ast.Expr(ast.CastExpr{
8101 typ: ast.Expr(ast.Ident{
8102 name: 'EmptyExpr'
8103 })
8104 expr: ast.Expr(ast.BasicLiteral{
8105 kind: .number
8106 value: '0'
8107 })
8108 }), 'EmptyExpr', 'Expr') or {
8109 assert false, 'EmptyExpr should be wrapped as Expr'
8110 return
8111 }
8112 assert wrapped is ast.InitExpr
8113 init := wrapped as ast.InitExpr
8114 assert init.fields.len == 2
8115 data_value := init.fields[1].value
8116 assert data_value is ast.CastExpr
8117 data_cast := data_value as ast.CastExpr
8118 assert data_cast.expr is ast.CastExpr, 'alias-to-scalar variants should be stored inline'
8119 assert data_cast.expr !is ast.CallExpr, 'alias-to-scalar variants should not be copied with memdup'
8120}
8121
8122fn test_transform_if_expr_preserves_else() {
8123 // Ensure else branch is preserved during IfGuardExpr expansion
8124 mut t := create_test_transformer()
8125
8126 if_expr := ast.IfExpr{
8127 cond: ast.IfGuardExpr{
8128 stmt: ast.AssignStmt{
8129 op: .decl_assign
8130 lhs: [ast.Expr(ast.Ident{
8131 name: 'val'
8132 })]
8133 rhs: [
8134 ast.Expr(ast.CallExpr{
8135 lhs: ast.Ident{
8136 name: 'try_get'
8137 }
8138 args: []
8139 }),
8140 ]
8141 }
8142 }
8143 stmts: [ast.Stmt(ast.ExprStmt{
8144 expr: ast.Ident{
8145 name: 'val'
8146 }
8147 })]
8148 else_expr: ast.BasicLiteral{
8149 kind: .number
8150 value: '-1'
8151 }
8152 }
8153
8154 result := t.transform_if_expr(if_expr)
8155
8156 // Result should have an else branch
8157 if result is ast.IfExpr {
8158 assert result.else_expr !is ast.EmptyExpr, 'else branch should be preserved'
8159 } else if result is ast.UnsafeExpr {
8160 // Result expansion wraps in UnsafeExpr; find the inner IfExpr
8161 mut found_if := false
8162 for s in result.stmts {
8163 if s is ast.ExprStmt {
8164 if s.expr is ast.IfExpr {
8165 assert s.expr.else_expr !is ast.EmptyExpr, 'else branch should be preserved in UnsafeExpr'
8166 found_if = true
8167 }
8168 }
8169 }
8170 assert found_if, 'expected IfExpr inside UnsafeExpr'
8171 } else {
8172 assert false, 'expected IfExpr or UnsafeExpr, got ${result.type_name()}'
8173 }
8174}
8175
8176fn test_transform_for_in_stmt_lowers_to_for_stmt() {
8177 mut t := create_test_transformer()
8178 result := t.transform_for_in_stmt(ast.ForInStmt{
8179 value: ast.Ident{
8180 name: 'v'
8181 }
8182 expr: ast.Ident{
8183 name: 'items'
8184 }
8185 })
8186
8187 // NOTE: transform_for_in_stmt returns `ast.ForStmt` directly.
8188 assert result.init is ast.AssignStmt, 'expected lowered init AssignStmt, got ${result.init.type_name()}'
8189}
8190
8191fn test_static_method_call_treats_type_object_receiver_as_static() {
8192 mut scope := types.new_scope(unsafe { nil })
8193 scope.insert('LineBufferReader', types.TypeObject{
8194 typ: types.Type(types.Struct{
8195 name: 'LineBufferReader'
8196 })
8197 })
8198 mut t := create_test_transformer()
8199 t.scope = scope
8200 assert t.is_static_method_call(ast.Expr(ast.Ident{
8201 name: 'LineBufferReader'
8202 }))
8203}
8204
8205// --- Inline array `in` optimization tests ---
8206
8207fn test_transform_in_inline_array_expands_to_eq_chain() {
8208 // x in [1, 2, 3] => (x == 1 || x == 2 || x == 3)
8209 mut t := create_transformer_with_vars({
8210 'x': types.Type(types.int_)
8211 })
8212
8213 expr := ast.InfixExpr{
8214 op: .key_in
8215 lhs: ast.Ident{
8216 name: 'x'
8217 }
8218 rhs: ast.ArrayInitExpr{
8219 exprs: [
8220 ast.Expr(ast.BasicLiteral{
8221 kind: .number
8222 value: '1'
8223 }),
8224 ast.Expr(ast.BasicLiteral{
8225 kind: .number
8226 value: '2'
8227 }),
8228 ast.Expr(ast.BasicLiteral{
8229 kind: .number
8230 value: '3'
8231 }),
8232 ]
8233 }
8234 }
8235
8236 result := t.transform_infix_expr(expr)
8237
8238 // Should be: (x == 1 || x == 2 || x == 3)
8239 // Top level: (x == 1 || x == 2) || (x == 3)
8240 assert result is ast.InfixExpr, 'expected InfixExpr (||), got ${result.type_name()}'
8241 top := result as ast.InfixExpr
8242 assert top.op == .logical_or, 'expected || at top, got ${top.op}'
8243 // RHS should be x == 3
8244 assert top.rhs is ast.InfixExpr
8245 rhs := top.rhs as ast.InfixExpr
8246 assert rhs.op == .eq
8247 assert rhs.lhs is ast.Ident
8248 assert (rhs.lhs as ast.Ident).name == 'x'
8249 assert rhs.rhs is ast.BasicLiteral
8250 assert (rhs.rhs as ast.BasicLiteral).value == '3'
8251}
8252
8253fn test_transform_in_inline_array_single_element() {
8254 // x in [5] => x == 5
8255 mut t := create_transformer_with_vars({
8256 'x': types.Type(types.int_)
8257 })
8258
8259 expr := ast.InfixExpr{
8260 op: .key_in
8261 lhs: ast.Ident{
8262 name: 'x'
8263 }
8264 rhs: ast.ArrayInitExpr{
8265 exprs: [
8266 ast.Expr(ast.BasicLiteral{
8267 kind: .number
8268 value: '5'
8269 }),
8270 ]
8271 }
8272 }
8273
8274 result := t.transform_infix_expr(expr)
8275
8276 // Single element: x == 5
8277 assert result is ast.InfixExpr, 'expected InfixExpr (==), got ${result.type_name()}'
8278 eq := result as ast.InfixExpr
8279 assert eq.op == .eq
8280 assert eq.lhs is ast.Ident
8281 assert (eq.lhs as ast.Ident).name == 'x'
8282 assert eq.rhs is ast.BasicLiteral
8283 assert (eq.rhs as ast.BasicLiteral).value == '5'
8284}
8285
8286fn test_transform_not_in_inline_array_wraps_with_not() {
8287 // x !in [1, 2] => !(x == 1 || x == 2)
8288 mut t := create_transformer_with_vars({
8289 'x': types.Type(types.int_)
8290 })
8291
8292 expr := ast.InfixExpr{
8293 op: .not_in
8294 lhs: ast.Ident{
8295 name: 'x'
8296 }
8297 rhs: ast.ArrayInitExpr{
8298 exprs: [
8299 ast.Expr(ast.BasicLiteral{
8300 kind: .number
8301 value: '1'
8302 }),
8303 ast.Expr(ast.BasicLiteral{
8304 kind: .number
8305 value: '2'
8306 }),
8307 ]
8308 }
8309 }
8310
8311 result := t.transform_infix_expr(expr)
8312
8313 // Should be: !(x == 1 || x == 2)
8314 assert result is ast.PrefixExpr, 'expected PrefixExpr (!), got ${result.type_name()}'
8315 prefix := result as ast.PrefixExpr
8316 assert prefix.op == .not
8317 assert prefix.expr is ast.InfixExpr
8318 inner := prefix.expr as ast.InfixExpr
8319 assert inner.op == .logical_or
8320}
8321
8322fn test_transformer_preserves_pointer_lifetime_in_v_syntax_but_not_c_names() {
8323 mut t := create_test_transformer()
8324 ptr_type := types.Type(types.Pointer{
8325 base_type: types.Type(types.NamedType('Foo'))
8326 lifetime: 'a'
8327 })
8328
8329 ast_expr := t.type_to_ast_type_expr(ptr_type)
8330 assert ast_expr is ast.Type
8331 assert (ast_expr as ast.Type) is ast.PointerType
8332 ptr_ast := (ast_expr as ast.Type) as ast.PointerType
8333 assert ptr_ast.lifetime == 'a'
8334 assert ptr_ast.base_type is ast.Ident
8335 assert (ptr_ast.base_type as ast.Ident).name == 'Foo'
8336
8337 assert t.types_type_to_v(ptr_type) == '&^a Foo'
8338 assert t.type_to_c_name(ptr_type) == 'Fooptr'
8339}
8340
8341fn test_transformer_uses_pointer_type_receiver_name_for_scope_key() {
8342 t := create_test_transformer()
8343 receiver := ast.Expr(ast.Type(ast.PointerType{
8344 base_type: ast.Expr(ast.Ident{
8345 name: 'Ignore'
8346 })
8347 lifetime: 'a'
8348 }))
8349 assert t.get_receiver_type_name(receiver) == 'Ignore'
8350}
8351
8352fn test_transformer_uses_pointer_type_for_generic_specialization_token() {
8353 t := create_test_transformer()
8354 foo_ptr := ast.Expr(ast.Type(ast.PointerType{
8355 base_type: ast.Expr(ast.Ident{
8356 name: 'Foo'
8357 })
8358 }))
8359 bar_ptr := ast.Expr(ast.Type(ast.PointerType{
8360 base_type: ast.Expr(ast.Ident{
8361 name: 'Bar'
8362 })
8363 }))
8364 assert t.generic_specialization_token(foo_ptr) == 'Fooptr'
8365 assert t.generic_specialization_token(bar_ptr) == 'Barptr'
8366 assert t.generic_specialization_suffix([foo_ptr]) == '_T_Fooptr'
8367 assert t.generic_specialization_suffix([bar_ptr]) == '_T_Barptr'
8368}
8369
8370fn test_expr_to_type_name_handles_array_pointer_type() {
8371 t := create_test_transformer()
8372 array_ptr := ast.Expr(ast.Type(ast.ArrayType{
8373 elem_type: ast.Expr(ast.Type(ast.PointerType{
8374 base_type: ast.Expr(ast.Ident{
8375 name: 'char'
8376 })
8377 }))
8378 }))
8379 assert t.expr_to_type_name(array_ptr) == 'Array_charptr'
8380}
8381
8382fn test_c_name_to_type_resolves_builtin_pointer_aliases_without_scope_lookup() {
8383 t := create_test_transformer()
8384 voidptr_type := t.c_name_to_type('voidptr') or {
8385 assert false, 'voidptr should resolve as a builtin type'
8386 return
8387 }
8388 assert voidptr_type is types.Alias
8389 assert (voidptr_type as types.Alias).name == 'voidptr'
8390
8391 array_type := t.c_name_to_type('Array_voidptr') or {
8392 assert false, 'Array_voidptr should resolve as a builtin array type'
8393 return
8394 }
8395 assert array_type is types.Array
8396 array_elem := (array_type as types.Array).elem_type
8397 assert array_elem is types.Alias
8398 assert (array_elem as types.Alias).name == 'voidptr'
8399}
8400
8401fn test_get_array_init_expr_type_resolves_voidptr_element_type() {
8402 t := create_test_transformer()
8403 array_init := ast.ArrayInitExpr{
8404 typ: ast.Expr(ast.Type(ast.ArrayType{
8405 elem_type: ast.Expr(ast.Ident{
8406 name: 'voidptr'
8407 })
8408 }))
8409 }
8410 array_type := t.get_array_init_expr_type(array_init) or {
8411 assert false, '[]voidptr{} should produce an array type'
8412 return
8413 }
8414 assert array_type is types.Array
8415 array_elem := (array_type as types.Array).elem_type
8416 assert array_elem is types.Alias
8417 assert (array_elem as types.Alias).name == 'voidptr'
8418}
8419
8420fn test_match_variant_resolves_fixed_array_c_name() {
8421 t := create_test_transformer()
8422 constructor_variant := t.match_variant('Array_fixed_u32_64', ['ArrayFixed']) or { '' }
8423 assert constructor_variant == 'ArrayFixed'
8424 fixed_array_variant := t.match_variant('Array_fixed_u32_64', ['[64]u32']) or { '' }
8425 assert fixed_array_variant == '[64]u32'
8426}
8427
8428fn test_sumtype_return_wrap_prefers_init_constructor_over_contextual_type() {
8429 sum_type := types.Type(types.SumType{
8430 name: 'types__Type'
8431 variants: [
8432 types.Type(types.Struct{
8433 name: 'ArrayFixed'
8434 }),
8435 ]
8436 })
8437 mut types_scope := types.new_scope(unsafe { nil })
8438 types_scope.insert('Type', sum_type)
8439 mut env := types.Environment.new()
8440 pos := token.Pos{
8441 id: 9101
8442 }
8443 env.set_expr_type(pos.id, sum_type)
8444 mut t := &Transformer{
8445 pref: &vpref.Preferences{}
8446 env: unsafe { env }
8447 cached_scopes: {
8448 'types': types_scope
8449 }
8450 needed_clone_fns: map[string]string{}
8451 needed_array_contains_fns: map[string]ArrayMethodInfo{}
8452 needed_array_index_fns: map[string]ArrayMethodInfo{}
8453 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
8454 }
8455 value := ast.Expr(ast.InitExpr{
8456 typ: ast.Expr(ast.SelectorExpr{
8457 lhs: ast.Expr(ast.Ident{
8458 name: 'types'
8459 })
8460 rhs: ast.Ident{
8461 name: 'ArrayFixed'
8462 }
8463 })
8464 pos: pos
8465 })
8466 variants := t.get_sum_type_variants('types__Type')
8467 assert variants == ['ArrayFixed']
8468 assert t.init_expr_sumtype_variant_name(value as ast.InitExpr, variants, 'types__Type') == 'ArrayFixed'
8469 wrapped := t.wrap_sumtype_value_transformed(value, 'types__Type') or {
8470 assert false, 'ArrayFixed constructor should be wrapped as types.Type'
8471 return
8472 }
8473 assert wrapped is ast.InitExpr
8474 wrapped_init := wrapped as ast.InitExpr
8475 assert wrapped_init.typ is ast.Ident
8476 assert (wrapped_init.typ as ast.Ident).name == 'types__Type'
8477
8478 option_pos := token.Pos{
8479 id: 9102
8480 }
8481 env.set_expr_type(option_pos.id, types.Type(types.OptionType{
8482 base_type: sum_type
8483 }))
8484 option_context_value := ast.Expr(ast.InitExpr{
8485 typ: ast.Expr(ast.SelectorExpr{
8486 lhs: ast.Expr(ast.Ident{
8487 name: 'types'
8488 })
8489 rhs: ast.Ident{
8490 name: 'ArrayFixed'
8491 }
8492 })
8493 pos: option_pos
8494 })
8495 t.cur_fn_ret_type_name = 'types__Type'
8496 t.cur_fn_returns_option = true
8497 return_stmt := t.transform_return_stmt(ast.ReturnStmt{
8498 exprs: [option_context_value]
8499 })
8500 assert return_stmt.exprs.len == 1
8501 assert return_stmt.exprs[0] is ast.InitExpr
8502 return_init := return_stmt.exprs[0] as ast.InitExpr
8503 assert return_init.typ is ast.Ident
8504 assert (return_init.typ as ast.Ident).name == 'types__Type'
8505
8506 or_context_value := ast.Expr(ast.InitExpr{
8507 typ: ast.Expr(ast.SelectorExpr{
8508 lhs: ast.Expr(ast.Ident{
8509 name: 'types'
8510 })
8511 rhs: ast.Ident{
8512 name: 'ArrayFixed'
8513 }
8514 })
8515 fields: [
8516 ast.FieldInit{
8517 name: 'len'
8518 value: ast.Expr(ast.OrExpr{
8519 expr: ast.Expr(ast.CallExpr{
8520 lhs: ast.Expr(ast.Ident{
8521 name: 'parse_len'
8522 })
8523 })
8524 stmts: [
8525 ast.Stmt(ast.ExprStmt{
8526 expr: ast.Expr(ast.BasicLiteral{
8527 kind: .number
8528 value: '0'
8529 })
8530 }),
8531 ]
8532 })
8533 },
8534 ]
8535 })
8536 expanded := t.try_expand_or_expr_return(ast.ReturnStmt{
8537 exprs: [or_context_value]
8538 }) or {
8539 assert false, 'return with nested or should expand'
8540 return
8541 }
8542 assert expanded.len > 0
8543 last_stmt := expanded[expanded.len - 1]
8544 assert last_stmt is ast.ReturnStmt
8545 expanded_return := last_stmt as ast.ReturnStmt
8546 assert expanded_return.exprs.len == 1
8547 assert expanded_return.exprs[0] is ast.InitExpr
8548 expanded_return_init := expanded_return.exprs[0] as ast.InitExpr
8549 assert expanded_return_init.typ is ast.Ident
8550 assert (expanded_return_init.typ as ast.Ident).name == 'types__Type'
8551}
8552
8553fn test_return_match_expr_expands_to_branch_returns() {
8554 mut t := create_test_transformer()
8555 t.cur_fn_returns_option = true
8556 match_expr := ast.MatchExpr{
8557 expr: ast.Expr(ast.Ident{
8558 name: 'name'
8559 })
8560 branches: [
8561 ast.MatchBranch{
8562 cond: [
8563 ast.Expr(ast.StringLiteral{
8564 kind: .v
8565 value: 'int'
8566 }),
8567 ]
8568 stmts: [
8569 ast.Stmt(ast.ExprStmt{
8570 expr: ast.Expr(ast.Ident{
8571 name: 'int_type'
8572 })
8573 }),
8574 ]
8575 },
8576 ast.MatchBranch{
8577 stmts: [
8578 ast.Stmt(ast.ExprStmt{
8579 expr: ast.Expr(ast.Ident{
8580 name: 'lookup_type'
8581 })
8582 }),
8583 ]
8584 },
8585 ]
8586 }
8587 expanded := t.try_expand_return_match_expr(ast.ReturnStmt{
8588 exprs: [ast.Expr(match_expr)]
8589 }) or {
8590 assert false, 'return match should expand'
8591 return
8592 }
8593 assert expanded.len == 1
8594 assert expanded[0] is ast.ExprStmt
8595 if_stmt := (expanded[0] as ast.ExprStmt).expr
8596 assert if_stmt is ast.IfExpr
8597 if_expr := if_stmt as ast.IfExpr
8598 assert if_expr.stmts.len == 1
8599 assert if_expr.stmts[0] is ast.ReturnStmt
8600 assert if_expr.else_expr is ast.IfExpr
8601 else_expr := if_expr.else_expr as ast.IfExpr
8602 assert else_expr.cond is ast.EmptyExpr
8603 assert else_expr.stmts.len == 1
8604 assert else_expr.stmts[0] is ast.ReturnStmt
8605}
8606
8607fn test_return_match_void_branch_stays_side_effect() {
8608 mut t := create_test_transformer()
8609 match_expr := ast.MatchExpr{
8610 expr: ast.Expr(ast.Ident{
8611 name: 'name'
8612 })
8613 branches: [
8614 ast.MatchBranch{
8615 cond: [
8616 ast.Expr(ast.StringLiteral{
8617 kind: .v
8618 value: 'bad'
8619 }),
8620 ]
8621 stmts: [
8622 ast.Stmt(ast.ExprStmt{
8623 expr: ast.Expr(ast.CallExpr{
8624 lhs: ast.Expr(ast.Ident{
8625 name: 'eprintln_exit'
8626 })
8627 })
8628 }),
8629 ]
8630 },
8631 ast.MatchBranch{
8632 stmts: [
8633 ast.Stmt(ast.ExprStmt{
8634 expr: ast.Expr(ast.Ident{
8635 name: 'fallback'
8636 })
8637 }),
8638 ]
8639 },
8640 ]
8641 }
8642 expanded := t.try_expand_return_match_expr(ast.ReturnStmt{
8643 exprs: [ast.Expr(match_expr)]
8644 }) or {
8645 assert false, 'return match should expand'
8646 return
8647 }
8648 if_stmt := (expanded[0] as ast.ExprStmt).expr as ast.IfExpr
8649 assert if_stmt.stmts.len == 1
8650 assert if_stmt.stmts[0] is ast.ExprStmt
8651 assert if_stmt.stmts[0] !is ast.ReturnStmt
8652}
8653
8654fn test_return_match_or_branch_preserves_success_payload_return() {
8655 mut t := create_test_transformer()
8656 t.cur_fn_returns_option = true
8657 match_expr := ast.MatchExpr{
8658 expr: ast.Expr(ast.Ident{
8659 name: 'name'
8660 })
8661 branches: [
8662 ast.MatchBranch{
8663 cond: [
8664 ast.Expr(ast.StringLiteral{
8665 kind: .v
8666 value: 'rune'
8667 }),
8668 ]
8669 stmts: [
8670 ast.Stmt(ast.ExprStmt{
8671 expr: ast.Expr(ast.OrExpr{
8672 expr: ast.Expr(ast.CallExpr{
8673 lhs: ast.Expr(ast.Ident{
8674 name: 'lookup_type'
8675 })
8676 })
8677 stmts: [
8678 ast.Stmt(ast.ReturnStmt{
8679 exprs: [
8680 ast.Expr(ast.Ident{
8681 name: 'none'
8682 }),
8683 ]
8684 }),
8685 ]
8686 })
8687 }),
8688 ]
8689 },
8690 ast.MatchBranch{
8691 stmts: [
8692 ast.Stmt(ast.ExprStmt{
8693 expr: ast.Expr(ast.Ident{
8694 name: 'none'
8695 })
8696 }),
8697 ]
8698 },
8699 ]
8700 }
8701 expanded := t.try_expand_return_match_expr(ast.ReturnStmt{
8702 exprs: [ast.Expr(match_expr)]
8703 }) or {
8704 assert false, 'return match should expand'
8705 return
8706 }
8707 assert expanded.len == 1
8708 if_stmt := (expanded[0] as ast.ExprStmt).expr as ast.IfExpr
8709 assert if_stmt.stmts.len == 1
8710 assert if_stmt.stmts[0] is ast.ReturnStmt
8711 ret := if_stmt.stmts[0] as ast.ReturnStmt
8712 assert ret.exprs.len == 1
8713 assert ret.exprs[0] is ast.UnsafeExpr, 'or payload should stay in value context, got ${ret.exprs[0].type_name()}'
8714 unsafe_expr := ret.exprs[0] as ast.UnsafeExpr
8715 assert unsafe_expr.stmts.len >= 3
8716 last_stmt := unsafe_expr.stmts[unsafe_expr.stmts.len - 1]
8717 assert last_stmt is ast.ExprStmt
8718 payload_expr := (last_stmt as ast.ExprStmt).expr
8719 assert payload_expr is ast.SelectorExpr, 'expected final or expression to return payload selector, got ${payload_expr.type_name()}'
8720 payload_selector := payload_expr as ast.SelectorExpr
8721 assert payload_selector.rhs.name == 'data'
8722}
8723
8724fn test_transform_return_match_reprocesses_nested_return_if_expr() {
8725 mut t := create_test_transformer()
8726 t.cur_fn_returns_option = true
8727 match_expr := ast.MatchExpr{
8728 expr: ast.Expr(ast.Ident{
8729 name: 'expr'
8730 })
8731 branches: [
8732 ast.MatchBranch{
8733 cond: [
8734 ast.Expr(ast.Ident{
8735 name: 'CallOrCastExpr'
8736 }),
8737 ]
8738 stmts: [
8739 ast.Stmt(ast.ExprStmt{
8740 expr: ast.Expr(ast.IfExpr{
8741 cond: ast.Expr(ast.Ident{
8742 name: 'is_type_expr'
8743 })
8744 stmts: [
8745 ast.Stmt(ast.ExprStmt{
8746 expr: ast.Expr(ast.StringLiteral{
8747 kind: .v
8748 value: 'type-name'
8749 })
8750 }),
8751 ]
8752 else_expr: ast.Expr(ast.IfExpr{
8753 cond: ast.empty_expr
8754 stmts: [
8755 ast.Stmt(ast.ExprStmt{
8756 expr: ast.Expr(ast.Ident{
8757 name: 'none'
8758 })
8759 }),
8760 ]
8761 })
8762 })
8763 }),
8764 ]
8765 },
8766 ast.MatchBranch{
8767 stmts: [
8768 ast.Stmt(ast.ExprStmt{
8769 expr: ast.Expr(ast.Ident{
8770 name: 'none'
8771 })
8772 }),
8773 ]
8774 },
8775 ]
8776 }
8777 transformed := t.transform_stmts([
8778 ast.Stmt(ast.ReturnStmt{
8779 exprs: [ast.Expr(match_expr)]
8780 }),
8781 ])
8782 assert transformed.len == 1
8783 assert transformed[0] is ast.ExprStmt
8784 top_if := (transformed[0] as ast.ExprStmt).expr as ast.IfExpr
8785 assert top_if.stmts.len == 1
8786 assert top_if.stmts[0] is ast.ExprStmt
8787 nested_if := (top_if.stmts[0] as ast.ExprStmt).expr as ast.IfExpr
8788 assert nested_if.stmts.len == 1
8789 assert nested_if.stmts[0] is ast.ReturnStmt
8790 assert nested_if.else_expr is ast.IfExpr
8791 nested_else := nested_if.else_expr as ast.IfExpr
8792 assert nested_else.cond is ast.EmptyExpr
8793 assert nested_else.stmts.len == 1
8794 assert nested_else.stmts[0] is ast.ReturnStmt
8795}
8796
8797fn test_return_match_expands_if_guard_unsafe_branch_value_to_statement_returns() {
8798 mut t := create_test_transformer()
8799 nested_guard_value := ast.Expr(ast.UnsafeExpr{
8800 stmts: [
8801 ast.Stmt(ast.AssignStmt{
8802 op: .decl_assign
8803 lhs: [ast.Expr(ast.Ident{
8804 name: '_or_t2'
8805 })]
8806 rhs: [
8807 ast.Expr(ast.CallExpr{
8808 lhs: ast.Expr(ast.Ident{
8809 name: 'lookup_builtin'
8810 })
8811 }),
8812 ]
8813 }),
8814 ast.Stmt(ast.ExprStmt{
8815 expr: ast.Expr(ast.IfExpr{
8816 cond: ast.Expr(ast.Ident{
8817 name: '_or_t2_ok'
8818 })
8819 stmts: [
8820 ast.Stmt(ast.ExprStmt{
8821 expr: ast.Expr(ast.Ident{
8822 name: 'const_expr2'
8823 })
8824 }),
8825 ]
8826 else_expr: ast.Expr(ast.IfExpr{
8827 cond: ast.empty_expr
8828 stmts: [
8829 ast.Stmt(ast.ExprStmt{
8830 expr: ast.Expr(ast.Ident{
8831 name: 'none'
8832 })
8833 }),
8834 ]
8835 })
8836 })
8837 }),
8838 ]
8839 })
8840 outer_guard_value := ast.Expr(ast.UnsafeExpr{
8841 stmts: [
8842 ast.Stmt(ast.AssignStmt{
8843 op: .decl_assign
8844 lhs: [ast.Expr(ast.Ident{
8845 name: '_or_t1'
8846 })]
8847 rhs: [
8848 ast.Expr(ast.CallExpr{
8849 lhs: ast.Expr(ast.Ident{
8850 name: 'lookup_current'
8851 })
8852 }),
8853 ]
8854 }),
8855 ast.Stmt(ast.ExprStmt{
8856 expr: ast.Expr(ast.IfExpr{
8857 cond: ast.Expr(ast.Ident{
8858 name: '_or_t1_ok'
8859 })
8860 stmts: [
8861 ast.Stmt(ast.ExprStmt{
8862 expr: ast.Expr(ast.Ident{
8863 name: 'const_expr'
8864 })
8865 }),
8866 ]
8867 else_expr: nested_guard_value
8868 })
8869 }),
8870 ]
8871 })
8872 stmts := t.return_stmts_for_branch_expr(outer_guard_value, true)
8873 assert stmts.len == 2
8874 assert stmts[0] is ast.AssignStmt
8875 assert stmts[1] is ast.ExprStmt
8876 outer_if := (stmts[1] as ast.ExprStmt).expr as ast.IfExpr
8877 assert outer_if.stmts.len == 1
8878 assert outer_if.stmts[0] is ast.ReturnStmt
8879 assert outer_if.else_expr is ast.IfExpr
8880 outer_else := outer_if.else_expr as ast.IfExpr
8881 assert outer_else.cond is ast.EmptyExpr
8882 assert outer_else.stmts.len == 2
8883 assert outer_else.stmts[0] is ast.AssignStmt
8884 assert outer_else.stmts[1] is ast.ExprStmt
8885 inner_if := (outer_else.stmts[1] as ast.ExprStmt).expr as ast.IfExpr
8886 assert inner_if.stmts.len == 1
8887 assert inner_if.stmts[0] is ast.ReturnStmt
8888 assert inner_if.else_expr is ast.IfExpr
8889 inner_else := inner_if.else_expr as ast.IfExpr
8890 assert inner_else.cond is ast.EmptyExpr
8891 assert inner_else.stmts.len == 1
8892 assert inner_else.stmts[0] is ast.ReturnStmt
8893}
8894
8895fn test_return_match_expr_without_else_keeps_empty_terminal_branch() {
8896 mut t := create_test_transformer()
8897 match_expr := ast.MatchExpr{
8898 expr: ast.Expr(ast.Ident{
8899 name: 'kind'
8900 })
8901 branches: [
8902 ast.MatchBranch{
8903 cond: [
8904 ast.Expr(ast.StringLiteral{
8905 kind: .v
8906 value: 'a'
8907 }),
8908 ]
8909 stmts: [
8910 ast.Stmt(ast.ExprStmt{
8911 expr: ast.Expr(ast.StringLiteral{
8912 kind: .v
8913 value: 'alpha'
8914 })
8915 }),
8916 ]
8917 },
8918 ast.MatchBranch{
8919 cond: [
8920 ast.Expr(ast.StringLiteral{
8921 kind: .v
8922 value: 'b'
8923 }),
8924 ]
8925 stmts: [
8926 ast.Stmt(ast.ExprStmt{
8927 expr: ast.Expr(ast.StringLiteral{
8928 kind: .v
8929 value: 'beta'
8930 })
8931 }),
8932 ]
8933 },
8934 ]
8935 }
8936 expanded := t.try_expand_return_match_expr(ast.ReturnStmt{
8937 exprs: [ast.Expr(match_expr)]
8938 }) or {
8939 assert false, 'return match should expand'
8940 return
8941 }
8942 assert expanded.len == 1
8943 assert expanded[0] is ast.ExprStmt
8944 if_stmt := (expanded[0] as ast.ExprStmt).expr
8945 assert if_stmt is ast.IfExpr
8946 if_expr := if_stmt as ast.IfExpr
8947 assert if_expr.stmts.len == 1
8948 assert if_expr.stmts[0] is ast.ReturnStmt
8949 assert if_expr.else_expr is ast.IfExpr
8950 else_if := if_expr.else_expr as ast.IfExpr
8951 assert else_if.cond !is ast.EmptyExpr
8952 assert else_if.stmts.len == 1
8953 assert else_if.stmts[0] is ast.ReturnStmt
8954 assert else_if.else_expr is ast.EmptyExpr
8955}
8956
8957fn test_transformer_preserves_lifetime_method_signature_and_nested_generic_return_type() {
8958 files := transform_code_for_test('
8959struct Ignore {}
8960
8961struct DirEntry {}
8962
8963struct Match[T] {
8964 value T
8965}
8966
8967struct IgnoreMatch[^a] {
8968 ig &^a Ignore
8969}
8970
8971fn (ig &^a Ignore) matched_dir_entry[^a](dent &DirEntry) Match[IgnoreMatch[^a]] {
8972 return Match[IgnoreMatch[^a]]{
8973 value: IgnoreMatch[^a]{
8974 ig: ig
8975 }
8976 }
8977}
8978
8979fn main() {
8980 ig := Ignore{}
8981 dent := DirEntry{}
8982 ig.matched_dir_entry(&dent)
8983}
8984')
8985 assert files.len == 1
8986 file := files[0]
8987 mut saw_method := false
8988 mut saw_call := false
8989 for stmt in file.stmts {
8990 if stmt is ast.FnDecl && stmt.name == 'matched_dir_entry' {
8991 saw_method = true
8992 assert stmt.is_method
8993 assert stmt.stmts.len > 0
8994 assert stmt.receiver.typ is ast.Type
8995 receiver_type := stmt.receiver.typ as ast.Type
8996 assert receiver_type is ast.PointerType
8997 receiver_ptr := receiver_type as ast.PointerType
8998 assert receiver_ptr.lifetime == 'a'
8999 assert stmt.typ.generic_params.len == 1
9000 assert stmt.typ.generic_params[0] is ast.LifetimeExpr
9001 assert (stmt.typ.generic_params[0] as ast.LifetimeExpr).name == 'a'
9002 assert stmt.typ.return_type is ast.Type
9003 return_type := stmt.typ.return_type as ast.Type
9004 assert return_type is ast.GenericType
9005 outer_generic := return_type as ast.GenericType
9006 assert outer_generic.name is ast.Ident
9007 assert (outer_generic.name as ast.Ident).name == 'Match'
9008 assert outer_generic.params.len == 1
9009 assert outer_generic.params[0] is ast.Type
9010 inner_type := outer_generic.params[0] as ast.Type
9011 assert inner_type is ast.GenericType
9012 inner_generic := inner_type as ast.GenericType
9013 assert inner_generic.name is ast.Ident
9014 assert (inner_generic.name as ast.Ident).name == 'IgnoreMatch'
9015 assert inner_generic.params.len == 1
9016 assert inner_generic.params[0] is ast.LifetimeExpr
9017 assert (inner_generic.params[0] as ast.LifetimeExpr).name == 'a'
9018 }
9019 if stmt is ast.FnDecl && stmt.name == 'main' {
9020 assert stmt.stmts.len == 3
9021 assert stmt.stmts[2] is ast.ExprStmt
9022 expr_stmt := stmt.stmts[2] as ast.ExprStmt
9023 assert expr_stmt.expr is ast.CallExpr
9024 call := expr_stmt.expr as ast.CallExpr
9025 assert call.lhs is ast.Ident
9026 assert (call.lhs as ast.Ident).name == 'Ignore__matched_dir_entry'
9027 assert call.args.len == 2
9028 saw_call = true
9029 }
9030 }
9031 assert saw_method
9032 assert saw_call
9033}
9034
9035fn test_nested_filter_map_filter_receiver_cache_names_are_unique() {
9036 files := transform_code_for_test('
9037fn nested_filter_map_filter(xs []int, keep []string) []string {
9038 unknown := xs.filter(it > 0).map(it.str()).filter(it !in keep)
9039 return unknown
9040}
9041')
9042 assert files.len == 1
9043 mut recv_names := []string{}
9044 for stmt in files[0].stmts {
9045 if stmt is ast.FnDecl && stmt.name == 'nested_filter_map_filter' {
9046 for inner in stmt.stmts {
9047 if inner is ast.AssignStmt {
9048 for lhs in inner.lhs {
9049 if lhs is ast.Ident && lhs.name.starts_with('_filter_recv') {
9050 recv_names << lhs.name
9051 }
9052 }
9053 }
9054 }
9055 }
9056 }
9057 assert recv_names.len >= 2, 'expected receiver caches for nested map/filter, got ${recv_names}'
9058 mut seen := map[string]bool{}
9059 for name in recv_names {
9060 assert name !in seen, 'duplicate receiver cache name: ${name}'
9061 seen[name] = true
9062 }
9063}
9064
9065fn test_for_in_filter_receiver_temp_is_hoisted_before_loop() {
9066 files := transform_code_for_test('
9067struct ImportSymbol {
9068 name string
9069}
9070
9071struct Import {
9072 syms []ImportSymbol
9073}
9074
9075fn use_filtered_imports(imports []Import, owner string) int {
9076 mut count := 0
9077 for import_sym in imports.filter(it.syms.any(it.name == owner)) {
9078 _ = import_sym
9079 count++
9080 }
9081 return count
9082}
9083')
9084 assert files.len == 1
9085 mut found_fn := false
9086 for stmt in files[0].stmts {
9087 if stmt is ast.FnDecl && stmt.name == 'use_filtered_imports' {
9088 found_fn = true
9089 mut saw_filter_temp_decl := false
9090 mut found_outer_loop := false
9091 for inner in stmt.stmts {
9092 if inner is ast.AssignStmt && inner.op == .decl_assign && inner.lhs.len > 0 {
9093 if lhs := ident_name_from_expr_for_test(inner.lhs[0]) {
9094 if lhs.starts_with('_filter_t') {
9095 saw_filter_temp_decl = true
9096 }
9097 }
9098 }
9099 if inner is ast.ForStmt && inner.stmts.len > 0 {
9100 first_body := inner.stmts[0]
9101 if first_body is ast.AssignStmt && first_body.lhs.len > 0 {
9102 if lhs := ident_name_from_expr_for_test(first_body.lhs[0]) {
9103 if lhs == 'import_sym' {
9104 found_outer_loop = true
9105 assert saw_filter_temp_decl
9106 for loop_stmt in inner.stmts {
9107 if loop_stmt is ast.AssignStmt && loop_stmt.op == .decl_assign
9108 && loop_stmt.lhs.len > 0 {
9109 if loop_lhs := ident_name_from_expr_for_test(loop_stmt.lhs[0]) {
9110 assert !loop_lhs.starts_with('_filter_t')
9111 }
9112 }
9113 }
9114 }
9115 }
9116 }
9117 }
9118 }
9119 assert found_outer_loop
9120 }
9121 }
9122 assert found_fn
9123}
9124
9125fn test_filter_map_receiver_pending_stmts_precede_receiver_cache() {
9126 files := transform_code_for_test('
9127fn mapped_positive(xs []int) []string {
9128 parts := xs.filter(it > 0).map(it.str())
9129 return parts
9130}
9131')
9132 assert files.len == 1
9133 mut filter_pos := -1
9134 mut recv_pos := -1
9135 for stmt in files[0].stmts {
9136 if stmt is ast.FnDecl && stmt.name == 'mapped_positive' {
9137 for i, inner in stmt.stmts {
9138 if inner is ast.AssignStmt && inner.op == .decl_assign && inner.lhs.len > 0 {
9139 if lhs := ident_name_from_expr_for_test(inner.lhs[0]) {
9140 if lhs.starts_with('_filter_t') && filter_pos == -1 {
9141 filter_pos = i
9142 }
9143 if lhs.starts_with('_filter_recv') && recv_pos == -1 {
9144 recv_pos = i
9145 }
9146 }
9147 }
9148 }
9149 }
9150 }
9151 assert filter_pos >= 0
9152 assert recv_pos >= 0
9153 assert filter_pos < recv_pos
9154}
9155
9156fn test_direct_or_assign_fn_pointer_result_declares_final_lhs() {
9157 files := transform_code_for_test('
9158fn use_sql(sql_from_v fn (int) !string) !string {
9159 mut col_typ := sql_from_v(1) or {
9160 sql_from_v(2)!
9161 }
9162 if col_typ == "" {
9163 return ""
9164 }
9165 return col_typ
9166}
9167')
9168 assert files.len == 1
9169 mut saw_fn := false
9170 mut col_decl_count := 0
9171 for stmt in files[0].stmts {
9172 if stmt is ast.FnDecl && stmt.name == 'use_sql' {
9173 saw_fn = true
9174 for inner in stmt.stmts {
9175 if inner is ast.AssignStmt && inner.op == .decl_assign && inner.lhs.len > 0 {
9176 if lhs := ident_name_from_expr_for_test(inner.lhs[0]) {
9177 if lhs == 'col_typ' {
9178 col_decl_count++
9179 }
9180 }
9181 }
9182 }
9183 }
9184 }
9185 assert saw_fn
9186 assert col_decl_count == 1
9187}
9188
9189fn test_sql_orm_vattribute_array_keeps_builtin_element_type() {
9190 attribute_kind_type := types.Type(types.Enum{
9191 name: 'AttributeKind'
9192 })
9193 vattribute_type := types.Type(types.Struct{
9194 name: 'VAttribute'
9195 fields: [
9196 types.Field{
9197 name: 'name'
9198 typ: types.string_
9199 },
9200 types.Field{
9201 name: 'has_arg'
9202 typ: types.Type(types.bool_)
9203 },
9204 types.Field{
9205 name: 'arg'
9206 typ: types.string_
9207 },
9208 types.Field{
9209 name: 'kind'
9210 typ: attribute_kind_type
9211 },
9212 ]
9213 })
9214 table_field_type := types.Type(types.Struct{
9215 name: 'orm__TableField'
9216 fields: [
9217 types.Field{
9218 name: 'name'
9219 typ: types.string_
9220 },
9221 types.Field{
9222 name: 'typ'
9223 typ: types.Type(types.int_)
9224 },
9225 types.Field{
9226 name: 'nullable'
9227 typ: types.Type(types.bool_)
9228 },
9229 types.Field{
9230 name: 'default_val'
9231 typ: types.string_
9232 },
9233 types.Field{
9234 name: 'attrs'
9235 typ: types.Type(types.Array{
9236 elem_type: vattribute_type
9237 })
9238 },
9239 types.Field{
9240 name: 'is_arr'
9241 typ: types.Type(types.bool_)
9242 },
9243 ]
9244 })
9245 mut builtin_scope := types.new_scope(unsafe { nil })
9246 builtin_scope.insert('AttributeKind', attribute_kind_type)
9247 builtin_scope.insert('VAttribute', vattribute_type)
9248 mut orm_scope := types.new_scope(unsafe { nil })
9249 orm_scope.insert('TableField', table_field_type)
9250 mut t := create_test_transformer()
9251 t.cur_module = 'gitly'
9252 t.cached_scopes = {
9253 'builtin': builtin_scope
9254 'orm': orm_scope
9255 }
9256 pos := token.Pos{
9257 id: 8123
9258 }
9259 attr := ast.Attribute{
9260 name: 'sql'
9261 value: ast.StringLiteral{
9262 kind: .v
9263 value: "'serial'"
9264 pos: pos
9265 }
9266 }
9267 expr := t.sql_orm_table_field_expr(types.Field{
9268 name: 'id'
9269 typ: types.Type(types.int_)
9270 attributes: [attr]
9271 }, pos) or { panic('expected orm table field expression') }
9272 result := t.transform_expr(expr)
9273 assert result is ast.InitExpr
9274 init := result as ast.InitExpr
9275 mut saw_attrs := false
9276 for field in init.fields {
9277 if field.name != 'attrs' {
9278 continue
9279 }
9280 saw_attrs = true
9281 assert field.value is ast.CallExpr
9282 call := field.value as ast.CallExpr
9283 assert call.args.len == 4
9284 assert call.args[2] is ast.KeywordOperator
9285 sizeof_arg := call.args[2] as ast.KeywordOperator
9286 assert sizeof_arg.exprs.len == 1
9287 assert sizeof_arg.exprs[0] is ast.Ident
9288 assert (sizeof_arg.exprs[0] as ast.Ident).name == 'VAttribute'
9289 assert call.args[3] is ast.ArrayInitExpr
9290 inner := call.args[3] as ast.ArrayInitExpr
9291 assert inner.typ is ast.Type
9292 inner_typ := inner.typ as ast.Type
9293 assert inner_typ is ast.ArrayType
9294 inner_arr_typ := inner_typ as ast.ArrayType
9295 assert inner_arr_typ.elem_type is ast.Ident
9296 assert (inner_arr_typ.elem_type as ast.Ident).name == 'VAttribute'
9297 assert inner.exprs.len == 1
9298 assert inner.exprs[0] is ast.InitExpr
9299 attr_init := inner.exprs[0] as ast.InitExpr
9300 assert attr_init.typ is ast.Ident
9301 assert (attr_init.typ as ast.Ident).name == 'VAttribute'
9302 }
9303 assert saw_attrs
9304}
9305
9306fn test_replace_it_ident_keeps_nested_any_body_scope() {
9307 mut t := create_test_transformer()
9308 nested_any := ast.Expr(ast.CallOrCastExpr{
9309 lhs: ast.Expr(ast.SelectorExpr{
9310 lhs: ast.Expr(ast.SelectorExpr{
9311 lhs: ast.Expr(ast.Ident{
9312 name: 'it'
9313 })
9314 rhs: ast.Ident{
9315 name: 'exprs'
9316 }
9317 })
9318 rhs: ast.Ident{
9319 name: 'any'
9320 }
9321 })
9322 expr: ast.Expr(ast.CallExpr{
9323 lhs: ast.Expr(ast.SelectorExpr{
9324 lhs: ast.Expr(ast.Ident{
9325 name: 'g'
9326 })
9327 rhs: ast.Ident{
9328 name: 'match_must_reset_if'
9329 }
9330 })
9331 args: [
9332 ast.Expr(ast.Ident{
9333 name: 'it'
9334 }),
9335 ]
9336 })
9337 })
9338 replaced := t.replace_it_ident(nested_any, '_outer_it')
9339 assert replaced is ast.CallOrCastExpr
9340 call := replaced as ast.CallOrCastExpr
9341 assert call.lhs is ast.SelectorExpr
9342 outer_sel := call.lhs as ast.SelectorExpr
9343 assert outer_sel.lhs is ast.SelectorExpr
9344 exprs_sel := outer_sel.lhs as ast.SelectorExpr
9345 assert exprs_sel.lhs is ast.Ident
9346 assert (exprs_sel.lhs as ast.Ident).name == '_outer_it'
9347 assert call.expr is ast.CallExpr
9348 inner_call := call.expr as ast.CallExpr
9349 assert inner_call.args.len == 1
9350 assert inner_call.args[0] is ast.Ident
9351 assert (inner_call.args[0] as ast.Ident).name == 'it'
9352}
9353
9354fn test_smartcast_and_all_condition_hoists_temp_before_inner_if() {
9355 files := transform_code_for_test('
9356struct Aggregate {
9357 types []int
9358}
9359
9360struct Other {}
9361
9362type Info = Aggregate | Other
9363
9364struct Sym {
9365 info Info
9366}
9367
9368fn use_all(sym Sym) bool {
9369 if sym.info is Aggregate && sym.info.types.all(it > 0) {
9370 return true
9371 }
9372 return false
9373}
9374')
9375 assert files.len == 1
9376 mut found := false
9377 for stmt in files[0].stmts {
9378 if stmt is ast.FnDecl && stmt.name == 'use_all' {
9379 found = true
9380 assert stmt.stmts.len >= 2
9381 assert stmt.stmts[0] is ast.ExprStmt
9382 outer_expr_stmt := stmt.stmts[0] as ast.ExprStmt
9383 assert outer_expr_stmt.expr is ast.IfExpr
9384 outer_if := outer_expr_stmt.expr as ast.IfExpr
9385 assert outer_if.stmts.len >= 3, 'expected condition expansion before inner if, got ${outer_if.stmts.len} stmts'
9386 assert outer_if.stmts[0] is ast.AssignStmt
9387 init_stmt := outer_if.stmts[0] as ast.AssignStmt
9388 assert init_stmt.lhs.len == 1
9389 assert init_stmt.lhs[0] is ast.ModifierExpr
9390 init_lhs := init_stmt.lhs[0] as ast.ModifierExpr
9391 assert init_lhs.expr is ast.Ident
9392 assert (init_lhs.expr as ast.Ident).name.starts_with('_filter_t')
9393 assert outer_if.stmts[1] is ast.ForStmt
9394 assert outer_if.stmts[2] is ast.ExprStmt
9395 inner_expr_stmt := outer_if.stmts[2] as ast.ExprStmt
9396 assert inner_expr_stmt.expr is ast.IfExpr
9397 inner_if := inner_expr_stmt.expr as ast.IfExpr
9398 assert inner_if.cond is ast.Ident
9399 assert (inner_if.cond as ast.Ident).name.starts_with('_filter_t')
9400 }
9401 }
9402 assert found
9403}
9404
9405fn test_smartcast_and_else_if_label_branch_is_not_duplicated() {
9406 files := transform_code_for_test('
9407struct A {}
9408struct B {}
9409struct D {}
9410
9411type Node = A | B | D
9412
9413fn labeled_else_branch(left Node, right Node) {
9414 if left is A && right is B {
9415 return
9416 } else if right is D {
9417 out: for i := 0; i < 1; i++ {
9418 continue out
9419 }
9420 }
9421}
9422')
9423 assert count_label_name_in_files(files, 'labeled_else_branch', 'out') == 1
9424}
9425
9426fn test_smartcasted_field_method_receiver_after_explicit_parent_cast() {
9427 files := transform_code_for_test('
9428type Expr = Ident | IndexExpr
9429
9430struct Ident {
9431 flag bool
9432}
9433
9434fn (i &Ident) is_mut() bool {
9435 return i.flag
9436}
9437
9438struct IndexExpr {
9439 left Expr
9440 index Expr
9441}
9442
9443fn uses_smartcasted_field_method(init Expr) bool {
9444 return init is IndexExpr && (init as IndexExpr).left is Ident
9445 && (init as IndexExpr).left.is_mut()
9446}
9447')
9448 assert files.len == 1
9449 mut call_names := []string{}
9450 for stmt in files[0].stmts {
9451 if stmt is ast.FnDecl && stmt.name == 'uses_smartcasted_field_method' {
9452 for inner in stmt.stmts {
9453 collect_call_names_from_stmt(inner, mut call_names)
9454 }
9455 }
9456 }
9457 assert 'Ident__is_mut' in call_names, 'expected smartcasted method call, got ${call_names}'
9458}
9459
9460fn struct_field_names(file ast.File, struct_name string) []string {
9461 for stmt in file.stmts {
9462 if stmt is ast.StructDecl && stmt.name == struct_name {
9463 mut names := []string{cap: stmt.fields.len}
9464 for field in stmt.fields {
9465 names << field.name
9466 }
9467 return names
9468 }
9469 }
9470 return []string{}
9471}
9472
9473fn parse_code_with_defines_for_test(code string, defines []string) []ast.File {
9474 return parse_code_with_prefs_for_test(code, .cleanc, defines)
9475}
9476
9477fn parse_code_with_prefs_for_test(code string, backend vpref.Backend, defines []string) []ast.File {
9478 prefs := &vpref.Preferences{
9479 backend: backend
9480 no_parallel: true
9481 user_defines: defines
9482 }
9483 return parse_code_with_custom_prefs_for_test(code, prefs)
9484}
9485
9486fn parse_code_with_custom_prefs_for_test(code string, prefs &vpref.Preferences) []ast.File {
9487 tmp_file := '/tmp/v2_parser_cond_field_test_${os.getpid()}.v'
9488 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
9489 defer {
9490 os.rm(tmp_file) or {}
9491 }
9492 mut file_set := token.FileSet.new()
9493 mut par := parser.Parser.new(prefs)
9494 return par.parse_files([tmp_file], mut file_set)
9495}
9496
9497fn test_struct_comptime_if_field_block_default_branch() {
9498 files := parse_code_with_defines_for_test('
9499module main
9500
9501struct Container {
9502$if my_feature ? {
9503 feature_val string = "on"
9504} $else {
9505 feature_val string = "off"
9506}
9507 always_present int
9508}
9509', [])
9510 assert files.len == 1
9511 assert struct_field_names(files[0], 'Container') == ['feature_val', 'always_present']
9512}
9513
9514fn test_struct_comptime_if_field_block_selected_branch() {
9515 files := parse_code_with_defines_for_test('
9516module main
9517
9518struct Container {
9519$if my_feature ? {
9520 feature_val string = "on"
9521} $else {
9522 feature_val string = "off"
9523}
9524 always_present int
9525}
9526', [
9527 'my_feature',
9528 ])
9529 assert files.len == 1
9530 assert struct_field_names(files[0], 'Container') == ['feature_val', 'always_present']
9531}
9532
9533fn test_struct_comptime_if_field_block_omits_when_unset() {
9534 files := parse_code_with_defines_for_test('
9535module main
9536
9537struct Container {
9538$if optional ? {
9539 opt_field int
9540}
9541 always int
9542}
9543', [])
9544 assert files.len == 1
9545 assert struct_field_names(files[0], 'Container') == ['always']
9546}
9547
9548fn test_struct_comptime_else_if_chain_picks_first_match() {
9549 files := parse_code_with_defines_for_test('
9550module main
9551
9552struct Container {
9553$if feat_a ? {
9554 tag string = "A"
9555} $else $if feat_b ? {
9556 tag string = "B"
9557} $else {
9558 tag string = "default"
9559}
9560}
9561', [
9562 'feat_b',
9563 ])
9564 assert files.len == 1
9565 names := struct_field_names(files[0], 'Container')
9566 assert names == ['tag']
9567}
9568
9569fn test_struct_field_if_attribute_elides_when_false() {
9570 files := parse_code_with_defines_for_test('
9571module main
9572
9573struct Container {
9574 name string
9575 debug_only int @[if absent_flag ?]
9576}
9577', [])
9578 assert files.len == 1
9579 assert struct_field_names(files[0], 'Container') == ['name']
9580}
9581
9582fn test_struct_field_if_attribute_keeps_when_true() {
9583 files := parse_code_with_defines_for_test('
9584module main
9585
9586struct Container {
9587 name string
9588 debug_only int @[if present_flag ?]
9589}
9590', [
9591 'present_flag',
9592 ])
9593 assert files.len == 1
9594 assert struct_field_names(files[0], 'Container') == ['name', 'debug_only']
9595}
9596
9597fn test_struct_pkgconfig_fields_inactive_for_cross_target() {
9598 if !vpref.comptime_pkgconfig_value('sqlite3') {
9599 return
9600 }
9601 prefs := &vpref.Preferences{
9602 backend: .cleanc
9603 no_parallel: true
9604 target_os: 'cross'
9605 output_cross_c: true
9606 }
9607 files := parse_code_with_custom_prefs_for_test('
9608module main
9609
9610struct Container {
9611$if $pkgconfig("sqlite3") {
9612 host_field HostSqliteField
9613} $else {
9614 cross_field int
9615}
9616attr_field int @[if $pkgconfig("sqlite3")]
9617 always int
9618}
9619',
9620 prefs)
9621 assert files.len == 1
9622 assert struct_field_names(files[0], 'Container') == ['cross_field', 'always']
9623}
9624
9625fn test_transform_pkgconfig_branch_inactive_for_cross_target() {
9626 if !vpref.comptime_pkgconfig_value('sqlite3') {
9627 return
9628 }
9629 prefs := &vpref.Preferences{
9630 backend: .cleanc
9631 no_parallel: true
9632 target_os: 'cross'
9633 output_cross_c: true
9634 }
9635 files := transform_code_with_prefs_for_test('
9636module main
9637
9638fn pick() int {
9639 $if $pkgconfig("sqlite3") {
9640 return 1
9641 } $else {
9642 return 2
9643 }
9644}
9645',
9646 prefs)
9647 assert files.len == 1
9648 for stmt in files[0].stmts {
9649 if stmt is ast.FnDecl && stmt.name == 'pick' {
9650 assert stmt.stmts.len == 1
9651 assert stmt.stmts[0] is ast.ReturnStmt
9652 ret := stmt.stmts[0] as ast.ReturnStmt
9653 assert ret.exprs.len == 1
9654 assert ret.exprs[0] is ast.BasicLiteral
9655 lit := ret.exprs[0] as ast.BasicLiteral
9656 assert lit.value == '2'
9657 return
9658 }
9659 }
9660 assert false
9661}
9662
9663// Regression: `$else` starts on the line after `}`. The auto-inserted `;`
9664// between `}` and `$` must not hide the `$else` branch.
9665fn test_struct_comptime_else_on_next_line() {
9666 files := parse_code_with_defines_for_test('
9667module main
9668
9669struct Container {
9670$if my_feature ? {
9671 val string = "on"
9672}
9673$else {
9674 val string = "off"
9675}
9676 always int
9677}
9678', [])
9679 assert files.len == 1
9680 assert struct_field_names(files[0], 'Container') == ['val', 'always']
9681}
9682
9683// Regression: `{` starts on the line after `$if cond ?`. The scanner inserts
9684// a `;` after `?`, which must not block the opening brace.
9685fn test_struct_comptime_if_lcbr_on_next_line() {
9686 files := parse_code_with_defines_for_test('
9687module main
9688
9689struct Container {
9690$if my_feature ?
9691{
9692 val string = "on"
9693}
9694$else
9695{
9696 val string = "off"
9697}
9698 always int
9699}
9700', [
9701 'my_feature',
9702 ])
9703 assert files.len == 1
9704 assert struct_field_names(files[0], 'Container') == ['val', 'always']
9705}
9706
9707// Regression: `$else $if` chain where each `$else` starts on a new line.
9708fn test_struct_comptime_else_if_on_next_line() {
9709 files := parse_code_with_defines_for_test('
9710module main
9711
9712struct Container {
9713$if feat_a ? {
9714 tag string = "A"
9715}
9716$else $if feat_b ? {
9717 tag string = "B"
9718}
9719$else {
9720 tag string = "default"
9721}
9722}
9723', [
9724 'feat_b',
9725 ])
9726 assert files.len == 1
9727 assert struct_field_names(files[0], 'Container') == ['tag']
9728}
9729
9730// Native backend flags are recognized by the shared `pref.comptime_flag_value`.
9731// Confirms the parser sees the same flag set the transformer does.
9732fn test_struct_comptime_native_backend_flags() {
9733 files := parse_code_with_prefs_for_test('
9734module main
9735
9736struct Container {
9737$if tinyc ? {
9738 tinyc_field string
9739}
9740$if no_backtrace ? {
9741 no_backtrace_field string
9742}
9743$if builtin_write_buf_to_fd_should_use_c_write ? {
9744 write_field string
9745}
9746$if new_int ? {
9747 never_field string
9748}
9749 always int
9750}
9751',
9752 .x64, [])
9753 assert files.len == 1
9754 assert struct_field_names(files[0], 'Container') == ['tinyc_field', 'no_backtrace_field',
9755 'write_field', 'always']
9756}
9757