v / vlib / v2 / transformer / monomorphize_test.v
4213 lines · 3918 sloc · 111.03 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: !windows
5module transformer
6
7import os
8import v2.ast
9import v2.parser
10import v2.pref as vpref
11import v2.token
12import v2.types
13
14fn mono_test_transformer() &Transformer {
15 env := &types.Environment{}
16 return &Transformer{
17 pref: &vpref.Preferences{}
18 env: unsafe { env }
19 needed_clone_fns: map[string]string{}
20 needed_array_contains_fns: map[string]ArrayMethodInfo{}
21 needed_array_index_fns: map[string]ArrayMethodInfo{}
22 needed_array_last_index_fns: map[string]ArrayMethodInfo{}
23 }
24}
25
26fn test_worker_clone_sees_sumtype_decl_variant_cache() {
27 checked := mono_check_sources_for_test([
28 MonoSource{
29 rel: 'foo/box.v'
30 code: '
31module foo
32
33pub struct Box[T] {
34pub:
35 value T
36}
37'
38 },
39 MonoSource{
40 rel: 'main.v'
41 code: '
42module main
43
44import foo as f
45
46type ImportedValue = f.Box[int] | f.Box[string]
47'
48 },
49 ])
50 mut transformer := checked.trans
51 transformer.pre_pass(checked.files)
52 mut worker := transformer.new_worker_clone(1)
53 expected := ['foo__Box_T_int', 'foo__Box_T_string']
54
55 assert transformer.get_sum_type_variants('ImportedValue') == expected
56 assert worker.get_sum_type_variants('ImportedValue') == expected
57 worker.sum_type_decl_variant_names['WorkerOnly'] = ['Nope']
58 assert 'WorkerOnly' !in transformer.sum_type_decl_variant_names
59
60 flat := ast.flatten_files(checked.files)
61 mut flat_transformer := Transformer.new_with_pref(checked.trans.env, checked.trans.pref)
62 flat_transformer.pre_pass_from_flat(&flat)
63 mut flat_worker := flat_transformer.new_worker_clone(2)
64
65 assert flat_transformer.get_sum_type_variants('ImportedValue') == expected
66 assert flat_worker.get_sum_type_variants('ImportedValue') == expected
67 flat_worker.sum_type_decl_variant_names['WorkerOnly'] = ['Nope']
68 assert 'WorkerOnly' !in flat_transformer.sum_type_decl_variant_names
69}
70
71fn test_get_expr_type_prefers_cloned_init_expr_type_over_stale_position_type() {
72 mut env := types.Environment.new()
73 pos := token.Pos{
74 id: 7
75 }
76 env.set_expr_type(pos.id, types.Type(types.Struct{
77 name: 'gitly__GitHubRepoInfo'
78 }))
79 mut t := mono_test_transformer()
80 t.env = env
81 t.cur_module = 'json2'
82 mut config_scope := types.new_scope(unsafe { nil })
83 config_scope.insert_type('Config', types.Type(types.Struct{
84 name: 'config__Config'
85 }))
86 t.cached_scopes['config'] = config_scope
87 expr := ast.Expr(ast.InitExpr{
88 typ: ast.Expr(ast.SelectorExpr{
89 lhs: ast.Expr(ast.Ident{
90 name: 'config'
91 })
92 rhs: ast.Ident{
93 name: 'Config'
94 }
95 })
96 pos: pos
97 })
98 typ := t.get_expr_type(expr) or { panic('expected cloned init expression type') }
99 assert typ.name() == 'config__Config'
100}
101
102// substitute_type: primitives
103
104fn test_substitute_type_returns_same_type_when_bindings_empty() {
105 original := types.Type(types.NamedType('T'))
106 out := substitute_type(original, map[string]types.Type{})
107 assert out.name() == 'T'
108}
109
110fn test_substitute_type_replaces_placeholder_named_type() {
111 bindings := {
112 'T': types.Type(types.int_)
113 }
114 out := substitute_type(types.Type(types.NamedType('T')), bindings)
115 assert out.name() == 'int'
116}
117
118fn test_generic_bindings_from_method_call_args_skip_receiver_param() {
119 mut t := mono_test_transformer()
120 t.local_decl_types['file'] = types.Type(types.Struct{
121 name: 'File'
122 })
123 info := CallFnInfo{
124 param_types: [
125 types.Type(types.Struct{
126 name: 'Walker'
127 }),
128 types.Type(types.NamedType('T')),
129 ]
130 generic_param_names_by_param: ['', 'T']
131 generic_param_indexes_by_param: [-1, 0]
132 generic_params: ['T']
133 }
134 bindings := t.generic_bindings_from_call_args(info, [
135 ast.Expr(ast.Ident{
136 name: 'file'
137 }),
138 ]) or { panic('expected binding') }
139 concrete := bindings['T'] or { panic('missing T binding') }
140 assert concrete.name() == 'File'
141}
142
143fn test_generic_bindings_from_call_args_use_ast_direct_generic_metadata() {
144 mut t := mono_test_transformer()
145 t.local_decl_types['file'] = types.Type(types.Struct{
146 name: 'File'
147 })
148 info := CallFnInfo{
149 param_types: [
150 types.Type(types.void_),
151 ]
152 generic_param_names_by_param: ['T']
153 generic_param_indexes_by_param: [0]
154 generic_params: ['T']
155 }
156 bindings := t.generic_bindings_from_call_args(info, [
157 ast.Expr(ast.Ident{
158 name: 'file'
159 }),
160 ]) or { panic('expected binding from direct generic parameter metadata') }
161 concrete := bindings['T'] or { panic('missing T binding') }
162 assert concrete.name() == 'File'
163}
164
165fn test_generic_call_info_lookup_does_not_match_unrelated_short_name() {
166 mut t := mono_test_transformer()
167 t.generic_fn_decl_index['write'] = ast.FnDecl{
168 name: 'write'
169 typ: ast.FnType{
170 generic_params: [
171 ast.Expr(ast.Ident{
172 name: 'T'
173 }),
174 ]
175 }
176 }
177 if _ := t.generic_fn_decl_for_call_info('v__Gen__write') {
178 assert false, 'qualified non-generic methods must not resolve to unrelated generic functions'
179 }
180 if decl := t.generic_fn_decl_for_call_info('write') {
181 assert decl.name == 'write'
182 } else {
183 assert false, 'expected exact generic function lookup to work'
184 }
185}
186
187fn test_resolve_monomorphize_decl_key_prefers_module_suffix_over_short_name() {
188 t := mono_test_transformer()
189 decl := ast.FnDecl{
190 name: 'uniq'
191 }
192 decl_node := {
193 'uniq': decl
194 'arrays__uniq': decl
195 }
196 resolved := t.resolve_monomorphize_decl_key('http__arrays__uniq', decl_node) or {
197 panic('missing resolved key')
198 }
199 assert resolved == 'arrays__uniq'
200}
201
202fn test_resolve_monomorphize_decl_key_rejects_unrelated_short_name() {
203 t := mono_test_transformer()
204 decl := ast.FnDecl{
205 name: 'encode'
206 }
207 decl_node := {
208 'encode': decl
209 'json2__encode': decl
210 }
211 if _ := t.resolve_monomorphize_decl_key('base64__encode', decl_node) {
212 assert false, 'qualified generic lookup must not fall back to unrelated short names'
213 }
214 if _ := t.resolve_monomorphize_decl_key('base64.encode', decl_node) {
215 assert false, 'dotted generic lookup must not fall back to unrelated short names'
216 }
217}
218
219fn test_generic_call_info_bare_lookup_stays_in_current_module() {
220 mut t := mono_test_transformer()
221 t.cur_module = 'cbor'
222 json_decl := ast.FnDecl{
223 name: 'encode'
224 typ: ast.FnType{
225 generic_params: [
226 ast.Expr(ast.Ident{
227 name: 'JsonT'
228 }),
229 ]
230 }
231 }
232 cbor_decl := ast.FnDecl{
233 name: 'encode'
234 typ: ast.FnType{
235 generic_params: [
236 ast.Expr(ast.Ident{
237 name: 'CborT'
238 }),
239 ]
240 }
241 }
242 t.generic_fn_decl_index['encode'] = json_decl
243 t.generic_fn_decl_index['json2__encode'] = json_decl
244 t.generic_fn_decl_index['cbor__encode'] = cbor_decl
245 decl := t.generic_fn_decl_for_call_info('encode') or { panic('missing current module decl') }
246 gp := decl.typ.generic_params[0] as ast.Ident
247 assert gp.name == 'CborT'
248}
249
250fn test_transformer_pointer_guards_accept_low_canonical_pointers() {
251 assert sumtype_payload_word_is_valid(1, 0x10000)
252 assert transformer_data_ptr_has_valid_address(0x10000)
253 assert !sumtype_payload_word_is_valid(1, 0)
254 assert !sumtype_payload_word_is_valid(1, 3)
255}
256
257fn mono_transform_dijkstra_shape_for_test() []ast.File {
258 source_path := os.join_path(os.temp_dir(), 'v2_mono_dijkstra_shape_${os.getpid()}.v')
259 os.write_file(source_path, '
260module main
261
262struct NODE {
263mut:
264 data int
265 priority int
266}
267
268fn push_pq[T](mut prior_queue []T, data int, priority int) {
269 _ = prior_queue
270 _ = data
271 _ = priority
272}
273
274fn updating_priority[T](mut prior_queue []T, search_data int, new_priority int) {
275 _ = prior_queue
276 _ = search_data
277 _ = new_priority
278}
279
280fn departure_priority[T](mut prior_queue []T) int {
281 _ = prior_queue
282 return 0
283}
284
285fn all_adjacents[T](g [][]T, v int) []int {
286 mut temp := []int{}
287 for i in 0 .. g.len {
288 if g[v][i] > 0 {
289 temp << i
290 }
291 }
292 return temp
293}
294
295fn print_solution[T](dist []T) {
296 _ = dist
297}
298
299fn print_paths_dist[T](path []T, dist []T) {
300 _ = path
301 _ = dist
302}
303
304fn dijkstra(g [][]int, s int) {
305 mut pq_queue := []NODE{}
306 push_pq(mut pq_queue, s, 0)
307 mut n := g.len
308 mut dist := []int{len: n, init: -1}
309 mut path := []int{len: n, init: -1}
310 dist[s] = 0
311 for pq_queue.len != 0 {
312 mut v := departure_priority(mut pq_queue)
313 mut adjs_of_v := all_adjacents(g, v)
314 mut new_dist := 0
315 for w in adjs_of_v {
316 new_dist = dist[v] + g[v][w]
317 if dist[w] == -1 {
318 dist[w] = new_dist
319 push_pq(mut pq_queue, w, dist[w])
320 path[w] = v
321 }
322 if dist[w] > new_dist {
323 dist[w] = new_dist
324 updating_priority(mut pq_queue, w, dist[w])
325 path[w] = v
326 }
327 }
328 }
329 print_solution(dist)
330 print_paths_dist(path, dist)
331}
332') or {
333 panic('failed to write temp source')
334 }
335 defer {
336 os.rm(source_path) or {}
337 }
338 prefs := &vpref.Preferences{
339 backend: .x64
340 no_parallel: true
341 }
342 mut file_set := token.FileSet.new()
343 mut par := parser.Parser.new(prefs)
344 files := par.parse_files([source_path], mut file_set)
345 mut env := types.Environment.new()
346 mut checker := types.Checker.new(prefs, file_set, env)
347 checker.check_files(files)
348 mut trans := Transformer.new_with_pref(env, prefs)
349 return trans.transform_files(files)
350}
351
352fn mono_collect_call_names_from_expr(expr ast.Expr, mut names []string) {
353 match expr {
354 ast.ArrayInitExpr {
355 for nested in expr.exprs {
356 mono_collect_call_names_from_expr(nested, mut names)
357 }
358 }
359 ast.CallExpr {
360 if expr.lhs is ast.Ident {
361 names << expr.lhs.name
362 }
363 mono_collect_call_names_from_expr(expr.lhs, mut names)
364 for arg in expr.args {
365 mono_collect_call_names_from_expr(arg, mut names)
366 }
367 }
368 ast.IfExpr {
369 mono_collect_call_names_from_expr(expr.cond, mut names)
370 for stmt in expr.stmts {
371 mono_collect_call_names_from_stmt(stmt, mut names)
372 }
373 mono_collect_call_names_from_expr(expr.else_expr, mut names)
374 }
375 ast.InfixExpr {
376 mono_collect_call_names_from_expr(expr.lhs, mut names)
377 mono_collect_call_names_from_expr(expr.rhs, mut names)
378 }
379 ast.MatchExpr {
380 mono_collect_call_names_from_expr(expr.expr, mut names)
381 for branch in expr.branches {
382 for cond in branch.cond {
383 mono_collect_call_names_from_expr(cond, mut names)
384 }
385 for stmt in branch.stmts {
386 mono_collect_call_names_from_stmt(stmt, mut names)
387 }
388 }
389 }
390 ast.ModifierExpr {
391 mono_collect_call_names_from_expr(expr.expr, mut names)
392 }
393 ast.OrExpr {
394 mono_collect_call_names_from_expr(expr.expr, mut names)
395 for stmt in expr.stmts {
396 mono_collect_call_names_from_stmt(stmt, mut names)
397 }
398 }
399 ast.ParenExpr {
400 mono_collect_call_names_from_expr(expr.expr, mut names)
401 }
402 ast.PrefixExpr {
403 mono_collect_call_names_from_expr(expr.expr, mut names)
404 }
405 ast.SelectorExpr {
406 mono_collect_call_names_from_expr(expr.lhs, mut names)
407 }
408 ast.StringInterLiteral {
409 for inter in expr.inters {
410 mono_collect_call_names_from_expr(inter.expr, mut names)
411 mono_collect_call_names_from_expr(inter.format_expr, mut names)
412 }
413 }
414 else {}
415 }
416}
417
418fn mono_collect_string_literals_from_expr(expr ast.Expr, mut values []string) {
419 match expr {
420 ast.ArrayInitExpr {
421 for nested in expr.exprs {
422 mono_collect_string_literals_from_expr(nested, mut values)
423 }
424 }
425 ast.CallExpr {
426 mono_collect_string_literals_from_expr(expr.lhs, mut values)
427 for arg in expr.args {
428 mono_collect_string_literals_from_expr(arg, mut values)
429 }
430 }
431 ast.IfExpr {
432 mono_collect_string_literals_from_expr(expr.cond, mut values)
433 for stmt in expr.stmts {
434 mono_collect_string_literals_from_stmt(stmt, mut values)
435 }
436 mono_collect_string_literals_from_expr(expr.else_expr, mut values)
437 }
438 ast.InfixExpr {
439 mono_collect_string_literals_from_expr(expr.lhs, mut values)
440 mono_collect_string_literals_from_expr(expr.rhs, mut values)
441 }
442 ast.ModifierExpr {
443 mono_collect_string_literals_from_expr(expr.expr, mut values)
444 }
445 ast.OrExpr {
446 mono_collect_string_literals_from_expr(expr.expr, mut values)
447 for stmt in expr.stmts {
448 mono_collect_string_literals_from_stmt(stmt, mut values)
449 }
450 }
451 ast.ParenExpr {
452 mono_collect_string_literals_from_expr(expr.expr, mut values)
453 }
454 ast.PrefixExpr {
455 mono_collect_string_literals_from_expr(expr.expr, mut values)
456 }
457 ast.SelectorExpr {
458 mono_collect_string_literals_from_expr(expr.lhs, mut values)
459 }
460 ast.StringInterLiteral {
461 values << expr.values
462 for inter in expr.inters {
463 mono_collect_string_literals_from_expr(inter.expr, mut values)
464 mono_collect_string_literals_from_expr(inter.format_expr, mut values)
465 }
466 }
467 ast.StringLiteral {
468 values << expr.value
469 }
470 else {}
471 }
472}
473
474fn mono_collect_string_literals_from_stmt(stmt ast.Stmt, mut values []string) {
475 match stmt {
476 ast.AssignStmt {
477 for expr in stmt.lhs {
478 mono_collect_string_literals_from_expr(expr, mut values)
479 }
480 for expr in stmt.rhs {
481 mono_collect_string_literals_from_expr(expr, mut values)
482 }
483 }
484 ast.ExprStmt {
485 mono_collect_string_literals_from_expr(stmt.expr, mut values)
486 }
487 ast.ForStmt {
488 mono_collect_string_literals_from_expr(stmt.cond, mut values)
489 for nested in stmt.stmts {
490 mono_collect_string_literals_from_stmt(nested, mut values)
491 }
492 }
493 ast.ReturnStmt {
494 for expr in stmt.exprs {
495 mono_collect_string_literals_from_expr(expr, mut values)
496 }
497 }
498 else {}
499 }
500}
501
502fn mono_collect_call_names_from_stmt(stmt ast.Stmt, mut names []string) {
503 match stmt {
504 ast.AssignStmt {
505 for expr in stmt.rhs {
506 mono_collect_call_names_from_expr(expr, mut names)
507 }
508 }
509 ast.ExprStmt {
510 mono_collect_call_names_from_expr(stmt.expr, mut names)
511 }
512 ast.ForStmt {
513 mono_collect_call_names_from_expr(stmt.cond, mut names)
514 for nested in stmt.stmts {
515 mono_collect_call_names_from_stmt(nested, mut names)
516 }
517 }
518 ast.ReturnStmt {
519 for expr in stmt.exprs {
520 mono_collect_call_names_from_expr(expr, mut names)
521 }
522 }
523 else {}
524 }
525}
526
527fn mono_call_names_for_fn(files []ast.File, fn_name string) []string {
528 mut names := []string{}
529 for file in files {
530 for stmt in file.stmts {
531 if stmt is ast.FnDecl && stmt.name == fn_name {
532 for nested in stmt.stmts {
533 mono_collect_call_names_from_stmt(nested, mut names)
534 }
535 }
536 }
537 }
538 return names
539}
540
541fn mono_assign_rhs_for_var(files []ast.File, fn_name string, var_name string) ?ast.Expr {
542 for file in files {
543 for stmt in file.stmts {
544 if stmt is ast.FnDecl && stmt.name == fn_name {
545 for nested in stmt.stmts {
546 if nested is ast.AssignStmt {
547 for i, lhs in nested.lhs {
548 if lhs is ast.Ident && lhs.name == var_name && i < nested.rhs.len {
549 return nested.rhs[i]
550 }
551 }
552 }
553 }
554 }
555 }
556 }
557 return none
558}
559
560fn mono_init_field(init ast.InitExpr, field_name string) ?ast.Expr {
561 for field in init.fields {
562 if field.name == field_name {
563 return field.value
564 }
565 }
566 return none
567}
568
569fn mono_assert_init_ident_name(init ast.InitExpr, expected string) {
570 assert init.typ is ast.Ident, 'expected InitExpr typ ${expected}, got ${init.typ}'
571 assert (init.typ as ast.Ident).name == expected
572}
573
574fn mono_assert_basic_number_expr(expr ast.Expr, expected string) {
575 assert expr is ast.BasicLiteral, 'expected number literal ${expected}, got ${expr}'
576 lit := expr as ast.BasicLiteral
577 assert lit.kind == token.Token.number
578 assert lit.value == expected
579}
580
581fn mono_assert_sumtype_payload_memdup_expr(expr ast.Expr, expected_variant string) {
582 assert expr is ast.CastExpr, 'expected voidptr payload cast for ${expected_variant}, got ${expr}'
583 voidptr_cast := expr as ast.CastExpr
584 assert voidptr_cast.typ is ast.Ident
585 assert (voidptr_cast.typ as ast.Ident).name == 'voidptr'
586 assert voidptr_cast.expr is ast.CallExpr, 'expected memdup payload call, got ${voidptr_cast.expr}'
587 memdup_call := voidptr_cast.expr as ast.CallExpr
588 assert memdup_call.lhs is ast.Ident
589 assert (memdup_call.lhs as ast.Ident).name == 'memdup'
590 assert memdup_call.args.len == 2, 'expected memdup payload and sizeof args, got ${memdup_call.args}'
591 payload_ref := memdup_call.args[0]
592 assert payload_ref is ast.PrefixExpr, 'expected memdup payload address, got ${payload_ref}'
593 payload_prefix := payload_ref as ast.PrefixExpr
594 assert payload_prefix.op == token.Token.amp
595 assert payload_prefix.expr is ast.InitExpr, 'expected concrete variant init payload, got ${payload_prefix.expr}'
596 payload_init := payload_prefix.expr as ast.InitExpr
597 mono_assert_init_ident_name(payload_init, expected_variant)
598 size_arg := memdup_call.args[1]
599 assert size_arg is ast.KeywordOperator, 'expected sizeof payload arg, got ${size_arg}'
600 size_op := size_arg as ast.KeywordOperator
601 assert size_op.op == token.Token.key_sizeof
602 assert size_op.exprs.len == 1
603 assert size_op.exprs[0] is ast.Ident
604 assert (size_op.exprs[0] as ast.Ident).name == expected_variant
605}
606
607fn mono_collect_tag_compare_rhs_from_expr(expr ast.Expr, mut values []ast.Expr) {
608 match expr {
609 ast.IfExpr {
610 mono_collect_tag_compare_rhs_from_expr(expr.cond, mut values)
611 for stmt in expr.stmts {
612 mono_collect_tag_compare_rhs_from_stmt(stmt, mut values)
613 }
614 mono_collect_tag_compare_rhs_from_expr(expr.else_expr, mut values)
615 }
616 ast.InfixExpr {
617 if expr.op == token.Token.eq && expr.lhs is ast.SelectorExpr {
618 lhs := expr.lhs as ast.SelectorExpr
619 if lhs.rhs.name == '_tag' {
620 values << expr.rhs
621 }
622 }
623 mono_collect_tag_compare_rhs_from_expr(expr.lhs, mut values)
624 mono_collect_tag_compare_rhs_from_expr(expr.rhs, mut values)
625 }
626 ast.ParenExpr {
627 mono_collect_tag_compare_rhs_from_expr(expr.expr, mut values)
628 }
629 else {}
630 }
631}
632
633fn mono_collect_tag_compare_rhs_from_stmt(stmt ast.Stmt, mut values []ast.Expr) {
634 match stmt {
635 ast.AssignStmt {
636 for expr in stmt.rhs {
637 mono_collect_tag_compare_rhs_from_expr(expr, mut values)
638 }
639 }
640 ast.ExprStmt {
641 mono_collect_tag_compare_rhs_from_expr(stmt.expr, mut values)
642 }
643 ast.ReturnStmt {
644 for expr in stmt.exprs {
645 mono_collect_tag_compare_rhs_from_expr(expr, mut values)
646 }
647 }
648 else {}
649 }
650}
651
652fn mono_collect_return_exprs_from_stmt(stmt ast.Stmt, mut exprs []ast.Expr) {
653 match stmt {
654 ast.BlockStmt {
655 for nested in stmt.stmts {
656 mono_collect_return_exprs_from_stmt(nested, mut exprs)
657 }
658 }
659 ast.ExprStmt {
660 mono_collect_return_exprs_from_expr(stmt.expr, mut exprs)
661 }
662 ast.ForStmt {
663 for nested in stmt.stmts {
664 mono_collect_return_exprs_from_stmt(nested, mut exprs)
665 }
666 mono_collect_return_exprs_from_stmt(stmt.init, mut exprs)
667 mono_collect_return_exprs_from_stmt(stmt.post, mut exprs)
668 }
669 ast.ReturnStmt {
670 exprs << stmt.exprs
671 }
672 else {}
673 }
674}
675
676fn mono_collect_return_exprs_from_expr(expr ast.Expr, mut exprs []ast.Expr) {
677 match expr {
678 ast.IfExpr {
679 for stmt in expr.stmts {
680 mono_collect_return_exprs_from_stmt(stmt, mut exprs)
681 }
682 mono_collect_return_exprs_from_expr(expr.else_expr, mut exprs)
683 }
684 else {}
685 }
686}
687
688fn mono_return_exprs_for_decl(decl ast.FnDecl) []ast.Expr {
689 mut exprs := []ast.Expr{}
690 for stmt in decl.stmts {
691 mono_collect_return_exprs_from_stmt(stmt, mut exprs)
692 }
693 return exprs
694}
695
696fn mono_tag_compare_rhs_for_fn(files []ast.File, fn_name string) []ast.Expr {
697 mut values := []ast.Expr{}
698 for file in files {
699 for stmt in file.stmts {
700 if stmt is ast.FnDecl && stmt.name == fn_name {
701 for nested in stmt.stmts {
702 mono_collect_tag_compare_rhs_from_stmt(nested, mut values)
703 }
704 }
705 }
706 }
707 return values
708}
709
710fn mono_tag_compare_rhs_for_decl(decl ast.FnDecl) []ast.Expr {
711 mut values := []ast.Expr{}
712 for stmt in decl.stmts {
713 mono_collect_tag_compare_rhs_from_stmt(stmt, mut values)
714 }
715 return values
716}
717
718fn mono_has_fn(files []ast.File, fn_name string) bool {
719 for file in files {
720 for stmt in file.stmts {
721 if stmt is ast.FnDecl && stmt.name == fn_name {
722 return true
723 }
724 }
725 }
726 return false
727}
728
729fn mono_fn_names(files []ast.File) []string {
730 mut names := []string{}
731 for file in files {
732 for stmt in file.stmts {
733 if stmt is ast.FnDecl {
734 names << stmt.name
735 }
736 }
737 }
738 return names
739}
740
741fn mono_method_decl_by_receiver(files []ast.File, receiver_name string, method_name string) ?ast.FnDecl {
742 for file in files {
743 for stmt in file.stmts {
744 if stmt is ast.FnDecl && stmt.is_method && stmt.name == method_name {
745 receiver := stmt.receiver.typ
746 if receiver is ast.Ident && receiver.name == receiver_name {
747 return stmt
748 }
749 }
750 }
751 }
752 return none
753}
754
755fn mono_fn_decl_by_name(files []ast.File, fn_name string) ?ast.FnDecl {
756 for file in files {
757 for stmt in file.stmts {
758 if stmt is ast.FnDecl && stmt.name == fn_name {
759 return stmt
760 }
761 }
762 }
763 return none
764}
765
766fn mono_assert_method_clone_by_receiver(files []ast.File, receiver_name string, method_name string) ast.FnDecl {
767 fn_names := mono_fn_names(files)
768 decl := mono_method_decl_by_receiver(files, receiver_name, method_name) or {
769 assert false, 'missing concrete method clone ${receiver_name}.${method_name}, got ${fn_names}'
770 return ast.FnDecl{}
771 }
772 assert decl.is_method, '${receiver_name}.${method_name} must stay a method clone'
773 assert decl.name == method_name
774 assert decl.stmts.len > 0, '${receiver_name}.${method_name} must keep its cloned method body'
775 return decl
776}
777
778fn mono_call_names_for_decl(decl ast.FnDecl) []string {
779 mut names := []string{}
780 for stmt in decl.stmts {
781 mono_collect_call_names_from_stmt(stmt, mut names)
782 }
783 return names
784}
785
786fn mono_call_lhs_shape(expr ast.Expr) string {
787 match expr {
788 ast.GenericArgs {
789 return 'GenericArgs(${mono_call_lhs_shape(expr.lhs)})'
790 }
791 ast.GenericArgOrIndexExpr {
792 return 'GenericArgOrIndexExpr(${mono_call_lhs_shape(expr.lhs)})'
793 }
794 ast.Ident {
795 return 'Ident(${expr.name})'
796 }
797 ast.IndexExpr {
798 return 'IndexExpr(${mono_call_lhs_shape(expr.lhs)})'
799 }
800 ast.SelectorExpr {
801 return 'Selector(${mono_call_lhs_shape(expr.lhs)}.${expr.rhs.name})'
802 }
803 else {
804 return typeof(expr).name
805 }
806 }
807}
808
809fn mono_collect_call_lhs_shapes_from_expr(expr ast.Expr, mut shapes []string) {
810 match expr {
811 ast.CallExpr {
812 shapes << mono_call_lhs_shape(expr.lhs)
813 mono_collect_call_lhs_shapes_from_expr(expr.lhs, mut shapes)
814 for arg in expr.args {
815 mono_collect_call_lhs_shapes_from_expr(arg, mut shapes)
816 }
817 }
818 ast.IfExpr {
819 mono_collect_call_lhs_shapes_from_expr(expr.cond, mut shapes)
820 for stmt in expr.stmts {
821 mono_collect_call_lhs_shapes_from_stmt(stmt, mut shapes)
822 }
823 mono_collect_call_lhs_shapes_from_expr(expr.else_expr, mut shapes)
824 }
825 ast.InfixExpr {
826 mono_collect_call_lhs_shapes_from_expr(expr.lhs, mut shapes)
827 mono_collect_call_lhs_shapes_from_expr(expr.rhs, mut shapes)
828 }
829 ast.MatchExpr {
830 mono_collect_call_lhs_shapes_from_expr(expr.expr, mut shapes)
831 for branch in expr.branches {
832 for cond in branch.cond {
833 mono_collect_call_lhs_shapes_from_expr(cond, mut shapes)
834 }
835 for stmt in branch.stmts {
836 mono_collect_call_lhs_shapes_from_stmt(stmt, mut shapes)
837 }
838 }
839 }
840 ast.ParenExpr {
841 mono_collect_call_lhs_shapes_from_expr(expr.expr, mut shapes)
842 }
843 ast.PrefixExpr {
844 mono_collect_call_lhs_shapes_from_expr(expr.expr, mut shapes)
845 }
846 ast.SelectorExpr {
847 mono_collect_call_lhs_shapes_from_expr(expr.lhs, mut shapes)
848 }
849 else {}
850 }
851}
852
853fn mono_collect_call_lhs_shapes_from_stmt(stmt ast.Stmt, mut shapes []string) {
854 match stmt {
855 ast.AssignStmt {
856 for expr in stmt.rhs {
857 mono_collect_call_lhs_shapes_from_expr(expr, mut shapes)
858 }
859 }
860 ast.ExprStmt {
861 mono_collect_call_lhs_shapes_from_expr(stmt.expr, mut shapes)
862 }
863 ast.ReturnStmt {
864 for expr in stmt.exprs {
865 mono_collect_call_lhs_shapes_from_expr(expr, mut shapes)
866 }
867 }
868 else {}
869 }
870}
871
872fn mono_call_lhs_shapes_for_decl(decl ast.FnDecl) []string {
873 mut shapes := []string{}
874 for stmt in decl.stmts {
875 mono_collect_call_lhs_shapes_from_stmt(stmt, mut shapes)
876 }
877 return shapes
878}
879
880fn mono_raw_call_name(expr ast.Expr) string {
881 match expr {
882 ast.Ident {
883 return expr.name
884 }
885 ast.SelectorExpr {
886 return expr.rhs.name
887 }
888 else {
889 return mono_call_lhs_shape(expr)
890 }
891 }
892}
893
894fn mono_collect_raw_call_names_from_expr(expr ast.Expr, mut names []string) {
895 match expr {
896 ast.CallExpr {
897 names << mono_raw_call_name(expr.lhs)
898 mono_collect_raw_call_names_from_expr(expr.lhs, mut names)
899 for arg in expr.args {
900 mono_collect_raw_call_names_from_expr(arg, mut names)
901 }
902 }
903 ast.IfExpr {
904 mono_collect_raw_call_names_from_expr(expr.cond, mut names)
905 for stmt in expr.stmts {
906 mono_collect_raw_call_names_from_stmt(stmt, mut names)
907 }
908 mono_collect_raw_call_names_from_expr(expr.else_expr, mut names)
909 }
910 ast.InfixExpr {
911 mono_collect_raw_call_names_from_expr(expr.lhs, mut names)
912 mono_collect_raw_call_names_from_expr(expr.rhs, mut names)
913 }
914 ast.MatchExpr {
915 mono_collect_raw_call_names_from_expr(expr.expr, mut names)
916 for branch in expr.branches {
917 for cond in branch.cond {
918 mono_collect_raw_call_names_from_expr(cond, mut names)
919 }
920 for stmt in branch.stmts {
921 mono_collect_raw_call_names_from_stmt(stmt, mut names)
922 }
923 }
924 }
925 ast.ParenExpr {
926 mono_collect_raw_call_names_from_expr(expr.expr, mut names)
927 }
928 ast.PrefixExpr {
929 mono_collect_raw_call_names_from_expr(expr.expr, mut names)
930 }
931 ast.SelectorExpr {
932 mono_collect_raw_call_names_from_expr(expr.lhs, mut names)
933 }
934 else {}
935 }
936}
937
938fn mono_collect_raw_call_names_from_stmt(stmt ast.Stmt, mut names []string) {
939 match stmt {
940 ast.AssignStmt {
941 for expr in stmt.rhs {
942 mono_collect_raw_call_names_from_expr(expr, mut names)
943 }
944 }
945 ast.ExprStmt {
946 mono_collect_raw_call_names_from_expr(stmt.expr, mut names)
947 }
948 ast.ReturnStmt {
949 for expr in stmt.exprs {
950 mono_collect_raw_call_names_from_expr(expr, mut names)
951 }
952 }
953 else {}
954 }
955}
956
957fn mono_raw_call_names_for_decl(decl ast.FnDecl) []string {
958 mut names := []string{}
959 for stmt in decl.stmts {
960 mono_collect_raw_call_names_from_stmt(stmt, mut names)
961 }
962 return names
963}
964
965fn mono_string_literals_for_decl(decl ast.FnDecl) []string {
966 mut values := []string{}
967 for stmt in decl.stmts {
968 mono_collect_string_literals_from_stmt(stmt, mut values)
969 }
970 return values
971}
972
973fn mono_specs_summary(t &Transformer) string {
974 mut keys := t.env.generic_types.keys()
975 keys.sort()
976 mut parts := []string{}
977 for key in keys {
978 bindings_list := t.env.generic_types[key] or { continue }
979 mut sigs := []string{cap: bindings_list.len}
980 for bindings in bindings_list {
981 sigs << generic_bindings_signature(bindings)
982 }
983 sigs.sort()
984 parts << '${key}=[${sigs.join(',')}]'
985 }
986 return parts.join(';')
987}
988
989fn mono_has_spec(t &Transformer, key string, sig string) bool {
990 bindings_list := t.env.generic_types[key] or { return false }
991 for bindings in bindings_list {
992 if generic_bindings_signature(bindings) == sig {
993 return true
994 }
995 }
996 return false
997}
998
999fn mono_decl_label(decl ast.FnDecl) string {
1000 if decl.is_method && decl.receiver.typ is ast.Ident {
1001 return '${(decl.receiver.typ as ast.Ident).name}__${decl.name}'
1002 }
1003 return decl.name
1004}
1005
1006fn mono_last_clone_names(t &Transformer) []string {
1007 mut names := []string{}
1008 for _, stmts in t.last_mono_clones {
1009 for stmt in stmts {
1010 if stmt is ast.FnDecl {
1011 names << mono_decl_label(stmt)
1012 }
1013 }
1014 }
1015 names.sort()
1016 return names
1017}
1018
1019fn mono_last_clone_decl(t &Transformer, name string) ?ast.FnDecl {
1020 for _, stmts in t.last_mono_clones {
1021 for stmt in stmts {
1022 if stmt is ast.FnDecl && (stmt.name == name || mono_decl_label(stmt) == name) {
1023 return stmt
1024 }
1025 }
1026 }
1027 return none
1028}
1029
1030fn mono_binding_lookup_summary(t &Transformer, keys []string) string {
1031 mut parts := []string{cap: keys.len}
1032 for key in keys {
1033 if bindings := t.lookup_monomorphized_fn_bindings('main', key) {
1034 parts << '${key}=${generic_bindings_signature(bindings)}'
1035 } else {
1036 parts << '${key}=none'
1037 }
1038 }
1039 return parts.join(';')
1040}
1041
1042fn mono_assert_no_qualified_queue_array_string_fn_decl(fn_names []string, method_name string) {
1043 qualified := 'datatypes__Queue_T_Array_string__${method_name}'
1044 assert qualified !in fn_names, 'qualified method FnDecl should not be emitted for canonical clone: ${fn_names}'
1045}
1046
1047fn mono_assert_no_open_queue_method_names(fn_names []string, call_names []string, method_name string) {
1048 short_open := 'Queue__${method_name}'
1049 qualified_open := 'datatypes__Queue__${method_name}'
1050 assert short_open !in fn_names, 'open Queue method FnDecl leaked into ${fn_names}'
1051 assert qualified_open !in fn_names, 'open datatypes Queue method FnDecl leaked into ${fn_names}'
1052 assert short_open !in call_names, 'open Queue method call leaked into ${call_names}'
1053 assert qualified_open !in call_names, 'open datatypes Queue method call leaked into ${call_names}'
1054}
1055
1056fn mono_assert_no_open_queue_is_empty_names(fn_names []string, call_names []string) {
1057 mono_assert_no_open_queue_method_names(fn_names, call_names, 'is_empty')
1058}
1059
1060fn mono_assert_specialized_inner_calls(call_names []string, expected string, open_names []string) {
1061 assert expected in call_names, 'expected specialized internal call ${expected}, got ${call_names}'
1062 for open_name in open_names {
1063 assert open_name !in call_names, 'open internal call ${open_name} leaked into ${call_names}'
1064 }
1065}
1066
1067fn mono_assert_no_open_str_calls(call_names []string, open_names []string) {
1068 for open_name in open_names {
1069 assert open_name !in call_names, 'open str call ${open_name} leaked into ${call_names}'
1070 }
1071}
1072
1073fn mono_assert_no_unresolved_generic_receiver_names(names []string, label string) {
1074 for name in names {
1075 assert !name.contains('unknown__'), '${label} leaked unresolved call ${name} in ${names}'
1076 assert !name.contains('_T_datatypes_T'), '${label} kept unresolved receiver generic ${name} in ${names}'
1077 }
1078}
1079
1080struct MonoSource {
1081 rel string
1082 code string
1083}
1084
1085struct MonoCheckedSources {
1086 files []ast.File
1087 trans &Transformer
1088}
1089
1090fn mono_nested_min_source() string {
1091 return '
1092module main
1093
1094struct Empty {}
1095
1096struct Node[T] {
1097 value T
1098 left Tree[T]
1099 right Tree[T]
1100}
1101
1102type Tree[T] = Empty | Node[T]
1103
1104fn (tree Tree[T]) min[T]() T {
1105 return match tree {
1106 Empty { T(1e9) }
1107 Node[T] { tree.value }
1108 }
1109}
1110
1111fn (tree Tree[T]) take_min[T]() T {
1112 return match tree {
1113 Empty { T(0) }
1114 Node[T] { tree.right.min() }
1115 }
1116}
1117
1118fn use() f64 {
1119 empty := Tree[f64](Empty{})
1120 tree := Tree[f64](Node[f64]{1.0, empty, empty})
1121 return tree.take_min()
1122}
1123'
1124}
1125
1126fn mono_insert_size_source() string {
1127 return '
1128module main
1129
1130struct Empty {}
1131
1132struct Node[T] {
1133 value T
1134 left Tree[T]
1135 right Tree[T]
1136}
1137
1138type Tree[T] = Empty | Node[T]
1139
1140fn (tree Tree[T]) size[T]() int {
1141 return match tree {
1142 Empty { 0 }
1143 Node[T] { 1 + tree.left.size() + tree.right.size() }
1144 }
1145}
1146
1147fn (tree Tree[T]) insert[T](x T) Tree[T] {
1148 return match tree {
1149 Empty { Node[T]{x, tree, tree} }
1150 Node[T] {
1151 if x == tree.value {
1152 tree
1153 } else if x < tree.value {
1154 Node[T]{
1155 ...tree
1156 left: tree.left.insert(x)
1157 }
1158 } else {
1159 Node[T]{
1160 ...tree
1161 right: tree.right.insert(x)
1162 }
1163 }
1164 }
1165 }
1166}
1167
1168fn use() int {
1169 mut tree := Tree[f64](Empty{})
1170 tree = tree.insert(0.2)
1171 tree = tree.insert(0.5)
1172 return tree.size()
1173}
1174'
1175}
1176
1177fn mono_transform_sources_for_test(sources []MonoSource) []ast.File {
1178 checked := mono_check_sources_for_test(sources)
1179 mut trans := checked.trans
1180 return trans.transform_files(checked.files)
1181}
1182
1183fn mono_check_sources_for_test(sources []MonoSource) MonoCheckedSources {
1184 tmp_dir := os.join_path(os.temp_dir(), 'v2_mono_sources_${os.getpid()}')
1185 os.rmdir_all(tmp_dir) or {}
1186 os.mkdir_all(tmp_dir) or { panic(err) }
1187 defer {
1188 os.rmdir_all(tmp_dir) or {}
1189 }
1190 mut paths := []string{cap: sources.len}
1191 for source in sources {
1192 path := os.join_path(tmp_dir, source.rel)
1193 os.mkdir_all(os.dir(path)) or { panic(err) }
1194 os.write_file(path, source.code) or { panic('failed to write ${path}') }
1195 paths << path
1196 }
1197 prefs := &vpref.Preferences{
1198 backend: .x64
1199 no_parallel: true
1200 }
1201 mut file_set := token.FileSet.new()
1202 mut par := parser.Parser.new(prefs)
1203 files := par.parse_files(paths, mut file_set)
1204 mut env := types.Environment.new()
1205 mut checker := types.Checker.new(prefs, file_set, env)
1206 checker.check_files(files)
1207 mut trans := Transformer.new_with_pref(env, prefs)
1208 return MonoCheckedSources{
1209 files: files
1210 trans: trans
1211 }
1212}
1213
1214fn mono_transform_sources_flat_direct_for_test(sources []MonoSource) []ast.File {
1215 tmp_dir := os.join_path(os.temp_dir(), 'v2_mono_sources_flat_${os.getpid()}')
1216 os.rmdir_all(tmp_dir) or {}
1217 os.mkdir_all(tmp_dir) or { panic(err) }
1218 defer {
1219 os.rmdir_all(tmp_dir) or {}
1220 }
1221 mut paths := []string{cap: sources.len}
1222 for source in sources {
1223 path := os.join_path(tmp_dir, source.rel)
1224 os.mkdir_all(os.dir(path)) or { panic(err) }
1225 os.write_file(path, source.code) or { panic('failed to write ${path}') }
1226 paths << path
1227 }
1228 prefs := &vpref.Preferences{
1229 backend: .x64
1230 no_parallel: true
1231 }
1232 mut file_set := token.FileSet.new()
1233 mut par := parser.Parser.new(prefs)
1234 files := par.parse_files(paths, mut file_set)
1235 mut env := types.Environment.new()
1236 mut checker := types.Checker.new(prefs, file_set, env)
1237 checker.check_files(files)
1238 flat := ast.flatten_files(files)
1239 mut trans := Transformer.new_with_pref(env, prefs)
1240 return trans.transform_flat_to_flat_direct(&flat, []).to_files()
1241}
1242
1243fn test_imported_generic_struct_init_receiver_method_call_is_monomorphized() {
1244 files := mono_transform_sources_for_test([
1245 MonoSource{
1246 rel: 'datatypes/queue.v'
1247 code: '
1248module datatypes
1249
1250pub struct Queue[T] {
1251mut:
1252 elements []T
1253}
1254
1255pub fn (mut queue Queue[T]) push(value T) {
1256 queue.elements << value
1257}
1258
1259pub fn (queue Queue[T]) is_empty() bool {
1260 return queue.elements.len == 0
1261}
1262
1263pub fn (mut queue Queue[T]) pop() !T {
1264 return queue.elements[0]
1265}
1266'
1267 },
1268 MonoSource{
1269 rel: 'main.v'
1270 code: '
1271module main
1272
1273import datatypes
1274
1275fn use(value []string) bool {
1276 mut queue := datatypes.Queue[[]string]{}
1277 queue.push(value)
1278 if queue.is_empty() {
1279 return false
1280 }
1281 popped := queue.pop() or { return false }
1282 return popped.len == 1
1283}
1284'
1285 },
1286 ])
1287 fn_names := mono_fn_names(files)
1288 call_names := mono_call_names_for_fn(files, 'use')
1289 mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'push')
1290 mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'is_empty')
1291 mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'pop')
1292 mono_assert_no_qualified_queue_array_string_fn_decl(fn_names, 'push')
1293 mono_assert_no_qualified_queue_array_string_fn_decl(fn_names, 'is_empty')
1294 mono_assert_no_qualified_queue_array_string_fn_decl(fn_names, 'pop')
1295 assert call_names == [
1296 'datatypes__Queue_T_Array_string__push',
1297 'datatypes__Queue_T_Array_string__is_empty',
1298 'datatypes__Queue_T_Array_string__pop',
1299 ], 'expected exact imported Queue[[]string] method calls, got ${call_names}'
1300 mono_assert_no_open_queue_method_names(fn_names, call_names, 'push')
1301 mono_assert_no_open_queue_is_empty_names(fn_names, call_names)
1302 mono_assert_no_open_queue_method_names(fn_names, call_names, 'pop')
1303}
1304
1305fn test_imported_generic_method_clone_body_rewrites_nested_receiver_calls() {
1306 files := mono_transform_sources_for_test([
1307 MonoSource{
1308 rel: 'datatypes/inner.v'
1309 code: '
1310module datatypes
1311
1312pub struct Inner[T] {
1313mut:
1314 values []T
1315}
1316
1317pub fn (inner Inner[T]) is_empty() bool {
1318 return inner.values.len == 0
1319}
1320
1321pub fn (mut inner Inner[T]) shift() !T {
1322 return inner.values[0]
1323}
1324'
1325 },
1326 MonoSource{
1327 rel: 'datatypes/outer.v'
1328 code: '
1329module datatypes
1330
1331pub struct Outer[T] {
1332mut:
1333 inner Inner[T]
1334}
1335
1336pub fn (outer Outer[T]) is_empty() bool {
1337 return outer.inner.is_empty()
1338}
1339
1340pub fn (mut outer Outer[T]) pop() !T {
1341 return outer.inner.shift()
1342}
1343'
1344 },
1345 MonoSource{
1346 rel: 'main.v'
1347 code: '
1348module main
1349
1350import datatypes
1351
1352fn use() bool {
1353 mut outer := datatypes.Outer[[]string]{}
1354 if outer.is_empty() {
1355 return false
1356 }
1357 popped := outer.pop() or { return false }
1358 return popped.len == 1
1359}
1360'
1361 },
1362 ])
1363 fn_names := mono_fn_names(files)
1364 call_names := mono_call_names_for_fn(files, 'use')
1365 outer_is_empty := mono_assert_method_clone_by_receiver(files, 'Outer_T_Array_string',
1366 'is_empty')
1367 outer_pop := mono_assert_method_clone_by_receiver(files, 'Outer_T_Array_string', 'pop')
1368 mono_assert_method_clone_by_receiver(files, 'Inner_T_Array_string', 'is_empty')
1369 mono_assert_method_clone_by_receiver(files, 'Inner_T_Array_string', 'shift')
1370 assert call_names == [
1371 'datatypes__Outer_T_Array_string__is_empty',
1372 'datatypes__Outer_T_Array_string__pop',
1373 ], 'expected exact imported Outer[[]string] method calls, got ${call_names}'
1374 assert 'datatypes__Outer__is_empty' !in call_names
1375 assert 'datatypes__Outer__pop' !in call_names
1376 assert 'Outer__is_empty' !in call_names
1377 assert 'Outer__pop' !in call_names
1378 assert 'datatypes__Outer_T_Array_string__is_empty' !in fn_names
1379 assert 'datatypes__Outer_T_Array_string__pop' !in fn_names
1380 is_empty_body_calls := mono_call_names_for_decl(outer_is_empty)
1381 pop_body_calls := mono_call_names_for_decl(outer_pop)
1382 mono_assert_specialized_inner_calls(is_empty_body_calls,
1383 'datatypes__Inner_T_Array_string__is_empty', [
1384 'datatypes__Inner__is_empty',
1385 'Inner__is_empty',
1386 ])
1387 mono_assert_specialized_inner_calls(pop_body_calls, 'datatypes__Inner_T_Array_string__shift', [
1388 'datatypes__Inner__shift',
1389 'Inner__shift',
1390 ])
1391}
1392
1393fn test_local_generic_struct_init_receiver_method_call_is_monomorphized() {
1394 files := mono_transform_sources_for_test([
1395 MonoSource{
1396 rel: 'main.v'
1397 code: '
1398module main
1399
1400struct Queue[T] {
1401mut:
1402 elements []T
1403}
1404
1405fn (queue Queue[T]) is_empty() bool {
1406 return queue.elements.len == 0
1407}
1408
1409fn use() bool {
1410 mut queue := Queue[[]string]{}
1411 return queue.is_empty()
1412}
1413'
1414 },
1415 ])
1416 fn_names := mono_fn_names(files)
1417 call_names := mono_call_names_for_fn(files, 'use')
1418 mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'is_empty')
1419 assert call_names == ['Queue_T_Array_string__is_empty'], 'expected exact local Queue[[]string].is_empty call, got ${call_names}'
1420 mono_assert_no_open_queue_is_empty_names(fn_names, call_names)
1421}
1422
1423fn test_generic_sumtype_cast_init_does_not_lower_to_open_call() {
1424 files := mono_transform_sources_for_test([
1425 MonoSource{
1426 rel: 'main.v'
1427 code: '
1428module main
1429
1430struct Empty {}
1431
1432struct Node[T] {
1433 value T
1434}
1435
1436type Tree[T] = Empty | Node[T]
1437
1438fn use() {
1439 tree := Tree[f64](Empty{})
1440 _ = tree
1441}
1442'
1443 },
1444 ])
1445 call_names := mono_call_names_for_fn(files, 'use')
1446 tree_rhs := mono_assign_rhs_for_var(files, 'use', 'tree') or {
1447 assert false, 'missing transformed tree assignment'
1448 return
1449 }
1450 assert tree_rhs is ast.InitExpr, 'generic sumtype cast should lower to InitExpr, got ${typeof(tree_rhs).name}'
1451 tree_init := tree_rhs as ast.InitExpr
1452 mono_assert_init_ident_name(tree_init, 'Tree_T_f64')
1453 tag_value := mono_init_field(tree_init, '_tag') or {
1454 assert false, 'missing Tree_T_f64 _tag field in ${tree_init.fields}'
1455 return
1456 }
1457 mono_assert_basic_number_expr(tag_value, '0')
1458 assert call_names == [], 'generic sumtype cast should lower to a wrapper, got calls ${call_names}'
1459 assert 'Tree_T_f64' !in call_names
1460}
1461
1462fn test_generic_sumtype_concrete_variant_wrap_does_not_lower_to_open_call() {
1463 files := mono_transform_sources_for_test([
1464 MonoSource{
1465 rel: 'main.v'
1466 code: '
1467module main
1468
1469struct Empty {}
1470
1471struct Node[T] {
1472 value T
1473 left Tree[T]
1474 right Tree[T]
1475}
1476
1477type Tree[T] = Empty | Node[T]
1478
1479fn use() {
1480 empty := Tree[f64](Empty{})
1481 tree := Tree[f64](Node[f64]{1.0, empty, empty})
1482 _ = tree
1483}
1484'
1485 },
1486 ])
1487 call_names := mono_call_names_for_fn(files, 'use')
1488 tree_rhs := mono_assign_rhs_for_var(files, 'use', 'tree') or {
1489 assert false, 'missing transformed tree assignment'
1490 return
1491 }
1492 assert tree_rhs is ast.InitExpr, 'generic sumtype variant cast should lower to InitExpr, got ${typeof(tree_rhs).name}'
1493 tree_init := tree_rhs as ast.InitExpr
1494 mono_assert_init_ident_name(tree_init, 'Tree_T_f64')
1495 tag_value := mono_init_field(tree_init, '_tag') or {
1496 assert false, 'missing Tree_T_f64 _tag field in ${tree_init.fields}'
1497 return
1498 }
1499 mono_assert_basic_number_expr(tag_value, '1')
1500 payload_value := mono_init_field(tree_init, '_data._Node_T_f64') or {
1501 assert false, 'missing Tree_T_f64 Node_T_f64 payload field in ${tree_init.fields}'
1502 return
1503 }
1504 mono_assert_sumtype_payload_memdup_expr(payload_value, 'Node_T_f64')
1505 assert 'Tree_T_f64' !in call_names, 'generic sumtype cast leaked callable Tree_T_f64 in ${call_names}'
1506 assert 'Tree' !in call_names, 'generic sumtype cast leaked callable Tree in ${call_names}'
1507 for name in call_names {
1508 assert !name.contains('Tree_T_T'), 'generic sumtype cast leaked open generic name ${name} in ${call_names}'
1509 }
1510}
1511
1512fn test_non_generic_concrete_sumtype_return_wraps_variant() {
1513 files := mono_transform_sources_for_test([
1514 MonoSource{
1515 rel: 'main.v'
1516 code: '
1517module main
1518
1519struct Empty {}
1520
1521struct Node[T] {
1522 value T
1523}
1524
1525type Tree[T] = Empty | Node[T]
1526
1527fn make() Tree[int] {
1528 return Node[int]{value: 7}
1529}
1530'
1531 },
1532 ])
1533 make_decl := mono_fn_decl_by_name(files, 'make') or {
1534 assert false, 'missing transformed make function'
1535 return
1536 }
1537 returns := mono_return_exprs_for_decl(make_decl)
1538 assert returns.len == 1, 'expected one make return, got ${returns}'
1539 ret_expr := returns[0]
1540 assert ret_expr is ast.InitExpr, 'Tree[int] return should wrap Node[int], got ${ret_expr}'
1541 ret_init := ret_expr as ast.InitExpr
1542 mono_assert_init_ident_name(ret_init, 'Tree_T_int')
1543 tag_value := mono_init_field(ret_init, '_tag') or {
1544 assert false, 'missing Tree_T_int _tag field in ${ret_init.fields}'
1545 return
1546 }
1547 mono_assert_basic_number_expr(tag_value, '1')
1548 payload_value := mono_init_field(ret_init, '_data._Node_T_int') or {
1549 assert false, 'missing Tree_T_int Node_T_int payload field in ${ret_init.fields}'
1550 return
1551 }
1552 mono_assert_sumtype_payload_memdup_expr(payload_value, 'Node_T_int')
1553}
1554
1555fn test_nested_module_concrete_generic_sumtype_base_does_not_fall_back_to_local_tree() {
1556 nested_tree_type := types.Type(types.SumType{
1557 name: 'foo.bar__Tree'
1558 generic_params: ['T']
1559 variants: [
1560 types.Type(types.Struct{
1561 name: 'foo.bar__Empty'
1562 }),
1563 types.Type(types.Struct{
1564 name: 'foo.bar__Node'
1565 generic_params: ['T']
1566 }),
1567 ]
1568 })
1569 local_tree_type := types.Type(types.SumType{
1570 name: 'main__Tree'
1571 generic_params: ['T']
1572 variants: [
1573 types.Type(types.Struct{
1574 name: 'main__Wrong'
1575 }),
1576 ]
1577 })
1578 foo_tree_type := types.Type(types.SumType{
1579 name: 'foo__Tree'
1580 generic_params: ['T']
1581 variants: [
1582 types.Type(types.Struct{
1583 name: 'foo__Wrong'
1584 }),
1585 ]
1586 })
1587 mut nested_scope := types.new_scope(unsafe { nil })
1588 nested_scope.insert_type('Tree', nested_tree_type)
1589 mut foo_scope := types.new_scope(unsafe { nil })
1590 foo_scope.insert_type('Tree', foo_tree_type)
1591 mut main_scope := types.new_scope(unsafe { nil })
1592 main_scope.insert_type('Tree', local_tree_type)
1593 mut t := mono_test_transformer()
1594 t.cur_module = 'main'
1595 t.cached_scopes = {
1596 'foo.bar': nested_scope
1597 'foo': foo_scope
1598 'main': main_scope
1599 }
1600 t.cur_monomorphized_fn_bindings = {
1601 'T': types.Type(types.int_)
1602 }
1603
1604 base := t.concrete_generic_sumtype_base_type('foo__bar__Tree_T_int') or {
1605 assert false, 'missing nested foo.bar Tree base'
1606 return
1607 }
1608 assert base is types.SumType
1609 assert (base as types.SumType).name == 'foo.bar__Tree'
1610 info := t.sumtype_wrap_info_for_name('foo__bar__Tree_T_int') or {
1611 assert false, 'missing nested foo.bar Tree wrap info'
1612 return
1613 }
1614 assert info.name == 'foo__bar__Tree_T_int'
1615 assert info.variants == ['foo__bar__Empty', 'foo__bar__Node_T_int']
1616 assert 'foo__Wrong_T_int' !in info.variants
1617 assert 'main__Wrong_T_int' !in info.variants
1618}
1619
1620fn test_leaf_module_concrete_generic_sumtype_base_fallback_beats_parent_module_tree() {
1621 bar_tree_type := types.Type(types.SumType{
1622 name: 'bar__Tree'
1623 generic_params: ['T']
1624 variants: [
1625 types.Type(types.Struct{
1626 name: 'bar__Empty'
1627 }),
1628 types.Type(types.Struct{
1629 name: 'bar__Node'
1630 generic_params: ['T']
1631 }),
1632 ]
1633 })
1634 foo_tree_type := types.Type(types.SumType{
1635 name: 'foo__Tree'
1636 generic_params: ['T']
1637 variants: [
1638 types.Type(types.Struct{
1639 name: 'foo__Wrong'
1640 }),
1641 ]
1642 })
1643 mut bar_scope := types.new_scope(unsafe { nil })
1644 bar_scope.insert_type('Tree', bar_tree_type)
1645 mut foo_scope := types.new_scope(unsafe { nil })
1646 foo_scope.insert_type('Tree', foo_tree_type)
1647 mut t := mono_test_transformer()
1648 t.cur_module = 'main'
1649 t.cached_scopes = {
1650 'bar': bar_scope
1651 'foo': foo_scope
1652 }
1653 t.cur_monomorphized_fn_bindings = {
1654 'T': types.Type(types.int_)
1655 }
1656
1657 base := t.concrete_generic_sumtype_base_type('foo__bar__Tree_T_int') or {
1658 assert false, 'missing leaf module bar Tree base'
1659 return
1660 }
1661 assert base is types.SumType
1662 assert (base as types.SumType).name == 'bar__Tree'
1663 info := t.sumtype_wrap_info_for_name('foo__bar__Tree_T_int') or {
1664 assert false, 'missing leaf module bar Tree wrap info'
1665 return
1666 }
1667 assert info.name == 'foo__bar__Tree_T_int'
1668 assert info.variants == ['bar__Empty', 'bar__Node_T_int']
1669 assert 'foo__Wrong_T_int' !in info.variants
1670}
1671
1672fn test_option_concrete_sumtype_return_wraps_variant() {
1673 files := mono_transform_sources_for_test([
1674 MonoSource{
1675 rel: 'main.v'
1676 code: '
1677module main
1678
1679struct Empty {}
1680
1681struct Node[T] {
1682 value T
1683}
1684
1685type Tree[T] = Empty | Node[T]
1686
1687fn make() ?Tree[int] {
1688 return Node[int]{value: 7}
1689}
1690'
1691 },
1692 ])
1693 make_decl := mono_fn_decl_by_name(files, 'make') or {
1694 assert false, 'missing transformed make function'
1695 return
1696 }
1697 returns := mono_return_exprs_for_decl(make_decl)
1698 assert returns.len == 1, 'expected one make return, got ${returns}'
1699 ret_expr := returns[0]
1700 assert ret_expr is ast.InitExpr, '?Tree[int] return should wrap Node[int], got ${ret_expr}'
1701 ret_init := ret_expr as ast.InitExpr
1702 mono_assert_init_ident_name(ret_init, 'Tree_T_int')
1703 tag_value := mono_init_field(ret_init, '_tag') or {
1704 assert false, 'missing Tree_T_int _tag field in ${ret_init.fields}'
1705 return
1706 }
1707 mono_assert_basic_number_expr(tag_value, '1')
1708 payload_value := mono_init_field(ret_init, '_data._Node_T_int') or {
1709 assert false, 'missing Tree_T_int Node_T_int payload field in ${ret_init.fields}'
1710 return
1711 }
1712 mono_assert_sumtype_payload_memdup_expr(payload_value, 'Node_T_int')
1713}
1714
1715fn test_result_concrete_sumtype_return_wraps_variant() {
1716 files := mono_transform_sources_for_test([
1717 MonoSource{
1718 rel: 'main.v'
1719 code: '
1720module main
1721
1722struct Empty {}
1723
1724struct Node[T] {
1725 value T
1726}
1727
1728type Tree[T] = Empty | Node[T]
1729
1730fn make() !Tree[int] {
1731 return Node[int]{value: 7}
1732}
1733'
1734 },
1735 ])
1736 make_decl := mono_fn_decl_by_name(files, 'make') or {
1737 assert false, 'missing transformed make function'
1738 return
1739 }
1740 returns := mono_return_exprs_for_decl(make_decl)
1741 assert returns.len == 1, 'expected one make return, got ${returns}'
1742 ret_expr := returns[0]
1743 assert ret_expr is ast.InitExpr, '!Tree[int] return should wrap Node[int], got ${ret_expr}'
1744 ret_init := ret_expr as ast.InitExpr
1745 mono_assert_init_ident_name(ret_init, 'Tree_T_int')
1746 tag_value := mono_init_field(ret_init, '_tag') or {
1747 assert false, 'missing Tree_T_int _tag field in ${ret_init.fields}'
1748 return
1749 }
1750 mono_assert_basic_number_expr(tag_value, '1')
1751 payload_value := mono_init_field(ret_init, '_data._Node_T_int') or {
1752 assert false, 'missing Tree_T_int Node_T_int payload field in ${ret_init.fields}'
1753 return
1754 }
1755 mono_assert_sumtype_payload_memdup_expr(payload_value, 'Node_T_int')
1756}
1757
1758fn test_pointer_generic_sumtype_variant_uses_concrete_variant_name() {
1759 checked := mono_check_sources_for_test([
1760 MonoSource{
1761 rel: 'main.v'
1762 code: '
1763module main
1764
1765struct Empty {}
1766
1767struct Node[T] {
1768 value T
1769}
1770
1771type Tree[T] = &Node[T] | Empty
1772
1773fn make() Tree[int] {
1774 return &Node[int]{value: 7}
1775}
1776'
1777 },
1778 ])
1779 mut trans := checked.trans
1780 main_scope := trans.env.get_scope('main') or { panic('missing main scope') }
1781 tree_type := main_scope.lookup_type_parent('Tree', 0) or { panic('missing Tree type') }
1782 assert tree_type is types.SumType, 'expected Tree sum type, got ${tree_type}'
1783 tree := tree_type as types.SumType
1784 mut variants := []string{cap: tree.variants.len}
1785 bindings := {
1786 'T': types.Type(types.int_)
1787 }
1788 for variant in tree.variants {
1789 concrete_variant := trans.instantiate_generic_sumtype_variant(variant, bindings)
1790 variants << trans.type_to_c_name(concrete_variant)
1791 }
1792 assert variants == ['Node_T_intptr', 'Empty']
1793}
1794
1795fn test_generic_sumtype_declared_param_order_is_preserved_for_crossed_variants() {
1796 checked := mono_check_sources_for_test([
1797 MonoSource{
1798 rel: 'main.v'
1799 code: '
1800module main
1801
1802struct Left[T] {
1803 value T
1804}
1805
1806struct Right[T] {
1807 value T
1808}
1809
1810type Either[K, V] = Left[V] | Right[K]
1811
1812fn make() Either[string, int] {
1813 return Left[int]{value: 1}
1814}
1815'
1816 },
1817 ])
1818 mut trans := checked.trans
1819 main_scope := trans.env.get_scope('main') or { panic('missing main scope') }
1820 either_type := main_scope.lookup_type_parent('Either', 0) or { panic('missing Either type') }
1821 params := generic_template_type_param_names_from_type(either_type)
1822 assert params == ['K', 'V'], 'Either generic param order should stay K,V, got ${params}'
1823 files := trans.transform_files(checked.files)
1824 make_decl := mono_fn_decl_by_name(files, 'make') or {
1825 assert false, 'missing transformed make function'
1826 return
1827 }
1828 returns := mono_return_exprs_for_decl(make_decl)
1829 assert returns.len == 1, 'expected one make return, got ${returns}'
1830 ret_expr := returns[0]
1831 assert ret_expr is ast.InitExpr, 'Either[string, int] return should wrap Left[int], got ${ret_expr}'
1832 ret_init := ret_expr as ast.InitExpr
1833 mono_assert_init_ident_name(ret_init, 'Either_T_string_int')
1834 tag_value := mono_init_field(ret_init, '_tag') or {
1835 assert false, 'missing Either_T_string_int _tag field in ${ret_init.fields}'
1836 return
1837 }
1838 mono_assert_basic_number_expr(tag_value, '0')
1839 payload_value := mono_init_field(ret_init, '_data._Left_T_int') or {
1840 assert false, 'missing Either_T_string_int Left_T_int payload field in ${ret_init.fields}'
1841 return
1842 }
1843 mono_assert_sumtype_payload_memdup_expr(payload_value, 'Left_T_int')
1844}
1845
1846fn test_generic_template_type_param_names_handles_recursive_generic_struct_fields() {
1847 checked := mono_check_sources_for_test([
1848 MonoSource{
1849 rel: 'datatypes/linked_list.v'
1850 code: '
1851module datatypes
1852
1853pub struct ListNode[T] {
1854mut:
1855 data T
1856 next &ListNode[T] = unsafe { nil }
1857}
1858
1859pub struct LinkedList[T] {
1860mut:
1861 head &ListNode[T] = unsafe { nil }
1862}
1863'
1864 },
1865 MonoSource{
1866 rel: 'datatypes/queue.v'
1867 code: '
1868module datatypes
1869
1870pub struct Queue[T] {
1871mut:
1872 elements LinkedList[T]
1873}
1874'
1875 },
1876 MonoSource{
1877 rel: 'main.v'
1878 code: '
1879module main
1880
1881import datatypes
1882
1883fn use() {
1884 _ := datatypes.Queue[[]string]{}
1885}
1886'
1887 },
1888 ])
1889 mut trans := checked.trans
1890 datatypes_scope := trans.env.get_scope('datatypes') or { panic('missing datatypes scope') }
1891 queue_type := datatypes_scope.lookup_type_parent('Queue', 0) or { panic('missing Queue type') }
1892 params := generic_template_type_param_names_from_type(queue_type)
1893 assert params == ['T'], 'recursive generic Queue params should be [T], got ${params}'
1894}
1895
1896fn test_generic_template_type_param_names_collects_embedded_generic_params() {
1897 wrapper_type := types.Type(types.Struct{
1898 name: 'Wrapper'
1899 generic_params: ['T']
1900 fields: [
1901 types.Field{
1902 name: 'value'
1903 typ: types.Type(types.NamedType('T'))
1904 },
1905 ]
1906 embedded: [
1907 types.Struct{
1908 name: 'Embedded'
1909 generic_params: ['U']
1910 fields: [types.Field{
1911 name: 'inner'
1912 typ: types.Type(types.NamedType('U'))
1913 }]
1914 },
1915 ]
1916 })
1917 params := generic_template_type_param_names_from_type(wrapper_type)
1918 assert params == ['T', 'U'], 'embedded generic params should be collected, got ${params}'
1919}
1920
1921fn test_generic_template_type_param_names_keeps_same_name_different_generic_params_separate() {
1922 root_type := types.Type(types.Struct{
1923 name: 'Root'
1924 fields: [
1925 types.Field{
1926 name: 'left'
1927 typ: types.Type(types.Struct{
1928 name: 'Box'
1929 generic_params: ['T']
1930 })
1931 },
1932 types.Field{
1933 name: 'right'
1934 typ: types.Type(types.Struct{
1935 name: 'Box'
1936 generic_params: ['U']
1937 })
1938 },
1939 ]
1940 })
1941 params := generic_template_type_param_names_from_type(root_type)
1942 assert params == ['T', 'U'], 'same-name structs with different params should stay distinct, got ${params}'
1943}
1944
1945fn test_generic_template_type_param_names_does_not_guard_empty_struct_names() {
1946 root_type := types.Type(types.Struct{
1947 fields: [
1948 types.Field{
1949 name: 'left'
1950 typ: types.Type(types.Struct{
1951 generic_params: ['T']
1952 })
1953 },
1954 types.Field{
1955 name: 'right'
1956 typ: types.Type(types.Struct{
1957 generic_params: ['U']
1958 })
1959 },
1960 ]
1961 })
1962 params := generic_template_type_param_names_from_type(root_type)
1963 assert params == ['T', 'U'], 'empty-name structs should not share one structural guard, got ${params}'
1964}
1965
1966fn test_generic_sumtype_match_lowers_generic_branch_to_numeric_tag() {
1967 files := mono_transform_sources_for_test([
1968 MonoSource{
1969 rel: 'main.v'
1970 code: '
1971module main
1972
1973struct Empty {}
1974
1975struct Node[T] {
1976 value T
1977 left Tree[T]
1978 right Tree[T]
1979}
1980
1981type Tree[T] = Empty | Node[T]
1982
1983fn tag(tree Tree[f64]) int {
1984 return match tree {
1985 Empty { 0 }
1986 Node[f64] { 1 }
1987 }
1988}
1989'
1990 },
1991 ])
1992 rhs_values := mono_tag_compare_rhs_for_fn(files, 'tag')
1993 mut saw_zero := false
1994 mut saw_one := false
1995 for rhs in rhs_values {
1996 assert rhs !is ast.IndexExpr, 'generic sumtype match left index rhs ${rhs}'
1997 assert rhs !is ast.GenericArgs, 'generic sumtype match left generic args rhs ${rhs}'
1998 assert rhs !is ast.GenericArgOrIndexExpr, 'generic sumtype match left generic arg/index rhs ${rhs}'
1999 if rhs is ast.BasicLiteral && rhs.kind == token.Token.number {
2000 if rhs.value == '0' {
2001 saw_zero = true
2002 }
2003 if rhs.value == '1' {
2004 saw_one = true
2005 }
2006 }
2007 }
2008 assert saw_zero, 'generic sumtype match missing Empty tag 0 in ${rhs_values}'
2009 assert saw_one, 'generic sumtype match missing Node[f64] tag 1 in ${rhs_values}'
2010}
2011
2012fn test_sumtype_decl_variant_cache_records_concrete_generic_variants_only() {
2013 checked := mono_check_sources_for_test([
2014 MonoSource{
2015 rel: 'foo/box.v'
2016 code: '
2017module foo
2018
2019pub struct Box[T] {
2020pub:
2021 value T
2022}
2023'
2024 },
2025 MonoSource{
2026 rel: 'main.v'
2027 code: '
2028module main
2029
2030import foo as f
2031
2032struct Empty {}
2033
2034struct Node[T] {
2035 value T
2036}
2037
2038struct Box[T] {
2039 value T
2040}
2041
2042type Tree[T] = Empty | Node[T]
2043type Value = Box[int] | Box[string]
2044type ImportedValue = f.Box[int] | f.Box[string]
2045'
2046 },
2047 ])
2048 mut trans := checked.trans
2049 trans.pre_pass(checked.files)
2050 assert trans.get_sum_type_variants('Value') == ['Box_T_int', 'Box_T_string']
2051 assert trans.get_sum_type_variants('main__Value') == ['Box_T_int', 'Box_T_string']
2052 assert trans.get_sum_type_variants('ImportedValue') == ['foo__Box_T_int', 'foo__Box_T_string']
2053 assert trans.get_sum_type_variants('main__ImportedValue') == ['foo__Box_T_int',
2054 'foo__Box_T_string']
2055 assert 'Tree' !in trans.sum_type_decl_variant_names
2056 assert 'main__Tree' !in trans.sum_type_decl_variant_names
2057
2058 flat := ast.flatten_files(checked.files)
2059 mut flat_trans := Transformer.new_with_pref(checked.trans.env, checked.trans.pref)
2060 flat_trans.pre_pass_from_flat(&flat)
2061 assert flat_trans.get_sum_type_variants('Value') == ['Box_T_int', 'Box_T_string']
2062 assert flat_trans.get_sum_type_variants('main__Value') == ['Box_T_int', 'Box_T_string']
2063 assert flat_trans.get_sum_type_variants('ImportedValue') == ['foo__Box_T_int',
2064 'foo__Box_T_string']
2065 assert flat_trans.get_sum_type_variants('main__ImportedValue') == [
2066 'foo__Box_T_int',
2067 'foo__Box_T_string',
2068 ]
2069 assert 'Tree' !in flat_trans.sum_type_decl_variant_names
2070 assert 'main__Tree' !in flat_trans.sum_type_decl_variant_names
2071}
2072
2073fn test_generic_sumtype_match_uses_full_specialization_when_generic_base_repeats() {
2074 files := mono_transform_sources_for_test([
2075 MonoSource{
2076 rel: 'main.v'
2077 code: '
2078module main
2079
2080struct Box[T] {
2081 value T
2082}
2083
2084type Value = Box[int] | Box[string]
2085
2086fn tag(value Value) int {
2087 return match value {
2088 Box[string] { 20 }
2089 else { 0 }
2090 }
2091}
2092'
2093 },
2094 ])
2095 assert_generic_sumtype_repeated_base_string_tag(files, 'legacy')
2096}
2097
2098fn test_flat_direct_generic_sumtype_match_uses_full_specialization_when_generic_base_repeats() {
2099 files := mono_transform_sources_flat_direct_for_test([
2100 MonoSource{
2101 rel: 'main.v'
2102 code: '
2103module main
2104
2105struct Box[T] {
2106 value T
2107}
2108
2109type Value = Box[int] | Box[string]
2110
2111fn tag(value Value) int {
2112 return match value {
2113 Box[string] { 20 }
2114 else { 0 }
2115 }
2116}
2117'
2118 },
2119 ])
2120 assert_generic_sumtype_repeated_base_string_tag(files, 'flat direct')
2121}
2122
2123fn test_generic_sumtype_match_smartcast_scans_full_specialization_body() {
2124 files := mono_transform_sources_for_test([
2125 MonoSource{
2126 rel: 'main.v'
2127 code: generic_sumtype_repeated_base_smartcast_source()
2128 },
2129 ])
2130 assert_generic_sumtype_repeated_base_smartcast_echo(files, 'legacy')
2131}
2132
2133fn test_flat_direct_generic_sumtype_match_smartcast_scans_full_specialization_body() {
2134 files := mono_transform_sources_flat_direct_for_test([
2135 MonoSource{
2136 rel: 'main.v'
2137 code: generic_sumtype_repeated_base_smartcast_source()
2138 },
2139 ])
2140 assert_generic_sumtype_repeated_base_smartcast_echo(files, 'flat direct')
2141}
2142
2143fn generic_sumtype_repeated_base_smartcast_source() string {
2144 return '
2145module main
2146
2147struct Box[T] {
2148 value T
2149}
2150
2151type Value = Box[int] | Box[string]
2152
2153fn echo[T](value T) T {
2154 return value
2155}
2156
2157fn use(value Value) string {
2158 return match value {
2159 Box[string] { echo(value.value) }
2160 else { "" }
2161 }
2162}
2163'
2164}
2165
2166fn assert_generic_sumtype_repeated_base_string_tag(files []ast.File, label string) {
2167 rhs_values := mono_tag_compare_rhs_for_fn(files, 'tag')
2168 mut saw_string_tag := false
2169 for rhs in rhs_values {
2170 assert rhs !is ast.IndexExpr, '${label}: generic sumtype repeated base left index rhs ${rhs}'
2171 assert rhs !is ast.GenericArgs, '${label}: generic sumtype repeated base left generic args rhs ${rhs}'
2172 assert rhs !is ast.GenericArgOrIndexExpr, '${label}: generic sumtype repeated base left generic arg/index rhs ${rhs}'
2173 if rhs is ast.BasicLiteral && rhs.kind == token.Token.number {
2174 assert rhs.value != '0', '${label}: Box[string] branch matched first Box[int] tag in ${rhs_values}'
2175 if rhs.value == '1' {
2176 saw_string_tag = true
2177 }
2178 }
2179 }
2180 assert saw_string_tag, '${label}: Box[string] branch should match the second generic specialization tag, got ${rhs_values}'
2181}
2182
2183fn assert_generic_sumtype_repeated_base_smartcast_echo(files []ast.File, label string) {
2184 fn_names := mono_fn_names(files)
2185 assert 'echo_T_string' in fn_names, '${label}: smartcasted Box[string] branch did not collect echo[string], got ${fn_names}'
2186 assert 'echo_T_int' !in fn_names, '${label}: Box[string] branch collected wrong echo[int] specialization, got ${fn_names}'
2187 call_names := mono_call_names_for_fn(files, 'use')
2188 assert 'echo_T_string' in call_names, '${label}: use() should call echo_T_string from Box[string] branch, got ${call_names}'
2189 assert 'echo' !in call_names, '${label}: open echo call leaked in ${call_names}'
2190}
2191
2192fn test_generic_sumtype_receiver_no_arg_method_call_is_monomorphized() {
2193 files := mono_transform_sources_for_test([
2194 MonoSource{
2195 rel: 'main.v'
2196 code: '
2197module main
2198
2199struct Empty {}
2200
2201struct Node[T] {
2202 value T
2203}
2204
2205type Tree[T] = Empty | Node[T]
2206
2207fn (tree Tree[T]) size[T]() int {
2208 return 0
2209}
2210
2211fn use() int {
2212 tree := Tree[f64](Empty{})
2213 return tree.size()
2214}
2215'
2216 },
2217 ])
2218 fn_names := mono_fn_names(files)
2219 call_names := mono_call_names_for_fn(files, 'use')
2220 mono_assert_method_clone_by_receiver(files, 'Tree_T_f64', 'size_T_f64')
2221 assert call_names == ['Tree_T_f64__size_T_f64'], 'expected concrete Tree[f64].size call, got ${call_names}'
2222 assert 'Tree__size' !in fn_names
2223 assert 'Tree__size' !in call_names
2224 assert 'Tree_T_f64' !in call_names
2225}
2226
2227fn test_generic_sumtype_receiver_recursive_size_calls_are_monomorphized() {
2228 files := mono_transform_sources_for_test([
2229 MonoSource{
2230 rel: 'main.v'
2231 code: '
2232module main
2233
2234struct Empty {}
2235
2236struct Node[T] {
2237 value T
2238 left Tree[T]
2239 right Tree[T]
2240}
2241
2242type Tree[T] = Empty | Node[T]
2243
2244fn (tree Tree[T]) size[T]() int {
2245 return match tree {
2246 Empty { 0 }
2247 Node[T] { 1 + tree.left.size() + tree.right.size() }
2248 }
2249}
2250
2251fn use() string {
2252 tree := Tree[f64](Empty{})
2253 return "size \${tree.size()}"
2254}
2255'
2256 },
2257 ])
2258 fn_names := mono_fn_names(files)
2259 use_call_names := mono_call_names_for_fn(files, 'use')
2260 size_decl := mono_assert_method_clone_by_receiver(files, 'Tree_T_f64', 'size_T_f64')
2261 size_call_names := mono_call_names_for_decl(size_decl)
2262 assert 'Tree_T_f64__size_T_f64' in use_call_names, 'interpolation should call concrete Tree[f64].size, got ${use_call_names}'
2263 assert size_call_names == [
2264 'Tree_T_f64__size_T_f64',
2265 'Tree_T_f64__size_T_f64',
2266 ], 'recursive Tree[f64].size body should call concrete size twice, got ${size_call_names}'
2267 assert 'Tree__size' !in fn_names
2268 assert 'Tree__size' !in use_call_names
2269 assert 'Tree__size' !in size_call_names
2270 assert 'Tree_T_T__size_T' !in fn_names
2271 assert 'Tree_T_T__size_T' !in use_call_names
2272 assert 'Tree_T_T__size_T' !in size_call_names
2273 assert 'Tree_T_f64' !in use_call_names
2274}
2275
2276fn test_generic_sumtype_receiver_match_lowers_generic_branch_to_numeric_tag() {
2277 files := mono_transform_sources_for_test([
2278 MonoSource{
2279 rel: 'main.v'
2280 code: '
2281module main
2282
2283struct Empty {}
2284
2285struct Node[T] {
2286 value T
2287 left Tree[T]
2288 right Tree[T]
2289}
2290
2291type Tree[T] = Empty | Node[T]
2292
2293fn (tree Tree[T]) size[T]() int {
2294 return match tree {
2295 Empty { 0 }
2296 Node[T] { 1 }
2297 }
2298}
2299
2300fn use() int {
2301 empty := Tree[f64](Empty{})
2302 tree := Tree[f64](Node[f64]{1.0, empty, empty})
2303 return tree.size()
2304}
2305'
2306 },
2307 ])
2308 fn_names := mono_fn_names(files)
2309 call_names := mono_call_names_for_fn(files, 'use')
2310 size_decl := mono_assert_method_clone_by_receiver(files, 'Tree_T_f64', 'size_T_f64')
2311 assert call_names == ['Tree_T_f64__size_T_f64'], 'expected concrete Tree[f64].size call, got ${call_names}'
2312 assert 'Tree_T_T__size_T' !in fn_names
2313 assert 'Tree_T_T__size_T' !in call_names
2314 rhs_values := mono_tag_compare_rhs_for_decl(size_decl)
2315 mut saw_zero := false
2316 mut saw_one := false
2317 for rhs in rhs_values {
2318 assert rhs !is ast.IndexExpr, 'generic receiver match left index rhs ${rhs}'
2319 assert rhs !is ast.GenericArgs, 'generic receiver match left generic args rhs ${rhs}'
2320 assert rhs !is ast.GenericArgOrIndexExpr, 'generic receiver match left generic arg/index rhs ${rhs}'
2321 if rhs is ast.BasicLiteral && rhs.kind == token.Token.number {
2322 if rhs.value == '0' {
2323 saw_zero = true
2324 }
2325 if rhs.value == '1' {
2326 saw_one = true
2327 }
2328 }
2329 }
2330 assert saw_zero, 'generic receiver match missing Empty tag 0 in ${rhs_values}'
2331 assert saw_one, 'generic receiver match missing Node[T] tag 1 in ${rhs_values}'
2332}
2333
2334fn test_generic_sumtype_receiver_nested_min_call_is_monomorphized() {
2335 files := mono_transform_sources_for_test([
2336 MonoSource{
2337 rel: 'main.v'
2338 code: mono_nested_min_source()
2339 },
2340 ])
2341 fn_names := mono_fn_names(files)
2342 use_call_names := mono_call_names_for_fn(files, 'use')
2343 take_min_decl := mono_assert_method_clone_by_receiver(files, 'Tree_T_f64', 'take_min_T_f64')
2344 take_min_call_names := mono_call_names_for_decl(take_min_decl)
2345 assert use_call_names == ['Tree_T_f64__take_min_T_f64'], 'expected use to call only concrete take_min, got ${use_call_names}'
2346 assert 'Tree_T_f64__min_T_f64' in take_min_call_names, 'take_min min-call classification: calls=${take_min_call_names}; has_concrete=${'Tree_T_f64__min_T_f64' in take_min_call_names}; has_open_tree=${'Tree__min' in take_min_call_names}; has_open_generic=${'Tree_T_T__min_T' in take_min_call_names}; has_raw_min=${'min' in take_min_call_names}; has_any_min=${take_min_call_names.any(it.contains('min'))}'
2347 if _ := mono_method_decl_by_receiver(files, 'Tree_T_f64', 'min_T_f64') {
2348 } else {
2349 assert false, 'missing concrete method clone Tree_T_f64.min_T_f64; take_min_call_names=${take_min_call_names}; fn_names=${fn_names}; has_concrete_call=${'Tree_T_f64__min_T_f64' in take_min_call_names}; has_open_tree=${'Tree__min' in take_min_call_names}; has_open_generic=${'Tree_T_T__min_T' in take_min_call_names}; has_raw_min=${'min' in take_min_call_names}; has_any_min=${take_min_call_names.any(it.contains('min'))}'
2350 }
2351 assert 'Tree_T_f64__min_T_f64' in take_min_call_names, 'concrete take_min should call concrete min, got ${take_min_call_names}'
2352 for names in [fn_names, use_call_names, take_min_call_names] {
2353 assert 'Tree__min' !in names
2354 assert 'Tree_T_T__min_T' !in names
2355 assert 'Node_T' !in names
2356 assert 'Node_T_T' !in names
2357 assert 'unknown__min' !in names
2358 for name in names {
2359 assert !name.contains('unknown__'), 'unresolved generic receiver call leaked in ${names}'
2360 assert !name.contains('Tree_T_T__min_T'), 'open generic min call leaked in ${names}'
2361 assert !name.contains('Node_T_T'), 'open generic Node leaked in ${names}'
2362 }
2363 }
2364}
2365
2366fn test_generic_sumtype_receiver_nested_min_collects_smartcast_callee() {
2367 checked := mono_check_sources_for_test([
2368 MonoSource{
2369 rel: 'main.v'
2370 code: mono_nested_min_source()
2371 },
2372 ])
2373 mut trans := checked.trans
2374 files := checked.files
2375 trans.env.generic_types = map[string][]map[string]types.Type{}
2376 trans.pre_pass(files)
2377 trans.collect_declared_method_fns(files)
2378 trans.collect_struct_field_generic_decl_types(files)
2379 trans.collect_generic_call_specs(files)
2380 sig := 'T:f64'
2381 before_has_take_min := mono_has_spec(trans, 'Tree__take_min', sig)
2382 before_has_min := mono_has_spec(trans, 'Tree__min', sig)
2383 before_specs := mono_specs_summary(trans)
2384 prepared := trans.monomorphize_pass(files)
2385 clone_names := mono_last_clone_names(trans)
2386 mut take_min_call_names := []string{}
2387 mut take_min_call_shapes := []string{}
2388 if take_min_decl := mono_last_clone_decl(trans, 'Tree_T_f64__take_min_T_f64') {
2389 take_min_call_names = mono_raw_call_names_for_decl(take_min_decl)
2390 take_min_call_shapes = mono_call_lhs_shapes_for_decl(take_min_decl)
2391 }
2392 binding_summary := mono_binding_lookup_summary(trans, [
2393 'Tree_T_f64__take_min_T_f64',
2394 'take_min_T_f64',
2395 'Tree_T_f64__take_min',
2396 ])
2397 trans.collect_generic_call_specs_in_new_clones(prepared)
2398 after_has_min := mono_has_spec(trans, 'Tree__min', sig)
2399 after_specs := mono_specs_summary(trans)
2400 assert before_has_take_min, 'expected initial take_min spec; before_specs=${before_specs}'
2401 assert !before_has_min, 'min spec should be discovered from the first take_min clone, before_specs=${before_specs}'
2402 assert 'Tree_T_f64__take_min_T_f64' in clone_names, 'missing first take_min clone; clones=${clone_names}'
2403 assert take_min_call_names == ['min'], 'first take_min clone should still contain raw selector call before collect2; calls=${take_min_call_names}; shapes=${take_min_call_shapes}'
2404 assert take_min_call_shapes == ['Selector(Selector(Ident(tree).right).min)'], 'unexpected take_min clone call shape before collect2: ${take_min_call_shapes}'
2405 assert binding_summary.contains('Tree_T_f64__take_min_T_f64=T:f64'), 'missing take_min clone bindings: ${binding_summary}'
2406 assert after_has_min, 'collect2 should discover Tree__min ${sig}; after_specs=${after_specs}'
2407 assert after_specs.contains('Tree__min=[${sig}]'), 'missing concrete min spec after collect2; after_specs=${after_specs}'
2408 assert !after_specs.contains('Tree_T_T__min_T'), 'open generic min spec leaked after collect2: ${after_specs}'
2409 assert !after_specs.contains('unknown__min'), 'unknown min spec leaked after collect2: ${after_specs}'
2410}
2411
2412fn test_generic_sumtype_receiver_insert_return_wraps_concrete_variant() {
2413 checked := mono_check_sources_for_test([
2414 MonoSource{
2415 rel: 'main.v'
2416 code: mono_insert_size_source()
2417 },
2418 ])
2419 mut trans := checked.trans
2420 files := trans.transform_files(checked.files)
2421 fn_names := mono_fn_names(files)
2422 use_call_names := mono_call_names_for_fn(files, 'use')
2423 insert_decl := mono_assert_method_clone_by_receiver(files, 'Tree_T_f64', 'insert_T_f64')
2424 binding_summary := mono_binding_lookup_summary(trans, [
2425 'Tree_T_f64__insert_T_f64',
2426 'insert_T_f64',
2427 'Tree_T_f64__insert',
2428 ])
2429 mono_assert_method_clone_by_receiver(files, 'Tree_T_f64', 'size_T_f64')
2430 insert_returns := mono_return_exprs_for_decl(insert_decl)
2431 mut assoc_type_summary := []string{}
2432 for ret_expr in insert_returns {
2433 if ret_expr is ast.Ident && ret_expr.name.starts_with('_assoc_t') {
2434 assoc_type_summary << if typ := trans.lookup_var_type(ret_expr.name) {
2435 '${ret_expr.name}:${trans.type_to_c_name(typ)}'
2436 } else {
2437 '${ret_expr.name}:<none>'
2438 }
2439 }
2440 }
2441 mut node_wrap_count := 0
2442 mut saw_node_wrap := false
2443 for ret_expr in insert_returns {
2444 assert ret_expr !is ast.GenericArgs, 'insert returned open GenericArgs ${ret_expr}'
2445 assert ret_expr !is ast.GenericArgOrIndexExpr, 'insert returned open GenericArgOrIndexExpr ${ret_expr}'
2446 assert ret_expr !is ast.Ident || (ret_expr as ast.Ident).name != 'Node_T_f64', 'insert returned raw Node_T_f64 variant instead of Tree_T_f64 wrapper'
2447
2448 if ret_expr is ast.InitExpr {
2449 if ret_expr.typ is ast.Ident {
2450 assert ret_expr.typ.name != 'Node_T_f64', 'insert returned raw Node_T_f64 init instead of Tree_T_f64 wrapper: ${insert_returns}'
2451 }
2452 }
2453
2454 if ret_expr is ast.InitExpr {
2455 if ret_expr.typ is ast.Ident && ret_expr.typ.name == 'Tree_T_f64' {
2456 tag_value := mono_init_field(ret_expr, '_tag') or { continue }
2457 if tag_value is ast.BasicLiteral && tag_value.value == '1' {
2458 node_wrap_count++
2459 payload_value := mono_init_field(ret_expr, '_data._Node_T_f64') or { continue }
2460 if payload_value is ast.CastExpr && payload_value.expr is ast.CallExpr {
2461 memdup_call := payload_value.expr as ast.CallExpr
2462 if memdup_call.args.len == 2 && memdup_call.args[0] is ast.PrefixExpr {
2463 payload_ref := memdup_call.args[0] as ast.PrefixExpr
2464 if payload_ref.expr is ast.InitExpr {
2465 mono_assert_sumtype_payload_memdup_expr(payload_value, 'Node_T_f64')
2466 saw_node_wrap = true
2467 }
2468 }
2469 }
2470 }
2471 }
2472 }
2473 }
2474 assert saw_node_wrap, 'Tree_T_f64.insert_T_f64 should wrap Node_T_f64 return with tag 1; return_type=${insert_decl.typ.return_type}; bindings=${binding_summary}; returns=${insert_returns}; fn_names=${fn_names}; use_calls=${use_call_names}'
2475 assert node_wrap_count >= 3, 'Tree_T_f64.insert_T_f64 should wrap Empty plus both recursive Node branches; got ${node_wrap_count}; assoc_types=${assoc_type_summary}; returns=${insert_returns}'
2476 for names in [fn_names, use_call_names, mono_call_names_for_decl(insert_decl)] {
2477 assert 'Tree_T_T__insert_T' !in names
2478 assert 'Node_T' !in names
2479 assert 'Node_T_T' !in names
2480 for name in names {
2481 assert !name.contains('Tree_T_T'), 'open Tree generic leaked in ${names}'
2482 assert !name.contains('Node_T_T'), 'open Node generic leaked in ${names}'
2483 }
2484 }
2485}
2486
2487fn test_shadowed_local_receiver_names_keep_distinct_generic_bindings() {
2488 files := mono_transform_sources_for_test([
2489 MonoSource{
2490 rel: 'main.v'
2491 code: '
2492module main
2493
2494struct Queue[T] {
2495mut:
2496 elements []T
2497}
2498
2499fn (queue Queue[T]) is_empty() bool {
2500 return queue.elements.len == 0
2501}
2502
2503fn use_strings() bool {
2504 mut queue := Queue[[]string]{}
2505 return queue.is_empty()
2506}
2507
2508fn use_ints() bool {
2509 mut queue := Queue[int]{}
2510 return queue.is_empty()
2511}
2512'
2513 },
2514 ])
2515 fn_names := mono_fn_names(files)
2516 string_call_names := mono_call_names_for_fn(files, 'use_strings')
2517 int_call_names := mono_call_names_for_fn(files, 'use_ints')
2518 mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'is_empty')
2519 mono_assert_method_clone_by_receiver(files, 'Queue_T_int', 'is_empty')
2520 assert string_call_names == ['Queue_T_Array_string__is_empty'], 'expected shadowed string queue call, got ${string_call_names}'
2521 assert int_call_names == ['Queue_T_int__is_empty'], 'expected shadowed int queue call, got ${int_call_names}'
2522 mono_assert_no_open_queue_is_empty_names(fn_names, string_call_names)
2523 mono_assert_no_open_queue_is_empty_names(fn_names, int_call_names)
2524}
2525
2526fn test_explicit_generic_str_interpolation_uses_specialized_method_not_auto_str() {
2527 files := mono_transform_sources_for_test([
2528 MonoSource{
2529 rel: 'main.v'
2530 code: '
2531module main
2532
2533struct Box[T] {
2534 value T
2535}
2536
2537fn (box Box[T]) str() string {
2538 return "explicit-box"
2539}
2540
2541fn use(value int) string {
2542 box := Box[int]{value: value}
2543 return "\${box}"
2544}
2545'
2546 },
2547 ])
2548 fn_names := mono_fn_names(files)
2549 box_str := mono_assert_method_clone_by_receiver(files, 'Box_T_int', 'str')
2550 assert 'Box_T_int__str' !in fn_names, 'auto-str/full-symbol FnDecl should not replace explicit generic method: ${fn_names}'
2551 assert 'Box__str' !in fn_names
2552 literals := mono_string_literals_for_decl(box_str)
2553 assert '"explicit-box"' in literals, 'explicit generic str body was not cloned, got literals ${literals}'
2554 call_names := mono_call_names_for_fn(files, 'use')
2555 assert call_names == ['Box_T_int__str'], 'interpolation should call specialized explicit str, got ${call_names}'
2556 mono_assert_no_open_str_calls(call_names, [
2557 'Box__str',
2558 'main__Box__str',
2559 ])
2560}
2561
2562fn test_flat_direct_explicit_generic_str_interpolation_uses_specialized_method_not_auto_str() {
2563 files := mono_transform_sources_flat_direct_for_test([
2564 MonoSource{
2565 rel: 'main.v'
2566 code: '
2567module main
2568
2569struct Box[T] {
2570 value T
2571}
2572
2573fn (box Box[T]) str() string {
2574 return "explicit-box"
2575}
2576
2577fn use(value int) string {
2578 box := Box[int]{value: value}
2579 return "\${box}"
2580}
2581'
2582 },
2583 ])
2584 fn_names := mono_fn_names(files)
2585 box_str := mono_assert_method_clone_by_receiver(files, 'Box_T_int', 'str')
2586 assert 'Box_T_int__str' !in fn_names, 'auto-str/full-symbol FnDecl should not replace explicit generic method: ${fn_names}'
2587 assert 'Box__str' !in fn_names
2588 literals := mono_string_literals_for_decl(box_str)
2589 assert '"explicit-box"' in literals, 'flat direct explicit generic str body was not cloned, got literals ${literals}'
2590 call_names := mono_call_names_for_fn(files, 'use')
2591 assert call_names == ['Box_T_int__str'], 'flat direct interpolation should call specialized explicit str, got ${call_names}'
2592 mono_assert_no_open_str_calls(call_names, [
2593 'Box__str',
2594 'main__Box__str',
2595 ])
2596}
2597
2598fn test_nested_generic_explicit_str_rewrites_internal_str_calls() {
2599 files := mono_transform_sources_for_test([
2600 MonoSource{
2601 rel: 'datatypes/node.v'
2602 code: '
2603module datatypes
2604
2605pub struct ListNode[T] {
2606 value T
2607}
2608
2609pub fn (node ListNode[T]) str() string {
2610 return "node"
2611}
2612'
2613 },
2614 MonoSource{
2615 rel: 'datatypes/linked_list.v'
2616 code: '
2617module datatypes
2618
2619pub struct LinkedList[T] {
2620 head ListNode[T]
2621}
2622
2623pub fn (list LinkedList[T]) str() string {
2624 return "\${list.head}"
2625}
2626'
2627 },
2628 MonoSource{
2629 rel: 'datatypes/queue.v'
2630 code: '
2631module datatypes
2632
2633pub struct Queue[T] {
2634 list LinkedList[T]
2635}
2636
2637pub fn (queue Queue[T]) str() string {
2638 return "\${queue.list}"
2639}
2640'
2641 },
2642 MonoSource{
2643 rel: 'main.v'
2644 code: '
2645module main
2646
2647import datatypes
2648
2649fn use() string {
2650 queue := datatypes.Queue[[]string]{}
2651 return "\${queue}"
2652}
2653'
2654 },
2655 ])
2656 queue_str := mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'str')
2657 linked_str := mono_assert_method_clone_by_receiver(files, 'LinkedList_T_Array_string', 'str')
2658 mono_assert_method_clone_by_receiver(files, 'ListNode_T_Array_string', 'str')
2659 call_names := mono_call_names_for_fn(files, 'use')
2660 assert call_names == ['datatypes__Queue_T_Array_string__str'], 'interpolation should call specialized Queue str, got ${call_names}'
2661 mono_assert_no_open_str_calls(call_names, [
2662 'datatypes__Queue__str',
2663 'Queue__str',
2664 ])
2665 queue_body_calls := mono_call_names_for_decl(queue_str)
2666 linked_body_calls := mono_call_names_for_decl(linked_str)
2667 assert 'datatypes__LinkedList_T_Array_string__str' in queue_body_calls, 'Queue str body should call specialized LinkedList str, got ${queue_body_calls}'
2668 mono_assert_no_open_str_calls(queue_body_calls, [
2669 'datatypes__LinkedList__str',
2670 'LinkedList__str',
2671 ])
2672 assert 'datatypes__ListNode_T_Array_string__str' in linked_body_calls, 'LinkedList str body should call specialized ListNode str, got ${linked_body_calls}'
2673 mono_assert_no_open_str_calls(linked_body_calls, [
2674 'datatypes__ListNode__str',
2675 'ListNode__str',
2676 ])
2677}
2678
2679fn test_flat_direct_queue_nested_array_str_interpolation_forces_str_cascade() {
2680 files := mono_transform_sources_flat_direct_for_test([
2681 MonoSource{
2682 rel: 'datatypes/linked_list.v'
2683 code: '
2684module datatypes
2685
2686pub struct LinkedList[T] {
2687mut:
2688 values []T
2689}
2690
2691pub fn (mut list LinkedList[T]) push(value T) {
2692 list.values << value
2693}
2694
2695pub fn (list LinkedList[T]) array() []T {
2696 return list.values
2697}
2698
2699pub fn (list LinkedList[T]) str() string {
2700 return list.array().str()
2701}
2702'
2703 },
2704 MonoSource{
2705 rel: 'datatypes/queue.v'
2706 code: '
2707module datatypes
2708
2709pub struct Queue[T] {
2710mut:
2711 elements LinkedList[T]
2712}
2713
2714pub fn (mut queue Queue[T]) push(value T) {
2715 queue.elements.push(value)
2716}
2717
2718pub fn (queue Queue[T]) str() string {
2719 return queue.elements.str()
2720}
2721'
2722 },
2723 MonoSource{
2724 rel: 'main.v'
2725 code: '
2726module main
2727
2728import datatypes
2729
2730fn use(value []string) string {
2731 mut queue := datatypes.Queue[[]string]{}
2732 queue.push(value)
2733 return "\${queue}"
2734}
2735'
2736 },
2737 ])
2738 fn_names := mono_fn_names(files)
2739 call_names := mono_call_names_for_fn(files, 'use')
2740 queue_push := mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'push')
2741 queue_str := mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'str')
2742 linked_push := mono_assert_method_clone_by_receiver(files, 'LinkedList_T_Array_string', 'push')
2743 linked_str := mono_assert_method_clone_by_receiver(files, 'LinkedList_T_Array_string', 'str')
2744 mono_assert_method_clone_by_receiver(files, 'LinkedList_T_Array_string', 'array')
2745 assert 'datatypes__Queue_T_Array_string__str' !in fn_names, 'auto-str/full-symbol FnDecl should not replace explicit Queue.str: ${fn_names}'
2746 assert call_names == [
2747 'datatypes__Queue_T_Array_string__push',
2748 'datatypes__Queue_T_Array_string__str',
2749 ], 'flat direct Queue interpolation should call specialized explicit str without a prior queue.str(), got ${call_names}'
2750 queue_push_calls := mono_call_names_for_decl(queue_push)
2751 queue_str_calls := mono_call_names_for_decl(queue_str)
2752 linked_push_calls := mono_call_names_for_decl(linked_push)
2753 linked_str_calls := mono_call_names_for_decl(linked_str)
2754 assert 'datatypes__LinkedList_T_Array_string__push' in queue_push_calls, 'Queue.push body should specialize LinkedList.push, got ${queue_push_calls}'
2755 assert 'datatypes__LinkedList_T_Array_string__str' in queue_str_calls, 'Queue.str body should specialize LinkedList.str, got ${queue_str_calls}'
2756 assert 'datatypes__LinkedList_T_Array_string__array' in linked_str_calls, 'LinkedList.str body should specialize LinkedList.array, got ${linked_str_calls}'
2757 assert 'Array_Array_string_str' in linked_str_calls, 'LinkedList.str body should call nested array str helper, got ${linked_str_calls}'
2758 mono_assert_no_unresolved_generic_receiver_names(fn_names, 'flat direct Queue str fn decls')
2759 mono_assert_no_unresolved_generic_receiver_names(call_names,
2760 'flat direct Queue interpolation body')
2761 mono_assert_no_unresolved_generic_receiver_names(queue_push_calls,
2762 'flat direct Queue.push body')
2763 mono_assert_no_unresolved_generic_receiver_names(queue_str_calls, 'flat direct Queue.str body')
2764 mono_assert_no_unresolved_generic_receiver_names(linked_push_calls,
2765 'flat direct LinkedList.push body')
2766 mono_assert_no_unresolved_generic_receiver_names(linked_str_calls,
2767 'flat direct LinkedList.str body')
2768 mono_assert_no_open_str_calls(call_names, [
2769 'datatypes__Queue__str',
2770 'Queue__str',
2771 ])
2772 mono_assert_no_open_str_calls(queue_str_calls, [
2773 'datatypes__LinkedList__str',
2774 'LinkedList__str',
2775 ])
2776}
2777
2778fn test_flat_direct_imported_generic_receiver_clone_body_keeps_bindings() {
2779 files := mono_transform_sources_flat_direct_for_test([
2780 MonoSource{
2781 rel: 'datatypes/linked_list.v'
2782 code: '
2783module datatypes
2784
2785pub struct LinkedList[T] {
2786mut:
2787 values []T
2788}
2789
2790pub fn (mut list LinkedList[T]) push(value T) {
2791 list.values << value
2792}
2793
2794pub fn (list LinkedList[T]) is_empty() bool {
2795 return list.values.len == 0
2796}
2797
2798pub fn (mut list LinkedList[T]) shift() !T {
2799 return list.values[0]
2800}
2801'
2802 },
2803 MonoSource{
2804 rel: 'datatypes/queue.v'
2805 code: '
2806module datatypes
2807
2808pub struct Queue[T] {
2809mut:
2810 elements LinkedList[T]
2811}
2812
2813pub fn (mut queue Queue[T]) push(value T) {
2814 queue.elements.push(value)
2815}
2816
2817pub fn (queue Queue[T]) is_empty() bool {
2818 return queue.elements.is_empty()
2819}
2820
2821pub fn (mut queue Queue[T]) pop() !T {
2822 return queue.elements.shift()
2823}
2824'
2825 },
2826 MonoSource{
2827 rel: 'main.v'
2828 code: '
2829module main
2830
2831import datatypes
2832
2833fn use(value []string) bool {
2834 mut queue := datatypes.Queue[[]string]{}
2835 queue.push(value)
2836 if queue.is_empty() {
2837 return false
2838 }
2839 popped := queue.pop() or { return false }
2840 return popped.len == 1
2841}
2842'
2843 },
2844 ])
2845 fn_names := mono_fn_names(files)
2846 call_names := mono_call_names_for_fn(files, 'use')
2847 queue_push := mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'push')
2848 queue_is_empty := mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string',
2849 'is_empty')
2850 queue_pop := mono_assert_method_clone_by_receiver(files, 'Queue_T_Array_string', 'pop')
2851 mono_assert_method_clone_by_receiver(files, 'LinkedList_T_Array_string', 'push')
2852 mono_assert_method_clone_by_receiver(files, 'LinkedList_T_Array_string', 'is_empty')
2853 mono_assert_method_clone_by_receiver(files, 'LinkedList_T_Array_string', 'shift')
2854 assert call_names == [
2855 'datatypes__Queue_T_Array_string__push',
2856 'datatypes__Queue_T_Array_string__is_empty',
2857 'datatypes__Queue_T_Array_string__pop',
2858 ], 'flat direct should specialize imported Queue[[]string] calls, got ${call_names}'
2859 push_body_calls := mono_call_names_for_decl(queue_push)
2860 is_empty_body_calls := mono_call_names_for_decl(queue_is_empty)
2861 pop_body_calls := mono_call_names_for_decl(queue_pop)
2862 assert 'datatypes__LinkedList_T_Array_string__push' in push_body_calls, 'Queue.push body should specialize LinkedList.push, got ${push_body_calls}'
2863 assert 'datatypes__LinkedList_T_Array_string__is_empty' in is_empty_body_calls, 'Queue.is_empty body should specialize LinkedList.is_empty, got ${is_empty_body_calls}'
2864 assert 'datatypes__LinkedList_T_Array_string__shift' in pop_body_calls, 'Queue.pop body should specialize LinkedList.shift, got ${pop_body_calls}'
2865 mono_assert_no_unresolved_generic_receiver_names(fn_names, 'flat direct fn decls')
2866 mono_assert_no_unresolved_generic_receiver_names(call_names, 'flat direct use body')
2867 mono_assert_no_unresolved_generic_receiver_names(push_body_calls, 'flat direct Queue.push body')
2868 mono_assert_no_unresolved_generic_receiver_names(is_empty_body_calls,
2869 'flat direct Queue.is_empty body')
2870 mono_assert_no_unresolved_generic_receiver_names(pop_body_calls, 'flat direct Queue.pop body')
2871}
2872
2873fn test_transform_dijkstra_shape_rewrites_inferred_generic_nested_array_call() {
2874 files := mono_transform_dijkstra_shape_for_test()
2875 call_names := mono_call_names_for_fn(files, 'dijkstra')
2876 assert 'all_adjacents_T_int' in call_names
2877 assert 'all_adjacents' !in call_names
2878 assert mono_has_fn(files, 'all_adjacents_T_int')
2879}
2880
2881fn test_inferred_generic_call_name_for_nested_array_param() {
2882 mut t := mono_test_transformer()
2883 t.cur_module = 'main'
2884 t.generic_fn_decl_index['all_adjacents'] = ast.FnDecl{
2885 name: 'all_adjacents'
2886 typ: ast.FnType{
2887 generic_params: [
2888 ast.Expr(ast.Ident{
2889 name: 'T'
2890 }),
2891 ]
2892 params: [
2893 ast.Parameter{
2894 name: 'g'
2895 typ: ast.Expr(ast.Type(ast.ArrayType{
2896 elem_type: ast.Expr(ast.Type(ast.ArrayType{
2897 elem_type: ast.Expr(ast.Ident{
2898 name: 'T'
2899 })
2900 }))
2901 }))
2902 },
2903 ast.Parameter{
2904 name: 'v'
2905 typ: ast.Expr(ast.Ident{
2906 name: 'int'
2907 })
2908 },
2909 ]
2910 }
2911 }
2912 t.local_decl_types['g'] = types.Type(types.Array{
2913 elem_type: types.Type(types.Array{
2914 elem_type: types.Type(types.int_)
2915 })
2916 })
2917 info := CallFnInfo{
2918 param_types: [
2919 types.Type(types.Array{
2920 elem_type: types.Type(types.Array{
2921 elem_type: types.Type(types.NamedType('T'))
2922 })
2923 }),
2924 types.Type(types.int_),
2925 ]
2926 generic_param_names_by_param: ['T', '']
2927 generic_param_indexes_by_param: [0, -1]
2928 generic_params: ['T']
2929 }
2930 call_name := t.inferred_generic_call_name('all_adjacents', info, [
2931 ast.Expr(ast.Ident{
2932 name: 'g'
2933 }),
2934 ast.Expr(ast.Ident{
2935 name: 'v'
2936 }),
2937 ]) or { panic('missing inferred generic call name') }
2938 assert call_name == 'all_adjacents_T_int'
2939}
2940
2941fn test_generic_call_info_preserves_nested_array_generic_param() {
2942 mut t := mono_test_transformer()
2943 t.cur_module = 'main'
2944 t.generic_fn_decl_index['all_adjacents'] = ast.FnDecl{
2945 name: 'all_adjacents'
2946 typ: ast.FnType{
2947 generic_params: [
2948 ast.Expr(ast.Ident{
2949 name: 'T'
2950 }),
2951 ]
2952 params: [
2953 ast.Parameter{
2954 name: 'g'
2955 typ: ast.Expr(ast.Type(ast.ArrayType{
2956 elem_type: ast.Expr(ast.Type(ast.ArrayType{
2957 elem_type: ast.Expr(ast.Ident{
2958 name: 'T'
2959 })
2960 }))
2961 }))
2962 },
2963 ]
2964 }
2965 }
2966 info := t.generic_call_info_for_decl('all_adjacents') or { panic('missing generic call info') }
2967 assert info.param_types.len == 1
2968 outer := info.param_types[0] as types.Array
2969 inner := outer.elem_type as types.Array
2970 assert inner.elem_type.name() == 'T'
2971}
2972
2973fn test_transform_rewrites_inferred_generic_call_for_nested_array_param() {
2974 mut t := mono_test_transformer()
2975 t.cur_module = 'main'
2976 t.generic_fn_decl_index['all_adjacents'] = ast.FnDecl{
2977 name: 'all_adjacents'
2978 typ: ast.FnType{
2979 generic_params: [
2980 ast.Expr(ast.Ident{
2981 name: 'T'
2982 }),
2983 ]
2984 params: [
2985 ast.Parameter{
2986 name: 'g'
2987 typ: ast.Expr(ast.Type(ast.ArrayType{
2988 elem_type: ast.Expr(ast.Type(ast.ArrayType{
2989 elem_type: ast.Expr(ast.Ident{
2990 name: 'T'
2991 })
2992 }))
2993 }))
2994 },
2995 ast.Parameter{
2996 name: 'v'
2997 typ: ast.Expr(ast.Ident{
2998 name: 'int'
2999 })
3000 },
3001 ]
3002 }
3003 }
3004 t.local_decl_types['g'] = types.Type(types.Array{
3005 elem_type: types.Type(types.Array{
3006 elem_type: types.Type(types.int_)
3007 })
3008 })
3009 t.local_decl_types['v'] = types.Type(types.int_)
3010 out := t.transform_call_expr(ast.CallExpr{
3011 lhs: ast.Ident{
3012 name: 'all_adjacents'
3013 }
3014 args: [
3015 ast.Expr(ast.Ident{
3016 name: 'g'
3017 }),
3018 ast.Expr(ast.Ident{
3019 name: 'v'
3020 }),
3021 ]
3022 })
3023 assert out is ast.CallExpr
3024 call := out as ast.CallExpr
3025 assert call.lhs is ast.Ident
3026 assert (call.lhs as ast.Ident).name == 'all_adjacents_T_int'
3027}
3028
3029fn test_clone_fn_decl_substitutes_fn_literal_signature() {
3030 mut t := mono_test_transformer()
3031 decl := ast.FnDecl{
3032 name: 'make'
3033 typ: ast.FnType{
3034 generic_params: [
3035 ast.Expr(ast.Ident{
3036 name: 'T'
3037 }),
3038 ]
3039 }
3040 stmts: [
3041 ast.Stmt(ast.ReturnStmt{
3042 exprs: [
3043 ast.Expr(ast.FnLiteral{
3044 typ: ast.FnType{
3045 params: [
3046 ast.Parameter{
3047 name: 'x'
3048 typ: ast.Expr(ast.Ident{
3049 name: 'T'
3050 })
3051 },
3052 ]
3053 return_type: ast.Expr(ast.Ident{
3054 name: 'T'
3055 })
3056 }
3057 stmts: [
3058 ast.Stmt(ast.ReturnStmt{
3059 exprs: [
3060 ast.Expr(ast.Ident{
3061 name: 'x'
3062 }),
3063 ]
3064 }),
3065 ]
3066 }),
3067 ]
3068 }),
3069 ]
3070 }
3071 cloned := t.clone_fn_decl_with_substitutions(decl, {
3072 'T': types.Type(types.int_)
3073 }, 'make_T_int', 'main', 'main')
3074 ret := cloned.stmts[0] as ast.ReturnStmt
3075 lit := ret.exprs[0] as ast.FnLiteral
3076 param_typ := lit.typ.params[0].typ as ast.Ident
3077 return_typ := lit.typ.return_type as ast.Ident
3078
3079 assert param_typ.name == 'int'
3080 assert return_typ.name == 'int'
3081}
3082
3083fn test_monomorphize_pass_keeps_imported_clone_in_declaring_file() {
3084 mut t := mono_test_transformer()
3085 t.env = types.Environment.new()
3086 bindings := {
3087 'T': types.Type(types.string_)
3088 }
3089 t.env.generic_types['m__wrap'] = [bindings]
3090 t.generic_spec_owner_file[generic_spec_owner_key('m__wrap', bindings)] = 1
3091 helper_decl := ast.FnDecl{
3092 name: 'helper'
3093 typ: ast.FnType{
3094 return_type: ast.Expr(ast.Ident{
3095 name: 'int'
3096 })
3097 }
3098 }
3099 decl := ast.FnDecl{
3100 name: 'wrap'
3101 typ: ast.FnType{
3102 generic_params: [
3103 ast.Expr(ast.Ident{
3104 name: 'T'
3105 }),
3106 ]
3107 return_type: ast.Expr(ast.Ident{
3108 name: 'int'
3109 })
3110 }
3111 stmts: [
3112 ast.Stmt(ast.ReturnStmt{
3113 exprs: [
3114 ast.Expr(ast.CallExpr{
3115 lhs: ast.Expr(ast.Ident{
3116 name: 'helper'
3117 })
3118 }),
3119 ]
3120 }),
3121 ]
3122 }
3123 files := [
3124 ast.File{
3125 mod: 'm'
3126 stmts: [ast.Stmt(helper_decl), ast.Stmt(decl)]
3127 },
3128 ast.File{
3129 mod: 'main'
3130 },
3131 ]
3132 out := t.monomorphize_pass(files)
3133 assert out[0].stmts.len == 3
3134 assert out[1].stmts.len == 0
3135 assert out[0].stmts[2] is ast.FnDecl
3136 cloned := out[0].stmts[2] as ast.FnDecl
3137 assert cloned.name == 'm__wrap_T_string'
3138 ret := cloned.stmts[0] as ast.ReturnStmt
3139 call := ret.exprs[0] as ast.CallExpr
3140 assert call.lhs is ast.Ident
3141 assert (call.lhs as ast.Ident).name == 'helper'
3142}
3143
3144fn test_register_generic_bindings_keeps_first_owner_for_duplicate_spec() {
3145 mut t := mono_test_transformer()
3146 t.env = types.Environment.new()
3147 bindings := {
3148 'T': types.Type(types.string_)
3149 }
3150 t.cur_generic_call_file_idx = 0
3151 t.register_generic_bindings('json2__Encoder__encode_value', bindings)
3152 t.cur_generic_call_file_idx = 1
3153 t.register_generic_bindings('json2__Encoder__encode_value', bindings)
3154 owner_key := generic_spec_owner_key('json2__Encoder__encode_value', bindings)
3155 owner := t.generic_spec_owner_file[owner_key] or { -1 }
3156 assert owner == 0
3157}
3158
3159fn test_generic_call_concrete_return_type_prefers_substitution_over_stale_pos_type() {
3160 mut env := types.Environment.new()
3161 env.set_expr_type(77, types.Type(types.int_))
3162 mut t := mono_test_transformer()
3163 t.env = env
3164 t.generic_fn_decl_index['identity'] = ast.FnDecl{
3165 name: 'identity'
3166 typ: ast.FnType{
3167 generic_params: [
3168 ast.Expr(ast.Ident{
3169 name: 'T'
3170 }),
3171 ]
3172 params: [
3173 ast.Parameter{
3174 name: 'x'
3175 typ: ast.Expr(ast.Ident{
3176 name: 'T'
3177 })
3178 },
3179 ]
3180 return_type: ast.Expr(ast.Ident{
3181 name: 'T'
3182 })
3183 }
3184 }
3185 call := ast.Expr(ast.CallExpr{
3186 lhs: ast.Expr(ast.Ident{
3187 name: 'identity'
3188 })
3189 args: [
3190 ast.Expr(ast.BasicLiteral{
3191 kind: .number
3192 value: '0.0'
3193 }),
3194 ]
3195 pos: token.Pos{
3196 id: 77
3197 }
3198 })
3199 ret := t.get_expr_type(call) or { panic('missing generic call return type') }
3200 assert ret is types.Primitive
3201 assert (ret as types.Primitive).props.has(types.Properties.float)
3202}
3203
3204fn test_generic_call_concrete_return_type_finds_current_module_bare_call() {
3205 mut t := mono_test_transformer()
3206 t.cur_module = 'math'
3207 t.generic_fn_decl_index['math__identity'] = ast.FnDecl{
3208 name: 'identity'
3209 typ: ast.FnType{
3210 generic_params: [
3211 ast.Expr(ast.Ident{
3212 name: 'T'
3213 }),
3214 ]
3215 params: [
3216 ast.Parameter{
3217 name: 'x'
3218 typ: ast.Expr(ast.Ident{
3219 name: 'T'
3220 })
3221 },
3222 ]
3223 return_type: ast.Expr(ast.Ident{
3224 name: 'T'
3225 })
3226 }
3227 }
3228 call := ast.Expr(ast.CallExpr{
3229 lhs: ast.Expr(ast.Ident{
3230 name: 'identity'
3231 })
3232 args: [
3233 ast.Expr(ast.BasicLiteral{
3234 kind: .number
3235 value: '0.0'
3236 }),
3237 ]
3238 })
3239 ret := t.generic_call_concrete_return_type(call) or {
3240 panic('missing current-module generic return type')
3241 }
3242 assert ret is types.Primitive
3243 assert (ret as types.Primitive).props.has(types.Properties.float)
3244}
3245
3246fn test_get_expr_type_prefers_local_decl_cache_over_stale_scope_type() {
3247 mut t := mono_test_transformer()
3248 mut scope := types.new_scope(unsafe { nil })
3249 scope.insert('ret', types.Type(types.int_))
3250 t.scope = scope
3251 t.local_decl_types['ret'] = types.Type(types.f64_)
3252 typ := t.get_expr_type(ast.Expr(ast.Ident{
3253 name: 'ret'
3254 })) or { panic('missing ident type') }
3255 assert typ.name() == 'f64'
3256}
3257
3258fn test_clone_generic_type_name_selector_to_string_literal() {
3259 mut t := mono_test_transformer()
3260 expr := ast.Expr(ast.SelectorExpr{
3261 lhs: ast.Ident{
3262 name: 'T'
3263 }
3264 rhs: ast.Ident{
3265 name: 'name'
3266 }
3267 pos: token.Pos{
3268 id: 11
3269 }
3270 })
3271 out := t.clone_expr_with_bindings_and_fields(expr, {
3272 'T': types.Type(types.i64_)
3273 }, []CloneComptimeFieldCtx{})
3274 assert out is ast.StringLiteral
3275 assert (out as ast.StringLiteral).value == 'i64'
3276}
3277
3278fn test_substitute_type_leaves_non_matching_named_type_alone() {
3279 bindings := {
3280 'T': types.Type(types.int_)
3281 }
3282 out := substitute_type(types.Type(types.NamedType('U')), bindings)
3283 assert out.name() == 'U'
3284}
3285
3286// substitute_type: compound recursion
3287
3288fn test_substitute_type_recurses_into_pointer_base() {
3289 bindings := {
3290 'T': types.Type(types.int_)
3291 }
3292 original := types.Type(types.Pointer{
3293 base_type: types.Type(types.NamedType('T'))
3294 })
3295 out := substitute_type(original, bindings)
3296 if out is types.Pointer {
3297 assert out.base_type.name() == 'int'
3298 } else {
3299 assert false, 'expected Pointer, got ${out.type_name()}'
3300 }
3301}
3302
3303fn test_substitute_type_recurses_into_array_elem() {
3304 bindings := {
3305 'T': types.Type(types.int_)
3306 }
3307 original := types.Type(types.Array{
3308 elem_type: types.Type(types.NamedType('T'))
3309 })
3310 out := substitute_type(original, bindings)
3311 if out is types.Array {
3312 assert out.elem_type.name() == 'int'
3313 } else {
3314 assert false, 'expected Array, got ${out.type_name()}'
3315 }
3316}
3317
3318fn test_substitute_type_recurses_into_map_key_and_value() {
3319 bindings := {
3320 'K': types.Type(types.string_)
3321 'V': types.Type(types.int_)
3322 }
3323 original := types.Type(types.Map{
3324 key_type: types.Type(types.NamedType('K'))
3325 value_type: types.Type(types.NamedType('V'))
3326 })
3327 out := substitute_type(original, bindings)
3328 if out is types.Map {
3329 assert out.key_type.name() == 'string'
3330 assert out.value_type.name() == 'int'
3331 } else {
3332 assert false, 'expected Map, got ${out.type_name()}'
3333 }
3334}
3335
3336fn test_substitute_type_recurses_into_option_base() {
3337 bindings := {
3338 'T': types.Type(types.int_)
3339 }
3340 original := types.Type(types.OptionType{
3341 base_type: types.Type(types.NamedType('T'))
3342 })
3343 out := substitute_type(original, bindings)
3344 if out is types.OptionType {
3345 assert out.base_type.name() == 'int'
3346 } else {
3347 assert false, 'expected OptionType, got ${out.type_name()}'
3348 }
3349}
3350
3351fn test_substitute_type_recurses_into_nested_array_pointer() {
3352 bindings := {
3353 'T': types.Type(types.f64_)
3354 }
3355 original := types.Type(types.Array{
3356 elem_type: types.Type(types.Pointer{
3357 base_type: types.Type(types.NamedType('T'))
3358 })
3359 })
3360 out := substitute_type(original, bindings)
3361 if out is types.Array {
3362 elem := out.elem_type
3363 if elem is types.Pointer {
3364 assert elem.base_type.name() == 'f64'
3365 } else {
3366 assert false, 'expected nested Pointer, got ${elem.type_name()}'
3367 }
3368 } else {
3369 assert false, 'expected outer Array, got ${out.type_name()}'
3370 }
3371}
3372
3373// substitute_type_in_expr: AST-level
3374
3375fn test_substitute_type_in_expr_replaces_placeholder_ident() {
3376 mut t := mono_test_transformer()
3377 bindings := {
3378 'T': types.Type(types.int_)
3379 }
3380 expr := ast.Expr(ast.Ident{
3381 name: 'T'
3382 })
3383 out := t.substitute_type_in_expr(expr, bindings)
3384 if out is ast.Ident {
3385 assert out.name == 'int'
3386 } else {
3387 assert false, 'expected Ident, got ${out.type_name()}'
3388 }
3389}
3390
3391fn test_substitute_type_in_expr_recurses_into_array_type() {
3392 mut t := mono_test_transformer()
3393 bindings := {
3394 'T': types.Type(types.int_)
3395 }
3396 expr := ast.Expr(ast.Type(ast.ArrayType{
3397 elem_type: ast.Expr(ast.Ident{
3398 name: 'T'
3399 })
3400 }))
3401 out := t.substitute_type_in_expr(expr, bindings)
3402 out_ty := out as ast.Type
3403 at := out_ty as ast.ArrayType
3404 elem := at.elem_type as ast.Ident
3405 assert elem.name == 'int'
3406}
3407
3408fn test_substitute_type_in_expr_recurses_into_map_type() {
3409 mut t := mono_test_transformer()
3410 bindings := {
3411 'K': types.Type(types.string_)
3412 'V': types.Type(types.int_)
3413 }
3414 expr := ast.Expr(ast.Type(ast.MapType{
3415 key_type: ast.Expr(ast.Ident{
3416 name: 'K'
3417 })
3418 value_type: ast.Expr(ast.Ident{
3419 name: 'V'
3420 })
3421 }))
3422 out := t.substitute_type_in_expr(expr, bindings)
3423 out_ty := out as ast.Type
3424 mt := out_ty as ast.MapType
3425 key := mt.key_type as ast.Ident
3426 val := mt.value_type as ast.Ident
3427 assert key.name == 'string'
3428 assert val.name == 'int'
3429}
3430
3431fn test_substitute_type_in_expr_recurses_into_generic_index_type() {
3432 mut t := mono_test_transformer()
3433 bindings := {
3434 'T': types.Type(types.int_)
3435 }
3436 expr := ast.Expr(ast.GenericArgOrIndexExpr{
3437 lhs: ast.Expr(ast.Ident{
3438 name: 'AtomicVal'
3439 })
3440 expr: ast.Expr(ast.Ident{
3441 name: 'T'
3442 })
3443 })
3444 out := t.substitute_type_in_expr(expr, bindings)
3445 gen := out as ast.GenericArgOrIndexExpr
3446 arg := gen.expr as ast.Ident
3447 assert arg.name == 'int'
3448}
3449
3450// specialized_fn_name: matches cleanc's naming
3451
3452fn test_specialized_fn_name_uses_all_placeholders_suffix_when_concrete_is_self() {
3453 t := mono_test_transformer()
3454 decl := ast.FnDecl{
3455 name: 'foo'
3456 typ: ast.FnType{
3457 generic_params: [
3458 ast.Expr(ast.Ident{
3459 name: 'T'
3460 }),
3461 ]
3462 }
3463 }
3464 bindings := {
3465 'T': types.Type(types.NamedType('T'))
3466 }
3467 assert t.specialized_fn_name(decl, bindings) == 'foo_T'
3468}
3469
3470fn test_specialized_fn_name_uses_concrete_token_suffix() {
3471 t := mono_test_transformer()
3472 decl := ast.FnDecl{
3473 name: 'foo'
3474 typ: ast.FnType{
3475 generic_params: [
3476 ast.Expr(ast.Ident{
3477 name: 'T'
3478 }),
3479 ]
3480 }
3481 }
3482 bindings := {
3483 'T': types.Type(types.int_)
3484 }
3485 assert t.specialized_fn_name(decl, bindings) == 'foo_T_int'
3486}
3487
3488fn test_specialized_fn_name_handles_multiple_params() {
3489 t := mono_test_transformer()
3490 decl := ast.FnDecl{
3491 name: 'pair'
3492 typ: ast.FnType{
3493 generic_params: [
3494 ast.Expr(ast.Ident{
3495 name: 'K'
3496 }),
3497 ast.Expr(ast.Ident{
3498 name: 'V'
3499 }),
3500 ]
3501 }
3502 }
3503 bindings := {
3504 'K': types.Type(types.string_)
3505 'V': types.Type(types.int_)
3506 }
3507 assert t.specialized_fn_name(decl, bindings) == 'pair_T_string_int'
3508}
3509
3510fn test_clone_fn_decl_tracks_local_decl_types_for_interpolation_repair() {
3511 value_kind_type := types.Type(types.Enum{
3512 name: 'json2__ValueKind'
3513 })
3514 value_info_type := types.Type(types.Struct{
3515 name: 'json2__ValueInfo'
3516 fields: [
3517 types.Field{
3518 name: 'value_kind'
3519 typ: value_kind_type
3520 },
3521 ]
3522 })
3523 node_type := types.Type(types.Struct{
3524 name: 'json2__Node'
3525 fields: [
3526 types.Field{
3527 name: 'value'
3528 typ: value_info_type
3529 },
3530 ]
3531 })
3532 decoder_type := types.Type(types.Struct{
3533 name: 'json2__Decoder'
3534 fields: [
3535 types.Field{
3536 name: 'current_node'
3537 typ: types.Type(types.Pointer{
3538 base_type: node_type
3539 })
3540 },
3541 ]
3542 })
3543 mut t := mono_test_transformer()
3544 t.env = types.Environment.new()
3545 t.cur_module = 'json2'
3546 mut json2_scope := types.new_scope(unsafe { nil })
3547 json2_scope.insert_type('Decoder', decoder_type)
3548 json2_scope.insert_type('Node', node_type)
3549 json2_scope.insert_type('ValueInfo', value_info_type)
3550 t.cached_scopes['json2'] = json2_scope
3551 t.needed_str_fns = map[string]string{}
3552 t.needed_enum_str_fns = map[string]types.Enum{}
3553 value_kind_expr := ast.Expr(ast.SelectorExpr{
3554 lhs: ast.Expr(ast.Ident{
3555 name: 'string_info'
3556 })
3557 rhs: ast.Ident{
3558 name: 'value_kind'
3559 }
3560 })
3561 stale_arg := ast.Expr(ast.SelectorExpr{
3562 lhs: ast.Expr(ast.CallExpr{
3563 lhs: ast.Expr(ast.Ident{
3564 name: 'string__str'
3565 })
3566 args: [value_kind_expr]
3567 })
3568 rhs: ast.Ident{
3569 name: 'str'
3570 }
3571 })
3572 decl := ast.FnDecl{
3573 name: 'decode_string'
3574 is_method: true
3575 receiver: ast.Parameter{
3576 name: 'decoder'
3577 typ: ast.Expr(ast.Ident{
3578 name: 'Decoder'
3579 })
3580 }
3581 typ: ast.FnType{
3582 generic_params: [
3583 ast.Expr(ast.Ident{
3584 name: 'T'
3585 }),
3586 ]
3587 }
3588 stmts: [
3589 ast.Stmt(ast.AssignStmt{
3590 op: .decl_assign
3591 lhs: [
3592 ast.Expr(ast.Ident{
3593 name: 'string_info'
3594 }),
3595 ]
3596 rhs: [
3597 ast.Expr(ast.SelectorExpr{
3598 lhs: ast.Expr(ast.SelectorExpr{
3599 lhs: ast.Expr(ast.Ident{
3600 name: 'decoder'
3601 })
3602 rhs: ast.Ident{
3603 name: 'current_node'
3604 }
3605 })
3606 rhs: ast.Ident{
3607 name: 'value'
3608 }
3609 }),
3610 ]
3611 }),
3612 ast.Stmt(ast.ExprStmt{
3613 expr: ast.Expr(ast.StringInterLiteral{
3614 values: [
3615 "'Expected string, but got ",
3616 "'",
3617 ]
3618 inters: [
3619 ast.StringInter{
3620 expr: stale_arg
3621 },
3622 ]
3623 })
3624 }),
3625 ]
3626 }
3627 cloned := t.clone_fn_decl_with_substitutions(decl, {
3628 'T': types.Type(types.string_)
3629 }, 'decode_string_T_string', 'json2', 'main')
3630 _ = t.env.get_fn_scope('main', 'Decoder__decode_string_T_string') or {
3631 panic('missing target module cloned function scope')
3632 }
3633 expr_stmt := cloned.stmts[1] as ast.ExprStmt
3634 lit := expr_stmt.expr as ast.StringInterLiteral
3635 repaired := lit.inters[0].expr
3636 assert repaired is ast.SelectorExpr
3637 repaired_call := (repaired as ast.SelectorExpr).lhs
3638 assert repaired_call is ast.CallExpr
3639 repaired_lhs := (repaired_call as ast.CallExpr).lhs
3640 assert repaired_lhs is ast.Ident
3641 assert (repaired_lhs as ast.Ident).name == 'json2__ValueKind__str'
3642}
3643
3644fn test_moved_clone_qualifies_source_module_type_positions() {
3645 mut t := mono_test_transformer()
3646 t.env = types.Environment.new()
3647 mut json2_scope := types.new_scope(unsafe { nil })
3648 json2_scope.insert_type('DecoderOptions', types.Type(types.Struct{
3649 name: 'json2__DecoderOptions'
3650 }))
3651 json2_scope.insert_type('Decoder', types.Type(types.Struct{
3652 name: 'json2__Decoder'
3653 }))
3654 json2_scope.insert_type('StructFieldInfo', types.Type(types.Struct{
3655 name: 'json2__StructFieldInfo'
3656 }))
3657 t.cached_scopes['json2'] = json2_scope
3658 decl := ast.FnDecl{
3659 name: 'decode'
3660 typ: ast.FnType{
3661 generic_params: [
3662 ast.Expr(ast.Ident{
3663 name: 'T'
3664 }),
3665 ]
3666 params: [
3667 ast.Parameter{
3668 name: 'val'
3669 typ: ast.Expr(ast.Ident{
3670 name: 'string'
3671 })
3672 },
3673 ast.Parameter{
3674 name: 'params'
3675 typ: ast.Expr(ast.Ident{
3676 name: 'DecoderOptions'
3677 })
3678 },
3679 ]
3680 return_type: ast.Expr(ast.Type(ast.ResultType{
3681 base_type: ast.Expr(ast.Ident{
3682 name: 'T'
3683 })
3684 }))
3685 }
3686 stmts: [
3687 ast.Stmt(ast.AssignStmt{
3688 op: .decl_assign
3689 lhs: [
3690 ast.Expr(ast.Ident{
3691 name: 'decoder'
3692 }),
3693 ]
3694 rhs: [
3695 ast.Expr(ast.InitExpr{
3696 typ: ast.Expr(ast.Ident{
3697 name: 'Decoder'
3698 })
3699 }),
3700 ]
3701 }),
3702 ast.Stmt(ast.ExprStmt{
3703 expr: ast.Expr(ast.ArrayInitExpr{
3704 typ: ast.Expr(ast.Type(ast.ArrayType{
3705 elem_type: ast.Expr(ast.Ident{
3706 name: 'StructFieldInfo'
3707 })
3708 }))
3709 })
3710 }),
3711 ]
3712 }
3713 mut cloned := t.clone_fn_decl_with_substitutions(decl, {
3714 'T': types.Type(types.Struct{
3715 name: 'api__ApiBranchCount'
3716 })
3717 }, 'json2__decode_T_api_ApiBranchCount', 'json2', 'main')
3718 cloned = t.qualify_moved_clone_source_module_types(cloned, 'json2')
3719 options_type := cloned.typ.params[1].typ as ast.Ident
3720 assert options_type.name == 'json2__DecoderOptions'
3721 assign := cloned.stmts[0] as ast.AssignStmt
3722 init := assign.rhs[0] as ast.InitExpr
3723 init_type := init.typ as ast.Ident
3724 assert init_type.name == 'json2__Decoder'
3725 expr_stmt := cloned.stmts[1] as ast.ExprStmt
3726 array_init := expr_stmt.expr as ast.ArrayInitExpr
3727 array_type := array_init.typ as ast.Type
3728 array := array_type as ast.ArrayType
3729 elem := array.elem_type as ast.Ident
3730 assert elem.name == 'json2__StructFieldInfo'
3731}
3732
3733fn test_moved_clone_qualifies_source_module_generic_function_value() {
3734 mut t := mono_test_transformer()
3735 handler_decl := ast.FnDecl{
3736 name: 'parallel_request_handler'
3737 typ: ast.FnType{
3738 generic_params: [
3739 ast.Expr(ast.Ident{
3740 name: 'A'
3741 }),
3742 ast.Expr(ast.Ident{
3743 name: 'X'
3744 }),
3745 ]
3746 }
3747 }
3748 t.generic_fn_decl_index['parallel_request_handler'] = handler_decl
3749 t.generic_fn_decl_index['json2__parallel_request_handler'] = handler_decl
3750 decl := ast.FnDecl{
3751 name: 'run_new'
3752 typ: ast.FnType{
3753 generic_params: [
3754 ast.Expr(ast.Ident{
3755 name: 'A'
3756 }),
3757 ast.Expr(ast.Ident{
3758 name: 'X'
3759 }),
3760 ]
3761 }
3762 stmts: [
3763 ast.Stmt(ast.ExprStmt{
3764 expr: ast.Expr(ast.InitExpr{
3765 typ: ast.Expr(ast.Ident{
3766 name: 'ServerConfig'
3767 })
3768 fields: [
3769 ast.FieldInit{
3770 name: 'handler'
3771 value: ast.Expr(ast.GenericArgs{
3772 lhs: ast.Expr(ast.Ident{
3773 name: 'parallel_request_handler'
3774 })
3775 args: [
3776 ast.Expr(ast.Ident{
3777 name: 'A'
3778 }),
3779 ast.Expr(ast.Ident{
3780 name: 'X'
3781 }),
3782 ]
3783 })
3784 },
3785 ]
3786 })
3787 }),
3788 ]
3789 }
3790 mut cloned := t.clone_fn_decl_with_substitutions(decl, {
3791 'A': types.Type(types.Struct{
3792 name: 'main__App'
3793 })
3794 'X': types.Type(types.Struct{
3795 name: 'main__Context'
3796 })
3797 }, 'json2__run_new_T_App_Context', 'json2', 'main')
3798 cloned = t.qualify_moved_clone_source_module_types(cloned, 'json2')
3799 expr_stmt := cloned.stmts[0] as ast.ExprStmt
3800 init := expr_stmt.expr as ast.InitExpr
3801 handler := init.fields[0].value as ast.Ident
3802 assert handler.name == 'json2__parallel_request_handler_T_main_App_main_Context'
3803}
3804
3805fn test_moved_clone_qualifies_source_module_generic_function_value_index_expr() {
3806 mut t := mono_test_transformer()
3807 handler_decl := ast.FnDecl{
3808 name: 'parallel_request_handler'
3809 typ: ast.FnType{
3810 generic_params: [
3811 ast.Expr(ast.Ident{
3812 name: 'A'
3813 }),
3814 ast.Expr(ast.Ident{
3815 name: 'X'
3816 }),
3817 ]
3818 }
3819 }
3820 t.generic_fn_decl_index['parallel_request_handler'] = handler_decl
3821 t.generic_fn_decl_index['json2__parallel_request_handler'] = handler_decl
3822 decl := ast.FnDecl{
3823 name: 'run_new'
3824 typ: ast.FnType{
3825 generic_params: [
3826 ast.Expr(ast.Ident{
3827 name: 'A'
3828 }),
3829 ast.Expr(ast.Ident{
3830 name: 'X'
3831 }),
3832 ]
3833 }
3834 stmts: [
3835 ast.Stmt(ast.ExprStmt{
3836 expr: ast.Expr(ast.InitExpr{
3837 typ: ast.Expr(ast.Ident{
3838 name: 'ServerConfig'
3839 })
3840 fields: [
3841 ast.FieldInit{
3842 name: 'handler'
3843 value: ast.Expr(ast.IndexExpr{
3844 lhs: ast.Expr(ast.GenericArgOrIndexExpr{
3845 lhs: ast.Expr(ast.Ident{
3846 name: 'parallel_request_handler'
3847 })
3848 expr: ast.Expr(ast.Ident{
3849 name: 'A'
3850 })
3851 })
3852 expr: ast.Expr(ast.Ident{
3853 name: 'X'
3854 })
3855 })
3856 },
3857 ]
3858 })
3859 }),
3860 ]
3861 }
3862 mut cloned := t.clone_fn_decl_with_substitutions(decl, {
3863 'A': types.Type(types.Struct{
3864 name: 'main__App'
3865 })
3866 'X': types.Type(types.Struct{
3867 name: 'main__Context'
3868 })
3869 }, 'json2__run_new_T_App_Context', 'json2', 'main')
3870 cloned = t.qualify_moved_clone_source_module_types(cloned, 'json2')
3871 expr_stmt := cloned.stmts[0] as ast.ExprStmt
3872 init := expr_stmt.expr as ast.InitExpr
3873 handler := init.fields[0].value as ast.Ident
3874 assert handler.name == 'json2__parallel_request_handler_T_main_App_main_Context'
3875}
3876
3877fn test_collect_generic_struct_field_type_resolves_prefix_pointer_generic_arg() {
3878 value_kind_type := types.Type(types.Enum{
3879 name: 'json2__ValueKind'
3880 })
3881 value_info_type := types.Type(types.Struct{
3882 name: 'json2__ValueInfo'
3883 fields: [
3884 types.Field{
3885 name: 'value_kind'
3886 typ: value_kind_type
3887 },
3888 ]
3889 })
3890 node_type := types.Type(types.Struct{
3891 name: 'json2__Node'
3892 generic_params: ['T']
3893 fields: [
3894 types.Field{
3895 name: 'value'
3896 typ: types.Type(types.NamedType('T'))
3897 },
3898 ]
3899 })
3900 mut t := mono_test_transformer()
3901 t.env = types.Environment.new()
3902 t.cur_module = 'json2'
3903 mut json2_scope := types.new_scope(unsafe { nil })
3904 json2_scope.insert_type('Node', node_type)
3905 json2_scope.insert_type('ValueInfo', value_info_type)
3906 t.cached_scopes['json2'] = json2_scope
3907 t.collect_struct_decl_generic_field_types(ast.StructDecl{
3908 name: 'Decoder'
3909 fields: [
3910 ast.FieldDecl{
3911 name: 'current_node'
3912 typ: ast.Expr(ast.PrefixExpr{
3913 op: .amp
3914 expr: ast.Expr(ast.GenericArgOrIndexExpr{
3915 lhs: ast.Expr(ast.Ident{
3916 name: 'Node'
3917 })
3918 expr: ast.Expr(ast.Ident{
3919 name: 'ValueInfo'
3920 })
3921 })
3922 })
3923 },
3924 ]
3925 }, 'json2')
3926 field_typ := t.lookup_struct_field_generic_decl_type('json2__Decoder', 'current_node') or {
3927 panic('missing concrete current_node type')
3928 }
3929 assert field_typ is types.Pointer
3930 node_concrete := (field_typ as types.Pointer).base_type
3931 assert node_concrete is types.Struct
3932 value_field := struct_field_by_name(node_concrete as types.Struct, 'value') or {
3933 panic('missing Node.value field')
3934 }
3935 assert value_field.typ.name() == 'json2__ValueInfo'
3936}
3937
3938// clone_fn_decl_with_substitutions: end-to-end on a trivial generic fn
3939
3940fn test_clone_fn_decl_renames_and_substitutes_param_and_return_types() {
3941 mut t := mono_test_transformer()
3942 bindings := {
3943 'T': types.Type(types.int_)
3944 }
3945 body_pos := token.Pos{
3946 id: 1
3947 }
3948 decl := ast.FnDecl{
3949 name: 'identity'
3950 typ: ast.FnType{
3951 generic_params: [
3952 ast.Expr(ast.Ident{
3953 name: 'T'
3954 }),
3955 ]
3956 params: [
3957 ast.Parameter{
3958 name: 'x'
3959 typ: ast.Expr(ast.Ident{
3960 name: 'T'
3961 })
3962 },
3963 ]
3964 return_type: ast.Expr(ast.Ident{
3965 name: 'T'
3966 })
3967 }
3968 stmts: [
3969 ast.Stmt(ast.ReturnStmt{
3970 exprs: [
3971 ast.Expr(ast.Ident{
3972 name: 'x'
3973 pos: body_pos
3974 }),
3975 ]
3976 }),
3977 ]
3978 }
3979 cloned := t.clone_fn_decl_with_substitutions(decl, bindings, 'identity_T_int', '', '')
3980 assert cloned.name == 'identity_T_int'
3981 assert cloned.typ.generic_params.len == 0
3982 assert cloned.typ.params.len == 1
3983 param_typ := cloned.typ.params[0].typ as ast.Ident
3984 assert param_typ.name == 'int'
3985 ret_typ := cloned.typ.return_type as ast.Ident
3986 assert ret_typ.name == 'int'
3987 // Body is preserved structurally (x is a value identifier, not a type — stays 'x')
3988 assert cloned.stmts.len == 1
3989 if cloned.stmts[0] is ast.ReturnStmt {
3990 ret_stmt := cloned.stmts[0] as ast.ReturnStmt
3991 ret_ident := ret_stmt.exprs[0] as ast.Ident
3992 assert ret_ident.name == 'x'
3993 } else {
3994 assert false, 'expected ReturnStmt'
3995 }
3996}
3997
3998fn test_clone_fn_decl_substitutes_inside_array_param_type() {
3999 mut t := mono_test_transformer()
4000 bindings := {
4001 'T': types.Type(types.int_)
4002 }
4003 decl := ast.FnDecl{
4004 name: 'first'
4005 typ: ast.FnType{
4006 generic_params: [
4007 ast.Expr(ast.Ident{
4008 name: 'T'
4009 }),
4010 ]
4011 params: [
4012 ast.Parameter{
4013 name: 'arr'
4014 typ: ast.Expr(ast.Type(ast.ArrayType{
4015 elem_type: ast.Expr(ast.Ident{
4016 name: 'T'
4017 })
4018 }))
4019 },
4020 ]
4021 return_type: ast.Expr(ast.Ident{
4022 name: 'T'
4023 })
4024 }
4025 }
4026 cloned := t.clone_fn_decl_with_substitutions(decl, bindings, 'first_T_int', '', '')
4027 assert cloned.name == 'first_T_int'
4028 param_typ := cloned.typ.params[0].typ as ast.Type
4029 arr_typ := param_typ as ast.ArrayType
4030 elem := arr_typ.elem_type as ast.Ident
4031 assert elem.name == 'int'
4032}
4033
4034fn test_clone_fn_decl_registers_array_index_decl_type_in_clone_scope() {
4035 mut t := mono_test_transformer()
4036 value_pos := token.Pos{
4037 id: 801
4038 }
4039 index_pos := token.Pos{
4040 id: 802
4041 }
4042 first_lhs_pos := token.Pos{
4043 id: 803
4044 }
4045 t.env.set_expr_type(value_pos.id, types.Type(types.Array{
4046 elem_type: types.Type(types.f64_)
4047 }))
4048 t.env.set_expr_type(index_pos.id, types.Type(types.f64_))
4049 decl := ast.FnDecl{
4050 name: 'first'
4051 typ: ast.FnType{
4052 generic_params: [
4053 ast.Expr(ast.Ident{
4054 name: 'T'
4055 }),
4056 ]
4057 params: [
4058 ast.Parameter{
4059 name: 'value'
4060 typ: ast.Expr(ast.Type(ast.ArrayType{
4061 elem_type: ast.Expr(ast.Ident{
4062 name: 'T'
4063 })
4064 }))
4065 },
4066 ]
4067 }
4068 stmts: [
4069 ast.Stmt(ast.AssignStmt{
4070 op: .decl_assign
4071 lhs: [
4072 ast.Expr(ast.Ident{
4073 name: 'first'
4074 pos: first_lhs_pos
4075 }),
4076 ]
4077 rhs: [
4078 ast.Expr(ast.IndexExpr{
4079 lhs: ast.Expr(ast.Ident{
4080 name: 'value'
4081 pos: value_pos
4082 })
4083 expr: ast.Expr(ast.BasicLiteral{
4084 kind: .number
4085 value: '0'
4086 })
4087 pos: index_pos
4088 }),
4089 ]
4090 }),
4091 ]
4092 }
4093 cloned := t.clone_fn_decl_with_substitutions(decl, {
4094 'T': types.Type(types.string_)
4095 }, 'first_T_string', 'orm', 'main')
4096 scope := t.env.get_fn_scope('orm', 'first_T_string') or {
4097 panic('missing cloned function scope')
4098 }
4099 first_type := scope.lookup_var_type('first') or { panic('missing first type') }
4100 assert first_type is types.String
4101 stmt := cloned.stmts[0] as ast.AssignStmt
4102 lhs := stmt.lhs[0] as ast.Ident
4103 lhs_type := t.synth_types[lhs.pos.id] or { panic('missing first synth type') }
4104 assert lhs_type is types.String
4105}
4106
4107fn test_clone_fn_decl_registers_nested_array_index_decl_type_in_clone_scope() {
4108 mut t := mono_test_transformer()
4109 value_pos := token.Pos{
4110 id: 821
4111 }
4112 index_pos := token.Pos{
4113 id: 822
4114 }
4115 item_lhs_pos := token.Pos{
4116 id: 823
4117 }
4118 t.env.set_expr_type(value_pos.id, types.Type(types.Array{
4119 elem_type: types.Type(types.f64_)
4120 }))
4121 t.env.set_expr_type(index_pos.id, types.Type(types.f64_))
4122 decl := ast.FnDecl{
4123 name: 'first_nested'
4124 typ: ast.FnType{
4125 generic_params: [
4126 ast.Expr(ast.Ident{
4127 name: 'T'
4128 }),
4129 ]
4130 params: [
4131 ast.Parameter{
4132 name: 'value'
4133 typ: ast.Expr(ast.Type(ast.ArrayType{
4134 elem_type: ast.Expr(ast.Ident{
4135 name: 'T'
4136 })
4137 }))
4138 },
4139 ]
4140 }
4141 stmts: [
4142 ast.Stmt(ast.ForStmt{
4143 stmts: [
4144 ast.Stmt(ast.AssignStmt{
4145 op: .decl_assign
4146 lhs: [
4147 ast.Expr(ast.Ident{
4148 name: 'item'
4149 pos: item_lhs_pos
4150 }),
4151 ]
4152 rhs: [
4153 ast.Expr(ast.IndexExpr{
4154 lhs: ast.Expr(ast.Ident{
4155 name: 'value'
4156 pos: value_pos
4157 })
4158 expr: ast.Expr(ast.BasicLiteral{
4159 kind: .number
4160 value: '0'
4161 })
4162 pos: index_pos
4163 }),
4164 ]
4165 }),
4166 ]
4167 }),
4168 ]
4169 }
4170 cloned := t.clone_fn_decl_with_substitutions(decl, {
4171 'T': types.Type(types.string_)
4172 }, 'first_nested_T_string', 'orm', 'main')
4173 scope := t.env.get_fn_scope('orm', 'first_nested_T_string') or {
4174 panic('missing cloned function scope')
4175 }
4176 item_type := scope.lookup_var_type('item') or { panic('missing item type') }
4177 assert item_type is types.String
4178 for_stmt := cloned.stmts[0] as ast.ForStmt
4179 stmt := for_stmt.stmts[0] as ast.AssignStmt
4180 lhs := stmt.lhs[0] as ast.Ident
4181 lhs_type := t.synth_types[lhs.pos.id] or { panic('missing item synth type') }
4182 assert lhs_type is types.String
4183}
4184
4185fn test_clone_fn_decl_preserves_unrelated_stmts() {
4186 mut t := mono_test_transformer()
4187 bindings := {
4188 'T': types.Type(types.int_)
4189 }
4190 decl := ast.FnDecl{
4191 name: 'noop'
4192 typ: ast.FnType{
4193 generic_params: [
4194 ast.Expr(ast.Ident{
4195 name: 'T'
4196 }),
4197 ]
4198 }
4199 stmts: [
4200 ast.Stmt(ast.ExprStmt{
4201 expr: ast.Expr(ast.BasicLiteral{
4202 kind: .number
4203 value: '42'
4204 })
4205 }),
4206 ]
4207 }
4208 cloned := t.clone_fn_decl_with_substitutions(decl, bindings, 'noop_T_int', '', '')
4209 assert cloned.stmts.len == 1
4210 es := cloned.stmts[0] as ast.ExprStmt
4211 lit := es.expr as ast.BasicLiteral
4212 assert lit.value == '42'
4213}
4214