@@ -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+