@@ -99,7 +99,11 @@ fn C.fputs(msg &char, fstream &C.FILE) i32
9999fn C.fflush(fstream &C.FILE) i32
100100
101101// TODO: define args in these functions
102-fn C.fseek(stream &C.FILE, offset i32, whence i32) i32
102+$if windows {
103+ fn C.fseek(stream &C.FILE, offset i32, whence i32) i32
104+} $else {
105+ fn C.fseek(stream &C.FILE, offset isize, whence i32) i32
106+}
103107
104108fn C.fopen(filename &char, mode &char) &C.FILE
105109
@@ -45,13 +45,14 @@ fn fixed_array_index_info(t types.Type) (bool, bool, types.ArrayFixed) {
4545
4646// gen_array_literal_value emits array literal value output for c.
4747fn (mut g FlatGen) gen_array_literal_value(node flat.Node, elem_type types.Type) {
48- c_elem := g.tc.c_type(elem_type)
48+ c_elem := g.value_c_type(elem_type)
49+ sizeof_elem := g.value_sizeof_target(elem_type)
4950 count := node.children_count
5051 if count == 0 {
51- g.write('array_new(sizeof(${c_elem}), 0, 0)')
52+ g.write('array_new(sizeof(${sizeof_elem}), 0, 0)')
5253 return
5354 }
54- g.write('new_array_from_c_array(${count}, ${count}, sizeof(${c_elem}), (${c_elem}[]){')
55+ g.write('new_array_from_c_array(${count}, ${count}, sizeof(${sizeof_elem}), (${c_elem}[]){')
5556 for i in 0 .. count {
5657 if i > 0 {
5758 g.write(', ')
@@ -67,7 +68,7 @@ fn (mut g FlatGen) gen_array_literal_value(node flat.Node, elem_type types.Type)
6768}
6869
6970fn (mut g FlatGen) gen_array_literal_ptr_arg(node flat.Node, elem_type types.Type) {
70- c_elem := g.tc.c_type(elem_type)
71+ c_elem := g.value_c_type(elem_type)
7172 g.write('(${c_elem}[]){')
7273 for i in 0 .. node.children_count {
7374 if i > 0 {
@@ -96,7 +97,7 @@ fn (mut g FlatGen) gen_pointer_arg_from_array_literal(node flat.Node, expected t
9697fn (mut g FlatGen) gen_fixed_array_data_arg(id flat.NodeId, arr types.ArrayFixed) {
9798 node := g.a.nodes[int(id)]
9899 if node.kind == .array_literal {
99- c_elem := g.tc.c_type(arr.elem_type)
100+ c_elem := g.value_c_type(arr.elem_type)
100101 g.write('(${c_elem}[]){')
101102 for i in 0 .. node.children_count {
102103 if i > 0 {
@@ -107,6 +108,10 @@ fn (mut g FlatGen) gen_fixed_array_data_arg(id flat.NodeId, arr types.ArrayFixed
107108 g.write('}')
108109 return
109110 }
111+ if node.kind == .paren && node.children_count > 0 {
112+ g.gen_fixed_array_data_arg(g.a.child(&node, 0), arr)
113+ return
114+ }
110115 if node.kind == .postfix && node.children_count > 0 {
111116 child_id := g.a.child(&node, 0)
112117 child := g.a.nodes[int(child_id)]
@@ -219,22 +224,30 @@ fn (mut g FlatGen) gen_array_method_call(node flat.Node, fn_node &flat.Node, arr
219224 g.write(', 0)')
220225 }
221226 'delete_last' {
227+ g.write('array__delete_last(')
228+ if !is_ptr {
229+ g.write('&')
230+ }
222231 g.gen_expr(base_id)
223- g.write('${dot}len--')
232+ g.write(')')
224233 }
225234 'pop' {
226- g.write('({ ${c_elem} _pop${g.tmp_count} = *(${c_elem}*)array_get(')
227- g.gen_expr(base_id)
228- g.write(', ')
235+ amp := if is_ptr { '' } else { '&' }
236+ g.write('*(${c_elem}*)array__pop(${amp}')
229237 g.gen_expr(base_id)
230- g.write('${dot}len - 1); ')
238+ g.write(')')
239+ }
240+ 'pop_left' {
241+ amp := if is_ptr { '' } else { '&' }
242+ g.write('*(${c_elem}*)array__pop_left(${amp}')
231243 g.gen_expr(base_id)
232- g.write('${dot}len--; _pop${g.tmp_count}; })')
233- g.tmp_count++
244+ g.write(')')
234245 }
235246 'clear' {
247+ amp := if is_ptr { '' } else { '&' }
248+ g.write('array__clear(${amp}')
236249 g.gen_expr(base_id)
237- g.write('${dot}len = 0')
250+ g.write(')')
238251 }
239252 'push_many' {
240253 amp := if is_ptr { '' } else { '&' }
@@ -263,9 +276,12 @@ fn (mut g FlatGen) gen_array_method_call(node flat.Node, fn_node &flat.Node, arr
263276 g.write(')')
264277 }
265278 'trim' {
279+ amp := if is_ptr { '' } else { '&' }
280+ g.write('array__trim(${amp}')
266281 g.gen_expr(base_id)
267- g.write('${dot}len = ')
282+ g.write(', ')
268283 g.gen_expr(g.a.child(&node, 1))
284+ g.write(')')
269285 }
270286 'ensure_cap' {
271287 amp := if is_ptr { '' } else { '&' }
@@ -338,6 +354,33 @@ fn (mut g FlatGen) gen_array_method_call(node flat.Node, fn_node &flat.Node, arr
338354 g.gen_expr(g.a.child(&node, 1))
339355 g.write(')')
340356 }
357+ 'last_index' {
358+ if suffix := array_last_index_suffix(arr.elem_type) {
359+ index_fn := 'array_last_index_${suffix}'
360+ g.write('${index_fn}(')
361+ g.gen_expr(base_id)
362+ g.write(', ')
363+ g.gen_expr(g.a.child(&node, 1))
364+ g.write(')')
365+ } else {
366+ g.write('array_last_index_raw(')
367+ g.gen_expr(base_id)
368+ g.write(', &(${c_elem}[]){')
369+ g.gen_expr_with_expected_type(g.a.child(&node, 1), arr.elem_type)
370+ g.write('})')
371+ }
372+ }
373+ 'hex' {
374+ g.write('Array_u8__hex(')
375+ if base_node.kind == .array_literal {
376+ g.gen_expr_with_expected_type(base_id, types.Type(types.Array{
377+ elem_type: types.Type(types.u8_)
378+ }))
379+ } else {
380+ g.gen_expr(base_id)
381+ }
382+ g.write(')')
383+ }
341384 'wait' {
342385 // Only a thread array supports `.wait()` (joining every spawned thread and,
343386 // for non-void payloads, collecting their return values into a fresh `[]T`).
@@ -353,11 +396,11 @@ fn (mut g FlatGen) gen_array_method_call(node flat.Node, fn_node &flat.Node, arr
353396 if is_thread {
354397 g.gen_thread_array_wait(base_id, is_ptr, arr.elem_type)
355398 } else {
356- g.gen_array_method_call_fallback(node, fn_node.value, base_id, is_ptr)
399+ g.gen_array_method_call_fallback(node, fn_node.value, base_id, is_ptr, arr)
357400 }
358401 }
359402 else {
360- g.gen_array_method_call_fallback(node, fn_node.value, base_id, is_ptr)
403+ g.gen_array_method_call_fallback(node, fn_node.value, base_id, is_ptr, arr)
361404 }
362405 }
363406}
@@ -367,8 +410,8 @@ fn (mut g FlatGen) gen_array_method_call(node flat.Node, fn_node &flat.Node, arr
367410// emits the selector itself as a direct call. Shared by the catch-all `else` arm and by
368411// `.wait()` on non-thread arrays (which is unsupported and falls through here rather than
369412// joining elements as thread handles).
370-fn (mut g FlatGen) gen_array_method_call_fallback(node flat.Node, mname string, base_id flat.NodeId, is_ptr bool) {
371- best_mname := g.array_method_fallback(mname)
413+fn (mut g FlatGen) gen_array_method_call_fallback(node flat.Node, mname string, base_id flat.NodeId, is_ptr bool, arr types.Array) {
414+ best_mname := g.array_method_fallback_for_receiver(mname, arr)
372415 if best_mname.len > 0 {
373416 g.write(c_name(best_mname))
374417 g.write('(')
@@ -503,6 +546,16 @@ fn array_lookup_suffix(elem_type types.Type) string {
503546 return 'int'
504547}
505548
549+fn array_last_index_suffix(elem_type types.Type) ?string {
550+ elem_name := elem_type.name()
551+ return match elem_name {
552+ 'string' { 'string' }
553+ 'u8', 'byte' { 'u8' }
554+ 'int' { 'int' }
555+ else { none }
556+ }
557+}
558+
506559// array_method_fallback supports array method fallback handling for FlatGen.
507560fn (mut g FlatGen) array_method_fallback(method string) string {
508561 if method in g.array_method_cache {
@@ -521,6 +574,38 @@ fn (mut g FlatGen) array_method_fallback(method string) string {
521574 return best_mname
522575}
523576
577+fn (mut g FlatGen) array_method_fallback_for_receiver(method string, arr types.Array) string {
578+ key := '${arr.elem_type.name()}.${method}'
579+ if key in g.array_method_cache {
580+ return g.array_method_cache[key]
581+ }
582+ suffix := '.${method}'
583+ mut best_mname := ''
584+ for mname, ptypes in g.tc.fn_param_types {
585+ if !mname.ends_with(suffix) || ptypes.len == 0 {
586+ continue
587+ }
588+ recv := types.unwrap_pointer(ptypes[0])
589+ if recv is types.Array && g.array_elem_type_matches(recv.elem_type, arr.elem_type) {
590+ if best_mname.len == 0 || mname.len > best_mname.len {
591+ best_mname = mname
592+ }
593+ }
594+ }
595+ if best_mname.len == 0 {
596+ best_mname = g.array_method_fallback(method)
597+ }
598+ g.array_method_cache[key] = best_mname
599+ return best_mname
600+}
601+
602+fn (g &FlatGen) array_elem_type_matches(expected types.Type, actual types.Type) bool {
603+ if expected.name() == actual.name() {
604+ return true
605+ }
606+ return g.tc.c_type(expected) == g.tc.c_type(actual)
607+}
608+
524609// gen_map_ref_arg emits map ref arg output for c.
525610fn (mut g FlatGen) gen_map_ref_arg(base_id flat.NodeId, base_type types.Type) {
526611 if base_type is types.Pointer {
@@ -584,18 +669,55 @@ fn (mut g FlatGen) gen_index_assign(node flat.Node) {
584669 }
585670 if is_array_base {
586671 c_elem := g.value_c_type(arr_type.elem_type)
587- g.write('array_set(')
672+ tmp := g.tmp_count
673+ g.tmp_count++
674+ g.write('{ array* _a${tmp} = ')
588675 if base_type is types.Pointer {
589- g.write('*')
676+ g.gen_expr(base_id)
677+ } else {
678+ g.write('&')
679+ g.gen_expr(base_id)
590680 }
591- g.gen_expr(base_id)
592- g.write(', ')
681+ g.write('; int _i${tmp} = ')
593682 g.gen_expr(g.a.child(&lhs, 1))
594- g.write(', &(${c_elem}[]){')
595- g.gen_expr_with_expected_type(g.a.child(&node, 1), arr_type.elem_type)
596- g.writeln('});')
683+ g.write('; array__set(_a${tmp}, _i${tmp}, &(${c_elem}[]){')
684+ if op := compound_assign_to_infix_op(node.op) {
685+ if arr_type.elem_type is types.String && op == .plus {
686+ g.write('string__plus(')
687+ g.write('*(string*)array_get(*_a${tmp}, _i${tmp})')
688+ g.write(', ')
689+ g.gen_expr_as_string(g.a.child(&node, 1))
690+ g.write(')')
691+ } else {
692+ g.write('(')
693+ g.write('*(${c_elem}*)array_get(*_a${tmp}, _i${tmp})')
694+ g.write(' ${g.op_str(op)} ')
695+ g.gen_expr_with_expected_type(g.a.child(&node, 1), arr_type.elem_type)
696+ g.write(')')
697+ }
698+ } else {
699+ g.gen_expr_with_expected_type(g.a.child(&node, 1), arr_type.elem_type)
700+ }
701+ g.writeln('}); }')
597702 return
598703 }
599704 }
600705 g.gen_assign(node)
601706}
707+
708+fn compound_assign_to_infix_op(op flat.Op) ?flat.Op {
709+ match op {
710+ .plus_assign { return flat.Op.plus }
711+ .minus_assign { return flat.Op.minus }
712+ .mul_assign { return flat.Op.mul }
713+ .div_assign { return flat.Op.div }
714+ .mod_assign { return flat.Op.mod }
715+ .amp_assign { return flat.Op.amp }
716+ .pipe_assign { return flat.Op.pipe }
717+ .xor_assign { return flat.Op.xor }
718+ .left_shift_assign { return flat.Op.left_shift }
719+ .right_shift_assign { return flat.Op.right_shift }
720+ .right_shift_unsigned_assign { return flat.Op.right_shift_unsigned }
721+ else { return none }
722+ }
723+}
@@ -54,13 +54,17 @@ mut:
5454 fn_decl_ret_types map[string]types.Type // fn decl name (and qualified variants) -> return type
5555 struct_decl_infos map[string]StructDeclInfo
5656 struct_decl_short_infos map[string]StructDeclInfo
57+ const_runtime_inits []string
58+ const_runtime_init_modules []string
5759 runtime_inits []string
60+ runtime_init_modules []string
5861 compiler_vroot string
5962 c99_mode bool
6063 cur_fn_name string
6164 cur_param_names []string
6265 cur_param_type_values []types.Type
6366 cur_param_types map[string]types.Type
67+ cur_mut_params map[string]bool
6468 cur_fn_ret types.Type = types.Type(types.void_)
6569 cur_fn_ret_is_optional bool
6670 cur_fn_ret_base types.Type = types.Type(types.void_)
@@ -154,6 +158,7 @@ pub fn FlatGen.new() FlatGen {
154158 cur_param_names: []string{}
155159 cur_param_type_values: []types.Type{}
156160 cur_param_types: map[string]types.Type{}
161+ cur_mut_params: map[string]bool{}
157162 needed_optional_types: map[string]string{}
158163 emitted_optional_types: map[string]bool{}
159164 emitted_fns: map[string]bool{}
@@ -171,7 +176,10 @@ pub fn FlatGen.new() FlatGen {
171176 fn_defer_counts: map[int]string{}
172177 defer_capture_names: []string{}
173178 defer_capture_types: map[string]types.Type{}
179+ const_runtime_inits: []string{}
180+ const_runtime_init_modules: []string{}
174181 runtime_inits: []string{}
182+ runtime_init_modules: []string{}
175183 compiler_vroot: ''
176184 line_start: true
177185 }
@@ -207,7 +215,10 @@ pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[strin
207215 g.fn_defer_counts = map[int]string{}
208216 g.defer_capture_names = []string{}
209217 g.defer_capture_types = map[string]types.Type{}
218+ g.const_runtime_inits = []string{}
219+ g.const_runtime_init_modules = []string{}
210220 g.runtime_inits = []string{}
221+ g.runtime_init_modules = []string{}
211222 g.compiler_vroot = ''
212223 g.str_lit_ids = map[string]int{}
213224 g.global_types = map[string]types.Type{}
@@ -241,6 +252,7 @@ pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[strin
241252 g.cur_param_names = []string{}
242253 g.cur_param_type_values = []types.Type{}
243254 g.cur_param_types = map[string]types.Type{}
255+ g.cur_mut_params = map[string]bool{}
244256 g.needed_optional_types = map[string]string{}
245257 g.emitted_optional_types = map[string]bool{}
246258 g.emitted_fns = map[string]bool{}
@@ -312,15 +324,19 @@ pub fn (mut g FlatGen) gen_with_used_options(a &flat.FlatAst, used_fns map[strin
312324 g.sb.write_string(const_code)
313325 // The final builder now owns a copy of the const code.
314326 unsafe { const_code.free() }
315- if g.runtime_inits.len > 0 || g.module_init_fns.len > 0 || g.global_inits.len > 0 {
327+ if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0
328+ || g.global_inits.len > 0 {
316329 g.writeln('void _vinit() {')
317- for ri in g.runtime_inits {
318- g.writeln(ri)
319- }
320- // Module-level init() functions run after const/global initialization.
321- for init_fn in g.ordered_module_init_fns() {
322- g.writeln('\t${init_fn}();')
330+ mut emitted_const := []bool{len: g.const_runtime_inits.len}
331+ mut emitted_runtime := []bool{len: g.runtime_inits.len}
332+ init_fns := g.module_init_fn_map()
333+ for mod in g.ordered_startup_modules(init_fns) {
334+ g.emit_runtime_inits_for_module(mod, mut emitted_const, mut emitted_runtime)
335+ if init_fn := init_fns[mod] {
336+ g.writeln('\t${init_fn}();')
337+ }
323338 }
339+ g.emit_remaining_runtime_inits(mut emitted_const, mut emitted_runtime)
324340 g.writeln('}')
325341 g.writeln('')
326342 }
@@ -345,7 +361,7 @@ fn node_kind_id(node flat.Node) int {
345361// collect_gen_info updates collect gen info state for c.
346362fn (mut g FlatGen) collect_gen_info() {
347363 g.collect_c_flags_from_directives()
348- mut cur_module := ''
364+ mut cur_module := 'main'
349365 mut cur_file := ''
350366 mut seen_import_in_file := false
351367 for node_idx in 0 .. g.a.nodes.len {
@@ -354,9 +370,9 @@ fn (mut g FlatGen) collect_gen_info() {
354370 if kind_id == 77 {
355371 cur_file = node.value
356372 g.note_compiler_source_file(node.value)
357- g.tc.cur_file = cur_file
358- cur_module = ''
373+ cur_module = 'main'
359374 g.tc.cur_module = cur_module
375+ g.tc.cur_file = cur_file
360376 seen_import_in_file = false
361377 continue
362378 }
@@ -1317,7 +1333,7 @@ fn (mut g FlatGen) collect_const_init_order_from_files() {
13171333 if node_kind_id(node) != 77 || node.children_count == 0 {
13181334 continue
13191335 }
1320- mut cur_module := ''
1336+ mut cur_module := 'main'
13211337 for i in 0 .. node.children_count {
13221338 child := g.a.child_node(&node, i)
13231339 kind_id := node_kind_id(child)
@@ -1345,21 +1361,128 @@ fn (mut g FlatGen) collect_const_init_order_from_files() {
13451361
13461362// ordered_module_init_fns supports ordered module init fns handling for FlatGen.
13471363fn (g &FlatGen) ordered_module_init_fns() []string {
1364+ module_to_init := g.module_init_fn_map()
1365+ mut result := []string{}
1366+ mut visiting := map[string]bool{}
1367+ mut visited := map[string]bool{}
1368+ for init_fn in g.module_init_fns {
1369+ mod := g.module_init_fn_modules[init_fn] or { '' }
1370+ g.visit_module_init(mod, module_to_init, mut visiting, mut visited, mut result)
1371+ }
1372+ return result
1373+}
1374+
1375+fn (g &FlatGen) module_init_fn_map() map[string]string {
13481376 mut module_to_init := map[string]string{}
13491377 for init_fn in g.module_init_fns {
13501378 mod := g.module_init_fn_modules[init_fn] or { '' }
13511379 module_to_init[mod] = init_fn
13521380 }
1381+ return module_to_init
1382+}
1383+
1384+fn (g &FlatGen) ordered_startup_modules(module_to_init map[string]string) []string {
1385+ mut module_order := []string{}
1386+ for init_fn in g.module_init_fns {
1387+ mod := g.module_init_fn_modules[init_fn] or { '' }
1388+ if mod !in module_order {
1389+ module_order << mod
1390+ }
1391+ }
1392+ for mod in g.const_runtime_init_modules {
1393+ if mod !in module_order {
1394+ module_order << mod
1395+ }
1396+ }
1397+ for mod in g.runtime_init_modules {
1398+ if mod !in module_order {
1399+ module_order << mod
1400+ }
1401+ }
1402+ mut startup_modules := map[string]bool{}
1403+ for mod in module_order {
1404+ startup_modules[mod] = true
1405+ }
1406+ for mod, _ in module_to_init {
1407+ startup_modules[mod] = true
1408+ }
13531409 mut result := []string{}
13541410 mut visiting := map[string]bool{}
13551411 mut visited := map[string]bool{}
1356- for init_fn in g.module_init_fns {
1357- mod := g.module_init_fn_modules[init_fn] or { '' }
1358- g.visit_module_init(mod, module_to_init, mut visiting, mut visited, mut result)
1412+ for mod in module_order {
1413+ g.visit_startup_module(mod, startup_modules, mut visiting, mut visited, mut result)
13591414 }
13601415 return result
13611416}
13621417
1418+fn (g &FlatGen) visit_startup_module(mod string, startup_modules map[string]bool, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) {
1419+ if mod in visited || mod in visiting {
1420+ return
1421+ }
1422+ visiting[mod] = true
1423+ for dep in g.module_imports[mod] or { []string{} } {
1424+ dep_module := g.startup_dependency_module(dep, startup_modules)
1425+ g.visit_startup_module(dep_module, startup_modules, mut visiting, mut visited, mut result)
1426+ }
1427+ visiting.delete(mod)
1428+ visited[mod] = true
1429+ if mod in startup_modules {
1430+ result << mod
1431+ }
1432+}
1433+
1434+fn (g &FlatGen) startup_dependency_module(dep string, startup_modules map[string]bool) string {
1435+ if dep in startup_modules || dep in g.module_imports {
1436+ return dep
1437+ }
1438+ short := startup_module_key(dep)
1439+ if short in startup_modules || short in g.module_imports {
1440+ return short
1441+ }
1442+ return dep
1443+}
1444+
1445+fn (mut g FlatGen) emit_runtime_inits_for_module(mod string, mut emitted_const []bool, mut emitted_runtime []bool) {
1446+ for i, ri in g.const_runtime_inits {
1447+ if !emitted_const[i] && i < g.const_runtime_init_modules.len
1448+ && g.const_runtime_init_modules[i] == mod {
1449+ g.writeln(ri)
1450+ emitted_const[i] = true
1451+ }
1452+ }
1453+ for i, ri in g.runtime_inits {
1454+ if !emitted_runtime[i] && i < g.runtime_init_modules.len && g.runtime_init_modules[i] == mod {
1455+ g.writeln(ri)
1456+ emitted_runtime[i] = true
1457+ }
1458+ }
1459+}
1460+
1461+fn (mut g FlatGen) emit_remaining_runtime_inits(mut emitted_const []bool, mut emitted_runtime []bool) {
1462+ for i, ri in g.const_runtime_inits {
1463+ if !emitted_const[i] {
1464+ g.writeln(ri)
1465+ emitted_const[i] = true
1466+ }
1467+ }
1468+ for i, ri in g.runtime_inits {
1469+ if !emitted_runtime[i] {
1470+ g.writeln(ri)
1471+ emitted_runtime[i] = true
1472+ }
1473+ }
1474+}
1475+
1476+fn (mut g FlatGen) queue_const_runtime_init(line string) {
1477+ g.const_runtime_inits << line
1478+ g.const_runtime_init_modules << g.tc.cur_module
1479+}
1480+
1481+fn (mut g FlatGen) queue_runtime_init(line string) {
1482+ g.runtime_inits << line
1483+ g.runtime_init_modules << g.tc.cur_module
1484+}
1485+
13631486// visit_module_init updates visit module init state for FlatGen.
13641487fn (g &FlatGen) visit_module_init(mod string, module_to_init map[string]string, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) {
13651488 if mod in visited || mod in visiting {
@@ -1367,7 +1490,8 @@ fn (g &FlatGen) visit_module_init(mod string, module_to_init map[string]string,
13671490 }
13681491 visiting[mod] = true
13691492 for dep in g.module_imports[mod] or { []string{} } {
1370- g.visit_module_init(dep, module_to_init, mut visiting, mut visited, mut result)
1493+ dep_module := startup_module_key(dep)
1494+ g.visit_module_init(dep_module, module_to_init, mut visiting, mut visited, mut result)
13711495 }
13721496 visiting.delete(mod)
13731497 visited[mod] = true
@@ -1997,6 +2121,140 @@ fn (mut g FlatGen) gen_amp_c_string_literal(id flat.NodeId, node flat.Node) bool
19972121 return false
19982122}
19992123
2124+fn (mut g FlatGen) gen_expr_as_string(id flat.NodeId) {
2125+ typ := g.usable_expr_type(id)
2126+ if g.gen_map_str_expr(id, typ) {
2127+ return
2128+ }
2129+ g.gen_expr(id)
2130+}
2131+
2132+fn (mut g FlatGen) gen_map_str_expr(id flat.NodeId, typ types.Type) bool {
2133+ clean := map_str_clean_type(typ)
2134+ if clean !is types.Map {
2135+ return false
2136+ }
2137+ node := g.a.nodes[int(id)]
2138+ if node.kind == .map_init && typ !is types.Pointer {
2139+ tmp := '__map_str_tmp_${g.tmp_count}'
2140+ g.tmp_count++
2141+ g.write('({ map ${tmp} = ')
2142+ g.gen_expr_with_expected_type(id, clean)
2143+ g.write(';')
2144+ key_kind := map_str_kind(g.tc, clean.key_type)
2145+ val_kind := map_str_kind(g.tc, clean.value_type)
2146+ fixed_len := map_str_fixed_len(clean.value_type)
2147+ g.write(' v3_map_str(${tmp}, ${key_kind}, ${val_kind}, ${fixed_len}); })')
2148+ return true
2149+ }
2150+ g.write('v3_map_str(')
2151+ if typ is types.Pointer {
2152+ needs_paren := g.a.nodes[int(id)].kind !in [.ident, .selector, .call]
2153+ g.write('*')
2154+ if needs_paren {
2155+ g.write('(')
2156+ }
2157+ g.gen_expr(id)
2158+ if needs_paren {
2159+ g.write(')')
2160+ }
2161+ } else {
2162+ g.gen_expr(id)
2163+ }
2164+ key_kind := map_str_kind(g.tc, clean.key_type)
2165+ val_kind := map_str_kind(g.tc, clean.value_type)
2166+ fixed_len := map_str_fixed_len(clean.value_type)
2167+ g.write(', ${key_kind}, ${val_kind}, ${fixed_len})')
2168+ return true
2169+}
2170+
2171+fn map_str_clean_type(typ types.Type) types.Type {
2172+ clean := types.unwrap_pointer(typ)
2173+ if clean is types.Alias {
2174+ return clean.base_type
2175+ }
2176+ return clean
2177+}
2178+
2179+fn map_str_kind(tc &types.TypeChecker, typ types.Type) int {
2180+ clean := if typ is types.Alias { typ.base_type } else { typ }
2181+ if clean is types.String {
2182+ return 1
2183+ }
2184+ if clean is types.Rune {
2185+ return 4
2186+ }
2187+ if clean is types.ISize || clean is types.Char {
2188+ return 2
2189+ }
2190+ if clean is types.USize {
2191+ return 3
2192+ }
2193+ if clean is types.Primitive {
2194+ if clean.props.has(.float) {
2195+ return if tc.c_type(types.Type(clean)) == 'float' { 8 } else { 5 }
2196+ }
2197+ name := types.Type(clean).name()
2198+ if name in ['i8', 'i16', 'i32', 'i64', 'int'] {
2199+ return 2
2200+ }
2201+ if name in ['u8', 'byte'] {
2202+ return 3
2203+ }
2204+ if name in ['u16', 'u32', 'u64'] {
2205+ return 3
2206+ }
2207+ if name == 'bool' {
2208+ return 7
2209+ }
2210+ }
2211+ if fixed := array_fixed_type(clean) {
2212+ elem := if fixed.elem_type is types.Alias {
2213+ fixed.elem_type.base_type
2214+ } else {
2215+ fixed.elem_type
2216+ }
2217+ if elem is types.Primitive && elem.props.has(.float) {
2218+ return if tc.c_type(types.Type(elem)) == 'float' { 9 } else { 6 }
2219+ }
2220+ }
2221+ return 0
2222+}
2223+
2224+fn map_str_fixed_len(typ types.Type) int {
2225+ if fixed := array_fixed_type(typ) {
2226+ if fixed.len > 0 {
2227+ return fixed.len
2228+ }
2229+ if fixed.len_expr.len > 0 && fixed.len_expr.int().str() == fixed.len_expr {
2230+ return fixed.len_expr.int()
2231+ }
2232+ }
2233+ return 0
2234+}
2235+
2236+// gen_cast_from_mut_param_address emits pointer casts for `¶m` where `param`
2237+// is a mutable V parameter already represented as a C pointer.
2238+fn (mut g FlatGen) gen_cast_from_mut_param_address(id flat.NodeId, ct string) bool {
2239+ node := g.a.nodes[int(id)]
2240+ if node.kind != .prefix || node.op != .amp || node.children_count != 1 {
2241+ return false
2242+ }
2243+ child_id := g.a.child(&node, 0)
2244+ child := g.a.nodes[int(child_id)]
2245+ if child.kind != .ident || !g.current_param_is_mut(child.value) {
2246+ return false
2247+ }
2248+ param_type := g.current_param_type(child.value) or { return false }
2249+ if param_type !is types.Pointer {
2250+ return false
2251+ }
2252+ g.write('(${ct})(')
2253+ g.gen_expr(child_id)
2254+ g.write(')')
2255+ return true
2256+}
2257+
20002258// gen_expr_with_expected_type emits expr with expected type output for c.
20012259fn (mut g FlatGen) gen_expr_with_expected_type(id flat.NodeId, expected types.Type) {
20022260 old_expected := g.expected_expr_type
@@ -2022,6 +2280,11 @@ fn (mut g FlatGen) gen_expr_with_expected_type(id flat.NodeId, expected types.Ty
20222280 actual = param_type
20232281 }
20242282 }
2283+ if expected is types.String && g.gen_map_str_expr(id, actual) {
2284+ g.expected_expr_type = old_expected
2285+ g.expected_enum = old_expected_enum
2286+ return
2287+ }
20252288 if _ := fn_type_from(expected) {
20262289 if g.gen_callback_fn_value_for_expected_type(id, expected) {
20272290 g.expected_expr_type = old_expected
@@ -2036,17 +2299,13 @@ fn (mut g FlatGen) gen_expr_with_expected_type(id flat.NodeId, expected types.Ty
20362299 }
20372300 }
20382301 if expected is types.Array && node.kind == .array_literal {
2039- elem_type := if node.children_count > 0 {
2040- g.tc.resolve_type(g.a.child(&node, 0))
2041- } else {
2042- expected.elem_type
2043- }
2044- g.gen_array_literal_value(node, elem_type)
2302+ g.gen_array_literal_value(node, expected.elem_type)
20452303 g.expected_expr_type = old_expected
20462304 g.expected_enum = old_expected_enum
20472305 return
20482306 }
2049- if expected !is types.Pointer && expected !is types.Void && actual is types.Pointer
2307+ if expected !is types.Pointer && expected !is types.Void && expected !is types.OptionType
2308+ && expected !is types.ResultType && actual is types.Pointer
20502309 && g.type_names_match(actual.base_type, expected) {
20512310 needs_paren := node.kind !in [.ident, .selector, .call, .index]
20522311 g.write('*')
@@ -2290,11 +2549,20 @@ fn (mut g FlatGen) type_name_c_type(type_name string) string {
22902549 if _ := g.tc.cur_scope.lookup(type_name) {
22912550 return c_name(type_name)
22922551 }
2552+ if type_name.starts_with('fn_ptr:') {
2553+ return g.resolve_fn_ptr_type(type_name)
2554+ }
22932555 t := g.tc.parse_type(type_name)
22942556 return g.tc.c_type(t)
22952557}
22962558
22972559fn (mut g FlatGen) sizeof_target(value string) string {
2560+ if value.starts_with('fn_ptr:') {
2561+ return g.resolve_fn_ptr_type(value)
2562+ }
2563+ if fixed_target := c_fixed_array_typedef_sizeof_target(value) {
2564+ return fixed_target
2565+ }
22982566 if value.contains('.') {
22992567 parts := value.split('.')
23002568 if parts.len > 1 {
@@ -2306,9 +2574,29 @@ fn (mut g FlatGen) sizeof_target(value string) string {
23062574 }
23072575 }
23082576 }
2577+ if fixed := array_fixed_type(g.tc.parse_type(value)) {
2578+ c_elem, dims := g.fixed_array_decl_parts(fixed)
2579+ return '${c_elem}${dims}'
2580+ }
23092581 return g.type_name_c_type(value)
23102582}
23112583
2584+fn c_fixed_array_typedef_sizeof_target(value string) ?string {
2585+ if !value.starts_with('Array_fixed_') {
2586+ return none
2587+ }
2588+ payload := value['Array_fixed_'.len..]
2589+ if !payload.contains('_') {
2590+ return none
2591+ }
2592+ elem := payload.all_before_last('_')
2593+ len := payload.all_after_last('_')
2594+ if elem.len == 0 || len.len == 0 {
2595+ return none
2596+ }
2597+ return '${elem}[${len}]'
2598+}
2599+
23122600fn sizeof_selector_target(base string, fields []string) string {
23132601 mut expr := c_name(base)
23142602 for field in fields {
@@ -2858,7 +3146,12 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
28583146 } else if v.starts_with('\\') {
28593147 g.write("'${v}'")
28603148 } else {
2861- g.write(v)
3149+ runes := v.runes()
3150+ if runes.len == 0 {
3151+ g.write('0')
3152+ } else {
3153+ g.write(int(runes[0]).str())
3154+ }
28623155 }
28633156 }
28643157 .string_literal {
@@ -2975,6 +3268,10 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
29753268 g.expected_enum = old_expected_enum
29763269 return
29773270 }
3271+ if g.gen_array_infix_eq(node, lhs_id, rhs_id, lhs_type, rhs_type) {
3272+ g.expected_enum = old_expected_enum
3273+ return
3274+ }
29783275 if lhs_type is types.String || rhs_type is types.String {
29793276 if g.gen_string_infix_fallback(node, lhs_id, rhs_id) {
29803277 g.expected_enum = old_expected_enum
@@ -3239,6 +3536,23 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
32393536 } else {
32403537 g.write('0')
32413538 }
3539+ } else if base_type0 is types.String && node.value == 'len' {
3540+ g.gen_expr(base_id)
3541+ g.write('.len')
3542+ } else if types.unwrap_pointer(base_type0) is types.Array && node.value == 'len' {
3543+ needs_paren := base.kind !in [.ident, .selector, .call]
3544+ if needs_paren {
3545+ g.write('(')
3546+ }
3547+ g.gen_expr(base_id)
3548+ if needs_paren {
3549+ g.write(')')
3550+ }
3551+ if base_type0 is types.Pointer {
3552+ g.write('->len')
3553+ } else {
3554+ g.write('.len')
3555+ }
32423556 } else if base.kind == .call && base.children_count == 2
32433557 && g.c_typedef_cast_call_name(base).len > 0 {
32443558 cast_name := g.c_typedef_cast_call_name(base)
@@ -3476,7 +3790,7 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
34763790 ct := g.tc.c_type(raw_init_type)
34773791 g.write('(${ct}){0}')
34783792 } else {
3479- c_elem := g.tc.c_type(init_type)
3793+ c_elem := g.sizeof_target(node.value)
34803794 g.write('array_new(sizeof(${c_elem}), 0, 0)')
34813795 }
34823796 }
@@ -3496,6 +3810,9 @@ fn (mut g FlatGen) gen_expr(id flat.NodeId) {
34963810 g.write('(${ct}){0}')
34973811 } else if target_type is types.SumType {
34983812 g.gen_sum_cast_expr(target_type, g.a.child(&node, 0))
3813+ } else if target_type is types.Pointer
3814+ && g.gen_cast_from_mut_param_address(g.a.child(&node, 0), ct) {
3815+ return
34993816 } else {
35003817 g.write('(${ct})(')
35013818 g.gen_expr(g.a.child(&node, 0))
@@ -3623,51 +3940,51 @@ fn (mut g FlatGen) gen_string_infix_fallback(node flat.Node, lhs_id flat.NodeId,
36233940 match node.op {
36243941 .plus {
36253942 g.write('string__plus(')
3626- g.gen_expr(lhs_id)
3943+ g.gen_expr_as_string(lhs_id)
36273944 g.write(', ')
3628- g.gen_expr(rhs_id)
3945+ g.gen_expr_as_string(rhs_id)
36293946 g.write(')')
36303947 }
36313948 .eq {
36323949 g.write('string__eq(')
3633- g.gen_expr(lhs_id)
3950+ g.gen_expr_as_string(lhs_id)
36343951 g.write(', ')
3635- g.gen_expr(rhs_id)
3952+ g.gen_expr_as_string(rhs_id)
36363953 g.write(')')
36373954 }
36383955 .ne {
36393956 g.write('!string__eq(')
3640- g.gen_expr(lhs_id)
3957+ g.gen_expr_as_string(lhs_id)
36413958 g.write(', ')
3642- g.gen_expr(rhs_id)
3959+ g.gen_expr_as_string(rhs_id)
36433960 g.write(')')
36443961 }
36453962 .lt {
36463963 g.write('string__lt(')
3647- g.gen_expr(lhs_id)
3964+ g.gen_expr_as_string(lhs_id)
36483965 g.write(', ')
3649- g.gen_expr(rhs_id)
3966+ g.gen_expr_as_string(rhs_id)
36503967 g.write(')')
36513968 }
36523969 .gt {
36533970 g.write('string__lt(')
3654- g.gen_expr(rhs_id)
3971+ g.gen_expr_as_string(rhs_id)
36553972 g.write(', ')
3656- g.gen_expr(lhs_id)
3973+ g.gen_expr_as_string(lhs_id)
36573974 g.write(')')
36583975 }
36593976 .le {
36603977 g.write('!string__lt(')
3661- g.gen_expr(rhs_id)
3978+ g.gen_expr_as_string(rhs_id)
36623979 g.write(', ')
3663- g.gen_expr(lhs_id)
3980+ g.gen_expr_as_string(lhs_id)
36643981 g.write(')')
36653982 }
36663983 .ge {
36673984 g.write('!string__lt(')
3668- g.gen_expr(lhs_id)
3985+ g.gen_expr_as_string(lhs_id)
36693986 g.write(', ')
3670- g.gen_expr(rhs_id)
3987+ g.gen_expr_as_string(rhs_id)
36713988 g.write(')')
36723989 }
36733990 else {
@@ -3678,6 +3995,45 @@ fn (mut g FlatGen) gen_string_infix_fallback(node flat.Node, lhs_id flat.NodeId,
36783995 return true
36793996}
36803997
3998+fn (mut g FlatGen) gen_array_infix_eq(node flat.Node, lhs_id flat.NodeId, rhs_id flat.NodeId, lhs_type types.Type, rhs_type types.Type) bool {
3999+ if node.op !in [.eq, .ne] {
4000+ return false
4001+ }
4002+ if lhs_type is types.Pointer || rhs_type is types.Pointer {
4003+ return false
4004+ }
4005+ lhs_arr := array_like_type(types.unwrap_pointer(lhs_type)) or { return false }
4006+ rhs_arr := array_like_type(types.unwrap_pointer(rhs_type)) or { return false }
4007+ elem_type := if lhs_arr.elem_type.name() != 'unknown' {
4008+ lhs_arr.elem_type
4009+ } else {
4010+ rhs_arr.elem_type
4011+ }
4012+ if node.op == .ne {
4013+ g.write('!')
4014+ }
4015+ if elem_type is types.String {
4016+ g.write('array_eq_string(')
4017+ } else {
4018+ g.write('array_eq_raw(')
4019+ }
4020+ g.gen_array_value_arg(lhs_id, lhs_type)
4021+ g.write(', ')
4022+ g.gen_array_value_arg(rhs_id, rhs_type)
4023+ if elem_type !is types.String {
4024+ g.write(', sizeof(${g.tc.c_type(elem_type)})')
4025+ }
4026+ g.write(')')
4027+ return true
4028+}
4029+
4030+fn (mut g FlatGen) gen_array_value_arg(id flat.NodeId, typ types.Type) {
4031+ if typ is types.Pointer {
4032+ g.write('*')
4033+ }
4034+ g.gen_expr(id)
4035+}
4036+
36814037fn array_membership_fn_name(elem_type types.Type, fixed bool) string {
36824038 prefix := if fixed { 'fixed_array_contains_' } else { 'array_contains_' }
36834039 elem_name := elem_type.name()
@@ -3787,6 +4143,7 @@ fn (mut g FlatGen) c99_feature_test_macros() {
37874143}
37884144
37894145fn (mut g FlatGen) headerless_libc_preamble() {
4146+ g.collect_preserved_c_fns(c_headerless_libc_declared_fns)
37904147 g.writeln('#ifndef NULL')
37914148 g.writeln('#define NULL ((void*)0)')
37924149 g.writeln('#endif')
@@ -3979,7 +4336,7 @@ fn (mut g FlatGen) headerless_libc_preamble() {
39794336 g.writeln('typedef struct SRWLOCK { void* Ptr; } SRWLOCK;')
39804337 g.writeln('typedef struct CONDITION_VARIABLE { void* Ptr; } CONDITION_VARIABLE;')
39814338 g.writeln('typedef void* atomic_uintptr_t;')
3982- g.writeln('#if !defined(__cplusplus) && !defined(_WCHAR_T) && !defined(_WCHAR_T_DEFINED) && !defined(__WCHAR_T) && !defined(__wchar_t_defined) && !defined(_BSD_WCHAR_T_DEFINED_) && !defined(_WCHAR_T_DECLARED)')
4339+ g.writeln('#if !defined(__TINYC__) && !defined(__cplusplus) && !defined(_WCHAR_T) && !defined(_WCHAR_T_DEFINED) && !defined(__WCHAR_T) && !defined(__wchar_t_defined) && !defined(_BSD_WCHAR_T_DEFINED_) && !defined(_WCHAR_T_DECLARED)')
39834340 g.writeln('#ifdef _WIN32')
39844341 g.writeln('typedef unsigned short wchar_t;')
39854342 g.writeln('#else')
@@ -4010,6 +4367,56 @@ fn (mut g FlatGen) headerless_libc_preamble() {
40104367 g.headerless_platform_constants()
40114368}
40124369
4370+const c_headerless_libc_declared_fns = [
4371+ 'getenv',
4372+ 'setenv',
4373+ 'abort',
4374+ 'memset',
4375+ 'memcpy',
4376+ 'memmove',
4377+ 'memcmp',
4378+ 'strlen',
4379+ 'strcmp',
4380+ 'strncmp',
4381+ 'strncpy',
4382+ 'floor',
4383+ 'ceil',
4384+ 'floorf',
4385+ 'ceilf',
4386+ 'sqrt',
4387+ 'pow',
4388+ 'ldexp',
4389+ 'fmod',
4390+ 'cos',
4391+ 'acos',
4392+ 'fabs',
4393+ 'open',
4394+ 'read',
4395+ 'fork',
4396+ 'dup2',
4397+ 'execlp',
4398+ 'execvp',
4399+ '_exit',
4400+ 'access',
4401+ 'realpath',
4402+ 'strrchr',
4403+ 'strstr',
4404+ 'snprintf',
4405+ 'fcntl',
4406+ 'pipe',
4407+ 'close',
4408+ 'signal',
4409+ 'atexit',
4410+ '__error',
4411+ '__errno',
4412+ '__errno_location',
4413+ '_errno',
4414+ 'pthread_mutex_init',
4415+ 'pthread_mutex_lock',
4416+ 'pthread_mutex_unlock',
4417+ 'pthread_mutex_destroy',
4418+]
4419+
40134420fn (mut g FlatGen) headerless_windows_sdk_types() {
40144421 g.writeln('#ifndef WINAPI')
40154422 g.writeln('#if defined(_WIN32) && (defined(__i386__) || defined(_M_IX86))')
@@ -5527,17 +5934,7 @@ fn (mut g FlatGen) builtin_abi_decls() {
55275934 g.writeln('#ifndef __linux__')
55285935 g.writeln('#define pthread_rwlockattr_setkind_np(attr, kind) 0')
55295936 g.writeln('#endif')
5530- g.writeln('#ifndef V_OS_FILELOCK_HELPERS_H')
5531- g.writeln('#ifdef _WIN32')
5532- g.writeln('BOOL LockFileEx(HANDLE handle, DWORD flags, DWORD reserved, DWORD low, DWORD high, OVERLAPPED* overlap);')
5533- g.writeln('BOOL UnlockFileEx(HANDLE handle, DWORD reserved, DWORD low, DWORD high, OVERLAPPED* overlap);')
5534- g.writeln('static inline int v_filelock_lock(HANDLE handle, int exclusive, int immediate, u64 start, u64 len) { OVERLAPPED overlap; memset(&overlap, 0, sizeof(overlap)); overlap.Offset = (DWORD)(start & 0xffffffffULL); overlap.OffsetHigh = (DWORD)(start >> 32); DWORD flags = immediate ? LOCKFILE_FAIL_IMMEDIATELY : 0; if (exclusive) { flags |= LOCKFILE_EXCLUSIVE_LOCK; } DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL); DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32); return LockFileEx(handle, flags, 0, low, high, &overlap) ? 0 : -1; }')
5535- g.writeln('static inline int v_filelock_unlock(HANDLE handle, u64 start, u64 len) { OVERLAPPED overlap; memset(&overlap, 0, sizeof(overlap)); overlap.Offset = (DWORD)(start & 0xffffffffULL); overlap.OffsetHigh = (DWORD)(start >> 32); DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL); DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32); return UnlockFileEx(handle, 0, low, high, &overlap) ? 0 : -1; }')
5536- g.writeln('#else')
5537- g.writeln('static inline int v_filelock_lock(int fd, int exclusive, int immediate, u64 start, u64 len) { struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = exclusive ? F_WRLCK : F_RDLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)start; fl.l_len = len == 0 ? 0 : (off_t)len; return fcntl(fd, immediate ? F_SETLK : F_SETLKW, &fl); }')
5538- g.writeln('static inline int v_filelock_unlock(int fd, u64 start, u64 len) { struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = F_UNLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)start; fl.l_len = len == 0 ? 0 : (off_t)len; return fcntl(fd, F_SETLK, &fl); }')
5539- g.writeln('#endif')
5540- g.writeln('#endif')
5937+ g.filelock_compat_decls()
55415938 g.writeln('#define array_new(elem_size, len, cap) __new_array((len), (cap), (elem_size))')
55425939 g.writeln('#define array_push array__push')
55435940 g.writeln('void array__push_many(array* a, void* val, int size);')
@@ -5577,14 +5974,65 @@ fn (mut g FlatGen) builtin_abi_decls() {
55775974 g.writeln('#define v_signal_with_handler_cast(sig, handler) signal((sig), ((void (*)(int))(handler)))')
55785975 g.writeln('string string__clone(string a);')
55795976 g.writeln('void string__free(string* s);')
5977+ g.writeln('string string__plus(string s, string a);')
5978+ g.writeln('string int__str(int n);')
5979+ g.writeln('string i64__str(i64 n);')
5980+ g.writeln('string u64__str(u64 nn);')
5981+ g.writeln('string f64__str(double x);')
5982+ g.writeln('string rune__str(i32 c);')
5983+ g.writeln('u8* malloc_noscan(ptrdiff_t n);')
5984+ g.writeln('static inline string v3_c_lit(const char* s, int len) { return (string){.str = (u8*)s, .len = len, .is_lit = 1}; }')
5985+ g.writeln('static inline string v3_char_string(int c) { return rune__str((i32)c); }')
5986+ g.writeln('static inline string v3_f64_fixed(double x, int precision) { char tmp[128]; int n = snprintf(tmp, sizeof(tmp), "%.*f", precision, x); if (n < 0) return v3_c_lit("", 0); if (n < (int)sizeof(tmp)) { u8* out = malloc_noscan(n + 1); memcpy(out, tmp, n + 1); return (string){.str = out, .len = n, .is_lit = 0}; } u8* out = malloc_noscan(n + 1); snprintf((char*)out, (size_t)n + 1, "%.*f", precision, x); return (string){.str = out, .len = n, .is_lit = 0}; }')
5987+ g.writeln('static inline string v3_int_zpad(int n, int width) { string s = int__str(n); if (n < 0) return s; while (s.len < width) s = string__plus(v3_c_lit("0", 1), s); return s; }')
5988+ g.writeln('static inline string v3_i64_zpad(i64 n, int width) { string s = i64__str(n); if (n < 0) return s; while (s.len < width) s = string__plus(v3_c_lit("0", 1), s); return s; }')
5989+ g.writeln('static inline string v3_u64_zpad(u64 n, int width) { string s = u64__str(n); while (s.len < width) s = string__plus(v3_c_lit("0", 1), s); return s; }')
5990+ g.writeln('static inline i64 v3_map_signed(void* p, int bytes) { if (bytes == 1) return *(signed char*)p; if (bytes == 2) return *(short*)p; if (bytes == 8) return *(long long*)p; return *(int*)p; }')
5991+ g.writeln('static inline u64 v3_map_unsigned(void* p, int bytes) { if (bytes == 1) return *(unsigned char*)p; if (bytes == 2) return *(unsigned short*)p; if (bytes == 8) return *(unsigned long long*)p; return *(unsigned int*)p; }')
5992+ g.writeln('static inline string v3_f32_array_str(float* vals, int n) { string out = v3_c_lit("[", 1); for (int i = 0; i < n; ++i) { if (i > 0) out = string__plus(out, v3_c_lit(", ", 2)); out = string__plus(out, f64__str((double)vals[i])); } return string__plus(out, v3_c_lit("]", 1)); }')
5993+ g.writeln('static inline string v3_f64_array_str(double* vals, int n) { string out = v3_c_lit("[", 1); for (int i = 0; i < n; ++i) { if (i > 0) out = string__plus(out, v3_c_lit(", ", 2)); out = string__plus(out, f64__str(vals[i])); } return string__plus(out, v3_c_lit("]", 1)); }')
5994+ g.writeln('static inline string v3_map_str_piece(void* p, int kind, int bytes, int fixed_len) {')
5995+ g.writeln('\tif (kind == 1) { return string__plus(string__plus(v3_c_lit("\'", 1), *(string*)p), v3_c_lit("\'", 1)); }')
5996+ g.writeln('\tif (kind == 2) { return v3_i64_zpad(v3_map_signed(p, bytes), 0); }')
5997+ g.writeln('\tif (kind == 3) { return u64__str(v3_map_unsigned(p, bytes)); }')
5998+ g.writeln('\tif (kind == 4) { i32 r = bytes == 1 ? (i32)(*(u8*)p) : *(i32*)p; return string__plus(string__plus(v3_c_lit("`", 1), rune__str(r)), v3_c_lit("`", 1)); }')
5999+ g.writeln('\tif (kind == 5) { if (bytes == (int)sizeof(float)) return f64__str((double)*(float*)p); return f64__str(*(double*)p); }')
6000+ g.writeln('\tif (kind == 6) { if (fixed_len == 0 && bytes == (int)sizeof(Array)) { Array a = *(Array*)p; if (a.element_size == (int)sizeof(float)) return v3_f32_array_str((float*)a.data, a.len); if (a.element_size == (int)sizeof(double)) return v3_f64_array_str((double*)a.data, a.len); } if (fixed_len > 0 && bytes == fixed_len * (int)sizeof(float)) return v3_f32_array_str((float*)p, fixed_len); int n = fixed_len > 0 ? fixed_len : bytes / (int)sizeof(double); return v3_f64_array_str((double*)p, n); }')
6001+ g.writeln('\tif (kind == 8) { return f64__str((double)*(float*)p); }')
6002+ g.writeln('\tif (kind == 9) { int n = fixed_len > 0 ? fixed_len : bytes / (int)sizeof(float); return v3_f32_array_str((float*)p, n); }')
6003+ g.writeln('\tif (kind == 7) { return *(bool*)p ? v3_c_lit("true", 4) : v3_c_lit("false", 5); }')
6004+ g.writeln('\treturn v3_c_lit("<map value>", 11);')
6005+ g.writeln('}')
6006+ g.writeln('static inline string v3_map_str(map m, int key_kind, int val_kind, int val_fixed_len) {')
6007+ g.writeln('\tstring out = v3_c_lit("{", 1); bool first = true;')
6008+ g.writeln('\tfor (int i = 0; i < m.key_values.len; ++i) {')
6009+ g.writeln('\t\tif (m.key_values.deletes != 0 && m.key_values.all_deleted != 0 && m.key_values.all_deleted[i] != 0) continue;')
6010+ g.writeln('\t\tif (!first) out = string__plus(out, v3_c_lit(", ", 2));')
6011+ g.writeln('\t\tvoid* key = (void*)(m.key_values.keys + i * m.key_values.key_bytes);')
6012+ g.writeln('\t\tvoid* val = (void*)(m.key_values.values + i * m.key_values.value_bytes);')
6013+ g.writeln('\t\tout = string__plus(out, v3_map_str_piece(key, key_kind, m.key_values.key_bytes, 0));')
6014+ g.writeln('\t\tout = string__plus(out, v3_c_lit(": ", 2));')
6015+ g.writeln('\t\tout = string__plus(out, v3_map_str_piece(val, val_kind, m.value_bytes, val_fixed_len));')
6016+ g.writeln('\t\tfirst = false;')
6017+ g.writeln('\t}')
6018+ g.writeln('\treturn string__plus(out, v3_c_lit("}", 1));')
6019+ g.writeln('}')
55806020 g.writeln('static inline int array_index_int(Array a, int val) { for (int i = 0; i < a.len; i++) if (((int*)a.data)[i] == val) return i; return -1; }')
6021+ g.writeln('static inline int array_last_index_int(Array a, int val) { for (int i = a.len - 1; i >= 0; i--) if (((int*)a.data)[i] == val) return i; return -1; }')
55816022 g.writeln('static inline bool array_contains_int(Array a, int val) { return array_index_int(a, val) >= 0; }')
55826023 g.writeln('static inline int array_index_u8(Array a, u8 val) { for (int i = 0; i < a.len; i++) if (((u8*)a.data)[i] == val) return i; return -1; }')
6024+ g.writeln('static inline int array_last_index_u8(Array a, u8 val) { for (int i = a.len - 1; i >= 0; i--) if (((u8*)a.data)[i] == val) return i; return -1; }')
55836025 g.writeln('static inline bool array_contains_u8(Array a, u8 val) { return array_index_u8(a, val) >= 0; }')
55846026 g.writeln('static inline int array_index_string(Array a, string val) { string* data = (string*)a.data; for (int i = 0; i < a.len; i++) if (data[i].len == val.len && memcmp(data[i].str, val.str, val.len) == 0) return i; return -1; }')
6027+ g.writeln('static inline int array_last_index_string(Array a, string val) { string* data = (string*)a.data; for (int i = a.len - 1; i >= 0; i--) if (data[i].len == val.len && memcmp(data[i].str, val.str, val.len) == 0) return i; return -1; }')
55856028 g.writeln('static inline bool array_contains_string(Array a, string val) { return array_index_string(a, val) >= 0; }')
6029+ g.writeln('static inline int array_last_index_raw(Array a, const void* val) { for (int i = a.len - 1; i >= 0; i--) if (memcmp((u8*)a.data + (size_t)i * (size_t)a.element_size, val, (size_t)a.element_size) == 0) return i; return -1; }')
55866030 g.writeln('static inline bool array_eq_raw(Array a, Array b, int elem_size) { return a.len == b.len && (a.len == 0 || memcmp(a.data, b.data, (size_t)a.len * elem_size) == 0); }')
55876031 g.writeln('static inline bool array_eq_string(Array a, Array b) { if (a.len != b.len) return false; string* ad = (string*)a.data; string* bd = (string*)b.data; for (int i = 0; i < a.len; i++) if (ad[i].len != bd[i].len || memcmp(ad[i].str, bd[i].str, ad[i].len) != 0) return false; return true; }')
6032+ g.writeln('static inline bool array_eq_array(Array a, Array b, int depth) { if (a.len != b.len || a.element_size != b.element_size) return false; if (depth <= 1 || a.element_size != sizeof(Array)) { if (a.element_size == sizeof(string)) return array_eq_string(a, b); return array_eq_raw(a, b, a.element_size); } Array* ad = (Array*)a.data; Array* bd = (Array*)b.data; for (int i = 0; i < a.len; i++) { if (!array_eq_array(ad[i], bd[i], depth - 1)) return false; } return true; }')
6033+ g.writeln('static inline bool v3_map_map_eq(map a, map b);')
6034+ g.writeln('static inline bool v3_map_value_eq(void* a, void* b, int value_bytes) { if (value_bytes == sizeof(string)) { string sa = *(string*)a; string sb = *(string*)b; return sa.len == sb.len && (sa.len == 0 || memcmp(sa.str, sb.str, sa.len) == 0); } if (value_bytes == sizeof(map)) { return v3_map_map_eq(*(map*)a, *(map*)b); } if (value_bytes == sizeof(string) + sizeof(map)) { string sa = *(string*)a; string sb = *(string*)b; if (!(sa.len == sb.len && (sa.len == 0 || memcmp(sa.str, sb.str, sa.len) == 0))) return false; map ma = *(map*)((u8*)a + sizeof(string)); map mb = *(map*)((u8*)b + sizeof(string)); return v3_map_map_eq(ma, mb); } if (value_bytes == sizeof(Array)) { Array aa = *(Array*)a; Array bb = *(Array*)b; if (aa.element_size != bb.element_size) return false; if (aa.element_size == sizeof(string)) return array_eq_string(aa, bb); if (aa.element_size == sizeof(Array)) return array_eq_array(aa, bb, 8); return array_eq_raw(aa, bb, aa.element_size); } return memcmp(a, b, value_bytes) == 0; }')
6035+ g.writeln('static inline bool v3_map_map_eq(map a, map b) { if (a.len != b.len) return false; for (int i = 0; i < a.key_values.len; ++i) { if (a.key_values.deletes != 0 && a.key_values.all_deleted != 0 && a.key_values.all_deleted[i] != 0) continue; void* ak = (void*)(a.key_values.keys + i * a.key_values.key_bytes); void* av = (void*)(a.key_values.values + i * a.key_values.value_bytes); bool found = false; for (int j = 0; j < b.key_values.len; ++j) { if (b.key_values.deletes != 0 && b.key_values.all_deleted != 0 && b.key_values.all_deleted[j] != 0) continue; void* bk = (void*)(b.key_values.keys + j * b.key_values.key_bytes); if (a.key_eq_fn(ak, bk)) { void* bv = (void*)(b.key_values.values + j * b.key_values.value_bytes); if (!v3_map_value_eq(av, bv, a.value_bytes)) return false; found = true; break; } } if (!found) return false; } return true; }')
55886036 g.writeln('static inline bool fixed_array_contains_string(const string* a, int len, string val) { for (int i = 0; i < len; i++) if (a[i].len == val.len && memcmp(a[i].str, val.str, val.len) == 0) return true; return false; }')
55896037 g.writeln('static inline bool fixed_array_contains_u8(const u8* a, int len, u8 val) { for (int i = 0; i < len; i++) if (a[i] == val) return true; return false; }')
55906038 g.writeln('static inline bool fixed_array_contains_int(const int* a, int len, int val) { for (int i = 0; i < len; i++) if (a[i] == val) return true; return false; }')
@@ -5598,6 +6046,23 @@ fn (mut g FlatGen) builtin_abi_decls() {
55986046 g.writeln('')
55996047}
56006048
6049+fn (mut g FlatGen) filelock_compat_decls() {
6050+ if !g.libc_compat_fns['filelock'] {
6051+ return
6052+ }
6053+ g.writeln('#ifndef V_OS_FILELOCK_HELPERS_H')
6054+ g.writeln('#ifdef _WIN32')
6055+ g.writeln('BOOL LockFileEx(HANDLE handle, DWORD flags, DWORD reserved, DWORD low, DWORD high, OVERLAPPED* overlap);')
6056+ g.writeln('BOOL UnlockFileEx(HANDLE handle, DWORD reserved, DWORD low, DWORD high, OVERLAPPED* overlap);')
6057+ g.writeln('static inline int v_filelock_lock(HANDLE handle, int exclusive, int immediate, u64 start, u64 len) { OVERLAPPED overlap; memset(&overlap, 0, sizeof(overlap)); overlap.Offset = (DWORD)(start & 0xffffffffULL); overlap.OffsetHigh = (DWORD)(start >> 32); DWORD flags = immediate ? LOCKFILE_FAIL_IMMEDIATELY : 0; if (exclusive) { flags |= LOCKFILE_EXCLUSIVE_LOCK; } DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL); DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32); return LockFileEx(handle, flags, 0, low, high, &overlap) ? 0 : -1; }')
6058+ g.writeln('static inline int v_filelock_unlock(HANDLE handle, u64 start, u64 len) { OVERLAPPED overlap; memset(&overlap, 0, sizeof(overlap)); overlap.Offset = (DWORD)(start & 0xffffffffULL); overlap.OffsetHigh = (DWORD)(start >> 32); DWORD low = len == 0 ? MAXDWORD : (DWORD)(len & 0xffffffffULL); DWORD high = len == 0 ? MAXDWORD : (DWORD)(len >> 32); return UnlockFileEx(handle, 0, low, high, &overlap) ? 0 : -1; }')
6059+ g.writeln('#else')
6060+ g.writeln('static inline int v_filelock_lock(int fd, int exclusive, int immediate, u64 start, u64 len) { struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = exclusive ? F_WRLCK : F_RDLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)start; fl.l_len = len == 0 ? 0 : (off_t)len; return fcntl(fd, immediate ? F_SETLK : F_SETLKW, &fl); }')
6061+ g.writeln('static inline int v_filelock_unlock(int fd, u64 start, u64 len) { struct flock fl; memset(&fl, 0, sizeof(fl)); fl.l_type = F_UNLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)start; fl.l_len = len == 0 ? 0 : (off_t)len; return fcntl(fd, F_SETLK, &fl); }')
6062+ g.writeln('#endif')
6063+ g.writeln('#endif')
6064+}
6065+
56016066fn (mut g FlatGen) collect_fixed_array_typedefs_needed() map[string]FixedArrayTypedefInfo {
56026067 mut needed := map[string]FixedArrayTypedefInfo{}
56036068 old_module := g.tc.cur_module
@@ -5636,6 +6101,16 @@ fn (mut g FlatGen) collect_fixed_array_typedefs_needed() map[string]FixedArrayTy
56366101 g.collect_fixed_array_typedef(typ, mut needed)
56376102 }
56386103 g.tc.cur_module = old_module
6104+ for node in g.a.nodes {
6105+ g.collect_fixed_array_typedef_text(node.typ, mut needed)
6106+ match node.kind {
6107+ .array_init, .array_literal, .cast_expr, .sizeof_expr, .typeof_expr {
6108+ g.collect_fixed_array_typedef_text(node.value, mut needed)
6109+ }
6110+ else {}
6111+ }
6112+ }
6113+ g.tc.cur_module = old_module
56396114 return needed
56406115}
56416116
@@ -5927,6 +6402,62 @@ fn (mut g FlatGen) collect_fixed_array_typedef(typ types.Type, mut needed map[st
59276402 }
59286403}
59296404
6405+fn (mut g FlatGen) collect_fixed_array_typedef_text(type_text string, mut needed map[string]FixedArrayTypedefInfo) {
6406+ clean := type_text.trim_space()
6407+ if clean.len == 0 {
6408+ return
6409+ }
6410+ typ := g.tc.parse_type(clean)
6411+ if fixed_array_typedef_has_non_decimal_len(typ) {
6412+ return
6413+ }
6414+ g.collect_fixed_array_typedef(typ, mut needed)
6415+}
6416+
6417+fn fixed_array_typedef_has_non_decimal_len(typ types.Type) bool {
6418+ if typ is types.ArrayFixed {
6419+ if typ.len_expr.len > 0 {
6420+ return true
6421+ }
6422+ return fixed_array_typedef_has_non_decimal_len(typ.elem_type)
6423+ }
6424+ if typ is types.Pointer {
6425+ return fixed_array_typedef_has_non_decimal_len(typ.base_type)
6426+ }
6427+ if typ is types.Alias {
6428+ return fixed_array_typedef_has_non_decimal_len(typ.base_type)
6429+ }
6430+ if typ is types.OptionType {
6431+ return fixed_array_typedef_has_non_decimal_len(typ.base_type)
6432+ }
6433+ if typ is types.ResultType {
6434+ return fixed_array_typedef_has_non_decimal_len(typ.base_type)
6435+ }
6436+ if typ is types.Array {
6437+ return fixed_array_typedef_has_non_decimal_len(typ.elem_type)
6438+ }
6439+ if typ is types.Map {
6440+ return fixed_array_typedef_has_non_decimal_len(typ.key_type)
6441+ || fixed_array_typedef_has_non_decimal_len(typ.value_type)
6442+ }
6443+ if typ is types.FnType {
6444+ for param in typ.params {
6445+ if fixed_array_typedef_has_non_decimal_len(param) {
6446+ return true
6447+ }
6448+ }
6449+ return fixed_array_typedef_has_non_decimal_len(typ.return_type)
6450+ }
6451+ if typ is types.MultiReturn {
6452+ for item in typ.types {
6453+ if fixed_array_typedef_has_non_decimal_len(item) {
6454+ return true
6455+ }
6456+ }
6457+ }
6458+ return false
6459+}
6460+
59306461fn (mut g FlatGen) emit_fixed_array_typedef(name string, info FixedArrayTypedefInfo, needed map[string]FixedArrayTypedefInfo, mut emitted map[string]bool) {
59316462 if emitted[name] {
59326463 return
@@ -5961,10 +6492,13 @@ fn (mut g FlatGen) global_decls() {
59616492 g.writeln('${c_elem} ${c_name(name)}${dims}${init};')
59626493 continue
59636494 }
5964- ct := g.tc.c_type(typ)
6495+ mut ct := g.tc.c_type(typ)
59656496 if ct == 'void' {
59666497 continue
59676498 }
6499+ if ct.starts_with('fn_ptr:') {
6500+ ct = g.resolve_fn_ptr_type(ct)
6501+ }
59686502 if name.starts_with('C.') {
59696503 continue
59706504 }
@@ -6031,7 +6565,7 @@ fn (mut g FlatGen) emit_global_inits() {
60316565 continue
60326566 }
60336567 target := c_name(qname)
6034- g.runtime_inits << '\t${target} = ${expr_str};'
6568+ g.queue_runtime_init('\t${target} = ${expr_str};')
60356569 if typ := g.global_types[qname] {
60366570 if typ is types.Map {
60376571 g.queue_map_literal_sets(target, val_id, typ)
@@ -6120,7 +6654,16 @@ fn (mut g FlatGen) emit_const(name string, val_id flat.NodeId) {
61206654 g.tc.cur_module = old_module
61216655 return
61226656 }
6123- expr_str := if g.is_const_expr(val_id) {
6657+ v_type := if val_node.kind == .offsetof_expr {
6658+ types.Type(types.usize_)
6659+ } else {
6660+ g.tc.resolve_type(val_id)
6661+ }
6662+ ct := g.tc.c_type(v_type)
6663+ qname := g.const_ident_c_name(name)
6664+ expr_str := if v_type is types.Array && val_node.kind == .array_literal {
6665+ g.expr_to_string_with_expected_type(val_id, v_type)
6666+ } else if g.is_const_expr(val_id) {
61246667 g.const_expr_to_string(val_id, []string{})
61256668 } else {
61266669 g.expr_to_string(val_id)
@@ -6129,14 +6672,10 @@ fn (mut g FlatGen) emit_const(name string, val_id flat.NodeId) {
61296672 g.tc.cur_module = old_module
61306673 return
61316674 }
6132- v_type := if val_node.kind == .offsetof_expr {
6133- types.Type(types.usize_)
6134- } else {
6135- g.tc.resolve_type(val_id)
6136- }
6137- ct := g.tc.c_type(v_type)
6138- qname := g.const_ident_c_name(name)
61396675 mut is_static_const := g.is_const_expr(val_id) && !g.const_expr_needs_runtime_storage(expr_str)
6676+ if v_type is types.Array {
6677+ is_static_const = false
6678+ }
61406679 if v_type is types.ArrayFixed && v_type.elem_type is types.ArrayFixed {
61416680 is_static_const = false
61426681 }
@@ -6144,15 +6683,15 @@ fn (mut g FlatGen) emit_const(name string, val_id flat.NodeId) {
61446683 if v_type is types.ArrayFixed {
61456684 c_elem, dims := g.fixed_array_decl_parts(v_type)
61466685 g.writeln('${c_elem} ${qname}${dims};')
6147- g.queue_fixed_array_runtime_init(qname, val_id, v_type)
6686+ g.queue_const_fixed_array_runtime_init(qname, val_id, v_type)
61486687 } else if ct != 'void' {
61496688 g.writeln('${ct} ${qname};')
61506689 // The initializer is not a compile-time constant (e.g. `os.args =
61516690 // arguments()`), so it cannot be a C static initializer. Run it at startup
61526691 // in _vinit; otherwise the const stays zero/empty and first use is wrong.
6153- g.runtime_inits << '\t${qname} = ${expr_str};'
6692+ g.queue_const_runtime_init('\t${qname} = ${expr_str};')
61546693 if v_type is types.Map {
6155- g.queue_map_literal_sets(qname, val_id, v_type)
6694+ g.queue_const_map_literal_sets(qname, val_id, v_type)
61566695 }
61576696 }
61586697 g.tc.cur_module = old_module
@@ -6215,7 +6754,24 @@ fn (mut g FlatGen) queue_map_literal_sets(target string, val_id flat.NodeId, map
62156754 for i := 0; i + 1 < node.children_count; i += 2 {
62166755 key := g.expr_to_string_with_expected_type(g.a.child(&node, i), map_type.key_type)
62176756 val := g.expr_to_string_with_expected_type(g.a.child(&node, i + 1), map_type.value_type)
6218- g.runtime_inits << '\tmap__set(&${target}, &(${c_key}[]){${key}}, &(${c_val}[]){${val}});'
6757+ g.queue_runtime_init('\tmap__set(&${target}, &(${c_key}[]){${key}}, &(${c_val}[]){${val}});')
6758+ }
6759+}
6760+
6761+fn (mut g FlatGen) queue_const_map_literal_sets(target string, val_id flat.NodeId, map_type types.Map) {
6762+ if int(val_id) < 0 || int(val_id) >= g.a.nodes.len {
6763+ return
6764+ }
6765+ node := g.a.nodes[int(val_id)]
6766+ if node.kind != .map_init {
6767+ return
6768+ }
6769+ c_key := g.tc.c_type(map_type.key_type)
6770+ c_val := g.tc.c_type(map_type.value_type)
6771+ for i := 0; i + 1 < node.children_count; i += 2 {
6772+ key := g.expr_to_string_with_expected_type(g.a.child(&node, i), map_type.key_type)
6773+ val := g.expr_to_string_with_expected_type(g.a.child(&node, i + 1), map_type.value_type)
6774+ g.queue_const_runtime_init('\tmap__set(&${target}, &(${c_key}[]){${key}}, &(${c_val}[]){${val}});')
62196775 }
62206776}
62216777
@@ -6224,7 +6780,16 @@ fn (mut g FlatGen) queue_fixed_array_runtime_init(target string, val_id flat.Nod
62246780 if expr.trim_space().len == 0 {
62256781 return false
62266782 }
6227- g.runtime_inits << '\tmemmove(${target}, ${expr}, sizeof(${target}));'
6783+ g.queue_runtime_init('\tmemmove(${target}, ${expr}, sizeof(${target}));')
6784+ return true
6785+}
6786+
6787+fn (mut g FlatGen) queue_const_fixed_array_runtime_init(target string, val_id flat.NodeId, fixed types.ArrayFixed) bool {
6788+ expr := g.fixed_array_compound_literal_expr(val_id, fixed)
6789+ if expr.trim_space().len == 0 {
6790+ return false
6791+ }
6792+ g.queue_const_runtime_init('\tmemmove(${target}, ${expr}, sizeof(${target}));')
62286793 return true
62296794}
62306795
@@ -6295,6 +6860,7 @@ fn (mut g FlatGen) precompute_consts() string {
62956860 }
62966861 names << name
62976862 }
6863+ names = g.ordered_const_init_names(names)
62986864 for name in names {
62996865 val_id := g.const_vals[name] or { continue }
63006866 if g.is_const_alias_name(name) {
@@ -6361,6 +6927,52 @@ fn (mut g FlatGen) precompute_consts() string {
63616927 return result
63626928}
63636929
6930+fn (g &FlatGen) ordered_const_init_names(names []string) []string {
6931+ mut names_by_module := map[string][]string{}
6932+ mut module_order := []string{}
6933+ for name in names {
6934+ mod := g.const_modules[name] or { '' }
6935+ if mod !in names_by_module {
6936+ names_by_module[mod] = []string{}
6937+ module_order << mod
6938+ }
6939+ names_by_module[mod] << name
6940+ }
6941+ mut result := []string{}
6942+ mut visiting := map[string]bool{}
6943+ mut visited := map[string]bool{}
6944+ for mod in module_order {
6945+ g.visit_const_init_module(mod, names_by_module, mut visiting, mut visited, mut result)
6946+ }
6947+ return result
6948+}
6949+
6950+fn (g &FlatGen) visit_const_init_module(mod string, names_by_module map[string][]string, mut visiting map[string]bool, mut visited map[string]bool, mut result []string) {
6951+ if mod in visited || mod in visiting {
6952+ return
6953+ }
6954+ visiting[mod] = true
6955+ for dep in g.module_imports[mod] or { []string{} } {
6956+ dep_module := startup_module_key(dep)
6957+ if dep_module in names_by_module {
6958+ g.visit_const_init_module(dep_module, names_by_module, mut visiting, mut visited, mut
6959+ result)
6960+ }
6961+ }
6962+ visiting.delete(mod)
6963+ visited[mod] = true
6964+ if module_names := names_by_module[mod] {
6965+ result << module_names
6966+ }
6967+}
6968+
6969+fn startup_module_key(mod string) string {
6970+ if mod.contains('.') {
6971+ return mod.all_after_last('.')
6972+ }
6973+ return mod
6974+}
6975+
63646976fn (g &FlatGen) is_const_expr(id flat.NodeId) bool {
63656977 if int(id) < 0 || int(id) >= g.a.nodes.len {
63666978 return false
@@ -26,6 +26,7 @@ struct TopLevelStmt {
2626
2727// gen_fns emits fns output for c.
2828fn (mut g FlatGen) gen_fns() {
29+ preferred_fns := g.preferred_c_backend_fn_nodes()
2930 mut cur_module := ''
3031 mut cur_file := ''
3132 for i in 0 .. g.a.nodes.len {
@@ -46,10 +47,15 @@ fn (mut g FlatGen) gen_fns() {
4647 }
4748
4849 if kind_id == 61 {
49- if !g.should_emit_fn_node_in_module(node, i, cur_module) {
50+ if !g.should_emit_fn_node_in_module(node, i, cur_module, cur_file) {
5051 continue
5152 }
5253 qfn := g.fn_c_name_in_module(cur_module, node.value)
54+ if preferred_idx := preferred_fns[qfn] {
55+ if preferred_idx != i {
56+ continue
57+ }
58+ }
5359 if g.emitted_fn_contains(qfn) {
5460 continue
5561 }
@@ -61,6 +67,43 @@ fn (mut g FlatGen) gen_fns() {
6167 }
6268}
6369
70+fn (g &FlatGen) preferred_c_backend_fn_nodes() map[string]int {
71+ mut preferred := map[string]int{}
72+ mut ranks := map[string]int{}
73+ mut cur_module := ''
74+ mut cur_file := ''
75+ for i in 0 .. g.a.nodes.len {
76+ node := g.a.nodes[i]
77+ kind_id := node_kind_id(node)
78+ if kind_id == 77 {
79+ cur_file = node.value
80+ cur_module = ''
81+ continue
82+ }
83+ if kind_id == 73 {
84+ cur_module = node.value
85+ continue
86+ }
87+ if kind_id != 61 {
88+ continue
89+ }
90+ qfn := qualified_fn_name_in_module(cur_module, node.value)
91+ rank := c_backend_fn_file_rank(cur_file)
92+ if qfn !in preferred || rank > ranks[qfn] {
93+ preferred[qfn] = i
94+ ranks[qfn] = rank
95+ }
96+ }
97+ return preferred
98+}
99+
100+fn c_backend_fn_file_rank(file string) int {
101+ if file.ends_with('.c.v') {
102+ return 1
103+ }
104+ return 0
105+}
106+
64107fn (mut g FlatGen) gen_synthetic_main_after_fns() {
65108 if g.test_files.len > 0 {
66109 g.gen_test_main()
@@ -162,11 +205,11 @@ fn (g &FlatGen) cgen_is_top_level_stmt(id flat.NodeId) bool {
162205
163206// should_emit_fn_node reports whether should emit fn node applies in c.
164207fn (mut g FlatGen) should_emit_fn_node(node flat.Node, node_index int) bool {
165- return g.should_emit_fn_node_in_module(node, node_index, g.tc.cur_module)
208+ return g.should_emit_fn_node_in_module(node, node_index, g.tc.cur_module, g.tc.cur_file)
166209}
167210
168211// should_emit_fn_node_in_module reports whether should emit fn node in module applies in c.
169-fn (mut g FlatGen) should_emit_fn_node_in_module(node flat.Node, node_index int, module_name string) bool {
212+fn (mut g FlatGen) should_emit_fn_node_in_module(node flat.Node, node_index int, module_name string, file_name string) bool {
170213 _ = node_index
171214 qfn := qualified_fn_name_in_module(module_name, node.value)
172215 if g.should_rename_user_main_for_tests(module_name, node.value) {
@@ -183,12 +226,24 @@ fn (mut g FlatGen) should_emit_fn_node_in_module(node flat.Node, node_index int,
183226 if node.value.starts_with('__anon_fn_') || qfn.contains('__anon_fn_') {
184227 return true
185228 }
229+ if node.generic_params.len == 0 && cgen_is_operator_overload_fn(node.value)
230+ && g.test_files[file_name] {
231+ return true
232+ }
186233 if g.has_used_fn_filter() && !g.used_fn_contains_in_module(node.value, module_name) {
187234 return false
188235 }
189236 return true
190237}
191238
239+fn cgen_is_operator_overload_fn(name string) bool {
240+ if !name.contains('.') {
241+ return false
242+ }
243+ method := name.all_after_last('.')
244+ return method in ['+', '-', '*', '/', '%', '==', '!=', '<', '>', '<=', '>=']
245+}
246+
192247fn is_generated_fn_after_markused(name string) bool {
193248 return name.starts_with('__anon_fn_')
194249}
@@ -217,6 +272,9 @@ fn (g &FlatGen) used_fn_contains_in_module(name string, module_name string) bool
217272 if g.used_fn_contains(dfn) || g.used_fn_contains(qfn) {
218273 return true
219274 }
275+ if g.used_fn_contains(c_name(dfn)) || g.used_fn_contains(c_name(qfn)) {
276+ return true
277+ }
220278 if module_name.len == 0 || module_name == 'main' || module_name == 'builtin' {
221279 cfn := c_name(name)
222280 return g.used_fn_contains(name) || g.used_fn_contains(cfn)
@@ -339,6 +397,12 @@ fn (mut g FlatGen) direct_call_name(name string) string {
339397 if name == 'bool_str' {
340398 return 'bool__str'
341399 }
400+ if name == 'char.vstring' {
401+ return 'charptr__vstring'
402+ }
403+ if name == 'char.vstring_with_len' {
404+ return 'charptr__vstring_with_len'
405+ }
342406 return c_name(name)
343407}
344408
@@ -351,6 +415,9 @@ fn (mut g FlatGen) direct_call_name_for_call(id flat.NodeId, name string) string
351415 }
352416 return c_name(name)
353417 }
418+ if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, name, -1) {
419+ return c_name(specialized)
420+ }
354421 return g.direct_call_name(name)
355422}
356423
@@ -449,11 +516,75 @@ fn (mut g FlatGen) gen_fn(node flat.Node) {
449516}
450517
451518fn (mut g FlatGen) write_method_c_name(id flat.NodeId, node flat.Node, method_name string) {
452- _ = id
453- _ = node
519+ if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id, method_name,
520+ int(node.children_count))
521+ {
522+ g.write(c_name(specialized))
523+ return
524+ }
454525 g.write(c_name(method_name))
455526}
456527
528+fn (g &FlatGen) specialized_generic_method_name_for_call_with_arg_count(id flat.NodeId, method_name string, arg_count int) ?string {
529+ if !method_name.contains('.') {
530+ return none
531+ }
532+ receiver := method_name.all_before_last('.')
533+ method := method_name.all_after_last('.')
534+ if receiver.len == 0 || method.len == 0 {
535+ return none
536+ }
537+ call_ret := g.call_default_return_type(id)
538+ if type_arg := generic_method_type_arg_from_return(call_ret) {
539+ for arg in [type_arg, type_arg.all_after_last('.')] {
540+ for candidate in ['${receiver}[${arg}].${method}',
541+ '${receiver.all_after_last('.')}[${arg}].${method}'] {
542+ if candidate in g.tc.fn_param_types || candidate in g.tc.fn_ret_types {
543+ return candidate
544+ }
545+ }
546+ }
547+ }
548+ for allow_short_receiver in [false, true] {
549+ for candidate, ret in g.tc.fn_ret_types {
550+ if !candidate.contains('[') || candidate.all_after_last('.') != method {
551+ continue
552+ }
553+ candidate_receiver := candidate.all_before_last('.')
554+ base_receiver := candidate_receiver.all_before('[')
555+ receiver_matches := base_receiver == receiver || (allow_short_receiver
556+ && base_receiver.all_after_last('.') == receiver.all_after_last('.'))
557+ if !receiver_matches {
558+ continue
559+ }
560+ params := g.tc.fn_param_types[candidate] or { []types.Type{} }
561+ if arg_count >= 0 && params.len != arg_count {
562+ continue
563+ }
564+ if g.type_names_match(call_ret, ret) || call_ret.name() == ret.name() {
565+ return candidate
566+ }
567+ }
568+ }
569+ return none
570+}
571+
572+fn generic_method_type_arg_from_return(ret types.Type) ?string {
573+ if ret is types.ResultType {
574+ name := ret.base_type.name()
575+ if name.len > 0 && name != 'void' {
576+ return name
577+ }
578+ }
579+ if ret is types.OptionType {
580+ name := ret.base_type.name()
581+ if name.len > 0 && name != 'void' {
582+ return name
583+ }
584+ }
585+ return none
586+}
587+
457588// static_method_fn_name resolves `Type.method(...)` static calls where `Type` is a
458589// named type, struct, enum, sum type or type alias (e.g. `SimdFloat4.new` for
459590// `type SimdFloat4 = vec.Vec4[f32]`). Returns the fn key, or none if `type_ident`
@@ -510,6 +641,74 @@ fn c_string_literal_pointer_arg(arg_node flat.Node, expected types.Type) bool {
510641 return false
511642}
512643
644+fn (mut g FlatGen) gen_pointer_builtin_method_call(node flat.Node, fn_node &flat.Node, base_type types.Type) bool {
645+ receiver := pointer_builtin_receiver_name_for_c(base_type)
646+ if receiver.len == 0 {
647+ return false
648+ }
649+ method := fn_node.value
650+ if receiver in ['charptr', 'byteptr'] && method in ['vstring', 'vstring_with_len'] {
651+ g.write(c_name('${receiver}.${method}'))
652+ g.write('(')
653+ g.gen_expr(g.a.child(fn_node, 0))
654+ for i in 1 .. node.children_count {
655+ g.write(', ')
656+ g.gen_expr(g.a.child(&node, i))
657+ }
658+ g.write(')')
659+ return true
660+ }
661+ if receiver in ['byteptr', 'voidptr'] && method == 'vbytes' {
662+ g.write(c_name('${receiver}.${method}'))
663+ g.write('(')
664+ g.gen_expr(g.a.child(fn_node, 0))
665+ for i in 1 .. node.children_count {
666+ g.write(', ')
667+ g.gen_expr(g.a.child(&node, i))
668+ }
669+ g.write(')')
670+ return true
671+ }
672+ return false
673+}
674+
675+fn pointer_builtin_receiver_name_for_c(typ types.Type) string {
676+ if typ is types.Alias {
677+ if typ.name in ['charptr', 'byteptr', 'voidptr'] {
678+ return typ.name
679+ }
680+ return pointer_builtin_receiver_name_for_c(typ.base_type)
681+ }
682+ if typ is types.Pointer {
683+ base := typ.base_type
684+ if base is types.Alias {
685+ if base.name == 'byte' {
686+ return 'byteptr'
687+ }
688+ return pointer_builtin_receiver_name_for_c(base)
689+ }
690+ if base is types.Char {
691+ return 'charptr'
692+ }
693+ if base is types.Void {
694+ return 'voidptr'
695+ }
696+ if base is types.Primitive && base.name() == 'u8' {
697+ return 'byteptr'
698+ }
699+ }
700+ if typ is types.Char {
701+ return 'charptr'
702+ }
703+ if typ is types.Void {
704+ return 'voidptr'
705+ }
706+ if typ is types.Primitive && typ.name() == 'u8' {
707+ return 'byteptr'
708+ }
709+ return ''
710+}
711+
513712fn (mut g FlatGen) gen_special_c_callback_arg(fn_name string, arg_idx int, arg_id flat.NodeId, expected_param types.Type) bool {
514713 clean_name := fn_name.trim_string_left('C.').all_after_last('.')
515714 if clean_name == 'mbedtls_ssl_conf_sni' && arg_idx == 1 {
@@ -777,7 +976,7 @@ fn (mut g FlatGen) gen_spawn_expr(node flat.Node) {
777976 } else if fn_node.kind == .selector && fn_node.children_count > 0 {
778977 base_id := g.a.child(fn_node, 0)
779978 base_type := g.receiver_base_type(base_id)
780- clean_type := types.unwrap_pointer(base_type)
979+ clean_type := concrete_receiver_type(base_type)
781980 method_name := g.resolved_method_name_for_spawn(clean_type, fn_node.value)
782981 if method_name.len > 0 {
783982 param_types := g.param_types_for(method_name, fn_node.value)
@@ -954,9 +1153,11 @@ fn (mut g FlatGen) gen_fn_in_module(node flat.Node, module_name string) {
9541153 old_param_names := g.cur_param_names.clone()
9551154 old_param_type_values := g.cur_param_type_values.clone()
9561155 old_param_types := g.cur_param_types.clone()
1156+ old_mut_params := g.cur_mut_params.clone()
9571157 g.cur_param_names = []string{}
9581158 g.cur_param_type_values = []types.Type{}
9591159 g.cur_param_types = map[string]types.Type{}
1160+ g.cur_mut_params = map[string]bool{}
9601161 for i in 0 .. node.children_count {
9611162 param_id := g.a.child(&node, i)
9621163 p := g.a.node(param_id)
@@ -965,6 +1166,9 @@ fn (mut g FlatGen) gen_fn_in_module(node flat.Node, module_name string) {
9651166 g.cur_param_names << p.value
9661167 g.cur_param_type_values << param_type
9671168 g.cur_param_types[p.value] = param_type
1169+ if p.op == .amp {
1170+ g.cur_mut_params[p.value] = true
1171+ }
9681172 g.tc.cur_scope.insert(p.value, param_type)
9691173 }
9701174 }
@@ -979,7 +1183,8 @@ fn (mut g FlatGen) gen_fn_in_module(node flat.Node, module_name string) {
9791183 g.writeln('\tg_main_argv = argv;')
9801184 }
9811185 g.gen_compiler_vexe_env_setup()
982- if g.runtime_inits.len > 0 || g.module_init_fns.len > 0 || g.global_inits.len > 0 {
1186+ if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0
1187+ || g.global_inits.len > 0 {
9831188 g.writeln('\t_vinit();')
9841189 }
9851190 } else {
@@ -1019,6 +1224,7 @@ fn (mut g FlatGen) gen_fn_in_module(node flat.Node, module_name string) {
10191224 g.cur_param_names = old_param_names.clone()
10201225 g.cur_param_type_values = old_param_type_values.clone()
10211226 g.cur_param_types = old_param_types.clone()
1227+ g.cur_mut_params = old_mut_params.clone()
10221228 g.tc.pop_scope()
10231229}
10241230
@@ -1089,9 +1295,11 @@ fn (mut g FlatGen) gen_top_level_main(stmts []TopLevelStmt) {
10891295 old_param_names := g.cur_param_names.clone()
10901296 old_param_type_values := g.cur_param_type_values.clone()
10911297 old_param_types := g.cur_param_types.clone()
1298+ old_mut_params := g.cur_mut_params.clone()
10921299 g.cur_param_names = []string{}
10931300 g.cur_param_type_values = []types.Type{}
10941301 g.cur_param_types = map[string]types.Type{}
1302+ g.cur_mut_params = map[string]bool{}
10951303 mut fn_defer_ids := []flat.NodeId{}
10961304 for stmt in stmts {
10971305 g.collect_function_defer_ids_from(stmt.id, mut fn_defer_ids)
@@ -1103,7 +1311,8 @@ fn (mut g FlatGen) gen_top_level_main(stmts []TopLevelStmt) {
11031311 g.writeln('\tg_main_argv = argv;')
11041312 }
11051313 g.gen_compiler_vexe_env_setup()
1106- if g.runtime_inits.len > 0 || g.module_init_fns.len > 0 || g.global_inits.len > 0 {
1314+ if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0
1315+ || g.global_inits.len > 0 {
11071316 g.writeln('\t_vinit();')
11081317 }
11091318 g.indent++
@@ -1121,6 +1330,7 @@ fn (mut g FlatGen) gen_top_level_main(stmts []TopLevelStmt) {
11211330 g.cur_param_names = old_param_names.clone()
11221331 g.cur_param_type_values = old_param_type_values.clone()
11231332 g.cur_param_types = old_param_types.clone()
1333+ g.cur_mut_params = old_mut_params.clone()
11241334 g.cur_fn_name = old_fn_name
11251335 g.tc.cur_file = old_tc_file
11261336 g.tc.cur_module = old_tc_module
@@ -1153,7 +1363,8 @@ fn (mut g FlatGen) gen_test_main() {
11531363 g.writeln('\tg_main_argv = argv;')
11541364 }
11551365 g.gen_compiler_vexe_env_setup()
1156- if g.runtime_inits.len > 0 || g.module_init_fns.len > 0 || g.global_inits.len > 0 {
1366+ if g.const_runtime_inits.len > 0 || g.runtime_inits.len > 0 || g.module_init_fns.len > 0
1367+ || g.global_inits.len > 0 {
11571368 g.writeln('\t_vinit();')
11581369 }
11591370 g.indent++
@@ -1208,9 +1419,6 @@ fn (g &FlatGen) test_harness_fns() ([]TestHarnessFn, TestHarnessHooks) {
12081419 continue
12091420 }
12101421 module_name := g.test_file_module_name(file_node)
1211- if module_name.len > 0 && module_name != 'main' {
1212- continue
1213- }
12141422 mut decl_ids := []flat.NodeId{}
12151423 g.collect_test_harness_decl_ids(file_node, mut decl_ids)
12161424 for child_id in decl_ids {
@@ -1576,7 +1784,7 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
15761784 }
15771785 if fn_node.kind == .selector && fn_node.value == 'str' {
15781786 base_type := g.tc.resolve_type(g.a.child(fn_node, 0))
1579- clean_type := types.unwrap_pointer(base_type)
1787+ clean_type := concrete_receiver_type(base_type)
15801788 if clean_type is types.Enum {
15811789 if _ := g.enum_receiver_method_name(clean_type, fn_node.value) {
15821790 // Let normal method call generation handle custom enum str methods.
@@ -1594,6 +1802,40 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
15941802 }
15951803 }
15961804 match fn_name {
1805+ 'array_new' {
1806+ g.write('array_new(')
1807+ for i in 1 .. node.children_count {
1808+ if i > 1 {
1809+ g.write(', ')
1810+ }
1811+ arg_id := g.a.child(&node, i)
1812+ arg_node := g.a.nodes[int(arg_id)]
1813+ if i == 1 {
1814+ arg_expr := g.expr_to_string(arg_id)
1815+ if raw_sizeof := raw_sizeof_arg_value(arg_expr) {
1816+ if raw_sizeof_needs_normalization(raw_sizeof) {
1817+ g.write('sizeof(${g.sizeof_target(raw_sizeof)})')
1818+ } else {
1819+ g.write(arg_expr)
1820+ }
1821+ } else {
1822+ g.write(arg_expr)
1823+ }
1824+ } else if arg_node.kind == .sizeof_expr {
1825+ g.write('sizeof(${g.sizeof_target(arg_node.value)})')
1826+ } else if raw_sizeof := raw_sizeof_arg_value(arg_node.value) {
1827+ if raw_sizeof_needs_normalization(raw_sizeof) {
1828+ g.write('sizeof(${g.sizeof_target(raw_sizeof)})')
1829+ } else {
1830+ g.gen_expr(arg_id)
1831+ }
1832+ } else {
1833+ g.gen_expr(arg_id)
1834+ }
1835+ }
1836+ g.write(')')
1837+ return
1838+ }
15971839 'new_map' {
15981840 if node.typ.starts_with('map[') {
15991841 map_type := g.tc.parse_type(node.typ)
@@ -1718,7 +1960,7 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
17181960 }
17191961 {
17201962 base_type := g.tc.resolve_type(g.a.child(fn_node, 0))
1721- clean_type := types.unwrap_pointer(base_type)
1963+ clean_type := concrete_receiver_type(base_type)
17221964 if g.gen_interface_method_call(node, fn_node, base_type) {
17231965 return
17241966 }
@@ -1736,15 +1978,22 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
17361978 g.write(', ${len_expr})')
17371979 return
17381980 }
1739- if clean_type is types.Map && fn_node.value == 'clone' {
1740- g.write('map__clone(')
1741- g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type)
1742- g.write(')')
1981+ if g.gen_pointer_builtin_method_call(node, fn_node, base_type) {
17431982 return
17441983 }
1745- if clean_type is types.Map
1746- && fn_node.value in ['delete', 'clear', 'free', 'move', 'reserve'] {
1747- panic('map method `${fn_node.value}` should be lowered by v3 transform')
1984+ if clean_type is types.Map {
1985+ if fn_node.value == 'str' {
1986+ g.gen_map_str_expr(g.a.child(fn_node, 0), base_type)
1987+ return
1988+ } else if fn_node.value == 'clone' {
1989+ g.write('map__clone(')
1990+ g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type)
1991+ g.write(')')
1992+ return
1993+ } else if fn_node.value in ['keys', 'values', 'delete', 'clear', 'free',
1994+ 'move', 'reserve'] {
1995+ panic('map method `${fn_node.value}` should be lowered by v3 transform')
1996+ }
17481997 }
17491998 if clean_type is types.String {
17501999 method_name = 'string.${fn_node.value}'
@@ -1857,7 +2106,7 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
18572106 return
18582107 } else {
18592108 base_type := g.tc.resolve_type(g.a.child(fn_node, 0))
1860- clean_type := types.unwrap_pointer(base_type)
2109+ clean_type := concrete_receiver_type(base_type)
18612110 if g.gen_interface_method_call(node, fn_node, base_type) {
18622111 return
18632112 }
@@ -1875,15 +2124,22 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
18752124 g.write(', ${len_expr})')
18762125 return
18772126 }
1878- if clean_type is types.Map && fn_node.value == 'clone' {
1879- g.write('map__clone(')
1880- g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type)
1881- g.write(')')
2127+ if g.gen_pointer_builtin_method_call(node, fn_node, base_type) {
18822128 return
18832129 }
1884- if clean_type is types.Map
1885- && fn_node.value in ['delete', 'clear', 'free', 'move', 'reserve'] {
1886- panic('map method `${fn_node.value}` should be lowered by v3 transform')
2130+ if clean_type is types.Map {
2131+ if fn_node.value == 'str' {
2132+ g.gen_map_str_expr(g.a.child(fn_node, 0), base_type)
2133+ return
2134+ } else if fn_node.value == 'clone' {
2135+ g.write('map__clone(')
2136+ g.gen_map_ref_arg(g.a.child(fn_node, 0), base_type)
2137+ g.write(')')
2138+ return
2139+ } else if fn_node.value in ['keys', 'values', 'delete', 'clear', 'free',
2140+ 'move', 'reserve'] {
2141+ panic('map method `${fn_node.value}` should be lowered by v3 transform')
2142+ }
18872143 }
18882144 if clean_type is types.String {
18892145 method_name = 'string.${fn_node.value}'
@@ -2072,6 +2328,10 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
20722328 g.write(c_name(fn_ident.value))
20732329 } else if call_key in g.tc.fn_ret_types || call_key in g.tc.fn_param_types {
20742330 g.write(g.direct_call_name_for_call(id, call_key))
2331+ } else if specialized := g.specialized_generic_method_name_for_call_with_arg_count(id,
2332+ fn_ident.value, -1)
2333+ {
2334+ g.write(c_name(specialized))
20752335 } else {
20762336 g.write(c_name(fn_ident.value))
20772337 }
@@ -2211,6 +2471,10 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
22112471 if variadic_idx >= 0 && arg_idx == variadic_idx {
22122472 variadic_type := param_types[variadic_idx]
22132473 if variadic_type is types.Array {
2474+ if spread_id := g.spread_arg_child(arg_node) {
2475+ g.gen_expr_with_expected_type(spread_id, variadic_type)
2476+ continue
2477+ }
22142478 if num_call_args > param_types.len {
22152479 c_elem := g.tc.c_type(variadic_type.elem_type)
22162480 count := num_call_args - variadic_idx
@@ -2266,7 +2530,14 @@ fn (mut g FlatGen) gen_call(id flat.NodeId, node flat.Node) {
22662530 }
22672531 is_rvalue := arg_node.kind == .call
22682532 || (arg_node.kind == .index && arg_node.value == 'range')
2269- if needs_addr && is_rvalue {
2533+ || g.arg_is_const_ident(arg_node)
2534+ if needs_addr && g.arg_is_const_ident(arg_node) {
2535+ pt := param_types[arg_idx]
2536+ ct := g.tc.c_type(types.unwrap_pointer(pt))
2537+ g.write('(${ct}[]){')
2538+ g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(pt))
2539+ g.write('}')
2540+ } else if needs_addr && is_rvalue {
22702541 pt := param_types[arg_idx]
22712542 ct := g.tc.c_type(types.unwrap_pointer(pt))
22722543 g.write('({${ct} _t${g.tmp_count} = ')
@@ -2345,6 +2616,14 @@ fn (g &FlatGen) receiver_base_type(base_id flat.NodeId) types.Type {
23452616 return g.tc.resolve_type(base_id)
23462617}
23472618
2619+fn concrete_receiver_type(base_type types.Type) types.Type {
2620+ clean_type := types.unwrap_pointer(base_type)
2621+ if clean_type is types.Alias {
2622+ return clean_type.base_type
2623+ }
2624+ return clean_type
2625+}
2626+
23482627fn (g &FlatGen) receiver_storage_type(base_id flat.NodeId) ?types.Type {
23492628 if int(base_id) < 0 || int(base_id) >= g.a.nodes.len {
23502629 return none
@@ -2725,6 +3004,11 @@ fn (g &FlatGen) current_param_type(name string) ?types.Type {
27253004 return none
27263005}
27273006
3007+// current_param_is_mut returns true when a current param originated from `mut name T`.
3008+fn (g &FlatGen) current_param_is_mut(name string) bool {
3009+ return g.cur_mut_params[name] or { false }
3010+}
3011+
27283012// gen_enum_str_call emits an explicit `enum_val.str()` (with no user-defined `str`)
27293013// by routing to the compiler-synthesized `<Enum>__autostr`, so it matches `${enum}`
27303014// interpolation exactly — including `[flag]` enums' `Enum{.a | .b}` form, which the
@@ -3466,6 +3750,15 @@ fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) {
34663750 if g.gen_special_c_callback_arg(fn_name, arg_idx, arg_id, cb_param) {
34673751 continue
34683752 }
3753+ if variadic_idx >= 0 && arg_idx == variadic_idx {
3754+ variadic_type := param_types[variadic_idx]
3755+ if variadic_type is types.Array {
3756+ if spread_id := g.spread_arg_child(arg_node) {
3757+ g.gen_expr_with_expected_type(spread_id, variadic_type)
3758+ continue
3759+ }
3760+ }
3761+ }
34693762 if is_variadic && arg_idx == variadic_idx {
34703763 variadic_type := param_types[variadic_idx]
34713764 if variadic_type is types.Array {
@@ -3506,7 +3799,14 @@ fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) {
35063799 }
35073800 is_rvalue := arg_node.kind == .call
35083801 || (arg_node.kind == .index && arg_node.value == 'range')
3509- if needs_addr && is_rvalue {
3802+ || g.arg_is_const_ident(arg_node)
3803+ if needs_addr && g.arg_is_const_ident(arg_node) {
3804+ pt := param_types[arg_idx]
3805+ ct := g.tc.c_type(types.unwrap_pointer(pt))
3806+ g.write('(${ct}[]){')
3807+ g.gen_expr_with_expected_type(arg_id, types.unwrap_pointer(pt))
3808+ g.write('}')
3809+ } else if needs_addr && is_rvalue {
35103810 pt := param_types[arg_idx]
35113811 ct := g.tc.c_type(types.unwrap_pointer(pt))
35123812 g.write('({${ct} _t${g.tmp_count} = ')
@@ -3564,6 +3864,18 @@ fn (mut g FlatGen) gen_call_args(fn_name string, node flat.Node, start int) {
35643864 }
35653865}
35663866
3867+fn raw_sizeof_arg_value(value string) ?string {
3868+ clean := value.trim_space()
3869+ if !clean.starts_with('sizeof(') || !clean.ends_with(')') {
3870+ return none
3871+ }
3872+ return clean['sizeof('.len..clean.len - 1].trim_space()
3873+}
3874+
3875+fn raw_sizeof_needs_normalization(value string) bool {
3876+ return value.starts_with('fn_ptr:') || value.starts_with('Array_fixed_')
3877+}
3878+
35673879// is_flag_enum_method reports whether is flag enum method applies in c.
35683880fn (g &FlatGen) is_flag_enum_method(fn_node &flat.Node) bool {
35693881 if fn_node.kind != .selector {
@@ -3671,6 +3983,24 @@ fn (g &FlatGen) addressed_rvalue_arg(arg_node flat.Node) ?flat.NodeId {
36713983 return none
36723984}
36733985
3986+fn (g &FlatGen) spread_arg_child(arg_node flat.Node) ?flat.NodeId {
3987+ if arg_node.kind == .prefix && arg_node.value == '...' && arg_node.children_count > 0 {
3988+ return g.a.child(&arg_node, 0)
3989+ }
3990+ return none
3991+}
3992+
3993+fn (g &FlatGen) arg_is_const_ident(arg_node flat.Node) bool {
3994+ if arg_node.kind != .ident || arg_node.value.len == 0 {
3995+ return false
3996+ }
3997+ looked_up := g.tc.cur_scope.lookup(arg_node.value) or { types.Type(types.void_) }
3998+ if looked_up !is types.Void {
3999+ return false
4000+ }
4001+ return g.const_ref_name(arg_node.value).len > 0
4002+}
4003+
36744004// find_prim_method resolves find prim method information for c.
36754005fn (g &FlatGen) find_prim_method(method string) string {
36764006 if 'u8.${method}' in g.tc.fn_param_types {
@@ -3766,6 +4096,7 @@ fn (mut g FlatGen) gen_sum_variant_arg(arg_id flat.NodeId, expected types.Type)
37664096
37674097// forward_decls supports forward decls handling for FlatGen.
37684098fn (mut g FlatGen) forward_decls() {
4099+ preferred_fns := g.preferred_c_backend_fn_nodes()
37694100 mut cur_module := ''
37704101 mut cur_file := ''
37714102 mut forwarded := map[string]bool{}
@@ -3788,10 +4119,15 @@ fn (mut g FlatGen) forward_decls() {
37884119
37894120 is_entry_main := is_main_fn_in_main_module(cur_module, node.value) && g.test_files.len == 0
37904121 if kind_id == 61 && !is_entry_main {
3791- if !g.should_emit_fn_node_in_module(node, i, cur_module) {
4122+ if !g.should_emit_fn_node_in_module(node, i, cur_module, cur_file) {
37924123 continue
37934124 }
37944125 qfn := g.fn_c_name_in_module(cur_module, node.value)
4126+ if preferred_idx := preferred_fns[qfn] {
4127+ if preferred_idx != i {
4128+ continue
4129+ }
4130+ }
37954131 if forwarded[qfn] {
37964132 continue
37974133 }
@@ -181,6 +181,7 @@ fn split_flat_cgen_items(items []FlatFnGenItem, n_jobs int) [][]FlatFnGenItem {
181181// collect_fn_gen_items updates collect fn gen items state for c.
182182fn (mut g FlatGen) collect_fn_gen_items() []FlatFnGenItem {
183183 mut items := []FlatFnGenItem{}
184+ preferred_fns := g.preferred_c_backend_fn_nodes()
184185 mut cur_module := ''
185186 mut cur_file := ''
186187 for i in 0 .. g.a.nodes.len {
@@ -203,10 +204,15 @@ fn (mut g FlatGen) collect_fn_gen_items() []FlatFnGenItem {
203204 if kind_id != 61 {
204205 continue
205206 }
206- if !g.should_emit_fn_node_in_module(node, i, cur_module) {
207+ if !g.should_emit_fn_node_in_module(node, i, cur_module, cur_file) {
207208 continue
208209 }
209210 qfn := qualified_fn_name_in_module(cur_module, node.value)
211+ if preferred_idx := preferred_fns[qfn] {
212+ if preferred_idx != i {
213+ continue
214+ }
215+ }
210216 if g.emitted_fn_contains(qfn) {
211217 continue
212218 }
@@ -389,11 +395,13 @@ fn (g &FlatGen) new_parallel_worker(worker_id int) &FlatGen {
389395 fn_decl_ret_types: g.fn_decl_ret_types
390396 struct_decl_infos: g.struct_decl_infos
391397 struct_decl_short_infos: g.struct_decl_short_infos
398+ const_runtime_inits: g.const_runtime_inits.clone()
392399 runtime_inits: g.runtime_inits.clone()
393400 compiler_vroot: g.compiler_vroot
394401 cur_param_names: g.cur_param_names.clone()
395402 cur_param_type_values: g.cur_param_type_values.clone()
396403 cur_param_types: g.cur_param_types.clone()
404+ cur_mut_params: g.cur_mut_params.clone()
397405 cur_fn_ret: g.cur_fn_ret
398406 cur_fn_ret_is_optional: g.cur_fn_ret_is_optional
399407 cur_fn_ret_base: g.cur_fn_ret_base
@@ -78,6 +78,7 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
7878 var_name
7979 }
8080 clean_container_type := types.unwrap_pointer(container_type)
81+ mut map_snapshot_var := ''
8182 if clean_container_type is types.Map {
8283 c_key := g.value_c_type(clean_container_type.key_type)
8384 c_val := g.value_c_type(clean_container_type.value_type)
@@ -86,13 +87,42 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
8687 g.tmp_count++
8788 key_var := if has_index { idx_var } else { '__mk_${g.tmp_count}' }
8889 val_var_ := if has_index { elem_var } else { var_name }
89- access := if container_type is types.Pointer { '->' } else { '.' }
90- key_values := '(${container_str})${access}key_values'
90+ use_snapshot := g.for_in_body_contains_delete_call(node, body_start)
91+ key_values := if use_snapshot {
92+ map_snapshot_var = '__for_map_${g.tmp_count}'
93+ g.tmp_count++
94+ if container_type is types.Pointer {
95+ g.writeln('map ${map_snapshot_var} = map__clone(${container_str});')
96+ } else {
97+ map_src := '__for_map_src_${g.tmp_count}'
98+ g.tmp_count++
99+ g.writeln('map ${map_src} = ${container_str};')
100+ g.writeln('map ${map_snapshot_var} = map__clone(&${map_src});')
101+ }
102+ '${map_snapshot_var}.key_values'
103+ } else {
104+ access := if container_type is types.Pointer { '->' } else { '.' }
105+ '(${container_str})${access}key_values'
106+ }
91107 g.writeln('for (int ${iter_var} = 0; ${iter_var} < ${key_values}.len; ${iter_var}++) {')
92108 g.indent++
93109 g.writeln('if (${key_values}.all_deleted && ${key_values}.all_deleted[${iter_var}]) continue;')
94- g.writeln('${c_key} ${key_var} = *(${c_key}*)(${key_values}.keys + ${iter_var} * ${key_values}.key_bytes);')
95- g.writeln('${c_val} ${val_var_} = *(${c_val}*)(${key_values}.values + ${iter_var} * ${key_values}.value_bytes);')
110+ key_slot := '${key_values}.keys + ${iter_var} * ${key_values}.key_bytes'
111+ if key_fixed := array_fixed_type(clean_container_type.key_type) {
112+ c_elem, dims := g.fixed_array_decl_parts(key_fixed)
113+ g.writeln('${c_elem} ${key_var}${dims};')
114+ g.writeln('memmove(${key_var}, ${key_slot}, sizeof(${key_var}));')
115+ } else {
116+ g.writeln('${c_key} ${key_var} = *(${c_key}*)(${key_slot});')
117+ }
118+ val_slot := '${key_values}.values + ${iter_var} * ${key_values}.value_bytes'
119+ if val_fixed := array_fixed_type(clean_container_type.value_type) {
120+ c_elem, dims := g.fixed_array_decl_parts(val_fixed)
121+ g.writeln('${c_elem} ${val_var_}${dims};')
122+ g.writeln('memmove(${val_var_}, ${val_slot}, sizeof(${val_var_}));')
123+ } else {
124+ g.writeln('${c_val} ${val_var_} = *(${c_val}*)(${val_slot});')
125+ }
96126 if has_index {
97127 g.tc.cur_scope.insert(key_var, clean_container_type.key_type)
98128 }
@@ -145,6 +175,9 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
145175 }
146176 g.indent--
147177 g.writeln('}')
178+ if map_snapshot_var.len > 0 {
179+ g.writeln('map__free(&${map_snapshot_var});')
180+ }
148181 g.tc.pop_scope()
149182 return
150183 }
@@ -161,6 +194,37 @@ fn (mut g FlatGen) gen_for_in(node flat.Node) {
161194 g.tc.pop_scope()
162195}
163196
197+fn (g &FlatGen) for_in_body_contains_delete_call(node flat.Node, body_start int) bool {
198+ for i in body_start .. node.children_count {
199+ if g.node_contains_delete_call(g.a.child(&node, i)) {
200+ return true
201+ }
202+ }
203+ return false
204+}
205+
206+fn (g &FlatGen) node_contains_delete_call(id flat.NodeId) bool {
207+ if int(id) < 0 || int(id) >= g.a.nodes.len {
208+ return false
209+ }
210+ node := g.a.nodes[int(id)]
211+ if node.kind == .call && node.children_count > 0 {
212+ fn_node := g.a.child_node(&node, 0)
213+ if fn_node.kind == .selector && fn_node.value == 'delete' {
214+ return true
215+ }
216+ if fn_node.kind == .ident && fn_node.value in ['map.delete', 'map__delete'] {
217+ return true
218+ }
219+ }
220+ for i in 0 .. node.children_count {
221+ if g.node_contains_delete_call(g.a.child(&node, i)) {
222+ return true
223+ }
224+ }
225+ return false
226+}
227+
164228fn (g &FlatGen) c_loop_local_name(name string) string {
165229 if name.contains('.') {
166230 return c_name(name.all_after_last('.'))
@@ -292,10 +292,18 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) {
292292 && !g.clone_call_matches_base(ret_node, base)
293293 && expr_value_type !is types.Primitive
294294 && expr_value_type !is types.Unknown {
295- g.writeln('return (${ct}){.ok = false};')
295+ if err_expr := g.optional_error_return_expr(ret_id,
296+ expr_value_type, ct)
297+ {
298+ g.writeln('return ${err_expr};')
299+ } else {
300+ g.writeln('return (${ct}){.ok = false};')
301+ }
296302 } else {
297303 g.write('return (${ct}){.ok = true, .value = ')
298- g.gen_expr_with_expected_type(ret_id, base)
304+ if !g.gen_heap_local_address_expr(ret_id, base) {
305+ g.gen_expr_with_expected_type(ret_id, base)
306+ }
299307 g.writeln('};')
300308 }
301309 }
@@ -349,7 +357,7 @@ fn (mut g FlatGen) gen_node(id flat.NodeId) {
349357 if !g.gen_interface_value_expr(ret_id, g.cur_fn_ret) {
350358 g.gen_expr(ret_id)
351359 }
352- } else {
360+ } else if !g.gen_heap_local_address_expr(ret_id, g.cur_fn_ret) {
353361 g.gen_expr(ret_id)
354362 }
355363 g.writeln(';')
@@ -517,6 +525,67 @@ fn (mut g FlatGen) return_c_type() string {
517525 return g.tc.c_type(g.cur_fn_ret)
518526}
519527
528+// local_ident_type returns the type of an identifier that is local to the
529+// currently emitted function body.
530+fn (g &FlatGen) local_ident_type(name string) ?types.Type {
531+ if typ := g.current_param_type(name) {
532+ return typ
533+ }
534+ if typ := g.cur_param_types[name] {
535+ return typ
536+ }
537+ if g.cur_scope_has_local_name(name) {
538+ if typ := g.tc.cur_scope.lookup(name) {
539+ if typ !is types.Void {
540+ return typ
541+ }
542+ }
543+ }
544+ return none
545+}
546+
547+// heap_local_address_expr returns a heap-copy expression for `&local` when the
548+// surrounding return type is a pointer. V permits local address escapes; C needs
549+// the local value copied out of the stack frame before returning.
550+fn (mut g FlatGen) heap_local_address_expr(ret_id flat.NodeId, expected types.Type) ?string {
551+ if int(ret_id) < 0 || int(ret_id) >= g.a.nodes.len {
552+ return none
553+ }
554+ node := g.a.nodes[int(ret_id)]
555+ if node.kind != .prefix || node.op != .amp || node.children_count == 0 {
556+ return none
557+ }
558+ if expected !is types.Pointer {
559+ return none
560+ }
561+ ptr := expected as types.Pointer
562+ child_id := g.a.child(&node, 0)
563+ child := g.a.nodes[int(child_id)]
564+ if child.kind != .ident || child.value.len == 0 {
565+ return none
566+ }
567+ local_type := g.local_ident_type(child.value) or { return none }
568+ base_type := ptr.base_type
569+ local_ct := g.tc.c_type(local_type)
570+ base_ct := g.tc.c_type(base_type)
571+ if base_ct.len == 0 || base_ct == 'void' {
572+ return none
573+ }
574+ if local_ct != base_ct && !g.type_names_match(local_type, base_type) {
575+ return none
576+ }
577+ local_expr := g.expr_to_string(child_id)
578+ return '(${base_ct}*)memdup(&${local_expr}, sizeof(${base_ct}))'
579+}
580+
581+fn (mut g FlatGen) gen_heap_local_address_expr(ret_id flat.NodeId, expected types.Type) bool {
582+ if expr := g.heap_local_address_expr(ret_id, expected) {
583+ g.write(expr)
584+ return true
585+ }
586+ return false
587+}
588+
520589// return_expr_string supports return expr string handling for FlatGen.
521590fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_node flat.Node, ct string) string {
522591 if ret_node.kind == .call {
@@ -589,9 +658,15 @@ fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_no
589658 && !g.types_numeric_compatible(expr_value_type, base)
590659 && !g.call_constructs_type(ret_id, base) && !g.clone_call_matches_base(ret_node, base)
591660 && expr_value_type !is types.Primitive && expr_value_type !is types.Unknown {
661+ if err_expr := g.optional_error_return_expr(ret_id, expr_value_type, ct) {
662+ return err_expr
663+ }
592664 return '(${ct}){.ok = false}'
593665 }
594- return '(${ct}){.ok = true, .value = ${g.expr_to_string_with_expected_type(ret_id, base)}}'
666+ value := g.heap_local_address_expr(ret_id, base) or {
667+ g.expr_to_string_with_expected_type(ret_id, base)
668+ }
669+ return '(${ct}){.ok = true, .value = ${value}}'
595670 }
596671 if g.cur_fn_ret is types.MultiReturn {
597672 if node.children_count > 1 {
@@ -612,7 +687,53 @@ fn (mut g FlatGen) return_expr_string(node flat.Node, ret_id flat.NodeId, ret_no
612687 // preserves `_typ`/`_object` instead of zeroing the interface.
613688 return g.interface_value_to_string(ret_id, g.cur_fn_ret)
614689 }
615- return g.expr_to_string(ret_id)
690+ return g.heap_local_address_expr(ret_id, g.cur_fn_ret) or { g.expr_to_string(ret_id) }
691+}
692+
693+fn (mut g FlatGen) optional_error_return_expr(ret_id flat.NodeId, expr_type types.Type, ct string) ?string {
694+ if !g.type_can_return_as_ierror(expr_type) {
695+ return none
696+ }
697+ err := g.interface_value_to_string(ret_id, types.Type(types.Interface{
698+ name: 'IError'
699+ }))
700+ return '(${ct}){.ok = false, .err = ${err}}'
701+}
702+
703+fn (g &FlatGen) type_can_return_as_ierror(typ types.Type) bool {
704+ clean := types.unwrap_pointer(typ)
705+ if clean is types.Alias {
706+ return g.type_can_return_as_ierror(clean.base_type)
707+ }
708+ if clean is types.Interface {
709+ return clean.name == 'IError' || clean.name.ends_with('.IError')
710+ }
711+ if clean is types.Struct {
712+ if clean.name == 'IError' || clean.name.ends_with('.IError') {
713+ return true
714+ }
715+ if g.tc.named_type_implements_interface(clean.name, 'IError') {
716+ return true
717+ }
718+ return g.struct_type_embeds_error(clean.name)
719+ }
720+ return false
721+}
722+
723+fn (g &FlatGen) struct_type_embeds_error(type_name string) bool {
724+ if type_name == 'Error' || type_name.ends_with('.Error') {
725+ return true
726+ }
727+ for field in g.struct_embedded_fields(type_name) {
728+ embedded_type_name := g.embedded_field_type_name(field)
729+ if embedded_type_name.len == 0 {
730+ continue
731+ }
732+ if g.struct_type_embeds_error(embedded_type_name) {
733+ return true
734+ }
735+ }
736+ return false
616737}
617738
618739fn (g &FlatGen) local_fn_call_return_type(call_id flat.NodeId, call_node flat.Node) types.Type {
@@ -921,10 +1042,14 @@ fn (g &FlatGen) clone_call_matches_base(call_node flat.Node, base types.Type) bo
9211042// option_c_name_for_base returns the C optional type name used for a `?base`/`!base`
9221043// value, mirroring optional_type_name without its side effects.
9231044fn (g &FlatGen) option_c_name_for_base(base types.Type) string {
924- if base is types.Void || base is types.Primitive || base is types.Enum {
1045+ if base is types.Void {
1046+ return 'Optional'
1047+ }
1048+ inner_ct := g.tc.c_type(base)
1049+ if inner_ct == 'int' {
9251050 return 'Optional'
9261051 }
927- return 'Optional_' + g.tc.c_type(base).replace('*', 'ptr').replace(' ', '_')
1052+ return 'Optional_' + inner_ct.replace('*', 'ptr').replace(' ', '_')
9281053}
9291054
9301055fn (g &FlatGen) expr_is_nil_pointer_payload(id flat.NodeId, base types.Type) bool {
@@ -1178,6 +1303,24 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
11781303 rhs := g.a.nodes[int(rhs_id)]
11791304 lhs_is_defer_capture := lhs.kind == .ident && lhs.value in g.defer_capture_types
11801305 if rhs.kind == .array_literal {
1306+ rhs_v_type := if rhs.typ.len > 0 {
1307+ g.tc.parse_type(rhs.typ)
1308+ } else {
1309+ g.tc.resolve_type(rhs_id)
1310+ }
1311+ if fixed := array_fixed_type(rhs_v_type) {
1312+ lhs_str := g.decl_lhs_str(lhs_id)
1313+ if !lhs_is_defer_capture {
1314+ c_elem, dims := g.fixed_array_decl_parts(fixed)
1315+ g.writeln('${decl_prefix}${c_elem} ${lhs_str}${dims};')
1316+ }
1317+ g.gen_fixed_array_copy_from_node(lhs_str, rhs_id, fixed)
1318+ if lhs.kind == .ident {
1319+ g.tc.cur_scope.insert(lhs.value, rhs_v_type)
1320+ }
1321+ i += 2
1322+ continue
1323+ }
11811324 elem_type := if rhs.children_count > 0 {
11821325 g.tc.resolve_type(g.a.child(&rhs, 0))
11831326 } else {
@@ -1207,7 +1350,7 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
12071350 g.tc.cur_scope.insert(lhs.value, raw_init_type)
12081351 }
12091352 } else {
1210- c_elem := g.tc.c_type(init_type)
1353+ c_elem := g.sizeof_target(rhs.value)
12111354 mut init_len := '0'
12121355 mut init_cap := '0'
12131356 mut init_val := ''
@@ -1272,13 +1415,33 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
12721415 }
12731416 }
12741417 } else {
1275- v_type := if node.typ.len > 0 {
1276- g.tc.parse_type(node.typ)
1418+ mut v_type := if node.typ.len > 0 {
1419+ decl_type := g.tc.parse_type(node.typ)
1420+ if decl_type is types.Struct && decl_type.name == 'array' {
1421+ g.usable_expr_type(rhs_id)
1422+ } else if decl_annotation_is_unusable(decl_type, node.typ) {
1423+ rhs_type := g.decl_rhs_fallback_type(rhs_id, rhs)
1424+ if rhs_type is types.Unknown {
1425+ types.Type(decl_type)
1426+ } else {
1427+ rhs_type
1428+ }
1429+ } else {
1430+ decl_type
1431+ }
12771432 } else if rhs.kind == .if_expr {
12781433 g.if_expr_type(&rhs)
12791434 } else {
12801435 g.usable_expr_type(rhs_id)
12811436 }
1437+ if fixed := array_fixed_type(v_type) {
1438+ if g.fixed_array_decl_is_unusable(fixed) {
1439+ rhs_type := g.decl_rhs_fallback_type(rhs_id, rhs)
1440+ if rhs_type !is types.Unknown {
1441+ v_type = rhs_type
1442+ }
1443+ }
1444+ }
12821445 if fixed := array_fixed_type(v_type) {
12831446 lhs_str := g.decl_lhs_str(lhs_id)
12841447 if !lhs_is_defer_capture {
@@ -1320,6 +1483,55 @@ fn (mut g FlatGen) gen_decl_assign(node flat.Node) {
13201483 }
13211484}
13221485
1486+fn (g &FlatGen) decl_rhs_fallback_type(rhs_id flat.NodeId, rhs flat.Node) types.Type {
1487+ if rhs.kind == .index && rhs.value.len > 0 {
1488+ index_type := g.tc.parse_type(rhs.value)
1489+ if !decl_annotation_is_unusable(index_type, rhs.value) {
1490+ return index_type
1491+ }
1492+ }
1493+ if rhs.typ.len > 0 {
1494+ rhs_type := g.tc.parse_type(rhs.typ)
1495+ if !decl_annotation_is_unusable(rhs_type, rhs.typ) {
1496+ return rhs_type
1497+ }
1498+ }
1499+ if rhs.kind == .index && rhs.children_count > 0 {
1500+ base_type := types.unwrap_pointer(g.usable_expr_type(g.a.child(&rhs, 0)))
1501+ if base_type is types.Array {
1502+ return base_type.elem_type
1503+ }
1504+ if base_type is types.ArrayFixed {
1505+ return base_type.elem_type
1506+ }
1507+ if base_type is types.Map {
1508+ return base_type.value_type
1509+ }
1510+ }
1511+ return g.usable_expr_type(rhs_id)
1512+}
1513+
1514+fn (mut g FlatGen) fixed_array_decl_is_unusable(typ types.ArrayFixed) bool {
1515+ c_elem, _ := g.fixed_array_decl_parts(typ)
1516+ return c_elem == 'void'
1517+}
1518+
1519+fn decl_annotation_is_unusable(typ types.Type, raw string) bool {
1520+ if typ is types.ArrayFixed && fixed_array_contains_void(typ) {
1521+ return true
1522+ }
1523+ clean_raw := raw.replace(' ', '')
1524+ return clean_raw == 'void' || clean_raw.starts_with('void[') || clean_raw.ends_with(']void')
1525+ || clean_raw.contains(']void[')
1526+}
1527+
1528+fn fixed_array_contains_void(typ types.Type) bool {
1529+ if typ is types.ArrayFixed {
1530+ return fixed_array_contains_void(typ.elem_type)
1531+ }
1532+ return typ is types.Void
1533+}
1534+
13231535fn (mut g FlatGen) gen_fixed_array_zero_init_decl(lhs_id flat.NodeId, init_type types.ArrayFixed, decl_prefix string, lhs_is_defer_capture bool) {
13241536 c_elem, dims := g.fixed_array_decl_parts(init_type)
13251537 lhs_str := g.decl_lhs_str(lhs_id)
@@ -1486,7 +1698,8 @@ fn (mut g FlatGen) gen_assign(node flat.Node) {
14861698 g.write('(void)(')
14871699 g.gen_expr(g.a.child(&node, i + 1))
14881700 g.writeln(');')
1489- } else if node.op == .left_shift_assign && lhs.kind == .ident {
1701+ } else if node.op == .left_shift_assign && lhs.kind == .ident
1702+ && node.value in ['push', 'push_many'] {
14901703 if node.value == 'push_many' {
14911704 g.gen_array_push_many_stmt(g.a.child(&node, i), g.a.child(&node, i + 1))
14921705 } else if node.value == 'push' {
@@ -1735,6 +1948,10 @@ fn (mut g FlatGen) gen_multi_return_assign(node flat.Node) {
17351948fn (mut g FlatGen) gen_decl_lhs(id flat.NodeId) {
17361949 node := g.a.nodes[int(id)]
17371950 if node.kind == .ident {
1951+ if node.value == '_' {
1952+ g.write('__discard_${int(id)}')
1953+ return
1954+ }
17381955 g.write(c_name(node.value))
17391956 } else {
17401957 g.gen_expr(id)
@@ -1745,6 +1962,9 @@ fn (mut g FlatGen) gen_decl_lhs(id flat.NodeId) {
17451962fn (mut g FlatGen) decl_lhs_str(id flat.NodeId) string {
17461963 node := g.a.nodes[int(id)]
17471964 if node.kind == .ident {
1965+ if node.value == '_' {
1966+ return '__discard_${int(id)}'
1967+ }
17481968 return c_name(node.value)
17491969 }
17501970 return g.expr_to_string(id)
@@ -1755,8 +1975,9 @@ fn (mut g FlatGen) gen_assign_or_expr(node flat.Node, lhs_idx int, or_node flat.
17551975 expr_id := g.a.child(&or_node, 0)
17561976 or_body_id := g.a.child(&or_node, 1)
17571977 or_body := g.a.nodes[int(or_body_id)]
1978+ expr_node := g.a.nodes[int(expr_id)]
17581979 tmp := g.tmp_name()
1759- expr_type := g.tc.resolve_type(expr_id)
1980+ expr_type := g.or_expr_source_type(expr_id, expr_node)
17601981 opt_ct := g.optional_type_name(expr_type)
17611982 g.write('${opt_ct} ${tmp} = ')
17621983 g.gen_expr(expr_id)
@@ -1806,7 +2027,7 @@ fn (mut g FlatGen) gen_decl_or_expr(lhs flat.Node, or_node flat.Node) {
18062027 }
18072028 }
18082029 tmp := g.tmp_name()
1809- expr_type := g.tc.resolve_type(expr_id)
2030+ expr_type := g.or_expr_source_type(expr_id, expr_node)
18102031 opt_ct := g.optional_type_name(expr_type)
18112032 val_ct, val_type := g.optional_value_ct(expr_type)
18122033 g.tc.cur_scope.insert(lhs.value, val_type)
@@ -1951,7 +2172,7 @@ fn (mut g FlatGen) gen_or_expr(node flat.Node) {
19512172 }
19522173 }
19532174 tmp := g.tmp_name()
1954- expr_type := g.tc.resolve_type(expr_id)
2175+ expr_type := g.or_expr_source_type(expr_id, expr_node)
19552176 opt_ct := g.optional_type_name(expr_type)
19562177 val_ct, val_type := g.optional_value_ct(expr_type)
19572178 val := g.tmp_name()
@@ -1970,6 +2191,20 @@ fn (mut g FlatGen) gen_or_expr(node flat.Node) {
19702191 g.write(' } ${val};})')
19712192}
19722193
2194+fn (g &FlatGen) or_expr_source_type(expr_id flat.NodeId, expr_node flat.Node) types.Type {
2195+ if expr_node.kind == .call {
2196+ decl_type := g.declared_call_return_type(expr_id)
2197+ if decl_type is types.OptionType || decl_type is types.ResultType {
2198+ return decl_type
2199+ }
2200+ local_type := g.local_fn_call_return_type(expr_id, expr_node)
2201+ if local_type is types.OptionType || local_type is types.ResultType {
2202+ return local_type
2203+ }
2204+ }
2205+ return g.tc.resolve_type(expr_id)
2206+}
2207+
19732208// gen_or_body emits or body output for c.
19742209fn (mut g FlatGen) gen_or_body(or_body flat.Node) {
19752210 if or_body.children_count == 1 {
@@ -38,6 +38,8 @@ fn (mut g FlatGen) gen_string_interp(node flat.Node) {
3838 typ_name := types.Type(typ).name()
3939 if typ is types.String {
4040 g.gen_expr(child_id)
41+ } else if g.gen_map_str_expr(child_id, typ) {
42+ // emitted by gen_map_str_expr
4143 } else if typ_name == 'IError' || typ_name.ends_with('.IError') {
4244 // IError may resolve as Interface/Alias/Struct depending on context; match by
4345 // name and interpolate its `.message` (mirrors the transformer's IError path).
@@ -351,6 +351,10 @@ fn (mut g FlatGen) gen_fixed_array_copy_source(value_id flat.NodeId, field_type
351351 g.gen_expr(value_id)
352352 return
353353 }
354+ if val_node.kind == .paren && val_node.children_count > 0 {
355+ g.gen_fixed_array_copy_source(g.a.child(val_node, 0), field_type)
356+ return
357+ }
354358 val_type := types.unwrap_pointer(g.usable_expr_type(value_id))
355359 if val_type is types.Array {
356360 g.write('(')
@@ -1414,14 +1418,8 @@ fn (mut g FlatGen) gen_map_init(id flat.NodeId, node flat.Node) {
14141418
14151419// write_new_map writes new map output for c.
14161420fn (mut g FlatGen) write_new_map(key_type types.Type, value_type types.Type) {
1417- mut c_key := g.tc.c_type(key_type)
1418- mut c_val := g.tc.c_type(value_type)
1419- if c_key.starts_with('fn_ptr:') {
1420- c_key = g.resolve_fn_ptr_type(c_key)
1421- }
1422- if c_val.starts_with('fn_ptr:') {
1423- c_val = g.resolve_fn_ptr_type(c_val)
1424- }
1421+ c_key := g.value_sizeof_target(key_type)
1422+ c_val := g.value_sizeof_target(value_type)
14251423 hash_fn, eq_fn, clone_fn, free_fn := g.map_callback_names(key_type)
14261424 g.write('new_map(sizeof(${c_key}), sizeof(${c_val}), ${hash_fn}, ${eq_fn}, ${clone_fn}, ${free_fn})')
14271425}
@@ -14,13 +14,16 @@ fn (mut g FlatGen) optional_type_name(t types.Type) string {
1414 return g.tc.c_type(t)
1515 }
1616
17- if base_type is types.Void || base_type is types.Primitive || base_type is types.Enum {
17+ if base_type is types.Void {
1818 return 'Optional'
1919 }
2020 mut inner_ct := g.tc.c_type(base_type)
2121 if inner_ct.starts_with('fn_ptr:') {
2222 inner_ct = g.resolve_fn_ptr_type(inner_ct)
2323 }
24+ if inner_ct == 'int' {
25+ return 'Optional'
26+ }
2427 safe_name := inner_ct.replace('*', 'ptr').replace(' ', '_')
2528 opt_name := 'Optional_${safe_name}'
2629 g.needed_optional_types[opt_name] = inner_ct
@@ -38,6 +41,14 @@ fn (mut g FlatGen) value_c_type(t types.Type) string {
3841 return ct
3942}
4043
44+fn (mut g FlatGen) value_sizeof_target(t types.Type) string {
45+ if fixed := array_fixed_type(t) {
46+ c_elem, dims := g.fixed_array_decl_parts(fixed)
47+ return '${c_elem}${dims}'
48+ }
49+ return g.value_c_type(t)
50+}
51+
4152fn (mut g FlatGen) cast_c_type(t types.Type) string {
4253 if t is types.Pointer {
4354 return '${g.value_c_type(t.base_type)}*'
@@ -63,11 +74,7 @@ fn (mut g FlatGen) optional_value_ct(t types.Type) (string, types.Type) {
6374
6475// optional_typedefs supports optional typedefs handling for FlatGen.
6576fn (mut g FlatGen) optional_typedefs() {
66- for _, ret in g.tc.fn_ret_types {
67- if ret is types.OptionType || ret is types.ResultType {
68- g.optional_type_name(ret)
69- }
70- }
77+ g.collect_optional_typedefs()
7178 mut wrote := false
7279 for opt_name, val_type in g.needed_optional_types {
7380 if g.emit_optional_typedef(opt_name, val_type) {
@@ -79,6 +86,82 @@ fn (mut g FlatGen) optional_typedefs() {
7986 }
8087}
8188
89+fn (mut g FlatGen) collect_optional_typedefs() {
90+ for _, ret in g.tc.fn_ret_types {
91+ g.collect_optional_typedef_type(ret)
92+ }
93+ for _, params in g.tc.fn_param_types {
94+ for param in params {
95+ g.collect_optional_typedef_type(param)
96+ }
97+ }
98+ for _, fields in g.tc.structs {
99+ for field in fields {
100+ g.collect_optional_typedef_type(field.typ)
101+ }
102+ }
103+ for _, fields in g.tc.interface_fields {
104+ for field in fields {
105+ g.collect_optional_typedef_type(field.typ)
106+ }
107+ }
108+ for _, typ in g.tc.c_globals {
109+ g.collect_optional_typedef_type(typ)
110+ }
111+ for _, typ in g.tc.const_types {
112+ g.collect_optional_typedef_type(typ)
113+ }
114+ for idx, _ in g.a.nodes {
115+ if typ := g.tc.expr_type(flat.NodeId(idx)) {
116+ g.collect_optional_typedef_type(typ)
117+ }
118+ }
119+}
120+
121+fn (mut g FlatGen) collect_optional_typedef_type(t types.Type) {
122+ match t {
123+ types.OptionType {
124+ g.optional_type_name(t)
125+ g.collect_optional_typedef_type(t.base_type)
126+ }
127+ types.ResultType {
128+ g.optional_type_name(t)
129+ g.collect_optional_typedef_type(t.base_type)
130+ }
131+ types.Array {
132+ g.collect_optional_typedef_type(t.elem_type)
133+ }
134+ types.ArrayFixed {
135+ g.collect_optional_typedef_type(t.elem_type)
136+ }
137+ types.Channel {
138+ g.collect_optional_typedef_type(t.elem_type)
139+ }
140+ types.Map {
141+ g.collect_optional_typedef_type(t.key_type)
142+ g.collect_optional_typedef_type(t.value_type)
143+ }
144+ types.Pointer {
145+ g.collect_optional_typedef_type(t.base_type)
146+ }
147+ types.FnType {
148+ for param in t.params {
149+ g.collect_optional_typedef_type(param)
150+ }
151+ g.collect_optional_typedef_type(t.return_type)
152+ }
153+ types.Alias {
154+ g.collect_optional_typedef_type(t.base_type)
155+ }
156+ types.MultiReturn {
157+ for typ in t.types {
158+ g.collect_optional_typedef_type(typ)
159+ }
160+ }
161+ else {}
162+ }
163+}
164+
82165// emit_optional_typedef emits emit optional typedef output for c.
83166fn (mut g FlatGen) emit_optional_typedef(opt_name string, val_type string) bool {
84167 if opt_name in g.emitted_optional_types {
@@ -144,16 +144,20 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
144144 queue << 'c.gen_assign'
145145 used['c.gen_assign'] = true
146146 for seed in ['__new_array', 'new_array_from_c_array', 'array.get', 'array.set', 'array.push',
147- 'array.push_many', 'array.slice', 'array.clone', 'array.delete', 'array.ensure_cap',
147+ 'array.push_many', 'array.insert', 'array.insert_many', 'array.prepend', 'array.reverse',
148+ 'array.slice', 'array.pop_left', 'array.clone', 'array.delete', 'array.ensure_cap',
148149 'string.==', 'string.<', 'string.free', 'string.all_before', 'string.all_before_last',
149150 'string.all_after', 'string.all_after_last', 'string.substr', 'string__substr', 'u8.vstring',
150- '[]rune.string', 'map.set', 'map.exists', 'map.get', 'map.get_check', 'map.get_and_set',
151- 'map.delete', 'map.clone', 'map.clear', 'memdup', 'strings.Builder.write_ptr',
152- 'strings.Builder.write_runes', 'strings.Builder.free', 'strconv.format_int',
153- 'strconv.format_uint', 'bool.str', 'ptr_str', 'strconv__f32_to_str_l',
154- 'strconv__f64_to_str_l', 'sync.new_channel_st', 'sync.Channel.push', 'sync.Channel.pop',
155- 'sync.Channel.close', 'sync.Channel.len', 'sync.Channel.closed', 'new_channel_st',
156- 'Channel.push', 'Channel.pop', 'Channel.close', 'Channel.len', 'Channel.closed', 'panic',
151+ 'u8.vstring_with_len', 'charptr.vstring', 'charptr.vstring_with_len', 'byteptr.vstring',
152+ 'byteptr.vstring_with_len', 'byteptr.vbytes', 'voidptr.vbytes', '[]rune.string', 'map.set',
153+ 'map.exists', 'map.get', 'map.get_check', 'map.get_and_set', 'map.delete', 'map.clone',
154+ 'map.clear', 'map.keys', 'map.values', 'map.reserve', 'map_map_eq', 'memdup',
155+ 'strings.Builder.write_ptr', 'strings.Builder.write_runes', 'strings.Builder.free',
156+ 'strconv.format_int', 'strconv.format_uint', 'bool.str', 'int.str', 'u64.str', 'f64.str',
157+ 'rune.str', 'string.+', 'ptr_str', 'strconv__f32_to_str_l', 'strconv__f64_to_str_l',
158+ 'sync.new_channel_st', 'sync.Channel.push', 'sync.Channel.pop', 'sync.Channel.close',
159+ 'sync.Channel.len', 'sync.Channel.closed', 'new_channel_st', 'Channel.push', 'Channel.pop',
160+ 'Channel.close', 'Channel.len', 'Channel.closed', 'os.join_path_single', 'panic',
157161 'u8.is_letter', 'u8.is_capital', 'string.is_capital', 'string.to_lower_ascii',
158162 'rune.to_lower', 'Array_u8__bytestr', 'Array_u8__hex', 'data_to_hex_string',
159163 'map_hash_string', 'map_hash_int_1', 'map_hash_int_2', 'map_hash_int_4', 'map_hash_int_8',
@@ -164,6 +168,12 @@ fn mark_used_with_test_files(a &flat.FlatAst, tc &types.TypeChecker, test_files
164168 queue << seed
165169 used[seed] = true
166170 }
171+ queue << 'array.delete_last'
172+ used['array.delete_last'] = true
173+ for seed in ['i8.str', 'i16.str', 'i32.str', 'i64.str'] {
174+ queue << seed
175+ used[seed] = true
176+ }
167177
168178 if trace_markused {
169179 eprintln('markused: fn_count:')
@@ -739,9 +749,6 @@ fn enqueue_test_file_roots(a &flat.FlatAst, test_files map[string]bool, mut used
739749 continue
740750 }
741751 module_name := test_file_module_name(a, file_node)
742- if module_name.len > 0 && module_name != 'main' {
743- continue
744- }
745752 mut decl_ids := []flat.NodeId{}
746753 markused_collect_test_harness_decl_ids(a, file_node, mut decl_ids)
747754 for child_id in decl_ids {
@@ -816,6 +823,7 @@ fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut
816823 mut needs_string_plus_helper := false
817824 mut needs_string_membership_helpers := false
818825 mut needs_new_map := false
826+ mut needs_map_iteration_snapshot := false
819827 mut cur_module := ''
820828 mut imports := map[string]string{}
821829 for node in a.nodes {
@@ -872,6 +880,9 @@ fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut
872880 enqueue('join_path_single', mut used, mut queue)
873881 enqueue('os.join_path_single', mut used, mut queue)
874882 }
883+ if fn_node.kind == .selector && fn_node.value == 'runes_iterator' {
884+ enqueue('RunesIterator.next', mut used, mut queue)
885+ }
875886 if fn_node.kind == .ident
876887 && fn_node.value in ['print', 'println', 'eprint', 'eprintln']
877888 && node.children_count >= 2 {
@@ -913,6 +924,15 @@ fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut
913924 .map_init {
914925 needs_new_map = true
915926 }
927+ .for_in_stmt {
928+ if node.value.int() == 3 && node.children_count > 2 {
929+ container_id := a.child(&node, 2)
930+ container_type := tc.resolve_type(container_id)
931+ if types.unwrap_pointer(container_type) is types.Map {
932+ needs_map_iteration_snapshot = true
933+ }
934+ }
935+ }
916936 .enum_decl {
917937 // A `[flag]` enum's synthesized `<Enum>__autostr` helper (emitted
918938 // unconditionally in cgen) builds its `Enum{.a | .b}` string with
@@ -949,6 +969,11 @@ fn enqueue_detected_runtime_helpers(a &flat.FlatAst, tc &types.TypeChecker, mut
949969 if needs_new_map {
950970 enqueue('new_map', mut used, mut queue)
951971 }
972+ if needs_map_iteration_snapshot {
973+ for helper in ['map.clone', 'map__clone', 'map.free', 'map__free'] {
974+ enqueue(helper, mut used, mut queue)
975+ }
976+ }
952977}
953978
954979fn markused_call_lowers_to_join_path_single(a &flat.FlatAst, fn_node flat.Node, imports map[string]string) bool {
@@ -987,7 +1012,11 @@ fn enqueue_stringified_custom_str_method(expr_id flat.NodeId, cur_module string,
9871012 }
9881013 break
9891014 }
1015+ type_name := typ.name()
9901016 match typ {
1017+ types.Primitive, types.Rune, types.Char, types.ISize, types.USize, types.String {
1018+ enqueue_stringified_primitive_helpers(type_name, mut used, mut queue)
1019+ }
9911020 types.Enum {
9921021 enqueue_enum_str_method(typ.name, cur_module, tc, mut used, mut queue)
9931022 }
@@ -1001,6 +1030,40 @@ fn enqueue_stringified_custom_str_method(expr_id flat.NodeId, cur_module string,
10011030 }
10021031}
10031032
1033+fn enqueue_stringified_primitive_helpers(type_name string, mut used map[string]bool, mut queue []string) {
1034+ match type_name {
1035+ 'bool' {
1036+ enqueue('bool.str', mut used, mut queue)
1037+ }
1038+ 'rune', 'char' {
1039+ enqueue('rune.str', mut used, mut queue)
1040+ }
1041+ 'int', 'i8', 'i16', 'i32', 'i64' {
1042+ enqueue('${type_name}.str', mut used, mut queue)
1043+ enqueue(markused_c_name('${type_name}.str'), mut used, mut queue)
1044+ enqueue('strconv__format_int', mut used, mut queue)
1045+ }
1046+ 'isize' {
1047+ enqueue('strconv__format_int', mut used, mut queue)
1048+ }
1049+ 'u8', 'byte', 'u16', 'u32', 'usize' {
1050+ enqueue('strconv__format_uint', mut used, mut queue)
1051+ }
1052+ 'u64' {
1053+ enqueue('u64.str', mut used, mut queue)
1054+ enqueue(markused_c_name('u64.str'), mut used, mut queue)
1055+ enqueue('strconv__format_uint', mut used, mut queue)
1056+ }
1057+ 'f32' {
1058+ enqueue('strconv__f32_to_str_l', mut used, mut queue)
1059+ }
1060+ 'f64' {
1061+ enqueue('strconv__f64_to_str_l', mut used, mut queue)
1062+ }
1063+ else {}
1064+ }
1065+}
1066+
10041067// enqueue_enum_str_method supports enqueue enum str method handling for markused.
10051068fn enqueue_enum_str_method(type_name string, cur_module string, tc &types.TypeChecker, mut used map[string]bool, mut queue []string) {
10061069 for candidate in stringification_type_candidates(type_name, cur_module) {
@@ -1483,6 +1546,9 @@ fn (c &CallCollector) collect_calls(node &flat.Node, cur_module string, imports
14831546 .string_interp {
14841547 calls << 'string_plus_many'
14851548 }
1549+ .assign, .selector_assign, .index_assign {
1550+ c.collect_assign_operator_call(child, cur_module, mut calls)
1551+ }
14861552 .infix {
14871553 if child.op == .plus {
14881554 calls << 'string__plus'
@@ -1890,6 +1956,9 @@ fn (c &CallCollector) collect_top_level_expr_calls(id flat.NodeId, cur_module st
18901956 .string_interp {
18911957 calls << 'string_plus_many'
18921958 }
1959+ .assign, .selector_assign, .index_assign {
1960+ c.collect_assign_operator_call(child, cur_module, mut calls)
1961+ }
18931962 .infix {
18941963 if child.op == .plus {
18951964 calls << 'string__plus'
@@ -2157,6 +2226,19 @@ fn (c &CallCollector) collect_struct_operator_call(lhs_id flat.NodeId, op flat.O
21572226 c.add_operator_call_name(method_name, mut calls)
21582227}
21592228
2229+// collect_assign_operator_call adds operator overloads used through assignment operators.
2230+fn (c &CallCollector) collect_assign_operator_call(node &flat.Node, cur_module string, mut calls []string) {
2231+ op := markused_assign_operator_symbol(node.op) or { return }
2232+ mut i := 0
2233+ for i + 1 < node.children_count {
2234+ lhs_id := c.a.child(node, i)
2235+ if int(lhs_id) >= 0 {
2236+ c.collect_struct_operator_call(lhs_id, op, cur_module, mut calls)
2237+ }
2238+ i += 2
2239+ }
2240+}
2241+
21602242// struct_operator_call_name supports struct operator call name handling for CallCollector.
21612243fn (c &CallCollector) struct_operator_call_name(struct_type string, op flat.Op) ?string {
21622244 if op_name := markused_struct_operator_symbol(op) {
@@ -2181,6 +2263,20 @@ fn (c &CallCollector) struct_operator_call_name(struct_type string, op flat.Op)
21812263 return none
21822264}
21832265
2266+// markused_assign_operator_symbol maps assignment operators to their binary operator.
2267+fn markused_assign_operator_symbol(op flat.Op) ?flat.Op {
2268+ match op {
2269+ .plus_assign { return .plus }
2270+ .minus_assign { return .minus }
2271+ .mul_assign { return .mul }
2272+ .div_assign { return .div }
2273+ .mod_assign { return .mod }
2274+ else {}
2275+ }
2276+
2277+ return none
2278+}
2279+
21842280// markused_struct_operator_symbol supports markused struct operator symbol handling for markused.
21852281fn markused_struct_operator_symbol(op flat.Op) ?string {
21862282 match op {
@@ -625,6 +625,7 @@ fn (mut p Parser) fn_decl() flat.NodeId {
625625 mut name := ''
626626 mut receiver_name := ''
627627 mut receiver_type := ''
628+ mut receiver_is_mut := false
628629 mut is_method := false
629630
630631 // method receiver: fn (mut r Type) name()
@@ -634,6 +635,7 @@ fn (mut p Parser) fn_decl() flat.NodeId {
634635 mut is_mut := false
635636 if p.tok == .key_mut || p.tok == .key_shared {
636637 is_mut = p.tok == .key_mut
638+ receiver_is_mut = is_mut
637639 p.next()
638640 }
639641 receiver_name = p.expect_name()
@@ -648,7 +650,8 @@ fn (mut p Parser) fn_decl() flat.NodeId {
648650 if p.tok.is_overloadable() {
649651 op_name := overload_token_name(p.tok)
650652 p.next()
651- return p.fn_operator_overload(receiver_name, receiver_type, op_name)
653+ return p.fn_operator_overload(receiver_name, receiver_type, receiver_is_mut,
654+ op_name)
652655 }
653656 }
654657 }
@@ -669,7 +672,8 @@ fn (mut p Parser) fn_decl() flat.NodeId {
669672 clean_type := receiver_type.trim_left('&')
670673 name = '${clean_type}.${name}'
671674 }
672- return p.fn_decl_body(name, receiver_name, receiver_type, is_method, true)
675+ return p.fn_decl_body(name, receiver_name, receiver_type, receiver_is_mut,
676+ is_method, true)
673677 }
674678 // module.func or Type.static_method
675679 second := p.expect_name_or_keyword()
@@ -690,10 +694,10 @@ fn (mut p Parser) fn_decl() flat.NodeId {
690694 name = '${clean_type}.${name}'
691695 }
692696
693- return p.fn_decl_body(name, receiver_name, receiver_type, is_method, false)
697+ return p.fn_decl_body(name, receiver_name, receiver_type, receiver_is_mut, is_method, false)
694698}
695699
696-fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type string, op_name string) flat.NodeId {
700+fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type string, receiver_is_mut bool, op_name string) flat.NodeId {
697701 disable_body := p.disable_fn_body
698702 p.disable_fn_body = false
699703 // parse parameter
@@ -704,6 +708,7 @@ fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type strin
704708 kind: .param
705709 value: receiver_name
706710 typ: receiver_type
711+ op: if receiver_is_mut { .amp } else { .none }
707712 })
708713 // operator param
709714 if p.tok == .key_mut {
@@ -765,7 +770,7 @@ fn (mut p Parser) fn_operator_overload(receiver_name string, receiver_type strin
765770 return id
766771}
767772
768-fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type string, is_method bool, is_c_decl bool) flat.NodeId {
773+fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type string, receiver_is_mut bool, is_method bool, is_c_decl bool) flat.NodeId {
769774 // Capture & clear here so it applies only to this function (not nested closures
770775 // or a following declaration), and is cleared even on the extern/no-body path.
771776 disable_body := p.disable_fn_body
@@ -784,6 +789,7 @@ fn (mut p Parser) fn_decl_body(name string, receiver_name string, receiver_type
784789 kind: .param
785790 value: receiver_name
786791 typ: receiver_type
792+ op: if receiver_is_mut { .amp } else { .none }
787793 })
788794 }
789795 for p.tok != .rpar && p.tok != .eof {
@@ -908,6 +914,7 @@ fn (mut p Parser) parse_param_group(is_c_decl bool) []flat.NodeId {
908914 kind: .param
909915 value: ''
910916 typ: typ
917+ op: if is_mut { .amp } else { .none }
911918 })
912919 if p.tok == .comma {
913920 p.next()
@@ -923,6 +930,7 @@ fn (mut p Parser) parse_param_group(is_c_decl bool) []flat.NodeId {
923930 kind: .param
924931 value: ''
925932 typ: typ
933+ op: if is_mut { .amp } else { .none }
926934 })
927935 if p.tok == .comma {
928936 p.next()
@@ -950,6 +958,7 @@ fn (mut p Parser) parse_param_group(is_c_decl bool) []flat.NodeId {
950958 kind: .param
951959 value: name
952960 typ: typ
961+ op: if is_mut { .amp } else { .none }
953962 })
954963 }
955964 if p.tok == .comma {
@@ -1772,9 +1781,9 @@ fn (mut p Parser) parse_comptime_if() flat.NodeId {
17721781 p.next() // skip 'if'
17731782 cond := p.parse_comptime_cond()
17741783 if comptime_cond_has_type_test(cond) {
1775- p.skip_block()
1776- p.skip_comptime_else()
1777- return flat.empty_node
1784+ then_block := p.block_stmt()
1785+ else_block := p.parse_comptime_else()
1786+ return p.comptime_if_node(cond, then_block, else_block)
17781787 }
17791788 taken := eval_comptime_cond(p.prefs, cond)
17801789 if taken {
@@ -1810,9 +1819,9 @@ fn (mut p Parser) parse_top_level_comptime_if() flat.NodeId {
18101819 p.next() // skip 'if'
18111820 cond := p.parse_comptime_cond()
18121821 if comptime_cond_has_type_test(cond) {
1813- p.skip_block()
1814- p.skip_comptime_else()
1815- return flat.empty_node
1822+ then_block := p.top_level_block_stmt()
1823+ else_block := p.parse_top_level_comptime_else()
1824+ return p.comptime_if_node(cond, then_block, else_block)
18161825 }
18171826 taken := eval_comptime_cond(p.prefs, cond)
18181827 if taken {
@@ -1824,6 +1833,23 @@ fn (mut p Parser) parse_top_level_comptime_if() flat.NodeId {
18241833 return p.parse_top_level_comptime_else()
18251834}
18261835
1836+fn (mut p Parser) comptime_if_node(cond string, then_block flat.NodeId, else_block flat.NodeId) flat.NodeId {
1837+ mut ids := []flat.NodeId{cap: 2}
1838+ if int(then_block) >= 0 {
1839+ ids << then_block
1840+ }
1841+ if int(else_block) >= 0 {
1842+ ids << else_block
1843+ }
1844+ start := p.add_children(ids)
1845+ return p.a.add_node(flat.Node{
1846+ kind: .comptime_if
1847+ value: cond
1848+ children_start: start
1849+ children_count: flat.child_count(ids.len)
1850+ })
1851+}
1852+
18271853fn (mut p Parser) top_level_block_stmt() flat.NodeId {
18281854 ids := p.parse_top_level_block_body()
18291855 start := p.add_children(ids)
@@ -1853,21 +1879,20 @@ fn (mut p Parser) parse_top_level_block_body() []flat.NodeId {
18531879
18541880fn (mut p Parser) parse_comptime_cond() string {
18551881 mut cond := strings.new_builder(64)
1882+ mut prev_tok_str := ''
18561883 for p.tok != .lcbr && p.tok != .eof {
18571884 tok_str := p.comptime_cond_token_text()
1858- if cond.len > 0 && tok_str != '?' {
1885+ if cond.len > 0 && comptime_cond_needs_space(prev_tok_str, tok_str) {
18591886 cond.write_string(' ')
18601887 }
18611888 cond.write_string(tok_str)
1889+ prev_tok_str = tok_str
18621890 p.next()
18631891 }
18641892 return cond.str()
18651893}
18661894
18671895fn (p &Parser) comptime_cond_token_text() string {
1868- if p.lit.len > 0 {
1869- return p.lit
1870- }
18711896 if p.s.pos >= 0 && p.s.pos < p.s.src.len {
18721897 c := p.s.src[p.s.pos]
18731898 if c == `&` && p.s.pos + 1 < p.s.src.len && p.s.src[p.s.pos + 1] == `&` {
@@ -1876,6 +1901,10 @@ fn (p &Parser) comptime_cond_token_text() string {
18761901 if c == `|` && p.s.pos + 1 < p.s.src.len && p.s.src[p.s.pos + 1] == `|` {
18771902 return '||'
18781903 }
1904+ if c == `!` && p.s.pos + 2 < p.s.src.len && p.s.src[p.s.pos + 1] == `i`
1905+ && p.s.src[p.s.pos + 2] == `s` {
1906+ return '!is'
1907+ }
18791908 if c == `!` {
18801909 return '!'
18811910 }
@@ -1893,6 +1922,33 @@ fn (p &Parser) comptime_cond_token_text() string {
18931922 if tok == .not {
18941923 return '!'
18951924 }
1925+ if tok == .lpar {
1926+ return '('
1927+ }
1928+ if tok == .rpar {
1929+ return ')'
1930+ }
1931+ if tok == .key_is {
1932+ return 'is'
1933+ }
1934+ if tok == .not_is {
1935+ return '!is'
1936+ }
1937+ if tok == .dollar {
1938+ return '$'
1939+ }
1940+ if tok == .lsbr {
1941+ return '['
1942+ }
1943+ if tok == .rsbr {
1944+ return ']'
1945+ }
1946+ if tok == .dot {
1947+ return '.'
1948+ }
1949+ if tok == .comma {
1950+ return ','
1951+ }
18961952 if tok == .question {
18971953 return '?'
18981954 }
@@ -1902,9 +1958,28 @@ fn (p &Parser) comptime_cond_token_text() string {
19021958 if tok == .key_false {
19031959 return 'false'
19041960 }
1961+ if p.lit.len > 0 {
1962+ return p.lit
1963+ }
19051964 return ''
19061965}
19071966
1967+fn comptime_cond_needs_space(prev string, cur string) bool {
1968+ if prev == 'is' || prev == '!is' {
1969+ return true
1970+ }
1971+ if prev == '?' || prev == '!' {
1972+ return false
1973+ }
1974+ if cur == '?' || cur == ']' || cur == '[' || cur == '.' || cur == ',' {
1975+ return false
1976+ }
1977+ if prev == '$' || prev == '[' || prev == ']' || prev == '.' {
1978+ return false
1979+ }
1980+ return true
1981+}
1982+
19081983fn comptime_cond_has_type_test(cond string) bool {
19091984 return cond.contains(' is ') || cond.contains(' !is ')
19101985}
@@ -4542,14 +4617,28 @@ fn (mut p Parser) string_interp(first_part string, quote u8) flat.NodeId {
45424617 for int(p.tok) == 106 {
45434618 p.next() // skip $
45444619 p.check(.lcbr) // skip {
4545- ids << p.expr(.lowest)
4620+ expr_id := p.expr(.lowest)
4621+ mut part_id := expr_id
45464622 // format spec: :fmt
45474623 if p.tok == .colon {
45484624 p.next()
4625+ mut fmt := ''
45494626 for p.tok != .rcbr && p.tok != .eof {
4627+ fmt += if p.lit.len > 0 { p.lit } else { p.tok.str() }
45504628 p.next()
45514629 }
4630+ if fmt.len > 0 {
4631+ start := p.add_child(expr_id)
4632+ part_id = p.a.add_node(flat.Node{
4633+ kind: .directive
4634+ value: 'string_interp_format'
4635+ typ: fmt
4636+ children_start: start
4637+ children_count: 1
4638+ })
4639+ }
45524640 }
4641+ ids << part_id
45534642 p.check(.rcbr) // skip }
45544643 if int(p.tok) == 107 {
45554644 part := strip_interp_quotes(p.lit, quote)
@@ -4639,7 +4728,8 @@ fn (mut p Parser) array_literal() flat.NodeId {
46394728 if p.tok == .rsbr {
46404729 size_end := p.tok_pos
46414730 p.next()
4642- if p.tok == .name || p.tok == .amp || p.tok == .lsbr || p.tok == .question {
4731+ if p.tok == .name || p.tok == .amp || p.tok == .question
4732+ || (p.tok == .lsbr && p.current_lbr_starts_array_type()) {
46434733 // fixed array type: [N]Type. Use the literal node value for a plain integer
46444734 // size, but recover the full source text for a const expression (e.g.
46454735 // `[segs + 1]f32`) — an infix node has no `.value`, which would otherwise
@@ -4698,6 +4788,22 @@ fn (mut p Parser) array_literal() flat.NodeId {
46984788 })
46994789 }
47004790 // single-element array: [expr]
4791+ if p.tok == .not {
4792+ p.next()
4793+ start := p.add_child(ids[0])
4794+ lit := p.a.add_node(flat.Node{
4795+ kind: .array_literal
4796+ children_start: start
4797+ children_count: 1
4798+ })
4799+ pstart := p.add_child(lit)
4800+ return p.a.add_node(flat.Node{
4801+ kind: .postfix
4802+ op: .not
4803+ children_start: pstart
4804+ children_count: 1
4805+ })
4806+ }
47014807 start := p.add_children(ids)
47024808 return p.a.add_node(flat.Node{
47034809 kind: .array_literal
@@ -4715,15 +4821,27 @@ fn (mut p Parser) array_literal() flat.NodeId {
47154821 }
47164822 p.check(.rsbr)
47174823 // check for `!` (fixed array with values)
4824+ mut is_fixed_literal := false
47184825 if p.tok == .not {
47194826 p.next()
4827+ is_fixed_literal = true
47204828 }
47214829 start := p.add_children(ids)
4722- return p.a.add_node(flat.Node{
4830+ lit := p.a.add_node(flat.Node{
47234831 kind: .array_literal
47244832 children_start: start
47254833 children_count: flat.child_count(ids.len)
47264834 })
4835+ if is_fixed_literal {
4836+ pstart := p.add_child(lit)
4837+ return p.a.add_node(flat.Node{
4838+ kind: .postfix
4839+ op: .not
4840+ children_start: pstart
4841+ children_count: 1
4842+ })
4843+ }
4844+ return lit
47274845}
47284846
47294847// fn_literal supports fn literal handling for Parser.
@@ -4968,7 +5086,20 @@ fn (mut p Parser) peek_lbr_starts_array_type() bool {
49685086 if p.peek() != .lsbr {
49695087 return false
49705088 }
4971- mut idx := p.s.offset
5089+ return p.lbr_starts_array_type_from_offset(p.s.offset)
5090+}
5091+
5092+// current_lbr_starts_array_type reports whether the current `[` starts a nested
5093+// array type, not an index expression.
5094+fn (mut p Parser) current_lbr_starts_array_type() bool {
5095+ if p.tok != .lsbr {
5096+ return false
5097+ }
5098+ return p.lbr_starts_array_type_from_offset(p.s.offset)
5099+}
5100+
5101+fn (mut p Parser) lbr_starts_array_type_from_offset(offset int) bool {
5102+ mut idx := offset
49725103 for idx < p.s.src.len && (p.s.src[idx] == ` ` || p.s.src[idx] == `\t`
49735104 || p.s.src[idx] == `\n` || p.s.src[idx] == `\r`) {
49745105 idx++
@@ -4982,13 +5113,15 @@ fn (mut p Parser) peek_lbr_starts_array_type() bool {
49825113 depth--
49835114 if depth == 0 {
49845115 idx++
4985- for idx < p.s.src.len && (p.s.src[idx] == ` ` || p.s.src[idx] == `\t`
4986- || p.s.src[idx] == `\n` || p.s.src[idx] == `\r`) {
5116+ for idx < p.s.src.len && (p.s.src[idx] == ` ` || p.s.src[idx] == `\t`) {
49875117 idx++
49885118 }
49895119 if idx >= p.s.src.len {
49905120 return false
49915121 }
5122+ if p.s.src[idx] == `\n` || p.s.src[idx] == `\r` || p.s.src[idx] == `;` {
5123+ return false
5124+ }
49925125 next := p.s.src[idx]
49935126 return (next >= `a` && next <= `z`) || (next >= `A` && next <= `Z`)
49945127 || next == `_` || next == `&` || next == `?` || next == `!`
@@ -5279,6 +5412,44 @@ fn unescape_string(s string) string {
52795412 mut i := 0
52805413 for i < s.len {
52815414 if s[i] == `\\` && i + 1 < s.len {
5415+ if s[i + 1] == `\n` {
5416+ i += 2
5417+ for i < s.len && (s[i] == ` ` || s[i] == `\t` || s[i] == `\r`) {
5418+ i++
5419+ }
5420+ continue
5421+ }
5422+ if s[i + 1] == `\r` && i + 2 < s.len && s[i + 2] == `\n` {
5423+ i += 3
5424+ for i < s.len && (s[i] == ` ` || s[i] == `\t`) {
5425+ i++
5426+ }
5427+ continue
5428+ }
5429+ if s[i + 1] == `x` && i + 3 < s.len {
5430+ if code := parse_fixed_hex(s, i + 2, 2) {
5431+ unsafe {
5432+ buf[j] = u8(code)
5433+ }
5434+ j++
5435+ i += 4
5436+ continue
5437+ }
5438+ }
5439+ if s[i + 1] == `u` && i + 5 < s.len {
5440+ if code := parse_fixed_hex(s, i + 2, 4) {
5441+ j = write_utf8_codepoint(buf, j, u32(code))
5442+ i += 6
5443+ continue
5444+ }
5445+ }
5446+ if s[i + 1] == `U` && i + 9 < s.len {
5447+ if code := parse_fixed_hex(s, i + 2, 8) {
5448+ j = write_utf8_codepoint(buf, j, u32(code))
5449+ i += 10
5450+ continue
5451+ }
5452+ }
52825453 c := match s[i + 1] {
52835454 `n` { u8(`\n`) }
52845455 `t` { u8(`\t`) }
@@ -5322,6 +5493,56 @@ fn unescape_string(s string) string {
53225493 }
53235494}
53245495
5496+fn parse_fixed_hex(s string, start int, len int) ?u32 {
5497+ mut code := u32(0)
5498+ for i in 0 .. len {
5499+ if start + i >= s.len {
5500+ return none
5501+ }
5502+ n := hex_digit_value(s[start + i]) or { return none }
5503+ code = (code << 4) | u32(n)
5504+ }
5505+ return code
5506+}
5507+
5508+fn hex_digit_value(c u8) ?int {
5509+ if c >= `0` && c <= `9` {
5510+ return int(c - `0`)
5511+ }
5512+ if c >= `a` && c <= `f` {
5513+ return int(c - `a`) + 10
5514+ }
5515+ if c >= `A` && c <= `F` {
5516+ return int(c - `A`) + 10
5517+ }
5518+ return none
5519+}
5520+
5521+fn write_utf8_codepoint(buf &u8, j int, code u32) int {
5522+ unsafe {
5523+ if code <= 0x7f {
5524+ buf[j] = u8(code)
5525+ return j + 1
5526+ }
5527+ if code <= 0x7ff {
5528+ buf[j] = u8(0xc0 | (code >> 6))
5529+ buf[j + 1] = u8(0x80 | (code & 0x3f))
5530+ return j + 2
5531+ }
5532+ if code <= 0xffff {
5533+ buf[j] = u8(0xe0 | (code >> 12))
5534+ buf[j + 1] = u8(0x80 | ((code >> 6) & 0x3f))
5535+ buf[j + 2] = u8(0x80 | (code & 0x3f))
5536+ return j + 3
5537+ }
5538+ buf[j] = u8(0xf0 | (code >> 18))
5539+ buf[j + 1] = u8(0x80 | ((code >> 12) & 0x3f))
5540+ buf[j + 2] = u8(0x80 | ((code >> 6) & 0x3f))
5541+ buf[j + 3] = u8(0x80 | (code & 0x3f))
5542+ return j + 4
5543+ }
5544+}
5545+
53255546fn is_builtin_type(name string) bool {
53265547 return name in ['int', 'i8', 'i16', 'i32', 'i64', 'u8', 'u16', 'u32', 'u64', 'f32', 'f64',
53275548 'byte', 'bool', 'string', 'rune', 'char', 'voidptr', 'charptr', 'byteptr', 'usize', 'isize',
@@ -193,8 +193,10 @@ pub fn get_v_files_from_dir(dir string, user_defines []string, target_os string)
193193 return []string{}
194194 }
195195 all_files := os.ls(dir) or { return []string{} }
196+ mut sorted_files := all_files.clone()
197+ sorted_files.sort()
196198 mut has_os_specific := map[string]bool{}
197- for file in all_files {
199+ for file in sorted_files {
198200 if !file.ends_with('.v') || file.ends_with('.js.v') || file.contains('_test.') {
199201 continue
200202 }
@@ -206,30 +208,35 @@ pub fn get_v_files_from_dir(dir string, user_defines []string, target_os string)
206208 }
207209 }
208210 mut v_files := []string{}
209- for file in all_files {
210- if !file.ends_with('.v') || file.ends_with('.js.v') || file.contains('_test.') {
211- continue
212- }
213- if file_has_incompatible_os_suffix(file, target_os) {
214- continue
215- }
216- if base := default_file_base(file) {
217- if has_os_specific[base] {
211+ for backend_specific in [false, true] {
212+ for file in sorted_files {
213+ if file.ends_with('.c.v') != backend_specific {
218214 continue
219215 }
220- }
221- if file.contains('_notd_') {
222- feature := extract_define_feature(file, '_notd_')
223- if feature.len > 0 && feature in user_defines {
216+ if !file.ends_with('.v') || file.ends_with('.js.v') || file.contains('_test.') {
224217 continue
225218 }
226- } else if file.contains('_d_') {
227- feature := extract_define_feature(file, '_d_')
228- if feature.len == 0 || feature !in user_defines {
219+ if file_has_incompatible_os_suffix(file, target_os) {
229220 continue
230221 }
222+ if base := default_file_base(file) {
223+ if has_os_specific[base] {
224+ continue
225+ }
226+ }
227+ if file.contains('_notd_') {
228+ feature := extract_define_feature(file, '_notd_')
229+ if feature.len > 0 && feature in user_defines {
230+ continue
231+ }
232+ } else if file.contains('_d_') {
233+ feature := extract_define_feature(file, '_d_')
234+ if feature.len == 0 || feature !in user_defines {
235+ continue
236+ }
237+ }
238+ v_files << os.join_path_single(dir, file)
231239 }
232- v_files << os.join_path_single(dir, file)
233240 }
234241 return v_files
235242}
@@ -397,7 +404,19 @@ pub fn comptime_flag_value(p &Preferences, name string) bool {
397404 return tos == 'macos' || tos == 'freebsd' || tos == 'openbsd' || tos == 'netbsd'
398405 || tos == 'dragonfly'
399406 }
400- 'x64', 'amd64' {
407+ 'x64' {
408+ $if x64 {
409+ return true
410+ }
411+ return false
412+ }
413+ 'x32' {
414+ $if x32 {
415+ return true
416+ }
417+ return false
418+ }
419+ 'amd64' {
401420 $if amd64 {
402421 return true
403422 }
@@ -2,7 +2,21 @@
22
33import os
44
5-const total_steps = 6
5+const total_steps = 7
6+const temp_prefix = 'v3_test_all'
7+const requested_vlib_tests = [
8+ 'vlib/builtin/string_test.v',
9+ 'vlib/math/math_test.v',
10+ 'vlib/builtin/array_test.v',
11+ 'vlib/math/complex/complex_test.v',
12+ 'vlib/builtin/map_test.v',
13+ 'vlib/crypto/hmac/hmac_test.v',
14+ 'vlib/crypto/sha3/sha3_test.v',
15+ 'vlib/time/time_test.v',
16+ 'vlib/os/process_test.v',
17+ 'vlib/os/file_test.v',
18+ 'vlib/arrays/arrays_test.v',
19+]
620
721struct Config {
822 vexe string
@@ -51,13 +65,22 @@ fn main() {
5165 section(2, 'Build v3')
5266 run('${q(cfg.vexe)} -o ${q(v3_bin)} ${q(cfg.v3_src)}')
5367
54- section(3, 'C backend hello world')
68+ section(3, 'Requested vlib tests')
69+ for rel_path in requested_vlib_tests {
70+ test_path := os.join_path(cfg.repo_root, rel_path)
71+ test_bin := temp_path(rel_path.replace('/', '_').replace('.v', ''))
72+ run('${q(v3_bin)} ${q(test_path)} -o ${q(test_bin)}')
73+ run(q(test_bin))
74+ cleanup_files([test_bin, test_bin + '.c'])
75+ }
76+
77+ section(4, 'C backend hello world')
5578 hello_v := os.join_path(cfg.tests_dir, 'hello.v')
5679 run('${q(v3_bin)} ${cfg.c99_flag} ${q(hello_v)} -b c -o ${q(hello_c_bin)}')
5780 run(q(hello_c_bin))
5881 cleanup_files([hello_c_bin, hello_c_bin + '.c'])
5982
60- section(4, 'ARM64 self-host hello world')
83+ section(5, 'ARM64 self-host hello world')
6184 if cfg.c99 {
6285 println(' Skipping ARM64 self-host in C99 mode (-c99 applies to the C backend)')
6386 } else if cfg.host_backend == 'arm64' && cfg.host_os == 'macos' {
@@ -69,7 +92,7 @@ fn main() {
6992 println(' Skipping ARM64 self-host on ${cfg.host_os}/${cfg.host_backend} host (Mach-O only)')
7093 }
7194
72- section(5, 'Self-host chain (v3->v4->v5->v6)')
95+ section(6, 'Self-host chain (v3->v4->v5->v6)')
7396 println(' Building v4 from v3...')
7497 run('${q(v3_bin)} ${cfg.c99_flag} --no-parallel -selfhost -o ${q(v4_bin)} ${q(cfg.v3_src)}')
7598 println(' Building v5 from v4...')
@@ -81,7 +104,7 @@ fn main() {
81104 println(' v5.c=v6.c (${converged_size} bytes) - chain converged')
82105 cleanup_files([v4_bin, v4_bin + '.c', v5_bin, v5_bin + '.c', v6_bin, v6_bin + '.c'])
83106
84- section(6, 'Language feature parity')
107+ section(7, 'Language feature parity')
85108 lang_v := os.join_path(cfg.tests_dir, 'test_all_lang_features.v')
86109 lang_out := os.join_path(cfg.tests_dir, 'test_all_lang_features.out')
87110 run('${q(v3_bin)} ${cfg.c99_flag} ${q(lang_v)} -b c -o ${q(v3_lang_bin)}')
@@ -51,6 +51,7 @@ fn main() {
5151 _ = C.take_ptr(&native)
5252 _ = C.take_named(ptr)
5353 _ = C.take_void(voidptr(ptr))
54+ _ = C.take_void(voidptr(&ptr))
5455 _ = C.take_primitive(1, u64(2))
5556 _ = C.take_multi(pptr)
5657 _ = C.take_mixed(ptr, voidptr(ptr), 3, pptr)
@@ -76,6 +77,7 @@ fn main() {
7677 assert generated.contains('take_ptr(&native)'), generated
7778 assert generated.contains('take_named(ptr)'), generated
7879 assert generated.contains('take_void((void*)(ptr))'), generated
80+ assert generated.contains('take_void((void*)(&ptr))'), generated
7981 assert generated.contains('take_multi(pptr)'), generated
8082 assert generated.contains('take_mixed(ptr, (void*)(ptr), 3, pptr)'), generated
8183 fn_ptr_idx := generated.index('typedef int (*_fn_ptr_') or { -1 }
@@ -989,6 +989,31 @@ fn main() {
989989 assert compile.exit_code == 0, compile.output
990990}
991991
992+// test_print_signed_width_compile_keeps_str_runtime_helpers validates this v3 regression case.
993+fn test_print_signed_width_compile_keeps_str_runtime_helpers() {
994+ v3_bin := build_v3_bin('print_signed_width_test')
995+
996+ src := os.join_path(os.temp_dir(), 'v3_markused_print_signed_width_input.v')
997+ bin := os.join_path(os.temp_dir(), 'v3_markused_print_signed_width_input')
998+ os.write_file(src, "
999+fn main() {
1000+ println(i8(-5))
1001+ println(i16(-300))
1002+ println(i32(-70000))
1003+ println(i64(-5000000000))
1004+ println('\${i64(42)}')
1005+}
1006+") or {
1007+ panic(err)
1008+ }
1009+ compile := os.execute('${v3_bin} -o ${bin} ${src}')
1010+ assert compile.exit_code == 0, compile.output
1011+ assert !compile.output.contains('implicit declaration'), compile.output
1012+ run := os.execute(bin)
1013+ assert run.exit_code == 0, run.output
1014+ assert run.output.trim_space() == '-5\n-300\n-70000\n-5000000000\n42', run.output
1015+}
1016+
9921017// test_string_plus_compile_keeps_plus_runtime_helper validates this v3 regression case.
9931018fn test_string_plus_compile_keeps_plus_runtime_helper() {
9941019 v3_bin := build_v3_bin('string_plus_test')
@@ -43,6 +43,14 @@ fn test_optional_if_expr_codegen_initializes_optional_temp() {
4343 assert out == 'ok'
4444}
4545
46+// test_nested_local_optional_primitive_typedef validates this v3 regression case.
47+fn test_nested_local_optional_primitive_typedef() {
48+ v3_bin := build_v3()
49+ out := run_good(v3_bin, 'nested_local_optional_primitive_typedef_input',
50+ "fn main() {\n\txs := [?f32(1.5)]\n\tassert xs.len == 1\n\tprintln('ok')\n}\n")
51+ assert out == 'ok'
52+}
53+
4654// test_optional_clone_return_wraps_payload validates this v3 regression case.
4755fn test_optional_clone_return_wraps_payload() {
4856 v3_bin := build_v3()
@@ -100,17 +100,16 @@ fn test_filelock_helpers_are_inlined_in_generated_c() {
100100 c_source := gen_c(v3_bin, 'filelock_helpers_inline',
101101 'import os.filelock\n\nfn C.v_filelock_lock(i32, i32, i32, u64, u64) i32\nfn C.v_filelock_unlock(i32, u64, u64) i32\n\nfn main() {\n\t_ = filelock.LockMode.exclusive\n\t_ = C.v_filelock_lock(i32(-1), 1, 1, u64(0), u64(0))\n\t_ = C.v_filelock_unlock(i32(-1), u64(0), u64(0))\n}\n')
102102 assert !c_source.contains('filelock_helpers.h')
103- assert c_source.contains('static int v_filelock_lock(')
104- assert c_source.contains('static int v_filelock_unlock(')
103+ assert c_source.contains('static inline int v_filelock_lock(')
104+ assert c_source.contains('static inline int v_filelock_unlock(')
105105 assert c_source.contains('#ifndef V_OS_FILELOCK_HELPERS_H')
106106 assert !c_source.contains('v_filelock_status')
107107 status_source := gen_c(v3_bin, 'filelock_custom_prefix_decl',
108108 'import os.filelock\n\nfn C.v_filelock_lock(i32, i32, i32, u64, u64) i32\nfn C.v_filelock_unlock(i32, u64, u64) i32\nfn C.v_filelock_status() int\n\nfn main() {\n\t_ = filelock.LockMode.exclusive\n\t_ = C.v_filelock_lock(i32(-1), 1, 1, u64(0), u64(0))\n\t_ = C.v_filelock_status()\n}\n')
109109 assert status_source.contains('int v_filelock_status(')
110- user_name_source := gen_c(v3_bin, 'filelock_user_names_not_helpers',
110+ out := run_good(v3_bin, 'filelock_user_names_not_helpers',
111111 'fn v_filelock_lock() int {\n\treturn 3\n}\n\nfn v_filelock_unlock() int {\n\treturn 4\n}\n\nfn main() {\n\tprintln(int_str(v_filelock_lock() + v_filelock_unlock()))\n}\n')
112- assert !user_name_source.contains('static int v_filelock_lock(')
113- assert !user_name_source.contains('static int v_filelock_unlock(')
112+ assert out == '7'
114113}
115114
116115fn test_assoc_return_runs_defers() {
@@ -240,7 +239,7 @@ fn test_map_builtin_method_fallback_checks_arguments() {
240239 v3_bin := build_v3()
241240 run_bad(v3_bin, 'map_keys_rejects_extra_arg',
242241 'fn extra_arg() int {\n\treturn 1\n}\n\nfn main() {\n\tmut m := map[string]int{}\n\t_ := m.keys(extra_arg())\n}\n',
243- 'argument count mismatch for `m.keys`: expected 1, got 2')
242+ 'argument count mismatch for `m.keys`: expected 0, got 1')
244243 run_bad(v3_bin, 'map_delete_rejects_bad_key_type',
245244 'fn main() {\n\tmut m := map[string]int{}\n\tm.delete(123)\n}\n',
246245 'cannot use `int` as argument 2 to `m.delete`; expected `string`')
@@ -285,3 +284,61 @@ fn test_map_builtin_method_fallback_checks_arguments() {
285284 }, 'main.v')
286285 assert module_collection_out == '31\n42\n53'
287286}
287+
288+fn test_runtime_inits_run_before_module_init() {
289+ v3_bin := build_v3()
290+ out := run_good_project(v3_bin, 'runtime_inits_before_module_init', {
291+ 'main.v': 'module main\n\nimport moda\n\nfn main() {\n\tprintln(int_str(moda.const_seen()))\n\tprintln(int_str(moda.global_seen()))\n}\n'
292+ 'moda/moda.v': "module moda\n\nconst const_map = map[string]int{\n\t'const': 5\n}\n\n__global (\n\tglobal_map = map[string]int{\n\t\t'global': 7\n\t}\n\tseen_const int\n\tseen_global int\n)\n\nfn init() {\n\tseen_const = const_map['const']\n\tseen_global = global_map['global']\n}\n\npub fn const_seen() int {\n\treturn seen_const\n}\n\npub fn global_seen() int {\n\treturn seen_global\n}\n"
293+ }, 'main.v')
294+ assert out == '5\n7'
295+}
296+
297+fn test_string_index_type_is_u8() {
298+ v3_bin := build_v3()
299+ out := run_good(v3_bin, 'string_index_type_is_u8',
300+ "fn main() {\n\ts := 'ABC'\n\tprintln(typeof(s[0]).name)\n\tprintln('\${s[2]}')\n}\n")
301+ assert out == 'u8\n67'
302+}
303+
304+fn test_f32_map_and_fixed_array_stringification() {
305+ v3_bin := build_v3()
306+ out := run_good(v3_bin, 'f32_map_stringification',
307+ "fn main() {\n\tm := {\n\t\t'a': f32(1.5)\n\t}\n\tprintln(m)\n\tfixed := [f32(1.5), f32(2.25)]!\n\tmf := {\n\t\t'x': fixed\n\t}\n\tprintln(mf)\n}\n")
308+ assert out == "{'a': 1.5}\n{'x': [1.5, 2.25]}"
309+}
310+
311+fn test_u8_map_stringification_is_numeric() {
312+ v3_bin := build_v3()
313+ out := run_good(v3_bin, 'u8_map_stringification',
314+ "fn main() {\n\tkeys := {\n\t\tu8(23): 'x'\n\t}\n\tvals := {\n\t\t'x': u8(23)\n\t}\n\tboth := {\n\t\tu8(65): u8(10)\n\t}\n\tprintln(keys)\n\tprintln(vals)\n\tprintln(both)\n}\n")
315+ assert out == "{23: 'x'}\n{'x': 23}\n{65: 10}"
316+}
317+
318+fn test_map_equality_uses_semantic_value_comparison() {
319+ v3_bin := build_v3()
320+ out := run_good(v3_bin, 'map_semantic_value_equality',
321+ "struct Item {\n\tname string\n\tparts []string\n}\n\nfn join(a string, b string) string {\n\treturn a + b\n}\n\nfn main() {\n\tleft := {\n\t\t'x': Item{\n\t\t\tname: 'hello'.clone()\n\t\t\tparts: ['ab'.clone()]\n\t\t}\n\t}\n\tright := {\n\t\t'x': Item{\n\t\t\tname: join('he', 'llo')\n\t\t\tparts: [join('a', 'b')]\n\t\t}\n\t}\n\tarr_left := {\n\t\t'y': ['cd'.clone()]\n\t}\n\tarr_right := {\n\t\t'y': [join('c', 'd')]\n\t}\n\tprintln(left == right)\n\tprintln(left != right)\n\tprintln(arr_left == arr_right)\n}\n")
322+ assert out == 'true\nfalse\ntrue'
323+}
324+
325+fn test_array_equality_marks_struct_operator_used() {
326+ v3_bin := build_v3()
327+ out := run_good(v3_bin, 'array_eq_struct_operator_used',
328+ 'struct Item {\n\tvalue int\n}\n\nfn (a Item) == (b Item) bool {\n\treturn a.value % 10 == b.value % 10\n}\n\nfn main() {\n\tleft := [Item{value: 12}]\n\tright := [Item{value: 2}]\n\tprintln(left == right)\n}\n')
329+ assert out == 'true'
330+}
331+
332+fn test_zero_padded_interpolation_preserves_wide_integers() {
333+ v3_bin := build_v3()
334+ out := run_good(v3_bin, 'wide_zero_padded_interpolation',
335+ "fn main() {\n\tbig := i64(5000000000)\n\tubig := u64(18446744073709551615)\n\tsmall := u64(42)\n\tprintln('\${big:012d}')\n\tprintln('\${ubig:020d}')\n\tprintln('\${small:08d}')\n}\n")
336+ assert out == '005000000000\n18446744073709551615\n00000042'
337+}
338+
339+fn test_formatted_interpolation_rune_and_long_float() {
340+ v3_bin := build_v3()
341+ out := run_good(v3_bin, 'formatted_interpolation_rune_and_long_float',
342+ "fn main() {\n\tr := '\${rune(0x20ac):c}'\n\tprintln(int_str(r.len))\n\tprintln(int_str(int(r[0])) + ',' + int_str(int(r[1])) + ',' + int_str(int(r[2])))\n\tlong := '\${1.0:.200f}'\n\tprintln(int_str(long.len))\n\tprintln(int_str(int(long[0])) + ',' + int_str(int(long[1])) + ',' + int_str(int(long[2])) + ',' + int_str(int(long[long.len - 1])))\n}\n")
343+ assert out == '3\n226,130,172\n202\n49,46,48,48'
344+}
@@ -0,0 +1,164 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3_review_checker() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_review_checker_regressions_test')
11+ build := os.execute('${vexe} -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
12+ assert build.exit_code == 0, build.output
13+ return v3_bin
14+}
15+
16+fn run_bad(v3_bin string, name string, src string, expected string) {
17+ bad_src := os.join_path(os.temp_dir(), 'v3_${name}.v')
18+ os.write_file(bad_src, src) or { panic(err) }
19+ bad_bin := os.join_path(os.temp_dir(), 'v3_${name}')
20+ result := os.execute('${v3_bin} ${bad_src} -b c -o ${bad_bin}')
21+ assert result.exit_code != 0, '${name}: expected failure, got success\n${result.output}'
22+ assert result.output.contains(expected), '${name}: expected `${expected}` in\n${result.output}'
23+ assert !result.output.contains('C compilation failed'), '${name}: reached C compilation\n${result.output}'
24+}
25+
26+fn run_good(v3_bin string, name string, src string) string {
27+ good_src := os.join_path(os.temp_dir(), 'v3_${name}.v')
28+ os.write_file(good_src, src) or { panic(err) }
29+ good_bin := os.join_path(os.temp_dir(), 'v3_${name}')
30+ compile := os.execute('${v3_bin} ${good_src} -b c -o ${good_bin}')
31+ assert compile.exit_code == 0, '${name}: compile failed\n${compile.output}'
32+ assert !compile.output.contains('C compilation failed'), '${name}: C compilation failed\n${compile.output}'
33+ run := os.execute(good_bin)
34+ assert run.exit_code == 0, '${name}: run failed\n${run.output}'
35+ return run.output.trim_space()
36+}
37+
38+fn test_reject_pointer_expressions_for_value_returns() {
39+ v3_bin := build_v3_review_checker()
40+ run_bad(v3_bin, 'bad_return_pointer_to_value',
41+ 'fn f() int {\n\tx := 1\n\treturn &x\n}\nfn main() {}\n', 'cannot return `&int` as `int`')
42+ run_bad(v3_bin, 'bad_result_return_pointer_to_value',
43+ 'fn f() !int {\n\tx := 1\n\treturn &x\n}\nfn main() {}\n', 'cannot return `&int` as `!int`')
44+}
45+
46+fn test_restrict_synthetic_hex_fallback_receivers() {
47+ v3_bin := build_v3_review_checker()
48+ run_bad(v3_bin, 'bad_struct_hex_method', 'struct S {}\nfn main() {\n\t_ := S{}.hex()\n}\n',
49+ 'unknown function')
50+ run_bad(v3_bin, 'bad_int_array_hex_method', 'fn main() {\n\t_ := [1, 2].hex()\n}\n',
51+ 'unknown function')
52+ run_bad(v3_bin, 'bad_map_hex_method',
53+ 'fn main() {\n\tm := map[string]int{}\n\t_ := m.hex()\n}\n', 'unknown function')
54+ run_bad(v3_bin, 'bad_float_hex_method', 'fn main() {\n\t_ := f32(1.5).hex()\n}\n',
55+ 'unknown function')
56+ run_bad(v3_bin, 'bad_pointer_hex_method',
57+ 'fn main() {\n\tx := 1\n\tp := &x\n\t_ := p.hex()\n}\n', 'unknown function')
58+ run_bad(v3_bin, 'bad_numeric_hex_arg',
59+ 'fn side_effect() int {\n\treturn 1\n}\n\nfn main() {\n\t_ := u8(1).hex(side_effect())\n}\n',
60+ 'argument count mismatch')
61+ out := run_good(v3_bin, 'supported_hex_methods',
62+ "fn main() {\n\tprintln(u8(15).hex())\n\tprintln(i64(255).hex())\n\tprintln([u8(1), 15, 255].hex())\n\tprintln(char(65).hex())\n\tprintln(`A`.hex())\n\tprintln('abc'.hex())\n}\n")
63+ assert out == '0f\nff\n010fff\n41\n41\n616263'
64+}
65+
66+fn test_auto_str_rejects_arguments() {
67+ v3_bin := build_v3_review_checker()
68+ run_bad(v3_bin, 'bad_auto_str_arg',
69+ 'struct S {\n\tx int\n}\n\nfn side_effect() int {\n\treturn 1\n}\n\nfn main() {\n\t_ := S{\n\t\tx: 1\n\t}.str(side_effect())\n}\n',
70+ 'argument count mismatch')
71+}
72+
73+fn test_pointer_hex_receiver_methods_are_allowed() {
74+ v3_bin := build_v3_review_checker()
75+ out := run_good(v3_bin, 'good_pointer_hex_receiver_method',
76+ "struct S {\n\tvalue int\n}\n\nfn (s &S) hex() string {\n\treturn 'ptr:' + int_str(s.value)\n}\n\nfn main() {\n\ts := S{\n\t\tvalue: 7\n\t}\n\tp := &s\n\tprintln(p.hex())\n}\n")
77+ assert out == 'ptr:7'
78+}
79+
80+fn test_map_keys_and_values_reject_arguments() {
81+ v3_bin := build_v3_review_checker()
82+ run_bad(v3_bin, 'bad_map_keys_arg',
83+ 'fn main() {\n\tm := map[string]int{}\n\t_ := m.keys(123)\n}\n', 'argument count mismatch')
84+ run_bad(v3_bin, 'bad_map_values_arg',
85+ "fn main() {\n\tm := map[string]int{}\n\t_ := m.values('x')\n}\n",
86+ 'argument count mismatch')
87+}
88+
89+fn test_array_insert_and_prepend_reject_wrong_arity() {
90+ v3_bin := build_v3_review_checker()
91+ run_bad(v3_bin, 'bad_array_prepend_missing_arg',
92+ 'fn main() {\n\tmut a := [1, 2]\n\ta.prepend()\n}\n', 'argument count mismatch')
93+ run_bad(v3_bin, 'bad_array_prepend_extra_arg',
94+ 'fn side_effect() int {\n\treturn 3\n}\nfn main() {\n\tmut a := [1, 2]\n\ta.prepend(0, side_effect())\n}\n',
95+ 'argument count mismatch')
96+ run_bad(v3_bin, 'bad_array_insert_missing_arg',
97+ 'fn main() {\n\tmut a := [1, 2]\n\ta.insert(0)\n}\n', 'argument count mismatch')
98+ run_bad(v3_bin, 'bad_array_insert_extra_arg',
99+ 'fn side_effect() int {\n\treturn 3\n}\nfn main() {\n\tmut a := [1, 2]\n\ta.insert(0, 1, side_effect())\n}\n',
100+ 'argument count mismatch')
101+ run_bad(v3_bin, 'bad_array_prepend_arg_type',
102+ "fn main() {\n\tmut a := [1, 2]\n\ta.prepend('x')\n}\n", 'cannot use')
103+ run_bad(v3_bin, 'bad_array_insert_index_type',
104+ "fn main() {\n\tmut a := [1, 2]\n\ta.insert('0', 3)\n}\n", 'cannot use')
105+ run_bad(v3_bin, 'bad_array_insert_value_type',
106+ "fn main() {\n\tmut a := [1, 2]\n\ta.insert(0, 'x')\n}\n", 'cannot use')
107+ run_bad(v3_bin, 'bad_array_prepend_many_arg_type',
108+ "fn main() {\n\tmut a := [1, 2]\n\ta.prepend(['x'])\n}\n", 'cannot use')
109+ run_bad(v3_bin, 'bad_array_insert_many_arg_type',
110+ "fn main() {\n\tmut a := [1, 2]\n\ta.insert(0, ['x'])\n}\n", 'cannot use')
111+}
112+
113+fn test_array_insert_and_prepend_accept_many_operands() {
114+ v3_bin := build_v3_review_checker()
115+ out := run_good(v3_bin, 'good_array_insert_prepend_many_operands',
116+ "type Strings = []string\n\nfn main() {\n\tmut a := [3, 4]\n\ta.insert(0, [1, 2])\n\tb := [5, 6]\n\ta.insert(1, b)\n\ta.prepend([0])\n\tfixed := [7, 8]!\n\ta.insert(a.len, fixed)\n\tassert a == [0, 1, 5, 6, 2, 3, 4, 7, 8]\n\tmut strs := Strings(['hi'])\n\tstrs.insert(0, ['there'])\n\tstrs.prepend(['hello'])\n\tassert strs == ['hello', 'there', 'hi']\n\tprintln('ok')\n}\n")
117+ assert out == 'ok'
118+}
119+
120+fn test_comptime_if_selected_bodies_are_checked() {
121+ v3_bin := build_v3_review_checker()
122+ run_bad(v3_bin, 'bad_concrete_comptime_if_selected_call',
123+ 'fn main() {\n\t$if int is int {\n\t\tmissing_selected_symbol()\n\t}\n}\n',
124+ 'unknown function `missing_selected_symbol`')
125+ out := run_good(v3_bin, 'good_generic_comptime_if_unselected_branch_is_not_checked',
126+ "fn ok() {}\n\nfn f[T]() {\n\t$if T is int {\n\t\tok()\n\t} $else {\n\t\tonly_for_other_t()\n\t}\n}\n\nfn main() {\n\tf[int]()\n\tprintln('ok')\n}\n")
127+ assert out == 'ok'
128+}
129+
130+fn test_explicit_generic_calls_use_all_type_arguments() {
131+ v3_bin := build_v3_review_checker()
132+ out := run_good(v3_bin, 'good_multi_explicit_generic_call',
133+ "struct Pair[A, B] {\n\tleft A\n\tright B\n}\n\nfn make_pair[A, B]() Pair[A, B] {\n\treturn Pair[A, B]{}\n}\n\nfn expect_pair(p Pair[int, string]) string {\n\treturn 'ok'\n}\n\nfn main() {\n\tp := make_pair[int, string]()\n\tprintln(expect_pair(p))\n}\n")
134+ assert out == 'ok'
135+ nested := run_good(v3_bin, 'good_nested_explicit_generic_call',
136+ "struct Pair[A, B] {\n\tleft A\n\tright B\n}\n\nstruct Box[T] {\n\tvalue T\n}\n\nfn wrap[T]() Box[T] {\n\treturn Box[T]{}\n}\n\nfn expect_box(b Box[Pair[int, string]]) string {\n\treturn 'ok'\n}\n\nfn main() {\n\tb := wrap[Pair[int, string]]()\n\tprintln(expect_box(b))\n}\n")
137+ assert nested == 'ok'
138+ run_bad(v3_bin, 'bad_explicit_generic_too_many_type_args',
139+ 'fn id[T](x T) T {\n\treturn x\n}\n\nfn main() {\n\t_ := id[int, string](1)\n}\n',
140+ 'generic argument count mismatch')
141+}
142+
143+fn test_reject_escaping_capturing_fn_literals() {
144+ v3_bin := build_v3_review_checker()
145+ run_bad(v3_bin, 'bad_return_capturing_fn_literal',
146+ 'fn make(x int) fn () int {\n\treturn fn [x] () int {\n\t\treturn x\n\t}\n}\nfn main() {}\n',
147+ 'capturing fn literal cannot be stored or returned')
148+ run_bad(v3_bin, 'bad_store_capturing_fn_literal_alias',
149+ 'fn main() {\n\tx := 1\n\tf := fn [x] () int {\n\t\treturn x\n\t}\n\tmut cbs := []fn () int{}\n\tcbs << f\n}\n',
150+ 'capturing fn literal cannot be stored or returned')
151+}
152+
153+fn test_generic_functions_report_missing_return() {
154+ v3_bin := build_v3_review_checker()
155+ run_bad(v3_bin, 'bad_generic_missing_return', 'fn f[T]() int {\n}\nfn main() {}\n',
156+ 'missing return at end of function `f`')
157+}
158+
159+fn test_local_identifiers_shadow_module_consts() {
160+ v3_bin := build_v3_review_checker()
161+ out := run_good(v3_bin, 'good_const_shadowed_by_param_and_local',
162+ "const shadowed_value = 'const'\n\nfn param_shadow(shadowed_value int) int {\n\treturn shadowed_value + 1\n}\n\nfn local_shadow() int {\n\tshadowed_value := 2\n\treturn shadowed_value + 1\n}\n\nfn main() {\n\tprintln(int_str(param_shadow(1)))\n\tprintln(int_str(local_shadow()))\n\tprintln(shadowed_value)\n}\n")
163+ assert out == '2\n3\nconst'
164+}
@@ -0,0 +1,297 @@
1+import os
2+
3+const vexe = @VEXE
4+const tests_dir = os.dir(@FILE)
5+const v3_dir = os.dir(tests_dir)
6+const vlib_dir = os.dir(v3_dir)
7+const v3_src = os.join_path(v3_dir, 'v3.v')
8+
9+fn build_v3_review_transform() string {
10+ v3_bin := os.join_path(os.temp_dir(), 'v3_review_transform_regressions_test')
11+ build := os.execute('${vexe} -path "${vlib_dir}|@vlib|@vmodules" -o ${v3_bin} ${v3_src}')
12+ assert build.exit_code == 0, build.output
13+ return v3_bin
14+}
15+
16+fn run_bad(v3_bin string, name string, src string, expected string) {
17+ bad_src := os.join_path(os.temp_dir(), 'v3_${name}.v')
18+ os.write_file(bad_src, src) or { panic(err) }
19+ bad_bin := os.join_path(os.temp_dir(), 'v3_${name}')
20+ result := os.execute('${v3_bin} ${bad_src} -b c -o ${bad_bin}')
21+ assert result.exit_code != 0, '${name}: expected failure, got success\n${result.output}'
22+ assert result.output.contains(expected), '${name}: expected `${expected}` in\n${result.output}'
23+ assert !result.output.contains('C compilation failed'), '${name}: reached C compilation\n${result.output}'
24+}
25+
26+fn run_good(v3_bin string, name string, src string) string {
27+ good_src := os.join_path(os.temp_dir(), 'v3_${name}.v')
28+ os.write_file(good_src, src) or { panic(err) }
29+ good_bin := os.join_path(os.temp_dir(), 'v3_${name}')
30+ compile := os.execute('${v3_bin} ${good_src} -b c -o ${good_bin}')
31+ assert compile.exit_code == 0, '${name}: compile failed\n${compile.output}'
32+ assert !compile.output.contains('C compilation failed'), '${name}: C compilation failed\n${compile.output}'
33+ run := os.execute(good_bin)
34+ assert run.exit_code == 0, '${name}: run failed\n${run.output}'
35+ return run.output.trim_space()
36+}
37+
38+fn write_project_file(root string, rel string, src string) {
39+ path := os.join_path(root, rel)
40+ os.mkdir_all(os.dir(path)) or { panic(err) }
41+ os.write_file(path, src) or { panic(err) }
42+}
43+
44+fn run_good_project(v3_bin string, name string, files map[string]string, input string) string {
45+ root := os.join_path(os.temp_dir(), 'v3_${name}_project')
46+ if os.exists(root) {
47+ os.rmdir_all(root) or { panic(err) }
48+ }
49+ os.mkdir_all(root) or { panic(err) }
50+ for rel, src in files {
51+ write_project_file(root, rel, src)
52+ }
53+ input_path := if input.len == 0 { root } else { os.join_path(root, input) }
54+ good_bin := os.join_path(os.temp_dir(), 'v3_${name}')
55+ compile := os.execute('${v3_bin} ${input_path} -b c -o ${good_bin}')
56+ assert compile.exit_code == 0, '${name}: compile failed\n${compile.output}'
57+ assert !compile.output.contains('C compilation failed'), '${name}: C compilation failed\n${compile.output}'
58+ run := os.execute(good_bin)
59+ assert run.exit_code == 0, '${name}: run failed\n${run.output}'
60+ return run.output.trim_space()
61+}
62+
63+fn test_reject_dynamic_arrays_for_fixed_array_expectations() {
64+ v3_bin := build_v3_review_transform()
65+ run_bad(v3_bin, 'bad_fixed_array_literal_len',
66+ 'fn take3(a [3]int) int {\n\treturn a[0]\n}\nfn main() {\n\t_ := take3([1, 2])\n}\n',
67+ 'cannot use')
68+ run_bad(v3_bin, 'bad_dynamic_array_for_fixed_array',
69+ 'fn take3(a [3]int) int {\n\treturn a[0]\n}\nfn main() {\n\txs := [1, 2, 3]\n\t_ := take3(xs)\n}\n',
70+ 'cannot use')
71+ out := run_good(v3_bin, 'good_exact_fixed_array_literal',
72+ 'fn take3(a [3]int) int {\n\treturn a[0] + a[1] + a[2]\n}\nfn main() {\n\tprintln(int_str(take3([1, 2, 3])))\n}\n')
73+ assert out == '6'
74+ indexed := run_good(v3_bin, 'good_fixed_array_init_index',
75+ 'fn main() {\n\ta := [4]int{init: index * index}\n\tprintln(int_str(a[0]) + "," + int_str(a[1]) + "," + int_str(a[2]) + "," + int_str(a[3]))\n}\n')
76+ assert indexed == '0,1,4,9'
77+ const_indexed := run_good(v3_bin, 'good_fixed_array_const_init_index',
78+ 'const n = 4\n\nfn main() {\n\ta := [n]int{init: index * index}\n\tprintln(int_str(a[0]) + "," + int_str(a[1]) + "," + int_str(a[2]) + "," + int_str(a[3]))\n}\n')
79+ assert const_indexed == '0,1,4,9'
80+}
81+
82+fn test_array_equality_uses_semantic_element_comparison() {
83+ v3_bin := build_v3_review_transform()
84+ out := run_good(v3_bin, 'semantic_array_equality',
85+ "struct Child {\n\tlabel string\n}\n\nstruct Item {\n\tname string\n\tparts []string\n\tnested [][]string\n\tchildren []Child\n}\n\nfn join(a string, b string) string {\n\treturn a + b\n}\n\nfn main() {\n\tleft := [Item{\n\t\tname: 'hi'.clone()\n\t\tparts: ['ab'.clone()]\n\t\tnested: [[join('n', 'est')]]\n\t\tchildren: [Child{\n\t\t\tlabel: 'kid'.clone()\n\t\t}]\n\t}]\n\tright := [Item{\n\t\tname: join('h', 'i')\n\t\tparts: [join('a', 'b')]\n\t\tnested: [['nest'.clone()]]\n\t\tchildren: [Child{\n\t\t\tlabel: join('k', 'id')\n\t\t}]\n\t}]\n\tmaps_left := [{\n\t\t'k': 'value'.clone()\n\t}]\n\tmaps_right := [{\n\t\t'k': join('val', 'ue')\n\t}]\n\tnested_left := [[join('y', 'o')]]\n\tnested_right := [['yo'.clone()]]\n\tchild_map_left := {\n\t\t'items': [Child{\n\t\t\tlabel: 'mapkid'.clone()\n\t\t}]\n\t}\n\tchild_map_right := {\n\t\t'items': [Child{\n\t\t\tlabel: join('map', 'kid')\n\t\t}]\n\t}\n\tneedle := Item{\n\t\tname: join('h', 'i')\n\t\tparts: [join('a', 'b')]\n\t\tnested: [['nest'.clone()]]\n\t\tchildren: [Child{\n\t\t\tlabel: join('k', 'id')\n\t\t}]\n\t}\n\tprintln(left == right)\n\tprintln(left.equals(right))\n\tprintln(maps_left == maps_right)\n\tprintln(nested_left == nested_right)\n\tprintln(child_map_left == child_map_right)\n\tprintln(needle in left)\n\tprintln(int_str(left.index(needle)))\n}\n")
86+ assert out == 'true\ntrue\ntrue\ntrue\ntrue\ntrue\n0'
87+}
88+
89+fn test_nested_map_equality_uses_declared_value_type() {
90+ v3_bin := build_v3_review_transform()
91+ out := run_good(v3_bin, 'nested_map_semantic_equality',
92+ "struct Item {\n\tname string\n\tparts []string\n}\n\nstruct Holder {\n\titems map[string][]Item\n}\n\nfn join(a string, b string) string {\n\treturn a + b\n}\n\nfn main() {\n\tmut left_map := map[string][]Item{}\n\tleft_map['items'] = [Item{\n\t\tname: 'ab'.clone()\n\t\tparts: ['xy'.clone()]\n\t}]\n\tmut right_map := map[string][]Item{}\n\tright_map['items'] = [Item{\n\t\tname: join('a', 'b')\n\t\tparts: [join('x', 'y')]\n\t}]\n\tleft_arr := [left_map]\n\tright_arr := [right_map]\n\tleft_holder := Holder{\n\t\titems: left_map\n\t}\n\tright_holder := Holder{\n\t\titems: right_map\n\t}\n\tprintln(left_arr == right_arr)\n\tprintln(left_holder == right_holder)\n}\n")
93+ assert out == 'true\ntrue'
94+}
95+
96+fn test_pointer_array_equality_uses_pointer_identity() {
97+ v3_bin := build_v3_review_transform()
98+ out := run_good(v3_bin, 'pointer_array_equality',
99+ 'struct Node {\n\tvalue int\n}\n\nfn main() {\n\tleft_node := Node{\n\t\tvalue: 5\n\t}\n\tright_node := Node{\n\t\tvalue: 5\n\t}\n\tleft_ptr := &left_node\n\tright_ptr := &right_node\n\tleft := [left_ptr]\n\tright := [right_ptr]\n\tsame := [left_ptr]\n\tprintln(left == right)\n\tprintln(left != right)\n\tprintln(left == same)\n\tprintln(right_ptr in left)\n\tprintln(int_str(left.index(right_ptr)))\n}\n')
100+ assert out == 'false\ntrue\ntrue\nfalse\n-1'
101+}
102+
103+fn test_array_pointer_equality_uses_pointer_identity() {
104+ v3_bin := build_v3_review_transform()
105+ out := run_good(v3_bin, 'array_pointer_equality',
106+ 'fn main() {\n\tleft := [1, 2]\n\tright := [1, 2]\n\tleft_ptr := &left\n\tright_ptr := &right\n\tsame_ptr := left_ptr\n\tprintln(left_ptr == right_ptr)\n\tprintln(left_ptr != right_ptr)\n\tprintln(left_ptr == same_ptr)\n\tprintln(*left_ptr == *right_ptr)\n}\n')
107+ assert out == 'false\ntrue\ntrue\ntrue'
108+}
109+
110+fn test_map_pointer_equality_uses_pointer_identity() {
111+ v3_bin := build_v3_review_transform()
112+ out := run_good(v3_bin, 'map_pointer_equality',
113+ "fn main() {\n\tleft := {\n\t\t'x': 1\n\t}\n\tright := {\n\t\t'x': 1\n\t}\n\tleft_ptr := &left\n\tright_ptr := &right\n\tsame_ptr := left_ptr\n\tprintln(left_ptr == right_ptr)\n\tprintln(left_ptr != right_ptr)\n\tprintln(left_ptr == same_ptr)\n\tprintln(*left_ptr == *right_ptr)\n}\n")
114+ assert out == 'false\ntrue\ntrue\ntrue'
115+}
116+
117+fn test_fixed_array_values_compare_semantically() {
118+ v3_bin := build_v3_review_transform()
119+ out := run_good(v3_bin, 'fixed_array_semantic_equality',
120+ "fn join(a string, b string) string {\n\treturn a + b\n}\n\nfn main() {\n\tleft := [[1]string{init: 'ab'.clone()}]\n\tright := [[1]string{init: join('a', 'b')}]\n\tmut map_left := map[string][1]string{}\n\tmap_left['k'] = [1]string{init: 'cd'.clone()}\n\tmut map_right := map[string][1]string{}\n\tmap_right['k'] = [1]string{init: join('c', 'd')}\n\tmut ints_left := map[string][2]i64{}\n\tints_left['k'] = [i64(1), i64(0)]!\n\tmut ints_right := map[string][2]i64{}\n\tints_right['k'] = [i64(2), i64(0)]!\n\tprintln(left == right)\n\tprintln(left.equals(right))\n\tprintln(map_left == map_right)\n\tprintln(ints_left == ints_right)\n}\n")
121+ assert out == 'true\ntrue\ntrue\nfalse'
122+}
123+
124+fn test_const_length_fixed_array_map_values_compare_semantically() {
125+ v3_bin := build_v3_review_transform()
126+ out := run_good_project(v3_bin, 'const_len_fixed_array_map_equality', {
127+ 'main.v': 'module main\n\nimport store\n\nfn main() {\n\tprintln(store.check())\n}\n'
128+ 'store/store.v': "module store\n\nconst n = 2\n\nfn join(a string, b string) string {\n\treturn a + b\n}\n\npub fn check() bool {\n\tmut left := map[string][n]string{}\n\tleft['k'] = [n]string{init: 'ab'.clone()}\n\tmut right := map[string][n]string{}\n\tright['k'] = [n]string{init: join('a', 'b')}\n\treturn left == right\n}\n"
129+ }, 'main.v')
130+ assert out == 'true'
131+}
132+
133+fn test_interface_array_repeat_evaluates_receiver_once() {
134+ v3_bin := build_v3_review_transform()
135+ out := run_good(v3_bin, 'interface_repeat_side_effects',
136+ 'interface Thing {\n\tvalue() int\n}\n\nstruct Item {\n\tn int\n}\n\nfn (i Item) value() int {\n\treturn i.n\n}\n\n__global calls int\n\nfn make_item() Thing {\n\tcalls++\n\treturn Item{\n\t\tn: calls\n\t}\n}\n\nfn main() {\n\titems := [make_item()].repeat(3)\n\tprintln(int_str(calls))\n\tprintln(int_str(items[0].value() + items[1].value() + items[2].value()))\n}\n')
137+ assert out == '1\n3'
138+}
139+
140+fn test_comptime_type_conditions_handle_logical_ops() {
141+ v3_bin := build_v3_review_transform()
142+ out := run_good(v3_bin, 'comptime_type_condition_logical_ops',
143+ "fn classify[T](x T) int {\n\t_ := x\n\t\$if T !is string && T !is \$int && T !is []u8 {\n\t\treturn 1\n\t} \$else {\n\t\treturn 2\n\t}\n\treturn 0\n}\n\nfn grouped[T](x T) int {\n\t_ := x\n\t\$if (T is int || T is string) && T is bool {\n\t\treturn 1\n\t} \$else {\n\t\treturn 2\n\t}\n\treturn 0\n}\n\nfn main() {\n\tprintln(int_str(classify('abc')))\n\tprintln(int_str(classify(3)))\n\tprintln(int_str(classify([u8(1)])))\n\tprintln(int_str(classify(1.5)))\n\tprintln(int_str(grouped(3)))\n\tprintln(int_str(grouped('abc')))\n}\n")
144+ assert out == '2\n2\n2\n1\n2\n2'
145+}
146+
147+fn test_comptime_type_conditions_keep_prefix_types_compact() {
148+ v3_bin := build_v3_review_transform()
149+ out := run_good(v3_bin, 'comptime_type_condition_prefix_types',
150+ 'struct Foo {}\n\nfn main() {\n\t\$if ?int is ?int {\n\t\tprintln("opt")\n\t} \$else {\n\t\tprintln("badopt")\n\t}\n\t\$if !Foo is !Foo {\n\t\tprintln("res")\n\t} \$else {\n\t\tprintln("badres")\n\t}\n}\n')
151+ assert out == 'opt\nres'
152+}
153+
154+fn test_comptime_type_conditions_qualify_module_aliases() {
155+ v3_bin := build_v3_review_transform()
156+ out := run_good_project(v3_bin, 'comptime_type_condition_module_alias', {
157+ 'main.v': 'module main\n\nimport foo\n\nfn main() {\n\tprintln(foo.check())\n}\n'
158+ 'foo/foo.v': "module foo\n\ntype ID = int\n\npub fn check() string {\n\t\$if ID is \$alias {\n\t\treturn 'alias'\n\t} \$else {\n\t\treturn 'not alias'\n\t}\n}\n"
159+ }, 'main.v')
160+ assert out == 'alias'
161+}
162+
163+fn test_struct_equality_compares_pointer_fields_as_pointers() {
164+ v3_bin := build_v3_review_transform()
165+ out := run_good(v3_bin, 'struct_eq_pointer_field',
166+ 'struct Node {\n\tvalue int\n\tnext &Node\n}\n\nfn main() {\n\tleft := Node{\n\t\tvalue: 7\n\t\tnext: unsafe { nil }\n\t}\n\tright := Node{\n\t\tvalue: 7\n\t\tnext: unsafe { nil }\n\t}\n\tprintln([left] == [right])\n}\n')
167+ assert out == 'true'
168+}
169+
170+fn test_single_module_test_file_skips_premodule_attributes() {
171+ v3_bin := build_v3_review_transform()
172+ root := os.join_path(os.temp_dir(), 'v3_premodule_attr_module_test')
173+ os.rmdir_all(root) or {}
174+ os.mkdir_all(os.join_path(root, 'tar')) or { panic(err) }
175+ os.write_file(os.join_path(root, 'v.mod'), 'Module { name: "premodule_attr_module_test" }\n') or {
176+ panic(err)
177+ }
178+ os.write_file(os.join_path(root, 'tar', 'reader.v'),
179+ 'module tar /* implementation module */\n\nfn reader_value() string {\n\treturn "reader"\n}\n') or {
180+ panic(err)
181+ }
182+ test_file := os.join_path(root, 'tar', 'reader_test.v')
183+ os.write_file(test_file,
184+ '@[has_globals]\n/* block comment before module */\nmodule tar // test module\n\nfn test_reader_value() {\n\tprintln(reader_value())\n}\n') or {
185+ panic(err)
186+ }
187+ bin_path := os.join_path(root, 'reader_test_bin')
188+ compile := os.execute('${v3_bin} ${test_file} -b c -o ${bin_path}')
189+ assert compile.exit_code == 0, compile.output
190+ run := os.execute(bin_path)
191+ assert run.exit_code == 0, run.output
192+ assert run.output.trim_space() == 'reader'
193+}
194+
195+fn test_delete_last_empty_array_panics_before_tail_clear() {
196+ v3_bin := build_v3_review_transform()
197+ src := 'fn main() {\n\tmut values := []int{}\n\tvalues.delete_last()\n\tprintln("after")\n}\n'
198+ good_src := os.join_path(os.temp_dir(), 'v3_delete_last_empty.v')
199+ os.write_file(good_src, src) or { panic(err) }
200+ good_bin := os.join_path(os.temp_dir(), 'v3_delete_last_empty')
201+ compile := os.execute('${v3_bin} ${good_src} -b c -o ${good_bin}')
202+ assert compile.exit_code == 0, compile.output
203+ run := os.execute(good_bin)
204+ assert run.exit_code != 0, run.output
205+ assert run.output.contains('array.delete_last: array is empty'), run.output
206+}
207+
208+fn test_delete_last_preserves_shared_slice_buffer() {
209+ v3_bin := build_v3_review_transform()
210+ out := run_good(v3_bin, 'delete_last_preserves_shared_slice_buffer',
211+ "fn main() {\n\tmut a := [1, 2, 3, 4]\n\tb := unsafe { a[..a.len] }\n\told_data := a.data\n\ta.delete_last()\n\tassert a == [1, 2, 3]\n\tassert b == [1, 2, 3, 4]\n\tassert a.data != old_data\n\tprintln('ok')\n}\n")
212+ assert out == 'ok'
213+}
214+
215+fn test_slice_element_assignment_writes_through() {
216+ v3_bin := build_v3_review_transform()
217+ out := run_good(v3_bin, 'slice_element_assignment_writes_through',
218+ "fn main() {\n\tmut a := [1, 2, 3, 4]\n\tmut s := unsafe { a[1..3] }\n\ts[0] = 42\n\ts[1] += 5\n\tassert a == [1, 42, 8, 4]\n\tassert s == [42, 8]\n\tprintln('ok')\n}\n")
219+ assert out == 'ok'
220+}
221+
222+fn test_string_pointer_comparisons_keep_pointer_semantics() {
223+ v3_bin := build_v3_review_transform()
224+ out := run_good(v3_bin, 'string_pointer_comparison',
225+ "fn main() {\n\tleft := 'same'.clone()\n\tright := 'same'.clone()\n\tpleft := &left\n\tpright := &right\n\tprintln(pleft == pright)\n\tprintln(pleft != pright)\n\tprintln(*pleft == *pright)\n}\n")
226+ assert out == 'false\ntrue\ntrue'
227+}
228+
229+fn test_map_keys_and_values_lower_to_runtime_methods() {
230+ v3_bin := build_v3_review_transform()
231+ out := run_good(v3_bin, 'map_keys_values_lowering',
232+ "fn make_lookup() map[string]int {\n\treturn {\n\t\t'one': 1\n\t\t'two': 2\n\t}\n}\n\nfn main() {\n\tlookup := make_lookup()\n\tkeys := lookup.keys()\n\tvalues := make_lookup().values()\n\tmut total := 0\n\tfor value in values {\n\t\ttotal += value\n\t}\n\tprintln(int_str(keys.len))\n\tprintln(int_str(values.len))\n\tprintln(int_str(total))\n}\n")
233+ assert out == '2\n2\n3'
234+}
235+
236+fn test_map_str_preserves_signed_wide_entries() {
237+ v3_bin := build_v3_review_transform()
238+ out := run_good(v3_bin, 'map_str_signed_wide_entries',
239+ "fn main() {\n\tvalue_map := {\n\t\t'x': i64(5000000000)\n\t}\n\tkey_map := {\n\t\ti64(-5000000000): 'x'\n\t}\n\tprintln(value_map.str())\n\tprintln(key_map.str())\n}\n")
240+ assert out == "{'x': 5000000000}\n{-5000000000: 'x'}"
241+}
242+
243+fn test_map_str_normalizes_alias_key_and_value_types() {
244+ v3_bin := build_v3_review_transform()
245+ out := run_good(v3_bin, 'map_str_alias_kinds',
246+ "type ID = int\n\ntype Amount = f64\n\nfn main() {\n\tids := {\n\t\tID(23): 'id'\n\t}\n\tamounts := {\n\t\t'price': Amount(1.25)\n\t}\n\tprintln('\${ids}')\n\tprintln('\${amounts}')\n}\n")
247+ assert out == "{23: 'id'}\n{'price': 1.25}"
248+}
249+
250+fn test_map_literal_stringification_evaluates_entries_once() {
251+ v3_bin := build_v3_review_transform()
252+ out := run_good(v3_bin, 'map_literal_str_side_effects',
253+ "__global key_calls int\n__global val_calls int\n\nfn next_key() string {\n\tkey_calls++\n\treturn 'k' + int_str(key_calls)\n}\n\nfn next_val() int {\n\tval_calls++\n\treturn val_calls * 10\n}\n\nfn main() {\n\tprintln({\n\t\tnext_key(): next_val()\n\t})\n\tprintln(int_str(key_calls) + ',' + int_str(val_calls))\n}\n")
254+ assert out == "{'k1': 10}\n1,1"
255+}
256+
257+fn test_shadowed_minmaxof_calls_are_not_rewritten() {
258+ v3_bin := build_v3_review_transform()
259+ out := run_good_project(v3_bin, 'shadowed_minmaxof_calls', {
260+ 'main.v': 'module main\n\nimport shadow { maxof, minof }\n\nfn main() {\n\tprintln(int_str(maxof[int]()))\n\tprintln(int_str(minof[int]()))\n}\n'
261+ 'shadow/shadow.v': 'module shadow\n\npub fn maxof[T]() int {\n\treturn 7\n}\n\npub fn minof[T]() int {\n\treturn -7\n}\n'
262+ }, 'main.v')
263+ assert out == '7\n-7'
264+}
265+
266+fn test_runes_iterator_index_is_loop_scoped() {
267+ v3_bin := build_v3_review_transform()
268+ out := run_good(v3_bin, 'runes_iterator_index_scope',
269+ "fn main() {\n\tfor i, r in 'ab'.runes_iterator() {\n\t\t_ := r\n\t\tprintln(int_str(i))\n\t}\n\ti := 9\n\tprintln(int_str(i))\n}\n")
270+ assert out == '0\n1\n9'
271+}
272+
273+fn test_array_last_index_uses_element_width() {
274+ v3_bin := build_v3_review_transform()
275+ out := run_good(v3_bin, 'array_last_index_element_width',
276+ 'fn main() {\n\twide := [i64(1), i64(5000000000), i64(2), i64(5000000000)]\n\tfloats := [1.25, 2.5, 1.25]\n\tflags := [true, false, true]\n\tprintln(int_str(wide.last_index(i64(5000000000))))\n\tprintln(int_str(floats.last_index(1.25)))\n\tprintln(int_str(flags.last_index(true)))\n}\n')
277+ assert out == '3\n2\n2'
278+}
279+
280+fn test_array_last_index_uses_semantic_element_comparison() {
281+ v3_bin := build_v3_review_transform()
282+ out := run_good(v3_bin, 'array_last_index_semantic_equality',
283+ "struct Item {\n\tname string\n\tparts []string\n}\n\nfn join(a string, b string) string {\n\treturn a + b\n}\n\nfn main() {\n\tnested := [['ab'.clone()], [join('x', 'y')], [join('a', 'b')]]\n\tnested_needle := ['ab'.clone()]\n\titems := [Item{\n\t\tname: 'ab'.clone()\n\t\tparts: ['xy'.clone()]\n\t}, Item{\n\t\tname: join('a', 'b')\n\t\tparts: [join('x', 'y')]\n\t}]\n\tneedle := Item{\n\t\tname: 'ab'.clone()\n\t\tparts: ['xy'.clone()]\n\t}\n\tprintln(int_str(nested.last_index(nested_needle)))\n\tprintln(int_str(items.last_index(needle)))\n}\n")
284+ assert out == '2\n1'
285+}
286+
287+fn test_hierarchical_import_runtime_inits_before_importer_init() {
288+ v3_bin := build_v3_review_transform()
289+ out := run_good_project(v3_bin, 'hierarchical_runtime_init_order', {
290+ 'main.v': 'module main\n\nimport foo.user\nimport bar as shortbar\n\nfn main() {\n\t_ := shortbar.value()\n\tprintln(int_str(user.value()))\n}\n'
291+ 'foo/user/user.v': 'module user\n\nimport foo.bar as foobar\n\n__global seen int\n\nfn init() {\n\tseen = foobar.value() + 1\n}\n\npub fn value() int {\n\treturn seen\n}\n'
292+ 'foo/bar/bar.v': 'module bar\n\n__global flag = make_flag()\n\nfn make_flag() int {\n\treturn 40\n}\n\npub fn value() int {\n\treturn flag\n}\n'
293+ 'bar/bar.v': 'module bar\n\n__global flag = make_flag()\n\nfn make_flag() int {\n\treturn 3\n}\n\npub fn value() int {\n\treturn flag\n}\n'
294+ 'unrelated/other.v': 'module other\n\npub fn value() int {\n\treturn 0\n}\n'
295+ }, 'main.v')
296+ assert out == '41'
297+}
@@ -9,6 +9,123 @@ fn (mut t Transformer) make_array_new_call(elem_type string, len_expr flat.NodeI
99 '[]${elem_type}')
1010}
1111
12+fn (mut t Transformer) try_lower_array_repeat_call(_id flat.NodeId, node flat.Node) ?flat.NodeId {
13+ if node.children_count != 2 {
14+ return none
15+ }
16+ fn_id := t.a.child(&node, 0)
17+ fn_node := t.a.nodes[int(fn_id)]
18+ if fn_node.kind != .selector || fn_node.value != 'repeat' || fn_node.children_count == 0 {
19+ return none
20+ }
21+ base_id := t.a.child(&fn_node, 0)
22+ base_type := t.node_type(base_id)
23+ count_id := t.a.child(&node, 1)
24+ if expanded := t.try_expand_interface_array_literal_repeat(base_id, count_id, base_type) {
25+ return expanded
26+ }
27+ depth := array_repeat_clone_depth(base_type)
28+ if depth == 0 {
29+ return none
30+ }
31+ base := t.transform_expr(base_id)
32+ count := t.transform_expr(count_id)
33+ selector := t.make_selector(base, 'repeat_to_depth', '')
34+ return t.make_call_expr_typed(selector, arr2(count, t.make_int_literal(depth)), node.typ)
35+}
36+
37+fn (mut t Transformer) try_expand_interface_array_literal_repeat(base_id flat.NodeId, count_id flat.NodeId, base_type string) ?flat.NodeId {
38+ if !base_type.starts_with('[]') {
39+ return none
40+ }
41+ elem_type := base_type[2..]
42+ if elem_type !in t.tc.interface_names && t.tc.qualify_name(elem_type) !in t.tc.interface_names {
43+ return none
44+ }
45+ base := t.a.nodes[int(base_id)]
46+ count_node := t.a.nodes[int(count_id)]
47+ if base.kind != .array_literal || count_node.kind != .int_literal {
48+ return none
49+ }
50+ count := count_node.value.int()
51+ if count < 0 || count > 32 {
52+ return none
53+ }
54+ if !t.array_repeat_literal_can_duplicate(base) {
55+ return none
56+ }
57+ mut values := []flat.NodeId{cap: int(base.children_count) * count}
58+ for _ in 0 .. count {
59+ for i in 0 .. base.children_count {
60+ values << t.a.child(&base, i)
61+ }
62+ }
63+ lit := t.make_array_literal_typed(values, base_type)
64+ return t.transform_array_literal(lit, t.a.nodes[int(lit)])
65+}
66+
67+fn (t &Transformer) array_repeat_literal_can_duplicate(node flat.Node) bool {
68+ for i in 0 .. node.children_count {
69+ if !t.array_repeat_expr_can_duplicate(t.a.child(&node, i)) {
70+ return false
71+ }
72+ }
73+ return true
74+}
75+
76+fn (t &Transformer) array_repeat_expr_can_duplicate(id flat.NodeId) bool {
77+ node := t.a.nodes[int(id)]
78+ match node.kind {
79+ .int_literal, .float_literal, .bool_literal, .char_literal, .string_literal, .ident,
80+ .enum_val, .nil_literal, .none_expr {
81+ return true
82+ }
83+ .paren, .prefix, .postfix, .cast_expr, .as_expr, .field_init, .array_literal, .struct_init {
84+ for i in 0 .. node.children_count {
85+ if !t.array_repeat_expr_can_duplicate(t.a.child(&node, i)) {
86+ return false
87+ }
88+ }
89+ return true
90+ }
91+ else {
92+ return false
93+ }
94+ }
95+}
96+
97+fn array_repeat_clone_depth(typ string) int {
98+ mut clean := typ
99+ if clean.starts_with('&') {
100+ clean = clean[1..]
101+ }
102+ mut depth := 0
103+ for clean.starts_with('[]') {
104+ depth++
105+ clean = clean[2..]
106+ }
107+ if depth <= 1 {
108+ return 0
109+ }
110+ return depth - 1
111+}
112+
113+fn array_nested_eq_depth(typ string) int {
114+ mut clean := typ
115+ if clean.starts_with('&') {
116+ clean = clean[1..]
117+ }
118+ mut depth := 0
119+ for clean.starts_with('[]') {
120+ depth++
121+ clean = clean[2..]
122+ }
123+ if depth <= 1 {
124+ return 1
125+ }
126+ return depth
127+}
128+
12129fn (mut t Transformer) make_array_push_many_call(lhs_addr flat.NodeId, rhs flat.NodeId, rhs_type string) flat.NodeId {
13130 t.mark_fn_used('array__push_many')
14131 rhs_value := t.stable_transformed_expr_for_reuse(rhs, rhs_type, 'push_many')
@@ -16,10 +133,30 @@ fn (mut t Transformer) make_array_push_many_call(lhs_addr flat.NodeId, rhs flat.
16133 'voidptr'), t.make_selector(rhs_value, 'len', 'int')), 'void')
17134}
18135
136+fn (mut t Transformer) make_array_insert_many_call(lhs_addr flat.NodeId, index flat.NodeId, rhs flat.NodeId, rhs_type string) flat.NodeId {
137+ if t.is_fixed_array_type(rhs_type) {
138+ return t.make_call_typed('array__insert_many', arr4(lhs_addr, index, rhs,
139+ t.make_fixed_array_len_expr(rhs_type)), 'void')
140+ }
141+ rhs_value := t.stable_transformed_expr_for_reuse(rhs, rhs_type, 'insert_many')
142+ return t.make_call_typed('array__insert_many', arr4(lhs_addr, index, t.make_selector(rhs_value,
143+ 'data', 'voidptr'), t.make_selector(rhs_value, 'len', 'int')), 'void')
144+}
145+
19146fn (mut t Transformer) make_array_clone_call(base_id flat.NodeId, base_type string) flat.NodeId {
20147 t.mark_fn_used('array__clone')
21148 receiver := t.transform_expr(base_id)
22- return t.make_call_typed('array__clone', arr1(t.runtime_addr(receiver, base_type)), base_type)
149+ return t.make_array_clone_value(receiver, base_type)
150+}
151+
152+fn (mut t Transformer) make_array_clone_value(receiver flat.NodeId, base_type string) flat.NodeId {
153+ clean_type := if base_type.starts_with('&') { base_type[1..] } else { base_type }
154+ depth := array_repeat_clone_depth(clean_type)
155+ if depth > 0 {
156+ return t.make_call_typed('array__clone_to_depth', arr2(t.runtime_addr(receiver, base_type),
157+ t.make_int_literal(depth)), clean_type)
158+ }
159+ return t.make_call_typed('array__clone', arr1(t.runtime_addr(receiver, base_type)), clean_type)
23160}
24161
25162// lower_array_init_to_runtime converts lower array init to runtime data for transform.
@@ -46,7 +183,15 @@ fn (mut t Transformer) lower_array_init_to_runtime(id flat.NodeId, node flat.Nod
46183 }
47184 new_call := t.make_array_new_call(elem_type, len_expr, cap_expr)
48185 if int(init_expr) < 0 {
49- return new_call
186+ clean_elem_type := t.normalize_type_alias(elem_type)
187+ if clean_elem_type.starts_with('[]') {
188+ init_expr = t.make_array_new_call(clean_elem_type[2..], t.make_int_literal(0),
189+ t.make_int_literal(0))
190+ } else if default_value := t.make_struct_runtime_default_value(clean_elem_type) {
191+ init_expr = default_value
192+ } else {
193+ return new_call
194+ }
50195 }
51196 tmp_name := t.new_temp('arr_init')
52197 idx_name := t.new_temp('arr_idx')
@@ -56,18 +201,80 @@ fn (mut t Transformer) lower_array_init_to_runtime(id flat.NodeId, node flat.Nod
56201 'len', 'int'))
57202 post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
58203 elem_lhs := t.make_index(t.make_ident(tmp_name), t.make_ident(idx_name), elem_type)
59- assign := t.make_assign(elem_lhs, init_expr)
60204 // `init:` expressions may reference the magic `index` variable, which V binds to
61205 // the current element index. Declare it inside the loop body so it resolves to the
62206 // generated loop counter instead of leaking to an external symbol (e.g. libc `index`).
63207 index_decl := t.make_decl_assign_typed('index', t.make_ident(idx_name), 'int')
64- t.pending_stmts << t.make_for_stmt(init_idx, cond, post, arr2(index_decl, assign), node)
65- return t.make_ident(tmp_name)
208+ mut loop_body := []flat.NodeId{}
209+ loop_body << index_decl
210+ mut assign_value := init_expr
211+ clean_elem_type := t.normalize_type_alias(elem_type)
212+ if clean_elem_type.starts_with('[]') {
213+ if !t.expr_can_take_address(assign_value) {
214+ value_name := t.new_temp('arr_init_val')
215+ loop_body << t.make_decl_assign_typed(value_name, assign_value, clean_elem_type)
216+ assign_value = t.make_ident(value_name)
217+ }
218+ assign_value = t.make_array_clone_value(assign_value, clean_elem_type)
219+ }
220+ assign := t.make_assign(elem_lhs, assign_value)
221+ loop_body << assign
222+ t.pending_stmts << t.make_for_stmt(init_idx, cond, post, loop_body, node)
223+ result := t.make_ident(tmp_name)
224+ t.a.nodes[int(result)].typ = '[]${elem_type}'
225+ return result
226+}
227+
228+fn (mut t Transformer) make_struct_runtime_default_value(struct_type string) ?flat.NodeId {
229+ info := t.lookup_struct_info(struct_type) or { return none }
230+ mut field_ids := []flat.NodeId{}
231+ for field in info.fields {
232+ field_type := if field.typ.len > 0 { field.typ } else { field.raw_typ }
233+ clean_type := t.normalize_type_alias(field_type)
234+ mut value := flat.empty_node
235+ if clean_type.starts_with('map[') || clean_type.starts_with('[]') {
236+ value = t.zero_value_for_type(clean_type)
237+ } else if nested := t.make_struct_runtime_default_value(clean_type) {
238+ value = nested
239+ }
240+ if int(value) < 0 {
241+ continue
242+ }
243+ start := t.a.children.len
244+ t.a.children << value
245+ field_ids << t.a.add_node(flat.Node{
246+ kind: .field_init
247+ children_start: start
248+ children_count: 1
249+ value: field.name
250+ typ: field_type
251+ })
252+ }
253+ if field_ids.len == 0 {
254+ return none
255+ }
256+ start := t.a.children.len
257+ for field_id in field_ids {
258+ t.a.children << field_id
259+ }
260+ return t.a.add_node(flat.Node{
261+ kind: .struct_init
262+ children_start: start
263+ children_count: flat.child_count(field_ids.len)
264+ value: struct_type
265+ typ: struct_type
266+ })
66267}
67268
68269// lower_array_literal_to_runtime converts lower array literal to runtime data for transform.
69270fn (mut t Transformer) lower_array_literal_to_runtime(id flat.NodeId, node flat.Node) flat.NodeId {
70- array_type := t.node_type(id)
271+ array_type := if checker_alias_type := t.array_literal_checker_alias_type(id) {
272+ checker_alias_type
273+ } else if alias_type := t.array_literal_alias_type(node) {
274+ alias_type
275+ } else {
276+ t.node_type(id)
277+ }
71278 if !array_type.starts_with('[]') {
72279 return id
73280 }
@@ -95,19 +302,67 @@ fn (mut t Transformer) lower_array_literal_to_runtime(id flat.NodeId, node flat.
95302 value := if elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types {
96303 t.wrap_sum_value(elem_id, elem_type)
97304 } else {
98- t.transform_expr(elem_id)
305+ t.transform_expr_for_type(elem_id, elem_type)
99306 }
100307 t.pending_stmts << t.make_decl_assign_typed(value_name, value, elem_type)
101308 call := t.make_call_typed('array_push', arr2(t.make_prefix(.amp, t.make_ident(tmp_name)), t.make_prefix(.amp,
102309 t.make_ident(value_name))), 'void')
103310 t.pending_stmts << t.make_expr_stmt(call)
104311 }
105- return t.make_ident(tmp_name)
312+ result := t.make_ident(tmp_name)
313+ t.a.nodes[int(result)].typ = array_type
314+ return result
315+}
316+
317+fn (t &Transformer) array_literal_checker_alias_type(id flat.NodeId) ?string {
318+ if isnil(t.tc) || int(id) < 0 {
319+ return none
320+ }
321+ typ := t.tc.expr_type(id) or { t.tc.resolve_type(id) }
322+ name := typ.name()
323+ if !name.starts_with('[]') {
324+ return none
325+ }
326+ elem := name[2..]
327+ if !t.generic_arg_is_alias_name(elem, t.cur_module) {
328+ return none
329+ }
330+ return '[]${elem.all_after_last('.')}'
331+}
332+
333+fn (t &Transformer) array_literal_alias_type(node flat.Node) ?string {
334+ if node.kind != .array_literal || node.children_count == 0 {
335+ return none
336+ }
337+ first_id := t.a.child(&node, 0)
338+ first := t.a.nodes[int(first_id)]
339+ mut alias_name := ''
340+ if first.kind in [.cast_expr, .as_expr] && first.value.len > 0 {
341+ alias_name = first.value
342+ } else if first.kind == .call && first.children_count > 0 {
343+ callee := t.a.child_node(&first, 0)
344+ if callee.kind == .ident {
345+ alias_name = callee.value
346+ }
347+ }
348+ if alias_name.len == 0 {
349+ return none
350+ }
351+ if !t.generic_arg_is_alias_name(alias_name, t.cur_module) {
352+ return none
353+ }
354+ return '[]${alias_name}'
106355}
107356
108357// transform_array_literal_for_type transforms transform array literal for type data for transform.
109-fn (mut t Transformer) transform_array_literal_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
110- array_type := t.normalize_type_alias(target_type)
358+fn (mut t Transformer) transform_array_literal_for_type(id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
359+ array_type := if checker_alias_type := t.array_literal_checker_alias_type(id) {
360+ checker_alias_type
361+ } else if alias_type := t.array_literal_alias_type(node) {
362+ alias_type
363+ } else {
364+ t.normalize_type_alias(target_type)
365+ }
111366 if !array_type.starts_with('[]') {
112367 return none
113368 }
@@ -136,7 +391,9 @@ fn (mut t Transformer) transform_array_literal_for_type(_id flat.NodeId, node fl
136391 t.make_ident(value_name))), 'void')
137392 t.pending_stmts << t.make_expr_stmt(call)
138393 }
139- return t.make_ident(tmp_name)
394+ result := t.make_ident(tmp_name)
395+ t.a.nodes[int(result)].typ = array_type
396+ return result
140397}
141398
142399fn (mut t Transformer) transform_fixed_array_literal_for_type(_id flat.NodeId, node flat.Node, target_type string) ?flat.NodeId {
@@ -153,6 +410,40 @@ fn (mut t Transformer) transform_fixed_array_literal_for_type(_id flat.NodeId, n
153410 return t.make_array_literal_typed(values, fixed_type)
154411}
155412
413+fn (mut t Transformer) transform_fixed_array_init_expr(node flat.Node) ?flat.NodeId {
414+ fixed_type := t.normalize_type_alias(if node.value.len > 0 { node.value } else { node.typ })
415+ if !t.is_fixed_array_type(fixed_type) || node.children_count == 0 {
416+ return none
417+ }
418+ len_text := fixed_array_len_text(fixed_type)
419+ len := if is_decimal_text(len_text) {
420+ len_text.int()
421+ } else if !isnil(t.tc) {
422+ t.tc.const_int_value_in_module(len_text, t.cur_module, []string{}) or { return none }
423+ } else {
424+ return none
425+ }
426+ mut init_id := flat.empty_node
427+ for i in 0 .. node.children_count {
428+ child_id := t.a.child(&node, i)
429+ child := t.a.nodes[int(child_id)]
430+ if child.kind == .field_init && child.value == 'init' && child.children_count > 0 {
431+ init_id = t.a.child(&child, 0)
432+ break
433+ }
434+ }
435+ if int(init_id) < 0 {
436+ return none
437+ }
438+ elem_type := fixed_array_elem_type(fixed_type)
439+ mut values := []flat.NodeId{cap: len}
440+ for i in 0 .. len {
441+ indexed_init := t.substitute_ident_expr(init_id, 'index', t.make_int_literal(i))
442+ values << t.transform_expr_for_type(indexed_init, elem_type)
443+ }
444+ return t.make_array_literal_typed(values, fixed_type)
445+}
446+
156447// transform_empty_array_init_for_type supports transform_empty_array_init_for_type handling.
157448fn (mut t Transformer) transform_empty_array_init_for_type(node flat.Node, target_type string) ?flat.NodeId {
158449 if node.value.len > 0 || node.children_count > 0 {
@@ -298,7 +589,24 @@ fn (mut t Transformer) lower_array_prepend_call(node flat.Node, fn_node flat.Nod
298589 }
299590 base_id := t.a.child(&fn_node, 0)
300591 value_id := t.a.child(&node, 1)
592+ mut rhs_type := t.normalize_type_alias(t.node_type(value_id))
593+ value_node := t.a.nodes[int(value_id)]
594+ mut prepend_many := t.array_append_rhs_is_push_many(base_id, value_id, rhs_type, elem_type)
595+ if value_node.kind == .array_literal && !elem_type.starts_with('[]')
596+ && !t.is_fixed_array_type(elem_type) {
597+ prepend_many = true
598+ t.a.nodes[int(value_id)].typ = base_type
599+ rhs_type = base_type
600+ } else if prepend_many && value_node.kind == .array_literal && !rhs_type.starts_with('[]') {
601+ t.a.nodes[int(value_id)].typ = base_type
602+ rhs_type = base_type
603+ }
301604 base := t.transform_lvalue(base_id)
605+ if prepend_many {
606+ value := t.transform_expr(value_id)
607+ return t.make_array_insert_many_call(t.runtime_addr(base, base_type),
608+ t.make_int_literal(0), value, rhs_type)
609+ }
302610 value := if elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types {
303611 t.wrap_sum_value(value_id, elem_type)
304612 } else {
@@ -321,8 +629,25 @@ fn (mut t Transformer) lower_array_insert_call(node flat.Node, fn_node flat.Node
321629 base_id := t.a.child(&fn_node, 0)
322630 index_id := t.a.child(&node, 1)
323631 value_id := t.a.child(&node, 2)
632+ mut rhs_type := t.normalize_type_alias(t.node_type(value_id))
633+ value_node := t.a.nodes[int(value_id)]
634+ mut insert_many := t.array_append_rhs_is_push_many(base_id, value_id, rhs_type, elem_type)
635+ if value_node.kind == .array_literal && !elem_type.starts_with('[]')
636+ && !t.is_fixed_array_type(elem_type) {
637+ insert_many = true
638+ t.a.nodes[int(value_id)].typ = base_type
639+ rhs_type = base_type
640+ } else if insert_many && value_node.kind == .array_literal && !rhs_type.starts_with('[]') {
641+ t.a.nodes[int(value_id)].typ = base_type
642+ rhs_type = base_type
643+ }
324644 base := t.transform_lvalue(base_id)
325645 index := t.transform_expr_for_type(index_id, 'int')
646+ if insert_many {
647+ value := t.transform_expr(value_id)
648+ return t.make_array_insert_many_call(t.runtime_addr(base, base_type), index, value,
649+ rhs_type)
650+ }
326651 value := if elem_type in t.sum_types || t.resolve_sum_name(elem_type) in t.sum_types {
327652 t.wrap_sum_value(value_id, elem_type)
328653 } else {
@@ -373,11 +698,17 @@ fn (t &Transformer) push_many_count_is_type_name(id flat.NodeId) bool {
373698// array_append_rhs_is_push_many supports array append rhs is push many handling for Transformer.
374699fn (t &Transformer) array_append_rhs_is_push_many(lhs_id flat.NodeId, rhs_id flat.NodeId, rhs_type string, elem_type string) bool {
375700 clean_rhs_type := rhs_type.trim_space()
701+ if clean_rhs_type.starts_with('...') {
702+ return t.array_append_elem_types_match(clean_rhs_type[3..], elem_type)
703+ }
376704 if clean_rhs_type.starts_with('[]') {
377705 if t.array_append_elem_types_match(clean_rhs_type[2..], elem_type) {
378706 return true
379707 }
380708 if declared_rhs_type := t.array_append_ident_type(rhs_id) {
709+ if declared_rhs_type.starts_with('...') {
710+ return t.array_append_elem_types_match(declared_rhs_type[3..], elem_type)
711+ }
381712 if declared_rhs_type.starts_with('[]') {
382713 return t.array_append_elem_types_match(declared_rhs_type[2..], elem_type)
383714 }
@@ -501,6 +832,7 @@ fn (mut t Transformer) lower_array_filter_call(node flat.Node, fn_node flat.Node
501832 predicate_node := t.a.nodes[int(predicate_id)]
502833 mut predicate_expr_id := predicate_id
503834 mut lambda_param := ''
835+ mut predicate_fn_name := ''
504836 if predicate_node.kind == .lambda_expr && predicate_node.children_count > 0 {
505837 predicate_expr_id = t.a.child(&predicate_node, predicate_node.children_count - 1)
506838 if predicate_node.children_count > 1 {
@@ -509,6 +841,14 @@ fn (mut t Transformer) lower_array_filter_call(node flat.Node, fn_node flat.Node
509841 lambda_param = param.value
510842 }
511843 }
844+ } else if predicate_node.kind == .ident {
845+ if fn_name := t.resolve_fn_value_ident(predicate_node.value) {
846+ predicate_fn_name = fn_name
847+ } else if ret_type := t.fn_value_return_type_name(predicate_id) {
848+ if ret_type == 'bool' {
849+ predicate_fn_name = predicate_node.value
850+ }
851+ }
512852 }
513853 elem_name := if lambda_param.len > 0 { lambda_param } else { elem_name_default }
514854 elem_decl := t.make_decl_assign_typed(elem_name, elem_expr, elem_type)
@@ -519,7 +859,23 @@ fn (mut t Transformer) lower_array_filter_call(node flat.Node, fn_node flat.Node
519859 } else {
520860 t.substitute_ident(predicate_expr_id, 'it', elem_name)
521861 }
522- predicate := t.transform_expr(predicate_source)
862+ saved_pending := t.pending_stmts.clone()
863+ t.pending_stmts.clear()
864+ predicate := if predicate_fn_name.len > 0 {
865+ t.make_call_typed(predicate_fn_name, arr1(t.make_ident(elem_name)), 'bool')
866+ } else if predicate_node.kind == .fn_literal {
867+ fn_value := t.transform_expr(predicate_id)
868+ fn_value_node := t.a.nodes[int(fn_value)]
869+ if fn_value_node.kind == .ident {
870+ t.make_call_typed(fn_value_node.value, arr1(t.make_ident(elem_name)), 'bool')
871+ } else {
872+ fn_value
873+ }
874+ } else {
875+ t.transform_expr(predicate_source)
876+ }
877+ predicate_pending := t.pending_stmts.clone()
878+ t.pending_stmts = saved_pending
523879 if old_elem.len > 0 {
524880 t.set_var_type(elem_name, old_elem)
525881 } else {
@@ -527,7 +883,9 @@ fn (mut t Transformer) lower_array_filter_call(node flat.Node, fn_node flat.Node
527883 }
528884 mut loop_body := []flat.NodeId{}
529885 loop_body << elem_decl
530- t.drain_pending(mut loop_body)
886+ for stmt in predicate_pending {
887+ loop_body << stmt
888+ }
531889 push_call := t.make_call_typed('array_push', arr2(t.make_prefix(.amp, t.make_ident(out_name)), t.make_prefix(.amp,
532890 t.make_ident(elem_name))), 'void')
533891 then_block := t.make_block(arr1(t.make_expr_stmt(push_call)))
@@ -554,7 +912,16 @@ fn (mut t Transformer) lower_array_map_call(node flat.Node, fn_node flat.Node, b
554912 mapped_source := t.substitute_ident(map_expr_id, 'it', elem_name)
555913 mut result_elem_type := t.node_type(map_expr_id)
556914 mut direct_selector_type := ''
557- mapped_source_node := t.a.nodes[int(mapped_source)]
915+ mut mapped_source_node := t.a.nodes[int(mapped_source)]
916+ if mapped_source_node.kind == .map_init {
917+ inferred_map_type := t.infer_map_init_entry_type(mapped_source_node)
918+ if inferred_map_type.len > 0 {
919+ result_elem_type = inferred_map_type
920+ t.a.nodes[int(mapped_source)].value = inferred_map_type
921+ t.a.nodes[int(mapped_source)].typ = inferred_map_type
922+ mapped_source_node = t.a.nodes[int(mapped_source)]
923+ }
924+ }
558925 if mapped_source_node.kind == .selector {
559926 selector_type := t.resolve_selector_type(mapped_source_node)
560927 if selector_type.len > 0 {
@@ -572,6 +939,13 @@ fn (mut t Transformer) lower_array_map_call(node flat.Node, fn_node flat.Node, b
572939 result_elem_type = t.normalize_type_alias(ret_type.name())
573940 }
574941 }
942+ } else if ret_type := t.fn_value_return_type_name(map_expr_id) {
943+ map_fn_name = map_expr.value
944+ result_elem_type = ret_type
945+ }
946+ } else if map_expr.kind == .fn_literal || map_expr.kind == .lambda_expr {
947+ if ret_type := t.fn_value_return_type_name(map_expr_id) {
948+ result_elem_type = ret_type
575949 }
576950 }
577951 if result_elem_type.len == 0 {
@@ -581,6 +955,16 @@ fn (mut t Transformer) lower_array_map_call(node flat.Node, fn_node flat.Node, b
581955 t.pending_stmts.clear()
582956 mapped_expr := if map_fn_name.len > 0 {
583957 t.make_call_typed(map_fn_name, arr1(t.make_ident(elem_name)), result_elem_type)
958+ } else if map_expr.kind == .fn_literal || map_expr.kind == .lambda_expr {
959+ fn_value := t.transform_expr(map_expr_id)
960+ fn_value_node := t.a.nodes[int(fn_value)]
961+ if fn_value_node.kind == .ident && result_elem_type.len > 0 {
962+ t.make_call_typed(fn_value_node.value, arr1(t.make_ident(elem_name)), result_elem_type)
963+ } else {
964+ fn_value
965+ }
966+ } else if mapped_source_node.kind == .map_init && result_elem_type.starts_with('map[') {
967+ t.transform_expr_for_type(mapped_source, result_elem_type)
584968 } else {
585969 t.transform_expr(mapped_source)
586970 }
@@ -639,6 +1023,28 @@ fn (mut t Transformer) lower_array_map_call(node flat.Node, fn_node flat.Node, b
6391023 return result
6401024}
6411025
1026+fn (t &Transformer) fn_value_return_type_name(id flat.NodeId) ?string {
1027+ if int(id) < 0 {
1028+ return none
1029+ }
1030+ node := t.a.nodes[int(id)]
1031+ mut typ := node.typ
1032+ if node.kind == .ident {
1033+ typ = t.var_type(node.value)
1034+ }
1035+ if typ.len == 0 || isnil(t.tc) {
1036+ return none
1037+ }
1038+ parsed := t.tc.parse_type(typ)
1039+ if parsed is types.FnType {
1040+ name := parsed.return_type.name()
1041+ if name.len > 0 {
1042+ return t.normalize_type_alias(name)
1043+ }
1044+ }
1045+ return none
1046+}
1047+
6421048// selector_expr_node supports selector expr node handling for Transformer.
6431049fn (t &Transformer) selector_expr_node(id flat.NodeId) ?flat.Node {
6441050 if int(id) < 0 {
@@ -659,7 +1065,9 @@ fn (mut t Transformer) substitute_ident(id flat.NodeId, name string, replacement
6591065 node := t.a.nodes[int(id)]
6601066 if node.kind == .ident && node.value == name {
6611067 new_id := t.make_ident(replacement)
662- t.a.nodes[int(new_id)].typ = node.typ
1068+ if t.a.nodes[int(new_id)].typ.len == 0 {
1069+ t.a.nodes[int(new_id)].typ = node.typ
1070+ }
6631071 return new_id
6641072 }
6651073 if node.kind == .lambda_expr && node.children_count > 1 {
@@ -706,14 +1114,86 @@ fn (mut t Transformer) substitute_ident(id flat.NodeId, name string, replacement
7061114 return t.a.add_node(flat.Node{
7071115 kind: node.kind
7081116 op: node.op
1117+ kind_id: node.kind_id
7091118 children_start: start
7101119 children_count: flat.child_count(new_children.len)
7111120 pos: node.pos
7121121 value: node.value
7131122 typ: node.typ
1123+ generic_params: node.generic_params.clone()
7141124 })
7151125}
7161126
1127+fn (mut t Transformer) substitute_ident_expr(id flat.NodeId, name string, replacement flat.NodeId) flat.NodeId {
1128+ if int(id) < 0 || name.len == 0 || int(replacement) < 0 {
1129+ return id
1130+ }
1131+ node := t.a.nodes[int(id)]
1132+ if node.kind == .ident && node.value == name {
1133+ return replacement
1134+ }
1135+ if node.kind == .lambda_expr && node.children_count > 1 {
1136+ first := t.a.child_node(&node, 0)
1137+ if first.kind == .ident && first.value == name {
1138+ return id
1139+ }
1140+ }
1141+ if node.children_count == 0 {
1142+ return id
1143+ }
1144+ mut new_children := []flat.NodeId{cap: int(node.children_count)}
1145+ for i in 0 .. node.children_count {
1146+ new_children << t.substitute_ident_expr(t.a.child(&node, i), name, replacement)
1147+ }
1148+ start := t.a.children.len
1149+ for child in new_children {
1150+ t.a.children << child
1151+ }
1152+ return t.a.add_node(flat.Node{
1153+ kind: node.kind
1154+ op: node.op
1155+ kind_id: node.kind_id
1156+ children_start: start
1157+ children_count: flat.child_count(new_children.len)
1158+ pos: node.pos
1159+ value: node.value
1160+ typ: node.typ
1161+ generic_params: node.generic_params.clone()
1162+ })
1163+}
1164+
1165+fn (mut t Transformer) infer_map_init_entry_type(node flat.Node) string {
1166+ if node.kind != .map_init || node.children_count < 2 {
1167+ return ''
1168+ }
1169+ key_type := t.node_type(t.a.child(&node, 0))
1170+ value_type := t.node_type(t.a.child(&node, 1))
1171+ if key_type.len == 0 || value_type.len == 0 {
1172+ return ''
1173+ }
1174+ return 'map[${key_type}]${value_type}'
1175+}
1176+
1177+fn (t &Transformer) is_array_transform_call(id flat.NodeId) bool {
1178+ if int(id) < 0 {
1179+ return false
1180+ }
1181+ node := t.a.nodes[int(id)]
1182+ if node.kind != .call || node.children_count == 0 {
1183+ return false
1184+ }
1185+ fn_id := t.a.child(&node, 0)
1186+ fn_node := t.a.nodes[int(fn_id)]
1187+ if fn_node.kind != .selector || fn_node.children_count == 0 {
1188+ return false
1189+ }
1190+ if fn_node.value !in ['filter', 'map', 'sorted'] {
1191+ return false
1192+ }
1193+ base_type := t.node_type(t.a.child(&fn_node, 0))
1194+ return base_type.starts_with('[]')
1195+}
1196+
7171197// lower_array_count_call builds lower array count call data for transform.
7181198fn (mut t Transformer) lower_array_count_call(node flat.Node, fn_node flat.Node, base_type string) ?flat.NodeId {
7191199 if node.children_count < 2 || !base_type.starts_with('[]') {
@@ -831,6 +1311,7 @@ fn (mut t Transformer) lower_array_sorted_call(node flat.Node, fn_node flat.Node
8311311 base_id := t.a.child(&fn_node, 0)
8321312 clone_name := t.new_temp('sorted')
8331313 clone_call := t.make_array_clone_call(base_id, base_type)
1314+ t.set_var_type(clone_name, base_type)
8341315 t.pending_stmts << t.make_decl_assign_typed(clone_name, clone_call, base_type)
8351316 cmp_id := if node.children_count > 1 { t.a.child(&node, 1) } else { flat.empty_node }
8361317 t.pending_stmts << t.make_array_default_sort_stmt(t.make_ident(clone_name), base_type[2..],
@@ -862,9 +1343,10 @@ fn (mut t Transformer) lower_array_sorted_with_compare_call(node flat.Node, fn_n
8621343 base_id := t.a.child(&fn_node, 0)
8631344 clone_name := t.new_temp('sorted')
8641345 clone_call := t.make_array_clone_call(base_id, base_type)
865- t.pending_stmts << t.make_decl_assign_typed(clone_name, clone_call, base_type)
8661346 elem_type := base_type[2..]
8671347 cmp := t.stable_array_compare_fn(t.a.child(&node, 1), elem_type)
1348+ t.set_var_type(clone_name, base_type)
1349+ t.pending_stmts << t.make_decl_assign_typed(clone_name, clone_call, base_type)
8681350 t.pending_stmts << t.make_array_compare_sort_stmt(t.make_ident(clone_name), elem_type, node, cmp)
8691351 return t.make_ident(clone_name)
8701352}
@@ -17,15 +17,31 @@ fn (mut t Transformer) transform_infix_string_ops(_id flat.NodeId, node flat.Nod
1717
1818 lhs_id := t.a.children[node.children_start]
1919 rhs_id := t.a.children[node.children_start + 1]
20+ lhs_raw_type := t.node_type(lhs_id)
21+ rhs_raw_type := t.node_type(rhs_id)
22+ lhs_clean_type := t.normalize_type_alias(lhs_raw_type)
23+ rhs_clean_type := t.normalize_type_alias(rhs_raw_type)
24+ lhs_is_string_ptr := t.equality_expr_is_string_pointer(lhs_id, lhs_clean_type)
25+ rhs_is_string_ptr := t.equality_expr_is_string_pointer(rhs_id, rhs_clean_type)
26+ if node.op in [.plus, .eq, .ne] && (lhs_is_string_ptr || rhs_is_string_ptr) {
27+ return none
28+ }
2029
21- is_string := t.is_string_type(lhs_id) || t.is_string_type(rhs_id)
30+ is_string := t.is_string_type(lhs_id) || t.is_string_type(rhs_id) || lhs_is_string_ptr
31+ || rhs_is_string_ptr
2232
2333 if !is_string {
2434 return none
2535 }
2636
27- new_lhs := t.transform_expr(lhs_id)
28- new_rhs := t.transform_expr(rhs_id)
37+ mut new_lhs := t.transform_expr(lhs_id)
38+ mut new_rhs := t.transform_expr(rhs_id)
39+ if lhs_is_string_ptr {
40+ new_lhs = t.make_prefix(.mul, new_lhs)
41+ }
42+ if rhs_is_string_ptr {
43+ new_rhs = t.make_prefix(.mul, new_rhs)
44+ }
2945
3046 match node.op {
3147 .plus {
@@ -94,8 +110,30 @@ fn (mut t Transformer) transform_infix_array_ops(_id flat.NodeId, node flat.Node
94110 }
95111 lhs_id := t.a.children[node.children_start]
96112 rhs_id := t.a.children[node.children_start + 1]
97- lhs_type := t.membership_container_type(t.node_type(lhs_id))
98- rhs_type := t.membership_container_type(t.node_type(rhs_id))
113+ lhs_raw_type := t.node_type(lhs_id)
114+ rhs_raw_type := t.node_type(rhs_id)
115+ lhs_is_array_ptr := t.equality_type_is_array_pointer(lhs_raw_type)
116+ rhs_is_array_ptr := t.equality_type_is_array_pointer(rhs_raw_type)
117+ if lhs_is_array_ptr && rhs_is_array_ptr {
118+ return none
119+ }
120+ mut lhs_type := t.membership_container_type(lhs_raw_type)
121+ mut rhs_type := t.membership_container_type(rhs_raw_type)
122+ lhs_is_fixed := t.is_fixed_array_type(lhs_type)
123+ rhs_is_fixed := t.is_fixed_array_type(rhs_type)
124+ if lhs_is_fixed || rhs_is_fixed {
125+ if !lhs_is_fixed || !rhs_is_fixed {
126+ return none
127+ }
128+ fixed_type := t.resolved_fixed_array_canonical_type(lhs_type)
129+ new_lhs := t.transform_expr_for_type(lhs_id, fixed_type)
130+ new_rhs := t.transform_expr_for_type(rhs_id, fixed_type)
131+ eq_call := t.make_membership_eq_expr(new_lhs, new_rhs, fixed_type)
132+ if node.op == .ne {
133+ return t.make_prefix(.not, eq_call)
134+ }
135+ return eq_call
136+ }
99137 if !(lhs_type.starts_with('[]') || lhs_type == 'array') || !(rhs_type.starts_with('[]')
100138 || rhs_type == 'array') {
101139 return none
@@ -109,9 +147,29 @@ fn (mut t Transformer) transform_infix_array_ops(_id flat.NodeId, node flat.Node
109147 if elem_type.len == 0 {
110148 elem_type = 'int'
111149 }
112- new_lhs := t.transform_expr(lhs_id)
113- new_rhs := t.transform_expr(rhs_id)
114- eq_call := if elem_type == 'string' {
150+ mut new_lhs := t.transform_expr(lhs_id)
151+ mut new_rhs := t.transform_expr(rhs_id)
152+ new_lhs_type := t.membership_container_type(t.node_type(new_lhs))
153+ new_rhs_type := t.membership_container_type(t.node_type(new_rhs))
154+ if new_lhs_type.starts_with('[]') {
155+ elem_type = new_lhs_type[2..]
156+ lhs_type = new_lhs_type
157+ } else if new_rhs_type.starts_with('[]') {
158+ elem_type = new_rhs_type[2..]
159+ rhs_type = new_rhs_type
160+ }
161+ if lhs_is_array_ptr {
162+ new_lhs = t.make_prefix(.mul, new_lhs)
163+ }
164+ if rhs_is_array_ptr {
165+ new_rhs = t.make_prefix(.mul, new_rhs)
166+ }
167+ eq_call := if t.array_elem_needs_element_eq(elem_type) {
168+ t.make_array_elementwise_eq_call(new_lhs, new_rhs, elem_type, lhs_type, rhs_type, node)
169+ } else if elem_type.starts_with('[]') {
170+ t.make_call_typed('array_eq_array', arr3(new_lhs, new_rhs,
171+ t.make_int_literal(array_nested_eq_depth(lhs_type))), 'bool')
172+ } else if elem_type == 'string' {
115173 t.make_call_typed('array_eq_string', arr2(new_lhs, new_rhs), 'bool')
116174 } else {
117175 t.make_call_typed('array_eq_raw', arr3(new_lhs, new_rhs, t.make_sizeof_type(elem_type)),
@@ -130,28 +188,79 @@ fn (mut t Transformer) transform_infix_map_ops(_id flat.NodeId, node flat.Node)
130188 }
131189 lhs_id := t.a.children[node.children_start]
132190 rhs_id := t.a.children[node.children_start + 1]
133- lhs_type := t.node_type(lhs_id)
134- rhs_type := t.node_type(rhs_id)
135- lhs_map_type := t.clean_map_type(lhs_type)
136- rhs_map_type := t.clean_map_type(rhs_type)
191+ lhs_type := t.map_comparison_expr_type(lhs_id)
192+ rhs_type := t.map_comparison_expr_type(rhs_id)
193+ if t.equality_type_is_map_pointer(lhs_type) || t.equality_type_is_map_pointer(rhs_type) {
194+ return none
195+ }
196+ mut lhs_map_type := t.clean_map_type(lhs_type)
197+ mut rhs_map_type := t.clean_map_type(rhs_type)
198+ if !lhs_map_type.starts_with('map[') && rhs_map_type.starts_with('map[')
199+ && t.is_empty_map_init(lhs_id) {
200+ lhs_map_type = rhs_map_type
201+ }
202+ if !rhs_map_type.starts_with('map[') && lhs_map_type.starts_with('map[')
203+ && t.is_empty_map_init(rhs_id) {
204+ rhs_map_type = lhs_map_type
205+ }
137206 if !lhs_map_type.starts_with('map[') || !rhs_map_type.starts_with('map[') {
138207 return none
139208 }
140- mut new_lhs := t.transform_expr(lhs_id)
141- mut new_rhs := t.transform_expr(rhs_id)
209+ map_type := lhs_map_type
210+ mut new_lhs := t.transform_expr_for_type(lhs_id, map_type)
211+ mut new_rhs := t.transform_expr_for_type(rhs_id, map_type)
142212 if lhs_type.starts_with('&') {
143213 new_lhs = t.make_prefix(.mul, new_lhs)
144214 }
145215 if rhs_type.starts_with('&') {
146216 new_rhs = t.make_prefix(.mul, new_rhs)
147217 }
148- eq_call := t.make_call_typed('map_map_eq', arr2(new_lhs, new_rhs), 'bool')
218+ _, value_type := t.map_type_parts(map_type)
219+ eq_call := if value_type.len > 0 && t.map_value_needs_element_eq(value_type) {
220+ t.make_map_elementwise_eq_call(new_lhs, new_rhs, map_type, node)
221+ } else {
222+ t.make_call_typed('v3_map_map_eq', arr2(new_lhs, new_rhs), 'bool')
223+ }
149224 if node.op == .ne {
150225 return t.make_prefix(.not, eq_call)
151226 }
152227 return eq_call
153228}
154229
230+fn (mut t Transformer) map_comparison_expr_type(id flat.NodeId) string {
231+ if int(id) < 0 {
232+ return ''
233+ }
234+ node := t.a.nodes[int(id)]
235+ if node.kind == .call {
236+ concrete := t.concrete_generic_call_return_type(id, node)
237+ if concrete.len > 0 && t.clean_map_type(concrete).starts_with('map[') {
238+ return concrete
239+ }
240+ }
241+ if !isnil(t.tc) {
242+ if typ := t.tc.expr_type(id) {
243+ name := typ.name()
244+ if name.len > 0 && t.clean_map_type(name).starts_with('map[') {
245+ return name
246+ }
247+ }
248+ }
249+ mut typ := t.node_type(id)
250+ if typ.len == 0 && node.kind == .map_init {
251+ typ = if node.value.len > 0 { node.value } else { node.typ }
252+ }
253+ return typ
254+}
255+
256+fn (t &Transformer) is_empty_map_init(id flat.NodeId) bool {
257+ if int(id) < 0 {
258+ return false
259+ }
260+ node := t.a.nodes[int(id)]
261+ return node.kind == .map_init && node.children_count == 0
262+}
263+
155264// transform_infix_struct_ops transforms transform infix struct ops data for transform.
156265fn (mut t Transformer) transform_infix_struct_ops(_id flat.NodeId, node flat.Node) ?flat.NodeId {
157266 if node.children_count < 2 {
@@ -180,13 +289,21 @@ fn (mut t Transformer) transform_infix_struct_ops(_id flat.NodeId, node flat.Nod
180289 // so the operator lowers to the monomorphized method (`vec__Vec4_f32__plus`).
181290 struct_type = t.generic_struct_instance_name(lhs_type)
182291 }
292+ mut is_alias_operator := false
293+ if struct_type.len == 0 {
294+ if _ := t.struct_operator_call_info(lhs_type, node.op) {
295+ struct_type = lhs_type
296+ is_alias_operator = true
297+ }
298+ }
183299 if struct_type.len == 0 {
184300 return none
185301 }
186302 // Skip the checker/transformer agreement guard for generic-struct instances:
187303 // they resolve reliably, and an alias name (`SimdFloat4`) vs the resolved form
188304 // (`vec.Vec4[f32]`) would otherwise spuriously fail the comparison.
189- if checker_lhs_type.len > 0 && !has_smartcast && !struct_type.contains('[') {
305+ if checker_lhs_type.len > 0 && !has_smartcast && !struct_type.contains('[')
306+ && !is_alias_operator {
190307 checker_struct_type := t.struct_lookup_name(checker_lhs_type)
191308 if checker_struct_type.len > 0 && checker_struct_type != struct_type {
192309 return none
@@ -217,6 +334,7 @@ fn (mut t Transformer) transform_infix_struct_ops(_id flat.NodeId, node flat.Nod
217334 } else {
218335 arr2(call_lhs, call_rhs)
219336 }
337+ t.mark_fn_used_name(call_info.name)
220338 call := t.make_call_typed(call_info.name, args, node.typ)
221339 if call_info.negate {
222340 return t.make_prefix(.not, call)
@@ -229,6 +347,12 @@ fn (mut t Transformer) transform_infix_struct_ops(_id flat.NodeId, node flat.Nod
229347 if !t.has_struct_operator_fn(struct_type, '==') {
230348 lhs := t.stable_expr_for_reuse(lhs_id)
231349 rhs := t.stable_expr_for_reuse(t.a.children[node.children_start + 1])
350+ if field_eq := t.make_struct_field_eq_expr(lhs, rhs, struct_type) {
351+ if node.op == .ne {
352+ return t.make_prefix(.not, field_eq)
353+ }
354+ return field_eq
355+ }
232356 cmp := t.make_call_typed('memcmp', arr3(t.make_prefix(.amp, lhs), t.make_prefix(.amp, rhs),
233357 t.make_sizeof_type(struct_type)), 'int')
234358 return t.make_infix(node.op, cmp, t.make_int_literal(0))
@@ -239,6 +363,7 @@ fn (mut t Transformer) transform_infix_struct_ops(_id flat.NodeId, node flat.Nod
239363 }
240364 lhs := t.transform_expr(lhs_id)
241365 rhs := t.transform_expr(t.a.children[node.children_start + 1])
366+ t.mark_fn_used_name(eq_fn)
242367 eq_call := t.make_call_typed(eq_fn, arr2(lhs, rhs), 'bool')
243368 if node.op == .ne {
244369 return t.make_prefix(.not, eq_call)
@@ -282,6 +407,7 @@ fn (mut t Transformer) transform_transformed_struct_eq(node flat.Node, lhs flat.
282407 } else {
283408 arr2(call_lhs, call_rhs)
284409 }
410+ t.mark_fn_used_name(call_info.name)
285411 call := t.make_call_typed(call_info.name, args, node.typ)
286412 if call_info.negate {
287413 return t.make_prefix(.not, call)
@@ -297,6 +423,7 @@ fn (mut t Transformer) transform_transformed_struct_eq(node flat.Node, lhs flat.
297423 }
298424 call_lhs := t.stable_transformed_expr_for_reuse(lhs, lhs_type, 'eq_lhs')
299425 call_rhs := t.stable_transformed_expr_for_reuse(rhs, rhs_type, 'eq_rhs')
426+ t.mark_fn_used_name(eq_fn)
300427 eq_call := t.make_call_typed(eq_fn, arr2(call_lhs, call_rhs), 'bool')
301428 if node.op == .ne {
302429 return t.make_prefix(.not, eq_call)
@@ -400,19 +527,34 @@ fn struct_operator_symbol(op flat.Op) ?string {
400527
401528// struct_operator_fn_name supports struct operator fn name handling for Transformer.
402529fn (t &Transformer) struct_operator_fn_name(struct_type string, op_name string) ?string {
403- method_name := '${struct_type}.${op_name}'
404530 require_used := op_name in ['==', '!=']
405- if t.is_known_operator_fn_name(method_name, require_used) {
406- return method_name
531+ for receiver in t.operator_receiver_candidates(struct_type) {
532+ method_name := '${receiver}.${op_name}'
533+ if t.is_known_operator_fn_name(method_name, require_used) {
534+ return method_name
535+ }
536+ cmethod_name := c_name(method_name)
537+ if t.is_known_operator_fn_name(cmethod_name, require_used) {
538+ return cmethod_name
539+ }
540+ if name := t.generic_struct_operator_fn_name(receiver, op_name) {
541+ return name
542+ }
407543 }
408- cmethod_name := c_name(method_name)
409- if t.is_known_operator_fn_name(cmethod_name, require_used) {
410- return cmethod_name
544+ return none
545+}
546+
547+fn (t &Transformer) operator_receiver_candidates(struct_type string) []string {
548+ mut candidates := []string{cap: 2}
549+ if struct_type.len == 0 {
550+ return candidates
411551 }
412- if name := t.generic_struct_operator_fn_name(struct_type, op_name) {
413- return name
552+ candidates << struct_type
553+ if !struct_type.contains('.') && t.cur_module.len > 0 && t.cur_module != 'main'
554+ && t.cur_module != 'builtin' {
555+ candidates << '${t.cur_module}.${struct_type}'
414556 }
415- return none
557+ return candidates
416558}
417559
418560// generic_struct_instance_name returns the resolved generic-struct instance type
@@ -941,10 +1083,13 @@ fn (mut t Transformer) lower_array_membership_expr(base_id flat.NodeId, needle_i
9411083 cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(base, 'len', 'int'))
9421084 post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
9431085 elem_expr := t.array_get_value(base, t.make_ident(idx_name), elem_type)
1086+ pending_start := t.pending_stmts.len
9441087 eq_expr := t.make_membership_eq_expr(elem_expr, needle, elem_type)
1088+ mut loop_body := t.pending_stmts[pending_start..].clone()
1089+ t.pending_stmts = t.pending_stmts[..pending_start].clone()
9451090 assign_true := t.make_assign(t.make_ident(result_name), t.make_bool_literal(true))
9461091 then_block := t.make_block(arr1(assign_true))
947- loop_body := arr1(t.make_if(eq_expr, then_block, t.make_empty()))
1092+ loop_body << t.make_if(eq_expr, then_block, t.make_empty())
9481093 prefix << t.make_for_stmt(init, cond, post, loop_body, src)
9491094 for stmt in prefix {
9501095 t.pending_stmts << stmt
@@ -983,11 +1128,61 @@ fn (mut t Transformer) lower_array_index_expr(base_id flat.NodeId, needle_id fla
9831128 cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(base, 'len', 'int'))
9841129 post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
9851130 elem_expr := t.array_get_value(base, t.make_ident(idx_name), elem_type)
1131+ pending_start := t.pending_stmts.len
9861132 eq_expr := t.make_membership_eq_expr(elem_expr, needle, elem_type)
1133+ mut loop_body := t.pending_stmts[pending_start..].clone()
1134+ t.pending_stmts = t.pending_stmts[..pending_start].clone()
9871135 not_found := t.make_infix(.lt, t.make_ident(result_name), t.make_int_literal(0))
9881136 found_cond := t.make_infix(.logical_and, not_found, eq_expr)
9891137 assign_idx := t.make_assign(t.make_ident(result_name), t.make_ident(idx_name))
990- loop_body := arr1(t.make_if(found_cond, t.make_block(arr1(assign_idx)), t.make_empty()))
1138+ loop_body << t.make_if(found_cond, t.make_block(arr1(assign_idx)), t.make_empty())
1139+ prefix << t.make_for_stmt(init, cond, post, loop_body, src)
1140+ for stmt in prefix {
1141+ t.pending_stmts << stmt
1142+ }
1143+ return t.make_ident(result_name)
1144+}
1145+
1146+// lower_array_last_index_expr builds lower array last index expr data for transform.
1147+fn (mut t Transformer) lower_array_last_index_expr(base_id flat.NodeId, needle_id flat.NodeId, base_type string, receiver_first bool, src flat.Node) ?flat.NodeId {
1148+ clean_base_type := t.membership_container_type(base_type)
1149+ if !clean_base_type.starts_with('[]') && clean_base_type != 'array' {
1150+ return none
1151+ }
1152+ mut elem_type := if clean_base_type.starts_with('[]') { clean_base_type[2..] } else { '' }
1153+ if elem_type.len == 0 {
1154+ elem_type = t.membership_container_type(t.node_type(needle_id))
1155+ }
1156+ if elem_type.len == 0 {
1157+ return none
1158+ }
1159+ mut base := flat.empty_node
1160+ mut needle := flat.empty_node
1161+ if receiver_first {
1162+ base = t.stable_array_expr_for_membership(base_id, base_type, clean_base_type)
1163+ needle = t.stable_expr_for_reuse(needle_id)
1164+ } else {
1165+ needle = t.stable_expr_for_reuse(needle_id)
1166+ base = t.stable_array_expr_for_membership(base_id, base_type, clean_base_type)
1167+ }
1168+ mut prefix := []flat.NodeId{}
1169+ t.drain_pending(mut prefix)
1170+ result_name := t.new_temp('last_index')
1171+ idx_name := t.new_temp('last_index_idx')
1172+ prefix << t.make_decl_assign_typed(result_name, t.make_int_literal(-1), 'int')
1173+ init := t.make_decl_assign_typed(idx_name, t.make_infix(.minus, t.make_selector(base, 'len',
1174+ 'int'), t.make_int_literal(1)), 'int')
1175+ cond := t.make_infix(.ge, t.make_ident(idx_name), t.make_int_literal(0))
1176+ post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .dec))
1177+ elem_expr := t.array_get_value(base, t.make_ident(idx_name), elem_type)
1178+ pending_start := t.pending_stmts.len
1179+ eq_expr := t.make_membership_eq_expr(elem_expr, needle, elem_type)
1180+ mut loop_body := t.pending_stmts[pending_start..].clone()
1181+ t.pending_stmts = t.pending_stmts[..pending_start].clone()
1182+ not_found := t.make_infix(.lt, t.make_ident(result_name), t.make_int_literal(0))
1183+ found_cond := t.make_infix(.logical_and, not_found, eq_expr)
1184+ assign_idx := t.make_assign(t.make_ident(result_name), t.make_ident(idx_name))
1185+ loop_body << t.make_if(found_cond, t.make_block(arr1(assign_idx)), t.make_empty())
9911186 prefix << t.make_for_stmt(init, cond, post, loop_body, src)
9921187 for stmt in prefix {
9931188 t.pending_stmts << stmt
@@ -1007,23 +1202,78 @@ fn (mut t Transformer) stable_array_expr_for_membership(id flat.NodeId, raw_type
10071202
10081203// make_membership_eq_expr builds make membership eq expr data for transform.
10091204fn (mut t Transformer) make_membership_eq_expr(lhs flat.NodeId, rhs flat.NodeId, elem_type string) flat.NodeId {
1205+ return t.make_membership_eq_expr_with_seen(lhs, rhs, elem_type, []string{})
1206+}
1207+
1208+fn (mut t Transformer) make_membership_eq_expr_with_seen(lhs flat.NodeId, rhs flat.NodeId, elem_type string, seen []string) flat.NodeId {
1209+ if t.membership_type_is_pointer(elem_type) {
1210+ return t.make_infix(.eq, lhs, rhs)
1211+ }
10101212 mut clean := t.membership_container_type(elem_type)
10111213 if clean == 'string' {
10121214 return t.make_call_typed('string__eq', arr2(lhs, rhs), 'bool')
10131215 }
1216+ map_type := t.clean_map_type(clean)
1217+ if map_type.starts_with('map[') {
1218+ _, value_type := t.map_type_parts(map_type)
1219+ if value_type.len > 0 && t.map_value_needs_element_eq(value_type) {
1220+ src := if int(lhs) >= 0 && int(lhs) < t.a.nodes.len {
1221+ t.a.nodes[int(lhs)]
1222+ } else {
1223+ flat.Node{}
1224+ }
1225+ return t.make_map_elementwise_eq_call_with_seen(lhs, rhs, map_type, src, seen)
1226+ }
1227+ return t.make_call_typed('v3_map_map_eq', arr2(lhs, rhs), 'bool')
1228+ }
10141229 if clean.starts_with('[]') {
10151230 inner := clean[2..]
10161231 if inner == 'string' {
10171232 return t.make_call_typed('array_eq_string', arr2(lhs, rhs), 'bool')
10181233 }
1234+ if t.array_elem_needs_element_eq(inner) {
1235+ src := if int(lhs) >= 0 && int(lhs) < t.a.nodes.len {
1236+ t.a.nodes[int(lhs)]
1237+ } else {
1238+ flat.Node{}
1239+ }
1240+ return t.make_array_elementwise_eq_call_with_seen(lhs, rhs, inner, clean, clean, src,
1241+ seen)
1242+ }
1243+ if inner.starts_with('[]') {
1244+ return t.make_call_typed('array_eq_array', arr3(lhs, rhs,
1245+ t.make_int_literal(array_nested_eq_depth(clean))), 'bool')
1246+ }
10191247 return t.make_call_typed('array_eq_raw', arr3(lhs, rhs, t.make_sizeof_type(inner)), 'bool')
10201248 }
1249+ if t.is_fixed_array_type(clean) {
1250+ clean = t.resolved_fixed_array_canonical_type(clean)
1251+ if t.type_needs_semantic_eq(clean) {
1252+ if fixed_eq := t.make_fixed_array_elementwise_eq_expr_with_seen(lhs, rhs, clean, seen) {
1253+ return fixed_eq
1254+ }
1255+ }
1256+ lhs_value := t.stable_transformed_expr_for_reuse(lhs, clean, 'fixed_eq_lhs')
1257+ rhs_value := t.stable_transformed_expr_for_reuse(rhs, clean, 'fixed_eq_rhs')
1258+ cmp := t.make_call_typed('memcmp', arr3(t.make_prefix(.amp, lhs_value), t.make_prefix(.amp,
1259+ rhs_value), t.make_sizeof_type(clean)), 'int')
1260+ return t.make_infix(.eq, cmp, t.make_int_literal(0))
1261+ }
10211262 struct_type := t.struct_lookup_name(clean)
10221263 if struct_type.len > 0 {
10231264 method_name := '${struct_type}.=='
10241265 if t.is_known_fn_name(method_name) {
1266+ t.mark_fn_used_name(method_name)
10251267 return t.make_call(method_name, arr2(lhs, rhs))
10261268 }
1269+ if struct_type in seen {
1270+ cmp := t.make_call_typed('memcmp', arr3(t.make_prefix(.amp, lhs),
1271+ t.make_prefix(.amp, rhs), t.make_sizeof_type(struct_type)), 'int')
1272+ return t.make_infix(.eq, cmp, t.make_int_literal(0))
1273+ }
1274+ if field_eq := t.make_struct_field_eq_expr_with_seen(lhs, rhs, struct_type, seen) {
1275+ return field_eq
1276+ }
10271277 cmp := t.make_call_typed('memcmp', arr3(t.make_prefix(.amp, lhs), t.make_prefix(.amp, rhs),
10281278 t.make_sizeof_type(struct_type)), 'int')
10291279 return t.make_infix(.eq, cmp, t.make_int_literal(0))
@@ -1031,6 +1281,203 @@ fn (mut t Transformer) make_membership_eq_expr(lhs flat.NodeId, rhs flat.NodeId,
10311281 return t.make_infix(.eq, lhs, rhs)
10321282}
10331283
1284+fn (t &Transformer) array_elem_needs_element_eq(elem_type string) bool {
1285+ if t.membership_type_is_pointer(elem_type) {
1286+ return false
1287+ }
1288+ clean := t.membership_container_type(elem_type)
1289+ return clean != 'string' && t.type_needs_semantic_eq(clean)
1290+}
1291+
1292+fn (t &Transformer) map_value_needs_element_eq(value_type string) bool {
1293+ if t.membership_type_is_pointer(value_type) {
1294+ return false
1295+ }
1296+ clean := t.membership_container_type(value_type)
1297+ if t.is_fixed_array_type(clean) {
1298+ return true
1299+ }
1300+ return t.type_needs_semantic_eq(clean)
1301+}
1302+
1303+fn (t &Transformer) type_needs_semantic_eq(typ string) bool {
1304+ if t.membership_type_is_pointer(typ) {
1305+ return false
1306+ }
1307+ clean := t.membership_container_type(typ)
1308+ if clean == 'string' {
1309+ return true
1310+ }
1311+ if t.clean_map_type(clean).starts_with('map[') {
1312+ return true
1313+ }
1314+ if clean.starts_with('[]') {
1315+ return t.type_needs_semantic_eq(clean[2..])
1316+ }
1317+ if t.is_fixed_array_type(clean) {
1318+ elem_type := fixed_array_elem_type(clean)
1319+ return elem_type.len > 0 && t.type_needs_semantic_eq(elem_type)
1320+ }
1321+ return t.struct_lookup_name(clean).len > 0
1322+}
1323+
1324+fn (mut t Transformer) make_array_elementwise_eq_call(lhs flat.NodeId, rhs flat.NodeId, elem_type string, lhs_type string, rhs_type string, src flat.Node) flat.NodeId {
1325+ return t.make_array_elementwise_eq_call_with_seen(lhs, rhs, elem_type, lhs_type, rhs_type, src,
1326+ []string{})
1327+}
1328+
1329+fn (mut t Transformer) make_array_elementwise_eq_call_with_seen(lhs flat.NodeId, rhs flat.NodeId, elem_type string, lhs_type string, rhs_type string, src flat.Node, seen []string) flat.NodeId {
1330+ mut clean_elem := t.membership_container_type(elem_type)
1331+ if t.is_fixed_array_type(clean_elem) {
1332+ clean_elem = t.resolved_fixed_array_canonical_type(clean_elem)
1333+ }
1334+ lhs_value := t.stable_transformed_expr_for_reuse(lhs, lhs_type, 'arr_eq_lhs')
1335+ rhs_value := t.stable_transformed_expr_for_reuse(rhs, rhs_type, 'arr_eq_rhs')
1336+ result_name := t.new_temp('arr_eq')
1337+ idx_name := t.new_temp('arr_eq_idx')
1338+ len_eq := t.make_infix(.eq, t.make_selector(lhs_value, 'len', 'int'), t.make_selector(rhs_value,
1339+ 'len', 'int'))
1340+ t.pending_stmts << t.make_decl_assign_typed(result_name, len_eq, 'bool')
1341+ init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
1342+ in_bounds := t.make_infix(.lt, t.make_ident(idx_name), t.make_selector(lhs_value, 'len', 'int'))
1343+ cond := t.make_infix(.logical_and, t.make_ident(result_name), in_bounds)
1344+ post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
1345+ lhs_elem := t.array_get_value(lhs_value, t.make_ident(idx_name), clean_elem)
1346+ rhs_elem := t.array_get_value(rhs_value, t.make_ident(idx_name), clean_elem)
1347+ pending_start := t.pending_stmts.len
1348+ elem_eq := t.make_membership_eq_expr_with_seen(lhs_elem, rhs_elem, clean_elem, seen)
1349+ mut body_stmts := t.pending_stmts[pending_start..].clone()
1350+ t.pending_stmts = t.pending_stmts[..pending_start].clone()
1351+ set_false := t.make_assign(t.make_ident(result_name), t.make_bool_literal(false))
1352+ body_stmts << t.make_if(t.make_prefix(.not, t.make_paren(elem_eq)),
1353+ t.make_block(arr1(set_false)), t.make_empty())
1354+ t.pending_stmts << t.make_for_stmt(init, cond, post, body_stmts, src)
1355+ result := t.make_ident(result_name)
1356+ t.a.nodes[int(result)].typ = 'bool'
1357+ return result
1358+}
1359+
1360+fn (mut t Transformer) make_fixed_array_elementwise_eq_expr(lhs flat.NodeId, rhs flat.NodeId, fixed_type string) ?flat.NodeId {
1361+ return t.make_fixed_array_elementwise_eq_expr_with_seen(lhs, rhs, fixed_type, []string{})
1362+}
1363+
1364+fn (mut t Transformer) make_fixed_array_elementwise_eq_expr_with_seen(lhs flat.NodeId, rhs flat.NodeId, fixed_type string, seen []string) ?flat.NodeId {
1365+ clean_fixed_type := t.resolved_fixed_array_canonical_type(fixed_type)
1366+ elem_type := fixed_array_elem_type(clean_fixed_type)
1367+ if elem_type.len == 0 {
1368+ return none
1369+ }
1370+ lhs_value := t.stable_transformed_expr_for_reuse(lhs, clean_fixed_type, 'fixed_eq_lhs')
1371+ rhs_value := t.stable_transformed_expr_for_reuse(rhs, clean_fixed_type, 'fixed_eq_rhs')
1372+ result_name := t.new_temp('fixed_eq')
1373+ idx_name := t.new_temp('fixed_eq_idx')
1374+ t.pending_stmts << t.make_decl_assign_typed(result_name, t.make_bool_literal(true), 'bool')
1375+ init := t.make_decl_assign_typed(idx_name, t.make_int_literal(0), 'int')
1376+ cond := t.make_infix(.lt, t.make_ident(idx_name), t.make_fixed_array_len_expr(clean_fixed_type))
1377+ post := t.make_expr_stmt(t.make_postfix(t.make_ident(idx_name), .inc))
1378+ lhs_elem := t.make_index(lhs_value, t.make_ident(idx_name), elem_type)
1379+ rhs_elem := t.make_index(rhs_value, t.make_ident(idx_name), elem_type)
1380+ pending_start := t.pending_stmts.len
1381+ elem_eq := t.make_membership_eq_expr_with_seen(lhs_elem, rhs_elem, elem_type, seen)
1382+ mut body_stmts := t.pending_stmts[pending_start..].clone()
1383+ t.pending_stmts = t.pending_stmts[..pending_start].clone()
1384+ set_false := t.make_assign(t.make_ident(result_name), t.make_bool_literal(false))
1385+ body_stmts << t.make_if(t.make_prefix(.not, t.make_paren(elem_eq)),
1386+ t.make_block(arr1(set_false)), t.make_empty())
1387+ t.pending_stmts << t.make_for_stmt(init,
1388+ t.make_infix(.logical_and, t.make_ident(result_name), cond), post, body_stmts, flat.Node{})
1389+ result := t.make_ident(result_name)
1390+ t.a.nodes[int(result)].typ = 'bool'
1391+ return result
1392+}
1393+
1394+fn (mut t Transformer) make_map_elementwise_eq_call(lhs flat.NodeId, rhs flat.NodeId, map_type string, src flat.Node) flat.NodeId {
1395+ return t.make_map_elementwise_eq_call_with_seen(lhs, rhs, map_type, src, []string{})
1396+}
1397+
1398+fn (mut t Transformer) make_map_elementwise_eq_call_with_seen(lhs flat.NodeId, rhs flat.NodeId, map_type string, src flat.Node, seen []string) flat.NodeId {
1399+ key_type, raw_value_type := t.map_type_parts(map_type)
1400+ value_type := if t.is_fixed_array_type(raw_value_type) {
1401+ t.resolved_fixed_array_canonical_type(raw_value_type)
1402+ } else {
1403+ raw_value_type
1404+ }
1405+ if key_type.len == 0 || value_type.len == 0 {
1406+ return t.make_call_typed('v3_map_map_eq', arr2(lhs, rhs), 'bool')
1407+ }
1408+ lhs_value := t.stable_transformed_expr_for_reuse(lhs, map_type, 'map_eq_lhs')
1409+ rhs_value := t.stable_transformed_expr_for_reuse(rhs, map_type, 'map_eq_rhs')
1410+ result_name := t.new_temp('map_eq')
1411+ zero_name := t.new_temp('map_eq_zero')
1412+ key_name := t.new_temp('map_eq_key')
1413+ lhs_val_name := t.new_temp('map_eq_val')
1414+ len_eq := t.make_infix(.eq, t.make_selector(lhs_value, 'len', 'int'), t.make_selector(rhs_value,
1415+ 'len', 'int'))
1416+ t.pending_stmts << t.make_decl_assign_typed(result_name, len_eq, 'bool')
1417+ t.pending_stmts << t.make_decl_assign_typed(zero_name, t.zero_value_for_type(value_type),
1418+ value_type)
1419+ t.set_var_type(key_name, key_type)
1420+ t.set_var_type(lhs_val_name, value_type)
1421+ key_ident := t.make_ident(key_name)
1422+ val_ident := t.make_ident(lhs_val_name)
1423+ rhs_exists := t.make_map_exists_expr(rhs_value, map_type, key_name)
1424+ rhs_val := t.make_map_get_expr(rhs_value, map_type, key_name, zero_name, value_type)
1425+ pending_start := t.pending_stmts.len
1426+ value_eq := t.make_membership_eq_expr_with_seen(t.make_ident(lhs_val_name), rhs_val,
1427+ value_type, seen)
1428+ mut body := t.pending_stmts[pending_start..].clone()
1429+ t.pending_stmts = t.pending_stmts[..pending_start].clone()
1430+ missing := t.make_prefix(.not, t.make_paren(rhs_exists))
1431+ value_diff := t.make_prefix(.not, t.make_paren(value_eq))
1432+ failed := t.make_infix(.logical_or, missing, value_diff)
1433+ active := t.make_infix(.logical_and, t.make_ident(result_name), failed)
1434+ set_false := t.make_assign(t.make_ident(result_name), t.make_bool_literal(false))
1435+ body << t.make_if(active, t.make_block(arr1(set_false)), t.make_empty())
1436+ start := t.a.children.len
1437+ t.a.children << key_ident
1438+ t.a.children << val_ident
1439+ t.a.children << lhs_value
1440+ for stmt in body {
1441+ t.a.children << stmt
1442+ }
1443+ t.pending_stmts << t.a.add_node(flat.Node{
1444+ kind: .for_in_stmt
1445+ children_start: start
1446+ children_count: flat.child_count(3 + body.len)
1447+ pos: src.pos
1448+ value: '3'
1449+ })
1450+ result := t.make_ident(result_name)
1451+ t.a.nodes[int(result)].typ = 'bool'
1452+ return result
1453+}
1454+
1455+fn (mut t Transformer) make_struct_field_eq_expr(lhs flat.NodeId, rhs flat.NodeId, struct_type string) ?flat.NodeId {
1456+ return t.make_struct_field_eq_expr_with_seen(lhs, rhs, struct_type, []string{})
1457+}
1458+
1459+fn (mut t Transformer) make_struct_field_eq_expr_with_seen(lhs flat.NodeId, rhs flat.NodeId, struct_type string, seen []string) ?flat.NodeId {
1460+ info := t.lookup_struct_info(struct_type) or { return none }
1461+ mut next_seen := seen.clone()
1462+ next_seen << struct_type
1463+ mut eq := flat.empty_node
1464+ for field in info.fields {
1465+ field_type := if field.typ.len > 0 { field.typ } else { field.raw_typ }
1466+ lhs_field := t.make_selector(lhs, field.name, field_type)
1467+ rhs_field := t.make_selector(rhs, field.name, field_type)
1468+ field_eq := if t.membership_type_is_pointer(field_type) {
1469+ t.make_infix(.eq, lhs_field, rhs_field)
1470+ } else {
1471+ t.make_membership_eq_expr_with_seen(lhs_field, rhs_field, field_type, next_seen)
1472+ }
1473+ eq = if int(eq) < 0 { field_eq } else { t.make_infix(.logical_and, eq, field_eq) }
1474+ }
1475+ if int(eq) < 0 {
1476+ return t.make_bool_literal(true)
1477+ }
1478+ return eq
1479+}
1480+
10341481// selector_field_type supports selector field type handling for Transformer.
10351482fn (t &Transformer) selector_field_type(node flat.Node) string {
10361483 if node.value.len == 0 {
@@ -1083,6 +1530,78 @@ fn (t &Transformer) membership_container_type(typ string) string {
10831530 return t.normalize_type_alias(clean)
10841531}
10851532
1533+fn (t &Transformer) membership_type_is_pointer(typ string) bool {
1534+ mut clean := t.normalize_type_alias(typ).trim_space()
1535+ for {
1536+ if clean.starts_with('shared ') {
1537+ clean = clean[7..].trim_space()
1538+ continue
1539+ }
1540+ if clean.starts_with('mut ') {
1541+ clean = clean[4..].trim_space()
1542+ continue
1543+ }
1544+ break
1545+ }
1546+ return clean.starts_with('&')
1547+}
1548+
1549+fn (t &Transformer) equality_type_is_array_pointer(typ string) bool {
1550+ mut clean := typ.trim_space()
1551+ for clean.starts_with('shared ') {
1552+ clean = clean[7..].trim_space()
1553+ }
1554+ if clean.starts_with('mut ') {
1555+ return false
1556+ }
1557+ clean = t.normalize_type_alias(clean).trim_space()
1558+ for clean.starts_with('shared ') {
1559+ clean = clean[7..].trim_space()
1560+ }
1561+ if !clean.starts_with('&') {
1562+ return false
1563+ }
1564+ container := t.membership_container_type(clean)
1565+ return container.starts_with('[]') || container == 'array'
1566+}
1567+
1568+fn (t &Transformer) equality_expr_is_string_pointer(id flat.NodeId, typ string) bool {
1569+ if int(id) < 0 || int(id) >= t.a.nodes.len {
1570+ return false
1571+ }
1572+ node := t.a.nodes[int(id)]
1573+ if node.kind in [.string_literal, .string_interp] {
1574+ return false
1575+ }
1576+ mut clean := typ.trim_space()
1577+ for clean.starts_with('shared ') {
1578+ clean = clean[7..].trim_space()
1579+ }
1580+ if clean.starts_with('mut ') {
1581+ return false
1582+ }
1583+ clean = t.normalize_type_alias(clean).trim_space()
1584+ for clean.starts_with('shared ') {
1585+ clean = clean[7..].trim_space()
1586+ }
1587+ return clean == '&string'
1588+}
1589+
1590+fn (t &Transformer) equality_type_is_map_pointer(typ string) bool {
1591+ mut clean := typ.trim_space()
1592+ for clean.starts_with('shared ') {
1593+ clean = clean[7..].trim_space()
1594+ }
1595+ if clean.starts_with('mut ') {
1596+ return false
1597+ }
1598+ clean = t.normalize_type_alias(clean).trim_space()
1599+ for clean.starts_with('shared ') {
1600+ clean = clean[7..].trim_space()
1601+ }
1602+ return clean.starts_with('&') && t.clean_map_type(clean).starts_with('map[')
1603+}
1604+
10861605// membership_container_is_pointer_array supports membership_container_is_pointer_array handling.
10871606fn (t &Transformer) membership_container_is_pointer_array(typ string) bool {
10881607 mut clean := t.normalize_type_alias(typ).trim_space()
@@ -1121,7 +1640,10 @@ fn (mut t Transformer) stable_expr_for_reuse(id flat.NodeId) flat.NodeId {
11211640 return expr
11221641 }
11231642 tmp_name := t.new_temp('in_lhs')
1124- tmp_typ := t.node_type(id)
1643+ mut tmp_typ := t.node_type(expr)
1644+ if tmp_typ.len == 0 {
1645+ tmp_typ = t.node_type(id)
1646+ }
11251647 decl := t.make_decl_assign(tmp_name, expr)
11261648 if tmp_typ.len > 0 {
11271649 t.a.nodes[int(decl)].typ = tmp_typ
@@ -1442,11 +1964,27 @@ pub fn (mut t Transformer) make_int_literal(value int) flat.NodeId {
14421964 return t.a.add_val(.int_literal, '${value}')
14431965}
14441966
1967+pub fn (mut t Transformer) make_int_literal_typed(value string, typ string) flat.NodeId {
1968+ return t.a.add_node(flat.Node{
1969+ kind: .int_literal
1970+ value: value
1971+ typ: typ
1972+ })
1973+}
1974+
14451975// make_float_literal builds make float literal data for transform.
14461976pub fn (mut t Transformer) make_float_literal(value string) flat.NodeId {
14471977 return t.a.add_val(.float_literal, value)
14481978}
14491979
1980+pub fn (mut t Transformer) make_float_literal_typed(value string, typ string) flat.NodeId {
1981+ return t.a.add_node(flat.Node{
1982+ kind: .float_literal
1983+ value: value
1984+ typ: typ
1985+ })
1986+}
1987+
14501988// make_bool_literal builds make bool literal data for transform.
14511989pub fn (mut t Transformer) make_bool_literal(value bool) flat.NodeId {
14521990 return t.a.add_val(.bool_literal, if value { 'true' } else { 'false' })
@@ -1484,7 +2022,7 @@ fn (t &Transformer) is_fixed_array_type(s string) bool {
14842022 if len_text.contains(',') || isnil(t.tc) {
14852023 return false
14862024 }
1487- return t.tc.const_int_value(len_text, []string{}) != none
2025+ return t.tc.const_int_value_in_module(len_text, t.cur_module, []string{}) != none
14882026}
14892027
14902028// fixed_array_len supports fixed array len handling for transform.
@@ -1494,6 +2032,12 @@ fn fixed_array_len(s string) int {
14942032
14952033// fixed_array_len_text supports fixed array len text handling for transform.
14962034fn fixed_array_len_text(s string) string {
2035+ if !s.starts_with('[') {
2036+ _, dims := transform_postfix_fixed_array_parts(s)
2037+ if dims.len > 0 {
2038+ return dims[0]
2039+ }
2040+ }
14972041 return s.all_after('[').all_before(']').trim_space()
14982042}
14992043
@@ -1502,7 +2046,45 @@ fn fixed_array_elem_type(s string) string {
15022046 if s.starts_with('[') {
15032047 return s.all_after(']')
15042048 }
1505- return s.all_before('[')
2049+ elem, dims := transform_postfix_fixed_array_parts(s)
2050+ if dims.len == 0 {
2051+ return s.all_before('[')
2052+ }
2053+ mut out := elem
2054+ for i := dims.len - 1; i > 0; i-- {
2055+ out += '[${dims[i]}]'
2056+ }
2057+ return out
2058+}
2059+
2060+fn fixed_array_canonical_type(s string) string {
2061+ if !s.starts_with('[') {
2062+ return s
2063+ }
2064+ elem_type := fixed_array_canonical_type(fixed_array_elem_type(s))
2065+ len_text := fixed_array_len_text(s)
2066+ return '${elem_type}[${len_text}]'
2067+}
2068+
2069+fn (t &Transformer) resolved_fixed_array_canonical_type(s string) string {
2070+ clean := fixed_array_canonical_type(s)
2071+ if !t.is_fixed_array_type(clean) {
2072+ return clean
2073+ }
2074+ elem_type := fixed_array_elem_type(clean)
2075+ resolved_elem := if t.is_fixed_array_type(elem_type) {
2076+ t.resolved_fixed_array_canonical_type(elem_type)
2077+ } else {
2078+ elem_type
2079+ }
2080+ len_text := fixed_array_len_text(clean)
2081+ if is_decimal_text(len_text) || isnil(t.tc) {
2082+ return '${resolved_elem}[${len_text}]'
2083+ }
2084+ if v := t.tc.const_int_value_in_module(len_text, t.cur_module, []string{}) {
2085+ return '${resolved_elem}[${v}]'
2086+ }
2087+ return '${resolved_elem}[${len_text}]'
15062088}
15072089
15082090// is_decimal_text reports whether is decimal text applies in transform.
@@ -1529,7 +2111,7 @@ fn (mut t Transformer) make_fixed_array_len_expr(s string) flat.NodeId {
15292111 // the raw expression as an ident would c_name-mangle it into an undeclared
15302112 // identifier such as `segs_+_1`.
15312113 if !isnil(t.tc) {
1532- if v := t.tc.const_int_value(len_text, []string{}) {
2114+ if v := t.tc.const_int_value_in_module(len_text, t.cur_module, []string{}) {
15332115 return t.make_int_literal(v)
15342116 }
15352117 }
@@ -409,7 +409,7 @@ fn (mut t Transformer) normalize_generic_call_expr(id flat.NodeId, node flat.Nod
409409 if base.kind !in [.ident, .selector] {
410410 return id
411411 }
412- type_arg := t.generic_call_type_arg_name(t.a.child(&fn_node, 1))
412+ type_arg := t.generic_call_type_args_name(fn_node)
413413 if type_arg.len == 0 {
414414 return id
415415 }
@@ -453,6 +453,33 @@ fn (t &Transformer) generic_call_type_arg_name(id flat.NodeId) string {
453453 }
454454 return '${base}.${node.value}'
455455 }
456+ .index {
457+ if node.children_count < 2 || node.value == 'range' {
458+ return ''
459+ }
460+ base := t.generic_call_type_arg_name(t.a.child(&node, 0))
461+ if base.len == 0 {
462+ return ''
463+ }
464+ mut args := []string{}
465+ for i in 1 .. node.children_count {
466+ arg := t.generic_call_type_arg_name(t.a.child(&node, i))
467+ if arg.len == 0 {
468+ return ''
469+ }
470+ args << arg
471+ }
472+ return '${base}[${args.join(', ')}]'
473+ }
474+ .array_init {
475+ if node.value.len > 0 {
476+ return '[]${node.value}'
477+ }
478+ return ''
479+ }
480+ .map_init {
481+ return node.value
482+ }
456483 .prefix {
457484 if node.children_count == 0 {
458485 return ''
@@ -472,6 +499,21 @@ fn (t &Transformer) generic_call_type_arg_name(id flat.NodeId) string {
472499 }
473500}
474501
502+fn (t &Transformer) generic_call_type_args_name(index_node flat.Node) string {
503+ if index_node.kind != .index || index_node.children_count < 2 || index_node.value == 'range' {
504+ return ''
505+ }
506+ mut args := []string{}
507+ for i in 1 .. index_node.children_count {
508+ arg := t.generic_call_type_arg_name(t.a.child(&index_node, i))
509+ if arg.len == 0 {
510+ return ''
511+ }
512+ args << arg
513+ }
514+ return args.join(', ')
515+}
516+
475517// transform_call_args transforms all children of a call expression.
476518// child[0] is the function expression, children[1..n] are arguments.
477519fn (mut t Transformer) transform_call_args(id flat.NodeId, node flat.Node) flat.NodeId {
@@ -485,7 +527,10 @@ fn (mut t Transformer) transform_call_args(id flat.NodeId, node flat.Node) flat.
485527 })
486528 }
487529 call_name := t.call_name_for_node(id, node)
488- params := t.call_param_types(call_name)
530+ mut params := t.call_param_types(call_name)
531+ if concrete_params := t.concrete_generic_call_param_types(id, node) {
532+ params = concrete_params.clone()
533+ }
489534 param_offset := t.call_param_offset(call_name, node, params)
490535 is_variadic := t.call_is_variadic(call_name)
491536 variadic_idx := if is_variadic && params.len > 0 && params[params.len - 1] is types.Array {
@@ -515,6 +560,13 @@ fn (mut t Transformer) transform_call_args(id flat.NodeId, node flat.Node) flat.
515560 if variadic_idx >= 0 && param_idx == variadic_idx {
516561 variadic_type := params[variadic_idx]
517562 if variadic_type is types.Array {
563+ if arg_node.kind == .prefix && arg_node.value == '...'
564+ && arg_node.children_count > 0 {
565+ spread_id := t.a.child(&arg_node, 0)
566+ new_children << t.transform_call_arg_for_param(spread_id, param_type)
567+ i++
568+ break
569+ }
518570 remaining := int(node.children_count) - i
519571 if remaining == 1 {
520572 arg_type := t.node_type(arg_id)
@@ -675,13 +727,13 @@ fn (t &Transformer) call_param_offset(call_name string, node flat.Node, params [
675727 if _ := t.static_assoc_fn_name(base_id, fn_node.value) {
676728 return 0
677729 }
678- if t.receiver_method_param_offset(base_id, node, params) == 1 {
679- return 1
680- }
681730 method_name := t.resolve_receiver_method_name(base_id, fn_node.value)
682731 if method_name.len == 0 {
683732 return 0
684733 }
734+ if t.receiver_method_param_offset(base_id, node, params) == 1 {
735+ return 1
736+ }
685737 if call_name.len == 0 || call_name == method_name || call_name == c_name(method_name) {
686738 return 1
687739 }
@@ -882,6 +934,9 @@ fn (mut t Transformer) transform_call_arg_for_param(arg_id flat.NodeId, param_ty
882934 return const_arg
883935 }
884936 }
937+ if param_type.len > 0 && t.type_text_has_generic_placeholder(param_type, t.cur_module) {
938+ return t.transform_expr(arg_id)
939+ }
885940 return t.transform_expr_for_type(arg_id, param_type)
886941}
887942
@@ -1454,11 +1509,29 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat
14541509 'bool' {
14551510 return t.make_call_typed('bool.str', arr1(expr), 'string')
14561511 }
1457- 'u8', 'byte', 'u16', 'u32', 'u64' {
1512+ 'u8', 'byte', 'u16', 'u32', 'usize' {
14581513 return t.make_call_typed('strconv__format_uint', arr2(expr, t.make_int_literal(10)),
14591514 'string')
14601515 }
1461- 'int', 'int literal', 'i8', 'i16', 'i32', 'i64', 'isize', 'usize' {
1516+ 'u64' {
1517+ return t.make_call_typed('u64.str', arr1(expr), 'string')
1518+ }
1519+ 'int', 'int literal' {
1520+ return t.make_call_typed('int.str', arr1(expr), 'string')
1521+ }
1522+ 'i8' {
1523+ return t.make_call_typed('i8.str', arr1(expr), 'string')
1524+ }
1525+ 'i16' {
1526+ return t.make_call_typed('i16.str', arr1(expr), 'string')
1527+ }
1528+ 'i32' {
1529+ return t.make_call_typed('i32.str', arr1(expr), 'string')
1530+ }
1531+ 'i64' {
1532+ return t.make_call_typed('i64.str', arr1(expr), 'string')
1533+ }
1534+ 'isize' {
14621535 return t.make_call_typed('strconv__format_int', arr2(expr, t.make_int_literal(10)),
14631536 'string')
14641537 }
@@ -1508,9 +1581,10 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat
15081581 return t.wrap_string_conversion(arr, '[]${elem_type}')
15091582 } else if clean_typ.len > 0 && clean_typ.starts_with('[]') {
15101583 return t.lower_array_str(expr, clean_typ)
1584+ } else if clean_typ.len > 0 && clean_typ.starts_with('map[') {
1585+ return t.lower_map_str(expr, clean_typ)
15111586 } else if clean_typ == 'rune' {
1512- return t.make_call_typed('strconv__format_int', arr2(expr, t.make_int_literal(10)),
1513- 'string')
1587+ return t.make_call_typed('rune.str', arr1(expr), 'string')
15141588 } else {
15151589 return expr
15161590 }
@@ -1518,6 +1592,132 @@ fn (mut t Transformer) wrap_string_conversion(expr flat.NodeId, typ string) flat
15181592 }
15191593}
15201594
1595+fn (mut t Transformer) wrap_formatted_string_conversion(expr flat.NodeId, typ string, format string) flat.NodeId {
1596+ if format.len == 0 {
1597+ return t.wrap_string_conversion(expr, typ)
1598+ }
1599+ mut clean_typ := typ
1600+ if clean_typ.starts_with('&') {
1601+ clean_typ = clean_typ[1..]
1602+ }
1603+ if clean_typ.starts_with('builtin.') {
1604+ clean_typ = clean_typ.all_after_last('.')
1605+ }
1606+ if precision := fixed_decimal_precision(format) {
1607+ if clean_typ in ['f32', 'f64', 'float_literal'] {
1608+ arg := if clean_typ == 'f64' {
1609+ expr
1610+ } else {
1611+ t.make_cast('f64', expr, 'f64')
1612+ }
1613+ return t.make_call_typed('v3_f64_fixed', arr2(arg, t.make_int_literal(precision)),
1614+ 'string')
1615+ }
1616+ }
1617+ if format == 'c' {
1618+ if clean_typ in ['u8', 'byte', 'char', 'rune', 'int'] {
1619+ arg := if clean_typ == 'int' {
1620+ expr
1621+ } else {
1622+ t.make_cast('int', expr, 'int')
1623+ }
1624+ return t.make_call_typed('v3_char_string', arr1(arg), 'string')
1625+ }
1626+ }
1627+ if base := integer_format_base(format) {
1628+ if clean_typ in ['u8', 'byte', 'u16', 'u32', 'u64', 'usize'] {
1629+ return t.make_call_typed('strconv__format_uint', arr2(expr, t.make_int_literal(base)),
1630+ 'string')
1631+ }
1632+ if clean_typ in ['int', 'i8', 'i16', 'i32', 'i64', 'isize'] {
1633+ return t.make_call_typed('strconv__format_int', arr2(expr, t.make_int_literal(base)),
1634+ 'string')
1635+ }
1636+ }
1637+ if width := zero_padded_decimal_width(format) {
1638+ if clean_typ in ['int', 'i8', 'i16', 'i32', 'i64', 'isize', 'usize', 'u8', 'byte', 'u16',
1639+ 'u32', 'u64'] {
1640+ if clean_typ in ['u64', 'usize', 'u32', 'u16', 'u8', 'byte'] {
1641+ arg := if clean_typ == 'u64' {
1642+ expr
1643+ } else {
1644+ t.make_cast('u64', expr, 'u64')
1645+ }
1646+ return t.make_call_typed('v3_u64_zpad', arr2(arg, t.make_int_literal(width)),
1647+ 'string')
1648+ }
1649+ if clean_typ in ['i64', 'isize', 'i32', 'i16', 'i8'] {
1650+ arg := if clean_typ == 'i64' {
1651+ expr
1652+ } else {
1653+ t.make_cast('i64', expr, 'i64')
1654+ }
1655+ return t.make_call_typed('v3_i64_zpad', arr2(arg, t.make_int_literal(width)),
1656+ 'string')
1657+ }
1658+ return t.make_call_typed('v3_int_zpad', arr2(expr, t.make_int_literal(width)), 'string')
1659+ }
1660+ }
1661+ return t.wrap_string_conversion(expr, typ)
1662+}
1663+
1664+fn fixed_decimal_precision(format string) ?int {
1665+ if format.len < 3 || format[0] != `.` || format[format.len - 1] != `f` {
1666+ return none
1667+ }
1668+ mut n := 0
1669+ for i in 1 .. format.len - 1 {
1670+ ch := format[i]
1671+ if ch < `0` || ch > `9` {
1672+ return none
1673+ }
1674+ n = n * 10 + int(ch - `0`)
1675+ }
1676+ return n
1677+}
1678+
1679+fn integer_format_base(format string) ?int {
1680+ match format {
1681+ 'x' {
1682+ return 16
1683+ }
1684+ 'o' {
1685+ return 8
1686+ }
1687+ else {}
1688+ }
1689+
1690+ return none
1691+}
1692+
1693+fn zero_padded_decimal_width(format string) ?int {
1694+ if format.len < 2 || format[0] != `0` {
1695+ return none
1696+ }
1697+ mut end := format.len
1698+ if format[end - 1] == `d` {
1699+ end--
1700+ }
1701+ if end >= 3 && format[end - 2] == `.` && format[end - 1] == `0` {
1702+ end -= 2
1703+ }
1704+ if end <= 1 {
1705+ return none
1706+ }
1707+ mut width := 0
1708+ for i in 1 .. end {
1709+ ch := format[i]
1710+ if ch < `0` || ch > `9` {
1711+ return none
1712+ }
1713+ width = width * 10 + int(ch - `0`)
1714+ }
1715+ if width <= 0 {
1716+ return none
1717+ }
1718+ return width
1719+}
1720+
15211721// append_string builds `result = result + piece` using the runtime string concat helper.
15221722// Using string__plus directly (instead of `+=`) keeps the synthesized node independent of
15231723// type resolution for the freshly-introduced temp.
@@ -1570,6 +1770,68 @@ fn (mut t Transformer) lower_array_str(arr_expr flat.NodeId, base_type string) f
15701770 return t.make_ident(result_name)
15711771}
15721772
1773+fn (mut t Transformer) lower_map_str(map_expr flat.NodeId, map_type string) flat.NodeId {
1774+ key_type, value_type := t.map_type_parts(map_type)
1775+ if key_type.len == 0 || value_type.len == 0 {
1776+ return map_expr
1777+ }
1778+ lowered := t.transform_expr_for_type(map_expr, map_type)
1779+ return t.make_call_typed('v3_map_str', arr4(lowered,
1780+ t.make_int_literal(t.map_str_kind_for_type(key_type)),
1781+ t.make_int_literal(t.map_str_kind_for_type(value_type)),
1782+ t.make_int_literal(t.map_str_fixed_len_for_type(value_type))), 'string')
1783+}
1784+
1785+fn (t &Transformer) map_str_kind_for_type(typ string) int {
1786+ mut clean := t.normalize_type_alias(typ).trim_space()
1787+ if clean.starts_with('builtin.') {
1788+ clean = clean.all_after_last('.')
1789+ }
1790+ match clean {
1791+ 'string' {
1792+ return 1
1793+ }
1794+ 'rune' {
1795+ return 4
1796+ }
1797+ 'isize', 'char', 'i8', 'i16', 'i32', 'i64', 'int' {
1798+ return 2
1799+ }
1800+ 'usize', 'u8', 'byte', 'u16', 'u32', 'u64' {
1801+ return 3
1802+ }
1803+ 'f32', 'f64' {
1804+ return 5
1805+ }
1806+ 'bool' {
1807+ return 7
1808+ }
1809+ else {
1810+ if clean.starts_with('[]') && clean[2..] in ['f32', 'f64'] {
1811+ return 6
1812+ }
1813+ if transform_type_text_is_fixed_array(clean) {
1814+ elem := t.normalize_type_alias(fixed_array_elem_type(clean))
1815+ if elem == 'f32' {
1816+ return 9
1817+ }
1818+ if elem == 'f64' {
1819+ return 6
1820+ }
1821+ }
1822+ return 0
1823+ }
1824+ }
1825+}
1826+
1827+fn (t &Transformer) map_str_fixed_len_for_type(typ string) int {
1828+ clean := t.normalize_type_alias(typ).trim_space()
1829+ if transform_type_text_is_fixed_array(clean) {
1830+ return fixed_array_len(clean)
1831+ }
1832+ return 0
1833+}
1834+
15731835// wrap_optional_string_conversion transforms wrap optional string conversion data for transform.
15741836fn (mut t Transformer) wrap_optional_string_conversion(expr flat.NodeId, typ string) flat.NodeId {
15751837 opt_type := t.qualify_optional_type(typ)
@@ -1720,8 +1982,8 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
17201982 return none
17211983 }
17221984 array_builtin_method := t.array_builtin_method_name(fn_node.value) or { '' }
1723- if fn_node.value !in ['clone', 'reverse', 'contains', 'index', 'join', 'any', 'all', 'count',
1724- 'equals', 'prepend', 'insert', 'push_many'] {
1985+ if fn_node.value !in ['clone', 'reverse', 'contains', 'index', 'last_index', 'join', 'any',
1986+ 'all', 'count', 'equals', 'prepend', 'insert', 'push_many', 'str'] {
17251987 if fn_node.value !in ['filter', 'map', 'sort', 'sorted', 'sort_with_compare', 'sorted_with_compare']
17261988 && array_builtin_method.len == 0 {
17271989 return none
@@ -1729,6 +1991,14 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
17291991 }
17301992 base_id := t.a.children[fn_node.children_start]
17311993 mut base_type := t.node_type(base_id)
1994+ base_node := t.a.nodes[int(base_id)]
1995+ if (array_type_has_generic_placeholder(base_type) || base_type.contains('unknown'))
1996+ && base_node.kind == .call {
1997+ concrete_base_type := t.concrete_generic_call_return_type(base_id, base_node)
1998+ if concrete_base_type.starts_with('[]') {
1999+ base_type = concrete_base_type
2000+ }
2001+ }
17322002 if (!base_type.starts_with('[]') && !t.is_fixed_array_type(base_type)) || base_type == 'array' {
17332003 lvalue_base_type := t.lvalue_type(base_id)
17342004 if lvalue_base_type.starts_with('[]') || t.is_fixed_array_type(lvalue_base_type) {
@@ -1736,7 +2006,6 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
17362006 }
17372007 }
17382008 if !base_type.starts_with('[]') && !t.is_fixed_array_type(base_type) {
1739- base_node := t.a.nodes[int(base_id)]
17402009 if base_node.kind in [.call, .selector, .as_expr] {
17412010 new_base := t.transform_expr(base_id)
17422011 new_base_type := t.node_type(new_base)
@@ -1762,7 +2031,20 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
17622031 }
17632032 }
17642033 }
2034+ if fn_node.value == 'str' && base_node.kind == .call {
2035+ new_base := t.transform_expr(base_id)
2036+ new_base_type := t.node_type(new_base)
2037+ if new_base_type.starts_with('[]') {
2038+ return t.lower_array_str(new_base, new_base_type)
2039+ }
2040+ if new_base_type.starts_with('map[') {
2041+ return t.lower_map_str(new_base, new_base_type)
2042+ }
2043+ }
17652044 clean_base_type := if base_type.starts_with('&') { base_type[1..] } else { base_type }
2045+ if fn_node.value == 'str' && t.is_builder_receiver(base_id, base_type) {
2046+ return none
2047+ }
17662048 if t.is_fixed_array_type(clean_base_type) && fn_node.value == 'pointers'
17672049 && array_builtin_method.len > 0 {
17682050 method_name := t.resolve_receiver_method_name(base_id, fn_node.value)
@@ -1860,6 +2142,9 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
18602142 'count' {
18612143 return t.lower_array_count_call(node, fn_node, clean_base_type)
18622144 }
2145+ 'str' {
2146+ return t.wrap_string_conversion(t.transform_expr(base_id), clean_base_type)
2147+ }
18632148 'equals' {
18642149 if node.children_count < 2 {
18652150 return none
@@ -1872,6 +2157,14 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
18722157 }
18732158 receiver := t.transform_expr(base_id)
18742159 arg := t.transform_expr(t.a.children[node.children_start + 1])
2160+ if t.array_elem_needs_element_eq(elem_type) {
2161+ return t.make_array_elementwise_eq_call(receiver, arg, elem_type, clean_base_type,
2162+ clean_base_type, node)
2163+ }
2164+ if elem_type.starts_with('[]') {
2165+ return t.make_call_typed('array_eq_array', arr3(receiver, arg,
2166+ t.make_int_literal(array_nested_eq_depth(clean_base_type))), 'bool')
2167+ }
18752168 if elem_type == 'string' {
18762169 return t.make_call_typed('array_eq_string', arr2(receiver, arg), 'bool')
18772170 }
@@ -1891,10 +2184,7 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
18912184 t.mark_fn_used(method_name)
18922185 return t.make_call_typed(method_name, args, ret_type)
18932186 }
1894- receiver := t.transform_expr(base_id)
1895- t.mark_fn_used('array__clone')
1896- return t.make_call_typed('array__clone', arr1(t.runtime_addr(receiver, base_type)),
1897- clean_base_type)
2187+ return t.make_array_clone_call(base_id, base_type)
18982188 }
18992189 'reverse' {
19002190 mut receiver := t.transform_expr(base_id)
@@ -1934,6 +2224,16 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
19342224 fn_name := if elem_type == 'string' { 'array_index_string' } else { 'array_index_int' }
19352225 return t.make_call_typed(fn_name, arr2(receiver, arg), 'int')
19362226 }
2227+ 'last_index' {
2228+ if node.children_count < 2 {
2229+ return none
2230+ }
2231+ arg_id := t.a.children[node.children_start + 1]
2232+ if lowered := t.lower_array_last_index_expr(base_id, arg_id, base_type, true, node) {
2233+ return lowered
2234+ }
2235+ return none
2236+ }
19372237 'join' {
19382238 if node.children_count < 2 {
19392239 return none
@@ -1953,6 +2253,9 @@ fn (mut t Transformer) try_lower_array_method_call(call_id flat.NodeId, node fla
19532253 t.mark_fn_used(method_name)
19542254 return t.make_call_typed(method_name, args, ret_type)
19552255 }
2256+ if fn_node.value in ['pop', 'clear', 'trim'] {
2257+ t.mark_fn_used('array__${fn_node.value}')
2258+ }
19562259 return none
19572260 }
19582261 if array_builtin_method.len > 0 {
@@ -1982,8 +2285,8 @@ fn (t &Transformer) array_builtin_method_name(method string) ?string {
19822285}
19832286
19842287fn array_method_stays_in_cgen(method string) bool {
1985- return method in ['last', 'first', 'delete_last', 'pop', 'clear', 'repeat', 'repeat_to_depth',
1986- 'trim', 'ensure_cap', 'delete', 'free', 'str', 'bytestr', 'wait']
2288+ return method in ['last', 'first', 'delete_last', 'pop', 'pop_left', 'clear', 'repeat',
2289+ 'repeat_to_depth', 'trim', 'ensure_cap', 'delete', 'free', 'str', 'bytestr', 'wait']
19872290}
19882291
19892292// try_lower_map_method_call supports try lower map method call handling for Transformer.
@@ -1998,7 +2301,10 @@ fn (mut t Transformer) try_lower_map_method_call(call_id flat.NodeId, node flat.
19982301 return none
19992302 }
20002303 base_id := t.a.children[fn_node.children_start]
2001- base_type := t.node_type(base_id)
2304+ mut base_type := t.node_type(base_id)
2305+ if base_type.len == 0 {
2306+ base_type = t.checker_node_type(base_id)
2307+ }
20022308 clean_type := t.clean_map_type(base_type)
20032309 if !clean_type.starts_with('map[') {
20042310 return none
@@ -2136,6 +2442,7 @@ fn (mut t Transformer) lift_fn_literal(_id flat.NodeId, node flat.Node) flat.Nod
21362442 mut param_names := []string{}
21372443 mut capture_names := []string{}
21382444 mut capture_types := map[string]string{}
2445+ mut capture_globals := map[string]string{}
21392446 mut body_ids := []flat.NodeId{}
21402447 for i in 0 .. node.children_count {
21412448 child_id := t.a.child(&node, i)
@@ -2164,6 +2471,7 @@ fn (mut t Transformer) lift_fn_literal(_id flat.NodeId, node flat.Node) flat.Nod
21642471 }
21652472 capture_names << child.value
21662473 capture_types[child.value] = capture_type
2474+ capture_globals[child.value] = '${name}_${child.value}'
21672475 }
21682476 } else {
21692477 body_ids << child_id
@@ -2188,14 +2496,20 @@ fn (mut t Transformer) lift_fn_literal(_id flat.NodeId, node flat.Node) flat.Nod
21882496 continue
21892497 }
21902498 capture_type := capture_types[capture_name] or { continue }
2499+ capture_global := capture_globals[capture_name] or { continue }
2500+ t.add_fn_literal_capture_global(capture_global, capture_type)
2501+ t.pending_stmts << t.make_assign(t.make_ident(capture_global), t.make_ident(capture_name))
21912502 t.set_var_type(capture_name, capture_type)
2192- lifted_body << t.make_decl_assign_typed(capture_name, t.zero_value_for_type(capture_type),
2503+ lifted_body << t.make_decl_assign_typed(capture_name, t.make_ident(capture_global),
21932504 capture_type)
21942505 }
21952506 for body_id in body_ids {
21962507 lifted_body << body_id
21972508 }
2509+ outer_pending := t.pending_stmts.clone()
2510+ t.pending_stmts.clear()
21982511 new_body := t.transform_stmts(lifted_body)
2512+ t.pending_stmts = outer_pending
21992513 t.var_types = saved_vars
22002514 t.cur_fn_name = saved_fn_name
22012515 t.cur_fn_ret_type = saved_ret_type
@@ -2241,6 +2555,34 @@ fn (mut t Transformer) lift_fn_literal(_id flat.NodeId, node flat.Node) flat.Nod
22412555 return t.make_ident(name)
22422556}
22432557
2558+fn (mut t Transformer) add_fn_literal_capture_global(name string, typ string) {
2559+ if typ.len == 0 {
2560+ return
2561+ }
2562+ if t.cur_module.len > 0 {
2563+ t.a.add_node(flat.Node{
2564+ kind: .module_decl
2565+ value: t.cur_module
2566+ })
2567+ }
2568+ field := t.a.add_node(flat.Node{
2569+ kind: .field_decl
2570+ value: name
2571+ typ: typ
2572+ })
2573+ start := t.a.children.len
2574+ t.a.children << field
2575+ t.a.add_node(flat.Node{
2576+ kind: .global_decl
2577+ children_start: start
2578+ children_count: 1
2579+ })
2580+ t.globals[name] = typ
2581+ if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
2582+ t.globals['${t.cur_module}.${name}'] = typ
2583+ }
2584+}
2585+
22442586// try_lower_builtin_call checks if a call is to a builtin that needs special lowering.
22452587// Returns none for most calls so the caller falls through to generic call transform.
22462588fn (mut t Transformer) try_lower_builtin_call(_id flat.NodeId, node flat.Node) ?flat.NodeId {
@@ -2292,6 +2634,12 @@ fn (mut t Transformer) try_lower_builtin_call(_id flat.NodeId, node flat.Node) ?
22922634 return none
22932635 }
22942636 name := fn_node.value
2637+ if name in ['maxof', 'minof'] && node.value.len > 0 && node.children_count == 1
2638+ && t.is_std_minmaxof_call(_id, name) {
2639+ if value := t.try_lower_minmaxof_call(name, node.value) {
2640+ return value
2641+ }
2642+ }
22952643 match name {
22962644 'copy' {
22972645 return t.try_lower_copy_call(node)
@@ -2327,6 +2675,54 @@ fn (mut t Transformer) try_lower_builtin_call(_id flat.NodeId, node flat.Node) ?
23272675 }
23282676}
23292677
2678+fn (t &Transformer) is_std_minmaxof_call(id flat.NodeId, name string) bool {
2679+ if isnil(t.tc) {
2680+ return false
2681+ }
2682+ resolved := t.tc.resolved_call_name(id) or { return false }
2683+ return resolved == 'math.${name}'
2684+}
2685+
2686+fn (mut t Transformer) try_lower_minmaxof_call(name string, raw_type string) ?flat.NodeId {
2687+ mut typ := raw_type
2688+ if !isnil(t.tc) {
2689+ typ = t.normalize_type_in_module(raw_type, t.cur_module)
2690+ }
2691+ if typ.starts_with('builtin.') {
2692+ typ = typ.all_after_last('.')
2693+ }
2694+ value := if name == 'maxof' {
2695+ match typ {
2696+ 'i8' { '127' }
2697+ 'i16' { '32767' }
2698+ 'int', 'i32' { '2147483647' }
2699+ 'i64' { '9223372036854775807' }
2700+ 'u8' { '255' }
2701+ 'u16' { '65535' }
2702+ 'u32' { '4294967295' }
2703+ 'u64' { '18446744073709551615' }
2704+ 'f32' { '3.40282346638528859811704183484516925440e+38' }
2705+ 'f64' { '1.797693134862315708145274237317043567981e+308' }
2706+ else { return none }
2707+ }
2708+ } else {
2709+ match typ {
2710+ 'i8' { '-128' }
2711+ 'i16' { '-32768' }
2712+ 'int', 'i32' { '(-2147483647 - 1)' }
2713+ 'i64' { '(-9223372036854775807 - 1)' }
2714+ 'u8', 'u16', 'u32', 'u64' { '0' }
2715+ 'f32' { '-3.40282346638528859811704183484516925440e+38' }
2716+ 'f64' { '-1.797693134862315708145274237317043567981e+308' }
2717+ else { return none }
2718+ }
2719+ }
2720+ if typ in ['f32', 'f64'] {
2721+ return t.make_float_literal_typed(value, typ)
2722+ }
2723+ return t.make_int_literal_typed(value, typ)
2724+}
2725+
23302726fn (mut t Transformer) try_lower_copy_call(node flat.Node) ?flat.NodeId {
23312727 if node.children_count != 3 {
23322728 return none
@@ -2602,6 +2998,7 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.
26022998 if base_type.len == 0 {
26032999 base_type = t.lvalue_type(base_id)
26043000 }
3001+ base_is_pointer := base_type.starts_with('&')
26053002 if base_type.starts_with('&') {
26063003 base_type = base_type[1..]
26073004 }
@@ -2615,6 +3012,13 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.
26153012 if base_type == '[]rune' && method == 'string' {
26163013 return t.make_call_typed('Array_rune__string', arr1(t.transform_expr(base_id)), 'string')
26173014 }
3015+ if method == 'str' && t.is_array_transform_call(base_id) {
3016+ base := t.transform_expr(base_id)
3017+ transformed_type := t.node_type(base)
3018+ if transformed_type.starts_with('[]') {
3019+ return t.wrap_string_conversion(base, transformed_type)
3020+ }
3021+ }
26183022 if method == 'str' && t.is_builder_receiver(base_id, base_type) {
26193023 method_name := 'strings.Builder.str'
26203024 args := t.transform_receiver_method_args(node, base_id, method_name)
@@ -2629,11 +3033,14 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.
26293033 }
26303034 return t.wrap_string_conversion(t.transform_expr(base_id), base_type)
26313035 }
3036+ if builtin_base_type == 'string' && method == 'hex' && !base_is_pointer {
3037+ return t.make_call_typed('string.hex', arr1(t.transform_expr(base_id)), 'string')
3038+ }
26323039 if base_type == '[]u8' || base_type == '[]byte' {
26333040 if method == 'bytestr' {
26343041 return t.make_call_typed('Array_u8__bytestr', arr1(t.transform_expr(base_id)), 'string')
26353042 }
2636- if method == 'hex' {
3043+ if method == 'hex' && !base_is_pointer {
26373044 return t.make_call_typed('Array_u8__hex', arr1(t.transform_expr(base_id)), 'string')
26383045 }
26393046 }
@@ -2649,7 +3056,8 @@ fn (mut t Transformer) try_lower_receiver_method_call(id flat.NodeId, node flat.
26493056 if builtin_base_type == 'u8' && method in ['is_space', 'is_digit', 'is_hex_digit', 'is_letter'] {
26503057 return t.make_call_typed('u8__${method}', arr1(t.transform_expr(base_id)), 'bool')
26513058 }
2652- if builtin_base_type in ['u8', 'i8', 'u16', 'i16', 'u32', 'int', 'u64', 'i64', 'rune']
3059+ if !base_is_pointer
3060+ && builtin_base_type in ['u8', 'i8', 'u16', 'i16', 'u32', 'int', 'u64', 'i64', 'rune']
26533061 && method in ['hex', 'hex_full'] {
26543062 return t.make_call_typed('${builtin_base_type}__${method}',
26553063 arr1(t.transform_expr(base_id)), 'string')
@@ -3296,6 +3704,13 @@ fn (mut t Transformer) transform_receiver_method_args_with_base(node flat.Node,
32963704 if variadic_idx >= 0 && param_idx == variadic_idx {
32973705 variadic_type := params[variadic_idx]
32983706 if variadic_type is types.Array {
3707+ if arg_node.kind == .prefix && arg_node.value == '...'
3708+ && arg_node.children_count > 0 {
3709+ spread_id := t.a.child(&arg_node, 0)
3710+ args << t.transform_call_arg_for_param(spread_id, param_type)
3711+ i++
3712+ break
3713+ }
32993714 remaining := int(node.children_count) - i
33003715 if remaining == 1 {
33013716 arg_type := t.node_type(arg_id)
@@ -3474,7 +3889,8 @@ fn (t &Transformer) get_call_return_type(id flat.NodeId, node flat.Node) string
34743889 }
34753890 }
34763891 }
3477- if fn_node.kind == .selector && fn_node.value in ['clone', 'reverse']
3892+ if fn_node.kind == .selector
3893+ && fn_node.value in ['clone', 'reverse', 'repeat', 'repeat_to_depth']
34783894 && fn_node.children_count > 0 {
34793895 base_id := t.a.child(fn_node, 0)
34803896 base_type := t.node_type(base_id)
@@ -3500,7 +3916,10 @@ fn (t &Transformer) get_call_return_type(id flat.NodeId, node flat.Node) string
35003916 if fn_node.kind == .selector && fn_node.value in ['keys', 'values']
35013917 && fn_node.children_count > 0 {
35023918 base_id := t.a.child(fn_node, 0)
3503- base_type := t.node_type(base_id)
3919+ mut base_type := t.node_type(base_id)
3920+ if base_type.len == 0 {
3921+ base_type = t.checker_node_type(base_id)
3922+ }
35043923 clean_type := t.clean_map_type(base_type)
35053924 if clean_type.starts_with('map[') {
35063925 elem_type := if fn_node.value == 'keys' {
@@ -86,6 +86,9 @@ fn (mut t Transformer) transform_for_in_body(id flat.NodeId, node flat.Node) []f
8686 if header_count < 3 || node.children_count < 3 {
8787 return arr1(id)
8888 }
89+ if t.cur_fn_is_generic {
90+ return arr1(id)
91+ }
8992 key_id := t.a.child(&node, 0) // loop var ident — pass through (do not transform a binding)
9093 val_id := t.a.child(&node, 1) // may be flat.empty_node (-1)
9194 container_id := t.a.child(&node, 2)
@@ -117,6 +120,11 @@ fn (mut t Transformer) transform_for_in_body(id flat.NodeId, node flat.Node) []f
117120 return t.lower_indexed_for_in(id, node, key_id, val_id, container_id, pool_iter, has_index,
118121 body_ids)
119122 }
123+ if iter_elem_type := t.iterator_for_in_elem_type(iter_type) {
124+ body_ids := t.a.children_of(&node)[header_count..].clone()
125+ return t.lower_iterator_for_in(id, node, key_id, val_id, container_id, iter_type,
126+ iter_elem_type, has_index, body_ids)
127+ }
120128 effective_iter := if iter_type.starts_with('...') {
121129 '[]' + iter_type[3..]
122130 } else if iter_type.starts_with('&[]') {
@@ -179,11 +187,17 @@ fn (mut t Transformer) rebuild_for_in_stmt(_id flat.NodeId, node flat.Node) []fl
179187 false
180188 }
181189 if header_count == 4 || container_is_range {
182- // range `for i in 0 .. n`: single loop var (child0) is an int
190+ // range `for i in 0 .. n`: single loop var (child0) follows the lower bound
183191 if int(key_id) >= 0 {
184192 key_name := t.a.nodes[int(key_id)].value
185193 if key_name.len > 0 {
186- t.set_var_type(key_name, 'int')
194+ low_id := if container_is_range {
195+ range_node := t.a.nodes[int(container_id)]
196+ t.a.child(&range_node, 0)
197+ } else {
198+ container_id
199+ }
200+ t.set_var_type(key_name, t.range_loop_var_type_name(low_id))
187201 }
188202 }
189203 } else if has_index {
@@ -249,6 +263,17 @@ fn (mut t Transformer) rebuild_for_in_stmt(_id flat.NodeId, node flat.Node) []fl
249263 }))
250264}
251265
266+fn (t &Transformer) iterator_for_in_elem_type(iter_type string) ?string {
267+ mut clean := iter_type.trim_space()
268+ if clean.starts_with('&') {
269+ clean = clean[1..]
270+ }
271+ if clean == 'RunesIterator' || clean == 'builtin.RunesIterator' {
272+ return 'rune'
273+ }
274+ return none
275+}
276+
252277// lower_range_for_in builds lower range for in data for transform.
253278fn (mut t Transformer) lower_range_for_in(id flat.NodeId, node flat.Node, key_id flat.NodeId, low_id flat.NodeId, high_id flat.NodeId, body_ids []flat.NodeId) []flat.NodeId {
254279 if int(key_id) < 0 {
@@ -258,12 +283,13 @@ fn (mut t Transformer) lower_range_for_in(id flat.NodeId, node flat.Node, key_id
258283 if key.kind != .ident || key.value.len == 0 {
259284 return arr1(id)
260285 }
261- t.set_var_type(key.value, 'int')
286+ range_type := t.range_loop_var_type_name(low_id)
287+ t.set_var_type(key.value, range_type)
262288 low := t.transform_expr(low_id)
263289 high := t.stable_expr_for_reuse(high_id)
264290 mut prefix := []flat.NodeId{}
265291 t.drain_pending(mut prefix)
266- init := t.make_decl_assign_typed(key.value, low, 'int')
292+ init := t.make_decl_assign_typed(key.value, low, range_type)
267293 cond := t.make_infix(.lt, t.make_ident(key.value), high)
268294 post := t.make_expr_stmt(t.make_postfix(t.make_ident(key.value), .inc))
269295 new_body := t.transform_stmts(body_ids)
@@ -271,6 +297,71 @@ fn (mut t Transformer) lower_range_for_in(id flat.NodeId, node flat.Node, key_id
271297 return prefix
272298}
273299
300+fn (t &Transformer) range_loop_var_type_name(low_id flat.NodeId) string {
301+ typ := t.node_type(low_id)
302+ if typ.len == 0 {
303+ return 'int'
304+ }
305+ clean := t.normalize_type_alias(typ)
306+ if t.is_integer_type_name(clean) {
307+ return typ
308+ }
309+ return 'int'
310+}
311+
312+fn (mut t Transformer) lower_iterator_for_in(id flat.NodeId, node flat.Node, key_id flat.NodeId, val_id flat.NodeId, container_id flat.NodeId, iter_type string, elem_type string, has_index bool, body_ids []flat.NodeId) []flat.NodeId {
313+ if int(key_id) < 0 {
314+ return arr1(id)
315+ }
316+ key := t.a.nodes[int(key_id)]
317+ if key.kind != .ident || key.value.len == 0 {
318+ return arr1(id)
319+ }
320+ mut elem_name := key.value
321+ if has_index {
322+ if int(val_id) < 0 {
323+ return arr1(id)
324+ }
325+ val := t.a.nodes[int(val_id)]
326+ if val.kind != .ident || val.value.len == 0 {
327+ return arr1(id)
328+ }
329+ elem_name = val.value
330+ }
331+ iter_name := t.new_temp('iter')
332+ next_name := t.new_temp('iter_next')
333+ iter_expr := t.transform_expr(container_id)
334+ mut prefix := []flat.NodeId{}
335+ t.drain_pending(mut prefix)
336+ prefix << t.make_decl_assign_typed(iter_name, iter_expr, iter_type)
337+ init := if has_index {
338+ t.set_var_type(key.value, 'int')
339+ t.make_decl_assign_typed(key.value, t.make_int_literal(0), 'int')
340+ } else {
341+ t.make_empty()
342+ }
343+ t.set_var_type(elem_name, elem_type)
344+ next_call := t.make_call_typed('RunesIterator.next', arr1(t.make_prefix(.amp,
345+ t.make_ident(iter_name))), '?${elem_type}')
346+ next_decl := t.make_decl_assign_typed(next_name, next_call, '?${elem_type}')
347+ no_value := t.make_prefix(.not, t.make_selector(t.make_ident(next_name), 'ok', 'bool'))
348+ break_if_done := t.make_if(no_value, t.make_block(arr1(t.a.add(.break_stmt))), t.make_empty())
349+ elem_decl := t.make_decl_assign_typed(elem_name, t.make_selector(t.make_ident(next_name),
350+ 'value', elem_type), elem_type)
351+ mut loop_body := []flat.NodeId{}
352+ loop_body << next_decl
353+ loop_body << break_if_done
354+ loop_body << elem_decl
355+ loop_body << t.transform_stmts(body_ids)
356+ post := if has_index {
357+ t.make_expr_stmt(t.make_postfix(t.make_ident(key.value), .inc))
358+ } else {
359+ t.make_empty()
360+ }
361+ prefix << t.make_for_stmt(init, t.make_bool_literal(true), post, loop_body, node)
362+ return prefix
363+}
364+
274365// lower_indexed_for_in builds lower indexed for in data for transform.
275366fn (mut t Transformer) lower_indexed_for_in(id flat.NodeId, node flat.Node, key_id flat.NodeId, val_id flat.NodeId, container_id flat.NodeId, iter_type string, has_index bool, body_ids []flat.NodeId) []flat.NodeId {
276367 if int(key_id) < 0 {
@@ -285,7 +376,9 @@ fn (mut t Transformer) lower_indexed_for_in(id flat.NodeId, node flat.Node, key_
285376 t.drain_pending(mut prefix)
286377 mut actual_iter_type := iter_type
287378 container_type := t.node_type(container)
288- if container_type.len > 0 && !for_iter_type_has_generic_placeholder(actual_iter_type)
379+ if container_type.len > 0 && container_type !in ['array', 'map', 'unknown']
380+ && for_iter_type_is_container(container_type)
381+ && !for_iter_type_has_generic_placeholder(actual_iter_type)
289382 && !(t.is_fixed_array_type(actual_iter_type) && !t.is_fixed_array_type(container_type)) {
290383 actual_iter_type = container_type
291384 }
@@ -377,9 +470,6 @@ fn (mut t Transformer) make_for_stmt(init flat.NodeId, cond flat.NodeId, post fl
377470
378471// detect_for_in_type resolves detect for in type information for transform.
379472fn (mut t Transformer) detect_for_in_type(node flat.Node) string {
380- if node.typ.len > 0 {
381- return node.typ
382- }
383473 header_count := node.value.int()
384474 container_idx := if header_count >= 3 { header_count - 1 } else { 2 }
385475 if node.children_count > container_idx {
@@ -387,11 +477,26 @@ fn (mut t Transformer) detect_for_in_type(node flat.Node) string {
387477 if fixed_array_type := t.detect_for_in_global_fixed_array_type(iter_id) {
388478 return fixed_array_type
389479 }
390- return t.node_type(iter_id)
480+ iter_type := t.node_type(iter_id)
481+ if iter_type.len > 0
482+ && (node.typ.len == 0 || for_iter_type_has_generic_placeholder(node.typ)
483+ || for_iter_type_is_container(iter_type)) {
484+ return iter_type
485+ }
486+ }
487+ if node.typ.len > 0 {
488+ return node.typ
391489 }
392490 return ''
393491}
394492
493+fn for_iter_type_is_container(iter_type string) bool {
494+ clean := iter_type.trim_space()
495+ return clean == 'string' || clean.starts_with('[]') || clean.starts_with('&[]')
496+ || clean.starts_with('...') || clean.starts_with('map[') || clean.starts_with('&map[')
497+ || clean.starts_with('[')
498+}
499+
395500fn (t &Transformer) detect_for_in_global_fixed_array_type(id flat.NodeId) ?string {
396501 if int(id) < 0 {
397502 return none
@@ -61,7 +61,7 @@ fn (mut t Transformer) try_expand_if_guard(_id flat.NodeId, node flat.Node) ?[]f
6161 }
6262 }
6363 }
64- if value_decls.len == 0 {
64+ if value_decls.len == 0 && lhs.value != '_' && value_type != 'void' {
6565 value_decls << t.make_decl_assign_typed(lhs.value, t.make_selector(t.make_ident(tmp_name),
6666 'value', value_type), value_type)
6767 }
@@ -78,7 +78,9 @@ fn (mut t Transformer) try_expand_if_guard(_id flat.NodeId, node flat.Node) ?[]f
7878 }
7979 }
8080 } else {
81- t.set_var_type(lhs.value, value_type)
81+ if lhs.value != '_' && value_type != 'void' {
82+ t.set_var_type(lhs.value, value_type)
83+ }
8284 }
8385 mut then_children := []flat.NodeId{}
8486 for value_decl in value_decls {
@@ -107,13 +109,17 @@ fn (mut t Transformer) try_expand_if_guard(_id flat.NodeId, node flat.Node) ?[]f
107109}
108110
109111// optional_result_expr_type_name supports optional result expr type name handling for Transformer.
110-fn (t &Transformer) optional_result_expr_type_name(id flat.NodeId) string {
112+fn (mut t Transformer) optional_result_expr_type_name(id flat.NodeId) string {
111113 if int(id) < 0 {
112114 return ''
113115 }
114116 if !isnil(t.tc) {
115117 node := t.a.nodes[int(id)]
116118 if node.kind == .call {
119+ concrete_ret := t.concrete_generic_call_return_type(id, node)
120+ if t.is_optional_type_name(concrete_ret) {
121+ return concrete_ret
122+ }
117123 if name := t.tc.resolved_call_name(id) {
118124 if ret := t.tc.fn_ret_types[name] {
119125 ret_name := ret.name()
@@ -34,6 +34,23 @@ fn (mut t Transformer) make_new_map_call(map_type string) flat.NodeId {
3434 return t.make_call_typed('new_map', args, map_type)
3535}
3636
37+fn (t &Transformer) new_map_call_type(node flat.Node) string {
38+ if node.kind != .call || node.children_count < 3 {
39+ return ''
40+ }
41+ callee := t.a.child_node(&node, 0)
42+ if callee.kind != .ident || callee.value != 'new_map' {
43+ return ''
44+ }
45+ key_size := t.a.child_node(&node, 1)
46+ value_size := t.a.child_node(&node, 2)
47+ if key_size.kind != .sizeof_expr || value_size.kind != .sizeof_expr || key_size.value.len == 0
48+ || value_size.value.len == 0 {
49+ return ''
50+ }
51+ return 'map[${key_size.value}]${value_size.value}'
52+}
53+
3754// map_callback_names supports map callback names handling for transform.
3855fn map_callback_names(key_type string) (string, string, string, string) {
3956 if key_type == 'string' {
@@ -91,9 +108,14 @@ fn (mut t Transformer) map_index_info(index_id flat.NodeId) ?MapIndexInfo {
91108
92109// make_map_get_expr builds make map get expr data for transform.
93110fn (mut t Transformer) make_map_get_expr(map_expr flat.NodeId, base_type string, key_name string, zero_name string, value_type string) flat.NodeId {
111+ clean_value_type := if t.is_fixed_array_type(value_type) {
112+ fixed_array_canonical_type(value_type)
113+ } else {
114+ value_type
115+ }
94116 call := t.make_call_typed('map__get', arr3(t.runtime_addr(map_expr, base_type), t.make_prefix(.amp,
95117 t.make_ident(key_name)), t.make_prefix(.amp, t.make_ident(zero_name))), 'voidptr')
96- cast := t.make_cast('&${value_type}', call, '&${value_type}')
118+ cast := t.make_cast('&${clean_value_type}', call, '&${clean_value_type}')
97119 return t.make_prefix(.mul, cast)
98120}
99121
@@ -458,15 +480,15 @@ fn (mut t Transformer) lower_map_init_to_runtime(id flat.NodeId, node flat.Node)
458480 for i := 0; i + 1 < node.children_count; i += 2 {
459481 key_name := t.new_temp('map_key')
460482 value_name := t.new_temp('map_val')
461- t.pending_stmts << t.make_decl_assign_typed(key_name,
462- t.transform_expr(t.a.child(&node, i)), key_type)
483+ t.pending_stmts << t.make_decl_assign_typed(key_name, t.transform_expr_for_type(t.a.child(&node, i),
484+ key_type), key_type)
463485 value_id := t.a.child(&node, i + 1)
464486 value := if value_type.starts_with('&') && t.is_sum_type_name(value_type[1..]) {
465487 t.transform_expr_for_type(value_id, value_type)
466488 } else if value_type in t.sum_types || t.resolve_sum_name(value_type) in t.sum_types {
467489 t.wrap_sum_value(value_id, value_type)
468490 } else {
469- t.transform_expr(value_id)
491+ t.transform_expr_for_type(value_id, value_type)
470492 }
471493 t.pending_stmts << t.make_decl_assign_typed(value_name, value, value_type)
472494 call := t.make_call_typed('map__set', arr3(t.make_prefix(.amp, t.make_ident(tmp_name)), t.make_prefix(.amp,
@@ -60,11 +60,15 @@ fn (mut t Transformer) monomorphize_pass() []string {
6060 call_module := t.node_module_map_cache[i] or { cur_module }
6161 decl_key, args := t.generic_call_specialization(flat.NodeId(i), node,
6262 call_module, decls) or { continue }
63+ decl := decls[decl_key] or { continue }
64+ if !t.call_has_source_generic_args(node)
65+ && t.generic_args_contain_alias(args, decl.module) {
66+ t.a.nodes[i].value = args.join(', ')
67+ }
6368 spec_key := generic_fn_spec_key(decl_key, args)
6469 if emitted[spec_key] {
6570 continue
6671 }
67- decl := decls[decl_key] or { continue }
6872 generated << t.generated_fn_used_names(decl,
6973 t.emit_generic_fn_specialization(decl, args), args)
7074 emitted[spec_key] = true
@@ -563,6 +567,7 @@ fn (mut t Transformer) transform_specialized_fn_body(clone_id flat.NodeId, modul
563567 old_fn_name := t.cur_fn_name
564568 old_ret_type := t.cur_fn_ret_type
565569 old_var_types := t.var_types.clone()
570+ old_is_generic := t.cur_fn_is_generic
566571 t.cur_module = module_name
567572 t.cur_file = file_name
568573 if !isnil(t.tc) {
@@ -577,6 +582,7 @@ fn (mut t Transformer) transform_specialized_fn_body(clone_id flat.NodeId, modul
577582 t.cur_fn_name = old_fn_name
578583 t.cur_fn_ret_type = old_ret_type
579584 t.var_types = old_var_types
585+ t.cur_fn_is_generic = old_is_generic
580586}
581587
582588fn (mut t Transformer) generated_fn_used_names(decl GenericFnDecl, clone_id flat.NodeId, args []string) []string {
@@ -597,6 +603,7 @@ fn (mut t Transformer) specialize_cloned_fn_signature(clone_id flat.NodeId, decl
597603 params := t.generic_fn_param_names(decl.node, decl.module)
598604 t.a.nodes[int(clone_id)].typ = t.specialized_signature_type_text(decl, decl.node.typ, args,
599605 params)
606+ t.a.nodes[int(clone_id)].generic_params = []string{}
600607 mut dst_params := []flat.NodeId{}
601608 clone := t.a.nodes[int(clone_id)]
602609 for i in 0 .. clone.children_count {
@@ -819,6 +826,10 @@ fn (mut t Transformer) rewrite_generic_calls(decls map[string]GenericFnDecl, tem
819826 decl_key, args := t.generic_call_specialization(flat.NodeId(i), node, call_module,
820827 decls) or { continue }
821828 decl := decls[decl_key] or { continue }
829+ if !t.call_has_source_generic_args(node)
830+ && t.generic_args_contain_alias(args, decl.module) {
831+ t.a.nodes[i].value = args.join(', ')
832+ }
822833 if t.generic_decl_is_receiver_method(decl.node) {
823834 t.rewrite_generic_method_call(flat.NodeId(i), node, decl, args)
824835 } else {
@@ -859,6 +870,44 @@ fn (mut t Transformer) concrete_generic_call_return_type(id flat.NodeId, node fl
859870 return t.normalize_type_alias(ret)
860871}
861872
873+fn (mut t Transformer) concrete_generic_call_param_types(id flat.NodeId, node flat.Node) ?[]types.Type {
874+ if node.kind != .call || node.children_count == 0 || isnil(t.tc) {
875+ return none
876+ }
877+ decls := t.cached_generic_fn_decls()
878+ if decls.len == 0 {
879+ return none
880+ }
881+ decl_key := t.generic_call_decl_key(id, node, t.cur_module, decls) or { return none }
882+ decl := decls[decl_key] or { return none }
883+ if t.should_skip_generic_call_specialization(decl_key) {
884+ return none
885+ }
886+ mut args := []string{}
887+ if explicit := t.explicit_generic_call_args(node, t.cur_module) {
888+ args = explicit.clone()
889+ } else {
890+ args = t.infer_generic_call_args_from_params(decl, node) or { return none }
891+ }
892+ if args.len == 0 || t.generic_args_have_placeholders(args) {
893+ return none
894+ }
895+ params := t.generic_fn_param_names(decl.node, decl.module)
896+ mut result := []types.Type{}
897+ for i in 0 .. decl.node.children_count {
898+ child := t.a.child_node(&decl.node, i)
899+ if child.kind != .param {
900+ continue
901+ }
902+ param_type := t.specialized_signature_type_text(decl, child.typ, args, params)
903+ result << t.tc.parse_type(param_type)
904+ }
905+ if result.len == 0 {
906+ return none
907+ }
908+ return result
909+}
910+
862911fn (mut t Transformer) cached_generic_fn_decls() map[string]GenericFnDecl {
863912 if !t.generic_fn_decls_ready {
864913 t.generic_fn_decls_cache = t.collect_generic_fn_decls()
@@ -885,10 +934,7 @@ fn (mut t Transformer) infer_generic_call_args_from_params(decl GenericFnDecl, n
885934 param_idx++
886935 continue
887936 }
888- mut arg_type := t.node_type(arg_id)
889- if arg_type.len == 0 {
890- arg_type = t.generic_arg_expr_type(arg_id)
891- }
937+ arg_type := t.generic_call_arg_type_for_inference(arg_id)
892938 if arg_type.len > 0 {
893939 infer_generic_type_args(child.typ, arg_type, mut inferred)
894940 if is_recv_param {
@@ -910,10 +956,14 @@ fn (mut t Transformer) rewrite_generic_plain_call(id flat.NodeId, node flat.Node
910956 spec_value := specialized_generic_fn_value(decl.node.value, args)
911957 spec_name := transform_qualified_fn_name(decl.module, spec_value)
912958 ret_typ := t.specialized_fn_return_type_text(decl, args)
959+ param_types := t.specialized_generic_call_param_type_texts(decl, args)
913960 mut children := []flat.NodeId{cap: int(node.children_count)}
914961 children << t.make_ident(spec_name)
915962 for i in 1 .. node.children_count {
916- children << t.a.child(&node, i)
963+ arg_id := t.a.child(&node, i)
964+ param_idx := i - 1
965+ param_type := if param_idx < param_types.len { param_types[param_idx] } else { '' }
966+ children << t.retype_generic_call_literal_arg(arg_id, param_type)
917967 }
918968 start := t.a.children.len
919969 for child in children {
@@ -931,6 +981,159 @@ fn (mut t Transformer) rewrite_generic_plain_call(id flat.NodeId, node flat.Node
931981 t.clear_resolved_call(id)
932982}
933983
984+fn (mut t Transformer) specialized_generic_call_param_type_texts(decl GenericFnDecl, args []string) []string {
985+ params := t.generic_fn_param_names(decl.node, decl.module)
986+ mut result := []string{}
987+ for i in 0 .. decl.node.children_count {
988+ child := t.a.child_node(&decl.node, i)
989+ if child.kind != .param {
990+ continue
991+ }
992+ result << t.specialized_call_target_type_text(decl, child.typ, args, params)
993+ }
994+ return result
995+}
996+
997+fn (mut t Transformer) specialized_call_target_type_text(decl GenericFnDecl, typ string, args []string, params []string) string {
998+ substituted := substitute_generic_type_text_with_params(typ, args, params)
999+ return t.qualify_specialized_signature_type_text(substituted, decl)
1000+}
1001+
1002+fn (mut t Transformer) retype_generic_call_literal_arg(arg_id flat.NodeId, param_type string) flat.NodeId {
1003+ if int(arg_id) < 0 || param_type.len == 0 {
1004+ return arg_id
1005+ }
1006+ node := t.a.nodes[int(arg_id)]
1007+ if node.kind == .array_literal
1008+ && t.generic_call_array_literal_can_retype_inline(node, param_type) {
1009+ return t.transform_expr_for_type(arg_id, param_type)
1010+ }
1011+ if node.kind == .ident && node.value.contains('arr_lit') && node.typ.starts_with('[]')
1012+ && param_type.starts_with('[]') && node.typ != param_type {
1013+ if t.retype_lowered_array_literal_temp(node.value, param_type) {
1014+ t.a.nodes[int(arg_id)].typ = param_type
1015+ t.set_var_type(node.value, param_type)
1016+ }
1017+ }
1018+ return arg_id
1019+}
1020+
1021+fn (t &Transformer) generic_call_array_literal_can_retype_inline(node flat.Node, param_type string) bool {
1022+ if !param_type.starts_with('[]') {
1023+ return false
1024+ }
1025+ for i in 0 .. node.children_count {
1026+ child := t.a.child_node(&node, i)
1027+ if child.kind !in [.int_literal, .float_literal, .bool_literal, .char_literal,
1028+ .string_literal, .cast_expr, .as_expr] {
1029+ return false
1030+ }
1031+ }
1032+ return true
1033+}
1034+
1035+fn (mut t Transformer) retype_lowered_array_literal_temp(name string, array_type string) bool {
1036+ if !array_type.starts_with('[]') {
1037+ return false
1038+ }
1039+ elem_type := array_type[2..]
1040+ mut changed := false
1041+ for i in 0 .. t.a.nodes.len {
1042+ node := t.a.nodes[i]
1043+ if node.kind == .decl_assign && node.children_count >= 2 {
1044+ lhs_id := t.a.child(&node, 0)
1045+ lhs := t.a.nodes[int(lhs_id)]
1046+ if lhs.kind == .ident && lhs.value == name {
1047+ rhs_id := t.a.child(&node, 1)
1048+ t.a.nodes[i].typ = array_type
1049+ t.a.nodes[int(lhs_id)].typ = array_type
1050+ t.set_var_type(name, array_type)
1051+ t.retype_array_new_call(rhs_id, elem_type, array_type)
1052+ changed = true
1053+ }
1054+ }
1055+ if node.kind == .call && t.call_is_array_push_to_name(node, name) {
1056+ value_name := t.array_push_value_name(node) or { continue }
1057+ if t.retype_decl_assign_name(value_name, elem_type) {
1058+ changed = true
1059+ }
1060+ }
1061+ }
1062+ return changed
1063+}
1064+
1065+fn (mut t Transformer) retype_array_new_call(call_id flat.NodeId, elem_type string, array_type string) {
1066+ if int(call_id) < 0 || int(call_id) >= t.a.nodes.len {
1067+ return
1068+ }
1069+ call := t.a.nodes[int(call_id)]
1070+ if call.kind != .call || call.children_count < 2 {
1071+ return
1072+ }
1073+ callee := t.a.child_node(&call, 0)
1074+ if callee.kind != .ident || callee.value != 'array_new' {
1075+ return
1076+ }
1077+ sizeof_id := t.a.child(&call, 1)
1078+ if int(sizeof_id) >= 0 && int(sizeof_id) < t.a.nodes.len {
1079+ if t.a.nodes[int(sizeof_id)].kind == .sizeof_expr {
1080+ t.a.nodes[int(sizeof_id)].value = elem_type
1081+ }
1082+ }
1083+ t.a.nodes[int(call_id)].typ = array_type
1084+}
1085+
1086+fn (t &Transformer) call_is_array_push_to_name(node flat.Node, name string) bool {
1087+ if node.children_count < 3 {
1088+ return false
1089+ }
1090+ callee := t.a.child_node(&node, 0)
1091+ if callee.kind != .ident || callee.value != 'array_push' {
1092+ return false
1093+ }
1094+ arg := t.a.child_node(&node, 1)
1095+ if arg.kind != .prefix || arg.children_count == 0 {
1096+ return false
1097+ }
1098+ base := t.a.child_node(arg, 0)
1099+ return base.kind == .ident && base.value == name
1100+}
1101+
1102+fn (t &Transformer) array_push_value_name(node flat.Node) ?string {
1103+ if node.children_count < 3 {
1104+ return none
1105+ }
1106+ arg := t.a.child_node(&node, 2)
1107+ if arg.kind != .prefix || arg.children_count == 0 {
1108+ return none
1109+ }
1110+ base := t.a.child_node(arg, 0)
1111+ if base.kind != .ident || base.value.len == 0 {
1112+ return none
1113+ }
1114+ return base.value
1115+}
1116+
1117+fn (mut t Transformer) retype_decl_assign_name(name string, typ string) bool {
1118+ mut changed := false
1119+ for i in 0 .. t.a.nodes.len {
1120+ node := t.a.nodes[i]
1121+ if node.kind != .decl_assign || node.children_count < 1 {
1122+ continue
1123+ }
1124+ lhs_id := t.a.child(&node, 0)
1125+ lhs := t.a.nodes[int(lhs_id)]
1126+ if lhs.kind != .ident || lhs.value != name {
1127+ continue
1128+ }
1129+ t.a.nodes[i].typ = typ
1130+ t.a.nodes[int(lhs_id)].typ = typ
1131+ t.set_var_type(name, typ)
1132+ changed = true
1133+ }
1134+ return changed
1135+}
1136+
9341137// call_is_selector_form reports whether a call node embeds its receiver inside the
9351138// callee (`recv.method(args)`) rather than passing it as an explicit child
9361139// (`Type.method(recv, args)`, the ident-lowered form).
@@ -954,7 +1157,7 @@ fn (mut t Transformer) rewrite_generic_method_call(id flat.NodeId, node flat.Nod
9541157 // specialized function explicitly, passing the receiver as the first argument
9551158 // (exactly like a plain generic call). Pure struct-generic methods keep the
9561159 // selector callee, which cgen resolves via the already-monomorphized receiver.
957- if decl.node.generic_params.len > 0 {
1160+ if t.generic_decl_has_method_level_params(decl) {
9581161 t.rewrite_method_level_generic_call(id, node, decl, args)
9591162 return
9601163 }
@@ -1087,8 +1290,15 @@ fn (mut t Transformer) generic_call_specialization(id flat.NodeId, node flat.Nod
10871290 if t.should_skip_generic_call_specialization(decl_key) {
10881291 return none
10891292 }
1090- if args := t.explicit_generic_call_args(node, module_name) {
1091- if args.len > 0 && !t.generic_args_have_placeholders(args) {
1293+ if t.call_has_source_generic_args(node) {
1294+ if args := t.explicit_generic_call_args(node, module_name) {
1295+ if args.len > 0 && !t.generic_args_have_placeholders(args) {
1296+ return decl_key, args
1297+ }
1298+ }
1299+ } else if args := t.explicit_generic_call_args(node, module_name) {
1300+ if args.len > 0 && !t.generic_args_have_placeholders(args)
1301+ && t.generic_args_contain_alias(args, module_name) {
10921302 return decl_key, args
10931303 }
10941304 }
@@ -1097,9 +1307,22 @@ fn (mut t Transformer) generic_call_specialization(id flat.NodeId, node flat.Nod
10971307 return decl_key, args
10981308 }
10991309 }
1310+ if args := t.explicit_generic_call_args(node, module_name) {
1311+ if args.len > 0 && !t.generic_args_have_placeholders(args) {
1312+ return decl_key, args
1313+ }
1314+ }
11001315 return none
11011316}
11021317
1318+fn (t &Transformer) call_has_source_generic_args(node flat.Node) bool {
1319+ if node.children_count == 0 {
1320+ return false
1321+ }
1322+ fn_node := t.a.child_node(&node, 0)
1323+ return fn_node.kind == .index && fn_node.children_count >= 2 && fn_node.value != 'range'
1324+}
1325+
11031326fn (t &Transformer) should_skip_generic_call_specialization(decl_key string) bool {
11041327 return decl_key in ['json.decode', 'json2.decode', 'x.json2.decode', 'json.encode',
11051328 'json2.encode', 'x.json2.encode', 'veb.run_at', 'veb.run_new']
@@ -1236,10 +1459,11 @@ fn (t &Transformer) generic_call_arg_count_matches_decl(node flat.Node, decl Gen
12361459 // (`Type.method(recv, args)`) carries the receiver as a real child, so
12371460 // children = [callee, recv, args...] and actual == children_count - 1.
12381461 is_receiver := decl.node.value.contains('.')
1462+ actual_args := t.generic_call_effective_arg_count(node)
12391463 actual := if is_receiver && t.call_is_selector_form(node) {
1240- int(node.children_count)
1464+ actual_args + 1
12411465 } else {
1242- int(node.children_count) - 1
1466+ actual_args
12431467 }
12441468 if is_variadic {
12451469 return actual >= param_count - 1
@@ -1247,6 +1471,31 @@ fn (t &Transformer) generic_call_arg_count_matches_decl(node flat.Node, decl Gen
12471471 return actual == param_count
12481472}
12491473
1474+fn (t &Transformer) generic_call_effective_arg_count(node flat.Node) int {
1475+ if node.children_count <= 1 {
1476+ return 0
1477+ }
1478+ mut count := 0
1479+ mut i := 1
1480+ for i < node.children_count {
1481+ arg := t.a.child_node(&node, i)
1482+ if arg.kind == .field_init {
1483+ count++
1484+ for i < node.children_count {
1485+ field := t.a.child_node(&node, i)
1486+ if field.kind != .field_init {
1487+ break
1488+ }
1489+ i++
1490+ }
1491+ continue
1492+ }
1493+ count++
1494+ i++
1495+ }
1496+ return count
1497+}
1498+
12501499fn (t &Transformer) local_concrete_fn_shadows_generic(name string, module_name string, decls map[string]GenericFnDecl) bool {
12511500 qname := transform_qualified_fn_name(module_name, name)
12521501 if name in decls || qname in decls {
@@ -1299,7 +1548,7 @@ fn (t &Transformer) explicit_generic_call_args(node flat.Node, module_name strin
12991548 if fn_node.kind != .index || fn_node.children_count < 2 || fn_node.value == 'range' {
13001549 return none
13011550 }
1302- type_arg := t.generic_call_type_arg_name(t.a.child(fn_node, 1))
1551+ type_arg := t.generic_call_type_args_name(fn_node)
13031552 if type_arg.len == 0 {
13041553 return none
13051554 }
@@ -1343,10 +1592,7 @@ fn (mut t Transformer) infer_generic_call_args(decl GenericFnDecl, _id flat.Node
13431592 param_idx++
13441593 continue
13451594 }
1346- mut arg_type := t.node_type(arg_id)
1347- if arg_type.len == 0 {
1348- arg_type = t.generic_arg_expr_type(arg_id)
1349- }
1595+ arg_type := t.generic_call_arg_type_for_inference(arg_id)
13501596 if arg_type.len > 0 {
13511597 infer_generic_type_args(child.typ, arg_type, mut inferred)
13521598 if is_recv_param {
@@ -1369,6 +1615,9 @@ fn (mut t Transformer) infer_generic_call_args(decl GenericFnDecl, _id flat.Node
13691615}
13701616
13711617fn (t &Transformer) generic_arg_for_decl_module(arg string, module_name string) string {
1618+ if t.generic_arg_is_alias_name(arg, module_name) {
1619+ return t.qualify_generic_arg_for_decl_module(arg, module_name)
1620+ }
13721621 normalized := t.normalize_type_in_module(arg, module_name)
13731622 qualified := t.qualify_generic_arg_for_decl_module(normalized, module_name)
13741623 if qualified != normalized {
@@ -1380,6 +1629,36 @@ fn (t &Transformer) generic_arg_for_decl_module(arg string, module_name string)
13801629 return normalized
13811630}
13821631
1632+fn (t &Transformer) generic_arg_is_alias_name(arg string, module_name string) bool {
1633+ clean := arg.trim_space()
1634+ if clean.len == 0 || isnil(t.tc) {
1635+ return false
1636+ }
1637+ if clean in t.tc.type_aliases {
1638+ return true
1639+ }
1640+ if module_name.len > 0 && module_name != 'main' && module_name != 'builtin' {
1641+ if '${module_name}.${clean}' in t.tc.type_aliases {
1642+ return true
1643+ }
1644+ }
1645+ for alias, _ in t.tc.type_aliases {
1646+ if alias.all_after_last('.') == clean {
1647+ return true
1648+ }
1649+ }
1650+ return false
1651+}
1652+
1653+fn (t &Transformer) generic_args_contain_alias(args []string, module_name string) bool {
1654+ for arg in args {
1655+ if t.generic_arg_is_alias_name(arg, module_name) {
1656+ return true
1657+ }
1658+ }
1659+ return false
1660+}
1661+
13831662fn (t &Transformer) qualify_generic_arg_for_decl_module(arg string, module_name string) string {
13841663 clean := arg.trim_space()
13851664 if clean.len == 0 {
@@ -1448,6 +1727,31 @@ fn (t &Transformer) generic_arg_expr_type(id flat.NodeId) string {
14481727 }
14491728 node := t.a.nodes[int(id)]
14501729 match node.kind {
1730+ .array_literal {
1731+ if node.children_count > 0 {
1732+ child_id := t.a.child(&node, 0)
1733+ mut elem_type := t.generic_arg_expr_type(child_id)
1734+ if elem_type.len == 0 {
1735+ elem_type = t.node_type(child_id)
1736+ }
1737+ if elem_type.len > 0 {
1738+ return '[]${elem_type}'
1739+ }
1740+ }
1741+ if node.typ.len > 0 {
1742+ return t.normalize_type_alias(node.typ)
1743+ }
1744+ }
1745+ .fn_literal, .lambda_expr {
1746+ if fn_type := t.fn_value_type_name(id) {
1747+ return fn_type
1748+ }
1749+ }
1750+ .cast_expr, .as_expr {
1751+ if node.value.len > 0 {
1752+ return node.value
1753+ }
1754+ }
14511755 .infix {
14521756 if node.children_count >= 2 {
14531757 left_type := t.node_type(t.a.child(&node, 0))
@@ -1488,7 +1792,31 @@ fn (t &Transformer) generic_arg_expr_type(id flat.NodeId) string {
14881792 else {}
14891793 }
14901794
1491- return ''
1795+ return t.node_type(id)
1796+}
1797+
1798+fn (t &Transformer) generic_call_arg_type_for_inference(id flat.NodeId) string {
1799+ if int(id) < 0 || int(id) >= t.a.nodes.len {
1800+ return ''
1801+ }
1802+ node := t.a.nodes[int(id)]
1803+ if node.kind in [.array_literal, .fn_literal, .lambda_expr] {
1804+ typ := t.generic_arg_expr_type(id)
1805+ if typ.len > 0 {
1806+ return typ
1807+ }
1808+ }
1809+ if node.typ.len > 0 {
1810+ typ := t.normalize_type_alias(node.typ)
1811+ if typ.len > 0 && !t.generic_arg_is_unresolved(typ) {
1812+ return typ
1813+ }
1814+ }
1815+ mut typ := t.node_type(id)
1816+ if typ.len == 0 {
1817+ typ = t.generic_arg_expr_type(id)
1818+ }
1819+ return typ
14921820}
14931821
14941822fn (t &Transformer) infer_generic_receiver_suffix_args(param_type string, arg_type string, mut inferred map[string]string) {
@@ -1591,6 +1919,9 @@ fn infer_generic_type_args(param_type string, arg_type string, mut inferred map[
15911919 if param.len == 0 || arg.len == 0 {
15921920 return
15931921 }
1922+ if arg == 'unknown' || arg == 'generic' {
1923+ return
1924+ }
15941925 if is_generic_fn_placeholder_name(param) {
15951926 if param !in inferred {
15961927 inferred[param] = arg
@@ -1624,6 +1955,10 @@ fn infer_generic_type_args(param_type string, arg_type string, mut inferred map[
16241955 infer_generic_type_args(param[1..], arg[1..], mut inferred)
16251956 return
16261957 }
1958+ if param.starts_with('fn') && arg.starts_with('fn') {
1959+ infer_generic_fn_type_args(param, arg, mut inferred)
1960+ return
1961+ }
16271962 if param.starts_with('map[') && arg.starts_with('map[') {
16281963 p_end := generic_matching_bracket(param, 3)
16291964 a_end := generic_matching_bracket(arg, 3)
@@ -1646,6 +1981,90 @@ fn infer_generic_type_args(param_type string, arg_type string, mut inferred map[
16461981 }
16471982}
16481983
1984+fn infer_generic_fn_type_args(param string, arg string, mut inferred map[string]string) {
1985+ p_params, p_ret := fn_type_text_parts(param) or { return }
1986+ a_params, a_ret := fn_type_text_parts(arg) or { return }
1987+ for i, p_param in p_params {
1988+ if i >= a_params.len {
1989+ break
1990+ }
1991+ infer_generic_type_args(generic_fn_type_param_payload(p_param),
1992+ generic_fn_type_param_payload(a_params[i]), mut inferred)
1993+ }
1994+ if p_ret.len > 0 && a_ret.len > 0 {
1995+ infer_generic_type_args(p_ret, a_ret, mut inferred)
1996+ }
1997+}
1998+
1999+fn fn_type_text_parts(typ string) ?([]string, string) {
2000+ clean := typ.trim_space()
2001+ open := clean.index_u8(`(`)
2002+ if open < 0 {
2003+ return none
2004+ }
2005+ mut depth := 1
2006+ mut close := open + 1
2007+ for close < clean.len {
2008+ if clean[close] == `(` {
2009+ depth++
2010+ } else if clean[close] == `)` {
2011+ depth--
2012+ if depth == 0 {
2013+ break
2014+ }
2015+ }
2016+ close++
2017+ }
2018+ if close >= clean.len {
2019+ return none
2020+ }
2021+ params_text := clean[open + 1..close]
2022+ params := split_fn_type_params(params_text)
2023+ ret := clean[close + 1..].trim_space()
2024+ return params, ret
2025+}
2026+
2027+fn split_fn_type_params(s string) []string {
2028+ mut parts := []string{}
2029+ mut depth := 0
2030+ mut start := 0
2031+ for i := 0; i < s.len; i++ {
2032+ c := s[i]
2033+ if c == `(` || c == `[` {
2034+ depth++
2035+ } else if c == `)` || c == `]` {
2036+ depth--
2037+ } else if c == `,` && depth == 0 {
2038+ part := s[start..i].trim_space()
2039+ if part.len > 0 {
2040+ parts << part
2041+ }
2042+ start = i + 1
2043+ }
2044+ }
2045+ tail := s[start..].trim_space()
2046+ if tail.len > 0 {
2047+ parts << tail
2048+ }
2049+ return parts
2050+}
2051+
2052+fn generic_fn_type_param_payload(param string) string {
2053+ mut text := param.trim_space()
2054+ if text.starts_with('mut ') {
2055+ text = text[4..].trim_space()
2056+ }
2057+ space := generic_top_level_space_index(text)
2058+ if space > 0 {
2059+ head := text[..space].trim_space()
2060+ tail := text[space + 1..].trim_space()
2061+ if generic_fn_type_param_head_is_name(head, tail) {
2062+ return tail
2063+ }
2064+ }
2065+ return text
2066+}
2067+
16492068fn (mut t Transformer) clone_generic_fn_node(node flat.Node, args []string) flat.NodeId {
16502069 return t.clone_generic_node_from(node, args, true)
16512070}
@@ -1674,6 +2093,8 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is
16742093 for i in 0 .. node.children_count {
16752094 children << t.clone_generic_node(t.a.child(&node, i), args)
16762095 }
2096+ cloned_typ := t.subst_type(node.typ, args)
2097+ t.retarget_cloned_new_map_call(node, mut children, cloned_typ)
16772098 start := t.a.children.len
16782099 for child in children {
16792100 t.a.children << child
@@ -1690,11 +2111,30 @@ fn (mut t Transformer) clone_generic_node_from(node flat.Node, args []string, is
16902111 pos: node.pos
16912112 children_start: start
16922113 children_count: flat.child_count(children.len)
1693- typ: t.subst_type(node.typ, args)
2114+ typ: cloned_typ
16942115 value: cloned_value
16952116 })
16962117}
16972118
2119+fn (mut t Transformer) retarget_cloned_new_map_call(node flat.Node, mut children []flat.NodeId, map_type string) {
2120+ if node.kind != .call || children.len < 7 || !map_type.starts_with('map[') {
2121+ return
2122+ }
2123+ callee := t.a.nodes[int(children[0])]
2124+ if callee.kind != .ident || callee.value != 'new_map' {
2125+ return
2126+ }
2127+ key_type, _ := t.map_type_parts(map_type)
2128+ if key_type.len == 0 {
2129+ return
2130+ }
2131+ hash_fn, eq_fn, clone_fn, free_fn := map_callback_names(key_type)
2132+ children[3] = t.make_ident(hash_fn)
2133+ children[4] = t.make_ident(eq_fn)
2134+ children[5] = t.make_ident(clone_fn)
2135+ children[6] = t.make_ident(free_fn)
2136+}
2137+
16982138fn (mut t Transformer) copy_cloned_resolution(src_id flat.NodeId, dst_id flat.NodeId) {
16992139 if isnil(t.tc) {
17002140 return
@@ -1704,7 +2144,8 @@ fn (mut t Transformer) copy_cloned_resolution(src_id flat.NodeId, dst_id flat.No
17042144 if src_idx < 0 || dst_idx < 0 {
17052145 return
17062146 }
1707- if src_idx < t.tc.resolved_call_set.len && t.tc.resolved_call_set[src_idx] {
2147+ if src_idx < t.tc.resolved_call_set.len && t.tc.resolved_call_set[src_idx]
2148+ && !t.resolved_call_is_generic_fn(t.tc.resolved_call_names[src_idx]) {
17082149 for t.tc.resolved_call_names.len <= dst_idx {
17092150 t.tc.resolved_call_names << ''
17102151 t.tc.resolved_call_set << false
@@ -1722,6 +2163,26 @@ fn (mut t Transformer) copy_cloned_resolution(src_id flat.NodeId, dst_id flat.No
17222163 }
17232164}
17242165
2166+fn (t &Transformer) resolved_call_is_generic_fn(name string) bool {
2167+ if name.len == 0 || isnil(t.tc) {
2168+ return false
2169+ }
2170+ if name in t.tc.fn_generic_params {
2171+ return true
2172+ }
2173+ base := generic_fn_decl_base_value(name)
2174+ if base in t.tc.fn_generic_params {
2175+ return true
2176+ }
2177+ if name.contains('.') {
2178+ short := name.all_after_last('.')
2179+ if short in t.tc.fn_generic_params {
2180+ return true
2181+ }
2182+ }
2183+ return false
2184+}
2185+
17252186fn substitute_generic_node_value(node flat.Node, args []string) string {
17262187 match node.kind {
17272188 .call, .array_init, .struct_init, .cast_expr, .as_expr, .sizeof_expr, .typeof_expr,
@@ -1862,6 +2323,31 @@ fn (t &Transformer) generic_decl_is_receiver_method(node flat.Node) bool {
18622323 return node.value.contains('.')
18632324}
18642325
2326+fn (mut t Transformer) generic_decl_has_method_level_params(decl GenericFnDecl) bool {
2327+ all_params := t.generic_fn_param_names(decl.node, decl.module)
2328+ if all_params.len == 0 {
2329+ return false
2330+ }
2331+ mut receiver_params := []string{}
2332+ if decl.node.value.contains('.') {
2333+ receiver := decl.node.value.all_before_last('.')
2334+ t.collect_generic_param_names_from_type(receiver, decl.module, mut receiver_params)
2335+ }
2336+ for i in 0 .. decl.node.children_count {
2337+ child := t.a.child_node(&decl.node, i)
2338+ if child.kind == .param {
2339+ t.collect_generic_param_names_from_type(child.typ, decl.module, mut receiver_params)
2340+ break
2341+ }
2342+ }
2343+ for param in all_params {
2344+ if param !in receiver_params {
2345+ return true
2346+ }
2347+ }
2348+ return false
2349+}
2350+
18652351fn (mut t Transformer) generic_fn_param_names(node flat.Node, module_name string) []string {
18662352 mut names := []string{}
18672353 for param in node.generic_params {
@@ -2087,6 +2573,13 @@ fn substitute_generic_type_text(typ string, args []string) string {
20872573 1..], args)
20882574 }
20892575 }
2576+ if clean.starts_with('(') && clean.ends_with(')') && clean.contains(',') {
2577+ mut parts := []string{}
2578+ for part in split_generic_args(clean[1..clean.len - 1]) {
2579+ parts << substitute_generic_type_text(part, args)
2580+ }
2581+ return '(' + parts.join(', ') + ')'
2582+ }
20902583 base, nested_args, ok := generic_app_parts(clean)
20912584 if ok {
20922585 mut resolved_args := []string{}
@@ -2151,6 +2644,13 @@ fn substitute_generic_type_text_with_params(typ string, args []string, params []
21512644 substitute_generic_type_text_with_params(clean[bracket_end + 1..], args, params)
21522645 }
21532646 }
2647+ if clean.starts_with('(') && clean.ends_with(')') && clean.contains(',') {
2648+ mut parts := []string{}
2649+ for part in split_generic_args(clean[1..clean.len - 1]) {
2650+ parts << substitute_generic_type_text_with_params(part, args, params)
2651+ }
2652+ return '(' + parts.join(', ') + ')'
2653+ }
21542654 if clean.starts_with('fn(') || clean.starts_with('fn (') {
21552655 // Substitute the generic params inside a function-type parameter so a specialized
21562656 // method body emits e.g. `fn (string) int`, not the placeholder `fn (T) int` (which
@@ -2294,12 +2794,55 @@ fn (t &Transformer) subst_node_value(node flat.Node, args []string) string {
22942794 .is_expr, .type_decl, .field_decl, .param {
22952795 return t.subst_type(node.value, args)
22962796 }
2797+ .comptime_if {
2798+ return t.subst_comptime_type_condition(node.value, args)
2799+ }
22972800 else {
22982801 return node.value
22992802 }
23002803 }
23012804}
23022805
2806+fn (t &Transformer) subst_comptime_type_condition(cond string, args []string) string {
2807+ clean := cond.trim_space()
2808+ if clean.starts_with('(') {
2809+ end := comptime_condition_matching_paren(clean, 0)
2810+ if end == clean.len - 1 {
2811+ inner := t.subst_comptime_type_condition(clean[1..clean.len - 1], args)
2812+ return '(${inner})'
2813+ }
2814+ }
2815+ or_idx := comptime_condition_top_level_index(clean, '||')
2816+ if or_idx >= 0 {
2817+ left := t.subst_comptime_type_condition(clean[..or_idx], args)
2818+ right := t.subst_comptime_type_condition(clean[or_idx + 2..], args)
2819+ return '${left} || ${right}'
2820+ }
2821+ and_idx := comptime_condition_top_level_index(clean, '&&')
2822+ if and_idx >= 0 {
2823+ left := t.subst_comptime_type_condition(clean[..and_idx], args)
2824+ right := t.subst_comptime_type_condition(clean[and_idx + 2..], args)
2825+ return '${left} && ${right}'
2826+ }
2827+ for op in [' !is ', ' is '] {
2828+ op_idx := comptime_condition_top_level_index(clean, op)
2829+ if op_idx >= 0 {
2830+ left := clean[..op_idx].trim_space()
2831+ right := clean[op_idx + op.len..].trim_space()
2832+ return '${t.subst_type(left, args)}${op}${t.subst_type(right, args)}'
2833+ }
2834+ }
2835+ if clean.starts_with('!') {
2836+ inner_raw := clean[1..].trim_space()
2837+ inner := t.subst_comptime_type_condition(inner_raw, args)
2838+ if inner_raw.starts_with('(') {
2839+ return '!(${inner})'
2840+ }
2841+ return '!${inner}'
2842+ }
2843+ return clean
2844+}
2845+
23032846// active_generic_param_index returns the position of a placeholder name within the
23042847// active generic params, or the positional fallback when none are active.
23052848fn (t &Transformer) active_generic_param_index(name string) int {
@@ -11,6 +11,13 @@ struct ArrayIndexInfo {
1111 value_type string
1212}
1313
14+struct StringSliceInfo {
15+ base_id flat.NodeId
16+ start_id flat.NodeId
17+ end_id flat.NodeId
18+ has_end bool
19+}
20+
1421// EnumFromStringInfo stores enum from string info metadata used by transform.
1522struct EnumFromStringInfo {
1623 enum_type string
@@ -65,6 +72,81 @@ fn (mut t Transformer) is_array_index_or_expr(node flat.Node) bool {
6572 return true
6673}
6774
75+fn (mut t Transformer) string_slice_info(index_id flat.NodeId) ?StringSliceInfo {
76+ if int(index_id) < 0 {
77+ return none
78+ }
79+ expr := t.a.nodes[int(index_id)]
80+ if expr.kind != .index || expr.children_count < 2 || expr.value != 'range' {
81+ return none
82+ }
83+ base_id := t.a.child(&expr, 0)
84+ base_type := t.resolve_expr_type(base_id)
85+ if t.normalize_type_alias(base_type) != 'string' {
86+ return none
87+ }
88+ return StringSliceInfo{
89+ base_id: base_id
90+ start_id: t.a.child(&expr, 1)
91+ end_id: if expr.children_count > 2 { t.a.child(&expr, 2) } else { flat.empty_node }
92+ has_end: expr.children_count > 2
93+ }
94+}
95+
96+fn (mut t Transformer) is_string_slice_or_expr(node flat.Node) bool {
97+ if node.kind != .or_expr || node.children_count < 2 {
98+ return false
99+ }
100+ _ := t.string_slice_info(t.a.child(&node, 0)) or { return false }
101+ return true
102+}
103+
104+fn (mut t Transformer) transform_string_slice_or_expr(id flat.NodeId, node flat.Node) flat.NodeId {
105+ if node.children_count < 2 {
106+ return id
107+ }
108+ expr_id := t.a.child(&node, 0)
109+ body_id := t.a.child(&node, 1)
110+ info := t.string_slice_info(expr_id) or { return id }
111+ base_expr := t.stable_expr_for_reuse(info.base_id)
112+ start_name := t.new_temp('str_start')
113+ end_name := t.new_temp('str_end')
114+ val_name := t.new_temp('str_slice')
115+ outer_pending := t.pending_stmts.clone()
116+ t.pending_stmts.clear()
117+ start_expr := if int(info.start_id) >= 0 && t.a.nodes[int(info.start_id)].kind != .empty {
118+ t.transform_expr(info.start_id)
119+ } else {
120+ t.make_int_literal(0)
121+ }
122+ end_expr := if info.has_end {
123+ t.transform_expr(info.end_id)
124+ } else {
125+ t.make_selector(base_expr, 'len', 'int')
126+ }
127+ mut prelude := []flat.NodeId{}
128+ t.drain_pending(mut prelude)
129+ prelude << t.make_decl_assign_typed(start_name, start_expr, 'int')
130+ prelude << t.make_decl_assign_typed(end_name, end_expr, 'int')
131+ prelude << t.make_decl_assign_typed(val_name, t.zero_value_for_type('string'), 'string')
132+ start_ident := t.make_ident(start_name)
133+ lower_ok := t.make_infix(.ge, start_ident, t.make_int_literal(0))
134+ ordered := t.make_infix(.le, t.make_ident(start_name), t.make_ident(end_name))
135+ upper_ok := t.make_infix(.le, t.make_ident(end_name), t.make_selector(base_expr, 'len', 'int'))
136+ bounds_ok := t.make_infix(.logical_and, t.make_infix(.logical_and, lower_ok, ordered), upper_ok)
137+ slice_call := t.make_call_typed('string__substr', arr3(base_expr, t.make_ident(start_name),
138+ t.make_ident(end_name)), 'string')
139+ then_block := t.make_block(arr1(t.make_assign(t.make_ident(val_name), slice_call)))
140+ else_block :=
141+ t.make_block(t.lower_or_body_to_stmts(body_id, val_name, 'string', node.value, ''))
142+ t.pending_stmts = outer_pending
143+ for stmt in prelude {
144+ t.pending_stmts << stmt
145+ }
146+ t.pending_stmts << t.make_if(bounds_ok, then_block, else_block)
147+ return t.make_ident(val_name)
148+}
149+
68150// transform_array_index_or_expr transforms transform array index or expr data for transform.
69151fn (mut t Transformer) transform_array_index_or_expr(id flat.NodeId, node flat.Node) flat.NodeId {
70152 if node.children_count < 2 {
@@ -229,8 +311,23 @@ fn (mut t Transformer) transform_enum_from_string_or_expr(id flat.NodeId, node f
229311}
230312
231313// or_expr_types supports or expr types handling for Transformer.
232-fn (t &Transformer) or_expr_types(expr_id flat.NodeId, fallback_type string) (string, string) {
314+fn (mut t Transformer) or_expr_types(expr_id flat.NodeId, fallback_type string) (string, string) {
315+ expr_node := if int(expr_id) >= 0 && int(expr_id) < t.a.nodes.len {
316+ t.a.nodes[int(expr_id)]
317+ } else {
318+ flat.Node{}
319+ }
233320 if !isnil(t.tc) {
321+ if expr_node.kind == .call {
322+ concrete_ret := t.concrete_generic_call_return_type(expr_id, expr_node)
323+ if t.is_optional_type_name(concrete_ret) {
324+ return concrete_ret, t.optional_base_type(concrete_ret)
325+ }
326+ call_ret := t.get_call_return_type(expr_id, expr_node)
327+ if t.is_optional_type_name(call_ret) {
328+ return call_ret, t.optional_base_type(call_ret)
329+ }
330+ }
234331 if typ := t.tc.expr_type(expr_id) {
235332 if typ is types.OptionType {
236333 base_name := t.value_type_name(typ.base_type)
@@ -343,6 +440,18 @@ fn (mut t Transformer) zero_value_for_type(typ string) flat.NodeId {
343440 if clean.starts_with('map[') {
344441 return t.make_map_init(clean)
345442 }
443+ if t.is_fixed_array_type(clean) {
444+ fixed_type := fixed_array_canonical_type(clean)
445+ len_text := fixed_array_len_text(fixed_type)
446+ if is_decimal_text(len_text) {
447+ elem_type := fixed_array_elem_type(fixed_type)
448+ mut values := []flat.NodeId{cap: len_text.int()}
449+ for _ in 0 .. len_text.int() {
450+ values << t.zero_value_for_type(elem_type)
451+ }
452+ return t.make_array_literal_typed(values, fixed_type)
453+ }
454+ }
346455 if clean == 'string' {
347456 return t.make_string_literal('')
348457 }
@@ -54,12 +54,18 @@ fn (t &Transformer) branch_tail_expr(block_id flat.NodeId) flat.NodeId {
5454
5555// make_return builds a `return <val>` statement node with the given type.
5656fn (mut t Transformer) make_return(val flat.NodeId, ret_typ string) flat.NodeId {
57+ return t.make_return_values(arr1(val), ret_typ)
58+}
59+
60+fn (mut t Transformer) make_return_values(vals []flat.NodeId, ret_typ string) flat.NodeId {
5761 start := t.a.children.len
58- t.a.children << val
62+ for val in vals {
63+ t.a.children << val
64+ }
5965 return t.a.add_node(flat.Node{
6066 kind: .return_stmt
6167 children_start: start
62- children_count: 1
68+ children_count: flat.child_count(vals.len)
6369 typ: ret_typ
6470 })
6571}
@@ -135,7 +141,8 @@ fn (mut t Transformer) try_expand_return_optional_expr(node flat.Node) ?[]flat.N
135141 ok_cond := t.make_selector(tmp_ident, 'ok', 'bool')
136142 value := t.make_selector(t.make_ident(tmp_name), 'value', t.optional_base_type(expr_type))
137143 then_block := t.make_block(arr1(t.make_return(value, ret_type)))
138- else_block := t.make_block(arr1(t.make_none_return_stmt()))
144+ err_expr := t.make_selector(t.make_ident(tmp_name), 'err', 'IError')
145+ else_block := t.make_block(arr1(t.make_none_return_stmt_with_err_expr(err_expr)))
139146 result << t.make_if(ok_cond, then_block, else_block)
140147 return result
141148}
@@ -166,7 +173,7 @@ fn (t &Transformer) return_expr_is_optional_result(id flat.NodeId) bool {
166173
167174// return_block_from_branch builds a block that keeps leading statements
168175// (transformed) and turns the tail expression of the branch into a `return`.
169-fn (mut t Transformer) return_block_from_branch(branch_id flat.NodeId, ret_typ string) flat.NodeId {
176+fn (mut t Transformer) return_block_from_branch(branch_id flat.NodeId, ret_typ string, extra_return_vals []flat.NodeId) flat.NodeId {
170177 branch := t.a.nodes[int(branch_id)]
171178 if branch.kind == .return_stmt {
172179 mut all := []flat.NodeId{}
@@ -180,16 +187,9 @@ fn (mut t Transformer) return_block_from_branch(branch_id flat.NodeId, ret_typ s
180187 if branch.kind != .block {
181188 // single expression branch: just `return <expr>`
182189 mut all := []flat.NodeId{}
183- // Convert a fixed-array branch value (e.g. a fixed-array const) to a dynamic
184- // array when the function returns `[]T`, matching a plain `return <expr>` and
185- // the `return match {...}` path (match_branch_return_block).
186- ret_val := if converted := t.fixed_array_return_value(branch_id) {
187- converted
188- } else {
189- t.wrap_sum_return_expr(branch_id)
190- }
190+ ret_vals := t.return_values_with_extra(branch_id, extra_return_vals)
191191 t.drain_pending(mut all)
192- all << t.make_return(ret_val, ret_typ)
192+ all << t.make_return_values(ret_vals, ret_typ)
193193 return t.make_block(all)
194194 }
195195 mut stmt_ids := []flat.NodeId{}
@@ -217,20 +217,16 @@ fn (mut t Transformer) return_block_from_branch(branch_id flat.NodeId, ret_typ s
217217 }
218218 return t.make_block(all)
219219 }
220- ret_val := if converted := t.fixed_array_return_value(tail_expr) {
221- converted
222- } else {
223- t.wrap_sum_return_expr(tail_expr)
224- }
220+ ret_vals := t.return_values_with_extra(tail_expr, extra_return_vals)
225221 t.drain_pending(mut all)
226- ret := t.make_return(ret_val, ret_typ)
222+ ret := t.make_return_values(ret_vals, ret_typ)
227223 all << ret
228224 return t.make_block(all)
229225}
230226
231227// build_return_if_chain recursively converts an if_expr (possibly an else-if
232228// chain) into an if-statement whose branch tails are `return` statements.
233-fn (mut t Transformer) build_return_if_chain(if_id flat.NodeId, ret_typ string) flat.NodeId {
229+fn (mut t Transformer) build_return_if_chain(if_id flat.NodeId, ret_typ string, extra_return_vals []flat.NodeId) flat.NodeId {
234230 if_node := t.a.nodes[int(if_id)]
235231 cond_id := t.a.child(&if_node, 0)
236232 cond_smartcasts := t.extract_all_is_exprs(cond_id)
@@ -239,7 +235,7 @@ fn (mut t Transformer) build_return_if_chain(if_id flat.NodeId, ret_typ string)
239235 for info in cond_smartcasts {
240236 t.push_smartcast(info.expr_name, info.variant_name, info.sum_type_name)
241237 }
242- then_block := t.return_block_from_branch(then_id, ret_typ)
238+ then_block := t.return_block_from_branch(then_id, ret_typ, extra_return_vals)
243239 for _ in cond_smartcasts {
244240 t.pop_smartcast()
245241 }
@@ -249,10 +245,10 @@ fn (mut t Transformer) build_return_if_chain(if_id flat.NodeId, ret_typ string)
249245 else_node := t.a.nodes[int(else_id)]
250246 if else_node.kind == .if_expr {
251247 // else-if chain: recurse, wrap resulting if-stmt in a block
252- inner := t.build_return_if_chain(else_id, ret_typ)
248+ inner := t.build_return_if_chain(else_id, ret_typ, extra_return_vals)
253249 else_block = t.make_block(arr1(inner))
254250 } else {
255- else_block = t.return_block_from_branch(else_id, ret_typ)
251+ else_block = t.return_block_from_branch(else_id, ret_typ, extra_return_vals)
256252 }
257253 }
258254 return t.make_if(new_cond, then_block, else_block)
@@ -281,7 +277,11 @@ fn (mut t Transformer) try_expand_return_if(_id flat.NodeId, node flat.Node) ?[]
281277 if val_node.kind != .if_expr || val_node.children_count < 3 {
282278 return none
283279 }
284- return arr1(t.build_return_if_chain(val_id, node.typ))
280+ mut extra_return_vals := []flat.NodeId{}
281+ for i in 1 .. node.children_count {
282+ extra_return_vals << t.a.child(&node, i)
283+ }
284+ return arr1(t.build_return_if_chain(val_id, node.typ, extra_return_vals))
285285}
286286
287287// match_branch_return_block supports match branch return block handling for Transformer.
@@ -552,6 +552,9 @@ fn (mut t Transformer) fixed_array_value_to_array(value_id flat.NodeId, fixed_ty
552552// transform_array_init_expr transforms .array_init nodes (e.g. `[]int{len: n}`).
553553// Recursively transforms any child expressions (len, cap, init values).
554554fn (mut t Transformer) transform_array_init_expr(id flat.NodeId, node flat.Node) flat.NodeId {
555+ if fixed := t.transform_fixed_array_init_expr(node) {
556+ return fixed
557+ }
555558 lowered := t.lower_array_init_to_runtime(id, node)
556559 if lowered != id {
557560 return lowered
@@ -27,6 +27,16 @@ fn arr3(a flat.NodeId, b flat.NodeId, c flat.NodeId) []flat.NodeId {
2727 return r
2828}
2929
30+// arr4 supports arr4 handling for transform.
31+fn arr4(a flat.NodeId, b flat.NodeId, c flat.NodeId, d flat.NodeId) []flat.NodeId {
32+ mut r := []flat.NodeId{}
33+ r << a
34+ r << b
35+ r << c
36+ r << d
37+ return r
38+}
39+
3040// node_kind_id supports node kind id handling for transform.
3141fn node_kind_id(node flat.Node) int {
3242 mut kind_id := node.kind_id
@@ -64,6 +74,7 @@ mut:
6474 cur_module string
6575 cur_fn_name string
6676 cur_fn_ret_type string
77+ cur_fn_is_generic bool
6778 var_types []VarTypeBinding
6879 pointer_value_lvalues map[string]bool
6980 temp_counter int
@@ -217,7 +228,7 @@ pub fn monomorphize_with_used(mut a flat.FlatAst, tc &types.TypeChecker, used_fn
217228 t.used_fns[c_name(name)] = true
218229 }
219230 t.materialize_generic_structs()
220- return augmented_used_fns
231+ return t.used_fns
221232}
222233
223234fn new_transformer(mut a flat.FlatAst, tc &types.TypeChecker, used_fns map[string]bool) Transformer {
@@ -232,6 +243,34 @@ fn new_transformer(mut a flat.FlatAst, tc &types.TypeChecker, used_fns map[strin
232243 }
233244}
234245
246+fn (mut t Transformer) mark_fn_used_name(name string) {
247+ if name.len == 0 {
248+ return
249+ }
250+ t.used_fns[name] = true
251+ t.used_fns[c_name(name)] = true
252+ if t.cur_module.len > 0 && t.cur_module != 'main' && t.cur_module != 'builtin' {
253+ needs_module_prefix := !name.contains('.') || local_method_fn_name_needs_module_prefix(name)
254+ if needs_module_prefix && !name.starts_with('${t.cur_module}.') {
255+ qname := '${t.cur_module}.${name}'
256+ t.used_fns[qname] = true
257+ t.used_fns[c_name(qname)] = true
258+ }
259+ }
260+}
261+
262+fn local_method_fn_name_needs_module_prefix(name string) bool {
263+ if !name.contains('.') {
264+ return false
265+ }
266+ receiver_name := name.all_before('.')
267+ if receiver_name.len == 0 {
268+ return false
269+ }
270+ first := receiver_name[0]
271+ return first >= `A` && first <= `Z`
272+}
273+
235274fn (mut t Transformer) prepare() {
236275 t.collect_types()
237276 t.collect_const_suffixes()
@@ -860,8 +899,8 @@ fn (t &Transformer) clone_ast_base(base_nodes int, base_children int) &flat.Flat
860899// fork_worker builds a worker Transformer that shares this transformer's
861900// read-only collected maps (structs, sum types, fn return types, …) and
862901// operates on its own cloned AST `ast` and forked TypeChecker `wtc`. All
863-// per-function mutable state, helper-root tracking, and memoization caches are
864-// reset/private so the worker can run on its own thread.
902+// per-function mutable state, helper-root tracking, used-fn additions, and
903+// memoization caches are reset/private so the worker can run on its own thread.
865904fn (t &Transformer) fork_worker(ast &flat.FlatAst, wtc &types.TypeChecker) &Transformer {
866905 mut w := *t
867906 w.a = ast
@@ -880,6 +919,7 @@ fn (t &Transformer) fork_worker(ast &flat.FlatAst, wtc &types.TypeChecker) &Tran
880919 w.escaping_amp_ptrs = map[string]bool{}
881920 w.escaping_amp_sources = map[string]bool{}
882921 w.heaped_amp_locals = map[string]bool{}
922+ w.used_fns = t.used_fns.clone()
883923 w.temp_counter = 0
884924 w.cur_file = ''
885925 w.cur_module = ''
@@ -891,6 +931,14 @@ fn (t &Transformer) fork_worker(ast &flat.FlatAst, wtc &types.TypeChecker) &Tran
891931 return &w
892932}
893933
934+fn (mut t Transformer) merge_worker_used_fns(w &Transformer) {
935+ for name, used in w.used_fns {
936+ if used {
937+ t.used_fns[name] = true
938+ }
939+ }
940+}
941+
894942// merge_worker folds a finished worker's transformed output back into the master
895943// AST. The worker created its new nodes/children at indices base_nodes/base_children
896944// (matching the master at fork time); here they are appended to the master and every
@@ -1169,15 +1217,7 @@ fn (mut t Transformer) transform_const_string_interp(_id flat.NodeId, node flat.
11691217 mut parts := []flat.NodeId{cap: int(node.children_count)}
11701218 for i in 0 .. node.children_count {
11711219 child_id := t.a.child(&node, i)
1172- transformed := t.transform_expr(child_id)
1173- mut typ := t.reliable_stringify_type(child_id)
1174- if typ.len == 0 {
1175- typ = t.node_type(transformed)
1176- }
1177- if typ.len == 0 {
1178- typ = 'string'
1179- }
1180- parts << t.wrap_string_conversion(transformed, typ)
1220+ parts << t.transform_string_interp_part(child_id)
11811221 }
11821222 mut expr := parts[0]
11831223 for i in 1 .. parts.len {
@@ -1193,6 +1233,31 @@ fn (mut t Transformer) transform_const_string_interp(_id flat.NodeId, node flat.
11931233 return t.make_block(stmts)
11941234}
11951235
1236+fn (mut t Transformer) transform_string_interp_part(child_id flat.NodeId) flat.NodeId {
1237+ mut expr_id := child_id
1238+ mut format := ''
1239+ child := t.a.nodes[int(child_id)]
1240+ if child.kind == .directive && child.value == 'string_interp_format' && child.children_count > 0 {
1241+ expr_id = t.a.child(&child, 0)
1242+ format = child.typ
1243+ }
1244+ transformed := t.transform_expr(expr_id)
1245+ mut typ := t.node_type(transformed)
1246+ if typ.len == 0 {
1247+ typ = t.reliable_stringify_type(transformed)
1248+ }
1249+ if typ.len == 0 {
1250+ typ = t.reliable_stringify_type(expr_id)
1251+ }
1252+ if typ.len == 0 {
1253+ typ = t.node_type(expr_id)
1254+ }
1255+ if typ.len == 0 {
1256+ typ = 'string'
1257+ }
1258+ return t.wrap_formatted_string_conversion(transformed, typ, format)
1259+}
1260+
11961261// transform_fn_body transforms transform fn body data for transform.
11971262// try_heap_escaping_amp reports whether a `p := &v` decl is an escaping address of
11981263// a value local that must be heap-copied: `p` is in escaping_amp_ptrs (returned)
@@ -1407,6 +1472,8 @@ fn (mut t Transformer) collect_return_escape_idents(id flat.NodeId, mut names ma
14071472fn (mut t Transformer) transform_fn_body(fn_idx int) {
14081473 fn_node := t.a.nodes[fn_idx]
14091474 t.cur_fn_name = fn_node.value
1475+ old_is_generic := t.cur_fn_is_generic
1476+ t.cur_fn_is_generic = t.fn_decl_has_unresolved_generics(fn_node, t.cur_module)
14101477 param_count := t.fn_body_param_count(fn_node)
14111478 param_types := t.fn_body_param_types(fn_node, param_count)
14121479 t.cur_fn_ret_type = t.fn_body_return_type(fn_node)
@@ -1482,6 +1549,7 @@ fn (mut t Transformer) transform_fn_body(fn_idx int) {
14821549 generic_params: fn_node.generic_params
14831550 }
14841551 t.smartcast_stack.clear()
1552+ t.cur_fn_is_generic = old_is_generic
14851553}
14861554
14871555// fn_body_param_types supports fn body param types handling for Transformer.
@@ -1723,6 +1791,9 @@ pub fn (mut t Transformer) transform_stmt(id flat.NodeId) []flat.NodeId {
17231791 .block {
17241792 return t.transform_block_stmt(id, node)
17251793 }
1794+ .comptime_if {
1795+ return t.transform_comptime_if_stmt(id, node)
1796+ }
17261797 .if_expr {
17271798 return t.transform_if_stmt(id, node)
17281799 }
@@ -2074,16 +2145,7 @@ fn (mut t Transformer) transform_return_stmt(id flat.NodeId, node flat.Node) []f
20742145 mut new_children := []flat.NodeId{cap: int(node.children_count)}
20752146 for i in 0 .. node.children_count {
20762147 child_id := t.a.child(&node, i)
2077- if converted := t.fixed_array_return_value(child_id) {
2078- new_children << converted
2079- } else if copied := t.heap_copy_local_address_return(child_id) {
2080- new_children << copied
2081- } else if t.cur_fn_ret_type.len > 0 && t.cur_fn_ret_type !in t.sum_types
2082- && !t.is_optional_type_name(t.cur_fn_ret_type) {
2083- new_children << t.transform_expr_for_type(child_id, t.cur_fn_ret_type)
2084- } else {
2085- new_children << t.wrap_sum_return_expr(child_id)
2086- }
2148+ new_children << t.transform_return_child(child_id, i, int(node.children_count))
20872149 }
20882150 start := t.a.children.len
20892151 for nc in new_children {
@@ -2101,6 +2163,41 @@ fn (mut t Transformer) transform_return_stmt(id flat.NodeId, node flat.Node) []f
21012163 return t.with_pending_before(new_id)
21022164}
21032165
2166+fn (mut t Transformer) return_values_with_extra(first_id flat.NodeId, extra_ids []flat.NodeId) []flat.NodeId {
2167+ total := extra_ids.len + 1
2168+ mut vals := []flat.NodeId{cap: total}
2169+ vals << t.transform_return_child(first_id, 0, total)
2170+ for i, extra_id in extra_ids {
2171+ vals << t.transform_return_child(extra_id, i + 1, total)
2172+ }
2173+ return vals
2174+}
2175+
2176+fn (mut t Transformer) transform_return_child(child_id flat.NodeId, child_index int, total_children int) flat.NodeId {
2177+ if converted := t.fixed_array_return_value(child_id) {
2178+ return converted
2179+ }
2180+ if copied := t.heap_copy_local_address_return(child_id) {
2181+ return copied
2182+ }
2183+ target_type := t.return_child_target_type(child_index, total_children)
2184+ if target_type.len > 0 && target_type !in t.sum_types && !t.is_optional_type_name(target_type) {
2185+ return t.transform_expr_for_type(child_id, target_type)
2186+ }
2187+ return t.wrap_sum_return_expr(child_id)
2188+}
2189+
2190+fn (t &Transformer) return_child_target_type(child_index int, total_children int) string {
2191+ if total_children > 1 && !isnil(t.tc) && t.cur_fn_ret_type.len > 0 {
2192+ if items := multi_return_types_from_type(t.tc.parse_type(t.cur_fn_ret_type), total_children) {
2193+ if child_index >= 0 && child_index < items.len {
2194+ return items[child_index].name()
2195+ }
2196+ }
2197+ }
2198+ return t.cur_fn_ret_type
2199+}