v / vlib / v2 / transformer / transformer_test.v
9756 lines · 9066 sloc · 234.16 KB
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4// vtest build: macos
5module transformer
6
7import os
8import v2.ast
9import v2.parser
10import v2.pref as vpref
11import v2.token
12import v2.types
13
14// Helper to create a minimal transformer for testing
15fn create_test_transformer() &Transformer {
16 env := &types.Environment{}
17 return &Transformer{
18 pref: &vpref.Preferences{}
19 env: unsafe { env }
20 needed_clone_fns: map[string]string{}
21 needed_array_contains_fns: map[string]ArrayMethodInfo{}
22 needed_array_index_fns: map[string]ArrayMethodInfo{}
23 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
24 local_decl_types: map[string]types.Type{}
25 }
26}
27
28// Helper to create a transformer with a scope containing variable types
29fn create_transformer_with_vars(vars map[string]types.Type) &Transformer {
30 env := &types.Environment{}
31 mut scope := types.new_scope(unsafe { nil })
32 for name, typ in vars {
33 scope.insert(name, value_object_from_type(typ))
34 }
35 return &Transformer{
36 pref: &vpref.Preferences{}
37 env: unsafe { env }
38 scope: scope
39 needed_clone_fns: map[string]string{}
40 needed_array_contains_fns: map[string]ArrayMethodInfo{}
41 needed_array_index_fns: map[string]ArrayMethodInfo{}
42 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
43 local_decl_types: map[string]types.Type{}
44 }
45}
46
47fn test_value_object_from_type_stores_value_symbol_type() {
48 obj := value_object_from_type(types.Type(types.int_))
49 assert obj is types.TypeObject
50 assert obj.typ() is types.Primitive
51}
52
53fn test_worker_clone_owns_mutable_maps() {
54 prefs := &vpref.Preferences{}
55 env := types.Environment.new()
56 mut transformer := Transformer.new_with_pref(env, prefs)
57 transformer.synth_pos_counter = -73
58 scope := types.new_scope(unsafe { nil })
59 transformer.elided_fns = {
60 'main__skipped': true
61 }
62 transformer.cached_scopes = {
63 'main': scope
64 }
65 transformer.cached_fn_scopes = {
66 'main__known': scope
67 }
68 transformer.cached_method_keys = ['main__Foo']
69 transformer.generic_fn_value_names = {
70 'handler': true
71 }
72
73 mut worker := transformer.new_worker_clone(1)
74 assert worker.next_synth_pos().id == -100073
75 worker.elided_fns['main__worker_skipped'] = true
76 worker.cached_scopes['worker'] = scope
77 worker.cached_fn_scopes['main__worker'] = scope
78 worker.cached_method_keys << 'main__Bar'
79 worker.generic_fn_value_names['worker_handler'] = true
80
81 assert 'main__worker_skipped' !in transformer.elided_fns
82 assert 'worker' !in transformer.cached_scopes
83 assert 'main__worker' !in transformer.cached_fn_scopes
84 assert transformer.cached_method_keys == ['main__Foo']
85 assert 'worker_handler' !in transformer.generic_fn_value_names
86
87 transformer.merge_worker(worker)
88 assert transformer.elided_fns['main__worker_skipped']
89 assert transformer.cached_fn_scopes['main__worker'] == scope
90}
91
92fn transform_code_for_test(code string) []ast.File {
93 prefs := &vpref.Preferences{
94 backend: .cleanc
95 no_parallel: true
96 }
97 return transform_code_with_prefs_for_test(code, prefs)
98}
99
100fn transform_code_with_prefs_for_test(code string, prefs &vpref.Preferences) []ast.File {
101 tmp_file := '/tmp/v2_transformer_test_${os.getpid()}.v'
102 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
103 defer {
104 os.rm(tmp_file) or {}
105 }
106 mut file_set := token.FileSet.new()
107 mut par := parser.Parser.new(prefs)
108 files := par.parse_files([tmp_file], mut file_set)
109 mut env := types.Environment.new()
110 mut checker := types.Checker.new(prefs, file_set, env)
111 checker.check_files(files)
112 mut transformer := Transformer.new_with_pref(env, prefs)
113 return transformer.transform_files(files)
114}
115
116fn transform_code_with_env_for_test(code string) (&types.Environment, []ast.File) {
117 tmp_file := '/tmp/v2_transformer_env_test_${os.getpid()}.v'
118 os.write_file(tmp_file, code) or { panic('failed to write temp file') }
119 defer {
120 os.rm(tmp_file) or {}
121 }
122 prefs := &vpref.Preferences{
123 backend: .cleanc
124 no_parallel: true
125 }
126 mut file_set := token.FileSet.new()
127 mut par := parser.Parser.new(prefs)
128 files := par.parse_files([tmp_file], mut file_set)
129 mut env := types.Environment.new()
130 mut checker := types.Checker.new(prefs, file_set, env)
131 checker.check_files(files)
132 mut transformer := Transformer.new_with_pref(env, prefs)
133 transformed := transformer.transform_files(files)
134 return env, transformed
135}
136
137struct TestSource {
138 rel string
139 code string
140}
141
142fn transform_sources_for_test(sources []TestSource) []ast.File {
143 tmp_dir := os.join_path(os.temp_dir(), 'v2_transformer_sources_${os.getpid()}')
144 os.rmdir_all(tmp_dir) or {}
145 os.mkdir_all(tmp_dir) or { panic(err) }
146 defer {
147 os.rmdir_all(tmp_dir) or {}
148 }
149 mut paths := []string{cap: sources.len}
150 for source in sources {
151 path := os.join_path(tmp_dir, source.rel)
152 os.mkdir_all(os.dir(path)) or { panic(err) }
153 os.write_file(path, source.code) or { panic('failed to write ${path}') }
154 paths << path
155 }
156 prefs := &vpref.Preferences{
157 backend: .cleanc
158 no_parallel: true
159 }
160 mut file_set := token.FileSet.new()
161 mut par := parser.Parser.new(prefs)
162 files := par.parse_files(paths, mut file_set)
163 mut env := types.Environment.new()
164 mut checker := types.Checker.new(prefs, file_set, env)
165 checker.check_files(files)
166 mut transformer := Transformer.new_with_pref(env, prefs)
167 return transformer.transform_files(files)
168}
169
170fn transform_sources_with_env_for_test(sources []TestSource) (&types.Environment, []ast.File) {
171 tmp_dir := os.join_path(os.temp_dir(), 'v2_transformer_sources_env_${os.getpid()}')
172 os.rmdir_all(tmp_dir) or {}
173 os.mkdir_all(tmp_dir) or { panic(err) }
174 defer {
175 os.rmdir_all(tmp_dir) or {}
176 }
177 mut paths := []string{cap: sources.len}
178 for source in sources {
179 path := os.join_path(tmp_dir, source.rel)
180 os.mkdir_all(os.dir(path)) or { panic(err) }
181 os.write_file(path, source.code) or { panic('failed to write ${path}') }
182 paths << path
183 }
184 prefs := &vpref.Preferences{
185 backend: .cleanc
186 no_parallel: true
187 }
188 mut file_set := token.FileSet.new()
189 mut par := parser.Parser.new(prefs)
190 files := par.parse_files(paths, mut file_set)
191 mut env := types.Environment.new()
192 mut checker := types.Checker.new(prefs, file_set, env)
193 checker.check_files(files)
194 mut transformer := Transformer.new_with_pref(env, prefs)
195 transformed := transformer.transform_files(files)
196 return env, transformed
197}
198
199fn ident_name_from_expr_for_test(expr ast.Expr) ?string {
200 if expr is ast.Ident {
201 return expr.name
202 }
203 if expr is ast.ModifierExpr && expr.expr is ast.Ident {
204 return expr.expr.name
205 }
206 return none
207}
208
209fn test_sort_selector_path_after_root_keeps_nested_fields() {
210 t := create_test_transformer()
211 expr := ast.Expr(ast.SelectorExpr{
212 lhs: ast.Expr(ast.SelectorExpr{
213 lhs: ast.Expr(ast.Ident{
214 name: 'a'
215 })
216 rhs: ast.Ident{
217 name: 'path'
218 }
219 })
220 rhs: ast.Ident{
221 name: 'len'
222 }
223 })
224 path := t.selector_path_after_root(expr, 'a') or { panic('missing selector path') }
225
226 assert path.len == 2
227 assert path[0] == 'path'
228 assert path[1] == 'len'
229}
230
231fn test_get_expr_type_selector_prefers_struct_field_over_stale_pos_type() {
232 mut env := types.Environment.new()
233 env.set_expr_type(91, types.string_)
234 value_kind_type := types.Type(types.Enum{
235 name: 'json2__ValueKind'
236 })
237 value_info_type := types.Type(types.Struct{
238 name: 'json2__ValueInfo'
239 fields: [
240 types.Field{
241 name: 'value_kind'
242 typ: value_kind_type
243 },
244 ]
245 })
246 mut scope := types.new_scope(unsafe { nil })
247 scope.insert('struct_info', value_object_from_type(value_info_type))
248 mut t := Transformer{
249 pref: &vpref.Preferences{}
250 env: unsafe { env }
251 scope: scope
252 local_decl_types: map[string]types.Type{}
253 }
254 typ := t.get_expr_type(ast.SelectorExpr{
255 lhs: ast.Ident{
256 name: 'struct_info'
257 }
258 rhs: ast.Ident{
259 name: 'value_kind'
260 }
261 pos: token.Pos{
262 id: 91
263 }
264 }) or { panic('missing selector type') }
265 assert typ is types.Enum
266 assert (typ as types.Enum).name == 'json2__ValueKind'
267}
268
269fn test_get_expr_type_selector_prefers_struct_field_over_stale_synth_type() {
270 value_kind_type := types.Type(types.Enum{
271 name: 'json2__ValueKind'
272 })
273 value_info_type := types.Type(types.Struct{
274 name: 'json2__ValueInfo'
275 fields: [
276 types.Field{
277 name: 'value_kind'
278 typ: value_kind_type
279 },
280 ]
281 })
282 mut scope := types.new_scope(unsafe { nil })
283 scope.insert('struct_info', value_object_from_type(value_info_type))
284 mut t := Transformer{
285 pref: &vpref.Preferences{}
286 env: unsafe { types.Environment.new() }
287 scope: scope
288 local_decl_types: map[string]types.Type{}
289 }
290 pos := token.Pos{
291 id: 92
292 }
293 t.register_synth_type(pos, types.Type(types.string_))
294 typ := t.get_expr_type(ast.SelectorExpr{
295 lhs: ast.Ident{
296 name: 'struct_info'
297 }
298 rhs: ast.Ident{
299 name: 'value_kind'
300 }
301 pos: pos
302 }) or { panic('missing selector type') }
303 assert typ is types.Enum
304 assert (typ as types.Enum).name == 'json2__ValueKind'
305}
306
307fn test_string_interpolation_prefers_declared_selector_type_over_stale_string_pos() {
308 value_kind_type := types.Type(types.Enum{
309 name: 'json2__ValueKind'
310 })
311 value_info_type := types.Type(types.Struct{
312 name: 'json2__ValueInfo'
313 fields: [
314 types.Field{
315 name: 'value_kind'
316 typ: value_kind_type
317 },
318 ]
319 })
320 mut env := types.Environment.new()
321 env.set_expr_type(93, types.Type(types.string_))
322 mut scope := types.new_scope(unsafe { nil })
323 scope.insert('struct_info', value_object_from_type(value_info_type))
324 mut t := Transformer{
325 pref: &vpref.Preferences{}
326 env: unsafe { env }
327 scope: scope
328 local_decl_types: map[string]types.Type{}
329 needed_str_fns: map[string]string{}
330 needed_enum_str_fns: map[string]types.Enum{}
331 synth_types: map[int]types.Type{}
332 }
333 inter_expr := ast.Expr(ast.SelectorExpr{
334 lhs: ast.Ident{
335 name: 'struct_info'
336 }
337 rhs: ast.Ident{
338 name: 'value_kind'
339 }
340 pos: token.Pos{
341 id: 93
342 }
343 })
344 inter := ast.StringInter{
345 expr: inter_expr
346 }
347 assert t.resolve_sprintf_format(inter) == '%s'
348 arg := t.transform_sprintf_arg(inter)
349 assert arg is ast.SelectorExpr
350 call := (arg as ast.SelectorExpr).lhs
351 assert call is ast.CallExpr
352 lhs := (call as ast.CallExpr).lhs
353 assert lhs is ast.Ident
354 assert (lhs as ast.Ident).name == 'json2__ValueKind__str'
355
356 stale_arg := ast.Expr(ast.SelectorExpr{
357 lhs: ast.Expr(ast.CallExpr{
358 lhs: ast.Ident{
359 name: 'string__str'
360 }
361 args: [inter_expr]
362 pos: token.Pos{
363 id: 94
364 }
365 })
366 rhs: ast.Ident{
367 name: 'str'
368 }
369 pos: token.Pos{
370 id: 95
371 }
372 })
373 lit := t.transform_string_inter_literal(ast.StringInterLiteral{
374 values: ["'Expected object, but got ", "'"]
375 inters: [
376 ast.StringInter{
377 expr: stale_arg
378 },
379 ]
380 })
381 assert lit is ast.StringInterLiteral
382 repaired := (lit as ast.StringInterLiteral).inters[0].expr
383 assert repaired is ast.SelectorExpr
384 repaired_call := (repaired as ast.SelectorExpr).lhs
385 assert repaired_call is ast.CallExpr
386 repaired_lhs := (repaired_call as ast.CallExpr).lhs
387 assert repaired_lhs is ast.Ident
388 assert (repaired_lhs as ast.Ident).name == 'json2__ValueKind__str'
389
390 cloned := t.clone_expr_with_bindings_and_fields(ast.Expr(ast.StringInterLiteral{
391 values: ["'Expected object, but got ", "'"]
392 inters: [
393 ast.StringInter{
394 expr: stale_arg
395 },
396 ]
397 }), map[string]types.Type{}, []CloneComptimeFieldCtx{})
398 assert cloned is ast.StringInterLiteral
399 cloned_arg := (cloned as ast.StringInterLiteral).inters[0].expr
400 assert cloned_arg is ast.SelectorExpr
401 cloned_call := (cloned_arg as ast.SelectorExpr).lhs
402 assert cloned_call is ast.CallExpr
403 cloned_lhs := (cloned_call as ast.CallExpr).lhs
404 assert cloned_lhs is ast.Ident
405 assert (cloned_lhs as ast.Ident).name == 'json2__ValueKind__str'
406}
407
408fn collect_call_names_from_stmt(stmt ast.Stmt, mut names []string) {
409 match stmt {
410 ast.AssignStmt {
411 for expr in stmt.lhs {
412 collect_call_names_from_expr(expr, mut names)
413 }
414 for expr in stmt.rhs {
415 collect_call_names_from_expr(expr, mut names)
416 }
417 }
418 ast.AssertStmt {
419 collect_call_names_from_expr(stmt.expr, mut names)
420 collect_call_names_from_expr(stmt.extra, mut names)
421 }
422 ast.BlockStmt {
423 for nested in stmt.stmts {
424 collect_call_names_from_stmt(nested, mut names)
425 }
426 }
427 ast.ComptimeStmt {
428 collect_call_names_from_stmt(stmt.stmt, mut names)
429 }
430 ast.DeferStmt {
431 for nested in stmt.stmts {
432 collect_call_names_from_stmt(nested, mut names)
433 }
434 }
435 ast.ExprStmt {
436 collect_call_names_from_expr(stmt.expr, mut names)
437 }
438 ast.ForInStmt {
439 collect_call_names_from_expr(stmt.key, mut names)
440 collect_call_names_from_expr(stmt.value, mut names)
441 collect_call_names_from_expr(stmt.expr, mut names)
442 }
443 ast.ForStmt {
444 collect_call_names_from_stmt(stmt.init, mut names)
445 collect_call_names_from_expr(stmt.cond, mut names)
446 collect_call_names_from_stmt(stmt.post, mut names)
447 for nested in stmt.stmts {
448 collect_call_names_from_stmt(nested, mut names)
449 }
450 }
451 ast.LabelStmt {
452 collect_call_names_from_stmt(stmt.stmt, mut names)
453 }
454 ast.ReturnStmt {
455 for expr in stmt.exprs {
456 collect_call_names_from_expr(expr, mut names)
457 }
458 }
459 else {}
460 }
461}
462
463fn collect_call_names_from_expr(expr ast.Expr, mut names []string) {
464 match expr {
465 ast.ArrayInitExpr {
466 collect_call_names_from_expr(expr.init, mut names)
467 collect_call_names_from_expr(expr.cap, mut names)
468 collect_call_names_from_expr(expr.len, mut names)
469 for item in expr.exprs {
470 collect_call_names_from_expr(item, mut names)
471 }
472 }
473 ast.AsCastExpr {
474 collect_call_names_from_expr(expr.expr, mut names)
475 }
476 ast.AssocExpr {
477 collect_call_names_from_expr(expr.expr, mut names)
478 for field in expr.fields {
479 collect_call_names_from_expr(field.value, mut names)
480 }
481 }
482 ast.CallExpr {
483 if expr.lhs is ast.Ident {
484 names << expr.lhs.name
485 }
486 collect_call_names_from_expr(expr.lhs, mut names)
487 for arg in expr.args {
488 collect_call_names_from_expr(arg, mut names)
489 }
490 }
491 ast.CallOrCastExpr {
492 collect_call_names_from_expr(expr.lhs, mut names)
493 collect_call_names_from_expr(expr.expr, mut names)
494 }
495 ast.CastExpr {
496 collect_call_names_from_expr(expr.expr, mut names)
497 }
498 ast.FieldInit {
499 collect_call_names_from_expr(expr.value, mut names)
500 }
501 ast.FnLiteral {
502 for nested in expr.stmts {
503 collect_call_names_from_stmt(nested, mut names)
504 }
505 }
506 ast.GenericArgOrIndexExpr {
507 collect_call_names_from_expr(expr.lhs, mut names)
508 collect_call_names_from_expr(expr.expr, mut names)
509 }
510 ast.GenericArgs {
511 collect_call_names_from_expr(expr.lhs, mut names)
512 for arg in expr.args {
513 collect_call_names_from_expr(arg, mut names)
514 }
515 }
516 ast.IfExpr {
517 collect_call_names_from_expr(expr.cond, mut names)
518 for nested in expr.stmts {
519 collect_call_names_from_stmt(nested, mut names)
520 }
521 collect_call_names_from_expr(expr.else_expr, mut names)
522 }
523 ast.IndexExpr {
524 collect_call_names_from_expr(expr.lhs, mut names)
525 collect_call_names_from_expr(expr.expr, mut names)
526 }
527 ast.InfixExpr {
528 collect_call_names_from_expr(expr.lhs, mut names)
529 collect_call_names_from_expr(expr.rhs, mut names)
530 }
531 ast.InitExpr {
532 for field in expr.fields {
533 collect_call_names_from_expr(field.value, mut names)
534 }
535 }
536 ast.LambdaExpr {
537 collect_call_names_from_expr(expr.expr, mut names)
538 }
539 ast.LockExpr {
540 for nested in expr.stmts {
541 collect_call_names_from_stmt(nested, mut names)
542 }
543 }
544 ast.MapInitExpr {
545 for key in expr.keys {
546 collect_call_names_from_expr(key, mut names)
547 }
548 for val in expr.vals {
549 collect_call_names_from_expr(val, mut names)
550 }
551 }
552 ast.MatchExpr {
553 collect_call_names_from_expr(expr.expr, mut names)
554 for branch in expr.branches {
555 for cond in branch.cond {
556 collect_call_names_from_expr(cond, mut names)
557 }
558 for nested in branch.stmts {
559 collect_call_names_from_stmt(nested, mut names)
560 }
561 }
562 }
563 ast.ModifierExpr {
564 collect_call_names_from_expr(expr.expr, mut names)
565 }
566 ast.OrExpr {
567 collect_call_names_from_expr(expr.expr, mut names)
568 for nested in expr.stmts {
569 collect_call_names_from_stmt(nested, mut names)
570 }
571 }
572 ast.ParenExpr {
573 collect_call_names_from_expr(expr.expr, mut names)
574 }
575 ast.PostfixExpr {
576 collect_call_names_from_expr(expr.expr, mut names)
577 }
578 ast.PrefixExpr {
579 collect_call_names_from_expr(expr.expr, mut names)
580 }
581 ast.RangeExpr {
582 collect_call_names_from_expr(expr.start, mut names)
583 collect_call_names_from_expr(expr.end, mut names)
584 }
585 ast.SelectExpr {
586 collect_call_names_from_stmt(expr.stmt, mut names)
587 for nested in expr.stmts {
588 collect_call_names_from_stmt(nested, mut names)
589 }
590 collect_call_names_from_expr(expr.next, mut names)
591 }
592 ast.SelectorExpr {
593 collect_call_names_from_expr(expr.lhs, mut names)
594 }
595 ast.StringInterLiteral {
596 for inter in expr.inters {
597 collect_call_names_from_expr(inter.expr, mut names)
598 collect_call_names_from_expr(inter.format_expr, mut names)
599 }
600 }
601 ast.Tuple {
602 for item in expr.exprs {
603 collect_call_names_from_expr(item, mut names)
604 }
605 }
606 ast.UnsafeExpr {
607 for nested in expr.stmts {
608 collect_call_names_from_stmt(nested, mut names)
609 }
610 }
611 else {}
612 }
613}
614
615fn call_names_for_fn(files []ast.File, fn_name string) []string {
616 mut names := []string{}
617 for file in files {
618 for stmt in file.stmts {
619 if stmt is ast.FnDecl && stmt.name == fn_name {
620 for nested in stmt.stmts {
621 collect_call_names_from_stmt(nested, mut names)
622 }
623 }
624 }
625 }
626 return names
627}
628
629fn test_println_rune_lowers_to_rune_str_call() {
630 mut t := create_transformer_with_vars({
631 'r': types.Type(types.rune_)
632 })
633 out := t.transform_call_expr(ast.CallExpr{
634 lhs: ast.Ident{
635 name: 'println'
636 }
637 args: [ast.Expr(ast.Ident{
638 name: 'r'
639 })]
640 })
641 assert out is ast.CallExpr
642 call := out as ast.CallExpr
643 assert call.args.len == 1
644 assert call.args[0] is ast.CallExpr
645 str_call := call.args[0] as ast.CallExpr
646 assert str_call.lhs is ast.Ident
647 assert (str_call.lhs as ast.Ident).name == 'rune__str'
648 assert 'rune__str' in t.needed_str_fns
649}
650
651fn test_println_i64_call_or_cast_lowers_to_i64_str_call() {
652 mut t := create_test_transformer()
653 out := t.transform_call_expr(ast.CallExpr{
654 lhs: ast.Ident{
655 name: 'println'
656 }
657 args: [
658 ast.Expr(ast.CallOrCastExpr{
659 lhs: ast.Ident{
660 name: 'i64'
661 }
662 expr: ast.BasicLiteral{
663 kind: .number
664 value: '4294967296'
665 }
666 }),
667 ]
668 })
669 assert out is ast.CallExpr
670 call := out as ast.CallExpr
671 assert call.args.len == 1
672 assert call.args[0] is ast.CallExpr
673 str_call := call.args[0] as ast.CallExpr
674 assert str_call.lhs is ast.Ident
675 assert (str_call.lhs as ast.Ident).name == 'i64__str'
676 assert 'i64__str' in t.needed_str_fns
677}
678
679fn call_names_for_fn_suffix(files []ast.File, fn_suffix string) []string {
680 mut names := []string{}
681 for file in files {
682 for stmt in file.stmts {
683 if stmt is ast.FnDecl && stmt.name.ends_with(fn_suffix) {
684 for nested in stmt.stmts {
685 collect_call_names_from_stmt(nested, mut names)
686 }
687 }
688 }
689 }
690 return names
691}
692
693fn test_transform_generic_call_uses_sumtype_match_smartcast_for_inference() {
694 files := transform_code_for_test('
695struct Point {
696 x int
697}
698
699type Primitive = []int | []string | []Point | bool | int | string | Point
700
701fn wrap_first[T](values []T) Primitive {
702 return Primitive(values[0])
703}
704
705fn check(value Primitive) {
706 match value {
707 []int {
708 _ := wrap_first(value)
709 }
710 []string {
711 _ := wrap_first(value)
712 }
713 []Point {
714 _ := wrap_first(value)
715 }
716 else {}
717 }
718}
719')
720 names := call_names_for_fn(files, 'check')
721 assert 'wrap_first_T_int' in names
722 assert 'wrap_first_T_string' in names
723 assert 'wrap_first_T_Point' in names
724 assert 'wrap_first' !in names
725}
726
727fn test_transform_generic_receiver_methods_use_concrete_specializations() {
728 files := transform_sources_for_test([
729 TestSource{
730 rel: 'x/json2/decode.v'
731 code: '
732module json2
733
734struct ValueInfo {
735 x int
736}
737
738struct Node[T] {
739mut:
740 value T
741 next &Node[T] = unsafe { nil }
742}
743
744struct Decoder {
745mut:
746 values_info LinkedList[ValueInfo]
747}
748
749struct LinkedList[T] {
750mut:
751 head &Node[T] = unsafe { nil }
752 tail &Node[T] = unsafe { nil }
753 len int
754}
755
756fn (mut list LinkedList[T]) push(value T) {
757 _ = value
758}
759
760fn (list &LinkedList[T]) last() &T {
761 return &list.tail.value
762}
763'
764 },
765 TestSource{
766 rel: 'x/json2/check.v'
767 code: '
768module json2
769
770fn (mut checker Decoder) check() {
771 mut actual_value_info_pointer := unsafe { nil }
772 checker.values_info.push(ValueInfo{})
773 actual_value_info_pointer = checker.values_info.last()
774 _ = actual_value_info_pointer
775}
776'
777 },
778 ])
779 names := call_names_for_fn(files, 'check')
780 assert names.any(it.contains('json2__LinkedList__push_T_') && it.contains('json2_ValueInfo')), 'expected specialized push call, got ${names}'
781
782 assert names.any(it.contains('json2__LinkedList__last_T_') && it.contains('json2_ValueInfo')), 'expected specialized last call, got ${names}'
783
784 assert 'LinkedList__push' !in names
785 assert 'LinkedList__last' !in names
786 assert 'json2__LinkedList__push' !in names
787 assert 'json2__LinkedList__last' !in names
788}
789
790fn test_transform_nested_generic_receiver_method_call_in_monomorphized_clone() {
791 files := transform_sources_for_test([
792 TestSource{
793 rel: 'x/json2/encode.v'
794 code: '
795module json2
796
797pub struct EncoderOptions {}
798
799pub struct Any {}
800
801struct Encoder {}
802
803pub fn encode[T](val T, config EncoderOptions) string {
804 _ = config
805 mut encoder := Encoder{}
806 encoder.encode_value[T](val)
807 return ""
808}
809
810fn (mut encoder Encoder) encode_value[T](val T) {
811 _ = encoder
812 _ = val
813}
814'
815 },
816 TestSource{
817 rel: 'main.v'
818 code: '
819module main
820
821import json2
822
823fn use() {
824 _ = json2.encode(json2.Any{}, json2.EncoderOptions{})
825}
826'
827 },
828 ])
829 mut fn_names := []string{}
830 for file in files {
831 for stmt in file.stmts {
832 if stmt is ast.FnDecl {
833 fn_names << stmt.name
834 }
835 }
836 }
837 assert 'json2__encode_T_json2_Any' in fn_names
838 assert 'encode_value_T_json2_Any' in fn_names
839 call_names := call_names_for_fn(files, 'json2__encode_T_json2_Any')
840 assert 'json2__Encoder__encode_value_T_json2_Any' in call_names
841 assert 'encode_value_T_json2_Any' !in call_names
842}
843
844fn test_transform_eventbus_receiver_generic_method_call_is_monomorphized() {
845 files := transform_sources_for_test([
846 TestSource{
847 rel: 'eventbus/eventbus.v'
848 code: '
849module eventbus
850
851pub type EventHandlerFn = fn (receiver voidptr, args voidptr, sender voidptr)
852
853pub struct Subscriber[T] {}
854
855pub fn new_subscriber[T]() Subscriber[T] {
856 return Subscriber[T]{}
857}
858
859pub fn (mut s Subscriber[T]) subscribe_method(name T, handler EventHandlerFn, receiver voidptr) {
860 _ = s
861 _ = name
862 _ = handler
863 _ = receiver
864}
865'
866 },
867 TestSource{
868 rel: 'main.v'
869 code: "
870module main
871
872import eventbus
873
874fn handler(receiver voidptr, args voidptr, sender voidptr) {
875 _ = receiver
876 _ = args
877 _ = sender
878}
879
880fn use() {
881 mut subscriber := eventbus.new_subscriber[string]()
882 subscriber.subscribe_method('ready', handler, unsafe { nil })
883}
884"
885 },
886 ])
887 mut fn_names := []string{}
888 for file in files {
889 for stmt in file.stmts {
890 if stmt is ast.FnDecl {
891 fn_names << stmt.name
892 }
893 }
894 }
895 assert 'subscribe_method_T_string' in fn_names
896
897 call_names := call_names_for_fn(files, 'use')
898 assert 'eventbus__Subscriber__subscribe_method_T_string' in call_names
899 assert 'eventbus__Subscriber__subscribe_method' !in call_names
900}
901
902fn test_transform_embedded_method_promotion_keeps_owner_method_precedence() {
903 mut t := create_test_transformer()
904 t.collect_declared_method_fns([
905 ast.File{
906 mod: 'main'
907 stmts: [
908 ast.Stmt(ast.FnDecl{
909 name: 'redirect_to_index'
910 is_method: true
911 receiver: ast.Parameter{
912 name: 'ctx'
913 typ: ast.Expr(ast.Ident{
914 name: 'Context'
915 })
916 }
917 }),
918 ]
919 },
920 ])
921 recv_type := types.Type(types.Struct{
922 name: 'Context'
923 })
924 resolved := t.resolve_cached_method_fn_name_for_type(recv_type, 'redirect_to_index', 'Context') or {
925 panic('missing declared owner method')
926 }
927 assert resolved == 'Context__redirect_to_index'
928 assert 'Context__redirect_to_index' in t.declared_method_fns
929}
930
931fn test_generic_receiver_bindings_prefer_declared_selector_over_stale_wrapper_type() {
932 value_info_type := types.Type(types.Struct{
933 name: 'json2__ValueInfo'
934 })
935 template_type := types.Type(types.Struct{
936 name: 'json2__LinkedList'
937 generic_params: ['T']
938 fields: [
939 types.Field{
940 name: 'item'
941 typ: types.Type(types.NamedType('T'))
942 },
943 ]
944 })
945 concrete_type := types.Type(types.Struct{
946 name: 'json2__LinkedList'
947 fields: [
948 types.Field{
949 name: 'item'
950 typ: value_info_type
951 },
952 ]
953 })
954 decoder_type := types.Type(types.Struct{
955 name: 'json2__Decoder'
956 fields: [
957 types.Field{
958 name: 'values_info'
959 typ: concrete_type
960 },
961 ]
962 })
963 mut env := types.Environment.new()
964 env.set_expr_type(701, template_type)
965 mut scope := types.new_scope(unsafe { nil })
966 scope.insert('checker', value_object_from_type(decoder_type))
967 scope.insert_type('LinkedList', template_type)
968 scope.insert_type('json2__LinkedList', template_type)
969 mut t := Transformer{
970 pref: &vpref.Preferences{}
971 env: unsafe { env }
972 scope: scope
973 cur_module: 'json2'
974 cached_scopes: {
975 'json2': scope
976 }
977 local_decl_types: map[string]types.Type{}
978 }
979 receiver := ast.Expr(ast.ParenExpr{
980 expr: ast.Expr(ast.SelectorExpr{
981 lhs: ast.Ident{
982 name: 'checker'
983 }
984 rhs: ast.Ident{
985 name: 'values_info'
986 }
987 })
988 pos: token.Pos{
989 id: 701
990 }
991 })
992 decl := ast.FnDecl{
993 name: 'last'
994 is_method: true
995 receiver: ast.Parameter{
996 name: 'list'
997 typ: ast.Expr(ast.PrefixExpr{
998 op: .amp
999 expr: ast.Expr(ast.GenericArgOrIndexExpr{
1000 lhs: ast.Expr(ast.Ident{
1001 name: 'LinkedList'
1002 })
1003 expr: ast.Expr(ast.Ident{
1004 name: 'T'
1005 })
1006 })
1007 })
1008 }
1009 }
1010 bindings := t.generic_bindings_from_method_receiver(decl, receiver, 'json2__LinkedList__last') or {
1011 panic('missing receiver bindings')
1012 }
1013 binding := bindings['T'] or { panic('missing T binding') }
1014 assert binding is types.Struct
1015 assert (binding as types.Struct).name == 'json2__ValueInfo'
1016}
1017
1018fn test_decl_assign_rune_arithmetic_keeps_rune_storage_type() {
1019 mut t := create_transformer_with_vars({
1020 'unicode_point': types.builtin_type('rune') or { rune_type() }
1021 'unicode_point2': types.builtin_type('rune') or { rune_type() }
1022 })
1023 mut env := types.Environment.new()
1024 env.set_expr_type(93, types.Type(types.int_))
1025 env.set_expr_type(94, types.Type(types.int_))
1026 env.set_expr_type(95, types.Type(types.int_))
1027 t.env = unsafe { env }
1028 lhs := ast.Expr(ast.Ident{
1029 name: 'final_unicode_point'
1030 pos: token.Pos{
1031 id: 93
1032 }
1033 })
1034 cast_rhs := ast.Expr(ast.CastExpr{
1035 typ: ast.Expr(ast.Ident{
1036 name: 'rune'
1037 })
1038 expr: ast.Expr(ast.BasicLiteral{
1039 kind: .number
1040 value: '0'
1041 })
1042 })
1043 cast_typ := t.decl_assign_storage_type(lhs, cast_rhs) or { panic('missing cast type') }
1044 assert cast_typ is types.Rune
1045 rhs := ast.Expr(ast.InfixExpr{
1046 op: .plus
1047 lhs: ast.Expr(ast.InfixExpr{
1048 op: .amp
1049 lhs: ast.Expr(ast.Ident{
1050 name: 'unicode_point2'
1051 pos: token.Pos{
1052 id: 94
1053 }
1054 })
1055 rhs: ast.Expr(ast.BasicLiteral{
1056 kind: .number
1057 value: '0x3FF'
1058 })
1059 })
1060 rhs: ast.Expr(ast.InfixExpr{
1061 op: .left_shift
1062 lhs: ast.Expr(ast.InfixExpr{
1063 op: .amp
1064 lhs: ast.Expr(ast.Ident{
1065 name: 'unicode_point'
1066 pos: token.Pos{
1067 id: 95
1068 }
1069 })
1070 rhs: ast.Expr(ast.BasicLiteral{
1071 kind: .number
1072 value: '0x3FF'
1073 })
1074 })
1075 rhs: ast.Expr(ast.BasicLiteral{
1076 kind: .number
1077 value: '10'
1078 })
1079 })
1080 })
1081 typ := t.decl_assign_storage_type(lhs, rhs) or { panic('missing rune arithmetic type') }
1082 assert typ is types.Rune
1083}
1084
1085fn test_runtime_const_init_main_calls_run_main_consts_after_module_inits() {
1086 mut t := create_test_transformer()
1087 t.runtime_const_modules << 'main'
1088 t.runtime_const_init_fn_name['main'] = '__v_init_consts_main'
1089 files := [
1090 ast.File{
1091 mod: 'main'
1092 stmts: [
1093 ast.Stmt(ast.FnDecl{
1094 name: 'main'
1095 }),
1096 ]
1097 },
1098 ast.File{
1099 mod: 'rand'
1100 stmts: [
1101 ast.Stmt(ast.FnDecl{
1102 name: 'init'
1103 }),
1104 ]
1105 },
1106 ]
1107 calls := t.runtime_const_init_main_calls_parts(files)
1108 mut names := []string{}
1109 for call_stmt in calls {
1110 collect_call_names_from_stmt(call_stmt, mut names)
1111 }
1112 assert names == ['rand__init', '__v_init_consts_main']
1113}
1114
1115fn stmt_has_assign_op(stmt ast.Stmt, op token.Token) bool {
1116 match stmt {
1117 ast.AssignStmt {
1118 return stmt.op == op
1119 }
1120 ast.BlockStmt {
1121 for nested in stmt.stmts {
1122 if stmt_has_assign_op(nested, op) {
1123 return true
1124 }
1125 }
1126 }
1127 ast.ExprStmt {
1128 return expr_has_assign_op(stmt.expr, op)
1129 }
1130 ast.ForStmt {
1131 if stmt_has_assign_op(stmt.init, op) || stmt_has_assign_op(stmt.post, op) {
1132 return true
1133 }
1134 for nested in stmt.stmts {
1135 if stmt_has_assign_op(nested, op) {
1136 return true
1137 }
1138 }
1139 }
1140 ast.LabelStmt {
1141 return stmt_has_assign_op(stmt.stmt, op)
1142 }
1143 else {}
1144 }
1145
1146 return false
1147}
1148
1149fn expr_has_assign_op(expr ast.Expr, op token.Token) bool {
1150 match expr {
1151 ast.IfExpr {
1152 for stmt in expr.stmts {
1153 if stmt_has_assign_op(stmt, op) {
1154 return true
1155 }
1156 }
1157 return expr_has_assign_op(expr.else_expr, op)
1158 }
1159 ast.MatchExpr {
1160 for branch in expr.branches {
1161 for stmt in branch.stmts {
1162 if stmt_has_assign_op(stmt, op) {
1163 return true
1164 }
1165 }
1166 }
1167 }
1168 ast.UnsafeExpr {
1169 for stmt in expr.stmts {
1170 if stmt_has_assign_op(stmt, op) {
1171 return true
1172 }
1173 }
1174 }
1175 else {}
1176 }
1177
1178 return false
1179}
1180
1181fn fn_has_assign_op(files []ast.File, fn_name string, op token.Token) bool {
1182 for file in files {
1183 for stmt in file.stmts {
1184 if stmt is ast.FnDecl && stmt.name == fn_name {
1185 for nested in stmt.stmts {
1186 if stmt_has_assign_op(nested, op) {
1187 return true
1188 }
1189 }
1190 }
1191 }
1192 }
1193 return false
1194}
1195
1196fn count_label_name_in_files(files []ast.File, fn_name string, label string) int {
1197 mut count := 0
1198 for file in files {
1199 for stmt in file.stmts {
1200 if stmt is ast.FnDecl && stmt.name == fn_name {
1201 count += count_label_name_in_stmts(stmt.stmts, label)
1202 }
1203 }
1204 }
1205 return count
1206}
1207
1208fn count_label_name_in_stmts(stmts []ast.Stmt, label string) int {
1209 mut count := 0
1210 for stmt in stmts {
1211 count += count_label_name_in_stmt(stmt, label)
1212 }
1213 return count
1214}
1215
1216fn count_label_name_in_stmt(stmt ast.Stmt, label string) int {
1217 match stmt {
1218 ast.BlockStmt {
1219 return count_label_name_in_stmts(stmt.stmts, label)
1220 }
1221 ast.ComptimeStmt {
1222 return count_label_name_in_stmt(stmt.stmt, label)
1223 }
1224 ast.DeferStmt {
1225 return count_label_name_in_stmts(stmt.stmts, label)
1226 }
1227 ast.ExprStmt {
1228 return count_label_name_in_expr(stmt.expr, label)
1229 }
1230 ast.ForStmt {
1231 return count_label_name_in_stmt(stmt.init, label) +
1232 count_label_name_in_stmt(stmt.post, label) +
1233 count_label_name_in_expr(stmt.cond, label) +
1234 count_label_name_in_stmts(stmt.stmts, label)
1235 }
1236 ast.LabelStmt {
1237 mut count := if stmt.name == label { 1 } else { 0 }
1238 count += count_label_name_in_stmt(stmt.stmt, label)
1239 return count
1240 }
1241 ast.ReturnStmt {
1242 mut count := 0
1243 for expr in stmt.exprs {
1244 count += count_label_name_in_expr(expr, label)
1245 }
1246 return count
1247 }
1248 else {
1249 return 0
1250 }
1251 }
1252}
1253
1254fn count_label_name_in_expr(expr ast.Expr, label string) int {
1255 match expr {
1256 ast.IfExpr {
1257 return count_label_name_in_expr(expr.cond, label) +
1258 count_label_name_in_stmts(expr.stmts, label) +
1259 count_label_name_in_expr(expr.else_expr, label)
1260 }
1261 ast.MatchExpr {
1262 mut count := 0
1263 for branch in expr.branches {
1264 count += count_label_name_in_stmts(branch.stmts, label)
1265 }
1266 return count
1267 }
1268 ast.UnsafeExpr {
1269 return count_label_name_in_stmts(expr.stmts, label)
1270 }
1271 else {
1272 return 0
1273 }
1274 }
1275}
1276
1277fn find_call_with_lhs_suffix_in_stmts(stmts []ast.Stmt, suffix string) ?ast.CallExpr {
1278 for stmt in stmts {
1279 if call := find_call_with_lhs_suffix_in_stmt(stmt, suffix) {
1280 return call
1281 }
1282 }
1283 return none
1284}
1285
1286fn find_call_with_lhs_suffix_in_stmt(stmt ast.Stmt, suffix string) ?ast.CallExpr {
1287 match stmt {
1288 ast.AssignStmt {
1289 for expr in stmt.lhs {
1290 if call := find_call_with_lhs_suffix_in_expr(expr, suffix) {
1291 return call
1292 }
1293 }
1294 for expr in stmt.rhs {
1295 if call := find_call_with_lhs_suffix_in_expr(expr, suffix) {
1296 return call
1297 }
1298 }
1299 }
1300 ast.BlockStmt {
1301 return find_call_with_lhs_suffix_in_stmts(stmt.stmts, suffix)
1302 }
1303 ast.ExprStmt {
1304 return find_call_with_lhs_suffix_in_expr(stmt.expr, suffix)
1305 }
1306 ast.ForInStmt {
1307 if call := find_call_with_lhs_suffix_in_expr(stmt.expr, suffix) {
1308 return call
1309 }
1310 }
1311 ast.ForStmt {
1312 if call := find_call_with_lhs_suffix_in_stmt(stmt.init, suffix) {
1313 return call
1314 }
1315 if call := find_call_with_lhs_suffix_in_expr(stmt.cond, suffix) {
1316 return call
1317 }
1318 if call := find_call_with_lhs_suffix_in_stmt(stmt.post, suffix) {
1319 return call
1320 }
1321 return find_call_with_lhs_suffix_in_stmts(stmt.stmts, suffix)
1322 }
1323 ast.FnDecl {
1324 return find_call_with_lhs_suffix_in_stmts(stmt.stmts, suffix)
1325 }
1326 ast.ReturnStmt {
1327 for expr in stmt.exprs {
1328 if call := find_call_with_lhs_suffix_in_expr(expr, suffix) {
1329 return call
1330 }
1331 }
1332 }
1333 else {}
1334 }
1335
1336 return none
1337}
1338
1339fn find_call_with_lhs_suffix_in_expr(expr ast.Expr, suffix string) ?ast.CallExpr {
1340 match expr {
1341 ast.ArrayInitExpr {
1342 for item in expr.exprs {
1343 if call := find_call_with_lhs_suffix_in_expr(item, suffix) {
1344 return call
1345 }
1346 }
1347 }
1348 ast.CallExpr {
1349 if expr.lhs is ast.Ident && (expr.lhs as ast.Ident).name.ends_with(suffix) {
1350 return expr
1351 }
1352 if call := find_call_with_lhs_suffix_in_expr(expr.lhs, suffix) {
1353 return call
1354 }
1355 for arg in expr.args {
1356 if call := find_call_with_lhs_suffix_in_expr(arg, suffix) {
1357 return call
1358 }
1359 }
1360 }
1361 ast.CastExpr {
1362 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1363 }
1364 ast.FieldInit {
1365 return find_call_with_lhs_suffix_in_expr(expr.value, suffix)
1366 }
1367 ast.IfExpr {
1368 if call := find_call_with_lhs_suffix_in_expr(expr.cond, suffix) {
1369 return call
1370 }
1371 if call := find_call_with_lhs_suffix_in_stmts(expr.stmts, suffix) {
1372 return call
1373 }
1374 return find_call_with_lhs_suffix_in_expr(expr.else_expr, suffix)
1375 }
1376 ast.InfixExpr {
1377 if call := find_call_with_lhs_suffix_in_expr(expr.lhs, suffix) {
1378 return call
1379 }
1380 return find_call_with_lhs_suffix_in_expr(expr.rhs, suffix)
1381 }
1382 ast.InitExpr {
1383 for field in expr.fields {
1384 if call := find_call_with_lhs_suffix_in_expr(field.value, suffix) {
1385 return call
1386 }
1387 }
1388 }
1389 ast.MatchExpr {
1390 if call := find_call_with_lhs_suffix_in_expr(expr.expr, suffix) {
1391 return call
1392 }
1393 for branch in expr.branches {
1394 if call := find_call_with_lhs_suffix_in_stmts(branch.stmts, suffix) {
1395 return call
1396 }
1397 }
1398 }
1399 ast.ModifierExpr {
1400 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1401 }
1402 ast.ParenExpr {
1403 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1404 }
1405 ast.PostfixExpr {
1406 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1407 }
1408 ast.PrefixExpr {
1409 return find_call_with_lhs_suffix_in_expr(expr.expr, suffix)
1410 }
1411 ast.SelectorExpr {
1412 return find_call_with_lhs_suffix_in_expr(expr.lhs, suffix)
1413 }
1414 ast.UnsafeExpr {
1415 return find_call_with_lhs_suffix_in_stmts(expr.stmts, suffix)
1416 }
1417 else {}
1418 }
1419
1420 return none
1421}
1422
1423// string_type returns the builtin v2 string type.
1424fn string_type() types.Type {
1425 return types.string_
1426}
1427
1428fn test_transform_decl_assign_keeps_explicit_as_cast_type_with_stale_lhs_name_scope() {
1429 env := &types.Environment{}
1430 mut scope := types.new_scope(unsafe { nil })
1431 scope.insert('inner', types.Type(types.Struct{
1432 name: 'ast__Expr'
1433 }))
1434 mut t := &Transformer{
1435 pref: &vpref.Preferences{}
1436 env: unsafe { env }
1437 scope: scope
1438 needed_clone_fns: map[string]string{}
1439 needed_array_contains_fns: map[string]ArrayMethodInfo{}
1440 needed_array_index_fns: map[string]ArrayMethodInfo{}
1441 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
1442 }
1443 result := t.transform_assign_stmt(ast.AssignStmt{
1444 op: .decl_assign
1445 lhs: [
1446 ast.Expr(ast.Ident{
1447 name: 'inner'
1448 }),
1449 ]
1450 rhs: [
1451 ast.Expr(ast.AsCastExpr{
1452 expr: ast.Expr(ast.Ident{
1453 name: 'node'
1454 })
1455 typ: ast.Expr(ast.SelectorExpr{
1456 lhs: ast.Expr(ast.Ident{
1457 name: 'ast'
1458 })
1459 rhs: ast.Ident{
1460 name: 'PrefixExpr'
1461 }
1462 })
1463 }),
1464 ]
1465 })
1466 assert result.rhs[0] is ast.AsCastExpr
1467}
1468
1469fn test_transform_decl_assign_does_not_wrap_rhs_with_stale_lhs_sumtype_scope() {
1470 env := &types.Environment{}
1471 type_sum := types.Type(types.SumType{
1472 name: 'types__Type'
1473 variants: [string_type()]
1474 })
1475 mut scope := types.new_scope(unsafe { nil })
1476 scope.insert('receiver_type', type_sum)
1477 mut types_scope := types.new_scope(unsafe { nil })
1478 types_scope.insert('Type', type_sum)
1479 mut t := &Transformer{
1480 pref: &vpref.Preferences{}
1481 env: unsafe { env }
1482 scope: scope
1483 cur_module: 'transformer'
1484 cached_scopes: {
1485 'types': types_scope
1486 }
1487 needed_clone_fns: map[string]string{}
1488 needed_array_contains_fns: map[string]ArrayMethodInfo{}
1489 needed_array_index_fns: map[string]ArrayMethodInfo{}
1490 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
1491 }
1492 result := t.transform_assign_stmt(ast.AssignStmt{
1493 op: .decl_assign
1494 lhs: [
1495 ast.Expr(ast.Ident{
1496 name: 'receiver_type'
1497 }),
1498 ]
1499 rhs: [
1500 ast.Expr(ast.StringLiteral{
1501 value: 'flag'
1502 kind: .v
1503 }),
1504 ]
1505 })
1506 assert result.rhs[0] is ast.StringLiteral
1507}
1508
1509fn test_transform_decl_assign_registers_smartcast_selector_type() {
1510 env := &types.Environment{}
1511 mut ast_scope := types.new_scope(unsafe { nil })
1512 ast_scope.insert('Interface', types.Type(types.Struct{
1513 name: 'ast__Interface'
1514 }))
1515 mut fn_scope := types.new_scope(unsafe { nil })
1516 mut t := &Transformer{
1517 pref: &vpref.Preferences{}
1518 env: unsafe { env }
1519 scope: fn_scope
1520 fn_root_scope: fn_scope
1521 cur_module: 'checker'
1522 cached_scopes: {
1523 'ast': ast_scope
1524 }
1525 needed_clone_fns: map[string]string{}
1526 needed_array_contains_fns: map[string]ArrayMethodInfo{}
1527 needed_array_index_fns: map[string]ArrayMethodInfo{}
1528 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
1529 }
1530 t.push_smartcast_full('parent_sym.info', 'ast__Interface', 'ast__Interface', 'ast__TypeInfo')
1531 _ := t.transform_assign_stmt(ast.AssignStmt{
1532 op: .decl_assign
1533 lhs: [ast.Expr(ast.Ident{
1534 name: 'generic_info'
1535 })]
1536 rhs: [
1537 ast.Expr(ast.SelectorExpr{
1538 lhs: ast.Expr(ast.Ident{
1539 name: 'parent_sym'
1540 })
1541 rhs: ast.Ident{
1542 name: 'info'
1543 }
1544 }),
1545 ]
1546 })
1547 typ := fn_scope.lookup_var_type('generic_info') or {
1548 assert false, 'generic_info type was not registered'
1549 return
1550 }
1551 assert typ is types.Struct
1552 assert (typ as types.Struct).name == 'ast__Interface'
1553}
1554
1555fn test_smartcast_method_call_prefers_variant_method_over_sumtype_method() {
1556 files := transform_code_for_test('
1557struct Primitive {}
1558
1559struct Enum {
1560 name string
1561}
1562
1563type Type = Primitive | Enum
1564
1565fn (t Type) name() string {
1566 _ = t
1567 return "type"
1568}
1569
1570fn (t Primitive) name() string {
1571 _ = t
1572 return "primitive"
1573}
1574
1575fn (t Enum) name() string {
1576 return t.name
1577}
1578
1579fn type_name(t Type) string {
1580 return match t {
1581 Primitive, Enum { t.name() }
1582 }
1583}
1584')
1585 call_names := call_names_for_fn(files, 'type_name')
1586 assert 'Primitive__name' in call_names
1587 assert 'Enum__name' in call_names
1588 assert 'Type__name' !in call_names
1589}
1590
1591fn test_flag_enum_set_on_local_variable_lowers_to_assignment() {
1592 files := transform_code_for_test('
1593@[flag]
1594enum Flags {
1595 empty
1596 enabled
1597}
1598
1599fn set_flag() Flags {
1600 mut attrs := Flags.empty
1601 attrs.set(.enabled)
1602 return attrs
1603}
1604')
1605 call_names := call_names_for_fn(files, 'set_flag')
1606 assert 'Flags__set' !in call_names
1607 assert fn_has_assign_op(files, 'set_flag', .or_assign)
1608}
1609
1610fn test_flag_enum_set_inside_statement_match_lowers_to_assignment() {
1611 files := transform_code_for_test('
1612@[flag]
1613enum Flags {
1614 empty
1615 enabled
1616}
1617
1618fn set_flag(names []string) Flags {
1619 mut attrs := Flags.empty
1620 for name in names {
1621 match name {
1622 "enabled" { attrs.set(.enabled) }
1623 else {}
1624 }
1625 }
1626 return attrs
1627}
1628')
1629 call_names := call_names_for_fn(files, 'set_flag')
1630 assert 'Flags__set' !in call_names
1631 assert fn_has_assign_op(files, 'set_flag', .or_assign)
1632}
1633
1634fn test_embedded_struct_selector_uses_embedded_method_owner() {
1635 files := transform_code_for_test('
1636struct Request {}
1637struct Response {}
1638
1639struct SilentStreamingDownloader {}
1640
1641fn (mut d SilentStreamingDownloader) on_finish(request &Request, response &Response) ! {
1642 _ = request
1643 _ = response
1644}
1645
1646struct TerminalStreamingDownloader {
1647 SilentStreamingDownloader
1648}
1649
1650fn finish(mut d TerminalStreamingDownloader, request &Request, response &Response) ! {
1651 d.SilentStreamingDownloader.on_finish(request, response)!
1652}
1653')
1654 call_names := call_names_for_fn(files, 'finish')
1655 assert 'SilentStreamingDownloader__on_finish' in call_names
1656 assert 'int__on_finish' !in call_names
1657}
1658
1659fn test_promoted_embedded_generic_method_call_lowers_to_concrete_method() {
1660 files := transform_code_for_test('
1661struct Options[T] {
1662 handler fn (mut ctx T) bool
1663}
1664
1665struct Middleware[T] {}
1666
1667fn (mut m Middleware[T]) use(options Options[T]) {
1668 _ = options
1669}
1670
1671struct Context {}
1672
1673struct App {
1674 Middleware[Context]
1675}
1676
1677fn (mut app App) before_request(mut ctx Context) bool {
1678 _ = ctx
1679 return true
1680}
1681
1682fn main() {
1683 mut app := App{}
1684 app.use(handler: app.before_request)
1685}
1686')
1687 mut fn_names := []string{}
1688 for file in files {
1689 for stmt in file.stmts {
1690 if stmt is ast.FnDecl {
1691 fn_names << stmt.name
1692 }
1693 }
1694 }
1695 call_names := call_names_for_fn(files, 'main')
1696 assert 'use_T_Context' in fn_names
1697 assert 'Middleware__use_T_Context' in call_names
1698 assert 'app.use' !in call_names
1699}
1700
1701fn test_promoted_embedded_generic_method_uses_live_struct_metadata() {
1702 files := transform_code_for_test('
1703struct Result {}
1704
1705struct BaseContext {}
1706
1707fn (mut ctx BaseContext) json[T](j T) Result {
1708 _ = j
1709 return Result{}
1710}
1711
1712struct Context {
1713 BaseContext
1714}
1715
1716struct App {}
1717
1718fn (mut app App) index(mut ctx Context) Result {
1719 _ = app
1720 return ctx.json("ok")
1721}
1722
1723fn main() {
1724 mut app := App{}
1725 mut ctx := Context{}
1726 _ = app.index(mut ctx)
1727}
1728')
1729 call_names := call_names_for_fn(files, 'index')
1730 assert 'BaseContext__json_T_string' in call_names
1731 assert 'ctx.json' !in call_names
1732}
1733
1734fn test_alias_receiver_method_is_resolved_before_base_container_method() {
1735 files := transform_code_for_test('
1736type Builder = []u8
1737
1738fn (mut b Builder) str() string {
1739 _ = b
1740 return ""
1741}
1742
1743fn builder_str() string {
1744 mut b := Builder([]u8{})
1745 return b.str()
1746}
1747')
1748 call_names := call_names_for_fn(files, 'builder_str')
1749 assert 'Builder__str' in call_names
1750 assert 'array__str' !in call_names
1751}
1752
1753fn test_string_interpolation_does_not_generate_default_over_explicit_str_method() {
1754 files := transform_code_for_test('
1755module globset
1756
1757pub struct ErrorKind {}
1758
1759pub fn (kind ErrorKind) str() string {
1760 _ = kind
1761 return "custom"
1762}
1763
1764pub fn message(kind ErrorKind) string {
1765 return "\${kind}"
1766}
1767')
1768 mut explicit_methods := 0
1769 mut generated_defaults := 0
1770 for file in files {
1771 for stmt in file.stmts {
1772 if stmt is ast.FnDecl {
1773 if stmt.is_method && stmt.name == 'str' {
1774 explicit_methods++
1775 }
1776 if !stmt.is_method && stmt.name == 'globset__ErrorKind__str' {
1777 generated_defaults++
1778 }
1779 }
1780 }
1781 }
1782 call_names := call_names_for_fn(files, 'message')
1783 assert explicit_methods == 1
1784 assert generated_defaults == 0
1785 assert 'globset__ErrorKind__str' in call_names
1786}
1787
1788fn test_array_str_call_uses_element_specific_helper() {
1789 files := transform_code_for_test('
1790module globset
1791
1792pub struct Token {}
1793
1794pub fn (tok Token) str() string {
1795 _ = tok
1796 return "Token"
1797}
1798
1799pub fn show(tokens []Token) string {
1800 return tokens.str()
1801}
1802')
1803 call_names := call_names_for_fn(files, 'show')
1804 assert 'Array_globset__Token_str' in call_names
1805 assert 'array__str' !in call_names
1806}
1807
1808fn test_string_array_str_call_keeps_builtin_method() {
1809 files := transform_code_for_test('
1810module builtin
1811
1812fn (a []string) str() string {
1813 _ = a
1814 return ""
1815}
1816
1817fn show(values []string) string {
1818 return values.str()
1819}
1820')
1821 call_names := call_names_for_fn(files, 'show')
1822 assert 'Array_string__str' in call_names
1823 assert 'Array_string_str' !in call_names
1824}
1825
1826fn test_qualified_alias_receiver_uses_concrete_base_method_with_same_short_name() {
1827 files := transform_sources_for_test([
1828 TestSource{
1829 rel: 'base/base.v'
1830 code: 'module base
1831
1832pub struct SSLConn {}
1833
1834pub fn (mut s SSLConn) connect() {
1835 _ = s
1836}
1837'
1838 },
1839 TestSource{
1840 rel: 'ssl/ssl.v'
1841 code: 'module ssl
1842
1843import base
1844
1845pub type SSLConn = base.SSLConn
1846'
1847 },
1848 TestSource{
1849 rel: 'main.v'
1850 code: 'module main
1851
1852import ssl
1853
1854fn call(mut s ssl.SSLConn) {
1855 s.connect()
1856}
1857'
1858 },
1859 ])
1860 call_names := call_names_for_fn(files, 'call')
1861 assert 'base__SSLConn__connect' in call_names, 'expected base method owner, got ${call_names}'
1862 assert 'ssl__SSLConn__connect' !in call_names
1863}
1864
1865fn test_imported_global_receiver_method_uses_global_type() {
1866 files := transform_sources_for_test([
1867 TestSource{
1868 rel: 'ast/ast.v'
1869 code: 'module ast
1870
1871pub type Type = u32
1872
1873pub struct Table {}
1874
1875pub __global global_table = &Table(unsafe { nil })
1876
1877pub fn (t &Table) type_to_str(typ Type) string {
1878 _ = t
1879 _ = typ
1880 return ""
1881}
1882'
1883 },
1884 TestSource{
1885 rel: 'main.v'
1886 code: 'module transformer
1887
1888import ast
1889
1890struct Param {
1891 typ ast.Type
1892}
1893
1894struct FnDecl {
1895 receiver Param
1896}
1897
1898fn call(node &FnDecl) string {
1899 return ast.global_table.type_to_str(node.receiver.typ)
1900}
1901'
1902 },
1903 ])
1904 call_names := call_names_for_fn(files, 'call')
1905 assert 'ast__Table__type_to_str' in call_names, 'expected ast.Table method owner, got ${call_names}'
1906 assert 'int__type_to_str' !in call_names
1907}
1908
1909fn test_sumtype_type_name_on_imported_struct_field_is_lowered() {
1910 files := transform_sources_for_test([
1911 TestSource{
1912 rel: 'astx/astx.v'
1913 code: 'module astx
1914
1915pub type Expr = Ident | Number
1916
1917pub struct Ident {}
1918
1919pub struct Number {}
1920
1921pub struct CallExpr {
1922pub:
1923 lhs Expr
1924}
1925'
1926 },
1927 TestSource{
1928 rel: 'main.v'
1929 code: 'module main
1930
1931import astx
1932
1933fn call_target_kind(expr astx.CallExpr) string {
1934 return expr.lhs.type_name()
1935}
1936'
1937 },
1938 ])
1939 call_names := call_names_for_fn(files, 'call_target_kind')
1940 assert 'astx__Expr__type_name' !in call_names
1941 assert 'Expr__type_name' !in call_names
1942}
1943
1944fn test_map_alias_field_clone_lowers_to_builtin_map_clone() {
1945 files := transform_code_for_test('
1946type TypeID = int
1947
1948struct TypeStore {
1949 cache map[string]TypeID
1950}
1951
1952struct Module {
1953 type_store TypeStore
1954}
1955
1956fn clone_cache(m Module) map[string]TypeID {
1957 return m.type_store.cache.clone()
1958}
1959')
1960 call_names := call_names_for_fn(files, 'clone_cache')
1961 assert 'map__clone' in call_names
1962 assert 'Map_string_TypeID__clone' !in call_names
1963 assert 'Map_string_main__TypeID__clone' !in call_names
1964}
1965
1966fn test_string_index_byte_methods_on_nested_receiver_fields_use_u8_receiver() {
1967 files := transform_code_for_test('
1968struct Scanner {
1969 src string
1970 offset int
1971}
1972
1973struct Parser {
1974 scanner &Scanner
1975}
1976
1977fn (c u8) is_space() bool {
1978 return true
1979}
1980
1981fn (c u8) is_letter() bool {
1982 return true
1983}
1984
1985fn (p &Parser) peek_dollar_keyword() string {
1986 if p.scanner.offset >= p.scanner.src.len {
1987 return ""
1988 }
1989 mut idx := p.scanner.offset
1990 for idx < p.scanner.src.len && p.scanner.src[idx].is_space() {
1991 idx++
1992 }
1993 start := idx
1994 for idx < p.scanner.src.len && p.scanner.src[idx].is_letter() {
1995 idx++
1996 }
1997 return p.scanner.src[start..idx]
1998}
1999')
2000 call_names := call_names_for_fn(files, 'peek_dollar_keyword')
2001 assert 'u8__is_space' in call_names
2002 assert 'u8__is_letter' in call_names
2003 assert 'int__is_space' !in call_names
2004 assert 'int__is_letter' !in call_names
2005}
2006
2007fn test_smartcast_call_arg_keeps_original_sumtype_when_param_expects_sumtype() {
2008 files := transform_code_for_test('
2009struct Table {}
2010
2011struct Checker {
2012 table Table
2013}
2014
2015struct GlobalField {}
2016struct Var {}
2017
2018type ScopeObject = GlobalField | Var
2019
2020fn (t Table) is_interface_var(obj ScopeObject) bool {
2021 _ = t
2022 _ = obj
2023 return true
2024}
2025
2026fn uses_scope_object(mut c Checker, mut obj ScopeObject) bool {
2027 match mut obj {
2028 Var {
2029 return c.table.is_interface_var(obj)
2030 }
2031 else {}
2032 }
2033 return false
2034}
2035')
2036 for file in files {
2037 for stmt in file.stmts {
2038 if stmt is ast.FnDecl && stmt.name == 'uses_scope_object' {
2039 call := find_call_with_lhs_suffix_in_stmts(stmt.stmts, '__is_interface_var') or {
2040 assert false, 'expected transformed is_interface_var call'
2041 return
2042 }
2043 assert call.args.len > 0
2044 arg := call.args[call.args.len - 1]
2045 assert arg is ast.Ident, 'sumtype call arg was wrapped as ${arg.type_name()}'
2046 assert (arg as ast.Ident).name == 'obj'
2047 return
2048 }
2049 }
2050 }
2051 assert false, 'uses_scope_object was not found'
2052}
2053
2054fn test_smartcast_method_call_arg_keeps_original_sumtype_when_param_expects_sumtype() {
2055 files := transform_code_for_test('
2056struct Holder {}
2057struct Gen {}
2058
2059type Type = Holder | int
2060
2061fn (mut g Gen) use_type(value Type, holder Holder) {
2062 _ = g
2063 _ = value
2064 _ = holder
2065}
2066
2067fn use_smartcasted_type(mut g Gen, concrete Type) {
2068 if concrete is Holder {
2069 g.use_type(concrete, concrete as Holder)
2070 }
2071}
2072')
2073 for file in files {
2074 for stmt in file.stmts {
2075 if stmt is ast.FnDecl && stmt.name == 'use_smartcasted_type' {
2076 call := find_call_with_lhs_suffix_in_stmts(stmt.stmts, 'Gen__use_type') or {
2077 assert false, 'expected transformed Gen__use_type call'
2078 return
2079 }
2080 assert call.args.len == 3
2081 arg := call.args[1]
2082 assert arg is ast.Ident, 'sumtype method call arg was wrapped as ${arg.type_name()}'
2083 assert (arg as ast.Ident).name == 'concrete'
2084 return
2085 }
2086 }
2087 }
2088 assert false, 'use_smartcasted_type was not found'
2089}
2090
2091fn test_map_index_or_decl_keeps_declared_sumtype_for_later_smartcast_call_arg() {
2092 files := transform_code_for_test('
2093struct Holder {}
2094struct Gen {}
2095
2096type Type = Holder | int
2097
2098fn (mut g Gen) use_type(value Type, holder Holder) {
2099 _ = g
2100 _ = value
2101 _ = holder
2102}
2103
2104fn use_map_or_smartcasted_type(mut g Gen, active map[string]Type, name string) {
2105 concrete := active[name] or { return }
2106 if concrete !is Holder {
2107 return
2108 }
2109 g.use_type(concrete, concrete as Holder)
2110}
2111')
2112 for file in files {
2113 for stmt in file.stmts {
2114 if stmt is ast.FnDecl && stmt.name == 'use_map_or_smartcasted_type' {
2115 call := find_call_with_lhs_suffix_in_stmts(stmt.stmts, 'Gen__use_type') or {
2116 assert false, 'expected transformed Gen__use_type call'
2117 return
2118 }
2119 assert call.args.len == 3
2120 arg := call.args[1]
2121 assert arg is ast.Ident, 'sumtype method call arg was wrapped as ${arg.type_name()}'
2122 assert (arg as ast.Ident).name == 'concrete'
2123 return
2124 }
2125 }
2126 }
2127 assert false, 'use_map_or_smartcasted_type was not found'
2128}
2129
2130fn test_sumtype_call_arg_uses_declared_local_type_when_current_type_is_narrowed() {
2131 global_field_type := types.Type(types.Struct{
2132 name: 'GlobalField'
2133 })
2134 var_type := types.Type(types.Struct{
2135 name: 'Var'
2136 })
2137 scope_object_type := types.Type(types.SumType{
2138 name: 'ScopeObject'
2139 variants: [global_field_type, var_type]
2140 })
2141 mut scope := types.new_scope(unsafe { nil })
2142 scope.insert('GlobalField', global_field_type)
2143 scope.insert('Var', var_type)
2144 scope.insert('ScopeObject', scope_object_type)
2145 scope.insert_or_update('obj', var_type)
2146 mut env := types.Environment.new()
2147 obj_pos := token.Pos{
2148 id: 8901
2149 }
2150 env.set_expr_type(obj_pos.id, var_type)
2151 mut t := &Transformer{
2152 pref: &vpref.Preferences{}
2153 env: unsafe { env }
2154 scope: scope
2155 fn_root_scope: scope
2156 cached_scopes: {
2157 'main': scope
2158 }
2159 local_decl_types: {
2160 'obj': scope_object_type
2161 }
2162 needed_clone_fns: map[string]string{}
2163 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2164 needed_array_index_fns: map[string]ArrayMethodInfo{}
2165 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2166 smartcast_expr_counts: map[string]int{}
2167 }
2168 arg := ast.Expr(ast.Ident{
2169 name: 'obj'
2170 pos: obj_pos
2171 })
2172 out := t.transform_call_arg_with_sumtype_check(arg, CallFnInfo{
2173 param_types: [scope_object_type]
2174 }, 0)
2175 assert out is ast.Ident, 'declared sumtype local was wrapped as ${out.type_name()}'
2176 assert (out as ast.Ident).name == 'obj'
2177}
2178
2179fn test_is_pointer_type_handles_unresolved_alias_base_type() {
2180 t := create_test_transformer()
2181 assert !t.is_pointer_type(types.Type(types.Alias{
2182 name: 'UnresolvedAlias'
2183 }))
2184}
2185
2186fn test_expr_to_string_handles_no_arg_selector_call_for_smartcasts() {
2187 t := create_test_transformer()
2188 call := ast.Expr(ast.CallExpr{
2189 lhs: ast.Expr(ast.SelectorExpr{
2190 lhs: ast.Expr(ast.SelectorExpr{
2191 lhs: ast.Expr(ast.Ident{
2192 name: 'branch'
2193 })
2194 rhs: ast.Ident{
2195 name: 'stmts'
2196 }
2197 })
2198 rhs: ast.Ident{
2199 name: 'last'
2200 }
2201 })
2202 })
2203 assert t.expr_to_string(call) == 'branch.stmts.last()'
2204}
2205
2206fn test_expr_to_string_handles_indexed_selector_for_smartcasts() {
2207 t := create_test_transformer()
2208 expr := ast.Expr(ast.SelectorExpr{
2209 lhs: ast.Expr(ast.IndexExpr{
2210 lhs: ast.Expr(ast.SelectorExpr{
2211 lhs: ast.Expr(ast.Ident{
2212 name: 'node'
2213 })
2214 rhs: ast.Ident{
2215 name: 'branches'
2216 }
2217 })
2218 expr: ast.Expr(ast.BasicLiteral{
2219 kind: .number
2220 value: '0'
2221 })
2222 })
2223 rhs: ast.Ident{
2224 name: 'cond'
2225 }
2226 })
2227 assert t.expr_to_string(expr) == 'node.branches[0].cond'
2228}
2229
2230fn test_smartcast_context_from_lowered_tag_check_uses_sumtype_metadata() {
2231 variants := [
2232 types.Type(types.Struct{
2233 name: 'ast__Ident'
2234 }),
2235 types.Type(types.Struct{
2236 name: 'ast__BasicLiteral'
2237 }),
2238 ]
2239 sum_type := types.Type(types.SumType{
2240 name: 'ast__Expr'
2241 variants: variants
2242 })
2243 mut ast_scope := types.new_scope(unsafe { nil })
2244 ast_scope.insert('Expr', sum_type)
2245 ast_scope.insert('Ident', variants[0])
2246 mut fn_scope := types.new_scope(unsafe { nil })
2247 fn_scope.insert('node', sum_type)
2248 env := &types.Environment{}
2249 t := &Transformer{
2250 pref: &vpref.Preferences{}
2251 env: unsafe { env }
2252 scope: fn_scope
2253 cur_module: 'checker'
2254 cached_scopes: {
2255 'ast': ast_scope
2256 }
2257 needed_clone_fns: map[string]string{}
2258 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2259 needed_array_index_fns: map[string]ArrayMethodInfo{}
2260 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2261 }
2262 ctx := t.smartcast_context_from_condition_term(ast.InfixExpr{
2263 op: .eq
2264 lhs: ast.Expr(ast.SelectorExpr{
2265 lhs: ast.Expr(ast.Ident{
2266 name: 'node'
2267 })
2268 rhs: ast.Ident{
2269 name: '_tag'
2270 }
2271 })
2272 rhs: ast.Expr(ast.BasicLiteral{
2273 kind: .number
2274 value: '0'
2275 })
2276 }) or {
2277 assert false, 'lowered tag check did not recover smartcast context'
2278 return
2279 }
2280 assert ctx.expr == 'node'
2281 assert ctx.variant == 'ast__Ident'
2282 assert ctx.variant_full == 'ast__Ident'
2283 assert ctx.sumtype == 'ast__Expr'
2284}
2285
2286fn test_if_mut_selector_smartcast_rewrites_body_selector() {
2287 expr_sum_type := types.Type(types.SumType{
2288 name: 'ast__Expr'
2289 variants: [
2290 types.Type(types.Struct{
2291 name: 'ast__Ident'
2292 fields: [
2293 types.Field{
2294 name: 'language'
2295 typ: types.Type(types.Enum{
2296 name: 'ast__Language'
2297 })
2298 },
2299 ]
2300 }),
2301 types.Type(types.Struct{
2302 name: 'ast__BasicLiteral'
2303 }),
2304 ]
2305 })
2306 mut ast_scope := types.new_scope(unsafe { nil })
2307 ast_scope.insert('Expr', expr_sum_type)
2308 ast_scope.insert('Ident', types.Type(types.Struct{
2309 name: 'ast__Ident'
2310 fields: [
2311 types.Field{
2312 name: 'language'
2313 typ: types.Type(types.Enum{
2314 name: 'ast__Language'
2315 })
2316 },
2317 ]
2318 }))
2319 ast_scope.insert('EnumField', types.Type(types.Struct{
2320 name: 'ast__EnumField'
2321 fields: [
2322 types.Field{
2323 name: 'expr'
2324 typ: expr_sum_type
2325 },
2326 ]
2327 }))
2328 mut fn_scope := types.new_scope(unsafe { nil })
2329 fn_scope.insert('field', types.Type(types.Pointer{
2330 base_type: types.Type(types.Struct{
2331 name: 'ast__EnumField'
2332 })
2333 }))
2334 env := &types.Environment{}
2335 mut t := &Transformer{
2336 pref: &vpref.Preferences{}
2337 env: unsafe { env }
2338 scope: fn_scope
2339 cur_module: 'checker'
2340 cached_scopes: {
2341 'ast': ast_scope
2342 }
2343 needed_clone_fns: map[string]string{}
2344 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2345 needed_array_index_fns: map[string]ArrayMethodInfo{}
2346 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2347 synth_types: map[int]types.Type{}
2348 smartcast_expr_counts: map[string]int{}
2349 }
2350 field_expr := ast.Expr(ast.SelectorExpr{
2351 lhs: ast.Expr(ast.Ident{
2352 name: 'field'
2353 })
2354 rhs: ast.Ident{
2355 name: 'expr'
2356 }
2357 })
2358 result := t.transform_expr(ast.Expr(ast.IfExpr{
2359 cond: ast.Expr(ast.InfixExpr{
2360 op: .key_is
2361 lhs: ast.Expr(ast.ModifierExpr{
2362 kind: .key_mut
2363 expr: field_expr
2364 })
2365 rhs: ast.Expr(ast.SelectorExpr{
2366 lhs: ast.Expr(ast.Ident{
2367 name: 'ast'
2368 })
2369 rhs: ast.Ident{
2370 name: 'Ident'
2371 }
2372 })
2373 })
2374 stmts: [
2375 ast.Stmt(ast.ExprStmt{
2376 expr: ast.Expr(ast.SelectorExpr{
2377 lhs: field_expr
2378 rhs: ast.Ident{
2379 name: 'language'
2380 }
2381 })
2382 }),
2383 ]
2384 }))
2385 assert result is ast.IfExpr
2386 if_expr := result as ast.IfExpr
2387 assert if_expr.stmts.len == 1
2388 assert if_expr.stmts[0] is ast.ExprStmt
2389 stmt := if_expr.stmts[0] as ast.ExprStmt
2390 assert stmt.expr is ast.SelectorExpr
2391 selector := stmt.expr as ast.SelectorExpr
2392 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2393 cast := selector.lhs as ast.CastExpr
2394 assert cast.typ.name() == 'ast__Ident*'
2395}
2396
2397fn test_for_mut_selector_smartcast_rewrites_assignment_rhs_selector() {
2398 type_info_type := types.Type(types.SumType{
2399 name: 'ast__TypeInfo'
2400 variants: [
2401 types.Type(types.Struct{
2402 name: 'ast__Array'
2403 fields: [
2404 types.Field{
2405 name: 'elem_type'
2406 typ: types.Type(types.Struct{
2407 name: 'ast__Type'
2408 })
2409 },
2410 ]
2411 }),
2412 types.Type(types.Struct{
2413 name: 'ast__Struct'
2414 }),
2415 ]
2416 })
2417 mut ast_scope := types.new_scope(unsafe { nil })
2418 ast_scope.insert('TypeInfo', type_info_type)
2419 ast_scope.insert('Array', types.Type(types.Struct{
2420 name: 'ast__Array'
2421 fields: [
2422 types.Field{
2423 name: 'elem_type'
2424 typ: types.Type(types.Struct{
2425 name: 'ast__Type'
2426 })
2427 },
2428 ]
2429 }))
2430 ast_scope.insert('TypeSymbol', types.Type(types.Struct{
2431 name: 'ast__TypeSymbol'
2432 fields: [
2433 types.Field{
2434 name: 'info'
2435 typ: type_info_type
2436 },
2437 ]
2438 }))
2439 type_type := types.Type(types.Struct{
2440 name: 'ast__Type'
2441 })
2442 mut fn_scope := types.new_scope(unsafe { nil })
2443 fn_scope.insert('elem_sym', types.Type(types.Pointer{
2444 base_type: types.Type(types.Struct{
2445 name: 'ast__TypeSymbol'
2446 })
2447 }))
2448 fn_scope.insert('elem_type', type_type)
2449 env := &types.Environment{}
2450 mut t := &Transformer{
2451 pref: &vpref.Preferences{}
2452 env: unsafe { env }
2453 scope: fn_scope
2454 cur_module: 'ast'
2455 cached_scopes: {
2456 'ast': ast_scope
2457 }
2458 needed_clone_fns: map[string]string{}
2459 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2460 needed_array_index_fns: map[string]ArrayMethodInfo{}
2461 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2462 synth_types: map[int]types.Type{}
2463 smartcast_expr_counts: map[string]int{}
2464 }
2465 info_expr := ast.Expr(ast.SelectorExpr{
2466 lhs: ast.Expr(ast.Ident{
2467 name: 'elem_sym'
2468 })
2469 rhs: ast.Ident{
2470 name: 'info'
2471 }
2472 })
2473 result := t.transform_stmt(ast.Stmt(ast.ForStmt{
2474 cond: ast.Expr(ast.InfixExpr{
2475 op: .key_is
2476 lhs: ast.Expr(ast.ModifierExpr{
2477 kind: .key_mut
2478 expr: info_expr
2479 })
2480 rhs: ast.Expr(ast.Ident{
2481 name: 'Array'
2482 })
2483 })
2484 stmts: [
2485 ast.Stmt(ast.AssignStmt{
2486 op: .assign
2487 lhs: [
2488 ast.Expr(ast.Ident{
2489 name: 'elem_type'
2490 }),
2491 ]
2492 rhs: [
2493 ast.Expr(ast.SelectorExpr{
2494 lhs: info_expr
2495 rhs: ast.Ident{
2496 name: 'elem_type'
2497 }
2498 }),
2499 ]
2500 }),
2501 ]
2502 }))
2503 assert result is ast.ForStmt
2504 for_stmt := result as ast.ForStmt
2505 assert for_stmt.stmts.len == 1
2506 assert for_stmt.stmts[0] is ast.AssignStmt
2507 assign_stmt := for_stmt.stmts[0] as ast.AssignStmt
2508 assert assign_stmt.rhs[0] is ast.SelectorExpr
2509 selector := assign_stmt.rhs[0] as ast.SelectorExpr
2510 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2511 cast := selector.lhs as ast.CastExpr
2512 assert cast.typ.name() == 'ast__Array*'
2513}
2514
2515fn test_checked_for_mut_selector_smartcast_rewrites_assignment_rhs_selector() {
2516 files := transform_code_for_test('
2517module main
2518
2519struct Type {}
2520
2521struct Array {
2522 elem_type Type
2523}
2524
2525struct Struct {}
2526
2527type TypeInfo = Array | Struct
2528
2529struct TypeSymbol {
2530mut:
2531 info TypeInfo
2532}
2533
2534fn f(mut elem_sym TypeSymbol) {
2535 mut elem_type := Type{}
2536 for mut elem_sym.info is Array {
2537 elem_type = elem_sym.info.elem_type
2538 }
2539}
2540')
2541 for file in files {
2542 for stmt in file.stmts {
2543 if stmt is ast.FnDecl && stmt.name == 'f' {
2544 for nested in stmt.stmts {
2545 if nested is ast.ForStmt {
2546 assert nested.stmts.len == 1
2547 assert nested.stmts[0] is ast.AssignStmt
2548 assign_stmt := nested.stmts[0] as ast.AssignStmt
2549 assert assign_stmt.rhs[0] is ast.SelectorExpr
2550 selector := assign_stmt.rhs[0] as ast.SelectorExpr
2551 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2552 cast := selector.lhs as ast.CastExpr
2553 assert cast.typ.name() == 'Array*'
2554 return
2555 }
2556 }
2557 }
2558 }
2559 }
2560 assert false, 'function f loop was not found'
2561}
2562
2563fn test_label_detection_ignores_empty_ast_slots() {
2564 assert !transformer_expr_contains_label_stmt(ast.empty_expr)
2565 assert !transformer_stmt_contains_label_stmt(ast.empty_stmt)
2566 assert !transformer_expr_contains_label_stmt(ast.IfExpr{
2567 cond: ast.BasicLiteral{
2568 kind: .key_true
2569 value: 'true'
2570 }
2571 stmts: []
2572 })
2573}
2574
2575fn test_lowered_tag_check_selector_smartcast_rewrites_body_selector() {
2576 expr_sum_type := types.Type(types.SumType{
2577 name: 'ast__Expr'
2578 variants: [
2579 types.Type(types.Struct{
2580 name: 'ast__Ident'
2581 fields: [
2582 types.Field{
2583 name: 'language'
2584 typ: types.Type(types.Enum{
2585 name: 'ast__Language'
2586 })
2587 },
2588 ]
2589 }),
2590 types.Type(types.Struct{
2591 name: 'ast__BasicLiteral'
2592 }),
2593 ]
2594 })
2595 mut ast_scope := types.new_scope(unsafe { nil })
2596 ast_scope.insert('Expr', expr_sum_type)
2597 ast_scope.insert('Ident', types.Type(types.Struct{
2598 name: 'ast__Ident'
2599 fields: [
2600 types.Field{
2601 name: 'language'
2602 typ: types.Type(types.Enum{
2603 name: 'ast__Language'
2604 })
2605 },
2606 ]
2607 }))
2608 ast_scope.insert('EnumField', types.Type(types.Struct{
2609 name: 'ast__EnumField'
2610 fields: [
2611 types.Field{
2612 name: 'expr'
2613 typ: expr_sum_type
2614 },
2615 ]
2616 }))
2617 mut fn_scope := types.new_scope(unsafe { nil })
2618 fn_scope.insert('field', types.Type(types.Pointer{
2619 base_type: types.Type(types.Struct{
2620 name: 'ast__EnumField'
2621 })
2622 }))
2623 env := &types.Environment{}
2624 mut t := &Transformer{
2625 pref: &vpref.Preferences{}
2626 env: unsafe { env }
2627 scope: fn_scope
2628 cur_module: 'checker'
2629 cached_scopes: {
2630 'ast': ast_scope
2631 }
2632 needed_clone_fns: map[string]string{}
2633 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2634 needed_array_index_fns: map[string]ArrayMethodInfo{}
2635 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2636 synth_types: map[int]types.Type{}
2637 smartcast_expr_counts: map[string]int{}
2638 }
2639 field_expr := ast.Expr(ast.SelectorExpr{
2640 lhs: ast.Expr(ast.Ident{
2641 name: 'field'
2642 })
2643 rhs: ast.Ident{
2644 name: 'expr'
2645 }
2646 })
2647 result := t.transform_expr(ast.Expr(ast.IfExpr{
2648 cond: ast.Expr(ast.InfixExpr{
2649 op: .eq
2650 lhs: ast.Expr(ast.SelectorExpr{
2651 lhs: field_expr
2652 rhs: ast.Ident{
2653 name: '_tag'
2654 }
2655 })
2656 rhs: ast.Expr(ast.BasicLiteral{
2657 kind: .number
2658 value: '0'
2659 })
2660 })
2661 stmts: [
2662 ast.Stmt(ast.ExprStmt{
2663 expr: ast.Expr(ast.SelectorExpr{
2664 lhs: field_expr
2665 rhs: ast.Ident{
2666 name: 'language'
2667 }
2668 })
2669 }),
2670 ]
2671 }))
2672 assert result is ast.IfExpr
2673 if_expr := result as ast.IfExpr
2674 assert if_expr.stmts.len == 1
2675 assert if_expr.stmts[0] is ast.ExprStmt
2676 stmt := if_expr.stmts[0] as ast.ExprStmt
2677 assert stmt.expr is ast.SelectorExpr
2678 selector := stmt.expr as ast.SelectorExpr
2679 assert selector.lhs is ast.CastExpr, 'selector lhs was not narrowed: ${selector.lhs.type_name()}'
2680 cast := selector.lhs as ast.CastExpr
2681 assert cast.typ.name() == 'ast__Ident*'
2682}
2683
2684fn test_assignment_rhs_call_or_cast_lhs_preserves_nested_smartcast() {
2685 stmt_sum_type := types.Type(types.SumType{
2686 name: 'ast__Stmt'
2687 variants: [
2688 types.Type(types.Struct{
2689 name: 'ast__ExprStmt'
2690 }),
2691 ]
2692 })
2693 expr_sum_type := types.Type(types.SumType{
2694 name: 'ast__Expr'
2695 variants: [
2696 types.Type(types.Struct{
2697 name: 'ast__CallExpr'
2698 }),
2699 types.Type(types.Struct{
2700 name: 'ast__InfixExpr'
2701 }),
2702 ]
2703 })
2704 or_expr_type := types.Type(types.Struct{
2705 name: 'ast__OrExpr'
2706 fields: [
2707 types.Field{
2708 name: 'scope'
2709 typ: types.Type(types.Struct{
2710 name: 'ast__Scope'
2711 })
2712 },
2713 types.Field{
2714 name: 'err_used'
2715 typ: types.Type(types.bool_)
2716 },
2717 ]
2718 })
2719 mut ast_scope := types.new_scope(unsafe { nil })
2720 ast_scope.insert('Stmt', stmt_sum_type)
2721 ast_scope.insert('Expr', expr_sum_type)
2722 ast_scope.insert('ExprStmt', types.Type(types.Struct{
2723 name: 'ast__ExprStmt'
2724 fields: [
2725 types.Field{
2726 name: 'expr'
2727 typ: expr_sum_type
2728 },
2729 ]
2730 }))
2731 ast_scope.insert('InfixExpr', types.Type(types.Struct{
2732 name: 'ast__InfixExpr'
2733 fields: [
2734 types.Field{
2735 name: 'or_block'
2736 typ: or_expr_type
2737 },
2738 types.Field{
2739 name: 'right'
2740 typ: expr_sum_type
2741 },
2742 ]
2743 }))
2744 ast_scope.insert('CallExpr', types.Type(types.Struct{
2745 name: 'ast__CallExpr'
2746 }))
2747 mut fn_scope := types.new_scope(unsafe { nil })
2748 fn_scope.insert('node', stmt_sum_type)
2749 env := &types.Environment{}
2750 mut t := &Transformer{
2751 pref: &vpref.Preferences{}
2752 env: unsafe { env }
2753 scope: fn_scope
2754 cur_module: 'checker'
2755 cached_scopes: {
2756 'ast': ast_scope
2757 }
2758 needed_clone_fns: map[string]string{}
2759 needed_array_contains_fns: map[string]ArrayMethodInfo{}
2760 needed_array_index_fns: map[string]ArrayMethodInfo{}
2761 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
2762 synth_types: map[int]types.Type{}
2763 smartcast_expr_counts: map[string]int{}
2764 }
2765 t.push_smartcast_full('node', 'ast__ExprStmt', 'ast__ExprStmt', 'ast__Stmt')
2766 t.push_smartcast_full('node.expr', 'ast__InfixExpr', 'ast__InfixExpr', 'ast__Expr')
2767 t.push_smartcast_full('node.expr.right', 'ast__CallExpr', 'ast__CallExpr', 'ast__Expr')
2768 node_ident := ast.Expr(ast.Ident{
2769 name: 'node'
2770 })
2771 node_expr := ast.Expr(ast.SelectorExpr{
2772 lhs: node_ident
2773 rhs: ast.Ident{
2774 name: 'expr'
2775 }
2776 })
2777 node_or_block := ast.Expr(ast.SelectorExpr{
2778 lhs: node_expr
2779 rhs: ast.Ident{
2780 name: 'or_block'
2781 }
2782 })
2783 node_scope := ast.Expr(ast.SelectorExpr{
2784 lhs: node_or_block
2785 rhs: ast.Ident{
2786 name: 'scope'
2787 }
2788 })
2789 transformed := t.transform_assign_stmt(ast.AssignStmt{
2790 op: .assign
2791 lhs: [
2792 ast.Expr(ast.SelectorExpr{
2793 lhs: node_or_block
2794 rhs: ast.Ident{
2795 name: 'err_used'
2796 }
2797 }),
2798 ]
2799 rhs: [
2800 ast.Expr(ast.CallOrCastExpr{
2801 lhs: ast.Expr(ast.SelectorExpr{
2802 lhs: node_scope
2803 rhs: ast.Ident{
2804 name: 'known_var'
2805 }
2806 })
2807 expr: ast.Expr(ast.StringLiteral{
2808 kind: .v
2809 value: "'err'"
2810 })
2811 }),
2812 ]
2813 })
2814 assert transformed.rhs[0] is ast.CallExpr
2815 call := transformed.rhs[0] as ast.CallExpr
2816 assert call.lhs is ast.SelectorExpr
2817 call_lhs := call.lhs as ast.SelectorExpr
2818 assert call_lhs.lhs is ast.SelectorExpr
2819 scope_sel := call_lhs.lhs as ast.SelectorExpr
2820 assert scope_sel.lhs is ast.SelectorExpr
2821 or_block_sel := scope_sel.lhs as ast.SelectorExpr
2822 assert or_block_sel.lhs is ast.CastExpr, 'rhs call lhs lost node.expr smartcast: ${or_block_sel.lhs.type_name()}'
2823}
2824
2825fn test_transform_folds_string_literal_concat() {
2826 mut t := create_test_transformer()
2827 result := t.transform_expr(ast.InfixExpr{
2828 op: .plus
2829 lhs: ast.Expr(ast.StringLiteral{
2830 kind: .v
2831 value: "'left-'"
2832 })
2833 rhs: ast.Expr(ast.StringLiteral{
2834 kind: .v
2835 value: "'right'"
2836 })
2837 })
2838
2839 assert result is ast.StringLiteral, 'expected StringLiteral, got ${result.type_name()}'
2840 lit := result as ast.StringLiteral
2841 assert lit.kind == .v
2842 assert lit.value == 'left-right'
2843}
2844
2845fn test_transform_keeps_generic_array_elem_equality_as_infix() {
2846 mut t := create_transformer_with_vars({
2847 'a': types.Type(types.Array{
2848 elem_type: types.Type(types.NamedType('T'))
2849 })
2850 'e': types.Type(types.NamedType('T'))
2851 })
2852 t.cur_fn_generic_params = ['T']
2853 t.generic_var_type_params = {
2854 'a': 'T'
2855 }
2856 t.env.set_expr_type(101, types.string_)
2857 t.env.set_expr_type(102, types.string_)
2858
2859 result := t.transform_infix_expr(ast.InfixExpr{
2860 op: .eq
2861 lhs: ast.Expr(ast.IndexExpr{
2862 lhs: ast.Expr(ast.Ident{
2863 name: 'a'
2864 })
2865 expr: ast.Expr(ast.Ident{
2866 name: 'idx'
2867 })
2868 pos: token.Pos{
2869 id: 101
2870 }
2871 })
2872 rhs: ast.Expr(ast.Ident{
2873 pos: token.Pos{
2874 id: 102
2875 }
2876 name: 'e'
2877 })
2878 })
2879
2880 assert result is ast.InfixExpr, 'generic equality should stay for specialized codegen'
2881}
2882
2883fn test_transform_generic_module_call_uses_specialized_name() {
2884 mut t := create_test_transformer()
2885 mut scope := types.new_scope(unsafe { nil })
2886 scope.insert('json', types.Module{
2887 name: 'x.json2'
2888 })
2889 t.scope = scope
2890
2891 result := t.transform_expr(ast.CallExpr{
2892 lhs: ast.Expr(ast.GenericArgOrIndexExpr{
2893 lhs: ast.Expr(ast.SelectorExpr{
2894 lhs: ast.Expr(ast.Ident{
2895 name: 'json'
2896 })
2897 rhs: ast.Ident{
2898 name: 'decode'
2899 }
2900 })
2901 expr: ast.Expr(ast.Ident{
2902 name: 'GitHubRepoInfo'
2903 })
2904 })
2905 args: [
2906 ast.Expr(ast.Ident{
2907 name: 'body'
2908 }),
2909 ]
2910 })
2911
2912 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
2913 call := result as ast.CallExpr
2914 assert call.lhs is ast.Ident
2915 assert (call.lhs as ast.Ident).name == 'json2__decode_T_GitHubRepoInfo'
2916 assert call.args.len == 1
2917}
2918
2919fn test_transform_generic_module_call_or_cast_uses_specialized_name() {
2920 mut t := create_test_transformer()
2921 mut scope := types.new_scope(unsafe { nil })
2922 scope.insert('json', types.Module{
2923 name: 'x.json2'
2924 })
2925 t.scope = scope
2926
2927 result := t.transform_expr(ast.CallOrCastExpr{
2928 lhs: ast.Expr(ast.GenericArgOrIndexExpr{
2929 lhs: ast.Expr(ast.SelectorExpr{
2930 lhs: ast.Expr(ast.Ident{
2931 name: 'json'
2932 })
2933 rhs: ast.Ident{
2934 name: 'decode'
2935 }
2936 })
2937 expr: ast.Expr(ast.Ident{
2938 name: 'GitHubRepoInfo'
2939 })
2940 })
2941 expr: ast.Expr(ast.SelectorExpr{
2942 lhs: ast.Expr(ast.Ident{
2943 name: 'resp'
2944 })
2945 rhs: ast.Ident{
2946 name: 'body'
2947 }
2948 })
2949 })
2950
2951 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
2952 call := result as ast.CallExpr
2953 assert call.lhs is ast.Ident
2954 assert (call.lhs as ast.Ident).name == 'json2__decode_T_GitHubRepoInfo'
2955 assert call.args.len == 1
2956}
2957
2958fn test_transform_generic_module_call_or_cast_uses_array_specialized_name() {
2959 mut t := create_test_transformer()
2960 mut scope := types.new_scope(unsafe { nil })
2961 scope.insert('json', types.Module{
2962 name: 'x.json2'
2963 })
2964 t.scope = scope
2965
2966 result := t.transform_expr(ast.CallOrCastExpr{
2967 lhs: ast.Expr(ast.GenericArgOrIndexExpr{
2968 lhs: ast.Expr(ast.SelectorExpr{
2969 lhs: ast.Expr(ast.Ident{
2970 name: 'json'
2971 })
2972 rhs: ast.Ident{
2973 name: 'decode'
2974 }
2975 })
2976 expr: ast.Expr(ast.Type(ast.ArrayType{
2977 elem_type: ast.Expr(ast.Ident{
2978 name: 'GitHubContributor'
2979 })
2980 }))
2981 })
2982 expr: ast.Expr(ast.SelectorExpr{
2983 lhs: ast.Expr(ast.Ident{
2984 name: 'resp'
2985 })
2986 rhs: ast.Ident{
2987 name: 'body'
2988 }
2989 })
2990 })
2991
2992 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
2993 call := result as ast.CallExpr
2994 assert call.lhs is ast.Ident
2995 assert (call.lhs as ast.Ident).name == 'json2__decode_T_Array_GitHubContributor'
2996 assert call.args.len == 1
2997}
2998
2999fn test_transform_monomorphizes_imported_generic_module_call() {
3000 files := transform_sources_for_test([
3001 TestSource{
3002 rel: 'x/json2/decode.v'
3003 code: '
3004module json2
3005
3006struct Decoder {}
3007
3008fn (mut decoder Decoder) decode_value[T](mut value T) ! {
3009 _ = value
3010}
3011
3012pub fn decode[T]() !T {
3013 mut decoder := Decoder{}
3014 mut result := T{}
3015 decoder.decode_value(mut result)!
3016 return result
3017}
3018'
3019 },
3020 TestSource{
3021 rel: 'main.v'
3022 code: '
3023module main
3024
3025import x.json2
3026
3027struct Payload {
3028 value int
3029}
3030
3031fn main() {
3032 _ := json2.decode[Payload]() or { Payload{} }
3033}
3034'
3035 },
3036 ])
3037 call_names := call_names_for_fn(files, 'main')
3038 assert 'json2__decode_T_Payload' in call_names
3039 assert 'decode_T_Payload' !in call_names
3040
3041 mut found_prefixed_clone := false
3042 mut found_unprefixed_clone := false
3043 mut found_method_clone := false
3044 mut found_generic_decl := false
3045 for file in files {
3046 for stmt in file.stmts {
3047 if stmt is ast.FnDecl {
3048 if stmt.name == 'json2__decode_T_Payload' {
3049 found_prefixed_clone = true
3050 assert stmt.typ.generic_params.len == 0
3051 }
3052 if stmt.name == 'decode_T_Payload' {
3053 found_unprefixed_clone = true
3054 }
3055 if stmt.name == 'decode_value_T_Payload' {
3056 found_method_clone = true
3057 assert stmt.typ.generic_params.len == 0
3058 }
3059 if stmt.name == 'decode' && decl_generic_param_names(stmt).len > 0 {
3060 found_generic_decl = true
3061 }
3062 }
3063 }
3064 }
3065 assert found_prefixed_clone
3066 assert !found_unprefixed_clone
3067 assert found_method_clone
3068 assert !found_generic_decl
3069}
3070
3071fn test_transform_nested_module_selector_respects_local_shadow() {
3072 mut t := create_test_transformer()
3073 mut checker_scope := types.new_scope(unsafe { nil })
3074 checker_scope.insert('checker', types.Module{
3075 name: 'checker'
3076 })
3077 mut fn_scope := types.new_scope(checker_scope)
3078 fn_scope.insert('checker', types.Type(types.Struct{
3079 name: 'checker__Checker'
3080 }))
3081 t.cur_module = 'checker'
3082 t.scope = fn_scope
3083 t.cached_scopes = {
3084 'checker': checker_scope
3085 'type_resolver': types.new_scope(unsafe { nil })
3086 }
3087
3088 result := t.transform_expr(ast.SelectorExpr{
3089 lhs: ast.Expr(ast.SelectorExpr{
3090 lhs: ast.Expr(ast.Ident{
3091 name: 'checker'
3092 })
3093 rhs: ast.Ident{
3094 name: 'type_resolver'
3095 }
3096 })
3097 rhs: ast.Ident{
3098 name: 'info'
3099 }
3100 })
3101
3102 assert result is ast.SelectorExpr, 'local field chain must not become a module symbol'
3103 outer := result as ast.SelectorExpr
3104 assert outer.lhs is ast.SelectorExpr
3105 inner := outer.lhs as ast.SelectorExpr
3106 assert inner.lhs is ast.Ident
3107 assert (inner.lhs as ast.Ident).name == 'checker'
3108 assert inner.rhs.name == 'type_resolver'
3109 assert outer.rhs.name == 'info'
3110}
3111
3112fn test_expr_to_string_keeps_as_cast_selector_paths_distinct() {
3113 t := create_test_transformer()
3114 node_right := ast.Expr(ast.SelectorExpr{
3115 lhs: ast.Expr(ast.Ident{
3116 name: 'node'
3117 })
3118 rhs: ast.Ident{
3119 name: 'right'
3120 }
3121 })
3122 ast_as_cast := ast.Expr(ast.SelectorExpr{
3123 lhs: ast.Expr(ast.Ident{
3124 name: 'ast'
3125 })
3126 rhs: ast.Ident{
3127 name: 'AsCast'
3128 }
3129 })
3130 ast_par_expr := ast.Expr(ast.SelectorExpr{
3131 lhs: ast.Expr(ast.Ident{
3132 name: 'ast'
3133 })
3134 rhs: ast.Ident{
3135 name: 'ParExpr'
3136 }
3137 })
3138 as_cast_payload_expr := ast.Expr(ast.SelectorExpr{
3139 lhs: ast.Expr(ast.AsCastExpr{
3140 expr: ast.Expr(ast.SelectorExpr{
3141 lhs: node_right
3142 rhs: ast.Ident{
3143 name: 'expr'
3144 }
3145 })
3146 typ: ast_as_cast
3147 })
3148 rhs: ast.Ident{
3149 name: 'expr'
3150 }
3151 })
3152 par_expr_payload_expr := ast.Expr(ast.SelectorExpr{
3153 lhs: ast.Expr(ast.AsCastExpr{
3154 expr: node_right
3155 typ: ast_par_expr
3156 })
3157 rhs: ast.Ident{
3158 name: 'expr'
3159 }
3160 })
3161
3162 assert t.expr_to_string(as_cast_payload_expr) == '(node.right.expr as ast__AsCast).expr'
3163 assert t.expr_to_string(par_expr_payload_expr) == '(node.right as ast__ParExpr).expr'
3164 assert t.expr_to_string(as_cast_payload_expr) != t.expr_to_string(par_expr_payload_expr)
3165}
3166
3167fn test_transform_bare_generic_call_uses_specialized_name() {
3168 mut t := create_test_transformer()
3169 result := t.transform_expr(ast.CallExpr{
3170 lhs: ast.Expr(ast.GenericArgs{
3171 lhs: ast.Expr(ast.Ident{
3172 name: 'run_new'
3173 })
3174 args: [
3175 ast.Expr(ast.Ident{
3176 name: 'A'
3177 }),
3178 ast.Expr(ast.Ident{
3179 name: 'X'
3180 }),
3181 ]
3182 })
3183 args: [
3184 ast.Expr(ast.Ident{
3185 name: 'app'
3186 }),
3187 ast.Expr(ast.Ident{
3188 name: 'params'
3189 }),
3190 ]
3191 })
3192
3193 assert result is ast.CallExpr, 'expected CallExpr, got ${result.type_name()}'
3194 call := result as ast.CallExpr
3195 assert call.lhs is ast.Ident
3196 assert (call.lhs as ast.Ident).name == 'run_new_T_A_X'
3197 assert call.args.len == 2
3198}
3199
3200fn test_transform_transitive_generic_call_in_clone_emits_callee_clone() {
3201 files := transform_sources_for_test([
3202 TestSource{
3203 rel: 'webx/webx.v'
3204 code: 'module webx
3205
3206pub fn run_at[A, X]() ! {
3207 run_new[A, X]()!
3208}
3209
3210pub fn run_new[A, X]() ! {}
3211'
3212 },
3213 TestSource{
3214 rel: 'main.v'
3215 code: 'module main
3216
3217import webx
3218
3219struct App {}
3220struct Context {}
3221
3222fn boot() {
3223 webx.run_at[App, Context]() or {}
3224}
3225'
3226 },
3227 ])
3228 mut fn_names := []string{}
3229 mut webx_fn_names := []string{}
3230 mut main_fn_names := []string{}
3231 for file in files {
3232 for stmt in file.stmts {
3233 if stmt is ast.FnDecl {
3234 fn_names << stmt.name
3235 if file.mod == 'webx' {
3236 webx_fn_names << stmt.name
3237 }
3238 if file.mod == 'main' {
3239 main_fn_names << stmt.name
3240 }
3241 }
3242 }
3243 }
3244 assert 'webx__run_at_T_App_Context' in fn_names
3245 assert 'webx__run_new_T_App_Context' in fn_names
3246 assert 'webx__run_at_T_App_Context' in main_fn_names
3247 assert 'webx__run_new_T_App_Context' in main_fn_names
3248 assert 'webx__run_at_T_App_Context' !in webx_fn_names
3249 assert 'webx__run_new_T_App_Context' !in webx_fn_names
3250}
3251
3252fn test_transform_transitive_generic_call_with_mut_arg_in_clone_emits_callee_clone() {
3253 files := transform_sources_for_test([
3254 TestSource{
3255 rel: 'webx/webx.v'
3256 code: 'module webx
3257
3258pub struct RunParams {}
3259
3260pub fn run_at[A, X](mut global_app A, params RunParams) ! {
3261 run_new[A, X](mut global_app, params)!
3262}
3263
3264pub fn run_new[A, X](mut global_app A, params RunParams) ! {}
3265'
3266 },
3267 TestSource{
3268 rel: 'main.v'
3269 code: 'module main
3270
3271import webx
3272
3273struct App {}
3274struct Context {}
3275
3276fn boot() {
3277 mut app := App{}
3278 webx.run_at[App, Context](mut app, webx.RunParams{}) or {}
3279}
3280'
3281 },
3282 ])
3283 mut fn_names := []string{}
3284 mut webx_fn_names := []string{}
3285 mut main_fn_names := []string{}
3286 for file in files {
3287 for stmt in file.stmts {
3288 if stmt is ast.FnDecl {
3289 fn_names << stmt.name
3290 if file.mod == 'webx' {
3291 webx_fn_names << stmt.name
3292 }
3293 if file.mod == 'main' {
3294 main_fn_names << stmt.name
3295 }
3296 }
3297 }
3298 }
3299 assert 'webx__run_at_T_App_Context' in fn_names
3300 assert 'webx__run_new_T_App_Context' in fn_names
3301 assert 'webx__run_at_T_App_Context' in main_fn_names
3302 assert 'webx__run_new_T_App_Context' in main_fn_names
3303 assert 'webx__run_at_T_App_Context' !in webx_fn_names
3304 assert 'webx__run_new_T_App_Context' !in webx_fn_names
3305}
3306
3307fn test_transform_transitive_generic_function_value_in_clone_emits_full_callee_clone() {
3308 files := transform_sources_for_test([
3309 TestSource{
3310 rel: 'webx/webx.v'
3311 code: 'module webx
3312
3313pub struct ServerConfig {
3314 handler fn (req int) int
3315}
3316
3317pub struct Context {}
3318
3319pub fn run_at[A, X]() {
3320 run_new[A, X]()
3321}
3322
3323pub fn run_new[A, X]() {
3324 _ := ServerConfig{
3325 handler: parallel_request_handler[A, X]
3326 }
3327}
3328
3329fn parallel_request_handler[A, X](req int) int {
3330 return route[A, X](req)
3331}
3332
3333fn route[A, X](req int) int {
3334 return req
3335}
3336'
3337 },
3338 TestSource{
3339 rel: 'main.v'
3340 code: 'module main
3341
3342import webx
3343
3344struct App {}
3345struct Context {}
3346
3347fn boot() {
3348 webx.run_at[App, Context]()
3349}
3350'
3351 },
3352 ])
3353 mut main_fn_names := []string{}
3354 mut handler_name := ''
3355 for file in files {
3356 if file.mod != 'main' {
3357 continue
3358 }
3359 for stmt in file.stmts {
3360 if stmt is ast.FnDecl {
3361 main_fn_names << stmt.name
3362 if stmt.name == 'webx__run_new_T_App_Context' {
3363 assign := stmt.stmts[0] as ast.AssignStmt
3364 init := assign.rhs[0] as ast.InitExpr
3365 if init.fields[0].value is ast.Ident {
3366 handler_name = (init.fields[0].value as ast.Ident).name
3367 } else {
3368 handler_name = init.fields[0].value.type_name()
3369 }
3370 }
3371 }
3372 }
3373 }
3374 assert 'webx__run_new_T_App_Context' in main_fn_names
3375 assert handler_name == 'webx__parallel_request_handler_T_App_Context'
3376 assert 'webx__parallel_request_handler_T_App_Context' in main_fn_names
3377 assert 'webx__route_T_App_Context' in main_fn_names
3378}
3379
3380fn test_transform_imported_clone_substituted_init_type_keeps_declaring_module_context() {
3381 files := transform_sources_for_test([
3382 TestSource{
3383 rel: 'webx/webx.v'
3384 code: 'module webx
3385
3386pub struct Context {}
3387
3388pub fn make[A, X]() {
3389 ctx := &Context{}
3390 mut user_context := X{
3391 Context: ctx
3392 }
3393 route[A, X](mut user_context)
3394}
3395
3396fn route[A, X](mut user_context X) {
3397 _ = user_context
3398}
3399'
3400 },
3401 TestSource{
3402 rel: 'main.v'
3403 code: 'module main
3404
3405import webx
3406
3407struct App {}
3408
3409struct Context {
3410 webx.Context
3411}
3412
3413fn boot() {
3414 webx.make[App, Context]()
3415}
3416'
3417 },
3418 ])
3419 mut init_type_name := ''
3420 mut main_fn_names := []string{}
3421 for file in files {
3422 if file.mod != 'main' {
3423 continue
3424 }
3425 for stmt in file.stmts {
3426 if stmt is ast.FnDecl && stmt.name == 'webx__make_T_App_Context' {
3427 main_fn_names << stmt.name
3428 assign := stmt.stmts[1] as ast.AssignStmt
3429 init := assign.rhs[0] as ast.InitExpr
3430 init_type := init.typ as ast.Ident
3431 init_type_name = init_type.name
3432 } else if stmt is ast.FnDecl {
3433 main_fn_names << stmt.name
3434 }
3435 }
3436 }
3437 assert init_type_name == 'Context'
3438 assert 'webx__route_T_App_Context' in main_fn_names
3439}
3440
3441fn test_transform_generic_struct_clone_qualifies_module_type_arg_in_fn_field() {
3442 files := transform_sources_for_test([
3443 TestSource{
3444 rel: 'veb/veb.v'
3445 code: 'module veb
3446
3447pub struct Context {}
3448
3449pub struct MiddlewareOptions[T] {
3450 handler fn (mut ctx T) bool
3451}
3452
3453pub fn make[T]() MiddlewareOptions[T] {
3454 return MiddlewareOptions[T]{}
3455}
3456'
3457 },
3458 TestSource{
3459 rel: 'main.v'
3460 code: 'module main
3461
3462import veb
3463
3464struct Context {}
3465
3466fn boot() {
3467 _ := veb.make[veb.Context]()
3468}
3469'
3470 },
3471 ])
3472 mut clone_name := ''
3473 mut handler_param_name := ''
3474 for file in files {
3475 if file.mod != 'veb' {
3476 continue
3477 }
3478 for stmt in file.stmts {
3479 if stmt is ast.StructDecl && stmt.name == 'MiddlewareOptions_T_veb_Context' {
3480 clone_name = stmt.name
3481 field_typ := stmt.fields[0].typ as ast.Type
3482 fn_typ := field_typ as ast.FnType
3483 param_ident := fn_typ.params[0].typ as ast.Ident
3484 handler_param_name = param_ident.name
3485 }
3486 }
3487 }
3488 assert clone_name == 'MiddlewareOptions_T_veb_Context'
3489 assert handler_param_name == 'veb__Context'
3490}
3491
3492fn test_transform_embedded_generic_method_options_do_not_emit_bare_import_context() {
3493 files := transform_sources_for_test([
3494 TestSource{
3495 rel: 'veb/veb.v'
3496 code: 'module veb
3497
3498pub struct Context {}
3499
3500pub struct Middleware[T] {}
3501
3502@[params]
3503pub struct MiddlewareOptions[T] {
3504 handler fn (mut ctx T) bool
3505}
3506
3507pub fn (mut m Middleware[T]) use(options MiddlewareOptions[T]) {
3508 _ = options
3509}
3510'
3511 },
3512 TestSource{
3513 rel: 'main.v'
3514 code: 'module main
3515
3516import veb
3517
3518struct App {
3519 veb.Middleware[Context]
3520}
3521
3522struct Context {
3523 veb.Context
3524}
3525
3526fn (app &App) before_request(mut ctx Context) bool {
3527 return true
3528}
3529
3530fn boot() {
3531 mut app := App{}
3532 app.use(handler: app.before_request)
3533}
3534'
3535 },
3536 ])
3537 mut import_handler_param := ''
3538 mut main_handler_param := ''
3539 for file in files {
3540 for stmt in file.stmts {
3541 match stmt {
3542 ast.StructDecl {
3543 if !stmt.name.contains('MiddlewareOptions_T') {
3544 continue
3545 }
3546 field_typ := stmt.fields[0].typ as ast.Type
3547 fn_typ := field_typ as ast.FnType
3548 param_ident := fn_typ.params[0].typ as ast.Ident
3549 if file.mod == 'veb' && stmt.name == 'MiddlewareOptions_T_veb_Context' {
3550 import_handler_param = param_ident.name
3551 }
3552 if file.mod == 'main' && stmt.name == 'veb__MiddlewareOptions_T_Context' {
3553 main_handler_param = param_ident.name
3554 }
3555 }
3556 else {}
3557 }
3558 }
3559 }
3560 assert import_handler_param == '' || import_handler_param == 'veb__Context', 'import_handler_param=${import_handler_param}, main_handler_param=${main_handler_param}'
3561
3562 assert main_handler_param == '' || main_handler_param == 'Context', 'import_handler_param=${import_handler_param}, main_handler_param=${main_handler_param}'
3563}
3564
3565fn test_transform_imported_generic_struct_with_local_type_stays_in_call_file() {
3566 files := transform_sources_for_test([
3567 TestSource{
3568 rel: 'json2/json2.v'
3569 code: 'module json2
3570
3571pub struct StructKeyDecodeResult[T] {
3572 value T
3573}
3574
3575pub fn decode[T]() StructKeyDecodeResult[T] {
3576 return StructKeyDecodeResult[T]{}
3577}
3578'
3579 },
3580 TestSource{
3581 rel: 'main.v'
3582 code: 'module main
3583
3584import json2
3585
3586struct LocalResponse {}
3587
3588fn boot() {
3589 _ := json2.decode[LocalResponse]()
3590}
3591'
3592 },
3593 ])
3594 mut import_struct_found := false
3595 mut import_fn_found := false
3596 mut main_struct_found := false
3597 mut main_fn_found := false
3598 mut main_field_typ := ''
3599 for file in files {
3600 for stmt in file.stmts {
3601 match stmt {
3602 ast.StructDecl {
3603 if stmt.name == 'json2__StructKeyDecodeResult_T_LocalResponse'
3604 || stmt.name == 'StructKeyDecodeResult_T_LocalResponse' {
3605 if file.mod == 'json2' {
3606 import_struct_found = true
3607 }
3608 if file.mod == 'main'
3609 && stmt.name == 'json2__StructKeyDecodeResult_T_LocalResponse' {
3610 main_struct_found = true
3611 field_typ := stmt.fields[0].typ as ast.Ident
3612 main_field_typ = field_typ.name
3613 }
3614 }
3615 }
3616 ast.FnDecl {
3617 if stmt.name == 'json2__decode_T_LocalResponse'
3618 || stmt.name == 'decode_T_LocalResponse' {
3619 if file.mod == 'json2' {
3620 import_fn_found = true
3621 }
3622 if file.mod == 'main' && stmt.name == 'json2__decode_T_LocalResponse' {
3623 main_fn_found = true
3624 }
3625 }
3626 }
3627 else {}
3628 }
3629 }
3630 }
3631 assert !import_struct_found
3632 assert !import_fn_found
3633 assert main_struct_found
3634 assert main_fn_found
3635 assert main_field_typ == 'LocalResponse'
3636}
3637
3638fn test_transform_generic_struct_emits_module_clone_after_file_local_clone() {
3639 files := transform_sources_for_test([
3640 TestSource{
3641 rel: 'other/other.v'
3642 code: 'module other
3643
3644pub struct Type {}
3645'
3646 },
3647 TestSource{
3648 rel: 'm/m.v'
3649 code: 'module m
3650
3651import other
3652
3653pub struct Box[T] {
3654 value T
3655}
3656
3657pub fn use_external() Box[other.Type] {
3658 return Box[other.Type]{}
3659}
3660
3661pub fn use_int() Box[int] {
3662 return Box[int]{}
3663}
3664'
3665 },
3666 TestSource{
3667 rel: 'main.v'
3668 code: 'module main
3669
3670import m
3671
3672fn boot() {
3673 _ := m.use_external()
3674 _ := m.use_int()
3675}
3676'
3677 },
3678 ])
3679 mut m_structs := []string{}
3680 for file in files {
3681 if file.mod != 'm' {
3682 continue
3683 }
3684 for stmt in file.stmts {
3685 if stmt is ast.StructDecl {
3686 m_structs << stmt.name
3687 }
3688 }
3689 }
3690 assert 'Box_T_other_Type' in m_structs
3691 assert 'Box_T_int' in m_structs
3692}
3693
3694fn test_transform_moved_generic_struct_qualifies_source_module_field_types() {
3695 files := transform_sources_for_test([
3696 TestSource{
3697 rel: 'boxlib/boxlib.v'
3698 code: 'module boxlib
3699
3700pub struct Helper {}
3701
3702pub struct Box[T] {
3703 Helper
3704 helper Helper
3705 values []Helper
3706 value T
3707}
3708
3709pub fn make[T]() Box[T] {
3710 return Box[T]{}
3711}
3712'
3713 },
3714 TestSource{
3715 rel: 'main.v'
3716 code: 'module main
3717
3718import boxlib
3719
3720struct LocalResponse {}
3721
3722fn boot() {
3723 _ := boxlib.make[LocalResponse]()
3724}
3725'
3726 },
3727 ])
3728 mut moved_struct_found := false
3729 mut embedded_typ := ''
3730 mut helper_typ := ''
3731 mut values_elem_typ := ''
3732 mut value_typ := ''
3733 for file in files {
3734 if file.mod != 'main' {
3735 continue
3736 }
3737 for stmt in file.stmts {
3738 if stmt is ast.StructDecl && stmt.name == 'boxlib__Box_T_LocalResponse' {
3739 moved_struct_found = true
3740 if stmt.embedded.len > 0 {
3741 embedded_ident := stmt.embedded[0] as ast.Ident
3742 embedded_typ = embedded_ident.name
3743 }
3744 for field in stmt.fields {
3745 match field.name {
3746 'helper' {
3747 helper_ident := field.typ as ast.Ident
3748 helper_typ = helper_ident.name
3749 }
3750 'values' {
3751 array_typ := field.typ as ast.Type
3752 values_array := array_typ as ast.ArrayType
3753 values_elem_ident := values_array.elem_type as ast.Ident
3754 values_elem_typ = values_elem_ident.name
3755 }
3756 'value' {
3757 value_ident := field.typ as ast.Ident
3758 value_typ = value_ident.name
3759 }
3760 else {}
3761 }
3762 }
3763 }
3764 }
3765 }
3766 assert moved_struct_found
3767 assert embedded_typ == 'boxlib__Helper'
3768 assert helper_typ == 'boxlib__Helper'
3769 assert values_elem_typ == 'boxlib__Helper'
3770 assert value_typ == 'LocalResponse'
3771}
3772
3773fn test_transform_transitive_imported_clone_substitutes_nested_generic_route_context() {
3774 env, files := transform_sources_with_env_for_test([
3775 TestSource{
3776 rel: 'webx/webx.v'
3777 code: 'module webx
3778
3779pub struct Request {
3780 route int
3781}
3782
3783pub struct ServerConfig {
3784 handler fn (req Request) &Context
3785}
3786
3787pub struct Context {
3788 current_path string
3789}
3790
3791pub fn run_new[A, X](mut global_app A) {
3792 _ := ServerConfig{
3793 handler: parallel_request_handler[A, X]
3794 }
3795 _ = global_app
3796}
3797
3798fn parallel_request_handler[A, X](req Request) &Context {
3799 mut app := A{}
3800 return handle_request_and_route[A, X](mut app, req)
3801}
3802
3803fn handle_request_and_route[A, X](mut app A, req Request) &Context {
3804 mut ctx := &Context{}
3805 mut user_context := X{
3806 Context: ctx
3807 }
3808 handle_route[A, X](mut app, mut user_context, req.route)
3809 return ctx
3810}
3811
3812fn handle_route[A, X](mut app A, mut user_context X, route int) {
3813 _ = app
3814 _ = user_context
3815 _ = route
3816}
3817'
3818 },
3819 TestSource{
3820 rel: 'main.v'
3821 code: 'module main
3822
3823import webx
3824
3825struct App {}
3826
3827struct Context {
3828 webx.Context
3829}
3830
3831fn boot() {
3832 mut app := App{}
3833 webx.run_new[App, Context](mut app)
3834}
3835'
3836 },
3837 ])
3838 mut main_fn_names := []string{}
3839 mut init_type_name := ''
3840 mut route_call_name := ''
3841 for file in files {
3842 if file.mod != 'main' {
3843 continue
3844 }
3845 for stmt in file.stmts {
3846 if stmt is ast.FnDecl {
3847 main_fn_names << stmt.name
3848 if stmt.name == 'webx__handle_request_and_route_T_App_Context' {
3849 for inner in stmt.stmts {
3850 if inner is ast.AssignStmt && inner.lhs.len == 1 && inner.rhs.len == 1 {
3851 lhs_name := ident_name_from_expr_for_test(inner.lhs[0]) or { '' }
3852 if lhs_name == 'user_context' && inner.rhs[0] is ast.InitExpr {
3853 init := inner.rhs[0] as ast.InitExpr
3854 init_type := init.typ as ast.Ident
3855 init_type_name = init_type.name
3856 }
3857 }
3858 if inner is ast.ExprStmt && inner.expr is ast.CallExpr {
3859 call := inner.expr as ast.CallExpr
3860 if call.lhs is ast.Ident {
3861 route_call_name = (call.lhs as ast.Ident).name
3862 } else {
3863 route_call_name = call.lhs.type_name()
3864 }
3865 }
3866 }
3867 }
3868 }
3869 }
3870 }
3871 assert 'webx__parallel_request_handler_T_App_Context' in main_fn_names
3872 assert 'webx__handle_request_and_route_T_App_Context' in main_fn_names
3873 assert 'webx__handle_route_T_App_Context' in main_fn_names
3874 assert init_type_name == 'Context'
3875 assert route_call_name == 'webx__handle_route_T_App_Context'
3876 route_scope := env.get_fn_scope('main', 'webx__handle_route_T_App_Context') or {
3877 panic('missing imported route clone scope')
3878 }
3879 user_context_type := route_scope.lookup_var_type('user_context') or {
3880 panic('missing user_context type')
3881 }
3882 assert user_context_type.name() == 'Context'
3883}
3884
3885fn test_transform_inferred_generic_method_call_uses_specialized_name() {
3886 files := transform_code_for_test('
3887module main
3888
3889struct Job {}
3890
3891struct Item {
3892 value int
3893}
3894
3895struct Out {
3896mut:
3897 value int
3898}
3899
3900fn fill(item Item, mut out Out) {
3901 out.value = item.value
3902}
3903
3904fn (job &Job) apply[K, D, F](items []K, mut out []D, f F) {
3905 _ = job
3906 if items.len > 0 && out.len > 0 {
3907 f(items[0], mut out[0])
3908 }
3909}
3910
3911fn use_apply() int {
3912 job := Job{}
3913 items := [Item{
3914 value: 7
3915 }]
3916 mut out := []Out{len: 1}
3917 job.apply(items, mut out, fill)
3918 return out[0].value
3919}
3920')
3921 call_names := call_names_for_fn(files, 'use_apply')
3922 assert 'Job__apply_T_Item_Out_fn_item_Item_out_Out_void' in call_names
3923 assert 'Job__apply' !in call_names
3924
3925 mut found_clone := false
3926 for file in files {
3927 for stmt in file.stmts {
3928 if stmt is ast.FnDecl && stmt.name == 'apply_T_Item_Out_fn_item_Item_out_Out_void' {
3929 found_clone = true
3930 assert stmt.typ.generic_params.len == 0
3931 assert stmt.typ.params.len == 3
3932 assert stmt.typ.params[2].typ is ast.Type
3933 fn_param_type := stmt.typ.params[2].typ as ast.Type
3934 assert fn_param_type is ast.FnType
3935 }
3936 }
3937 }
3938 assert found_clone
3939}
3940
3941fn test_transform_non_generic_method_does_not_match_generic_method_short_name() {
3942 files := transform_code_for_test('
3943module main
3944
3945struct Vec3[T] {
3946 x T
3947 y T
3948 z T
3949}
3950
3951fn (v Vec3[T]) div(u Vec3[T]) Vec3[T] {
3952 _ = u
3953 return v
3954}
3955
3956struct Float3 {
3957 x f32
3958 y f32
3959 z f32
3960}
3961
3962fn (a Float3) div(s f32) Float3 {
3963 _ = s
3964 return a
3965}
3966
3967fn use_float(a Float3) {
3968 _ = a.div(f32(2))
3969}
3970')
3971 call_names := call_names_for_fn(files, 'use_float')
3972 assert 'Float3__div' in call_names
3973 assert 'Float3__div_T_f32' !in call_names
3974}
3975
3976fn test_transform_inferred_generic_fn_call_uses_specialized_fn_arg_name() {
3977 files := transform_code_for_test('
3978module main
3979
3980struct Alpha {}
3981
3982fn use_alpha(mut a Alpha) {
3983 _ = a
3984}
3985
3986fn call_cb[T, F](mut value T, cb F) {
3987 cb(mut value)
3988}
3989
3990fn main() {
3991 mut a := Alpha{}
3992 call_cb(mut a, use_alpha)
3993}
3994')
3995 call_names := call_names_for_fn(files, 'main')
3996 assert 'call_cb_T_Alpha_fn_a_Alpha_void' in call_names
3997 assert 'call_cb' !in call_names
3998 assert 'call_cb_T_Alpha_voidptr' !in call_names
3999}
4000
4001fn test_transform_records_inferred_generic_binding_before_monomorphize() {
4002 files := transform_code_for_test('
4003module main
4004
4005fn clamp[T](a T, x T, b T) T {
4006 mut min := T(0)
4007 if x < b {
4008 min = x
4009 } else {
4010 min = b
4011 }
4012 return if min < a { a } else { min }
4013}
4014
4015fn main() {
4016 ratio := f32(0.5)
4017 _ = clamp(f32(0), ratio, 1.0)
4018}
4019')
4020 call_names := call_names_for_fn(files, 'main')
4021 assert 'clamp_T_f32' in call_names
4022 mut found_clone := false
4023 for file in files {
4024 for stmt in file.stmts {
4025 if stmt is ast.FnDecl && stmt.name == 'clamp_T_f32' {
4026 found_clone = true
4027 assert stmt.typ.generic_params.len == 0
4028 assert stmt.stmts.len > 0
4029 }
4030 }
4031 }
4032 assert found_clone
4033}
4034
4035fn stmts_have_defer(stmts []ast.Stmt) bool {
4036 for stmt in stmts {
4037 match stmt {
4038 ast.BlockStmt {
4039 if stmts_have_defer(stmt.stmts) {
4040 return true
4041 }
4042 }
4043 ast.ComptimeStmt {
4044 if stmts_have_defer([stmt.stmt]) {
4045 return true
4046 }
4047 }
4048 ast.DeferStmt {
4049 return true
4050 }
4051 ast.ExprStmt {
4052 if stmt.expr is ast.IfExpr {
4053 if stmts_have_defer(stmt.expr.stmts) {
4054 return true
4055 }
4056 }
4057 }
4058 ast.ForStmt {
4059 if stmts_have_defer(stmt.stmts) {
4060 return true
4061 }
4062 }
4063 else {}
4064 }
4065 }
4066 return false
4067}
4068
4069fn test_lower_defer_stmts_lowers_defer_inside_comptime_stmt() {
4070 mut t := create_test_transformer()
4071 cleanup_stmt := ast.Stmt(ast.ExprStmt{
4072 expr: ast.Expr(ast.Ident{
4073 name: 'cleanup'
4074 })
4075 })
4076 body_stmt := ast.Stmt(ast.ExprStmt{
4077 expr: ast.Expr(ast.Ident{
4078 name: 'body'
4079 })
4080 })
4081 lowered := t.lower_defer_stmts([
4082 ast.Stmt(ast.ComptimeStmt{
4083 stmt: ast.Stmt(ast.ForStmt{
4084 stmts: [
4085 body_stmt,
4086 ast.Stmt(ast.DeferStmt{
4087 stmts: [cleanup_stmt]
4088 }),
4089 ]
4090 })
4091 }),
4092 ], false, types.Type(types.void_))
4093
4094 assert !stmts_have_defer(lowered)
4095 assert lowered.len == 1
4096 assert lowered[0] is ast.ComptimeStmt
4097 comptime_stmt := lowered[0] as ast.ComptimeStmt
4098 assert comptime_stmt.stmt is ast.ForStmt
4099 for_stmt := comptime_stmt.stmt as ast.ForStmt
4100 assert for_stmt.stmts.len == 2
4101 assert for_stmt.stmts[1] is ast.ExprStmt
4102 assert (for_stmt.stmts[1] as ast.ExprStmt).expr.name() == 'cleanup'
4103}
4104
4105fn test_lower_defer_fn_inside_loop_runs_on_function_exit() {
4106 mut t := create_test_transformer()
4107 cleanup_stmt := ast.Stmt(ast.ExprStmt{
4108 expr: ast.Expr(ast.Ident{
4109 name: 'cleanup'
4110 })
4111 })
4112 body_stmt := ast.Stmt(ast.ExprStmt{
4113 expr: ast.Expr(ast.Ident{
4114 name: 'body'
4115 })
4116 })
4117 lowered := t.lower_defer_stmts([
4118 ast.Stmt(ast.ForStmt{
4119 stmts: [
4120 ast.Stmt(ast.DeferStmt{
4121 mode: .function
4122 stmts: [cleanup_stmt]
4123 }),
4124 body_stmt,
4125 ]
4126 }),
4127 ast.Stmt(ast.ReturnStmt{
4128 exprs: [
4129 ast.Expr(ast.Ident{
4130 name: 'x'
4131 }),
4132 ]
4133 }),
4134 ], true, types.Type(types.int_))
4135
4136 assert !stmts_have_defer(lowered)
4137 assert lowered.len == 5
4138 assert lowered[0] is ast.AssignStmt
4139 flag_decl := lowered[0] as ast.AssignStmt
4140 assert flag_decl.op == .decl_assign
4141 assert flag_decl.lhs[0] is ast.Ident
4142 flag_name := (flag_decl.lhs[0] as ast.Ident).name
4143
4144 assert lowered[1] is ast.ForStmt
4145 for_stmt := lowered[1] as ast.ForStmt
4146 assert for_stmt.stmts.len == 2
4147 assert for_stmt.stmts[0] is ast.AssignStmt
4148 flag_assign := for_stmt.stmts[0] as ast.AssignStmt
4149 assert flag_assign.op == .assign
4150 assert flag_assign.lhs[0] is ast.Ident
4151 assert (flag_assign.lhs[0] as ast.Ident).name == flag_name
4152 assert for_stmt.stmts[1] is ast.ExprStmt
4153 assert (for_stmt.stmts[1] as ast.ExprStmt).expr.name() == 'body'
4154
4155 assert lowered[3] is ast.ExprStmt
4156 guard_stmt := lowered[3] as ast.ExprStmt
4157 assert guard_stmt.expr is ast.IfExpr
4158 guard := guard_stmt.expr as ast.IfExpr
4159 assert guard.cond is ast.Ident
4160 assert (guard.cond as ast.Ident).name == flag_name
4161 assert guard.stmts.len == 1
4162 assert guard.stmts[0] is ast.ExprStmt
4163 assert (guard.stmts[0] as ast.ExprStmt).expr.name() == 'cleanup'
4164}
4165
4166fn test_defer_return_temp_uses_value_type_for_pointer_expr_returning_value() {
4167 mut scope := types.new_scope(unsafe { nil })
4168 mut env := types.Environment.new()
4169 stmt_type := types.Type(types.SumType{
4170 name: 'ast__Stmt'
4171 })
4172 ptr_stmt_type := types.Type(types.Pointer{
4173 base_type: stmt_type
4174 })
4175 pos := token.Pos{
4176 id: 99101
4177 }
4178 env.set_expr_type(pos.id, ptr_stmt_type)
4179 mut t := &Transformer{
4180 pref: &vpref.Preferences{}
4181 env: unsafe { env }
4182 scope: scope
4183 fn_root_scope: scope
4184 needed_clone_fns: map[string]string{}
4185 needed_array_contains_fns: map[string]ArrayMethodInfo{}
4186 needed_array_index_fns: map[string]ArrayMethodInfo{}
4187 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
4188 local_decl_types: map[string]types.Type{}
4189 }
4190 t.register_defer_return_temp('_defer_t1', ast.Expr(ast.Ident{
4191 name: 'node'
4192 pos: pos
4193 }), stmt_type, map[string]types.Type{})
4194 obj := scope.lookup_parent('_defer_t1', 0) or { panic('missing defer temp') }
4195 assert obj.typ().name() == 'ast__Stmt'
4196}
4197
4198fn test_defer_return_temp_uses_sum_return_type_for_smartcast_variant() {
4199 temp_type := defer_return_temp_type(types.Type(types.Struct{
4200 name: 'ast__ForStmt'
4201 }), types.Type(types.SumType{
4202 name: 'ast__Stmt'
4203 variants: [
4204 types.Type(types.Struct{
4205 name: 'ast__ForCStmt'
4206 }),
4207 types.Type(types.Struct{
4208 name: 'ast__ForInStmt'
4209 }),
4210 types.Type(types.Struct{
4211 name: 'ast__ForStmt'
4212 }),
4213 ]
4214 }))
4215 assert temp_type.name() == 'ast__Stmt'
4216}
4217
4218fn test_transform_persists_defer_return_temp_scope_for_codegen() {
4219 env, _ := transform_code_with_env_for_test('
4220type Stmt = EmptyStmt | ForInStmt
4221
4222struct EmptyStmt {}
4223
4224struct ForInStmt {
4225 val int
4226}
4227
4228struct G {
4229mut:
4230 values map[string]int
4231}
4232
4233fn cleanup() {}
4234
4235fn (mut g G) stmt(node Stmt) Stmt {
4236 if node is ForInStmt {
4237 defer(fn) {
4238 cleanup()
4239 }
4240 mut new_node := ForInStmt{
4241 ...node
4242 }
4243 return Stmt(new_node)
4244 }
4245 return node
4246}
4247')
4248 fn_scope := env.get_fn_scope('main', 'G__stmt') or { panic('missing transformed fn scope') }
4249 mut found_defer_temp := false
4250 for name, obj in fn_scope.objects {
4251 if !name.starts_with('_defer_t') {
4252 continue
4253 }
4254 found_defer_temp = true
4255 assert obj.typ() !is types.Pointer
4256 assert obj.typ().name().ends_with('Stmt')
4257 }
4258 assert found_defer_temp
4259}
4260
4261fn test_scoped_defer_return_err_temp_uses_ierror_type() {
4262 mut scope := types.new_scope(unsafe { nil })
4263 mut t := create_test_transformer()
4264 t.scope = scope
4265 t.fn_root_scope = scope
4266 t.set_synth_pos_counter(-1)
4267 err_type := types.Type(types.Struct{
4268 name: 'IError'
4269 })
4270 t.register_defer_return_temp('_defer_t_err', ast.Expr(ast.Ident{
4271 name: 'err'
4272 }), types.Type(types.ResultType{
4273 base_type: types.Type(types.SumType{
4274 name: 'ast__Expr'
4275 })
4276 }), {
4277 'err': err_type
4278 })
4279 mut found_ierror_temp := false
4280 mut temp_types := []string{}
4281 for name, obj in scope.objects {
4282 if !name.starts_with('_defer_t') {
4283 continue
4284 }
4285 temp_types << '${name}:${obj.typ().name()}'
4286 if obj.typ().name() == 'IError' {
4287 found_ierror_temp = true
4288 }
4289 }
4290 assert found_ierror_temp, temp_types.str()
4291}
4292
4293fn test_defer_decl_tracking_prefers_rhs_type_for_err_decl() {
4294 mut scope := types.new_scope(unsafe { nil })
4295 scope.insert_or_update('err', types.Type(types.Struct{
4296 name: 'ast__NodeError'
4297 }))
4298 mut t := create_test_transformer()
4299 t.scope = scope
4300 t.set_synth_pos_counter(-1)
4301 t.synth_types = map[int]types.Type{}
4302 or_tmp := ast.Expr(ast.Ident{
4303 name: '_or_t1'
4304 })
4305 mut decls := map[string]types.Type{}
4306 t.add_decl_types_from_stmt(mut decls, ast.Stmt(ast.AssignStmt{
4307 op: .decl_assign
4308 lhs: [ast.Expr(ast.Ident{
4309 name: 'err'
4310 })]
4311 rhs: [
4312 t.synth_selector(or_tmp, 'err', types.Type(types.Struct{
4313 name: 'IError'
4314 })),
4315 ]
4316 }))
4317 err_type := decls['err'] or {
4318 assert false, 'err declaration type was not tracked'
4319 return
4320 }
4321 assert err_type.name() == 'IError'
4322}
4323
4324fn test_transform_string_inter_smartcast_temp_uses_variant_type() {
4325 env, _ := transform_code_with_env_for_test('
4326type Value = int | string
4327
4328struct Entry {
4329 key Value
4330}
4331
4332fn entry_string(entry Entry) string {
4333 key_text := if entry.key is string {
4334 "\${entry.key}"
4335 } else {
4336 "other"
4337 }
4338 return key_text
4339}
4340')
4341 fn_scope := env.get_fn_scope('main', 'entry_string') or {
4342 panic('missing transformed fn scope')
4343 }
4344 mut found_string_temp := false
4345 for name, obj in fn_scope.objects {
4346 if !name.starts_with('_or_t') {
4347 continue
4348 }
4349 if obj.typ().name() == 'string' {
4350 found_string_temp = true
4351 }
4352 assert obj.typ().name() != 'Value'
4353 }
4354 assert found_string_temp
4355}
4356
4357fn test_lower_defer_fn_captures_block_local_values() {
4358 mut t := create_transformer_with_vars({
4359 'old': types.Type(types.bool_)
4360 })
4361 old_decl := ast.Stmt(ast.AssignStmt{
4362 op: .decl_assign
4363 lhs: [ast.Expr(ast.Ident{
4364 name: 'old'
4365 })]
4366 rhs: [ast.Expr(ast.BasicLiteral{
4367 kind: .key_false
4368 value: 'false'
4369 })]
4370 })
4371 defer_stmt := ast.Stmt(ast.DeferStmt{
4372 mode: .function
4373 stmts: [
4374 ast.Stmt(ast.AssignStmt{
4375 op: .assign
4376 lhs: [
4377 ast.Expr(ast.SelectorExpr{
4378 lhs: ast.Ident{
4379 name: 'g'
4380 }
4381 rhs: ast.Ident{
4382 name: 'inside_smartcast'
4383 }
4384 }),
4385 ]
4386 rhs: [
4387 ast.Expr(ast.Ident{
4388 name: 'old'
4389 }),
4390 ]
4391 }),
4392 ]
4393 })
4394 lowered := t.lower_defer_stmts([
4395 ast.Stmt(ast.BlockStmt{
4396 stmts: [old_decl, defer_stmt]
4397 }),
4398 ], false, types.Type(types.void_))
4399
4400 assert lowered.len == 4
4401 assert lowered[1] is ast.AssignStmt
4402 capture_decl := lowered[1] as ast.AssignStmt
4403 assert capture_decl.lhs[0] is ast.Ident
4404 capture_name := (capture_decl.lhs[0] as ast.Ident).name
4405 assert capture_name.starts_with('_defer_cap')
4406
4407 assert lowered[2] is ast.BlockStmt
4408 block_stmt := lowered[2] as ast.BlockStmt
4409 assert block_stmt.stmts.len == 3
4410 assert block_stmt.stmts[1] is ast.AssignStmt
4411 capture_assign := block_stmt.stmts[1] as ast.AssignStmt
4412 assert capture_assign.lhs[0] is ast.Ident
4413 assert (capture_assign.lhs[0] as ast.Ident).name == capture_name
4414
4415 assert lowered[3] is ast.ExprStmt
4416 guard := (lowered[3] as ast.ExprStmt).expr as ast.IfExpr
4417 restore := guard.stmts[0] as ast.AssignStmt
4418<