| 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 | |
| 5 | module cleanc |
| 6 | |
| 7 | import v2.ast |
| 8 | import v2.token |
| 9 | import v2.types |
| 10 | |
| 11 | fn is_zero_number_expr(expr ast.Expr) bool { |
| 12 | return expr is ast.BasicLiteral && expr.kind == .number && expr.value == '0' |
| 13 | } |
| 14 | |
| 15 | fn decl_lhs_name(expr ast.Expr) string { |
| 16 | match expr { |
| 17 | ast.Ident { |
| 18 | return expr.name |
| 19 | } |
| 20 | ast.ModifierExpr { |
| 21 | return decl_lhs_name(expr.expr) |
| 22 | } |
| 23 | else { |
| 24 | return '' |
| 25 | } |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | fn decl_lhs_has_modifier(expr ast.Expr, kind token.Token) bool { |
| 30 | match expr { |
| 31 | ast.ModifierExpr { |
| 32 | return expr.kind == kind || decl_lhs_has_modifier(expr.expr, kind) |
| 33 | } |
| 34 | else { |
| 35 | return false |
| 36 | } |
| 37 | } |
| 38 | } |
| 39 | |
| 40 | fn decl_lhs_storage_prefix(expr ast.Expr) string { |
| 41 | if decl_lhs_has_modifier(expr, .key_static) { |
| 42 | return 'static ' |
| 43 | } |
| 44 | return '' |
| 45 | } |
| 46 | |
| 47 | fn is_array_result_call_name(call_name string) bool { |
| 48 | if call_name in ['__new_array_with_default_noscan', '__new_array_with_default', |
| 49 | 'builtin____new_array_with_default_noscan', 'builtin____new_array_with_default', |
| 50 | 'new_array_from_c_array', 'builtin__new_array_from_c_array', |
| 51 | 'builtin__new_array_from_c_array_noscan', 'array__slice', 'array__slice_ni'] { |
| 52 | return true |
| 53 | } |
| 54 | return call_name.starts_with('Array_') |
| 55 | && (call_name.ends_with('__slice') || call_name.ends_with('__slice_ni')) |
| 56 | } |
| 57 | |
| 58 | fn (g &Gen) c_type_needs_typed_zero_expr(typ string) bool { |
| 59 | t := typ.trim_space() |
| 60 | if t == '' || t in primitive_types || t.ends_with('*') || g.is_enum_type(t) { |
| 61 | return false |
| 62 | } |
| 63 | if t in ['void*', 'char*', 'u8*', 'byteptr', 'charptr', 'voidptr'] { |
| 64 | return false |
| 65 | } |
| 66 | return true |
| 67 | } |
| 68 | |
| 69 | fn (mut g Gen) gen_array_push_elem_arg(rhs ast.Expr, elem_type string) { |
| 70 | if elem_type.ends_with('*') && !g.can_take_address(rhs) { |
| 71 | addr_type := unmangle_c_ptr_type(elem_type) |
| 72 | g.sb.write_string('&((${addr_type}[1]){') |
| 73 | g.expr(rhs) |
| 74 | g.sb.write_string('}[0])') |
| 75 | return |
| 76 | } |
| 77 | base_elem_type := elem_type.trim_space().trim_right('*') |
| 78 | if base_elem_type != '' && g.get_sum_type_variants_for(base_elem_type).len > 0 { |
| 79 | g.sb.write_string('&(${elem_type}[1]){') |
| 80 | if elem_type.ends_with('*') { |
| 81 | rhs_type := g.get_expr_type(rhs).trim_space() |
| 82 | if rhs_type == elem_type { |
| 83 | g.expr(rhs) |
| 84 | } else { |
| 85 | value_expr := g.unwrap_addr_of_value_expr(rhs) or { rhs } |
| 86 | heap_name := '_heap_t${g.tmp_counter}' |
| 87 | g.tmp_counter++ |
| 88 | malloc_call := g.c_heap_malloc_call('sizeof(${base_elem_type})') |
| 89 | g.sb.write_string('({ ${base_elem_type}* ${heap_name} = (${base_elem_type}*)${malloc_call}; *${heap_name} = ') |
| 90 | g.gen_type_cast_expr(base_elem_type, value_expr) |
| 91 | g.sb.write_string('; ${heap_name}; })') |
| 92 | } |
| 93 | } else { |
| 94 | g.gen_type_cast_expr(elem_type, rhs) |
| 95 | } |
| 96 | g.sb.write_string('}') |
| 97 | return |
| 98 | } |
| 99 | g.gen_addr_of_expr(rhs, elem_type) |
| 100 | } |
| 101 | |
| 102 | fn (mut g Gen) option_result_payload_type(wrapper_type string) string { |
| 103 | if wrapper_type.starts_with('_result_') { |
| 104 | return g.result_value_type(wrapper_type) |
| 105 | } |
| 106 | if wrapper_type.starts_with('_option_') { |
| 107 | return option_value_type(wrapper_type) |
| 108 | } |
| 109 | return '' |
| 110 | } |
| 111 | |
| 112 | fn valid_decl_cast_type(typ string) bool { |
| 113 | return typ != '' && typ != 'void' && typ != 'int_literal' && typ != 'float_literal' |
| 114 | } |
| 115 | |
| 116 | fn (mut g Gen) option_result_data_cast_type(wrapper_type string, lhs_type string, rhs_type string) string { |
| 117 | payload_type := g.option_result_payload_type(wrapper_type) |
| 118 | if valid_decl_cast_type(payload_type) { |
| 119 | return payload_type |
| 120 | } |
| 121 | if valid_decl_cast_type(lhs_type) { |
| 122 | return lhs_type |
| 123 | } |
| 124 | if valid_decl_cast_type(rhs_type) { |
| 125 | return rhs_type |
| 126 | } |
| 127 | return 'int' |
| 128 | } |
| 129 | |
| 130 | fn (mut g Gen) option_result_wrapper_type_for_selector(lhs ast.Expr, fallback string) string { |
| 131 | lhs_type := g.get_expr_type(lhs) |
| 132 | if lhs_type.starts_with('_result_') || lhs_type.starts_with('_option_') { |
| 133 | return lhs_type |
| 134 | } |
| 135 | if fallback.starts_with('_result_') || fallback.starts_with('_option_') { |
| 136 | return fallback |
| 137 | } |
| 138 | if lhs is ast.Ident { |
| 139 | if local_type := g.get_local_var_c_type(lhs.name) { |
| 140 | if local_type.starts_with('_result_') || local_type.starts_with('_option_') { |
| 141 | return local_type |
| 142 | } |
| 143 | } |
| 144 | } |
| 145 | return lhs_type |
| 146 | } |
| 147 | |
| 148 | fn (mut g Gen) option_result_rhs_type(rhs ast.Expr) string { |
| 149 | rhs_type := g.get_expr_type(rhs) |
| 150 | if rhs_type.starts_with('_result_') || rhs_type.starts_with('_option_') { |
| 151 | return rhs_type |
| 152 | } |
| 153 | if rhs is ast.SelectorExpr { |
| 154 | return g.option_result_wrapper_type_for_selector(rhs.lhs, rhs_type) |
| 155 | } |
| 156 | return rhs_type |
| 157 | } |
| 158 | |
| 159 | fn (mut g Gen) decl_rhs_has_concrete_type(rhs ast.Expr) bool { |
| 160 | match rhs { |
| 161 | ast.InitExpr { |
| 162 | typ := g.expr_type_to_c(rhs.typ) |
| 163 | return typ != '' && typ != 'void' |
| 164 | } |
| 165 | ast.SelectorExpr, ast.CastExpr, ast.InfixExpr { |
| 166 | return true |
| 167 | } |
| 168 | ast.PrefixExpr { |
| 169 | return rhs.op in [.amp, .mul] && g.decl_rhs_has_concrete_type(rhs.expr) |
| 170 | } |
| 171 | ast.UnsafeExpr { |
| 172 | if typ := g.unsafe_expr_result_type(rhs) { |
| 173 | return typ != '' && typ !in ['void', 'void*', 'voidptr'] |
| 174 | } |
| 175 | } |
| 176 | ast.CallExpr { |
| 177 | if ret := g.get_call_return_type(rhs.lhs, rhs.args) { |
| 178 | return ret != '' && ret !in ['void*', 'voidptr'] |
| 179 | } |
| 180 | } |
| 181 | ast.CallOrCastExpr { |
| 182 | if g.call_or_cast_lhs_is_type(rhs.lhs) { |
| 183 | return true |
| 184 | } |
| 185 | if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) { |
| 186 | return ret != '' && ret !in ['void*', 'voidptr'] |
| 187 | } |
| 188 | } |
| 189 | else {} |
| 190 | } |
| 191 | |
| 192 | return false |
| 193 | } |
| 194 | |
| 195 | fn (mut g Gen) decl_lhs_scope_raw_type(lhs ast.Expr) ?types.Type { |
| 196 | if lhs is ast.Ident { |
| 197 | if mut fn_scope := g.ensure_cur_fn_scope() { |
| 198 | if obj := fn_scope.lookup_parent(lhs.name, 0) { |
| 199 | if obj is types.Module { |
| 200 | return none |
| 201 | } |
| 202 | return obj.typ() |
| 203 | } |
| 204 | } |
| 205 | return none |
| 206 | } |
| 207 | return g.get_raw_type(lhs) |
| 208 | } |
| 209 | |
| 210 | fn valid_decl_env_type(typ string) bool { |
| 211 | return typ != '' && typ !in ['void', 'void*', 'voidptr', 'int /*corrupt type*/'] |
| 212 | } |
| 213 | |
| 214 | fn (mut g Gen) decl_lhs_synth_env_type(lhs ast.Expr) ?string { |
| 215 | if lhs is ast.Ident && lhs.pos.id < 0 { |
| 216 | typ := g.get_env_c_type(lhs) or { return none } |
| 217 | if valid_decl_env_type(typ) { |
| 218 | return typ |
| 219 | } |
| 220 | } |
| 221 | return none |
| 222 | } |
| 223 | |
| 224 | fn (mut g Gen) decl_selector_rhs_type(rhs ast.SelectorExpr) string { |
| 225 | if g.comptime_field_var != '' { |
| 226 | ct_type := g.get_comptime_selector_type(rhs) |
| 227 | if ct_type != '' { |
| 228 | return ct_type |
| 229 | } |
| 230 | } |
| 231 | field_name := rhs.rhs.name |
| 232 | if field_name == 'err' { |
| 233 | lhs_type := g.get_expr_type(rhs.lhs) |
| 234 | is_or_tmp := rhs.lhs is ast.Ident && rhs.lhs.name.starts_with('_or_t') |
| 235 | if lhs_type.starts_with('_result_') || lhs_type.starts_with('_option_') || is_or_tmp { |
| 236 | return 'IError' |
| 237 | } |
| 238 | } |
| 239 | if field_name.starts_with('arg') { |
| 240 | lhs_type := g.get_expr_type(rhs.lhs) |
| 241 | if lhs_type.starts_with('Tuple_') { |
| 242 | if field_types := g.tuple_aliases[lhs_type] { |
| 243 | idx := field_name['arg'.len..].int() |
| 244 | if idx >= 0 && idx < field_types.len { |
| 245 | return field_types[idx] |
| 246 | } |
| 247 | } |
| 248 | } |
| 249 | } |
| 250 | declared := g.selector_declared_field_type(rhs) |
| 251 | if declared != '' && !is_generic_placeholder_c_type_name(declared) { |
| 252 | return declared |
| 253 | } |
| 254 | field_type := g.selector_field_type(rhs) |
| 255 | if field_type != '' && !is_generic_placeholder_c_type_name(field_type) { |
| 256 | return field_type |
| 257 | } |
| 258 | return '' |
| 259 | } |
| 260 | |
| 261 | fn (mut g Gen) gen_map_index_assign_fallback(lhs ast.IndexExpr, rhs ast.Expr) bool { |
| 262 | mut key_type := '' |
| 263 | mut value_type := '' |
| 264 | mut lhs_is_ptr := g.expr_is_pointer(lhs.lhs) |
| 265 | mut lhs_c_type := g.get_expr_type(lhs.lhs).trim_space() |
| 266 | if lhs_c_type.ends_with('*') { |
| 267 | lhs_is_ptr = true |
| 268 | } |
| 269 | if raw_type := g.get_raw_type(lhs.lhs) { |
| 270 | match raw_type { |
| 271 | types.Map { |
| 272 | key_type = g.types_type_to_c(raw_type.key_type) |
| 273 | value_type = g.types_type_to_c(raw_type.value_type) |
| 274 | } |
| 275 | types.Pointer { |
| 276 | lhs_is_ptr = true |
| 277 | match raw_type.base_type { |
| 278 | types.Map { |
| 279 | key_type = g.types_type_to_c(raw_type.base_type.key_type) |
| 280 | value_type = g.types_type_to_c(raw_type.base_type.value_type) |
| 281 | } |
| 282 | types.Alias { |
| 283 | if raw_type.base_type.base_type is types.Map { |
| 284 | key_type = g.types_type_to_c(raw_type.base_type.base_type.key_type) |
| 285 | value_type = g.types_type_to_c(raw_type.base_type.base_type.value_type) |
| 286 | } |
| 287 | } |
| 288 | else {} |
| 289 | } |
| 290 | } |
| 291 | types.Alias { |
| 292 | if raw_type.base_type is types.Map { |
| 293 | key_type = g.types_type_to_c(raw_type.base_type.key_type) |
| 294 | value_type = g.types_type_to_c(raw_type.base_type.value_type) |
| 295 | } |
| 296 | } |
| 297 | else {} |
| 298 | } |
| 299 | } |
| 300 | if key_type == '' || value_type == '' { |
| 301 | mut map_c_type := lhs_c_type |
| 302 | if local_type := g.local_var_c_type_for_expr(lhs.lhs) { |
| 303 | local_map_type := local_type.trim_space() |
| 304 | if local_map_type != '' && local_map_type != 'int' { |
| 305 | map_c_type = local_map_type |
| 306 | } |
| 307 | } |
| 308 | if lhs.lhs is ast.SelectorExpr { |
| 309 | mut selector_type := g.selector_storage_field_type(lhs.lhs).trim_space() |
| 310 | if selector_type == '' { |
| 311 | selector_type = g.selector_field_type(lhs.lhs).trim_space() |
| 312 | } |
| 313 | if selector_type == '' || selector_type == 'int' { |
| 314 | if plain_selector_type := g.plain_local_selector_type(lhs.lhs) { |
| 315 | selector_type = plain_selector_type.trim_space() |
| 316 | } |
| 317 | } |
| 318 | if selector_type != '' { |
| 319 | map_c_type = selector_type |
| 320 | } |
| 321 | } |
| 322 | if map_c_type.ends_with('*') { |
| 323 | map_c_type = map_c_type[..map_c_type.len - 1].trim_space() |
| 324 | } |
| 325 | if map_c_type.starts_with('Map_') { |
| 326 | key, value := g.parse_map_kv_types(map_c_type['Map_'.len..]) |
| 327 | key_type = key |
| 328 | value_type = value |
| 329 | } |
| 330 | } |
| 331 | if key_type == '' || value_type == '' { |
| 332 | return false |
| 333 | } |
| 334 | key_tmp := '_map_key${g.tmp_counter}' |
| 335 | g.tmp_counter++ |
| 336 | value_tmp := '_map_val${g.tmp_counter}' |
| 337 | g.tmp_counter++ |
| 338 | g.write_indent() |
| 339 | g.sb.write_string('{ ${key_type} ${key_tmp} = ') |
| 340 | g.expr(lhs.expr) |
| 341 | g.sb.write_string('; ${value_type} ${value_tmp} = ') |
| 342 | g.expr(rhs) |
| 343 | g.sb.write_string('; map__set(') |
| 344 | if lhs_is_ptr { |
| 345 | g.expr(lhs.lhs) |
| 346 | } else { |
| 347 | g.sb.write_string('&(') |
| 348 | g.expr(lhs.lhs) |
| 349 | g.sb.write_string(')') |
| 350 | } |
| 351 | g.sb.writeln(', (void*)&${key_tmp}, (void*)&${value_tmp}); }') |
| 352 | return true |
| 353 | } |
| 354 | |
| 355 | fn (mut g Gen) gen_overloaded_compound_assign(lhs ast.Expr, rhs ast.Expr, op token.Token) bool { |
| 356 | if lhs !is ast.Ident { |
| 357 | return false |
| 358 | } |
| 359 | lhs_ident := lhs as ast.Ident |
| 360 | op_name := match op { |
| 361 | .plus_assign { 'op_plus' } |
| 362 | .minus_assign { 'op_minus' } |
| 363 | .mul_assign { 'op_mul' } |
| 364 | .div_assign { 'op_div' } |
| 365 | .mod_assign { 'op_mod' } |
| 366 | else { '' } |
| 367 | } |
| 368 | |
| 369 | if op_name == '' { |
| 370 | return false |
| 371 | } |
| 372 | mut lhs_type := g.get_expr_type(lhs) |
| 373 | if local_type := g.get_local_var_c_type(lhs_ident.name) { |
| 374 | lhs_type = local_type |
| 375 | } |
| 376 | if lhs_type == '' || lhs_type in primitive_types || lhs_type == 'string' |
| 377 | || lhs_type.ends_with('*') || lhs_type.ends_with('ptr') { |
| 378 | return false |
| 379 | } |
| 380 | mut rhs_type := g.get_expr_type(rhs) |
| 381 | if rhs is ast.Ident { |
| 382 | rhs_ident := rhs as ast.Ident |
| 383 | if local_type := g.get_local_var_c_type(rhs_ident.name) { |
| 384 | rhs_type = local_type |
| 385 | } |
| 386 | } |
| 387 | if rhs_type != '' && rhs_type != 'int' && rhs_type != lhs_type { |
| 388 | return false |
| 389 | } |
| 390 | method_fn := '${lhs_type}__${op_name}' |
| 391 | if method_fn !in g.fn_return_types && method_fn !in g.fn_param_is_ptr { |
| 392 | return false |
| 393 | } |
| 394 | g.write_indent() |
| 395 | g.expr(lhs) |
| 396 | g.sb.write_string(' = ${method_fn}(') |
| 397 | g.expr(lhs) |
| 398 | g.sb.write_string(', ') |
| 399 | g.expr(rhs) |
| 400 | g.sb.writeln(');') |
| 401 | return true |
| 402 | } |
| 403 | |
| 404 | fn (mut g Gen) gen_assign_stmt(node ast.AssignStmt) { |
| 405 | lhs := node.lhs[0] |
| 406 | rhs := node.rhs[0] |
| 407 | // Multi-assignment with parallel RHS values (non-declaration): |
| 408 | // `p, q = q, p` needs temp variables for correct swap semantics. |
| 409 | if node.op != .decl_assign && node.lhs.len > 1 && node.rhs.len == node.lhs.len { |
| 410 | swap_id := g.tmp_counter |
| 411 | // First, evaluate all RHS values into temporaries |
| 412 | for i, rhs_expr in node.rhs { |
| 413 | mut typ := g.get_expr_type(rhs_expr) |
| 414 | if typ == '' || typ == 'int_literal' { |
| 415 | typ = 'int' |
| 416 | } |
| 417 | if typ == 'float_literal' { |
| 418 | typ = 'f64' |
| 419 | } |
| 420 | g.write_indent() |
| 421 | g.sb.write_string('${typ} _swap_${swap_id}_${i} = ') |
| 422 | g.expr(rhs_expr) |
| 423 | g.sb.writeln(';') |
| 424 | } |
| 425 | // Then assign from temporaries to LHS |
| 426 | for i, lhs_expr in node.lhs { |
| 427 | if lhs_expr is ast.Ident && lhs_expr.name == '_' { |
| 428 | g.write_indent() |
| 429 | g.sb.writeln('(void)_swap_${swap_id}_${i};') |
| 430 | continue |
| 431 | } |
| 432 | g.write_indent() |
| 433 | g.expr(lhs_expr) |
| 434 | g.sb.writeln(' = _swap_${swap_id}_${i};') |
| 435 | } |
| 436 | g.tmp_counter++ |
| 437 | return |
| 438 | } |
| 439 | |
| 440 | // Multi-declaration with parallel RHS values: |
| 441 | // `a, b := x, y` should declare both variables (not just the first one). |
| 442 | if node.op == .decl_assign && node.lhs.len > 1 && node.rhs.len == node.lhs.len { |
| 443 | for i, lhs_expr in node.lhs { |
| 444 | rhs_expr := node.rhs[i] |
| 445 | mut name := decl_lhs_name(lhs_expr) |
| 446 | if name == '_' { |
| 447 | g.write_indent() |
| 448 | g.sb.write_string('(void)(') |
| 449 | g.expr(rhs_expr) |
| 450 | g.sb.writeln(');') |
| 451 | continue |
| 452 | } |
| 453 | mut typ := g.get_expr_type(lhs_expr) |
| 454 | if typ == '' || typ == 'int' { |
| 455 | typ = g.get_expr_type(rhs_expr) |
| 456 | } |
| 457 | if typ == '' || typ == 'int_literal' { |
| 458 | typ = 'int' |
| 459 | } |
| 460 | if typ == 'float_literal' { |
| 461 | typ = 'f64' |
| 462 | } |
| 463 | c_name := c_local_name(name) |
| 464 | g.write_indent() |
| 465 | g.sb.write_string('${typ} ${c_name} = ') |
| 466 | if is_zero_number_expr(rhs_expr) && g.c_type_needs_typed_zero_expr(typ) { |
| 467 | g.sb.write_string(zero_value_for_type(typ)) |
| 468 | } else { |
| 469 | g.expr(rhs_expr) |
| 470 | } |
| 471 | g.sb.writeln(';') |
| 472 | g.remember_runtime_local_type(name, typ) |
| 473 | } |
| 474 | return |
| 475 | } |
| 476 | |
| 477 | mut tuple_lhs := []ast.Expr{} |
| 478 | if node.lhs.len > 1 { |
| 479 | tuple_lhs = shallow_copy_exprs(node.lhs) |
| 480 | } else if node.lhs.len == 1 && node.lhs[0] is ast.Tuple { |
| 481 | lhs_tuple := node.lhs[0] as ast.Tuple |
| 482 | tuple_lhs = shallow_copy_exprs(lhs_tuple.exprs) |
| 483 | } |
| 484 | if tuple_lhs.len > 1 && node.rhs.len == 1 { |
| 485 | mut tuple_type := g.get_expr_type(rhs) |
| 486 | // For tuple-LHS assignments, prefer explicit call return metadata even when |
| 487 | // positional inference produced a scalar type. |
| 488 | if rhs is ast.CallExpr { |
| 489 | if ret := g.get_call_return_type(rhs.lhs, rhs.args) { |
| 490 | if ret != '' && ret != 'int' { |
| 491 | tuple_type = ret |
| 492 | } |
| 493 | } |
| 494 | // For interface vtable method calls, look up the method's return type |
| 495 | if (tuple_type == '' || tuple_type == 'int') && rhs.lhs is ast.SelectorExpr { |
| 496 | receiver_type := g.get_expr_type(rhs.lhs.lhs).trim_right('*') |
| 497 | if g.is_interface_type(receiver_type) { |
| 498 | method_name := rhs.lhs.rhs.name |
| 499 | if method := g.interface_method_by_name(receiver_type, method_name) { |
| 500 | if method.ret_type != '' && method.ret_type != 'int' |
| 501 | && method.ret_type != 'void' { |
| 502 | tuple_type = method.ret_type |
| 503 | } |
| 504 | } |
| 505 | } |
| 506 | } |
| 507 | } else if rhs is ast.UnsafeExpr { |
| 508 | // Handle inline or-block expansion: the transformer wraps or-blocks in |
| 509 | // UnsafeExpr (GCC statement expressions) when it can't expand them to |
| 510 | // separate statements. The stmts are: [_or_t := call(), if error, _or_t.data] |
| 511 | // Extract the call return type from the first AssignStmt inside. |
| 512 | for stmt_inner in rhs.stmts { |
| 513 | if stmt_inner is ast.AssignStmt && stmt_inner.op == .decl_assign |
| 514 | && stmt_inner.rhs.len == 1 { |
| 515 | inner_rhs := stmt_inner.rhs[0] |
| 516 | mut inner_ret := '' |
| 517 | if inner_rhs is ast.CallExpr { |
| 518 | if ret := g.get_call_return_type(inner_rhs.lhs, inner_rhs.args) { |
| 519 | inner_ret = ret |
| 520 | } |
| 521 | } |
| 522 | // Strip _option_/_result_ wrapper since the UnsafeExpr already |
| 523 | // unwraps the value (last expr is _or_t.data) |
| 524 | if inner_ret.starts_with('_option_') { |
| 525 | tuple_type = option_value_type(inner_ret) |
| 526 | } else if inner_ret.starts_with('_result_') { |
| 527 | tuple_type = g.result_value_type(inner_ret) |
| 528 | } |
| 529 | break |
| 530 | } |
| 531 | } |
| 532 | } |
| 533 | if tuple_type == 'int' { |
| 534 | if rhs is ast.CastExpr { |
| 535 | cast_type := g.expr_type_to_c(rhs.typ) |
| 536 | if cast_type != '' { |
| 537 | tuple_type = cast_type |
| 538 | } |
| 539 | } |
| 540 | } |
| 541 | // Function pointer call: tuple_type may be the fn pointer typedef name |
| 542 | // (e.g. 'ui__CanvasLayoutSizeFn') or 'int' (unresolved) instead of the actual |
| 543 | // return type (e.g. 'Tuple_int_int'). Resolve via the raw type's return type. |
| 544 | if tuple_type !in g.tuple_aliases && rhs is ast.CallExpr { |
| 545 | // Method 1: Use fn_pointer_return_type on the callee expression |
| 546 | fn_ptr_ret := g.fn_pointer_return_type(rhs.lhs) |
| 547 | if fn_ptr_ret != '' && fn_ptr_ret != 'int' && fn_ptr_ret in g.tuple_aliases { |
| 548 | tuple_type = fn_ptr_ret |
| 549 | } |
| 550 | // Method 2: For selector-based fn pointer calls, resolve the field's |
| 551 | // fn type from the receiver struct and extract the return type. |
| 552 | if tuple_type !in g.tuple_aliases && rhs.lhs is ast.SelectorExpr { |
| 553 | if receiver_raw := g.get_raw_type(rhs.lhs.lhs) { |
| 554 | base_raw := if receiver_raw is types.Pointer { |
| 555 | receiver_raw.base_type |
| 556 | } else { |
| 557 | receiver_raw |
| 558 | } |
| 559 | if base_raw is types.Struct { |
| 560 | field_name := rhs.lhs.rhs.name |
| 561 | fn_ret := g.resolve_struct_field_fn_return(base_raw, field_name) |
| 562 | if fn_ret != '' && fn_ret in g.tuple_aliases { |
| 563 | tuple_type = fn_ret |
| 564 | } |
| 565 | } |
| 566 | } |
| 567 | } |
| 568 | // Method 3: Try raw type resolution on the callee expression directly |
| 569 | if tuple_type !in g.tuple_aliases { |
| 570 | if fn_raw := g.get_raw_type(rhs.lhs) { |
| 571 | fn_ret_type := match fn_raw { |
| 572 | types.FnType { |
| 573 | if rt := fn_raw.get_return_type() { |
| 574 | g.fn_return_type_to_c(rt) |
| 575 | } else { |
| 576 | '' |
| 577 | } |
| 578 | } |
| 579 | types.Alias { |
| 580 | if fn_raw.base_type is types.FnType { |
| 581 | if rt := fn_raw.base_type.get_return_type() { |
| 582 | g.fn_return_type_to_c(rt) |
| 583 | } else { |
| 584 | '' |
| 585 | } |
| 586 | } else { |
| 587 | '' |
| 588 | } |
| 589 | } |
| 590 | else { |
| 591 | '' |
| 592 | } |
| 593 | } |
| 594 | |
| 595 | if fn_ret_type != '' && fn_ret_type != 'int' && fn_ret_type in g.tuple_aliases { |
| 596 | tuple_type = fn_ret_type |
| 597 | } |
| 598 | } |
| 599 | } |
| 600 | } |
| 601 | // If tuple_type is still unresolved (empty, 'int', or 'void'), synthesize from LHS types |
| 602 | if (tuple_type == '' || tuple_type == 'int' || tuple_type == 'void') |
| 603 | && tuple_type !in g.tuple_aliases { |
| 604 | mut lhs_types := []string{cap: tuple_lhs.len} |
| 605 | mut all_resolved := true |
| 606 | for lhs_expr in tuple_lhs { |
| 607 | mut lhs_t := g.get_expr_type(lhs_expr) |
| 608 | if (lhs_t == '' || lhs_t == 'int') && lhs_expr is ast.Ident { |
| 609 | lhs_t = g.get_local_var_c_type(lhs_expr.name) or { lhs_t } |
| 610 | } |
| 611 | if lhs_t == '' { |
| 612 | all_resolved = false |
| 613 | break |
| 614 | } |
| 615 | lhs_types << lhs_t |
| 616 | } |
| 617 | if all_resolved && lhs_types.len > 1 { |
| 618 | synthesized := g.register_tuple_alias(lhs_types) |
| 619 | if synthesized != '' { |
| 620 | tuple_type = synthesized |
| 621 | } |
| 622 | } |
| 623 | } |
| 624 | mut wrapped_tuple_type := '' |
| 625 | if tuple_type.starts_with('_result_') { |
| 626 | base := g.result_value_type(tuple_type) |
| 627 | if base in g.tuple_aliases { |
| 628 | wrapped_tuple_type = tuple_type |
| 629 | tuple_type = base |
| 630 | } |
| 631 | } else if tuple_type.starts_with('_option_') { |
| 632 | base := option_value_type(tuple_type) |
| 633 | if base in g.tuple_aliases { |
| 634 | wrapped_tuple_type = tuple_type |
| 635 | tuple_type = base |
| 636 | } |
| 637 | } |
| 638 | if field_types := g.tuple_aliases[tuple_type] { |
| 639 | tmp_name := '_tuple_tmp_${g.tmp_counter}' |
| 640 | g.tmp_counter++ |
| 641 | g.write_indent() |
| 642 | if wrapped_tuple_type != '' { |
| 643 | res_tmp := '_tuple_res_tmp_${g.tmp_counter}' |
| 644 | g.tmp_counter++ |
| 645 | g.sb.write_string('${wrapped_tuple_type} ${res_tmp} = ') |
| 646 | g.expr(rhs) |
| 647 | g.sb.writeln(';') |
| 648 | g.write_indent() |
| 649 | g.sb.writeln('${tuple_type} ${tmp_name} = (*(${tuple_type}*)(((u8*)(&${res_tmp}.err)) + sizeof(IError)));') |
| 650 | } else { |
| 651 | g.sb.write_string('${tuple_type} ${tmp_name} = ') |
| 652 | g.expr(rhs) |
| 653 | g.sb.writeln(';') |
| 654 | } |
| 655 | for i, lhs_expr in tuple_lhs { |
| 656 | g.write_indent() |
| 657 | if node.op == .decl_assign { |
| 658 | mut name := decl_lhs_name(lhs_expr) |
| 659 | if name == '_' { |
| 660 | g.sb.writeln('(void)${tmp_name}.arg${i};') |
| 661 | continue |
| 662 | } |
| 663 | elem_type := if i < field_types.len { field_types[i] } else { 'int' } |
| 664 | if elem_type.starts_with('Array_fixed_') { |
| 665 | g.sb.writeln('${elem_type} ${c_local_name(name)};') |
| 666 | g.write_indent() |
| 667 | g.sb.writeln('memcpy(${c_local_name(name)}, ${tmp_name}.arg${i}, sizeof(${elem_type}));') |
| 668 | g.remember_runtime_local_type(name, elem_type) |
| 669 | continue |
| 670 | } |
| 671 | g.sb.writeln('${elem_type} ${c_local_name(name)} = ${tmp_name}.arg${i};') |
| 672 | g.remember_runtime_local_type(name, elem_type) |
| 673 | } else { |
| 674 | mut assign_name := '' |
| 675 | if lhs_expr is ast.Ident { |
| 676 | assign_name = lhs_expr.name |
| 677 | } |
| 678 | if assign_name == '_' { |
| 679 | g.sb.writeln('(void)${tmp_name}.arg${i};') |
| 680 | continue |
| 681 | } |
| 682 | elem_type := if i < field_types.len { field_types[i] } else { '' } |
| 683 | if elem_type.starts_with('Array_fixed_') { |
| 684 | g.sb.write_string('memcpy(') |
| 685 | g.expr(lhs_expr) |
| 686 | g.sb.writeln(', ${tmp_name}.arg${i}, sizeof(${elem_type}));') |
| 687 | continue |
| 688 | } |
| 689 | g.expr(lhs_expr) |
| 690 | g.sb.writeln(' = ${tmp_name}.arg${i};') |
| 691 | } |
| 692 | } |
| 693 | return |
| 694 | } |
| 695 | } |
| 696 | |
| 697 | // Check for blank identifier |
| 698 | if lhs is ast.Ident && lhs.name == '_' { |
| 699 | g.write_indent() |
| 700 | g.sb.write_string('(void)(') |
| 701 | g.expr(rhs) |
| 702 | g.sb.writeln(');') |
| 703 | return |
| 704 | } |
| 705 | |
| 706 | if node.op == .assign && lhs is ast.IndexExpr { |
| 707 | if g.gen_map_index_assign_fallback(lhs, rhs) { |
| 708 | return |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | g.write_indent() |
| 713 | if node.op == .decl_assign { |
| 714 | // Variable declaration: type name = expr |
| 715 | mut name := decl_lhs_name(lhs) |
| 716 | storage_prefix := decl_lhs_storage_prefix(lhs) |
| 717 | // Rename V variables that clash with C type names |
| 718 | if name == 'array' { |
| 719 | name = '_v_array' |
| 720 | } |
| 721 | decl_c_name := c_local_name(name) |
| 722 | if rhs is ast.IndexExpr { |
| 723 | if g.gen_decl_assign_from_local_index(name, storage_prefix, decl_c_name, rhs) { |
| 724 | return |
| 725 | } |
| 726 | } |
| 727 | // Keep fixed-size arrays as C arrays in local declarations. |
| 728 | if rhs is ast.ArrayInitExpr { |
| 729 | array_init := rhs as ast.ArrayInitExpr |
| 730 | if (array_init.typ is ast.Type && array_init.typ is ast.ArrayFixedType) |
| 731 | || array_init_has_fixed_len_marker(array_init) { |
| 732 | mut elem_type := '' |
| 733 | mut fixed_len := ast.Expr(ast.BasicLiteral{ |
| 734 | kind: .number |
| 735 | value: '${array_init.exprs.len}' |
| 736 | }) |
| 737 | if array_init.typ is ast.Type && array_init.typ is ast.ArrayFixedType { |
| 738 | fixed_typ := array_init.typ as ast.ArrayFixedType |
| 739 | elem_type = g.expr_type_to_c(fixed_typ.elem_type) |
| 740 | fixed_len = fixed_typ.len |
| 741 | } else { |
| 742 | elem_type = g.extract_array_elem_type(array_init.typ) |
| 743 | if elem_type == '' && array_init.exprs.len > 0 { |
| 744 | elem_type = g.get_expr_type(array_init.exprs[0]) |
| 745 | if elem_type == 'int_literal' { |
| 746 | elem_type = 'int' |
| 747 | } else if elem_type == 'float_literal' { |
| 748 | elem_type = 'f64' |
| 749 | } |
| 750 | } |
| 751 | } |
| 752 | if elem_type == '' { |
| 753 | elem_type = 'int' |
| 754 | } |
| 755 | mut fixed_arr_size := array_init.exprs.len |
| 756 | if fixed_arr_size == 0 { |
| 757 | // For init-based fixed arrays, get size from the type annotation |
| 758 | if fixed_len is ast.BasicLiteral && fixed_len.kind == .number { |
| 759 | fixed_arr_size = fixed_len.value.int() |
| 760 | } |
| 761 | } |
| 762 | fixed_name := 'Array_fixed_' + mangle_alias_component(elem_type) + '_' + |
| 763 | fixed_arr_size.str() |
| 764 | g.register_alias_type(fixed_name) |
| 765 | g.collected_fixed_array_types[fixed_name] = FixedArrayInfo{ |
| 766 | elem_type: elem_type |
| 767 | size: fixed_arr_size |
| 768 | } |
| 769 | g.remember_runtime_local_type(name, fixed_name) |
| 770 | is_literal_size := fixed_len is ast.BasicLiteral |
| 771 | && (fixed_len as ast.BasicLiteral).kind == .number |
| 772 | g.sb.write_string('${storage_prefix}${elem_type} ${decl_c_name}[') |
| 773 | g.expr(fixed_len) |
| 774 | if array_init.exprs.len == 0 { |
| 775 | if array_init.init !is ast.EmptyExpr && is_literal_size { |
| 776 | // Has init: clause - expand to repeated init values |
| 777 | g.sb.write_string('] = ') |
| 778 | g.gen_array_init_expr(array_init) |
| 779 | g.sb.writeln(';') |
| 780 | } else if is_literal_size { |
| 781 | g.sb.writeln('] = {0};') |
| 782 | } else { |
| 783 | // Non-literal sizes are VLAs in C99 and cannot use = {0} |
| 784 | g.sb.writeln('];') |
| 785 | g.write_indent() |
| 786 | g.sb.writeln('memset(${decl_c_name}, 0, sizeof(${decl_c_name}));') |
| 787 | } |
| 788 | } else { |
| 789 | g.sb.write_string('] = {') |
| 790 | for i in 0 .. array_init.exprs.len { |
| 791 | expr := array_init.exprs[i] |
| 792 | if i > 0 { |
| 793 | g.sb.write_string(', ') |
| 794 | } |
| 795 | if !(elem_type.starts_with('Array_fixed_') |
| 796 | && g.gen_fixed_array_initializer_from_expr(expr)) { |
| 797 | g.expr(expr) |
| 798 | } |
| 799 | } |
| 800 | g.sb.writeln('};') |
| 801 | } |
| 802 | return |
| 803 | } |
| 804 | } |
| 805 | if rhs is ast.CallExpr { |
| 806 | if orm_ret := g.orm_create_call_result_type(rhs) { |
| 807 | g.sb.write_string('${orm_ret} ${decl_c_name} = ') |
| 808 | g.expr(rhs) |
| 809 | g.sb.writeln(';') |
| 810 | g.remember_runtime_local_type(name, orm_ret) |
| 811 | if orm_ret.starts_with('_result_') || orm_ret.starts_with('_option_') { |
| 812 | g.register_alias_type(orm_ret) |
| 813 | } |
| 814 | return |
| 815 | } |
| 816 | } |
| 817 | mut typ := g.get_expr_type(rhs) |
| 818 | if rhs is ast.InitExpr && rhs.typ is ast.Ident { |
| 819 | if g.type_expr_has_metadata(rhs.typ) { |
| 820 | // Position metadata is authoritative for synthesized type expressions. |
| 821 | } else if fn_local_type := g.current_fn_module_local_type_name(rhs.typ.name) { |
| 822 | typ = fn_local_type |
| 823 | } |
| 824 | } |
| 825 | lhs_env_type := g.get_expr_type_from_env(lhs) or { '' } |
| 826 | lhs_synth_env_type := g.decl_lhs_synth_env_type(lhs) or { '' } |
| 827 | if lhs_synth_env_type != '' { |
| 828 | typ = lhs_synth_env_type |
| 829 | } |
| 830 | lhs_env_type_is_decl := valid_decl_env_type(lhs_env_type) |
| 831 | && ((!name.starts_with('_or_t') && !name.starts_with('_tmp_') |
| 832 | && !name.starts_with('_defer_t') && !name.starts_with('_assoc_t')) |
| 833 | || lhs_synth_env_type != '') |
| 834 | if rhs is ast.CallExpr { |
| 835 | if ret := g.get_call_return_type(rhs.lhs, rhs.args) { |
| 836 | if ret.starts_with('_result_') || ret.starts_with('_option_') |
| 837 | || (ret != '' && ret != 'void' && ret != 'int' && !(ret in ['void*', 'voidptr'] |
| 838 | && typ !in ['', 'int', 'void*', 'voidptr'])) { |
| 839 | typ = ret |
| 840 | } |
| 841 | } |
| 842 | if typ == '' || typ == 'int' || typ == 'array' { |
| 843 | call_name := g.resolve_call_name(rhs.lhs, rhs.args.len) |
| 844 | if is_array_result_call_name(call_name) { |
| 845 | elem_type := g.infer_array_elem_type_from_expr(rhs) |
| 846 | if elem_type != '' && elem_type != 'array' && elem_type != 'int' |
| 847 | && elem_type != 'void' { |
| 848 | typ = 'Array_' + mangle_alias_component(elem_type) |
| 849 | g.register_alias_type(typ) |
| 850 | } |
| 851 | } |
| 852 | } |
| 853 | } else if rhs is ast.CallOrCastExpr && !g.call_or_cast_lhs_is_type(rhs.lhs) { |
| 854 | if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) { |
| 855 | if ret.starts_with('_result_') || ret.starts_with('_option_') |
| 856 | || (ret != '' && ret != 'void' && ret != 'int' && !(ret in ['void*', 'voidptr'] |
| 857 | && typ !in ['', 'int', 'void*', 'voidptr'])) { |
| 858 | typ = ret |
| 859 | } |
| 860 | } |
| 861 | } |
| 862 | mut elem_type_from_array := false |
| 863 | mut typ_from_active_generic_init := false |
| 864 | if rhs is ast.InitExpr && g.active_generic_types.len > 0 { |
| 865 | init_type_name := rhs.typ.name() |
| 866 | if concrete := g.active_generic_types[init_type_name] { |
| 867 | concrete_type := g.types_type_to_c(concrete).trim_space() |
| 868 | if concrete_type != '' { |
| 869 | typ = concrete_type |
| 870 | typ_from_active_generic_init = true |
| 871 | } |
| 872 | } |
| 873 | } |
| 874 | // For Ident RHS referencing a struct-typed constant (e.g., `col := no_color` |
| 875 | // where no_color is `#define`d as a Color struct literal), use the const type. |
| 876 | if (typ == 'int' || typ == '') && rhs is ast.Ident { |
| 877 | if ct := g.const_types[rhs.name] { |
| 878 | typ = ct |
| 879 | } else if g.cur_module != '' { |
| 880 | qualified := g.cur_module + '__' + rhs.name |
| 881 | if ct := g.const_types[qualified] { |
| 882 | typ = ct |
| 883 | } |
| 884 | } |
| 885 | } |
| 886 | // For CastExpr RHS (e.g., `(i64)(0)`), derive type from the cast target. |
| 887 | if rhs is ast.CastExpr { |
| 888 | cast_type := g.expr_type_to_c(rhs.typ) |
| 889 | if cast_type != '' { |
| 890 | typ = cast_type |
| 891 | } |
| 892 | } |
| 893 | if rhs is ast.IndexExpr { |
| 894 | index_elem_type := g.infer_array_elem_type_from_expr(rhs.lhs) |
| 895 | if index_elem_type != '' && index_elem_type !in ['array', 'int', 'void'] { |
| 896 | specialized := specialized_generic_elem_type_from_value(typ, index_elem_type) |
| 897 | if typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr' |
| 898 | || specialized != '' { |
| 899 | typ = if specialized != '' { specialized } else { index_elem_type } |
| 900 | elem_type_from_array = true |
| 901 | } |
| 902 | } |
| 903 | } |
| 904 | // For temp variables registered by the transformer with a specific type, |
| 905 | // prefer the scope-registered type over the RHS expression type. |
| 906 | if name.starts_with('_or_t') || name.starts_with('_tmp_') || name.starts_with('_defer_t') |
| 907 | || name.starts_with('_assoc_t') { |
| 908 | if lhs_synth_env_type != '' { |
| 909 | typ = lhs_synth_env_type |
| 910 | if typ.starts_with('_result_') || typ.starts_with('_option_') { |
| 911 | g.register_alias_type(typ) |
| 912 | } |
| 913 | } else if raw_type := g.decl_lhs_scope_raw_type(lhs) { |
| 914 | scope_type := g.types_type_to_c(raw_type) |
| 915 | if scope_type != '' && scope_type != 'int' && scope_type !in ['void*', 'voidptr'] { |
| 916 | typ_is_wrapper := typ.starts_with('_result_') || typ.starts_with('_option_') |
| 917 | scope_type_is_wrapper := scope_type.starts_with('_result_') |
| 918 | || scope_type.starts_with('_option_') |
| 919 | if typ_is_wrapper && scope_type_is_wrapper && typ != scope_type { |
| 920 | g.register_alias_type(typ) |
| 921 | } else { |
| 922 | typ = scope_type |
| 923 | // Ensure result/option wrapper types are registered so their |
| 924 | // typedef and struct definitions get emitted in the C output. |
| 925 | if scope_type_is_wrapper { |
| 926 | g.register_alias_type(scope_type) |
| 927 | } |
| 928 | } |
| 929 | } else if scope_type == 'int' && typ == 'bool' { |
| 930 | // Fix: literal like `1` mistyped as bool in env |
| 931 | typ = 'int' |
| 932 | } |
| 933 | } |
| 934 | // Transformer-created temp vars may not exist in the checker's scope. |
| 935 | // Try to infer the type from the RHS: if RHS is an Ident referencing |
| 936 | // another temp var already registered in runtime_local_types, use that. |
| 937 | if typ == 'int' || typ == '' { |
| 938 | if rhs is ast.Ident { |
| 939 | if rhs_local_type := g.runtime_local_types[rhs.name] { |
| 940 | if rhs_local_type != '' && rhs_local_type != 'int' { |
| 941 | typ = rhs_local_type |
| 942 | } |
| 943 | } |
| 944 | } |
| 945 | } |
| 946 | // For UnsafeExpr RHS (GCC statement expression from nested or-expressions), |
| 947 | // the result type is determined by the last ExprStmt. Find its declaration |
| 948 | // in the preceding stmts to extract the type. |
| 949 | if typ == 'int' || typ == '' { |
| 950 | if rhs is ast.UnsafeExpr && rhs.stmts.len > 1 { |
| 951 | last_s := rhs.stmts[rhs.stmts.len - 1] |
| 952 | if last_s is ast.ExprStmt && last_s.expr is ast.Ident { |
| 953 | result_name := last_s.expr.name |
| 954 | // Scan preceding stmts for the decl_assign of the result variable |
| 955 | for inner_stmt in rhs.stmts[..rhs.stmts.len - 1] { |
| 956 | if inner_stmt is ast.AssignStmt && inner_stmt.op == .decl_assign |
| 957 | && inner_stmt.lhs.len == 1 { |
| 958 | inner_lhs := inner_stmt.lhs[0] |
| 959 | if inner_lhs is ast.Ident && inner_lhs.name == result_name { |
| 960 | inner_typ := g.get_expr_type(inner_stmt.rhs[0]) |
| 961 | if inner_typ != '' && inner_typ != 'int' { |
| 962 | typ = inner_typ |
| 963 | } |
| 964 | break |
| 965 | } |
| 966 | } |
| 967 | } |
| 968 | } |
| 969 | } |
| 970 | } |
| 971 | // For _or_t vars where RHS is a call through a function pointer variable, |
| 972 | // infer the return type from the function pointer's type. |
| 973 | if typ == 'int' || typ == '' { |
| 974 | if rhs is ast.CallExpr { |
| 975 | fn_ptr_ret := g.fn_pointer_return_type(rhs.lhs) |
| 976 | if fn_ptr_ret != '' && fn_ptr_ret != 'int' && fn_ptr_ret != 'void' { |
| 977 | typ = fn_ptr_ret |
| 978 | } |
| 979 | } |
| 980 | } |
| 981 | } |
| 982 | // Fix: &T(x) pattern - the checker may assign only the inner type T instead of T*. |
| 983 | // Derive the pointer type directly from the expression structure. |
| 984 | if rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.CastExpr { |
| 985 | target_type := g.expr_type_to_c(rhs.expr.typ) |
| 986 | if target_type != '' { |
| 987 | typ = target_type + '*' |
| 988 | } |
| 989 | } |
| 990 | if rhs is ast.CallExpr { |
| 991 | if rhs.lhs is ast.Ident |
| 992 | && rhs.lhs.name in ['array__pop', 'array__pop_left', 'array__first', 'array__last'] { |
| 993 | if rhs.args.len > 0 { |
| 994 | pop_elem := g.infer_array_elem_type_from_expr(rhs.args[0]) |
| 995 | if pop_elem != '' { |
| 996 | typ = pop_elem |
| 997 | elem_type_from_array = true |
| 998 | } |
| 999 | } |
| 1000 | } else if rhs.lhs is ast.SelectorExpr |
| 1001 | && rhs.lhs.rhs.name in ['pop', 'pop_left', 'first', 'last'] { |
| 1002 | arr_expr := if rhs.args.len > 0 { rhs.args[0] } else { rhs.lhs.lhs } |
| 1003 | pop_elem := g.infer_array_elem_type_from_expr(arr_expr) |
| 1004 | if pop_elem != '' { |
| 1005 | typ = pop_elem |
| 1006 | elem_type_from_array = true |
| 1007 | } |
| 1008 | } |
| 1009 | } |
| 1010 | if typ == 'array' { |
| 1011 | mut elem_type := g.infer_array_elem_type_from_expr(rhs) |
| 1012 | if elem_type == '' && rhs is ast.CallExpr { |
| 1013 | call_name := g.resolve_call_name(rhs.lhs, rhs.args.len) |
| 1014 | if call_name in ['new_array_from_c_array', 'builtin__new_array_from_c_array', 'builtin__new_array_from_c_array_noscan'] |
| 1015 | && rhs.args.len > 3 { |
| 1016 | elem_type = g.infer_array_elem_type_from_expr(rhs.args[3]) |
| 1017 | } |
| 1018 | } |
| 1019 | if elem_type != '' && elem_type != 'array' && elem_type != 'void' { |
| 1020 | typ = 'Array_' + mangle_alias_component(elem_type) |
| 1021 | g.register_alias_type(typ) |
| 1022 | } |
| 1023 | } |
| 1024 | if typ in ['void*', 'voidptr'] { |
| 1025 | // Handle array__first/array__last/pop/pop_left for element type inference |
| 1026 | if rhs is ast.CallExpr { |
| 1027 | if rhs.lhs is ast.SelectorExpr { |
| 1028 | if rhs.lhs.rhs.name in ['first', 'last', 'pop', 'pop_left'] { |
| 1029 | arr2 := if rhs.args.len > 0 { rhs.args[0] } else { rhs.lhs.lhs } |
| 1030 | elem := g.infer_array_elem_type_from_expr(arr2) |
| 1031 | if elem != '' { |
| 1032 | typ = elem |
| 1033 | } |
| 1034 | } |
| 1035 | } |
| 1036 | } |
| 1037 | mut call_name := '' |
| 1038 | mut arr_expr := rhs |
| 1039 | mut has_arr_expr := false |
| 1040 | if rhs is ast.CallExpr { |
| 1041 | call_name = g.resolve_call_name(rhs.lhs, rhs.args.len) |
| 1042 | if rhs.args.len > 0 { |
| 1043 | arr_expr = rhs.args[0] |
| 1044 | has_arr_expr = true |
| 1045 | } else if rhs.lhs is ast.SelectorExpr { |
| 1046 | arr_expr = rhs.lhs.lhs |
| 1047 | has_arr_expr = true |
| 1048 | } |
| 1049 | } |
| 1050 | if has_arr_expr |
| 1051 | && call_name in ['array__pop', 'array__pop_left', 'array__first', 'array__last'] { |
| 1052 | pop_elem := g.infer_array_elem_type_from_expr(arr_expr) |
| 1053 | if pop_elem != '' { |
| 1054 | typ = pop_elem |
| 1055 | } |
| 1056 | } |
| 1057 | } |
| 1058 | // Check scope-resolved type first (most reliable for declarations). |
| 1059 | // Treat `void*`/`voidptr` as an "unknown" fallback when we have a concrete |
| 1060 | // type from the (checker/transformer) scope; this is important for patterns |
| 1061 | // like `tmp := map__get_check(...)` where the C builtin returns `void*` but |
| 1062 | // the variable is known to be `T*`. |
| 1063 | // But skip if element type was already inferred from array methods. |
| 1064 | // Also skip for tuple field selectors (_tuple_tN.argN) where the field type |
| 1065 | // is authoritative — scope lookup may find a wrong-scoped variable of the same name. |
| 1066 | mut type_from_tuple_field := false |
| 1067 | if rhs is ast.SelectorExpr { |
| 1068 | sel_rhs := rhs as ast.SelectorExpr |
| 1069 | if sel_rhs.rhs.name.starts_with('arg') && sel_rhs.lhs is ast.Ident { |
| 1070 | sel_lhs := sel_rhs.lhs as ast.Ident |
| 1071 | type_from_tuple_field = sel_lhs.name.starts_with('_tuple_t') |
| 1072 | } |
| 1073 | } |
| 1074 | rhs_has_concrete_type := g.decl_rhs_has_concrete_type(rhs) |
| 1075 | if lhs_env_type_is_decl && (typ == '' || typ == 'int_literal' |
| 1076 | || typ == 'float_literal' || typ == 'void*' || typ == 'voidptr' |
| 1077 | || (typ == 'int' && !rhs_has_concrete_type)) { |
| 1078 | typ = lhs_env_type |
| 1079 | } |
| 1080 | if !elem_type_from_array && !type_from_tuple_field && !typ_from_active_generic_init |
| 1081 | && name != '' && g.cur_fn_scope != unsafe { nil } { |
| 1082 | if obj := g.cur_fn_scope.lookup_parent(name, 0) { |
| 1083 | if obj !is types.Module { |
| 1084 | obj_type := obj.typ() |
| 1085 | if obj_type !is types.Alias { |
| 1086 | scoped_type := g.types_type_to_c(obj_type) |
| 1087 | generic_container_fallback := |
| 1088 | (typ == 'array' && scoped_type.starts_with('Array_')) |
| 1089 | || (typ == 'map' && scoped_type.starts_with('Map_')) |
| 1090 | typ_needs_scope := typ == '' || typ == 'int_literal' |
| 1091 | || typ == 'void*' || typ == 'voidptr' |
| 1092 | || generic_container_fallback |
| 1093 | || (typ == 'int' && !rhs_has_concrete_type && !lhs_env_type_is_decl) |
| 1094 | if typ_needs_scope && scoped_type != '' |
| 1095 | && scoped_type !in ['int', 'void', 'void*', 'voidptr'] { |
| 1096 | typ = scoped_type |
| 1097 | } |
| 1098 | } |
| 1099 | } |
| 1100 | } |
| 1101 | } |
| 1102 | // For decl_assign (:=), the LHS is a brand new variable, so don't use |
| 1103 | // cached types from runtime_local_types (which may hold stale types |
| 1104 | // from a previous variable with the same name, e.g. _filter_it). |
| 1105 | if node.op != .decl_assign { |
| 1106 | lhs_typ := g.get_expr_type(lhs) |
| 1107 | if lhs_typ != '' && lhs_typ !in ['int', 'int_literal', 'float_literal'] |
| 1108 | && lhs_typ != 'void' |
| 1109 | && (typ == '' || typ == 'int' || typ == 'void*' || typ == 'voidptr') { |
| 1110 | typ = lhs_typ |
| 1111 | } |
| 1112 | } |
| 1113 | if !elem_type_from_array && rhs is ast.CallExpr { |
| 1114 | if ret := g.get_call_return_type(rhs.lhs, rhs.args) { |
| 1115 | call_is_string_slice := rhs.lhs is ast.Ident && rhs.lhs.name == 'array__slice' |
| 1116 | && rhs.args.len > 0 |
| 1117 | && g.expr_resolves_to_string(rhs.args[0], g.get_expr_type(rhs.args[0]).trim_right('*')) |
| 1118 | if call_is_string_slice { |
| 1119 | typ = 'string' |
| 1120 | } else if (ret == 'array' && typ.starts_with('Array_')) |
| 1121 | || (ret == 'map' && typ.starts_with('Map_')) { |
| 1122 | // Preserve more specific local container types inferred from call arguments. |
| 1123 | } else if ret != '' |
| 1124 | && (ret != 'int' || typ in ['', 'void*', 'voidptr'] || typ.starts_with('Array_') |
| 1125 | || typ.starts_with('Map_')) { |
| 1126 | if !(ret in ['void*', 'voidptr'] && typ !in ['', 'int', 'void*', 'voidptr']) { |
| 1127 | typ = ret |
| 1128 | } |
| 1129 | } |
| 1130 | } |
| 1131 | // For interface vtable method calls (e.g., w->size(w->_object)), |
| 1132 | // look up the method's return type from the interface declaration. |
| 1133 | if (typ == '' || typ == 'int') && rhs.lhs is ast.SelectorExpr { |
| 1134 | receiver_type := g.get_expr_type(rhs.lhs.lhs).trim_right('*') |
| 1135 | if g.is_interface_type(receiver_type) { |
| 1136 | iface_method_name := rhs.lhs.rhs.name |
| 1137 | if method := g.interface_method_by_name(receiver_type, iface_method_name) { |
| 1138 | if method.ret_type != '' && method.ret_type != 'int' |
| 1139 | && method.ret_type != 'void' { |
| 1140 | typ = method.ret_type |
| 1141 | } |
| 1142 | } |
| 1143 | } |
| 1144 | } |
| 1145 | } else if !elem_type_from_array && rhs is ast.CallOrCastExpr |
| 1146 | && !g.call_or_cast_lhs_is_type(rhs.lhs) { |
| 1147 | if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) { |
| 1148 | if ret != '' |
| 1149 | && (ret != 'int' || typ in ['', 'void*', 'voidptr'] || typ.starts_with('Array_') |
| 1150 | || typ.starts_with('Map_')) { |
| 1151 | if !(ret in ['void*', 'voidptr'] && typ !in ['', 'int', 'void*', 'voidptr']) { |
| 1152 | typ = ret |
| 1153 | } |
| 1154 | } |
| 1155 | } |
| 1156 | } |
| 1157 | if rhs is ast.KeywordOperator && rhs.op in [.key_sizeof, .key_offsetof] { |
| 1158 | typ = 'usize' |
| 1159 | } |
| 1160 | mut rhs_type := g.option_result_rhs_type(rhs) |
| 1161 | if rhs is ast.IfExpr { |
| 1162 | if_type := g.get_if_expr_type(&rhs) |
| 1163 | if if_type != '' && if_type != 'int' { |
| 1164 | rhs_type = if_type |
| 1165 | } |
| 1166 | } |
| 1167 | if rhs_type == 'int' { |
| 1168 | if rhs is ast.CallExpr { |
| 1169 | if ret := g.get_call_return_type(rhs.lhs, rhs.args) { |
| 1170 | rhs_type = ret |
| 1171 | } |
| 1172 | } else if rhs is ast.CallOrCastExpr && !g.call_or_cast_lhs_is_type(rhs.lhs) { |
| 1173 | if ret := g.get_call_return_type(rhs.lhs, [rhs.expr]) { |
| 1174 | rhs_type = ret |
| 1175 | } |
| 1176 | } |
| 1177 | } |
| 1178 | if rhs is ast.SelectorExpr && rhs.rhs.name == 'err' { |
| 1179 | container_type := g.get_expr_type(rhs.lhs) |
| 1180 | is_or_tmp := rhs.lhs is ast.Ident && rhs.lhs.name.starts_with('_or_t') |
| 1181 | if container_type.starts_with('_result_') || container_type.starts_with('_option_') |
| 1182 | || is_or_tmp { |
| 1183 | rhs_type = 'IError' |
| 1184 | typ = 'IError' |
| 1185 | } |
| 1186 | } |
| 1187 | if name != '' && rhs is ast.SelectorExpr && rhs.rhs.name == 'data' { |
| 1188 | container_type := g.option_result_wrapper_type_for_selector(rhs.lhs, rhs_type) |
| 1189 | is_or_tmp := rhs.lhs is ast.Ident && rhs.lhs.name.starts_with('_or_t') |
| 1190 | if container_type.starts_with('_result_') || container_type.starts_with('_option_') |
| 1191 | || is_or_tmp { |
| 1192 | cast_type := g.option_result_data_cast_type(container_type, typ, rhs_type) |
| 1193 | if cast_type.starts_with('Array_fixed_') { |
| 1194 | g.sb.write_string('${cast_type} ${decl_c_name}; memcpy(${decl_c_name}, (${cast_type}*)(((u8*)(&') |
| 1195 | g.expr(rhs.lhs) |
| 1196 | g.sb.writeln('.err)) + sizeof(IError)), sizeof(${cast_type}));') |
| 1197 | g.remember_runtime_local_type(name, cast_type) |
| 1198 | return |
| 1199 | } |
| 1200 | g.sb.write_string('${cast_type} ${decl_c_name} = (*(${cast_type}*)(((u8*)(&') |
| 1201 | g.expr(rhs.lhs) |
| 1202 | g.sb.writeln('.err)) + sizeof(IError)));') |
| 1203 | g.remember_runtime_local_type(name, cast_type) |
| 1204 | return |
| 1205 | } |
| 1206 | } |
| 1207 | if !elem_type_from_array && (typ == '' || typ == 'int' |
| 1208 | || typ == 'int_literal' || typ == 'void*' || typ == 'voidptr') && rhs_type != '' |
| 1209 | && rhs_type !in ['int', 'int_literal', 'float_literal'] |
| 1210 | && !rhs_type.starts_with('_result_') && !rhs_type.starts_with('_option_') { |
| 1211 | typ = rhs_type |
| 1212 | } |
| 1213 | if !elem_type_from_array && typ != '' && rhs_type.starts_with('${typ}_T_') { |
| 1214 | typ = rhs_type |
| 1215 | } |
| 1216 | if (typ == '' || typ == 'int' || typ == 'int_literal') && rhs is ast.InfixExpr { |
| 1217 | inferred := g.infer_numeric_expr_type(rhs) |
| 1218 | if inferred != '' && inferred !in ['int', 'int_literal'] { |
| 1219 | typ = inferred |
| 1220 | } |
| 1221 | } |
| 1222 | typ = unmangle_c_ptr_type(typ) |
| 1223 | if name != '' && rhs_type.starts_with('_result_') && !typ.starts_with('_result_') { |
| 1224 | if typ.starts_with('Array_fixed_') { |
| 1225 | g.sb.write_string('${typ} ${decl_c_name}; { ${rhs_type} _tmp = ') |
| 1226 | g.expr(rhs) |
| 1227 | g.sb.writeln('; memcpy(${decl_c_name}, (${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError)), sizeof(${typ})); }') |
| 1228 | g.remember_runtime_local_type(name, typ) |
| 1229 | return |
| 1230 | } |
| 1231 | g.sb.write_string('${typ} ${decl_c_name} = ({ ${rhs_type} _tmp = ') |
| 1232 | g.expr(rhs) |
| 1233 | g.sb.writeln('; (*(${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError))); });') |
| 1234 | g.remember_runtime_local_type(name, typ) |
| 1235 | return |
| 1236 | } |
| 1237 | if name != '' && rhs_type.starts_with('_option_') && !typ.starts_with('_option_') { |
| 1238 | if typ.starts_with('Array_fixed_') { |
| 1239 | g.sb.write_string('${typ} ${decl_c_name}; { ${rhs_type} _tmp = ') |
| 1240 | g.expr(rhs) |
| 1241 | g.sb.writeln('; memcpy(${decl_c_name}, (${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError)), sizeof(${typ})); }') |
| 1242 | g.remember_runtime_local_type(name, typ) |
| 1243 | return |
| 1244 | } |
| 1245 | g.sb.write_string('${typ} ${decl_c_name} = ({ ${rhs_type} _tmp = ') |
| 1246 | g.expr(rhs) |
| 1247 | g.sb.writeln('; (*(${typ}*)(((u8*)(&_tmp.err)) + sizeof(IError))); });') |
| 1248 | g.remember_runtime_local_type(name, typ) |
| 1249 | return |
| 1250 | } |
| 1251 | if rhs is ast.IfExpr { |
| 1252 | if typ.starts_with('Array_fixed_') && rhs.else_expr !is ast.EmptyExpr { |
| 1253 | g.sb.writeln('${typ} ${decl_c_name};') |
| 1254 | if name != '' { |
| 1255 | g.remember_runtime_local_type(name, typ) |
| 1256 | } |
| 1257 | g.gen_decl_if_expr(decl_c_name, typ, &rhs) |
| 1258 | return |
| 1259 | } |
| 1260 | if !g.if_expr_can_be_ternary(&rhs) && rhs.else_expr !is ast.EmptyExpr { |
| 1261 | // If type is void/empty, infer from the branch's last expression |
| 1262 | if typ == 'void' || typ == '' { |
| 1263 | if rhs.stmts.len > 0 { |
| 1264 | last := rhs.stmts[rhs.stmts.len - 1] |
| 1265 | if last is ast.ExprStmt { |
| 1266 | branch_type := g.get_expr_type(last.expr) |
| 1267 | if branch_type != '' && branch_type != 'void' { |
| 1268 | typ = branch_type |
| 1269 | } |
| 1270 | } |
| 1271 | } |
| 1272 | } |
| 1273 | g.sb.writeln('${typ} ${decl_c_name};') |
| 1274 | if name != '' { |
| 1275 | g.remember_runtime_local_type(name, typ) |
| 1276 | } |
| 1277 | g.gen_decl_if_expr(decl_c_name, typ, &rhs) |
| 1278 | return |
| 1279 | } |
| 1280 | } |
| 1281 | // `ptr := &local` where `ptr` is returned from this function must not |
| 1282 | // keep a stack address. Heap-clone the local value for that binding. |
| 1283 | if name != '' && name in g.cur_fn_returned_idents && typ.ends_with('*') |
| 1284 | && rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.Ident { |
| 1285 | prefix_rhs := rhs as ast.PrefixExpr |
| 1286 | base_type := typ.trim_right('*') |
| 1287 | if base_type != '' && base_type != 'void' { |
| 1288 | heap_name := '_heap_t${g.tmp_counter}' |
| 1289 | g.tmp_counter++ |
| 1290 | malloc_call := g.c_heap_malloc_call('sizeof(${base_type})') |
| 1291 | g.sb.write_string('${typ} ${decl_c_name} = ({ ${base_type}* ${heap_name} = (${base_type}*)${malloc_call}; *${heap_name} = ') |
| 1292 | g.expr(prefix_rhs.expr) |
| 1293 | g.sb.writeln('; ${heap_name}; });') |
| 1294 | g.remember_runtime_local_type(name, typ) |
| 1295 | return |
| 1296 | } |
| 1297 | } |
| 1298 | if typ.ends_with('**') && rhs is ast.PrefixExpr && rhs.op == .amp { |
| 1299 | prefix_rhs := rhs as ast.PrefixExpr |
| 1300 | g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(') |
| 1301 | g.expr(prefix_rhs.expr) |
| 1302 | g.sb.writeln('));') |
| 1303 | if name != '' { |
| 1304 | g.remember_runtime_local_type(name, typ) |
| 1305 | } |
| 1306 | return |
| 1307 | } |
| 1308 | if name != '' && rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.SelectorExpr { |
| 1309 | prefix_rhs := rhs as ast.PrefixExpr |
| 1310 | sel := prefix_rhs.expr as ast.SelectorExpr |
| 1311 | if sel.lhs is ast.CallExpr { |
| 1312 | call_expr := sel.lhs as ast.CallExpr |
| 1313 | if call_expr.args.len == 1 && g.call_or_cast_lhs_is_type(call_expr.lhs) { |
| 1314 | target_type := g.expr_type_to_c(call_expr.lhs) |
| 1315 | if target_type != '' && target_type != 'int' { |
| 1316 | mut field_type := typ.trim_right('*') |
| 1317 | if field_type == 'voidptr' { |
| 1318 | field_type = 'void*' |
| 1319 | } |
| 1320 | if field_type == '' || field_type == 'void' { |
| 1321 | field_type = 'void*' |
| 1322 | } |
| 1323 | g.sb.write_string('${field_type} ${decl_c_name} = ((${target_type}*)(') |
| 1324 | g.expr(call_expr.args[0]) |
| 1325 | g.sb.writeln('))->${escape_c_keyword(sel.rhs.name)};') |
| 1326 | g.remember_runtime_local_type(name, field_type) |
| 1327 | return |
| 1328 | } |
| 1329 | } |
| 1330 | } else if sel.lhs is ast.CastExpr { |
| 1331 | cast_expr := sel.lhs as ast.CastExpr |
| 1332 | target_type := g.expr_type_to_c(cast_expr.typ) |
| 1333 | if target_type != '' && target_type != 'int' { |
| 1334 | mut field_type := typ.trim_right('*') |
| 1335 | if field_type == 'voidptr' { |
| 1336 | field_type = 'void*' |
| 1337 | } |
| 1338 | if field_type == '' || field_type == 'void' { |
| 1339 | field_type = 'void*' |
| 1340 | } |
| 1341 | g.sb.write_string('${field_type} ${decl_c_name} = ((${target_type}*)(') |
| 1342 | g.expr(cast_expr.expr) |
| 1343 | g.sb.writeln('))->${escape_c_keyword(sel.rhs.name)};') |
| 1344 | g.remember_runtime_local_type(name, field_type) |
| 1345 | return |
| 1346 | } |
| 1347 | } |
| 1348 | c_typedef := g.c_typedef_for_interface_object_access(sel) |
| 1349 | if c_typedef != '' { |
| 1350 | mut field_type := typ.trim_right('*') |
| 1351 | if field_type == 'voidptr' { |
| 1352 | field_type = 'void*' |
| 1353 | } |
| 1354 | if field_type == '' || field_type == 'void' { |
| 1355 | field_type = 'void*' |
| 1356 | } |
| 1357 | g.sb.write_string('${field_type} ${decl_c_name} = ((${c_typedef}*)(') |
| 1358 | g.expr(sel.lhs) |
| 1359 | g.sb.writeln('))->${escape_c_keyword(sel.rhs.name)};') |
| 1360 | g.remember_runtime_local_type(name, field_type) |
| 1361 | return |
| 1362 | } |
| 1363 | } |
| 1364 | if typ.ends_with('*') && rhs is ast.PrefixExpr && rhs.op == .amp { |
| 1365 | prefix_rhs := rhs as ast.PrefixExpr |
| 1366 | if prefix_rhs.expr is ast.CallExpr && prefix_rhs.expr.args.len == 1 { |
| 1367 | g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(') |
| 1368 | g.expr(prefix_rhs.expr.args[0]) |
| 1369 | g.sb.writeln('));') |
| 1370 | if name != '' { |
| 1371 | g.remember_runtime_local_type(name, typ) |
| 1372 | } |
| 1373 | return |
| 1374 | } |
| 1375 | if prefix_rhs.expr is ast.CastExpr { |
| 1376 | g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(') |
| 1377 | g.expr(prefix_rhs.expr.expr) |
| 1378 | g.sb.writeln('));') |
| 1379 | if name != '' { |
| 1380 | g.remember_runtime_local_type(name, typ) |
| 1381 | } |
| 1382 | return |
| 1383 | } |
| 1384 | if prefix_rhs.expr is ast.ParenExpr { |
| 1385 | if prefix_rhs.expr.expr is ast.CallExpr && prefix_rhs.expr.expr.args.len == 1 { |
| 1386 | g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(') |
| 1387 | g.expr(prefix_rhs.expr.expr.args[0]) |
| 1388 | g.sb.writeln('));') |
| 1389 | if name != '' { |
| 1390 | g.remember_runtime_local_type(name, typ) |
| 1391 | } |
| 1392 | return |
| 1393 | } |
| 1394 | if prefix_rhs.expr.expr is ast.CastExpr { |
| 1395 | g.sb.write_string('${typ} ${decl_c_name} = ((${typ})(') |
| 1396 | g.expr(prefix_rhs.expr.expr.expr) |
| 1397 | g.sb.writeln('));') |
| 1398 | if name != '' { |
| 1399 | g.remember_runtime_local_type(name, typ) |
| 1400 | } |
| 1401 | return |
| 1402 | } |
| 1403 | } |
| 1404 | } |
| 1405 | // FnLiteral: generate proper function pointer declaration |
| 1406 | if rhs is ast.FnLiteral { |
| 1407 | ret_type := if rhs.typ.return_type !is ast.EmptyExpr { |
| 1408 | g.expr_type_to_c(rhs.typ.return_type) |
| 1409 | } else { |
| 1410 | 'void' |
| 1411 | } |
| 1412 | mut params_str := '' |
| 1413 | for i, param in rhs.typ.params { |
| 1414 | if i > 0 { |
| 1415 | params_str += ', ' |
| 1416 | } |
| 1417 | params_str += g.expr_type_to_c(param.typ) |
| 1418 | } |
| 1419 | if params_str == '' { |
| 1420 | params_str = 'void' |
| 1421 | } |
| 1422 | g.sb.write_string('${ret_type} (*${decl_c_name})(${params_str}) = ') |
| 1423 | g.expr(rhs) |
| 1424 | g.sb.writeln(';') |
| 1425 | // Register the return type so map/filter can infer result type |
| 1426 | g.fn_return_types[name] = ret_type |
| 1427 | g.remember_runtime_local_type(name, 'fn_ptr') |
| 1428 | return |
| 1429 | } |
| 1430 | if typ.starts_with('Array_fixed_') && rhs is ast.CallExpr { |
| 1431 | if call_ret := g.get_call_return_type(rhs.lhs, rhs.args) { |
| 1432 | if call_ret == typ { |
| 1433 | wrapper_type := g.c_fn_return_type_from_v(typ) |
| 1434 | g.sb.writeln('${typ} ${decl_c_name};') |
| 1435 | g.write_indent() |
| 1436 | g.sb.write_string('{ ${wrapper_type} _tmp = ') |
| 1437 | g.expr(rhs) |
| 1438 | g.sb.writeln('; memcpy(${decl_c_name}, _tmp.ret_arr, sizeof(${typ})); }') |
| 1439 | g.remember_runtime_local_type(name, typ) |
| 1440 | return |
| 1441 | } |
| 1442 | } |
| 1443 | } |
| 1444 | if typ.starts_with('Array_fixed_') && !typ.ends_with('*') && rhs !is ast.ArrayInitExpr |
| 1445 | && rhs !is ast.CallExpr { |
| 1446 | g.sb.writeln('${typ} ${decl_c_name};') |
| 1447 | g.write_indent() |
| 1448 | // For fixed arrays, check if RHS is a zero-init or type-incompatible pattern. |
| 1449 | // UnsafeExpr wrapping an array init, plain InitExpr, or dynamic array type |
| 1450 | // should all use memset for zero-init instead of memcpy. |
| 1451 | mut is_fixed_arr_zero := rhs is ast.InitExpr && rhs.fields.len == 0 |
| 1452 | if !is_fixed_arr_zero && rhs is ast.UnsafeExpr { |
| 1453 | is_fixed_arr_zero = true // unsafe { [N]T{init: expr} } → memset for now |
| 1454 | } |
| 1455 | if !is_fixed_arr_zero { |
| 1456 | fa_rhs_type := g.get_expr_type(rhs) |
| 1457 | if fa_rhs_type == '' || fa_rhs_type == 'array' || fa_rhs_type == 'int' { |
| 1458 | is_fixed_arr_zero = true |
| 1459 | } |
| 1460 | } |
| 1461 | if is_fixed_arr_zero { |
| 1462 | g.sb.writeln('memset(${decl_c_name}, 0, sizeof(${typ}));') |
| 1463 | } else { |
| 1464 | g.sb.write_string('memcpy(${decl_c_name}, ') |
| 1465 | g.expr(rhs) |
| 1466 | g.sb.writeln(', sizeof(${typ}));') |
| 1467 | } |
| 1468 | g.remember_runtime_local_type(name, typ) |
| 1469 | return |
| 1470 | } |
| 1471 | if rhs is ast.CallExpr { |
| 1472 | call_name := g.resolve_call_name(rhs.lhs, rhs.args.len) |
| 1473 | if call_name in ['math__min', 'math__max'] && rhs.args.len == 2 { |
| 1474 | arg0_type := g.get_expr_type(rhs.args[0]) |
| 1475 | arg1_type := g.get_expr_type(rhs.args[1]) |
| 1476 | if !arg0_type.starts_with('f') && arg0_type != 'float_literal' |
| 1477 | && !arg1_type.starts_with('f') && arg1_type != 'float_literal' { |
| 1478 | typ = 'int' |
| 1479 | } |
| 1480 | } |
| 1481 | } |
| 1482 | rhs_is_none := is_none_expr(rhs) || (rhs is ast.Type && rhs is ast.NoneType) |
| 1483 | rhs_is_option_zero := rhs is ast.BasicLiteral && rhs.value == '0' |
| 1484 | && typ.starts_with('_option_') |
| 1485 | if rhs is ast.SelectorExpr { |
| 1486 | selector_typ := g.decl_selector_rhs_type(rhs) |
| 1487 | if selector_typ != '' { |
| 1488 | typ = selector_typ |
| 1489 | } |
| 1490 | } |
| 1491 | if (typ == '' || typ == 'int' || typ == 'int_literal') && name.starts_with('_defer_t') |
| 1492 | && g.cur_fn_ret_type != '' && g.cur_fn_ret_type != 'void' |
| 1493 | && (rhs_is_none || rhs_is_option_zero) { |
| 1494 | typ = g.cur_fn_ret_type |
| 1495 | } |
| 1496 | can_emit_none := typ.starts_with('_option_') || typ in ['IError', 'builtin__IError'] |
| 1497 | || is_type_name_pointer_like(typ) || typ in ['void*', 'voidptr', 'byteptr', 'charptr'] |
| 1498 | if name != '' && (rhs_is_none || rhs_is_option_zero) && can_emit_none { |
| 1499 | g.sb.write_string('${typ} ${decl_c_name} = ') |
| 1500 | g.gen_none_literal_for_type(typ) |
| 1501 | g.sb.writeln(';') |
| 1502 | g.remember_runtime_local_type(name, typ) |
| 1503 | return |
| 1504 | } |
| 1505 | if typ == '' || typ == 'void' { |
| 1506 | typ = 'int' |
| 1507 | } |
| 1508 | typ = g.normalize_builtin_qualified_c_type(typ) |
| 1509 | if name != '' && is_zero_number_expr(rhs) && g.c_type_needs_typed_zero_expr(typ) { |
| 1510 | g.sb.write_string('${storage_prefix}${typ} ${c_local_name(name)} = ${zero_value_for_type(typ)};') |
| 1511 | g.remember_runtime_local_type(name, typ) |
| 1512 | return |
| 1513 | } |
| 1514 | // Check if declaring an interface pointer initialized with a concrete type |
| 1515 | if typ.ends_with('*') && name != '' { |
| 1516 | decl_base := typ.trim_right('*') |
| 1517 | if g.is_interface_type(decl_base) { |
| 1518 | decl_rhs_type := g.get_expr_type(rhs) |
| 1519 | decl_rhs_base := decl_rhs_type.trim_right('*') |
| 1520 | if decl_rhs_base != '' && decl_rhs_base != 'int' && decl_rhs_base != decl_base |
| 1521 | && !g.is_interface_type(decl_rhs_base) { |
| 1522 | g.sb.write_string('${typ} ${decl_c_name} = ') |
| 1523 | if !g.gen_heap_interface_cast(decl_base, rhs) { |
| 1524 | g.expr(rhs) |
| 1525 | } |
| 1526 | g.sb.writeln(';') |
| 1527 | g.remember_runtime_local_type(name, typ) |
| 1528 | return |
| 1529 | } |
| 1530 | } |
| 1531 | } |
| 1532 | g.sb.write_string('${storage_prefix}${typ} ${c_local_name(name)} = ') |
| 1533 | if !g.gen_auto_deref_value_param_arg(typ, rhs) { |
| 1534 | g.expr_with_expected_init_type(rhs, typ) |
| 1535 | } |
| 1536 | g.sb.writeln(';') |
| 1537 | g.remember_runtime_local_type(name, typ) |
| 1538 | } else { |
| 1539 | // Assignment |
| 1540 | mut lhs_fixed_type := g.get_expr_type(lhs) |
| 1541 | if lhs_fixed_type == '' && lhs is ast.Ident { |
| 1542 | if local_type := g.get_local_var_c_type(lhs.name) { |
| 1543 | lhs_fixed_type = local_type |
| 1544 | } |
| 1545 | } |
| 1546 | // C has no string compound assignment; lower `s += x` to V's string concat helper. |
| 1547 | if node.op == .plus_assign && lhs_fixed_type == 'string' && g.get_expr_type(rhs) == 'string' { |
| 1548 | g.expr(lhs) |
| 1549 | g.sb.write_string(' = string__plus(') |
| 1550 | g.expr(lhs) |
| 1551 | g.sb.write_string(', ') |
| 1552 | g.expr(rhs) |
| 1553 | g.sb.writeln(');') |
| 1554 | return |
| 1555 | } |
| 1556 | if node.op == .assign && lhs is ast.Ident && lhs.name in g.cur_fn_returned_idents |
| 1557 | && rhs is ast.PrefixExpr && rhs.op == .amp && rhs.expr is ast.Ident { |
| 1558 | prefix_rhs := rhs as ast.PrefixExpr |
| 1559 | base_type := lhs_fixed_type.trim_right('*') |
| 1560 | if lhs_fixed_type.ends_with('*') && base_type != '' && base_type != 'void' { |
| 1561 | heap_name := '_heap_t${g.tmp_counter}' |
| 1562 | g.tmp_counter++ |
| 1563 | malloc_call := g.c_heap_malloc_call('sizeof(${base_type})') |
| 1564 | g.sb.write_string('${c_local_name(lhs.name)} = ({ ${base_type}* ${heap_name} = (${base_type}*)${malloc_call}; *${heap_name} = ') |
| 1565 | g.expr(prefix_rhs.expr) |
| 1566 | g.sb.writeln('; ${heap_name}; });') |
| 1567 | return |
| 1568 | } |
| 1569 | } |
| 1570 | if lhs_fixed_type.starts_with('Array_fixed_') && lhs !is ast.IndexExpr |
| 1571 | && rhs is ast.CallExpr { |
| 1572 | if call_ret := g.get_call_return_type(rhs.lhs, rhs.args) { |
| 1573 | if call_ret == lhs_fixed_type { |
| 1574 | wrapper_type := g.c_fn_return_type_from_v(lhs_fixed_type) |
| 1575 | g.write_indent() |
| 1576 | g.sb.write_string('{ ${wrapper_type} _tmp = ') |
| 1577 | g.expr(rhs) |
| 1578 | g.sb.write_string('; memcpy(') |
| 1579 | g.expr(lhs) |
| 1580 | g.sb.writeln(', _tmp.ret_arr, sizeof(${lhs_fixed_type})); }') |
| 1581 | return |
| 1582 | } |
| 1583 | } |
| 1584 | } |
| 1585 | if lhs_fixed_type.starts_with('Array_fixed_') && lhs !is ast.IndexExpr && node.op == .assign |
| 1586 | && rhs !is ast.CallExpr { |
| 1587 | g.write_indent() |
| 1588 | g.sb.write_string('memcpy(') |
| 1589 | g.expr(lhs) |
| 1590 | g.sb.write_string(', ') |
| 1591 | if rhs is ast.ArrayInitExpr { |
| 1592 | g.sb.write_string('((${lhs_fixed_type})') |
| 1593 | g.expr(rhs) |
| 1594 | g.sb.write_string(')') |
| 1595 | } else { |
| 1596 | g.expr(rhs) |
| 1597 | } |
| 1598 | g.sb.writeln(', sizeof(${lhs_fixed_type}));') |
| 1599 | return |
| 1600 | } |
| 1601 | if node.op == .left_shift_assign { |
| 1602 | is_array_append, elem_type := g.array_append_elem_type(lhs, rhs) |
| 1603 | if is_array_append { |
| 1604 | if g.expr_is_array_value(rhs) { |
| 1605 | rhs_tmp := '_arr_append_tmp_${g.tmp_counter}' |
| 1606 | g.tmp_counter++ |
| 1607 | arr_rhs_type := g.expr_array_runtime_type(rhs) |
| 1608 | g.write_indent() |
| 1609 | g.sb.write_string('${arr_rhs_type} ${rhs_tmp} = ') |
| 1610 | g.expr(rhs) |
| 1611 | g.sb.writeln(';') |
| 1612 | g.write_indent() |
| 1613 | g.sb.write_string('array__push_many((array*)') |
| 1614 | g.gen_array_append_target(lhs) |
| 1615 | g.sb.writeln(', ${rhs_tmp}.data, ${rhs_tmp}.len);') |
| 1616 | return |
| 1617 | } |
| 1618 | g.write_indent() |
| 1619 | g.sb.write_string('array__push((array*)') |
| 1620 | g.gen_array_append_target(lhs) |
| 1621 | g.sb.write_string(', ') |
| 1622 | // When pushing a concrete type into an interface array, wrap it |
| 1623 | if g.is_interface_type(elem_type) { |
| 1624 | g.sb.write_string('&(${elem_type}[1]){') |
| 1625 | if !g.gen_interface_cast(elem_type, rhs) { |
| 1626 | g.expr(rhs) |
| 1627 | } |
| 1628 | g.sb.write_string('}') |
| 1629 | } else { |
| 1630 | g.gen_array_push_elem_arg(rhs, elem_type) |
| 1631 | } |
| 1632 | g.sb.writeln(');') |
| 1633 | return |
| 1634 | } |
| 1635 | } |
| 1636 | if node.op == .assign && lhs is ast.Ident && g.get_local_var_c_type(lhs.name) == none |
| 1637 | && !g.is_module_ident(lhs.name) && !g.is_module_local_const_or_global(lhs.name) |
| 1638 | && lhs.name !in ['errno', 'stdin', 'stdout', 'stderr', 'environ'] { |
| 1639 | mut decl_type := g.get_expr_type(rhs) |
| 1640 | mut rhs_array_elem_type := g.infer_array_method_elem_type(rhs) |
| 1641 | if rhs_array_elem_type != '' && decl_type in ['void*', 'voidptr'] { |
| 1642 | decl_type = rhs_array_elem_type |
| 1643 | } |
| 1644 | if decl_type == '' || decl_type in ['int_literal', 'float_literal'] { |
| 1645 | decl_type = 'int' |
| 1646 | } |
| 1647 | if decl_type in ['void', 'void*', 'voidptr'] { |
| 1648 | decl_type = 'int' |
| 1649 | } |
| 1650 | decl_type = unmangle_c_ptr_type(decl_type) |
| 1651 | g.write_indent() |
| 1652 | g.sb.write_string('${decl_type} ${c_local_name(lhs.name)} = ') |
| 1653 | if rhs_array_elem_type != '' && decl_type !in ['void*', 'voidptr'] |
| 1654 | && !decl_type.ends_with('*') { |
| 1655 | g.sb.write_string('(*(${decl_type}*)') |
| 1656 | g.expr(rhs) |
| 1657 | g.sb.write_string(')') |
| 1658 | } else { |
| 1659 | g.expr(rhs) |
| 1660 | } |
| 1661 | g.sb.writeln(';') |
| 1662 | g.remember_runtime_local_type(lhs.name, decl_type) |
| 1663 | return |
| 1664 | } |
| 1665 | // Handle result/option .data field write: _t.data = val -> unwrapped value pointer = val |
| 1666 | if lhs is ast.SelectorExpr && lhs.rhs.name == 'data' { |
| 1667 | lhs_type := g.get_expr_type(lhs.lhs) |
| 1668 | is_or_tmp := lhs.lhs is ast.Ident && lhs.lhs.name.starts_with('_or_t') |
| 1669 | if lhs_type.starts_with('_result_') || lhs_type.starts_with('_option_') || is_or_tmp { |
| 1670 | base := if lhs_type.starts_with('_result_') { |
| 1671 | g.result_value_type(lhs_type) |
| 1672 | } else if lhs_type.starts_with('_option_') { |
| 1673 | option_value_type(lhs_type) |
| 1674 | } else { |
| 1675 | g.get_expr_type(rhs) |
| 1676 | } |
| 1677 | if base != '' && base != 'void' { |
| 1678 | g.write_indent() |
| 1679 | g.sb.write_string('(*(${base}*)(((u8*)(&') |
| 1680 | g.expr(lhs.lhs) |
| 1681 | g.sb.write_string('.err)) + sizeof(IError))) = ') |
| 1682 | g.expr(rhs) |
| 1683 | g.sb.writeln(';') |
| 1684 | return |
| 1685 | } |
| 1686 | g.write_indent() |
| 1687 | g.sb.write_string('(void)(') |
| 1688 | g.expr(rhs) |
| 1689 | g.sb.writeln(');') |
| 1690 | return |
| 1691 | } |
| 1692 | } |
| 1693 | if node.op == .assign && lhs is ast.SelectorExpr { |
| 1694 | if g.gen_sum_common_field_assign(lhs, rhs) { |
| 1695 | return |
| 1696 | } |
| 1697 | } |
| 1698 | if g.gen_overloaded_compound_assign(lhs, rhs, node.op) { |
| 1699 | return |
| 1700 | } |
| 1701 | if lhs is ast.SelectorExpr { |
| 1702 | if g.gen_plain_selector_assign(lhs, rhs, node.op) { |
| 1703 | return |
| 1704 | } |
| 1705 | } |
| 1706 | mut lhs_needs_deref := false |
| 1707 | // Only dereference for plain assignment, not compound assignments (+=, -=, etc.) |
| 1708 | // For compound assignments on pointers (ptr += x), we want pointer arithmetic. |
| 1709 | if node.op == .assign && lhs is ast.Ident { |
| 1710 | if lhs.name in g.cur_fn_mut_params { |
| 1711 | lhs_needs_deref = true |
| 1712 | } else if local_type := g.get_local_var_c_type(lhs.name) { |
| 1713 | if local_type.ends_with('*') { |
| 1714 | rhs_type := g.get_expr_type(rhs) |
| 1715 | // Only dereference if we're sure the RHS is not a pointer. |
| 1716 | // When rhs_type is '' or 'int' (unknown), skip deref for function |
| 1717 | // calls or unsafe blocks which may return pointers (e.g. malloc). |
| 1718 | rhs_is_ptr := rhs_type.ends_with('*') || rhs_type == 'voidptr' |
| 1719 | || rhs_type == 'void*' |
| 1720 | if !rhs_is_ptr && rhs_type != '' && rhs_type != 'int' { |
| 1721 | lhs_needs_deref = true |
| 1722 | } else if !rhs_is_ptr && rhs !is ast.CallExpr && rhs !is ast.UnsafeExpr { |
| 1723 | lhs_needs_deref = true |
| 1724 | } |
| 1725 | } |
| 1726 | } |
| 1727 | } |
| 1728 | if lhs_needs_deref { |
| 1729 | g.sb.write_string('*') |
| 1730 | } |
| 1731 | g.expr(lhs) |
| 1732 | op_str := match node.op { |
| 1733 | .assign { '=' } |
| 1734 | .plus_assign { '+=' } |
| 1735 | .minus_assign { '-=' } |
| 1736 | .mul_assign { '*=' } |
| 1737 | .div_assign { '/=' } |
| 1738 | .mod_assign { '%=' } |
| 1739 | .and_assign { '&=' } |
| 1740 | .or_assign { '|=' } |
| 1741 | .xor_assign { '^=' } |
| 1742 | .left_shift_assign { '<<=' } |
| 1743 | .right_shift_assign { '>>=' } |
| 1744 | else { '=' } |
| 1745 | } |
| 1746 | |
| 1747 | g.sb.write_string(' ${op_str} ') |
| 1748 | mut rhs_array_elem_type := g.infer_array_method_elem_type(rhs) |
| 1749 | mut assign_lhs_type := g.get_expr_type(lhs) |
| 1750 | if lhs is ast.Ident { |
| 1751 | if local_type := g.get_local_var_c_type(lhs.name) { |
| 1752 | assign_lhs_type = local_type |
| 1753 | } else { |
| 1754 | // For globals, use the declared type from global_var_types. |
| 1755 | // The env-based expr type may reflect the RHS type, not the LHS. |
| 1756 | mut global_name := lhs.name |
| 1757 | if global_name !in g.global_var_types && g.cur_module != '' |
| 1758 | && g.cur_module != 'main' && g.cur_module != 'builtin' { |
| 1759 | global_name = '${g.cur_module}__${lhs.name}' |
| 1760 | } |
| 1761 | if global_name in g.global_var_types { |
| 1762 | assign_lhs_type = g.global_var_types[global_name] |
| 1763 | } |
| 1764 | } |
| 1765 | } else if lhs is ast.SelectorExpr { |
| 1766 | if assign_lhs_type == '' || assign_lhs_type == 'int' { |
| 1767 | field_type := g.selector_field_type(lhs) |
| 1768 | if field_type != '' { |
| 1769 | assign_lhs_type = field_type |
| 1770 | } |
| 1771 | } |
| 1772 | } |
| 1773 | if lhs_needs_deref && assign_lhs_type.ends_with('*') { |
| 1774 | assign_lhs_type = assign_lhs_type[..assign_lhs_type.len - 1] |
| 1775 | } |
| 1776 | if node.op == .assign && is_none_like_expr(rhs) |
| 1777 | && g.gen_none_literal_for_type(assign_lhs_type) { |
| 1778 | g.sb.writeln(';') |
| 1779 | return |
| 1780 | } |
| 1781 | if node.op == .assign && g.gen_enum_shorthand_for_type(rhs, assign_lhs_type) { |
| 1782 | g.sb.writeln(';') |
| 1783 | return |
| 1784 | } |
| 1785 | // When RHS is an array method (first/last/pop/pop_left), the call emission |
| 1786 | // in fn.v already wraps with (*(elem_type*)call(...)). Skip the outer |
| 1787 | // assign-level cast to avoid double dereference. |
| 1788 | mut call_already_casts := false |
| 1789 | if rhs_array_elem_type != '' { |
| 1790 | if rhs is ast.CallExpr { |
| 1791 | if rhs.lhs is ast.Ident |
| 1792 | && rhs.lhs.name in ['array__pop', 'array__pop_left', 'array__first', 'array__last'] |
| 1793 | && rhs.args.len > 0 { |
| 1794 | call_elem := g.infer_array_elem_type_from_expr(rhs.args[0]) |
| 1795 | if call_elem != '' { |
| 1796 | call_already_casts = true |
| 1797 | } |
| 1798 | } |
| 1799 | } |
| 1800 | } |
| 1801 | if rhs_array_elem_type != '' && assign_lhs_type !in ['', 'void*', 'voidptr'] |
| 1802 | && !assign_lhs_type.ends_with('*') && !call_already_casts { |
| 1803 | g.sb.write_string('(*(${assign_lhs_type}*)') |
| 1804 | g.expr(rhs) |
| 1805 | g.sb.write_string(')') |
| 1806 | } else if node.op == .assign && assign_lhs_type != '' && assign_lhs_type.ends_with('*') { |
| 1807 | // Pointer assignment: check if LHS is a pointer to an interface type |
| 1808 | // and RHS is a pointer to a concrete type (e.g., Logger* = new_thread_safe_log()) |
| 1809 | rhs_type := g.get_expr_type(rhs) |
| 1810 | lhs_base := assign_lhs_type.trim_right('*') |
| 1811 | rhs_base2 := rhs_type.trim_right('*') |
| 1812 | if g.is_interface_type(lhs_base) && rhs_base2 != '' && rhs_base2 != 'int' |
| 1813 | && rhs_base2 != lhs_base && !g.is_interface_type(rhs_base2) |
| 1814 | && !lhs_base.starts_with('Array_') && lhs_base != 'array' |
| 1815 | && !lhs_base.starts_with('Map_') && lhs_base != 'map' { |
| 1816 | if !g.gen_heap_interface_cast(lhs_base, rhs) { |
| 1817 | g.expr(rhs) |
| 1818 | } |
| 1819 | } else { |
| 1820 | g.expr(rhs) |
| 1821 | } |
| 1822 | } else if node.op == .assign && assign_lhs_type != '' && !assign_lhs_type.ends_with('*') { |
| 1823 | rhs_type := g.get_expr_type(rhs) |
| 1824 | lhs_base := assign_lhs_type.trim_right('*') |
| 1825 | rhs_base2 := rhs_type.trim_right('*') |
| 1826 | rhs_casts_to_lhs := rhs is ast.CastExpr && g.expr_type_to_c(rhs.typ) == assign_lhs_type |
| 1827 | // Auto-wrap raw value into Option/Result when LHS is Option/Result and RHS is not. |
| 1828 | if assign_lhs_type.starts_with('_option_') && !rhs_casts_to_lhs |
| 1829 | && !rhs_type.starts_with('_option_') && rhs_type !in ['', 'int', 'void'] |
| 1830 | && !is_none_like_expr(rhs) { |
| 1831 | value_type := option_value_type(assign_lhs_type) |
| 1832 | if value_type != '' && value_type != 'void' { |
| 1833 | g.sb.write_string('({ ${assign_lhs_type} _opt = (${assign_lhs_type}){ .state = 2 }; ${value_type} _val = ') |
| 1834 | g.expr(rhs) |
| 1835 | g.sb.write_string('; _option_ok(&_val, (_option*)&_opt, sizeof(_val)); _opt; })') |
| 1836 | g.sb.writeln(';') |
| 1837 | return |
| 1838 | } |
| 1839 | } |
| 1840 | if assign_lhs_type.starts_with('_result_') && !rhs_casts_to_lhs |
| 1841 | && !rhs_type.starts_with('_result_') && rhs_type !in ['', 'int', 'void'] { |
| 1842 | value_type := g.result_value_type(assign_lhs_type) |
| 1843 | if value_type != '' && value_type != 'void' { |
| 1844 | g.sb.write_string('({ ${assign_lhs_type} _res = (${assign_lhs_type}){0}; ${value_type} _val = ') |
| 1845 | g.expr(rhs) |
| 1846 | g.sb.write_string('; _result_ok(&_val, (_result*)&_res, sizeof(_val)); _res; })') |
| 1847 | g.sb.writeln(';') |
| 1848 | return |
| 1849 | } |
| 1850 | } |
| 1851 | if lhs_base in g.sum_type_variants && rhs_base2 != lhs_base && rhs_base2 != 'void' { |
| 1852 | g.gen_type_cast_expr(lhs_base, rhs) |
| 1853 | } else if g.is_interface_type(lhs_base) && rhs_base2 != '' && rhs_base2 != 'int' |
| 1854 | && rhs_base2 != lhs_base && !g.is_interface_type(rhs_base2) |
| 1855 | && !lhs_base.starts_with('Array_') && lhs_base != 'array' |
| 1856 | && !lhs_base.starts_with('Map_') && lhs_base != 'map' { |
| 1857 | if g.gen_interface_cast(lhs_base, rhs) { |
| 1858 | } else { |
| 1859 | g.expr(rhs) |
| 1860 | } |
| 1861 | } else if rhs_type.ends_with('*') { |
| 1862 | rhs_base := rhs_type.trim_right('*') |
| 1863 | if rhs_base == assign_lhs_type |
| 1864 | || short_type_name(rhs_base) == short_type_name(assign_lhs_type) { |
| 1865 | g.sb.write_string('(*') |
| 1866 | g.expr(rhs) |
| 1867 | g.sb.write_string(')') |
| 1868 | } else { |
| 1869 | g.expr(rhs) |
| 1870 | } |
| 1871 | } else { |
| 1872 | g.expr(rhs) |
| 1873 | } |
| 1874 | } else { |
| 1875 | g.expr(rhs) |
| 1876 | } |
| 1877 | g.sb.writeln(';') |
| 1878 | } |
| 1879 | } |
| 1880 | |
| 1881 | fn (mut g Gen) gen_plain_selector_assign(lhs ast.SelectorExpr, rhs ast.Expr, op token.Token) bool { |
| 1882 | if op != .assign || lhs.rhs.name == 'data' { |
| 1883 | return false |
| 1884 | } |
| 1885 | local_type := g.local_var_c_type_for_expr(lhs.lhs) or { return false } |
| 1886 | lhs_struct := strip_pointer_type_name(local_type) |
| 1887 | if lhs_struct == '' { |
| 1888 | return false |
| 1889 | } |
| 1890 | field_type := g.lookup_struct_field_type_by_name(lhs_struct, lhs.rhs.name) or { return false } |
| 1891 | if field_type.starts_with('Array_fixed_') || field_type.starts_with('_option_') |
| 1892 | || field_type.starts_with('_result_') || field_type in g.sum_type_variants |
| 1893 | || g.is_interface_type(field_type.trim_right('*')) { |
| 1894 | return false |
| 1895 | } |
| 1896 | g.write_indent() |
| 1897 | g.gen_selector_lvalue(lhs, local_type, lhs_struct) |
| 1898 | g.sb.write_string(' = ') |
| 1899 | if !g.gen_enum_shorthand_for_type(rhs, field_type) { |
| 1900 | g.expr(rhs) |
| 1901 | } |
| 1902 | g.sb.writeln(';') |
| 1903 | return true |
| 1904 | } |
| 1905 | |
| 1906 | fn (mut g Gen) gen_selector_lvalue(sel ast.SelectorExpr, local_type string, lhs_struct string) { |
| 1907 | mut use_ptr := g.selector_use_ptr(sel.lhs) |
| 1908 | if sel.lhs is ast.Ident && sel.lhs.name in g.cur_fn_mut_params { |
| 1909 | use_ptr = true |
| 1910 | } else { |
| 1911 | use_ptr = local_type.ends_with('*') || local_type == 'chan' |
| 1912 | } |
| 1913 | owner := g.embedded_owner_for(lhs_struct, sel.rhs.name) |
| 1914 | field_name := escape_c_keyword(sel.rhs.name) |
| 1915 | selector := if use_ptr { '->' } else { '.' } |
| 1916 | g.expr(sel.lhs) |
| 1917 | if owner != '' { |
| 1918 | g.sb.write_string('${selector}${escape_c_keyword(owner)}.${field_name}') |
| 1919 | } else { |
| 1920 | g.sb.write_string('${selector}${field_name}') |
| 1921 | } |
| 1922 | } |
| 1923 | |
| 1924 | fn (mut g Gen) gen_decl_assign_from_local_index(name string, storage_prefix string, decl_c_name string, rhs ast.IndexExpr) bool { |
| 1925 | if name == '' || rhs.expr is ast.RangeExpr || rhs.lhs !is ast.Ident { |
| 1926 | return false |
| 1927 | } |
| 1928 | lhs_ident := rhs.lhs as ast.Ident |
| 1929 | local_type := g.get_local_var_c_type(lhs_ident.name) or { return false } |
| 1930 | local_c_type := local_type.trim_space() |
| 1931 | if local_c_type == '' || local_c_type.ends_with('*') { |
| 1932 | return false |
| 1933 | } |
| 1934 | elem_type := g.array_alias_elem_type_from_c_type(local_c_type) |
| 1935 | if elem_type == '' || elem_type == 'array' || elem_type == 'void' { |
| 1936 | return false |
| 1937 | } |
| 1938 | g.write_indent() |
| 1939 | g.sb.write_string('${storage_prefix}${elem_type} ${decl_c_name} = ') |
| 1940 | if local_c_type.starts_with('Array_fixed_') { |
| 1941 | g.expr(rhs.lhs) |
| 1942 | g.sb.write_string('[') |
| 1943 | } else { |
| 1944 | g.sb.write_string('((${elem_type}*)') |
| 1945 | g.expr(rhs.lhs) |
| 1946 | g.sb.write_string('.data)[') |
| 1947 | } |
| 1948 | g.gen_index_expr_value(rhs.expr) |
| 1949 | g.sb.writeln('];') |
| 1950 | g.remember_runtime_local_type(name, elem_type) |
| 1951 | return true |
| 1952 | } |
| 1953 | |
| 1954 | // resolve_struct_field_fn_return looks up a field by name in a struct type, |
| 1955 | // checks if it's a function pointer (possibly wrapped in an alias), and |
| 1956 | // returns the C type name of the function's return type. |
| 1957 | fn (mut g Gen) resolve_struct_field_fn_return(st types.Struct, field_name string) string { |
| 1958 | for field in st.fields { |
| 1959 | if field.name == field_name { |
| 1960 | ft := field.typ |
| 1961 | if ft is types.FnType { |
| 1962 | if rt := ft.get_return_type() { |
| 1963 | return g.fn_return_type_to_c(rt) |
| 1964 | } |
| 1965 | } else if ft is types.Alias { |
| 1966 | if ft.base_type is types.FnType { |
| 1967 | fn_t := ft.base_type as types.FnType |
| 1968 | if rt := fn_t.get_return_type() { |
| 1969 | return g.fn_return_type_to_c(rt) |
| 1970 | } |
| 1971 | } |
| 1972 | } |
| 1973 | break |
| 1974 | } |
| 1975 | } |
| 1976 | return '' |
| 1977 | } |
| 1978 | |