v / vlib / v2 / gen / cleanc / array.v
1507 lines · 1447 sloc · 43.31 KB · 7d6fd95995c08b27f8ddf5d469c9cdd889d10154
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4
5module cleanc
6
7import strings
8import v2.ast
9import v2.types
10
11fn array_alias_elem_type(arr_type string) string {
12 base := arr_type.trim_right('*')
13 if base.starts_with('Array_fixed_') {
14 without_prefix := base['Array_fixed_'.len..]
15 last_underscore := without_prefix.last_index('_') or { return '' }
16 return unmangle_c_ptr_type(without_prefix[..last_underscore])
17 }
18 if base.starts_with('Array_') {
19 return unmangle_c_ptr_type(base['Array_'.len..])
20 }
21 if base in ['strings__Builder', 'Builder'] {
22 return 'u8'
23 }
24 return ''
25}
26
27fn (mut g Gen) array_alias_base_type(arr_type string) string {
28 base := arr_type.trim_space().trim_right('*')
29 if base == '' {
30 return ''
31 }
32 if base.starts_with('Array_') || base.starts_with('Array_fixed_') {
33 return base
34 }
35 if alias_base := g.alias_base_types[base] {
36 return alias_base.trim_space().trim_right('*')
37 }
38 if raw := g.lookup_type_by_c_name(base) {
39 if raw is types.Alias {
40 return g.types_type_to_c(raw.base_type).trim_space().trim_right('*')
41 }
42 }
43 return ''
44}
45
46fn (mut g Gen) array_alias_elem_type_from_c_type(arr_type string) string {
47 elem := array_alias_elem_type(arr_type)
48 if elem != '' {
49 return elem
50 }
51 base := g.array_alias_base_type(arr_type)
52 if base != '' {
53 return array_alias_elem_type(base)
54 }
55 return ''
56}
57
58fn c_type_is_array_value(typ string) bool {
59 base := typ.trim_space().trim_right('*')
60 return base == 'array' || base.starts_with('Array_')
61}
62
63fn specialized_generic_elem_type_from_value(declared_elem string, value_elem string) string {
64 declared := unmangle_c_ptr_type(declared_elem.trim_space())
65 inferred := unmangle_c_ptr_type(value_elem.trim_space())
66 if declared == '' || inferred == '' || !inferred.contains('_T_') {
67 return ''
68 }
69 base := inferred.all_before('_T_')
70 if declared == base || short_type_name(declared) == short_type_name(base) {
71 return inferred
72 }
73 return ''
74}
75
76fn (mut g Gen) selector_array_value_type(sel ast.SelectorExpr) string {
77 mut field_type := g.selector_declared_field_type(sel)
78 if !c_type_is_array_value(field_type) {
79 field_type = g.selector_generated_field_type(sel)
80 }
81 if !c_type_is_array_value(field_type) {
82 field_type = g.selector_field_type(sel)
83 }
84 if c_type_is_array_value(field_type) {
85 return field_type
86 }
87 return ''
88}
89
90fn is_builtin_array_file(path string) bool {
91 normalized := path.replace('\\', '/')
92 return normalized.ends_with('vlib/builtin/array.v')
93}
94
95fn should_keep_builtin_array_decl(decl ast.FnDecl) bool {
96 return decl.name in [
97 '__new_array',
98 '__new_array_with_default',
99 '__new_array_with_multi_default',
100 '__new_array_with_array_default',
101 '__new_array_with_map_default',
102 'new_array_from_c_array',
103 'new_array_from_c_array_no_alloc',
104 'new_array_from_array_and_c_array',
105 'ensure_cap',
106 'repeat',
107 'repeat_to_depth',
108 'insert',
109 'insert_many',
110 'prepend',
111 'prepend_many',
112 'delete',
113 'delete_many',
114 'clear',
115 'reset',
116 'trim',
117 'drop',
118 'get_unsafe',
119 'get',
120 'get_with_check',
121 'first',
122 'last',
123 'pop_left',
124 'pop',
125 'delete_last',
126 'slice',
127 'slice_ni',
128 'contains',
129 'clone_static_to_depth',
130 'clone',
131 'clone_to_depth',
132 'set_unsafe',
133 'set',
134 'push',
135 'push_many',
136 'reverse_in_place',
137 'reverse',
138 'free',
139 'sort',
140 'sort_with_compare',
141 'sorted_with_compare',
142 'copy',
143 'write_u8',
144 'write_rune',
145 'write_string',
146 'write_ptr',
147 'grow_cap',
148 'grow_len',
149 'pointers',
150 'panic_on_negative_len',
151 'panic_on_negative_cap',
152 ]
153}
154
155fn array_interface_repeat_fn_name(iface_name string) string {
156 return '__v_array_repeat_interface__' + mangle_alias_component(iface_name)
157}
158
159fn (g &Gen) array_clone_depth_for_c_type(type_name string) int {
160 elem_type := array_alias_elem_type(type_name)
161 if elem_type == '' {
162 return 0
163 }
164 if elem_type == 'string' || elem_type == 'builtin__string' || elem_type == 'map'
165 || elem_type.starts_with('Map_') {
166 return 1
167 }
168 if elem_type == 'array' || elem_type.starts_with('Array_') {
169 return 1 + g.array_clone_depth_for_c_type(elem_type)
170 }
171 return 0
172}
173
174fn (mut g Gen) emit_array_interface_repeat_decls() {
175 mut names := g.emitted_interface_bodies.keys()
176 names.sort()
177 for name in names {
178 g.sb.writeln('static inline array ${array_interface_repeat_fn_name(name)}(array a, int count);')
179 }
180 if names.len > 0 {
181 g.sb.writeln('')
182 }
183}
184
185fn (mut g Gen) emit_array_interface_repeat_body(iface_name string) {
186 repeat_fn := array_interface_repeat_fn_name(iface_name)
187 clone_fn := interface_clone_fn_name(iface_name)
188 g.sb.writeln('static inline array ${repeat_fn}(array a, int count) {')
189 g.sb.writeln('\tarray res = array__repeat_to_depth(a, count, 0);')
190 g.sb.writeln('\tif (a.len > 0) {')
191 g.sb.writeln('\t\tfor (int i = 0; i < res.len; ++i) {')
192 g.sb.writeln('\t\t\t((${iface_name}*)res.data)[i] = ${clone_fn}(((${iface_name}*)a.data)[i % a.len]);')
193 g.sb.writeln('\t\t}')
194 g.sb.writeln('\t}')
195 g.sb.writeln('\treturn res;')
196 g.sb.writeln('}')
197 g.sb.writeln('')
198}
199
200fn (mut g Gen) emit_array_interface_repeat_helpers() {
201 mut names := g.emitted_interface_bodies.keys()
202 names.sort()
203 for name in names {
204 g.emit_array_interface_repeat_body(name)
205 }
206}
207
208struct ArrayContainsFallbackSpec {
209 fn_name string
210 arr_type string
211 elem_type string
212}
213
214fn (g &Gen) missing_array_contains_fallback_specs() []ArrayContainsFallbackSpec {
215 mut fn_name_set := map[string]bool{}
216 for fn_name in g.fn_param_types.keys() {
217 fn_name_set[fn_name] = true
218 }
219 for fn_name in g.called_fn_names.keys() {
220 if fn_name.starts_with('Array_') && fn_name.ends_with('_contains') {
221 fn_name_set[fn_name] = true
222 }
223 }
224 mut fn_names := fn_name_set.keys()
225 fn_names.sort()
226 mut specs := []ArrayContainsFallbackSpec{}
227 for fn_name in fn_names {
228 if !fn_name.starts_with('Array_') || !fn_name.ends_with('_contains') {
229 continue
230 }
231 if fn_name !in g.called_fn_names {
232 continue
233 }
234 fn_key := 'fn_${fn_name}'
235 if fn_key in g.emitted_types {
236 continue
237 }
238 has_signature := fn_name in g.fn_param_types
239 if has_signature {
240 if g.fn_return_types[fn_name] or { '' } != 'bool' {
241 continue
242 }
243 }
244 param_types := g.fn_param_types[fn_name] or { []string{} }
245 mut arr_type := ''
246 mut elem_type := ''
247 if param_types.len == 2 {
248 arr_type = param_types[0]
249 elem_type = param_types[1]
250 } else if fn_name.starts_with('Array_') && fn_name.ends_with('_contains') {
251 elem_type = fn_name['Array_'.len..fn_name.len - '_contains'.len]
252 arr_type = 'Array_${elem_type}'
253 } else {
254 continue
255 }
256 if arr_type.len == 0 || elem_type.len == 0 {
257 continue
258 }
259 specs << ArrayContainsFallbackSpec{
260 fn_name: fn_name
261 arr_type: arr_type
262 elem_type: elem_type
263 }
264 }
265 return specs
266}
267
268fn array_contains_fallback_decls(specs []ArrayContainsFallbackSpec) string {
269 if specs.len == 0 {
270 return ''
271 }
272 mut sb := strings.new_builder(specs.len * 80)
273 for spec in specs {
274 sb.writeln('bool ${spec.fn_name}(${spec.arr_type} a, ${spec.elem_type} v);')
275 }
276 sb.writeln('')
277 return sb.str()
278}
279
280fn (mut g Gen) emit_array_contains_fallbacks(specs []ArrayContainsFallbackSpec) {
281 mut emitted_any := false
282 for spec in specs {
283 fn_key := 'fn_${spec.fn_name}'
284 if fn_key in g.emitted_types {
285 continue
286 }
287 g.emitted_types[fn_key] = true
288 g.sb.writeln('')
289 if g.is_freestanding_target() && g.pref != unsafe { nil } && g.pref.skip_builtin {
290 g.sb.writeln('_Static_assert(0, "${freestanding_missing_heap_runtime_message}: ${spec.fn_name}");')
291 emitted_any = true
292 continue
293 }
294 // Fixed arrays are C arrays (e.g., voidptr[20]), not structs — use direct indexing.
295 is_fixed := spec.arr_type.starts_with('Array_fixed_')
296 if is_fixed {
297 _, fixed_len := g.parse_fixed_array_type(spec.arr_type)
298 g.sb.writeln('__attribute__((weak)) bool ${spec.fn_name}(${spec.arr_type} a, ${spec.elem_type} v) {')
299 g.sb.writeln('\tfor (int i = 0; i < ${fixed_len}; i++) {')
300 if spec.elem_type == 'string' {
301 g.sb.writeln('\t\tif (string__eq(a[i], v)) {')
302 } else {
303 g.sb.writeln('\t\tif (memcmp(&a[i], &v, sizeof(${spec.elem_type})) == 0) {')
304 }
305 } else {
306 g.sb.writeln('__attribute__((weak)) bool ${spec.fn_name}(${spec.arr_type} a, ${spec.elem_type} v) {')
307 g.sb.writeln('\tfor (int i = 0; (i < a.len); i += 1) {')
308 if spec.elem_type == 'string' {
309 g.sb.writeln('\t\tif (string__eq(*((string*)(array__get(a, i))), v)) {')
310 } else {
311 left_cmp := '_cmp_l_${g.tmp_counter}'
312 right_cmp := '_cmp_r_${g.tmp_counter + 1}'
313 g.tmp_counter += 2
314 g.sb.writeln('\t\tif (({ ${spec.elem_type} ${left_cmp} = *((${spec.elem_type}*)(array__get(a, i))); ${spec.elem_type} ${right_cmp} = v; memcmp(&${left_cmp}, &${right_cmp}, sizeof(${spec.elem_type})) == 0; })) {')
315 }
316 }
317 g.sb.writeln('\t\t\treturn true;')
318 g.sb.writeln('\t\t}')
319 g.sb.writeln('\t}')
320 g.sb.writeln('\treturn false;')
321 g.sb.writeln('}')
322 emitted_any = true
323 }
324 if emitted_any {
325 g.sb.writeln('')
326 }
327}
328
329fn (mut g Gen) emit_deferred_fixed_array_aliases() {
330 mut names := g.array_aliases.keys()
331 // Sort by name length then alphabetically so leaf types (shorter names)
332 // are emitted before compound types that depend on them.
333 // E.g. Array_fixed_string_2 before Array_fixed_Array_fixed_string_2_2.
334 for i := 0; i < names.len; i++ {
335 for j := i + 1; j < names.len; j++ {
336 if names[i].len > names[j].len || (names[i].len == names[j].len && names[i] > names[j]) {
337 names[i], names[j] = names[j], names[i]
338 }
339 }
340 }
341 for name in names {
342 if !name.starts_with('Array_fixed_') {
343 continue
344 }
345 // Skip if already emitted (safe to call this function multiple times).
346 alias_key := 'alias_${name}'
347 if alias_key in g.emitted_types {
348 continue
349 }
350 if info := g.collected_fixed_array_types[name] {
351 if !g.fixed_array_elem_type_ready(info.elem_type) {
352 continue
353 }
354 g.sb.writeln('typedef ${info.elem_type} ${name} [${info.size}];')
355 body_key := 'body_${name}'
356 g.emitted_types[alias_key] = true
357 g.emitted_types[body_key] = true
358 // Emit fallback str macros for fixed array types (only if no real function was generated)
359 str_fn := '${name}_str'
360 if str_fn !in g.fn_return_types {
361 g.sb.writeln('#define ${name}_str(a) ((string){.str = "${name}", .len = ${name.len}, .is_lit = 1})')
362 g.sb.writeln('#define ${name}__str(a) ${name}_str(a)')
363 }
364 }
365 }
366}
367
368fn (g &Gen) fixed_array_elem_type_ready(elem_type string) bool {
369 if elem_type in primitive_types
370 || elem_type in ['char', 'voidptr', 'charptr', 'byteptr', 'void*', 'char*'] {
371 return true
372 }
373 if elem_type in g.primitive_type_aliases {
374 return true
375 }
376 if elem_type.starts_with('Array_fixed_') {
377 return 'body_${elem_type}' in g.emitted_types || 'alias_${elem_type}' in g.emitted_types
378 }
379 if elem_type.ends_with('*') || g.is_c_type_name(elem_type) {
380 return true
381 }
382 if base_type := g.alias_base_types[elem_type] {
383 if base_type != elem_type {
384 return g.fixed_array_elem_type_ready(base_type)
385 }
386 }
387 return 'body_${elem_type}' in g.emitted_types || 'enum_${elem_type}' in g.emitted_types
388}
389
390fn (mut g Gen) array_append_elem_type(lhs ast.Expr, rhs ast.Expr) (bool, string) {
391 mut lhs_type := g.get_expr_type(lhs).trim_space()
392 lhs_base_type := lhs_type.trim_right('*')
393 mut elem_type := ''
394 mut is_array_append := lhs_base_type == 'array' || lhs_base_type.starts_with('Array_')
395 if lhs_base_type.starts_with('Array_') {
396 elem_type = lhs_base_type['Array_'.len..]
397 }
398 if !is_array_append {
399 if alias_base := g.alias_base_c_type(lhs_base_type) {
400 alias_base_clean := alias_base.trim_right('*')
401 if alias_base_clean == 'array' || alias_base_clean.starts_with('Array_') {
402 is_array_append = true
403 if alias_base_clean.starts_with('Array_') {
404 elem_type = alias_base_clean['Array_'.len..]
405 }
406 }
407 }
408 }
409 if raw_type := g.get_raw_type(lhs) {
410 match raw_type {
411 types.Array {
412 is_array_append = true
413 if elem_type == '' {
414 elem_type = g.types_type_to_c(raw_type.elem_type)
415 }
416 }
417 types.Alias {
418 if raw_type.base_type is types.Array {
419 is_array_append = true
420 if elem_type == '' {
421 elem_type = g.types_type_to_c(raw_type.base_type.elem_type)
422 }
423 }
424 }
425 types.Pointer {
426 match raw_type.base_type {
427 types.Array {
428 is_array_append = true
429 if elem_type == '' {
430 elem_type = g.types_type_to_c(raw_type.base_type.elem_type)
431 }
432 }
433 types.Alias {
434 if raw_type.base_type.base_type is types.Array {
435 is_array_append = true
436 if elem_type == '' {
437 elem_type =
438 g.types_type_to_c(raw_type.base_type.base_type.elem_type)
439 }
440 }
441 }
442 else {}
443 }
444 }
445 else {}
446 }
447 }
448 if !is_array_append {
449 return false, ''
450 }
451 if elem_type == '' || elem_type == 'int' {
452 rhs_type := g.get_expr_type(rhs)
453 if rhs_type != '' && rhs_type !in ['int_literal', 'float_literal'] {
454 elem_type = rhs_type.trim_right('*')
455 }
456 if elem_type == '' || elem_type == 'int' {
457 rhs_c_type := g.expr_type_to_c(rhs)
458 if rhs_c_type != '' && rhs_c_type !in ['int', 'int_literal', 'float_literal'] {
459 elem_type = rhs_c_type.trim_right('*')
460 }
461 }
462 }
463 // When raw_type gives a module-qualified name (e.g. term__Coord) but the rhs
464 // is a local struct (e.g. Coord), prefer the rhs type to match the actual typedef.
465 if elem_type.contains('__') {
466 rhs_type := g.get_expr_type(rhs).trim_right('*')
467 if rhs_type != '' && !rhs_type.contains('__')
468 && rhs_type !in ['int', 'int_literal', 'float_literal', 'void', 'void*', 'voidptr']
469 && elem_type.ends_with('__${rhs_type}') {
470 elem_type = rhs_type
471 }
472 }
473 if elem_type == '' {
474 elem_type = 'int'
475 }
476 return true, unmangle_c_ptr_type(elem_type)
477}
478
479fn (mut g Gen) array_append_lhs_is_pointer(lhs ast.Expr) bool {
480 unwrapped := strip_expr_wrappers(lhs)
481 if unwrapped is ast.Ident {
482 if unwrapped.name in g.cur_fn_mut_params {
483 return true
484 }
485 if local_type := g.get_local_var_c_type(unwrapped.name) {
486 local_type_clean := local_type.trim_space()
487 return local_type_clean.ends_with('*')
488 || local_type_clean in ['voidptr', 'charptr', 'byteptr', 'chan']
489 }
490 }
491 return g.expr_is_pointer(lhs)
492}
493
494fn (mut g Gen) gen_array_append_target(lhs ast.Expr) {
495 unwrapped := strip_expr_wrappers(lhs)
496 if unwrapped is ast.PrefixExpr && unwrapped.op == .amp {
497 inner := strip_expr_wrappers(unwrapped.expr)
498 if g.array_append_lhs_is_pointer(inner) {
499 g.expr(inner)
500 return
501 }
502 g.expr(lhs)
503 return
504 }
505 if g.array_append_lhs_is_pointer(lhs) {
506 g.expr(lhs)
507 } else {
508 g.sb.write_string('&')
509 g.expr(lhs)
510 }
511}
512
513fn (mut g Gen) map_index_key_value_types(node ast.IndexExpr) (bool, string, string, bool) {
514 mut key_type := ''
515 mut value_type := ''
516 mut lhs_is_ptr := g.expr_is_pointer(node.lhs)
517 mut lhs_c_type := g.get_expr_type(node.lhs).trim_space()
518 if lhs_c_type.ends_with('*') {
519 lhs_is_ptr = true
520 }
521 if raw_type := g.get_raw_type(node.lhs) {
522 match raw_type {
523 types.Map {
524 key_type = g.types_type_to_c(raw_type.key_type)
525 value_type = g.types_type_to_c(raw_type.value_type)
526 }
527 types.Pointer {
528 lhs_is_ptr = true
529 match raw_type.base_type {
530 types.Map {
531 key_type = g.types_type_to_c(raw_type.base_type.key_type)
532 value_type = g.types_type_to_c(raw_type.base_type.value_type)
533 }
534 types.Alias {
535 if raw_type.base_type.base_type is types.Map {
536 key_type = g.types_type_to_c(raw_type.base_type.base_type.key_type)
537 value_type = g.types_type_to_c(raw_type.base_type.base_type.value_type)
538 }
539 }
540 else {}
541 }
542 }
543 types.Alias {
544 if raw_type.base_type is types.Map {
545 key_type = g.types_type_to_c(raw_type.base_type.key_type)
546 value_type = g.types_type_to_c(raw_type.base_type.value_type)
547 }
548 }
549 else {}
550 }
551 }
552 if key_type == '' || value_type == '' {
553 mut map_c_type := lhs_c_type
554 if node.lhs is ast.SelectorExpr {
555 mut selector_type := g.selector_storage_field_type(node.lhs).trim_space()
556 if selector_type == '' {
557 selector_type = g.selector_field_type(node.lhs).trim_space()
558 }
559 if selector_type != '' {
560 map_c_type = selector_type
561 }
562 }
563 if map_c_type.ends_with('*') {
564 map_c_type = map_c_type[..map_c_type.len - 1].trim_space()
565 }
566 if map_c_type.starts_with('Map_') {
567 key, value := g.parse_map_kv_types(map_c_type['Map_'.len..])
568 key_type = key
569 value_type = value
570 }
571 }
572 if key_type == '' || value_type == '' {
573 return false, '', '', false
574 }
575 return true, key_type, value_type, lhs_is_ptr
576}
577
578fn (mut g Gen) gen_map_index_array_append(lhs ast.IndexExpr, rhs ast.Expr, elem_type string) bool {
579 ok, key_type, value_type, lhs_is_ptr := g.map_index_key_value_types(lhs)
580 if !ok {
581 return false
582 }
583 if value_type.trim_space().ends_with('*') {
584 return false
585 }
586 value_base := value_type.trim_right('*')
587 mut is_array_value := c_type_is_array_value(value_base)
588 if !is_array_value {
589 if alias_base := g.alias_base_c_type(value_base) {
590 is_array_value = c_type_is_array_value(alias_base)
591 }
592 }
593 if !is_array_value {
594 return false
595 }
596 key_tmp := '_map_key${g.tmp_counter}'
597 g.tmp_counter++
598 zero_tmp := '_map_zero${g.tmp_counter}'
599 g.tmp_counter++
600 arr_tmp := '_map_arr${g.tmp_counter}'
601 g.tmp_counter++
602 g.sb.write_string('({ ${key_type} ${key_tmp} = ')
603 g.expr(lhs.expr)
604 g.sb.write_string('; ${value_type} ${zero_tmp} = __new_array_with_default_noscan(0, 0, sizeof(${elem_type}), NULL); ${value_type}* ${arr_tmp} = (${value_type}*)map__get_and_set(')
605 if lhs_is_ptr {
606 g.expr(lhs.lhs)
607 } else {
608 g.sb.write_string('&(')
609 g.expr(lhs.lhs)
610 g.sb.write_string(')')
611 }
612 g.sb.write_string(', (void*)&${key_tmp}, (void*)&${zero_tmp}); ')
613 if g.expr_is_array_value(rhs) {
614 rhs_tmp := '_arr_append_tmp_${g.tmp_counter}'
615 g.tmp_counter++
616 arr_rhs_type := g.expr_array_runtime_type(rhs)
617 g.sb.write_string('${arr_rhs_type} ${rhs_tmp} = ')
618 g.expr(rhs)
619 g.sb.write_string('; array__push_many((array*)${arr_tmp}, ${rhs_tmp}.data, ${rhs_tmp}.len); })')
620 return true
621 }
622 g.sb.write_string('array__push((array*)${arr_tmp}, ')
623 if elem_type == 'string' {
624 g.sb.write_string('&(string[1]){ string__clone(')
625 g.expr(rhs)
626 g.sb.write_string(') }')
627 } else {
628 g.gen_addr_of_expr(rhs, elem_type)
629 }
630 g.sb.write_string('); })')
631 return true
632}
633
634fn (mut g Gen) expr_is_array_value(expr ast.Expr) bool {
635 if expr is ast.ParenExpr {
636 return g.expr_is_array_value(expr.expr)
637 }
638 if expr is ast.PrefixExpr && expr.op == .mul {
639 // `*ptr_to_array` is an array value.
640 return g.expr_is_array_value(expr.expr)
641 }
642 if expr is ast.InfixExpr && expr.op in [.amp, .pipe, .xor, .left_shift, .right_shift] {
643 return false
644 }
645 if expr is ast.SelectorExpr && g.selector_array_value_type(expr) != '' {
646 return true
647 }
648 expr_type := g.get_expr_type(expr)
649 if c_type_is_array_value(expr_type) {
650 return true
651 }
652 if alias_base := g.alias_base_c_type(expr_type.trim_right('*')) {
653 if c_type_is_array_value(alias_base) {
654 return true
655 }
656 }
657 if expr is ast.Ident {
658 local_type := g.get_local_var_c_type(expr.name) or { '' }
659 if c_type_is_array_value(local_type) {
660 return true
661 }
662 if alias_base := g.alias_base_c_type(local_type.trim_right('*')) {
663 if c_type_is_array_value(alias_base) {
664 return true
665 }
666 }
667 }
668 // Or-data-access: _or_tN.data where _or_tN has type _result_Array_X or _option_Array_X
669 if expr is ast.SelectorExpr && expr.rhs.name == 'data' {
670 if expr.lhs is ast.Ident {
671 lhs_type := g.get_expr_type(expr.lhs)
672 if (lhs_type.starts_with('_result_Array_') || lhs_type.starts_with('_result_array'))
673 || (lhs_type.starts_with('_option_Array_') || lhs_type.starts_with('_option_array')) {
674 return true
675 }
676 }
677 }
678 if raw_type := g.get_raw_type(expr) {
679 match raw_type {
680 types.Array {
681 return true
682 }
683 types.Alias {
684 return raw_type.base_type is types.Array
685 }
686 types.Pointer {
687 match raw_type.base_type {
688 types.Array {
689 return true
690 }
691 types.Alias {
692 return raw_type.base_type.base_type is types.Array
693 }
694 else {
695 return false
696 }
697 }
698 }
699 else {
700 return false
701 }
702 }
703 }
704 return false
705}
706
707fn (mut g Gen) expr_array_runtime_type(expr ast.Expr) string {
708 if expr is ast.ParenExpr {
709 return g.expr_array_runtime_type(expr.expr)
710 }
711 if expr is ast.PrefixExpr && expr.op == .mul {
712 // Try direct dereferenced type first.
713 deref_type := g.get_expr_type(expr)
714 if deref_type != '' && deref_type != 'int_literal' && deref_type != 'float_literal' {
715 return deref_type
716 }
717 inner_type := g.expr_array_runtime_type(expr.expr)
718 if inner_type.ends_with('*') {
719 return inner_type[..inner_type.len - 1]
720 }
721 }
722 if expr is ast.SelectorExpr {
723 field_type := g.selector_array_value_type(expr)
724 if field_type != '' {
725 return field_type
726 }
727 expr_type := g.get_expr_type(expr)
728 if alias_base := g.alias_base_c_type(expr_type.trim_right('*')) {
729 if c_type_is_array_value(alias_base) {
730 return expr_type.trim_right('*')
731 }
732 }
733 }
734 // Or-data-access: _or_tN.data — extract array type from result/option wrapper
735 if expr is ast.SelectorExpr && expr.rhs.name == 'data' && expr.lhs is ast.Ident {
736 lhs_type := g.get_expr_type(expr.lhs)
737 if lhs_type.starts_with('_result_') {
738 return lhs_type['_result_'.len..]
739 }
740 if lhs_type.starts_with('_option_') {
741 return lhs_type['_option_'.len..]
742 }
743 }
744 mut typ := g.get_expr_type(expr)
745 if expr is ast.Ident {
746 local_type := g.get_local_var_c_type(expr.name) or { '' }
747 if c_type_is_array_value(local_type) {
748 typ = local_type
749 }
750 }
751 if typ != '' && typ != 'int_literal' && typ != 'float_literal' {
752 return typ
753 }
754 if raw_type := g.get_raw_type(expr) {
755 typ = g.types_type_to_c(raw_type)
756 }
757 if typ == '' {
758 return 'array'
759 }
760 return typ
761}
762
763// array_elem_type_from_expr resolves the element type of an array expression
764// using the types.Environment.
765fn (mut g Gen) infer_array_elem_type_from_expr(arr_expr ast.Expr) string {
766 match arr_expr {
767 ast.ModifierExpr {
768 return g.infer_array_elem_type_from_expr(arr_expr.expr)
769 }
770 ast.ParenExpr {
771 return g.infer_array_elem_type_from_expr(arr_expr.expr)
772 }
773 ast.PrefixExpr {
774 if arr_expr.op in [.amp, .mul] {
775 return g.infer_array_elem_type_from_expr(arr_expr.expr)
776 }
777 }
778 ast.CastExpr {
779 return g.infer_array_elem_type_from_expr(arr_expr.expr)
780 }
781 ast.AsCastExpr {
782 return g.infer_array_elem_type_from_expr(arr_expr.expr)
783 }
784 ast.IndexExpr {
785 if arr_expr.expr is ast.RangeExpr {
786 return g.infer_array_elem_type_from_expr(arr_expr.lhs)
787 }
788 }
789 ast.SelectorExpr {
790 field_type := g.selector_array_value_type(arr_expr)
791 if field_type != '' {
792 elem_type := array_alias_elem_type(field_type)
793 if elem_type != '' {
794 return elem_type
795 }
796 }
797 }
798 else {}
799 }
800
801 if arr_expr is ast.ArrayInitExpr {
802 elem_type := g.extract_array_elem_type(arr_expr.typ)
803 value_elem_type := g.infer_array_init_value_elem_type(arr_expr)
804 specialized := specialized_generic_elem_type_from_value(elem_type, value_elem_type)
805 if specialized != '' {
806 return specialized
807 }
808 if (elem_type == '' || elem_type == 'int') && value_elem_type != ''
809 && value_elem_type != 'int' {
810 return value_elem_type
811 }
812 if elem_type != '' {
813 return elem_type
814 }
815 if arr_expr.exprs.len > 0 {
816 first_expr_type := g.get_expr_type(arr_expr.exprs[0])
817 if first_expr_type != '' && first_expr_type != 'int' && first_expr_type != 'int_literal' {
818 return first_expr_type
819 }
820 }
821 }
822 // For slice calls, the return type is generic `array`; resolve from the source array.
823 if arr_expr is ast.CallExpr {
824 if arr_expr.lhs is ast.Ident {
825 fn_name := sanitize_fn_ident(arr_expr.lhs.name)
826 if fn_name in ['new_array_from_c_array', 'builtin__new_array_from_c_array', 'builtin__new_array_from_c_array_noscan']
827 && arr_expr.args.len >= 3 {
828 sizeof_arg := arr_expr.args[2]
829 if sizeof_arg is ast.KeywordOperator && sizeof_arg.op == .key_sizeof
830 && sizeof_arg.exprs.len > 0 {
831 t := g.expr_type_to_c(sizeof_arg.exprs[0])
832 if arr_expr.args.len > 3 {
833 data_elem := g.infer_array_elem_type_from_expr(arr_expr.args[3])
834 specialized := specialized_generic_elem_type_from_value(t, data_elem)
835 if specialized != '' {
836 return specialized
837 }
838 }
839 if t != '' && t != 'int' {
840 return t
841 }
842 }
843 if arr_expr.args.len > 3 {
844 return g.infer_array_elem_type_from_expr(arr_expr.args[3])
845 }
846 }
847 // __new_array_with_default_noscan(len, cap, sizeof(T), default)
848 // arg[2] is sizeof(T) — extract T from the KeywordOperator
849 if fn_name in ['__new_array_with_default_noscan', '__new_array_with_default', 'builtin____new_array_with_default_noscan', 'builtin____new_array_with_default']
850 && arr_expr.args.len >= 3 {
851 sizeof_arg := arr_expr.args[2]
852 if sizeof_arg is ast.KeywordOperator && sizeof_arg.op == .key_sizeof
853 && sizeof_arg.exprs.len > 0 {
854 t := g.expr_type_to_c(sizeof_arg.exprs[0])
855 if t != '' && t != 'int' {
856 return t
857 }
858 }
859 }
860 if fn_name in ['array__slice', 'array__slice_ni']
861 || (fn_name.starts_with('Array_') && (fn_name.ends_with('__slice')
862 || fn_name.ends_with('__slice_ni'))) {
863 if arr_expr.args.len > 0 {
864 return g.infer_array_elem_type_from_expr(arr_expr.args[0])
865 }
866 }
867 if fn_name in ['array__clone', 'array__clone_to_depth']
868 || (fn_name.starts_with('Array_') && (fn_name.ends_with('__clone')
869 || fn_name.ends_with('__clone_to_depth'))) {
870 if arr_expr.args.len > 0 {
871 return g.infer_array_elem_type_from_expr(arr_expr.args[0])
872 }
873 }
874 }
875 }
876 if raw_type := g.get_raw_type(arr_expr) {
877 if elem := array_elem_type_from_raw(raw_type) {
878 return g.types_type_to_c(elem)
879 }
880 }
881 // Secondary path: when get_raw_type fails (e.g. SelectorExpr on sum type cast pointers
882 // with invalid pos.ids), resolve the C type name back to a raw type.
883 arr_type := g.get_expr_type(arr_expr)
884 if arr_type != '' && arr_type != 'int' && arr_type != 'array' {
885 if raw := g.resolve_c_type_to_raw(arr_type) {
886 if elem := array_elem_type_from_raw(raw) {
887 return g.types_type_to_c(elem)
888 }
889 }
890 }
891 // Last resort: string-based extraction (e.g. Array_int → int).
892 return array_alias_elem_type(arr_type)
893}
894
895fn (mut g Gen) infer_array_init_value_elem_type(node ast.ArrayInitExpr) string {
896 for expr in node.exprs {
897 mut elem_type := g.get_expr_type(expr)
898 if elem_type == '' || elem_type == 'int' || elem_type == 'int_literal'
899 || elem_type == 'float_literal' || elem_type == 'void' {
900 if raw_type := g.get_raw_type(expr) {
901 elem_type = g.types_type_to_c(raw_type)
902 }
903 }
904 if elem_type == '' || elem_type == 'int' || elem_type == 'int_literal'
905 || elem_type == 'float_literal' || elem_type == 'void' {
906 continue
907 }
908 return unmangle_c_ptr_type(elem_type)
909 }
910 return ''
911}
912
913// array_elem_type_from_raw extracts the element type from a raw types.Type
914// that represents an array, unwrapping Pointer and Alias layers as needed.
915fn array_elem_type_from_raw(t types.Type) ?types.Type {
916 if !type_has_valid_data(t) {
917 return none
918 }
919 match t {
920 types.Array {
921 return t.elem_type
922 }
923 types.Pointer {
924 if elem := array_elem_type_from_raw(t.base_type) {
925 return elem
926 }
927 return none
928 }
929 types.Alias {
930 if elem := array_elem_type_from_raw(t.base_type) {
931 return elem
932 }
933 return none
934 }
935 else {
936 return none
937 }
938 }
939}
940
941fn (mut g Gen) infer_array_method_elem_type(expr ast.Expr) string {
942 match expr {
943 ast.CallExpr {
944 if expr.lhs is ast.Ident
945 && expr.lhs.name in ['array__pop', 'array__pop_left', 'array__first', 'array__last'] {
946 if expr.args.len > 0 {
947 return g.infer_array_elem_type_from_expr(expr.args[0])
948 }
949 }
950 if expr.lhs is ast.SelectorExpr
951 && expr.lhs.rhs.name in ['pop', 'pop_left', 'first', 'last'] {
952 arr_expr := if expr.args.len > 0 { expr.args[0] } else { expr.lhs.lhs }
953 return g.infer_array_elem_type_from_expr(arr_expr)
954 }
955 }
956 else {}
957 }
958
959 return ''
960}
961
962fn (mut g Gen) infer_array_contains_elem_type(name string, call_args []ast.Expr) string {
963 arr_type := if call_args.len > 0 { g.get_expr_type(call_args[0]) } else { '' }
964 if arr_type.starts_with('Array_fixed_') {
965 if finfo := g.collected_fixed_array_types[arr_type] {
966 return finfo.elem_type
967 }
968 } else if arr_type.starts_with('Array_') {
969 return arr_type['Array_'.len..]
970 }
971 mut suffix := ''
972 if name.starts_with('array__contains_') {
973 suffix = name['array__contains_'.len..]
974 }
975 if suffix != '' && suffix != 'void' {
976 return suffix
977 }
978 if call_args.len > 1 {
979 rhs_type := g.get_expr_type(call_args[1])
980 if rhs_type != '' && rhs_type != 'int_literal' && rhs_type != 'float_literal'
981 && rhs_type != 'void' {
982 return rhs_type.trim_right('*')
983 }
984 }
985 return 'int'
986}
987
988fn (mut g Gen) fixed_array_selector_elem_type(sel ast.SelectorExpr) string {
989 mut struct_name := ''
990 if raw_type := g.get_raw_type(sel.lhs) {
991 match raw_type {
992 types.Pointer {
993 if raw_type.base_type is types.Struct {
994 struct_name = raw_type.base_type.name
995 } else if raw_type.base_type is types.Alias {
996 struct_name = raw_type.base_type.name
997 }
998 }
999 types.Struct {
1000 struct_name = raw_type.name
1001 }
1002 types.Alias {
1003 struct_name = raw_type.name
1004 }
1005 else {}
1006 }
1007 }
1008 if struct_name == '' {
1009 struct_name = g.get_expr_type(sel.lhs).trim_right('*')
1010 }
1011 if struct_name != '' {
1012 field_key := '${struct_name}.${sel.rhs.name}'
1013 if elem := g.fixed_array_field_elem[field_key] {
1014 return elem
1015 }
1016 }
1017 if struct_name.contains('__') {
1018 short_name := struct_name.all_after_last('__')
1019 short_field_key := '${short_name}.${sel.rhs.name}'
1020 if elem := g.fixed_array_field_elem[short_field_key] {
1021 return elem
1022 }
1023 }
1024 return ''
1025}
1026
1027fn (mut g Gen) is_fixed_array_selector(sel ast.SelectorExpr) bool {
1028 if g.fixed_array_selector_elem_type(sel) != '' {
1029 return true
1030 }
1031 return false
1032}
1033
1034fn interface_type_names_match(left string, right string) bool {
1035 left_base := left.trim_right('*')
1036 right_base := right.trim_right('*')
1037 return left_base == right_base
1038 || left_base.all_after_last('__') == right_base.all_after_last('__')
1039}
1040
1041fn (mut g Gen) expr_already_has_interface_type(expr ast.Expr, iface_type string) bool {
1042 expr_type := g.get_expr_type(expr)
1043 if expr_type != '' && interface_type_names_match(expr_type, iface_type)
1044 && g.is_interface_type(expr_type.trim_right('*')) {
1045 return true
1046 }
1047 if raw_type := g.get_raw_type(expr) {
1048 c_type := g.types_type_to_c(raw_type)
1049 return c_type != '' && interface_type_names_match(c_type, iface_type)
1050 && g.is_interface_type(c_type.trim_right('*'))
1051 }
1052 return false
1053}
1054
1055fn (mut g Gen) gen_array_init_expr(node ast.ArrayInitExpr) {
1056 raw_elem := g.extract_array_elem_type(node.typ)
1057 // Convert C pointer syntax to mangled name for composite types:
1058 // Array_int* -> Array_intptr (typedef'd alias)
1059 elem_type := if raw_elem.ends_with('*')
1060 && (raw_elem.starts_with('Array_') || raw_elem.starts_with('Map_')) {
1061 mangle_alias_component(raw_elem)
1062 } else {
1063 unmangle_c_ptr_type(raw_elem)
1064 }
1065 is_dyn := g.is_dynamic_array_type(node.typ) && !array_init_has_fixed_len_marker(node)
1066 // Fallback: if dynamic array but elem type couldn't be extracted from type annotation,
1067 // infer from the first expression (e.g., for [][2]int where inner [2]int has type info)
1068 mut final_elem := elem_type
1069 if is_dyn && node.exprs.len > 0 {
1070 inferred := g.infer_array_init_value_elem_type(node)
1071 specialized := specialized_generic_elem_type_from_value(final_elem, inferred)
1072 if specialized != '' {
1073 final_elem = specialized
1074 }
1075 }
1076 if (final_elem == '' || final_elem == 'int') && is_dyn && node.exprs.len > 0 {
1077 inferred := g.infer_array_init_value_elem_type(node)
1078 if inferred != '' && inferred != 'array' && inferred != 'int' {
1079 final_elem = inferred
1080 }
1081 }
1082 if is_dyn && is_generic_placeholder_c_type_name(final_elem) && node.exprs.len == 1 {
1083 elem_expr_type := g.get_expr_type(node.exprs[0]).trim_space()
1084 if elem_expr_type != '' && elem_expr_type !in ['int', 'void', 'void*', 'voidptr']
1085 && !is_generic_placeholder_c_type_name(elem_expr_type) {
1086 final_elem = elem_expr_type
1087 }
1088 }
1089 if node.exprs.len > 0 {
1090 // Has elements
1091 if final_elem != '' && is_dyn {
1092 // Dynamic array compound literal: (elem_type[N]){e1, e2, ...}
1093 g.sb.write_string('(${final_elem}[${node.exprs.len}]){')
1094 is_iface_elem := g.is_interface_type(final_elem)
1095 for i in 0 .. node.exprs.len {
1096 e := node.exprs[i]
1097 if i > 0 {
1098 g.sb.write_string(', ')
1099 }
1100 if is_iface_elem && !g.expr_already_has_interface_type(e, final_elem)
1101 && g.gen_interface_cast(final_elem, e) {
1102 // interface wrapping handled
1103 } else if g.get_sum_type_variants_for(final_elem).len > 0 {
1104 g.gen_type_cast_expr(final_elem, e)
1105 } else {
1106 g.expr(e)
1107 }
1108 }
1109 g.sb.write_string('}')
1110 return
1111 }
1112 // Fixed-size array or untyped: {e1, e2, ...}
1113 g.sb.write_string('{')
1114 for i in 0 .. node.exprs.len {
1115 e := node.exprs[i]
1116 if i > 0 {
1117 g.sb.write_string(', ')
1118 }
1119 if final_elem != '' && g.get_sum_type_variants_for(final_elem).len > 0 {
1120 g.gen_type_cast_expr(final_elem, e)
1121 } else {
1122 g.expr(e)
1123 }
1124 }
1125 g.sb.write_string('}')
1126 return
1127 }
1128 // Handle fixed array with init: clause (e.g., [3]int{init: 10} → {10, 10, 10})
1129 if node.init !is ast.EmptyExpr && !is_dyn {
1130 mut size := 0
1131 if node.typ is ast.Type && node.typ is ast.ArrayFixedType {
1132 fixed_typ := node.typ as ast.ArrayFixedType
1133 if fixed_typ.len is ast.BasicLiteral && fixed_typ.len.kind == .number {
1134 size = fixed_typ.len.value.int()
1135 }
1136 }
1137 if size > 0 {
1138 g.sb.write_string('{')
1139 for i := 0; i < size; i++ {
1140 if i > 0 {
1141 g.sb.write_string(', ')
1142 }
1143 g.expr(node.init)
1144 }
1145 g.sb.write_string('}')
1146 return
1147 }
1148 }
1149 // Empty dynamic arrays should have been lowered by transformer to
1150 // __new_array_with_default_noscan(). Fixed arrays still use C zero-init.
1151 if !is_dyn {
1152 g.sb.write_string('{0}')
1153 return
1154 }
1155 g.sb.write_string('(array){0}')
1156}
1157
1158fn (mut g Gen) gen_fixed_array_initializer(node ast.ArrayInitExpr) {
1159 if node.exprs.len > 0 {
1160 g.sb.write_string('{')
1161 for i in 0 .. node.exprs.len {
1162 if i > 0 {
1163 g.sb.write_string(', ')
1164 }
1165 elem := node.exprs[i]
1166 if !g.gen_fixed_array_initializer_from_expr(elem) {
1167 g.expr(elem)
1168 }
1169 }
1170 g.sb.write_string('}')
1171 return
1172 }
1173 if node.init !is ast.EmptyExpr {
1174 size := g.fixed_array_initializer_size(node)
1175 if size > 0 {
1176 g.sb.write_string('{')
1177 for i := 0; i < size; i++ {
1178 if i > 0 {
1179 g.sb.write_string(', ')
1180 }
1181 g.expr(node.init)
1182 }
1183 g.sb.write_string('}')
1184 return
1185 }
1186 }
1187 g.sb.write_string('{0}')
1188}
1189
1190fn (mut g Gen) gen_fixed_array_initializer_from_expr(expr ast.Expr) bool {
1191 unwrapped := strip_expr_wrappers(expr)
1192 match unwrapped {
1193 ast.ArrayInitExpr {
1194 g.gen_fixed_array_initializer(unwrapped)
1195 return true
1196 }
1197 ast.CallExpr {
1198 if unwrapped.lhs !is ast.Ident {
1199 return false
1200 }
1201 fn_name := (unwrapped.lhs as ast.Ident).name
1202 if fn_name !in ['builtin__new_array_from_c_array_noscan',
1203 'builtin__new_array_from_c_array', 'new_array_from_c_array'] {
1204 return false
1205 }
1206 if unwrapped.args.len < 4 {
1207 return false
1208 }
1209 array_data := extract_array_init_arg(unwrapped.args[3]) or { return false }
1210 g.gen_fixed_array_initializer(array_data)
1211 return true
1212 }
1213 else {
1214 return false
1215 }
1216 }
1217}
1218
1219fn (mut g Gen) fixed_array_initializer_size(node ast.ArrayInitExpr) int {
1220 if node.typ is ast.Type && node.typ is ast.ArrayFixedType {
1221 fixed_typ := node.typ as ast.ArrayFixedType
1222 resolved := g.expr_to_int_str_with_env(fixed_typ.len)
1223 if resolved != '0' {
1224 return resolved.int()
1225 }
1226 }
1227 if node.len !is ast.EmptyExpr {
1228 resolved := g.expr_to_int_str_with_env(node.len)
1229 if resolved != '0' {
1230 return resolved.int()
1231 }
1232 }
1233 return node.exprs.len
1234}
1235
1236// extract_array_elem_expr extracts the element type expression from an array type
1237
1238fn (g &Gen) extract_array_elem_expr(e ast.Expr) ast.Expr {
1239 match e {
1240 ast.Type {
1241 if e is ast.ArrayType {
1242 return e.elem_type
1243 }
1244 if e is ast.ArrayFixedType {
1245 return e.elem_type
1246 }
1247 }
1248 else {}
1249 }
1250
1251 return ast.empty_expr
1252}
1253
1254// extract_array_elem_type extracts the element C type from an array type expression
1255
1256fn (mut g Gen) extract_array_elem_type(e ast.Expr) string {
1257 elem_expr := g.extract_array_elem_expr(e)
1258 if elem_expr != ast.empty_expr {
1259 return g.expr_type_to_c(elem_expr)
1260 }
1261 return ''
1262}
1263
1264// is_dynamic_array_type checks if the type expression is a dynamic array (ArrayType, not ArrayFixedType)
1265
1266fn (g &Gen) is_dynamic_array_type(e ast.Expr) bool {
1267 match e {
1268 ast.Type {
1269 if e is ast.ArrayType {
1270 return true
1271 }
1272 }
1273 else {}
1274 }
1275
1276 return false
1277}
1278
1279fn array_init_has_fixed_len_marker(node ast.ArrayInitExpr) bool {
1280 if node.len is ast.PostfixExpr {
1281 postfix := node.len as ast.PostfixExpr
1282 return postfix.op == .not && postfix.expr is ast.EmptyExpr
1283 }
1284 return false
1285}
1286
1287fn extract_array_init_arg(expr ast.Expr) ?ast.ArrayInitExpr {
1288 unwrapped := strip_expr_wrappers(expr)
1289 match unwrapped {
1290 ast.ArrayInitExpr {
1291 return unwrapped
1292 }
1293 ast.PrefixExpr {
1294 if unwrapped.op == .amp {
1295 return extract_array_init_arg(unwrapped.expr)
1296 }
1297 }
1298 else {}
1299 }
1300
1301 return none
1302}
1303
1304fn (mut g Gen) try_emit_const_dynamic_array_call(name string, value ast.Expr) bool {
1305 base_value := strip_expr_wrappers(value)
1306 if base_value !is ast.CallExpr {
1307 return false
1308 }
1309 call := base_value as ast.CallExpr
1310 if call.lhs !is ast.Ident {
1311 return false
1312 }
1313 fn_name := (call.lhs as ast.Ident).name
1314 if fn_name !in ['builtin__new_array_from_c_array_noscan', 'builtin__new_array_from_c_array',
1315 'new_array_from_c_array'] {
1316 return false
1317 }
1318 if call.args.len < 4 {
1319 return false
1320 }
1321 array_data := extract_array_init_arg(call.args[3]) or { return false }
1322 mut elem_type := g.extract_array_elem_type(array_data.typ)
1323 if elem_type == '' && array_data.exprs.len > 0 {
1324 elem_type = g.get_expr_type(array_data.exprs[0])
1325 }
1326 if elem_type == '' || elem_type == 'array' {
1327 return false
1328 }
1329 // Check that no element contains a function call (not valid in C static initializers)
1330 for i in 0 .. array_data.exprs.len {
1331 elem := array_data.exprs[i]
1332 if g.const_initializer_needs_runtime(elem) {
1333 return false
1334 }
1335 }
1336 mut len_expr := expr_to_int_str(call.args[0])
1337 mut cap_expr := expr_to_int_str(call.args[1])
1338 if len_expr == '0' && array_data.exprs.len > 0 {
1339 len_expr = '${array_data.exprs.len}'
1340 }
1341 if cap_expr == '0' {
1342 cap_expr = len_expr
1343 }
1344 data_name := '__const_array_data_${name}'
1345 if array_data.exprs.len > 0 {
1346 g.sb.write_string('static ${elem_type} ${data_name}[${array_data.exprs.len}] = {')
1347 for i in 0 .. array_data.exprs.len {
1348 e := array_data.exprs[i]
1349 if i > 0 {
1350 g.sb.write_string(', ')
1351 }
1352 g.expr(e)
1353 }
1354 g.sb.writeln('};')
1355 g.sb.writeln('array ${name} = ((array){ .data = ${data_name}, .offset = 0, .len = ${len_expr}, .cap = ${cap_expr}, .flags = 0, .element_size = sizeof(${elem_type}) });')
1356 } else {
1357 g.sb.writeln('array ${name} = ((array){ .data = NULL, .offset = 0, .len = ${len_expr}, .cap = ${cap_expr}, .flags = 0, .element_size = sizeof(${elem_type}) });')
1358 }
1359 return true
1360}
1361
1362fn (mut g Gen) gen_fixed_array_cmp_operand(expr ast.Expr, fixed_type string) {
1363 // For ArrayInitExpr (fixed array literals), wrap in compound literal
1364 if expr is ast.ArrayInitExpr {
1365 g.sb.write_string('(${fixed_type})')
1366 g.expr(expr)
1367 return
1368 }
1369 // Unwrap parens to find ArrayInitExpr inside
1370 if expr is ast.ParenExpr {
1371 inner := g.unwrap_parens(expr.expr)
1372 if inner is ast.ArrayInitExpr {
1373 g.sb.write_string('(${fixed_type})')
1374 g.expr(inner)
1375 return
1376 }
1377 }
1378 g.expr(expr)
1379}
1380
1381// gen_typed_array_eq generates inline element-wise array comparison for struct/map element types.
1382// Uses GCC statement expression: ({ array _a = ...; array _b = ...; bool _eq = ...; ... _eq; })
1383fn (mut g Gen) gen_typed_array_eq(elem_type string, call_args []ast.Expr) {
1384 g.sb.write_string('({ array _a = ')
1385 if g.expr_is_pointer(call_args[0]) {
1386 g.sb.write_string('*')
1387 }
1388 g.expr(call_args[0])
1389 g.sb.write_string('; array _b = ')
1390 if g.expr_is_pointer(call_args[1]) {
1391 g.sb.write_string('*')
1392 }
1393 g.expr(call_args[1])
1394 g.sb.writeln('; bool _eq = (_a.len == _b.len);')
1395 g.sb.writeln(' for (int _i = 0; _eq && _i < _a.len; _i++) {')
1396 g.sb.writeln(' ${elem_type} _sa = ((${elem_type}*)_a.data)[_i];')
1397 g.sb.writeln(' ${elem_type} _sb = ((${elem_type}*)_b.data)[_i];')
1398 if elem_type.starts_with('Map_') {
1399 g.sb.writeln(' if (!${elem_type}_map_eq(_sa, _sb)) _eq = false;')
1400 } else {
1401 struct_type := g.lookup_struct_type_by_c_name(elem_type)
1402 if struct_type.fields.len > 0 {
1403 for field in struct_type.fields {
1404 fname := field.name
1405 ftype := field.typ
1406 match ftype {
1407 types.String {
1408 g.sb.writeln(' if (!string__eq(_sa.${fname}, _sb.${fname})) _eq = false;')
1409 }
1410 types.Map {
1411 c_type := g.types_type_to_c(ftype)
1412 if c_type.starts_with('Map_') {
1413 g.sb.writeln(' if (!${c_type}_map_eq(_sa.${fname}, _sb.${fname})) _eq = false;')
1414 } else {
1415 g.sb.writeln(' if (!map_map_eq(_sa.${fname}, _sb.${fname})) _eq = false;')
1416 }
1417 }
1418 types.Array {
1419 g.sb.writeln(' if (!__v2_array_eq(_sa.${fname}, _sb.${fname})) _eq = false;')
1420 }
1421 else {
1422 g.sb.writeln(' if (_sa.${fname} != _sb.${fname}) _eq = false;')
1423 }
1424 }
1425 }
1426 } else {
1427 // Struct not found, fall back to memcmp
1428 g.sb.writeln(' if (memcmp(&_sa, &_sb, sizeof(${elem_type})) != 0) _eq = false;')
1429 }
1430 }
1431 g.sb.writeln(' }')
1432 g.sb.write_string(' _eq; })')
1433}
1434
1435// parse_fixed_array_elem_type extracts element type and length from "Array_fixed_T_N".
1436fn parse_fixed_array_elem_type(fixed_type string) (string, int) {
1437 without_prefix := fixed_type['Array_fixed_'.len..]
1438 // Find the last underscore followed by digits (the array length)
1439 for i := without_prefix.len - 1; i >= 0; i-- {
1440 if without_prefix[i] == `_` {
1441 len_str := without_prefix[i + 1..]
1442 elem_type := without_prefix[..i]
1443 return elem_type, len_str.int()
1444 }
1445 }
1446 return without_prefix, 0
1447}
1448
1449// fixed_array_elem_needs_deep_eq returns true if the element type requires deep comparison.
1450fn fixed_array_elem_needs_deep_eq(elem_type string) bool {
1451 if elem_type == 'string' || elem_type == 'array' || elem_type.starts_with('Map_') {
1452 return true
1453 }
1454 // Dynamic arrays (Array_T but not Array_fixed_T) always need deep eq
1455 if elem_type.starts_with('Array_') && !elem_type.starts_with('Array_fixed_') {
1456 return true
1457 }
1458 // For nested fixed arrays (Array_fixed_T_N), recursively check inner element type
1459 if elem_type.starts_with('Array_fixed_') {
1460 inner_elem, _ := parse_fixed_array_elem_type(elem_type)
1461 return fixed_array_elem_needs_deep_eq(inner_elem)
1462 }
1463 return false
1464}
1465
1466// gen_fixed_array_deep_eq generates element-wise comparison for fixed arrays with non-trivial elements.
1467// Uses pointers to avoid C array assignment issues (C arrays can't be initialized with =).
1468// For nested fixed arrays (e.g., [2][2]string), flattens to the innermost non-fixed element type.
1469fn (mut g Gen) gen_fixed_array_deep_eq(fixed_type string, elem_type string, arr_len int, call_args []ast.Expr) {
1470 // Flatten nested fixed arrays to innermost element type
1471 mut flat_elem := elem_type
1472 mut total_len := arr_len
1473 for flat_elem.starts_with('Array_fixed_') {
1474 inner, inner_len := parse_fixed_array_elem_type(flat_elem)
1475 if inner_len <= 0 {
1476 break
1477 }
1478 total_len *= inner_len
1479 flat_elem = inner
1480 }
1481 // Determine C element type for pointer casting
1482 c_elem_type := if flat_elem == 'string' {
1483 'string'
1484 } else if flat_elem == 'array' || flat_elem.starts_with('Array_') {
1485 'array'
1486 } else if flat_elem.starts_with('Map_') {
1487 flat_elem
1488 } else {
1489 flat_elem
1490 }
1491 // Cast to flat element pointers (fixed arrays are contiguous in memory)
1492 g.sb.write_string('({ ${c_elem_type} *_pa = (${c_elem_type}*)')
1493 g.gen_fixed_array_cmp_operand(call_args[0], fixed_type)
1494 g.sb.write_string('; ${c_elem_type} *_pb = (${c_elem_type}*)')
1495 g.gen_fixed_array_cmp_operand(call_args[1], fixed_type)
1496 g.sb.writeln('; bool _eq = true;')
1497 g.sb.writeln(' for (int _i = 0; _eq && _i < ${total_len}; _i++) {')
1498 if flat_elem == 'string' {
1499 g.sb.writeln(' if (!string__eq(_pa[_i], _pb[_i])) _eq = false;')
1500 } else if flat_elem == 'array' || flat_elem.starts_with('Array_') {
1501 g.sb.writeln(' if (!__v2_array_eq(_pa[_i], _pb[_i])) _eq = false;')
1502 } else if flat_elem.starts_with('Map_') {
1503 g.sb.writeln(' if (!${flat_elem}_map_eq(_pa[_i], _pb[_i])) _eq = false;')
1504 }
1505 g.sb.writeln(' }')
1506 g.sb.write_string(' _eq; })')
1507}
1508