nomen-lang 0.0.12 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +1141 -562
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -2,8 +2,12 @@
2
2
  import { execFileSync, execSync } from "node:child_process";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
- import chokidar from "chokidar";
6
5
  import { fileURLToPath } from "node:url";
6
+ //#region node_modules/chokidar/index.mjs
7
+ var chokidar_default = { watch() {
8
+ throw new Error("chokidar stub: watch mode unavailable");
9
+ } };
10
+ //#endregion
7
11
  //#region ../src/nodes/BaseNode.ts
8
12
  /**
9
13
  * The base node type which all nodes extend
@@ -480,30 +484,6 @@ function resolve_mono_type(type, table) {
480
484
  return resolved;
481
485
  }
482
486
  //#endregion
483
- //#region ../src/built_in_types.ts
484
- const built_in_types = [
485
- "bool",
486
- "int",
487
- "uint",
488
- "int8",
489
- "uint8",
490
- "int16",
491
- "uint16",
492
- "int32",
493
- "uint32",
494
- "int64",
495
- "uint64",
496
- "float",
497
- "ufloat",
498
- "float32",
499
- "ufloat32",
500
- "float64",
501
- "ufloat64",
502
- "char",
503
- "string",
504
- "func"
505
- ];
506
- //#endregion
507
487
  //#region ../src/nodes/BranchNode.ts
508
488
  /**
509
489
  * A branch such as the result of an IfElseNode, or the arm of a SwitchNode
@@ -639,6 +619,30 @@ var FunctionNode = class extends BaseNode {
639
619
  }
640
620
  };
641
621
  //#endregion
622
+ //#region ../src/built_in_types.ts
623
+ const built_in_types = [
624
+ "bool",
625
+ "int",
626
+ "uint",
627
+ "int8",
628
+ "uint8",
629
+ "int16",
630
+ "uint16",
631
+ "int32",
632
+ "uint32",
633
+ "int64",
634
+ "uint64",
635
+ "float",
636
+ "ufloat",
637
+ "float32",
638
+ "ufloat32",
639
+ "float64",
640
+ "ufloat64",
641
+ "char",
642
+ "string",
643
+ "func"
644
+ ];
645
+ //#endregion
642
646
  //#region ../src/nodes/DeclarationNode.ts
643
647
  var DeclarationNode = class extends BaseNode {
644
648
  visibility;
@@ -736,6 +740,82 @@ var StructNode = class extends BaseNode {
736
740
  }
737
741
  };
738
742
  //#endregion
743
+ //#region ../src/build_common/scan_self_string_writes.ts
744
+ /**
745
+ * The plain (owned, non-ref, non-array) `string` fields of a VALUE struct that
746
+ * a method may overwrite through `self` — direct `self.<field> = ...` writes
747
+ * plus writes made by same-struct methods the method calls on `self`
748
+ * (transitively). Nested function/struct declarations inside the body are
749
+ * boundaries and are not descended into (they are separate functions).
750
+ */
751
+ function scan_self_string_field_writes(struct, method) {
752
+ const string_fields = new Set(struct.fields.filter((f) => f.type.name === "string" && !f.type.is_ref && !f.type.is_array).map((f) => f.name));
753
+ const written = /* @__PURE__ */ new Set();
754
+ if (!string_fields.size) return written;
755
+ const visited = /* @__PURE__ */ new Set();
756
+ const scan_method = (func) => {
757
+ if (visited.has(func.name)) return;
758
+ visited.add(func.name);
759
+ walk$2(func.statements ?? [], (n) => {
760
+ if (n.node_type === "assign") {
761
+ const lhs = n.left_value;
762
+ if (lhs?.node_type !== "access") return;
763
+ const access = lhs;
764
+ if (access.access.node_type !== "access_field") return;
765
+ const target = access.target;
766
+ const field = access.access.name ?? "";
767
+ if (target?.node_type === "value" && target.value === "self") {
768
+ if (string_fields.has(field)) written.add(field);
769
+ }
770
+ } else if (n.node_type === "access") {
771
+ const access = n;
772
+ if (access.access.node_type !== "access_func") return;
773
+ const target = access.target;
774
+ if (target?.node_type === "value" && target.value === "self") {
775
+ const callee = struct.functions.find((f) => f.name === access.access.name);
776
+ if (callee) scan_method(callee);
777
+ }
778
+ }
779
+ });
780
+ };
781
+ scan_method(method);
782
+ return written;
783
+ }
784
+ /**
785
+ * Drop a receiver's heap_string_fields records for the fields a value-struct
786
+ * method may have overwritten through `self`. The method's writes go through
787
+ * to the caller's storage, and the method cannot know whether the displaced
788
+ * values were heap-owned — that knowledge lives in the CALLER's records. A
789
+ * surviving record could free a non-heap value at scope exit (invalid free),
790
+ * so the records are dropped WITHOUT emitting frees. Conservative: a heap
791
+ * value the method displaced or wrote leaks instead of being freed — never a
792
+ * double-free.
793
+ */
794
+ function drop_self_written_string_field_records(status, receiver_name, fields) {
795
+ if (!status.heap_string_fields?.size || !fields.size) return;
796
+ for (const field of fields) status.heap_string_fields.delete(`${receiver_name}.${field}`);
797
+ }
798
+ /** Visit every AST node reachable from `value` — through arrays AND
799
+ * single-node properties (an `if` node's branch blocks are node objects, not
800
+ * statement arrays) — skipping `parent`/`scope` back-references and NOT
801
+ * descending INTO nested `func`/`struct`/`trait` declarations (a nested
802
+ * function's body is a separate function, not part of this one's writes). */
803
+ function walk$2(value, cb) {
804
+ if (!value || typeof value !== "object") return;
805
+ if (Array.isArray(value)) {
806
+ for (const item of value) walk$2(item, cb);
807
+ return;
808
+ }
809
+ const n = value;
810
+ const is_boundary = n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait";
811
+ if (typeof n.node_type === "string") cb(n);
812
+ if (is_boundary) return;
813
+ for (const key of Object.keys(value)) {
814
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
815
+ walk$2(value[key], cb);
816
+ }
817
+ }
818
+ //#endregion
739
819
  //#region ../src/check/utils/function_overload.ts
740
820
  function find_function_by_params(functions, name, arg_types) {
741
821
  const candidates = functions.filter((f) => f.name === name);
@@ -1368,6 +1448,50 @@ function array_struct_name(type, status) {
1368
1448
  return status.structs.find((s) => s.name === mono && !s.is_generic) ? mono : void 0;
1369
1449
  }
1370
1450
  //#endregion
1451
+ //#region ../src/build_common/scan_moved_param_consumed.ts
1452
+ /**
1453
+ * Whether a `mov` class parameter's ownership escapes the function body —
1454
+ * i.e. it is passed (as an argument or receiver) into some call/constructor
1455
+ * whose result may outlive the function (stored into a returned
1456
+ * container/struct), or it is a bare value used as an argument. In those cases
1457
+ * the callee must NOT destroy it at exit (it would double-free / leave a
1458
+ * dangling pointer in the escaping value). A bare reference that is only read
1459
+ * (e.g. field access `x.value` or interpolation) does NOT consume it.
1460
+ *
1461
+ * Shared by the C backend's function epilogue and the aarch64 function /
1462
+ * method mov-param reclaims so both agree on when a mov'd param is reclaimed.
1463
+ */
1464
+ function moved_param_is_consumed(root, name) {
1465
+ let consumed = false;
1466
+ const refs_name = (n) => !!n && n.node_type === "value" && n.value === name;
1467
+ const walk = (n) => {
1468
+ if (!n || typeof n !== "object" || consumed) return;
1469
+ const node = n;
1470
+ if (node.node_type === "func_call") {
1471
+ for (const p of node.params ?? []) if (refs_name(p)) consumed = true;
1472
+ }
1473
+ if (node.node_type === "access") {
1474
+ const access = node.access;
1475
+ if (access?.node_type === "access_func" && refs_name(node.target)) consumed = true;
1476
+ for (const p of access?.params ?? []) if (refs_name(p)) consumed = true;
1477
+ }
1478
+ if (node.node_type === "array") {
1479
+ for (const v of node.values ?? []) if (refs_name(v)) consumed = true;
1480
+ }
1481
+ if (node.node_type === "return" && refs_name(node.value)) consumed = true;
1482
+ if (node.node_type === "assign" && refs_name(node.right_value)) consumed = true;
1483
+ if (node.node_type === "declare" && refs_name(node.value)) consumed = true;
1484
+ for (const key of Object.keys(node)) {
1485
+ if (key === "node_type") continue;
1486
+ const v = node[key];
1487
+ if (Array.isArray(v)) for (const item of v) walk(item);
1488
+ else if (v && typeof v === "object") walk(v);
1489
+ }
1490
+ };
1491
+ for (const stmt of root.statements ?? []) walk(stmt);
1492
+ return consumed;
1493
+ }
1494
+ //#endregion
1371
1495
  //#region ../src/build_aarch64/utils/aarch64_size.ts
1372
1496
  function aarch64_size(type) {
1373
1497
  switch (type) {
@@ -1684,8 +1808,10 @@ function emit_var_address(status, reg, name) {
1684
1808
  if (offset !== void 0) status.code += `str ${alloc_reg}, [x29, #${offset}]\n`;
1685
1809
  }
1686
1810
  const offset = status.stack_offsets?.get(name);
1687
- if (offset !== void 0) status.code += `add ${reg}, x29, #${offset}\n`;
1688
- else {
1811
+ if (offset !== void 0) {
1812
+ if (status.function_struct_param_slots?.has(name)) status.code += `ldr ${reg}, [x29, #${offset}]\n`;
1813
+ else status.code += `add ${reg}, x29, #${offset}\n`;
1814
+ } else {
1689
1815
  const param_reg = status.function_param_regs?.get(name);
1690
1816
  if (param_reg) status.code += `mov ${reg}, ${param_reg}\n`;
1691
1817
  else status.code += `adr ${reg}, ${name}\n`;
@@ -1752,6 +1878,78 @@ function emit_var_store(status, reg, name, size) {
1752
1878
  }
1753
1879
  //#endregion
1754
1880
  //#region ../src/build_aarch64/utils/auto_destroy.ts
1881
+ /**
1882
+ * Whether a declaration's initializer is a non-`mov` FIELD ACCESS — a shallow
1883
+ * struct borrow (`diff.changes`, the checker-hoisted `_param_N` temp for a
1884
+ * struct call arg). The struct bytes are copied but any embedded buffer data
1885
+ * belongs to the owner, so the declaration must NOT be destroyed at scope
1886
+ * exit / return (mirrors the C backend's is_destructured_field_access).
1887
+ */
1888
+ function is_field_struct_borrow(decl) {
1889
+ if (!decl.value || typeof decl.value !== "object") return false;
1890
+ const value = decl.value;
1891
+ return value.node_type === "access" && value.access.node_type === "access_field" && !value.is_moved;
1892
+ }
1893
+ /**
1894
+ * Record that a VALUE-struct local's `string` field now holds a heap-owned
1895
+ * value ("var.field"). See BuildStatus.heap_string_fields.
1896
+ */
1897
+ function record_heap_string_field(status, var_name, field) {
1898
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
1899
+ status.heap_string_fields.add(`${var_name}.${field}`);
1900
+ }
1901
+ /**
1902
+ * Drop a local's heap-string-field records — used when the struct's bytes
1903
+ * (and thus its string pointers) transfer to the caller, e.g. `return u`.
1904
+ */
1905
+ function clear_heap_string_fields(status, var_name) {
1906
+ if (!status.heap_string_fields) return;
1907
+ const prefix = `${var_name}.`;
1908
+ for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) status.heap_string_fields.delete(key);
1909
+ }
1910
+ /**
1911
+ * Free every heap-owned string field recorded for `decl_name` (a VALUE-struct
1912
+ * local) and drop the records. Class locals are never recorded — their string
1913
+ * fields are unconditionally heap and freed by the destroy path. Called from
1914
+ * emit_destroy_for_decl and directly from cleanup loops that skip moved
1915
+ * declarations before reaching it.
1916
+ */
1917
+ function release_heap_string_fields(status, decl_name, decl_type_name) {
1918
+ if (!status.heap_string_fields?.size) return;
1919
+ const prefix = `${decl_name}.`;
1920
+ const fields = Array.from(status.heap_string_fields).filter((k) => k.startsWith(prefix)).map((k) => k.slice(prefix.length));
1921
+ if (!fields.length) return;
1922
+ for (const field of fields) {
1923
+ const offset = get_field_offset(decl_type_name, field, status);
1924
+ emit_var_address(status, "x0", decl_name);
1925
+ status.code += `ldr x0, [x0, #${offset}]\n`;
1926
+ emit_free(status);
1927
+ status.heap_string_fields.delete(`${decl_name}.${field}`);
1928
+ }
1929
+ }
1930
+ /**
1931
+ * Swap in a fresh scoped_declarations frame for a nested scope (if/while/
1932
+ * for/switch/match body), pushing the enclosing array onto
1933
+ * outer_scope_declarations so return-path cleanup can still reach it.
1934
+ * Pair with exit_scope_frame.
1935
+ */
1936
+ function enter_scope_frame(status) {
1937
+ const old = status.scoped_declarations ?? [];
1938
+ if (!status.outer_scope_declarations) status.outer_scope_declarations = [];
1939
+ status.outer_scope_declarations.push(old);
1940
+ status.scoped_declarations = [];
1941
+ return old;
1942
+ }
1943
+ /** Restore the enclosing scoped_declarations frame (enter_scope_frame's pair). */
1944
+ function exit_scope_frame(status, old) {
1945
+ status.outer_scope_declarations?.pop();
1946
+ status.scoped_declarations = old;
1947
+ }
1948
+ /** Every declaration frame a `return` must clean: enclosing scopes first,
1949
+ * the current (innermost) frame last — matching fall-through cleanup order. */
1950
+ function all_scope_frames(status) {
1951
+ return [...status.outer_scope_declarations ?? [], status.scoped_declarations ?? []];
1952
+ }
1755
1953
  function mark_heap_string(status, name) {
1756
1954
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
1757
1955
  status.heap_strings.add(name);
@@ -1898,7 +2096,7 @@ function emit_field_destroys_from_slot(status, struct_type, base_offset) {
1898
2096
  const field_size = get_type_size(field.type, status);
1899
2097
  emit_nested_field_destroys_from_slot(status, field_struct, base_offset + offset);
1900
2098
  offset += field_size;
1901
- } else if (field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !struct_type.is_class) {
2099
+ } else if (field.type.name === "string" && !field.type.is_array && !field.type.is_ref) {
1902
2100
  status.code += `ldr x0, [x29, #${base_offset}]\n`;
1903
2101
  status.code += `ldr x0, [x0, #${offset}]\n`;
1904
2102
  emit_free(status);
@@ -1949,7 +2147,9 @@ function is_struct_type$4(type_name, status) {
1949
2147
  return status.structs.find((s) => s.name === type_name && !s.is_simple_type);
1950
2148
  }
1951
2149
  function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, type_args, is_nullable) {
1952
- if ((status.moved ?? /* @__PURE__ */ new Set()).has(decl_name)) return;
2150
+ const moved = status.moved ?? /* @__PURE__ */ new Set();
2151
+ release_heap_string_fields(status, decl_name, decl_type_name);
2152
+ if (moved.has(decl_name)) return;
1953
2153
  if (status.heap_strings?.has(decl_name)) {
1954
2154
  if (addr_offset !== void 0) status.code += `add x0, x0, #${addr_offset}\n`;
1955
2155
  else emit_var_load(status, "x0", decl_name, 8);
@@ -1966,7 +2166,7 @@ function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, t
1966
2166
  skip_label = `.Lskip_nd_${status.label_counter = (status.label_counter ?? 0) + 1}`;
1967
2167
  status.code += `cbz x0, ${skip_label}\n`;
1968
2168
  }
1969
- if (has_destroy(struct_type)) {
2169
+ if (has_destroy(struct_type) || struct_type.is_class) {
1970
2170
  if (struct_type.is_class) {
1971
2171
  if (addr_offset !== void 0) status.code += `ldr x0, [x0, #${addr_offset}]\n`;
1972
2172
  else emit_var_load(status, "x0", decl_name, 8);
@@ -1980,7 +2180,6 @@ function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, t
1980
2180
  else emit_var_load(status, "x0", decl_name, 8);
1981
2181
  emit_free(status);
1982
2182
  }
1983
- emit_field_destroys(status, struct_type, decl_name, addr_offset, true);
1984
2183
  } else if (struct_needs_destroy(struct_type, status)) emit_field_destroys(status, struct_type, decl_name, addr_offset, void 0, false);
1985
2184
  if (skip_label) status.code += `${skip_label}:\n`;
1986
2185
  }
@@ -2016,7 +2215,7 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
2016
2215
  }
2017
2216
  const field_size = get_type_size(field.type, status);
2018
2217
  offset += field_size;
2019
- } else if (free_strings && field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !struct_type.is_class) {
2218
+ } else if (free_strings && field.type.name === "string" && !field.type.is_array && !field.type.is_ref) {
2020
2219
  const actual_offset = base_offset !== void 0 ? base_offset + offset : offset;
2021
2220
  if (decl_name) emit_base_ptr(status, decl_name, is_class_parent);
2022
2221
  status.code += `ldr x0, [x0, #${actual_offset}]\n`;
@@ -2101,6 +2300,7 @@ function emit_destroy_for_scope(status, declarations_before) {
2101
2300
  if (current_scope?.heap_slots.length) {
2102
2301
  for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
2103
2302
  const decl = status.scoped_declarations[i];
2303
+ release_heap_string_fields(status, decl.name, decl.type.name);
2104
2304
  if (moved.has(decl.name)) continue;
2105
2305
  if (status.heap_string_arrays?.has(decl.name)) {
2106
2306
  const len = status.heap_string_arrays.get(decl.name);
@@ -2138,6 +2338,7 @@ function emit_destroy_for_scope(status, declarations_before) {
2138
2338
  emit_free(status);
2139
2339
  continue;
2140
2340
  }
2341
+ if (is_field_struct_borrow(decl)) continue;
2141
2342
  const resolved_decl = resolve_decl_struct(decl, status);
2142
2343
  if (!resolved_decl) continue;
2143
2344
  const struct_type = resolved_decl.struct_type;
@@ -2152,6 +2353,7 @@ function emit_destroy_for_scope(status, declarations_before) {
2152
2353
  }
2153
2354
  for (let i = declarations_before; i < status.scoped_declarations.length; i++) {
2154
2355
  const decl = status.scoped_declarations[i];
2356
+ release_heap_string_fields(status, decl.name, decl.type.name);
2155
2357
  if (moved.has(decl.name)) continue;
2156
2358
  if (status.heap_string_arrays?.has(decl.name)) {
2157
2359
  const len = status.heap_string_arrays.get(decl.name);
@@ -2194,6 +2396,7 @@ function emit_destroy_for_scope(status, declarations_before) {
2194
2396
  emit_free(status);
2195
2397
  continue;
2196
2398
  }
2399
+ if (is_field_struct_borrow(decl)) continue;
2197
2400
  const resolved_decl = resolve_decl_struct(decl, status);
2198
2401
  if (!resolved_decl) continue;
2199
2402
  const struct_type = resolved_decl.struct_type;
@@ -2259,13 +2462,13 @@ function consolidate_temp_anchors$1(status, call_node, result_type_name) {
2259
2462
  status.moved.add(pname);
2260
2463
  }
2261
2464
  }
2262
- function mark_moved_if_struct(value, status) {
2465
+ function mark_moved_if_struct(value, status, opts) {
2263
2466
  if (value?.node_type !== "value") return;
2264
2467
  const var_name = value.value;
2265
2468
  let var_type = value.type;
2266
- if (!var_type?.name) var_type = status.scoped_declarations?.find((d) => d.name === var_name)?.type;
2469
+ if (!var_type?.name) var_type = all_scope_frames(status).flat().find((d) => d.name === var_name)?.type;
2267
2470
  if (!var_type) return;
2268
- const is_local = status.scoped_declarations.some((d) => d.name === var_name);
2471
+ const is_local = all_scope_frames(status).some((frame) => frame.some((d) => d.name === var_name));
2269
2472
  const has_anchor = find_anchor_slot(status, var_name) !== void 0;
2270
2473
  const is_class_param = !!status.moved_class_params?.has(var_name) || !!status.function_param_regs?.has(var_name) && is_struct_type$4(var_type.name, status);
2271
2474
  if (!is_local && !has_anchor && !is_class_param) return;
@@ -2273,7 +2476,7 @@ function mark_moved_if_struct(value, status) {
2273
2476
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
2274
2477
  status.moved.add(var_name);
2275
2478
  }
2276
- if (status.heap_strings?.has(var_name)) {
2479
+ if (opts?.for_return && status.heap_strings?.has(var_name)) {
2277
2480
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
2278
2481
  status.moved.add(var_name);
2279
2482
  }
@@ -2318,10 +2521,10 @@ var SwitchNode = class extends BaseNode {
2318
2521
  //#region ../src/build_aarch64/utils/scan_force_heap_strings.ts
2319
2522
  function scan_force_heap_strings(statements) {
2320
2523
  const result = /* @__PURE__ */ new Set();
2321
- walk(statements, result);
2524
+ walk$1(statements, result);
2322
2525
  return result;
2323
2526
  }
2324
- function walk(statements, result) {
2527
+ function walk$1(statements, result) {
2325
2528
  if (!statements) return;
2326
2529
  for (const stmt of statements) visit(stmt, result);
2327
2530
  }
@@ -2335,18 +2538,18 @@ function visit(node, result) {
2335
2538
  }
2336
2539
  case "while":
2337
2540
  case "for":
2338
- walk(node.statements, result);
2541
+ walk$1(node.statements, result);
2339
2542
  break;
2340
2543
  case "if": {
2341
2544
  const n = node;
2342
- walk(n.if_branch?.statements, result);
2343
- walk(n.else_branch?.statements, result);
2545
+ walk$1(n.if_branch?.statements, result);
2546
+ walk$1(n.else_branch?.statements, result);
2344
2547
  break;
2345
2548
  }
2346
2549
  case "switch": {
2347
2550
  const n = node;
2348
- for (const c of n.cases) walk(c.branch?.statements, result);
2349
- walk(n.else_branch?.statements, result);
2551
+ for (const c of n.cases) walk$1(c.branch?.statements, result);
2552
+ walk$1(n.else_branch?.statements, result);
2350
2553
  break;
2351
2554
  }
2352
2555
  }
@@ -2594,11 +2797,13 @@ function build_function_node$1(node, status) {
2594
2797
  const old_function_array_params = status.function_array_params;
2595
2798
  const old_function_ref_params = status.function_ref_params;
2596
2799
  const old_ref_class_slots = status.ref_class_slots;
2800
+ const old_struct_param_slots = status.function_struct_param_slots;
2597
2801
  status.function_param_regs = /* @__PURE__ */ new Map();
2598
2802
  status.function_param_vars = /* @__PURE__ */ new Set();
2599
2803
  status.function_array_params = /* @__PURE__ */ new Set();
2600
2804
  status.function_ref_params = /* @__PURE__ */ new Set();
2601
2805
  status.ref_class_slots = /* @__PURE__ */ new Map();
2806
+ status.function_struct_param_slots = /* @__PURE__ */ new Set();
2602
2807
  const old_variadic_params_aarch64 = status.function_variadic_params;
2603
2808
  status.function_variadic_params = /* @__PURE__ */ new Set();
2604
2809
  status.moved_class_params = /* @__PURE__ */ new Map();
@@ -2637,6 +2842,9 @@ function build_function_node$1(node, status) {
2637
2842
  const size = is_ref ? 8 : aarch64_size(param.type.name);
2638
2843
  const offset = allocate_stack_space(status, size, is_ref ? 8 : size);
2639
2844
  status.stack_offsets.set(param.name, offset);
2845
+ if (!is_ref && !param.type.is_array && !param.is_variadic) {
2846
+ if (!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.traits.find((t) => t.name === param.type.name) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data)) status.function_struct_param_slots.add(param.name);
2847
+ }
2640
2848
  if (param_idx < 8) {
2641
2849
  const reg = param_regs[param_idx];
2642
2850
  if (size === 1) status.code += `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`;
@@ -2726,13 +2934,15 @@ function build_function_node$1(node, status) {
2726
2934
  const moved_set = status.moved;
2727
2935
  if (moved_param_save_slots.size > 0 && node.name !== "main") {
2728
2936
  const need_guard = return_is_class;
2937
+ const need_save = !!node.return_type?.name;
2729
2938
  let return_save;
2730
- if (need_guard) {
2939
+ if (need_guard || need_save) {
2731
2940
  return_save = allocate_stack_space(status, 8);
2732
2941
  status.code += `str x0, [x29, #${return_save}]\n`;
2733
2942
  }
2734
2943
  for (const [name, info] of moved_param_save_slots) {
2735
2944
  if (moved_set?.has(name)) continue;
2945
+ if (moved_param_is_consumed(node, name)) continue;
2736
2946
  if (need_guard) {
2737
2947
  status.code += `ldr x0, [x29, #${info.offset}]\n`;
2738
2948
  status.code += `ldr x1, [x29, #${return_save}]\n`;
@@ -2744,7 +2954,7 @@ function build_function_node$1(node, status) {
2744
2954
  emit_free(status);
2745
2955
  if (need_guard) status.code += `${keep_prefix}_${name}:\n`;
2746
2956
  }
2747
- if (need_guard) status.code += `ldr x0, [x29, #${return_save}]\n`;
2957
+ if (need_guard || need_save) status.code += `ldr x0, [x29, #${return_save}]\n`;
2748
2958
  }
2749
2959
  const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
2750
2960
  status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
@@ -2787,6 +2997,7 @@ function build_function_node$1(node, status) {
2787
2997
  status.function_array_params = old_function_array_params;
2788
2998
  status.function_ref_params = old_function_ref_params;
2789
2999
  status.ref_class_slots = old_ref_class_slots;
3000
+ status.function_struct_param_slots = old_struct_param_slots;
2790
3001
  status.function_variadic_params = old_variadic_params_aarch64;
2791
3002
  status.function_return_label = old_return_label;
2792
3003
  status.struct_return_buffer = void 0;
@@ -2965,6 +3176,30 @@ function is_mutable_param(name, status) {
2965
3176
  return !!(status.function_param_vars?.has(name) || status.function_ref_params?.has(name));
2966
3177
  }
2967
3178
  /**
3179
+ * Whether `obj.field = rhs` writes a plain `string` field of a struct or
3180
+ * class instance. CLASS string fields follow an always-heap-owned convention
3181
+ * (see the assignment branch that uses this): `_init` strdup's defaults and
3182
+ * assignments strdup non-heap RHS, so destroys can free them unconditionally.
3183
+ * VALUE-struct string fields keep per-assignment ownership tracking instead
3184
+ * (construction may leave rodata in them): non-heap RHS is strdup'd and the
3185
+ * field is recorded in heap_string_fields for release at scope exit.
3186
+ * `ref` string fields (borrows) are excluded either way.
3187
+ */
3188
+ function field_is_struct_string(target_type, field_type, status) {
3189
+ if (!target_type?.name || !field_type) return void 0;
3190
+ if (field_type.name !== "string" || field_type.is_ref || field_type.is_array) return void 0;
3191
+ const target_struct = status.structs.find((s) => s.name === target_type.name && !s.is_simple_type);
3192
+ if (!target_struct) return void 0;
3193
+ if (target_struct.is_class) return {
3194
+ name: field_type.name,
3195
+ target_is_class: true
3196
+ };
3197
+ return {
3198
+ name: field_type.name,
3199
+ target_is_class: false
3200
+ };
3201
+ }
3202
+ /**
2968
3203
  * Load a `ref T` parameter's caller-side pointer into `reg`. The pointer is the
2969
3204
  * 8-byte value held in the parameter's stack slot (or, rarely, a register
2970
3205
  * allocation). Only writes `reg` — safe to run after a RHS has been built into
@@ -3530,6 +3765,38 @@ function build_assignment_node$1(node, status) {
3530
3765
  status.code += `mov x2, x0\n`;
3531
3766
  status.code += `ldr x0, [sp], #16\n`;
3532
3767
  status.code += `str x2, [x0, #${offset}]\n`;
3768
+ } else if (field_is_struct_string(target_type, field_type, status) && !node.operator && (() => {
3769
+ const st = field_is_struct_string(target_type, field_type, status);
3770
+ const tv = access.target.node_type === "value" ? access.target.value : "";
3771
+ return st.target_is_class || tv !== "" && tv !== "self";
3772
+ })()) {
3773
+ const string_target = field_is_struct_string(target_type, field_type, status);
3774
+ const target_var = access.target.node_type === "value" ? access.target.value : "";
3775
+ const tracked_key = `${target_var}.${field_name}`;
3776
+ const is_class_target = string_target.target_is_class;
3777
+ const old_was_heap = is_class_target || !!status.heap_string_fields?.has(tracked_key);
3778
+ const offset = get_field_offset(target_type.name, field_name, status);
3779
+ get_base_address(access, status, "x0");
3780
+ status.code += `str x0, [sp, #-16]!\n`;
3781
+ status.last_result_is_heap = false;
3782
+ build_node$1(node.right_value, status);
3783
+ if (!status.code.endsWith("\n")) status.code += "\n";
3784
+ const rhs_is_heap = status.last_result_is_heap;
3785
+ if (is_class_target && !rhs_is_heap) emit_strdup(status);
3786
+ mark_moved_if_struct(node.right_value, status);
3787
+ status.code += `str x0, [sp, #-16]!\n`;
3788
+ if (old_was_heap) {
3789
+ status.code += `ldr x0, [sp, #16]\n`;
3790
+ status.code += `ldr x0, [x0, #${offset}]\n`;
3791
+ emit_free(status);
3792
+ }
3793
+ status.code += `ldr x2, [sp], #16\n`;
3794
+ status.code += `ldr x0, [sp], #16\n`;
3795
+ status.code += `str x2, [x0, #${offset}]\n`;
3796
+ if (!is_class_target && target_var && target_var !== "self") {
3797
+ if (rhs_is_heap) record_heap_string_field(status, target_var, field_name);
3798
+ else status.heap_string_fields?.delete(tracked_key);
3799
+ }
3533
3800
  } else if (field_is_struct && !node.operator) {
3534
3801
  const field_struct = status.structs.find((s) => s.name === field_type.name);
3535
3802
  if (field_struct?.is_class) {
@@ -4810,6 +5077,7 @@ function build_declaration_node$1(node, status) {
4810
5077
  if (status.heap_cleanup_stack?.length) status.heap_cleanup_stack[status.heap_cleanup_stack.length - 1].heap_strings.add(node.name);
4811
5078
  }
4812
5079
  } else if (!node.type.is_array && status.structs.find((s) => s.name === node.type.name && s.is_class)) {
5080
+ if (process.env.NOMEN_DEBUG_ANCHOR) console.error("ANCHOR check_heap:", node.name, "value:", node.value?.node_type, node.value?.name ?? "");
4813
5081
  emit_var_load(status, "x0", node.name, 8);
4814
5082
  anchor_heap_pointer(status, node.name);
4815
5083
  consolidate_temp_anchors$1(status, node.value, node.type.name);
@@ -4916,10 +5184,13 @@ function build_declaration_node$1(node, status) {
4916
5184
  build_swap_params(func_call, status);
4917
5185
  } else {
4918
5186
  const old_buffer = status.struct_return_buffer;
5187
+ const old_preset = status.call_x8_preset;
4919
5188
  emit_var_address(status, "x8", node.name);
4920
5189
  status.struct_return_buffer = "x8";
5190
+ status.call_x8_preset = true;
4921
5191
  build_node$1(node.value, status);
4922
5192
  status.struct_return_buffer = old_buffer;
5193
+ status.call_x8_preset = old_preset;
4923
5194
  }
4924
5195
  return;
4925
5196
  }
@@ -4943,7 +5214,9 @@ function build_declaration_node$1(node, status) {
4943
5214
  }
4944
5215
  const value_is_field_borrow = node.value?.node_type === "access" && node.value.access.node_type === "access_field";
4945
5216
  const value_is_var_borrow = node.value?.node_type === "value" && !node.value.is_moved && node.value.value !== "null";
4946
- const is_borrowed_class_ref = !!(node.type?.name && struct_type && struct_type.is_class && (value_is_field_borrow || value_is_var_borrow));
5217
+ const value_is_method_borrow = node.value?.node_type === "access" && node.value.access.node_type === "access_func" && !node.value.access.owned_return;
5218
+ const value_is_borrowing_call = node.value?.node_type === "func_call" && !!status.borrow_returning_functions?.has(node.value.name);
5219
+ const is_borrowed_class_ref = !!(node.type?.name && struct_type && struct_type.is_class && (value_is_field_borrow || value_is_var_borrow || value_is_method_borrow || value_is_borrowing_call));
4947
5220
  if (!is_borrowed_class_ref) status.scoped_declarations.push(node);
4948
5221
  if (struct_type?.is_class) {
4949
5222
  const top = (status.heap_cleanup_stack?.length ?? 1) - 1;
@@ -4957,7 +5230,7 @@ function build_declaration_node$1(node, status) {
4957
5230
  status.alias_owns_flag?.set(node.name, flag_offset);
4958
5231
  }
4959
5232
  }
4960
- if (!is_borrowed_class_ref && struct_type && struct_needs_destroy(struct_type, status)) track_struct_decl(status, node.name, node.type.name, node.type.type_args, node.type.is_nullable);
5233
+ if (!is_borrowed_class_ref && !is_field_struct_borrow(node) && struct_type && struct_needs_destroy(struct_type, status)) track_struct_decl(status, node.name, node.type.name, node.type.type_args, node.type.is_nullable);
4961
5234
  if (status.enums.find((e) => e.name === node.type.name && e.has_associated_data)) {
4962
5235
  const enum_size = get_enum_size(node.type.name, status);
4963
5236
  if (status.function_return_label) {
@@ -5369,8 +5642,10 @@ function build_declaration_node$1(node, status) {
5369
5642
  build_node$1(func_call, status);
5370
5643
  if (!status.code.endsWith("\n")) status.code += "\n";
5371
5644
  emit_var_store(status, "x0", node.name, 8);
5372
- status.last_result_is_heap = true;
5373
- check_heap();
5645
+ if (!status.borrow_returning_functions?.has(func_call.name)) {
5646
+ status.last_result_is_heap = true;
5647
+ check_heap();
5648
+ }
5374
5649
  }
5375
5650
  } else if (node.value) {
5376
5651
  if (node.value.node_type === "value") {
@@ -5428,10 +5703,13 @@ function build_declaration_node$1(node, status) {
5428
5703
  build_swap_params(func_call, status);
5429
5704
  } else if (status.structs.find((s) => s.name === (func_call.type?.name ?? func_call.name) && !s.is_simple_type && !s.is_class) && status.function_return_label) {
5430
5705
  const old_buffer = status.struct_return_buffer;
5706
+ const old_preset = status.call_x8_preset;
5431
5707
  emit_var_address(status, "x8", node.name);
5432
5708
  status.struct_return_buffer = "x8";
5709
+ status.call_x8_preset = true;
5433
5710
  build_node$1(node.value, status);
5434
5711
  status.struct_return_buffer = old_buffer;
5712
+ status.call_x8_preset = old_preset;
5435
5713
  emit_var_address(status, "x0", node.name);
5436
5714
  } else {
5437
5715
  build_node$1(node.value, status);
@@ -5936,8 +6214,7 @@ function reset_label_counter$4() {
5936
6214
  label_counter$4 = 0;
5937
6215
  }
5938
6216
  function build_for_loop_node$1(node, status) {
5939
- const old_scoped_declarations = status.scoped_declarations;
5940
- status.scoped_declarations = [];
6217
+ const old_scoped_declarations = enter_scope_frame(status);
5941
6218
  const label = label_counter$4++;
5942
6219
  const item_name = node.item.value;
5943
6220
  const start_label = `.for_${label}`;
@@ -6227,7 +6504,7 @@ function build_for_loop_node$1(node, status) {
6227
6504
  status.buffer_data_cache = saved_buffer_cache;
6228
6505
  status.loop_labels.pop();
6229
6506
  status.loop_writebacks?.pop();
6230
- status.scoped_declarations = old_scoped_declarations;
6507
+ exit_scope_frame(status, old_scoped_declarations);
6231
6508
  }
6232
6509
  function is_enumerable_type$1(node, status) {
6233
6510
  if (node.node_type !== "value") return false;
@@ -6560,7 +6837,7 @@ function build_function_call_node$1(node, status) {
6560
6837
  const temp_addr = `_temp_${temp_counter - 1}`;
6561
6838
  const temp_offset = status.stack_offsets.get(temp_addr);
6562
6839
  status.code += `add x0, x29, #${temp_offset}\n`;
6563
- } else if (!is_struct && node.type?.name && !status.struct_return_buffer) {
6840
+ } else if (!is_struct && node.type?.name && !status.call_x8_preset) {
6564
6841
  if (status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class)) {
6565
6842
  const nullable_ret = is_nullable_struct_type$1(node.type, status);
6566
6843
  const struct_size = get_struct_size(node.type.name, status);
@@ -6604,7 +6881,7 @@ function build_function_call_node$1(node, status) {
6604
6881
  }
6605
6882
  status.code += `ldr x0, [sp], #16\n`;
6606
6883
  }
6607
- if (!is_struct && node.type?.name && !status.struct_return_buffer) {
6884
+ if (!is_struct && node.type?.name && !status.call_x8_preset) {
6608
6885
  if (status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class)) {
6609
6886
  const temp_name = `_call_ret_${temp_counter - 1}`;
6610
6887
  const offset = status.stack_offsets.get(temp_name);
@@ -6634,7 +6911,7 @@ function build_function_call_node$1(node, status) {
6634
6911
  const param = node.params[idx];
6635
6912
  if (param?.node_type === "value") {
6636
6913
  const vname = param.value;
6637
- if (((status.scoped_declarations?.find((d) => d.name === vname))?.type?.name ?? param.type?.name) === "string") continue;
6914
+ if ((all_scope_frames(status).flat().find((d) => d.name === vname)?.type?.name ?? param.type?.name) === "string") continue;
6638
6915
  }
6639
6916
  if (param) mark_moved_if_struct(param, status);
6640
6917
  }
@@ -6647,8 +6924,7 @@ function reset_label_counter$3() {
6647
6924
  }
6648
6925
  function build_if_else_node$1(node, status) {
6649
6926
  const label = label_counter$3++;
6650
- const old_scoped_declarations = status.scoped_declarations;
6651
- status.scoped_declarations = [];
6927
+ const old_scoped_declarations = enter_scope_frame(status);
6652
6928
  build_node$1(node.condition, status);
6653
6929
  status.code += `\ncmp x0, #0\n`;
6654
6930
  const pre_cache = status.buffer_data_cache;
@@ -6669,7 +6945,7 @@ function build_if_else_node$1(node, status) {
6669
6945
  }
6670
6946
  status.buffer_data_cache = pre_cache;
6671
6947
  status.code += `end_${label}:\n`;
6672
- status.scoped_declarations = old_scoped_declarations;
6948
+ exit_scope_frame(status, old_scoped_declarations);
6673
6949
  }
6674
6950
  //#endregion
6675
6951
  //#region ../src/build_aarch64/build_let_node.ts
@@ -6731,7 +7007,7 @@ function emit_pattern_tag(match_value, enum_name, status) {
6731
7007
  }
6732
7008
  function build_match_node$1(node, status) {
6733
7009
  const label = label_counter$2++;
6734
- const old_scoped_declarations = status.scoped_declarations;
7010
+ const old_scoped_declarations = enter_scope_frame(status);
6735
7011
  const old_stack_offsets = status.stack_offsets;
6736
7012
  status.stack_offsets = new Map(old_stack_offsets);
6737
7013
  const match_type_name = type_from_value_node$1(node.value)?.name;
@@ -6799,7 +7075,7 @@ function build_match_node$1(node, status) {
6799
7075
  }
6800
7076
  status.buffer_data_cache = pre_cache;
6801
7077
  status.code += `end_match_${label}:\n`;
6802
- status.scoped_declarations = old_scoped_declarations;
7078
+ exit_scope_frame(status, old_scoped_declarations);
6803
7079
  status.stack_offsets = old_stack_offsets;
6804
7080
  }
6805
7081
  //#endregion
@@ -7443,8 +7719,12 @@ function build_return_node$1(node, status) {
7443
7719
  status.code += `str xzr, [x8, #${struct_size}]\n`;
7444
7720
  }
7445
7721
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
7446
- for (const decl of status.scoped_declarations) {
7447
- if (finalized.has(decl.name)) continue;
7722
+ for (const decl of all_scope_frames(status).flat()) {
7723
+ if (finalized.has(decl.name)) {
7724
+ release_heap_string_fields(status, decl.name, decl.type.name);
7725
+ continue;
7726
+ }
7727
+ if (is_field_struct_borrow(decl)) continue;
7448
7728
  emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
7449
7729
  }
7450
7730
  emit_heap_slots_cleanup_for_return(status);
@@ -7458,8 +7738,12 @@ function build_return_node$1(node, status) {
7458
7738
  emit_var_store(status, "x0", status.return_assign, size);
7459
7739
  } else if (status.function_return_label) {
7460
7740
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
7461
- for (const decl of status.scoped_declarations) {
7462
- if (finalized.has(decl.name)) continue;
7741
+ for (const decl of all_scope_frames(status).flat()) {
7742
+ if (finalized.has(decl.name)) {
7743
+ release_heap_string_fields(status, decl.name, decl.type.name);
7744
+ continue;
7745
+ }
7746
+ if (is_field_struct_borrow(decl)) continue;
7463
7747
  emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
7464
7748
  }
7465
7749
  emit_heap_slots_cleanup_for_return(status);
@@ -7623,11 +7907,16 @@ function build_return_node$1(node, status) {
7623
7907
  status.moved.add(var_name);
7624
7908
  }
7625
7909
  }
7626
- mark_moved_if_struct(node.value, status);
7910
+ mark_moved_if_struct(node.value, status, { for_return: true });
7911
+ if (node.value?.node_type === "value") clear_heap_string_fields(status, node.value.value);
7627
7912
  const finalized = status.moved ?? /* @__PURE__ */ new Set();
7628
7913
  status.code += `str x0, [sp, #-16]!\n`;
7629
- for (const decl of status.scoped_declarations) {
7630
- if (finalized.has(decl.name)) continue;
7914
+ for (const decl of all_scope_frames(status).flat()) {
7915
+ if (finalized.has(decl.name)) {
7916
+ release_heap_string_fields(status, decl.name, decl.type.name);
7917
+ continue;
7918
+ }
7919
+ if (is_field_struct_borrow(decl)) continue;
7631
7920
  emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
7632
7921
  }
7633
7922
  emit_heap_slots_cleanup_for_return(status);
@@ -7643,7 +7932,7 @@ function reset_label_counter$1() {
7643
7932
  }
7644
7933
  function build_switch_node$1(node, status) {
7645
7934
  const label = label_counter$1++;
7646
- const old_scoped_declarations = status.scoped_declarations;
7935
+ const old_scoped_declarations = enter_scope_frame(status);
7647
7936
  const pre_cache = status.buffer_data_cache;
7648
7937
  for (let i = 0; i < node.cases.length; i++) {
7649
7938
  status.scoped_declarations = [];
@@ -7663,7 +7952,7 @@ function build_switch_node$1(node, status) {
7663
7952
  }
7664
7953
  status.buffer_data_cache = pre_cache;
7665
7954
  status.code += `end_switch_${label}:\n`;
7666
- status.scoped_declarations = old_scoped_declarations;
7955
+ exit_scope_frame(status, old_scoped_declarations);
7667
7956
  }
7668
7957
  //#endregion
7669
7958
  //#region ../src/build_aarch64/build_todo_node.ts
@@ -7890,8 +8179,7 @@ function reset_label_counter() {
7890
8179
  label_counter = 0;
7891
8180
  }
7892
8181
  function build_while_loop_node$1(node, status) {
7893
- const old_scoped_declarations = status.scoped_declarations;
7894
- status.scoped_declarations = [];
8182
+ const old_scoped_declarations = enter_scope_frame(status);
7895
8183
  const label = label_counter++;
7896
8184
  const start_label = `.while_${label}`;
7897
8185
  const end_label = `.end_while_${label}`;
@@ -8011,7 +8299,7 @@ function build_while_loop_node$1(node, status) {
8011
8299
  else status.register_allocations = void 0;
8012
8300
  status.buffer_data_cache = saved_buffer_cache;
8013
8301
  status.loop_labels.pop();
8014
- status.scoped_declarations = old_scoped_declarations;
8302
+ exit_scope_frame(status, old_scoped_declarations);
8015
8303
  }
8016
8304
  //#endregion
8017
8305
  //#region ../src/build_aarch64/build_node.ts
@@ -8732,7 +9020,7 @@ function build_auto_destroy_function(node, status) {
8732
9020
  status.function_param_regs.set("self", "x19");
8733
9021
  status.code += `sub sp, sp, #${stack_placeholder}\n`;
8734
9022
  status.code += `mov x29, sp\n`;
8735
- emit_field_destroys(status, node, "self", void 0, false);
9023
+ emit_field_destroys(status, node, "self", void 0, false, node.is_class);
8736
9024
  status.code += `${return_label}:\n`;
8737
9025
  const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
8738
9026
  status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
@@ -8815,6 +9103,13 @@ function build_init_function(node, status) {
8815
9103
  }
8816
9104
  } else {
8817
9105
  const field_size = get_type_size(field.type, status);
9106
+ if (node.is_class && field.type.name === "string" && !field.type.is_ref) {
9107
+ status.code += `str ${src_reg}, [sp, #-16]!\n`;
9108
+ status.code += `mov x0, ${src_reg}\n`;
9109
+ status.code += `bl _strdup\n`;
9110
+ status.code += `mov ${src_reg}, x0\n`;
9111
+ status.code += `ldr x0, [sp], #16\n`;
9112
+ }
8818
9113
  emit_typed_store(status, src_reg, "x19", offset, field_size);
8819
9114
  }
8820
9115
  }
@@ -8832,6 +9127,11 @@ function build_init_function(node, status) {
8832
9127
  const label = `_str_${func_name}_${field.name}`;
8833
9128
  status.strings.set(label, val);
8834
9129
  status.code += `adr x1, ${label}\n`;
9130
+ if (node.is_class && field.type.name === "string" && !field.type.is_ref) {
9131
+ status.code += `mov x0, x1\n`;
9132
+ status.code += `bl _strdup\n`;
9133
+ status.code += `mov x1, x0\n`;
9134
+ }
8835
9135
  } else {
8836
9136
  const resolved = resolve_global_const_value(val, status);
8837
9137
  if (resolved !== void 0) status.code += `ldr x1, =${resolved}\n`;
@@ -8969,6 +9269,11 @@ function build_custom_init_function(node, func, status) {
8969
9269
  const label = `_str_${func_name}_${field.name}`;
8970
9270
  status.strings.set(label, val);
8971
9271
  status.code += `adr x1, ${label}\n`;
9272
+ if (node.is_class && field.type.name === "string" && !field.type.is_ref) {
9273
+ status.code += `mov x0, x1\n`;
9274
+ status.code += `bl _strdup\n`;
9275
+ status.code += `mov x1, x0\n`;
9276
+ }
8972
9277
  } else {
8973
9278
  const resolved = resolve_global_const_value(val, status);
8974
9279
  if (resolved !== void 0) status.code += `ldr x1, =${resolved}\n`;
@@ -9177,8 +9482,33 @@ function build_struct_functions$1(node, status) {
9177
9482
  }
9178
9483
  second_slot_idx++;
9179
9484
  }
9485
+ const moved_param_save_slots = /* @__PURE__ */ new Map();
9486
+ for (const param of func.params) {
9487
+ if (!param.is_moved || param.is_self_param) continue;
9488
+ if (!status.structs.find((s) => s.name === param.type.name && s.is_class)) continue;
9489
+ const reg = status.function_param_regs.get(param.name);
9490
+ if (reg) {
9491
+ const save_offset = allocate_stack_space(status, 8);
9492
+ status.code += `str ${reg}, [x29, #${save_offset}]\n`;
9493
+ moved_param_save_slots.set(param.name, {
9494
+ offset: save_offset,
9495
+ type_name: param.type.name,
9496
+ type_args: param.type.type_args,
9497
+ is_nullable: param.type.is_nullable
9498
+ });
9499
+ } else {
9500
+ const offset = status.stack_offsets.get(param.name);
9501
+ if (offset !== void 0) moved_param_save_slots.set(param.name, {
9502
+ offset,
9503
+ type_name: param.type.name,
9504
+ type_args: param.type.type_args,
9505
+ is_nullable: param.type.is_nullable
9506
+ });
9507
+ }
9508
+ }
9180
9509
  status.force_heap_strings = scan_force_heap_strings(func.statements);
9181
9510
  status.buffer_data_cache = void 0;
9511
+ const moved_before = new Set(status.moved ?? []);
9182
9512
  if (!emit_owning_buffer_standalone_aarch64(node, func.name, status)) build_block_node$1(func, status);
9183
9513
  const loop_regs_used = status.callee_saved_regs_used ? [...status.callee_saved_regs_used].sort() : [];
9184
9514
  status.callee_saved_regs_used = void 0;
@@ -9193,6 +9523,31 @@ function build_struct_functions$1(node, status) {
9193
9523
  }
9194
9524
  }
9195
9525
  status.code += `${return_label}:\n`;
9526
+ if (moved_param_save_slots.size > 0) {
9527
+ const ret_is_class = !!func.return_type?.name && !!status.structs.find((s) => s.name === func.return_type.name && s.is_class);
9528
+ const need_save = !!func.return_type?.name;
9529
+ const keep_prefix = `.Lkeep_mparam_${func_label.replace(/[^\w]/g, "_")}`;
9530
+ let return_save;
9531
+ if (ret_is_class || need_save) {
9532
+ return_save = allocate_stack_space(status, 8);
9533
+ status.code += `str x0, [x29, #${return_save}]\n`;
9534
+ }
9535
+ for (const [name, info] of moved_param_save_slots) {
9536
+ if (status.moved?.has(name) && !moved_before.has(name)) continue;
9537
+ if (moved_param_is_consumed(func, name)) continue;
9538
+ if (ret_is_class) {
9539
+ status.code += `ldr x0, [x29, #${info.offset}]\n`;
9540
+ status.code += `ldr x1, [x29, #${return_save}]\n`;
9541
+ status.code += `cmp x0, x1\n`;
9542
+ status.code += `beq ${keep_prefix}_${name}\n`;
9543
+ }
9544
+ emit_destroy_for_anchor_slot(status, info.offset, info.type_name, info.type_args, info.is_nullable);
9545
+ status.code += `ldr x0, [x29, #${info.offset}]\n`;
9546
+ emit_free(status);
9547
+ if (ret_is_class) status.code += `${keep_prefix}_${name}:\n`;
9548
+ }
9549
+ if (ret_is_class || need_save) status.code += `ldr x0, [x29, #${return_save}]\n`;
9550
+ }
9196
9551
  const total_stack = Math.ceil((status.stack_size || 0) / 16) * 16;
9197
9552
  status.code = status.code.replace(`sub sp, sp, #${stack_placeholder}`, total_stack > 0 ? `sub sp, sp, #${total_stack}` : `// no stack needed`);
9198
9553
  status.code = patch_overflow_placeholders(status.code, func_label, callee_idx + loop_regs_used.length, total_stack);
@@ -9649,6 +10004,8 @@ function build_inline_method(struct_node, func, status) {
9649
10004
  const old_function_return_type = status.function_return_type;
9650
10005
  const old_register_allocations = status.register_allocations;
9651
10006
  const old_buffer_data_cache = status.buffer_data_cache;
10007
+ const old_heap_cleanup_stack = status.heap_cleanup_stack;
10008
+ const old_moved = status.moved;
9652
10009
  const return_label = `.inline_ret_${inline_counter++}`;
9653
10010
  status.function_return_label = return_label;
9654
10011
  status.scoped_declarations = [];
@@ -9656,6 +10013,8 @@ function build_inline_method(struct_node, func, status) {
9656
10013
  status.struct_return_buffer = void 0;
9657
10014
  status.return_buffer_stack_offset = void 0;
9658
10015
  status.buffer_data_cache = void 0;
10016
+ status.heap_cleanup_stack = [];
10017
+ status.moved = /* @__PURE__ */ new Set();
9659
10018
  if (needs_x19) {
9660
10019
  status.code += `str x19, [sp, #-16]!\n`;
9661
10020
  status.code += `mov x19, x0\n`;
@@ -9734,6 +10093,8 @@ function build_inline_method(struct_node, func, status) {
9734
10093
  status.function_return_type = old_function_return_type;
9735
10094
  status.register_allocations = old_register_allocations;
9736
10095
  status.buffer_data_cache = old_buffer_data_cache;
10096
+ status.heap_cleanup_stack = old_heap_cleanup_stack;
10097
+ status.moved = old_moved;
9737
10098
  }
9738
10099
  let inline_fn_depth = 0;
9739
10100
  const MAX_INLINE_DEPTH = 2;
@@ -9751,6 +10112,8 @@ function build_inline_function(func, status) {
9751
10112
  const old_function_return_type = status.function_return_type;
9752
10113
  const old_register_allocations = status.register_allocations;
9753
10114
  const old_buffer_data_cache = status.buffer_data_cache;
10115
+ const old_heap_cleanup_stack = status.heap_cleanup_stack;
10116
+ const old_moved = status.moved;
9754
10117
  const return_label = `.inline_fn_ret_${inline_counter++}`;
9755
10118
  status.function_return_label = return_label;
9756
10119
  status.scoped_declarations = [];
@@ -9758,6 +10121,8 @@ function build_inline_function(func, status) {
9758
10121
  status.struct_return_buffer = void 0;
9759
10122
  status.return_buffer_stack_offset = void 0;
9760
10123
  status.buffer_data_cache = void 0;
10124
+ status.heap_cleanup_stack = [];
10125
+ status.moved = /* @__PURE__ */ new Set();
9761
10126
  const param_regs = [
9762
10127
  "x0",
9763
10128
  "x1",
@@ -9824,6 +10189,8 @@ function build_inline_function(func, status) {
9824
10189
  status.function_return_type = old_function_return_type;
9825
10190
  status.register_allocations = old_register_allocations;
9826
10191
  status.buffer_data_cache = old_buffer_data_cache;
10192
+ status.heap_cleanup_stack = old_heap_cleanup_stack;
10193
+ status.moved = old_moved;
9827
10194
  inline_fn_depth--;
9828
10195
  return true;
9829
10196
  }
@@ -10432,6 +10799,12 @@ function build_access_field(node, status) {
10432
10799
  if (paramReg !== "x0") status.code += `mov x0, ${paramReg}\n`;
10433
10800
  } else emit_var_load(status, "x0", name, 8);
10434
10801
  const final_offset = get_field_offset(target_type?.name || "", access_field.name, status);
10802
+ const field_type_obj = resolve_field_type(access_field, target_type?.name, status);
10803
+ const resolved_field_type = field_type_obj?.name || "";
10804
+ if (!!resolved_field_type && !field_type_obj?.is_ref && !field_type_obj?.is_nullable && is_struct_type(resolved_field_type, status)) {
10805
+ if (final_offset > 0) status.code += `add x0, x0, #${final_offset}\n`;
10806
+ return;
10807
+ }
10435
10808
  const field_type = access_field.type?.name || "";
10436
10809
  const size = aarch64_size(field_type);
10437
10810
  const signed = field_type.startsWith("int") || field_type === "float" || field_type === "float32" || field_type === "float64";
@@ -10985,10 +11358,14 @@ function build_access_method(node, access_func, status) {
10985
11358
  const param = access_func.params[idx];
10986
11359
  if (param?.node_type === "value") {
10987
11360
  const vname = param.value;
10988
- if (((status.scoped_declarations?.find((d) => d.name === vname))?.type?.name ?? param.type?.name) === "string") continue;
11361
+ if ((all_scope_frames(status).flat().find((d) => d.name === vname)?.type?.name ?? param.type?.name) === "string") continue;
10989
11362
  }
10990
11363
  if (param) mark_moved_if_struct(param, status);
10991
11364
  }
11365
+ if (node.target.node_type === "value" && target_struct && !target_struct.is_class && !trait_target) {
11366
+ const target_method = target_struct.functions.find((f) => f.name === access_func.name);
11367
+ if (target_method) drop_self_written_string_field_records(status, node.target.value, scan_self_string_field_writes(target_struct, target_method));
11368
+ }
10992
11369
  if (method_name.endsWith("_to_string") && method_name !== "string_to_string") status.last_result_is_heap = true;
10993
11370
  if (status.heap_returning_functions?.has(method_name)) status.last_result_is_heap = true;
10994
11371
  if (method_name === "Buffer_string_move_T") status.last_result_is_heap = true;
@@ -12054,21 +12431,25 @@ function build_operation_node(node, status) {
12054
12431
  } else if (node.operator_func) {
12055
12432
  const label = node.operator_func.mangled_name || `${node.operator_func.struct_name}_${node.operator_func.func_name}`;
12056
12433
  const is_string_op = node.type?.name === "string";
12057
- const left_temp = is_string_op && is_owned_heap_temp(node.left_value, status);
12058
- const right_temp = is_string_op && is_owned_heap_temp(node.right_value, status);
12434
+ const is_string_cmp = node.op === "==" || node.op === "!=";
12435
+ const left_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.left_value, status);
12436
+ const right_temp = (is_string_op || is_string_cmp) && is_owned_heap_temp(node.right_value, status);
12059
12437
  if (left_temp || right_temp) {
12060
12438
  const id = status.label_counter = (status.label_counter ?? 0) + 1;
12061
12439
  const lt = `_ltmp_${id}`;
12062
12440
  const rt = `_rtmp_${id}`;
12441
+ const cres = `_cres_${id}`;
12063
12442
  status.code += `({ `;
12064
12443
  status.code += `char* ${lt} = `;
12065
12444
  build_operand(node.left_value, status);
12066
12445
  status.code += `; char* ${rt} = `;
12067
12446
  build_operand(node.right_value, status);
12068
- status.code += `; char* _cres_${id} = ${label}(${lt}, ${rt}); `;
12447
+ status.code += `; `;
12448
+ if (is_string_op) status.code += `char* ${cres} = ${label}(${lt}, ${rt}); `;
12449
+ else status.code += `int ${cres} = ${label}(${lt}, ${rt}); `;
12069
12450
  if (left_temp) status.code += `free(${lt}); `;
12070
12451
  if (right_temp) status.code += `free(${rt}); `;
12071
- status.code += `_cres_${id}; })`;
12452
+ status.code += `${cres}; })`;
12072
12453
  } else {
12073
12454
  const is_array_op = node.operator_func.struct_name.startsWith("Array") && (type_from_value_node$1(node.left_value).is_array || type_from_value_node$1(node.right_value).is_array);
12074
12455
  if (node.operator_func.invert) status.code += `(!`;
@@ -12127,7 +12508,7 @@ function is_owned_heap_temp(node, status) {
12127
12508
  if (heap_set?.has(mangled)) return true;
12128
12509
  if (heap_set && target_value && heap_set.has(`${target_value}_${raw_name}`)) return true;
12129
12510
  if (heap_set && target_type_name && heap_set.has(`${target_type_name}_${raw_name}`)) return true;
12130
- return !(check_node.node_type === "access_func" && (raw_name === "at" || raw_name === "first") && !check_node.owned_return);
12511
+ return !(check_node.node_type === "access_func" && (raw_name === "at" || raw_name === "first" || raw_name === "load_T") && !check_node.owned_return);
12131
12512
  }
12132
12513
  return false;
12133
12514
  }
@@ -12235,56 +12616,401 @@ function build_array_operand_for_call(node, status) {
12235
12616
  }
12236
12617
  let ns_tmp_counter = 0;
12237
12618
  //#endregion
12238
- //#region ../src/build_c/build_access_node.ts
12619
+ //#region ../src/build_c/utils/is_string_borrow.ts
12239
12620
  /**
12240
- * The C type of a single element of a `view T` slice, used to cast the
12241
- * universal `nomen_view.ptr` for `.at`/`.set`. `view string`'s element is a
12242
- * `char`; every other view's element is its own type name.
12621
+ * Whether a value node denotes a BORROWED string a pointer into storage the
12622
+ * receiver does not own (an array element accessed via `.at()`/`.first()`, or
12623
+ * `init.args.at(n)` which points into the C runtime's `argv`). Borrowed
12624
+ * strings must NOT be freed by auto_free or by reassignment: freeing them
12625
+ * reclaims memory owned by the container (or argv), crashing with
12626
+ * "pointer being freed was not allocated". Mirrors aarch64's `heap_strings`
12627
+ * ownership tracking, which only frees freshly-allocated strings.
12243
12628
  */
12244
- function view_element_c_type(view_type, status) {
12245
- const elem_name = view_type.name === "string" ? "char" : view_type.name;
12246
- if (!!status.structs.find((s) => s.name === elem_name && !s.is_simple_type)) return `struct ${elem_name}`;
12247
- return c_type(elem_name);
12629
+ function is_string_borrow(node) {
12630
+ if (!node || node.node_type !== "access") return false;
12631
+ const access = node.access;
12632
+ if (access.node_type !== "access_func") return false;
12633
+ const func = access;
12634
+ return (func.name === "at" || func.name === "first") && !func.owned_return;
12248
12635
  }
12249
- /**
12250
- * Compute a C expression that yields a `struct Nursery *` for the receiver of
12251
- * a `name.spawn(...)` escape-hatch call. A `ref Nursery` parameter is already a
12252
- * pointer; any other Nursery lvalue (the async block's named local, etc.)
12253
- * needs its address taken.
12254
- */
12255
- function nursery_pointer_expr(target, status) {
12256
- if (target.node_type === "value") {
12257
- const name = target.value;
12258
- if (status.function_ref_params?.has(name)) return name;
12636
+ //#endregion
12637
+ //#region ../src/build_c/build_auto_free.ts
12638
+ function build_auto_free(status) {
12639
+ free_scoped_declarations(status, status.scoped_declarations);
12640
+ if (status.deferred_frees?.length) {
12641
+ status.code += "\n// Deferred frees\n";
12642
+ for (const d of status.deferred_frees) if (d.is_nullable) status.code += `if (${d.temp}) { ${d.struct_name}_destroy(${d.temp}); free(${d.temp}); }\n`;
12643
+ else status.code += `${d.struct_name}_destroy(${d.temp}); free(${d.temp});\n`;
12644
+ status.deferred_frees.length = 0;
12259
12645
  }
12260
- const before = status.code.length;
12261
- status.suppress_dereference = true;
12262
- build_node(target, status);
12263
- status.suppress_dereference = false;
12264
- const expr = status.code.substring(before);
12265
- status.code = status.code.substring(0, before);
12266
- return "&" + expr;
12646
+ status.scoped_declarations = [];
12267
12647
  }
12268
12648
  /**
12269
- * Build a node for use as a vtable dispatch target. The vtable lives at offset
12270
- * 0 of the struct (`_vt`), so `_get_trait_func` needs a POINTER to the struct
12271
- * (not the by-value struct). When the target is the implicit `self` parameter,
12272
- * the build normally renames it to `_self` (the local by-value copy made at
12273
- * function entry) but for vtable dispatch we need the original `self` pointer
12274
- * param, so emit it directly. A ref/trait/class param is already a pointer; any
12275
- * other lvalue (local variable) gets its address taken. `&*x` is valid C and
12276
- * simplifies to `x`, so a ref param that slipped through still lands on its
12277
- * pointer.
12649
+ * Emit scope-exit free/destroy code for a list of declarations. Extracted from
12650
+ * build_auto_free so that break/continue can reclaim declarations from the
12651
+ * current scope AND enclosing scopes (up to the loop body) before jumping
12652
+ * see build_break_node. Does NOT process deferred_frees or clear the list
12653
+ * (those are scope-exit-only concerns handled by build_auto_free).
12278
12654
  */
12279
- function build_vtable_target(node, status) {
12280
- if (node.node_type === "value") {
12281
- const name = node.value;
12282
- if (name === "self") {
12283
- status.code += "self";
12284
- return;
12285
- }
12286
- if (status.function_ref_params?.has(name) || status.class_vars?.has(name)) {
12287
- status.code += c_function_name(name);
12655
+ function free_scoped_declarations(status, decls, persist_string_field_records = false) {
12656
+ let commented = false;
12657
+ if (status.heap_string_fields?.size) for (const dec of decls) {
12658
+ const prefix = `${dec.name}.`;
12659
+ for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) {
12660
+ if (!commented) {
12661
+ status.code += "\n// Auto-free\n";
12662
+ commented = true;
12663
+ }
12664
+ status.code += `free(${key});\n`;
12665
+ if (!persist_string_field_records) status.heap_string_fields.delete(key);
12666
+ }
12667
+ }
12668
+ for (const dec of decls) {
12669
+ const struct = status.structs.find((s) => s.name === dec.type.name);
12670
+ if (struct && struct.traits.includes("Disposable")) {
12671
+ const trait = status.traits.find((t) => t.name === "Disposable");
12672
+ const func = trait?.functions.find((f) => f.name == "dispose");
12673
+ if (trait && func) {
12674
+ if (!commented) {
12675
+ status.code += "\n// Auto-free\n";
12676
+ commented = true;
12677
+ }
12678
+ const cast = "(void *(*)(void *))";
12679
+ const traitIndex = status.traits.indexOf(trait);
12680
+ const funcIndex = trait.functions.indexOf(func);
12681
+ status.code += `(${cast}_get_trait_func((void *)&${dec.name}, ${traitIndex}, ${funcIndex}))(&${dec.name});\n`;
12682
+ }
12683
+ }
12684
+ const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved;
12685
+ const is_borrowed_string = is_string_borrow(dec.value) || !!status.string_borrow_vars?.has(dec.name);
12686
+ const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call");
12687
+ const dec_value = dec.value;
12688
+ const dec_val_is_string_literal = dec.value?.node_type === "value" && dec_value.value.length >= 2 && dec_value.value.startsWith("\"") && dec_value.value.endsWith("\"");
12689
+ const dec_val_is_heap_string_var = dec.value?.node_type === "value" && !dec_val_is_string_literal && !!status.scoped_declarations.find((d) => d.name === dec_value.value);
12690
+ const was_strdup_string_var = dec.declaration === "var" && !dec.type.is_view && !is_borrowed_string && (dec_val_is_string_literal || dec_val_is_heap_string_var);
12691
+ const is_normalized_join_string = !!status.string_join_owned_vars?.has(dec.name);
12692
+ const dec_struct = status.structs.find((s) => s.name === dec.type.name);
12693
+ const is_class_var = !!dec_struct?.is_class;
12694
+ const trait_class_trait = status.trait_class_locals?.get(dec.name);
12695
+ if (trait_class_trait !== void 0 && !is_destructured_field_access) {
12696
+ if (!commented) {
12697
+ status.code += "\n// Auto-free\n";
12698
+ commented = true;
12699
+ }
12700
+ if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${trait_class_trait}_destroy(${dec.name}); free(${dec.name}); }\n`;
12701
+ else status.code += `${trait_class_trait}_destroy(${dec.name}); free(${dec.name});\n`;
12702
+ }
12703
+ if (!is_destructured_field_access && !is_borrowed_string && (!dec.type.is_static || value_is_heap_string || was_strdup_string_var || is_normalized_join_string) && dec.type.name === "string" && !dec.type.is_array) {
12704
+ if (!commented) {
12705
+ status.code += "\n// Auto-free\n";
12706
+ commented = true;
12707
+ }
12708
+ status.code += `free(${dec.name});\n`;
12709
+ }
12710
+ if (!is_destructured_field_access && is_class_var && !dec.type.is_array) {
12711
+ if (!commented) {
12712
+ status.code += "\n// Auto-free\n";
12713
+ commented = true;
12714
+ }
12715
+ const cls = struct ?? dec_struct;
12716
+ const mono_cls_name = cls ? mono_type_name(dec.type) : void 0;
12717
+ const has_destroy_fn = !!cls?.functions.find((f) => f.name === "#destroy") || !!cls?.is_class;
12718
+ if (cls) {
12719
+ const destroy_call = has_destroy_fn ? `${mono_cls_name}_destroy(${dec.name}); ` : "";
12720
+ if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${destroy_call}free(${dec.name}); }\n`;
12721
+ else status.code += `${destroy_call}free(${dec.name});\n`;
12722
+ } else status.code += `free(${dec.name});\n`;
12723
+ }
12724
+ if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && dec.type.name !== "string") {
12725
+ const mono_name = mono_type_name(dec.type);
12726
+ const struct_type = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
12727
+ if (struct_type && struct_needs_destroy(struct_type, status)) {
12728
+ if (!commented) {
12729
+ status.code += "\n// Auto-free\n";
12730
+ commented = true;
12731
+ }
12732
+ emit_struct_destroys(status, struct_type, dec.name);
12733
+ }
12734
+ }
12735
+ if (!!status.traits.find((t) => t.name === dec.type.name) && !is_destructured_field_access && !dec.type.is_array && dec.value) {
12736
+ const val_type = type_from_value_node$1(dec.value);
12737
+ const concrete = val_type?.name ? status.structs.find((s) => s.name === val_type.name && !s.is_simple_type && !s.is_generic) : void 0;
12738
+ if (concrete && struct_needs_destroy(concrete, status)) {
12739
+ if (!commented) {
12740
+ status.code += "\n// Auto-free\n";
12741
+ commented = true;
12742
+ }
12743
+ emit_struct_destroys(status, concrete, dec.name);
12744
+ }
12745
+ }
12746
+ if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && is_nullable_struct_type(dec.type, status)) {
12747
+ const inner = status.structs.find((s) => s.name === dec.type.name);
12748
+ if (inner && struct_needs_destroy(inner, status)) {
12749
+ if (!commented) {
12750
+ status.code += "\n// Auto-free\n";
12751
+ commented = true;
12752
+ }
12753
+ const body = capture_destroys(status, inner, dec.name, ".");
12754
+ status.code += `if (${has_flag_name(dec.name)}) { ${body} }\n`;
12755
+ }
12756
+ }
12757
+ if (!is_destructured_field_access && dec.type.is_array && status.heap_array_vars?.has(dec.name)) {
12758
+ if (!commented) {
12759
+ status.code += "\n// Auto-free\n";
12760
+ commented = true;
12761
+ }
12762
+ const elem_name = dec.type.name;
12763
+ const elem_is_class = !!status.structs.find((s) => s.name === elem_name)?.is_class;
12764
+ const elem_is_string = elem_name === "string";
12765
+ const elem_c_type = elem_is_class ? `struct ${elem_name}*` : elem_name;
12766
+ if (elem_is_class) {
12767
+ status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
12768
+ status.code += `\t${elem_c_type}* _data = (${elem_c_type}*)((char*)${dec.name} + sizeof(struct Array_${elem_name}));\n`;
12769
+ status.code += `\t${elem_name}_destroy(_data[_i]); free(_data[_i]);\n`;
12770
+ status.code += `}\n`;
12771
+ } else if (elem_is_string) {
12772
+ status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
12773
+ status.code += `\tchar** _data = (char**)((char*)${dec.name} + sizeof(struct Array_string));\n`;
12774
+ status.code += `\tfree(_data[_i]);\n`;
12775
+ status.code += `}\n`;
12776
+ }
12777
+ status.code += `free(${dec.name});\n`;
12778
+ }
12779
+ if (!is_destructured_field_access && dec.type.is_array && status.stack_array_vars?.has(dec.name)) {
12780
+ if (!commented) {
12781
+ status.code += "\n// Auto-free\n";
12782
+ commented = true;
12783
+ }
12784
+ const elem_name = dec.type.name;
12785
+ const elem_struct = status.structs.find((s) => s.name === elem_name);
12786
+ const elem_is_class = !!elem_struct?.is_class;
12787
+ const elem_is_string = elem_name === "string";
12788
+ const elem_struct_type = status.structs.find((s) => s.name === elem_name && !s.is_simple_type && !s.is_generic);
12789
+ const arr_len = status.stack_array_lengths?.get(dec.name) ?? "0";
12790
+ if (elem_is_string) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
12791
+ else if (elem_is_class) {
12792
+ if (has_destroy(elem_struct)) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { if (${dec.name}[_i]) { ${elem_name}_destroy(${dec.name}[_i]); free(${dec.name}[_i]); } }\n`;
12793
+ else status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
12794
+ } else if (elem_struct_type && struct_needs_destroy(elem_struct_type, status)) {
12795
+ status.code += `for (long _i = 0; _i < ${arr_len}; _i++) {\n`;
12796
+ emit_struct_destroys(status, elem_struct_type, `${dec.name}[_i]`);
12797
+ status.code += `}\n`;
12798
+ }
12799
+ }
12800
+ }
12801
+ }
12802
+ /** Name-based variant of struct_needs_destroy for callers without the StructNode. */
12803
+ function struct_needs_destroy_by_name(name, status) {
12804
+ const struct = status.structs.find((s) => s.name === name && !s.is_simple_type && !s.is_generic);
12805
+ if (!struct) return false;
12806
+ return struct_needs_destroy(struct, status);
12807
+ }
12808
+ /**
12809
+ * Emit destroy calls for a struct variable at scope exit. Calls the struct's
12810
+ * own `#destroy` first (if any), then walks each field: class-typed fields
12811
+ * are destroyed + freed (pointer); nested struct fields are recursively
12812
+ * destroyed via their own `#destroy`. Mirrors aarch64's
12813
+ * `emit_destroy_for_decl` + `emit_field_destroys`.
12814
+ */
12815
+ function emit_struct_destroys(status, struct, var_expr) {
12816
+ if (has_destroy(struct)) status.code += `${struct.name}_destroy(&${var_expr});\n`;
12817
+ for (const field of struct.fields) {
12818
+ if (field.type.is_ref) continue;
12819
+ const field_struct = resolve_struct_type(field.type, status);
12820
+ if (!field_struct) continue;
12821
+ const field_expr = `${var_expr}.${field.name}`;
12822
+ if (field_struct.is_class) {
12823
+ if (has_destroy(field_struct)) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); }\n`;
12824
+ } else if (is_nullable_struct_type(field.type, status)) {
12825
+ if (struct_needs_destroy(field_struct, status)) {
12826
+ const body = capture_destroys(status, field_struct, field_expr, ".");
12827
+ status.code += `if (${field_expr}_has) { ${body} }\n`;
12828
+ }
12829
+ } else emit_struct_destroys(status, field_struct, field_expr);
12830
+ }
12831
+ }
12832
+ /**
12833
+ * Capture the destroy calls for a struct value as a single line (no trailing
12834
+ * newline) so it can be embedded inside an `if (...) { ... }` guard. Uses
12835
+ * `accessor` (`.` or `->`) for nested field expressions — `.` for by-value
12836
+ * locals/fields, `->` when the container is a class pointer.
12837
+ */
12838
+ function capture_destroys(status, struct, var_expr, accessor) {
12839
+ const before = status.code.length;
12840
+ if (has_destroy(struct)) status.code += `${struct.name}_destroy(&${var_expr}); `;
12841
+ for (const field of struct.fields) {
12842
+ if (field.type.is_ref) continue;
12843
+ const field_struct = resolve_struct_type(field.type, status);
12844
+ if (!field_struct) continue;
12845
+ const field_expr = `${var_expr}${accessor}${field.name}`;
12846
+ if (field_struct.is_class) {
12847
+ if (has_destroy(field_struct)) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); } `;
12848
+ } else if (is_nullable_struct_type(field.type, status)) {
12849
+ if (struct_needs_destroy(field_struct, status)) {
12850
+ const inner_before = status.code.length;
12851
+ capture_destroys(status, field_struct, field_expr, accessor);
12852
+ const inner_body = status.code.substring(inner_before).trim();
12853
+ status.code = status.code.substring(0, inner_before);
12854
+ status.code += `if (${field_expr}_has) { ${inner_body} } `;
12855
+ }
12856
+ } else capture_destroys(status, field_struct, field_expr, accessor);
12857
+ }
12858
+ const captured = status.code.substring(before).replace(/\s+/g, " ").trim();
12859
+ status.code = status.code.substring(0, before);
12860
+ return captured;
12861
+ }
12862
+ //#endregion
12863
+ //#region ../src/build_c/utils/c_scope.ts
12864
+ /**
12865
+ * Begin a new C scope frame: allocate a fresh declarations array, push it onto
12866
+ * c_scope_stack, and make it the active scoped_declarations. Returns the frame
12867
+ * so the caller can assign it to status.scoped_declarations (mirroring the
12868
+ * existing save/restore idiom). Pair with leave_c_scope at scope exit.
12869
+ */
12870
+ function enter_c_scope(status) {
12871
+ const frame = [];
12872
+ if (!status.c_scope_stack) status.c_scope_stack = [];
12873
+ status.c_scope_stack.push(frame);
12874
+ return frame;
12875
+ }
12876
+ /** Pop the current scope frame from c_scope_stack (scope-exit counterpart to enter_c_scope). */
12877
+ function leave_c_scope(status) {
12878
+ status.c_scope_stack?.pop();
12879
+ }
12880
+ /**
12881
+ * Find a declaration by name in the active scope frame or any enclosing frame
12882
+ * on c_scope_stack (innermost frame first, so a shadowing inner declaration
12883
+ * wins). Returns the owning frame and the declaration's index, so a mov site
12884
+ * can resolve and splice a declaration living in an OUTER scope — a `mov`
12885
+ * inside an if/loop branch must still transfer ownership of variables
12886
+ * declared before the branch (mirrors aarch64's all_scope_frames).
12887
+ */
12888
+ function find_decl_in_c_scopes(status, name) {
12889
+ const stack = status.c_scope_stack ?? [];
12890
+ for (let i = stack.length - 1; i >= 0; i--) {
12891
+ const index = stack[i].findIndex((d) => d.name === name);
12892
+ if (index !== -1) return {
12893
+ frame: stack[i],
12894
+ index
12895
+ };
12896
+ }
12897
+ const index = status.scoped_declarations.findIndex((d) => d.name === name);
12898
+ return index === -1 ? void 0 : {
12899
+ frame: status.scoped_declarations,
12900
+ index
12901
+ };
12902
+ }
12903
+ /**
12904
+ * Splice a declaration out of whichever scope frame holds it (the current
12905
+ * frame or an enclosing frame on c_scope_stack). Used at ownership-transfer
12906
+ * sites (`mov` args, alias moves) — without this, a declaration left in an
12907
+ * outer frame is reclaimed by that scope's exit cleanup even though the
12908
+ * callee/new owner now owns the value (latent double-free).
12909
+ */
12910
+ function splice_decl_from_c_scopes(status, name) {
12911
+ const hit = find_decl_in_c_scopes(status, name);
12912
+ return hit ? hit.frame.splice(hit.index, 1)[0] : void 0;
12913
+ }
12914
+ /**
12915
+ * Mark the current top frame as a loop body, so break/continue know how far up
12916
+ * the scope stack to reclaim. Call AFTER entering the loop body scope.
12917
+ */
12918
+ function push_c_loop_frame(status) {
12919
+ if (!status.c_scope_stack?.length) return;
12920
+ if (!status.c_loop_frame_depth) status.c_loop_frame_depth = [];
12921
+ status.c_loop_frame_depth.push(status.c_scope_stack.length - 1);
12922
+ }
12923
+ function pop_c_loop_frame(status) {
12924
+ status.c_loop_frame_depth?.pop();
12925
+ }
12926
+ /**
12927
+ * Reclaim declarations from every scope frame on c_scope_stack — a `return`
12928
+ * exits ALL enclosing scopes up to the function boundary, not just the
12929
+ * current one, so declarations living in outer frames (e.g. a class instance
12930
+ * declared before an `if (...) { ... return }`) must be freed before the
12931
+ * jump. Nothing is cleared: sibling return statements and the fall-through
12932
+ * path are mutually exclusive at runtime but are ALL emitted, so every path
12933
+ * needs its own copy of the frees (the function-tail scope-exit auto_free
12934
+ * serves the fall-through). Deferred frees are handled like build_auto_free.
12935
+ */
12936
+ function reclaim_all_c_scopes(status) {
12937
+ const stack = status.c_scope_stack;
12938
+ if (!stack?.length) free_scoped_declarations(status, status.scoped_declarations, true);
12939
+ else for (const frame of stack) free_scoped_declarations(status, frame, true);
12940
+ if (status.deferred_frees?.length) {
12941
+ status.code += "\n// Deferred frees\n";
12942
+ for (const d of status.deferred_frees) if (d.is_nullable) status.code += `if (${d.temp}) { ${d.struct_name}_destroy(${d.temp}); free(${d.temp}); }\n`;
12943
+ else status.code += `${d.struct_name}_destroy(${d.temp}); free(${d.temp});\n`;
12944
+ }
12945
+ }
12946
+ /**
12947
+ * Reclaim declarations from every frame between the current scope and the
12948
+ * innermost loop's body frame (inclusive), then return the loop body index.
12949
+ * Used by break/continue: the freed declarations' scope-exit auto_free either
12950
+ * runs on the (mutually exclusive) non-jump path or is dead code after the
12951
+ * jump, so this never double-frees. The innermost frame is cleared afterwards
12952
+ * so its dead post-jump auto_free emits nothing.
12953
+ */
12954
+ function reclaim_to_loop_body(status) {
12955
+ const stack = status.c_scope_stack;
12956
+ const loopDepth = status.c_loop_frame_depth;
12957
+ if (!stack?.length || !loopDepth?.length) return void 0;
12958
+ const loopBodyIdx = loopDepth[loopDepth.length - 1];
12959
+ for (let i = stack.length - 1; i >= loopBodyIdx; i--) free_scoped_declarations(status, stack[i]);
12960
+ stack[stack.length - 1].length = 0;
12961
+ return loopBodyIdx;
12962
+ }
12963
+ //#endregion
12964
+ //#region ../src/build_c/build_access_node.ts
12965
+ /**
12966
+ * The C type of a single element of a `view T` slice, used to cast the
12967
+ * universal `nomen_view.ptr` for `.at`/`.set`. `view string`'s element is a
12968
+ * `char`; every other view's element is its own type name.
12969
+ */
12970
+ function view_element_c_type(view_type, status) {
12971
+ const elem_name = view_type.name === "string" ? "char" : view_type.name;
12972
+ if (!!status.structs.find((s) => s.name === elem_name && !s.is_simple_type)) return `struct ${elem_name}`;
12973
+ return c_type(elem_name);
12974
+ }
12975
+ /**
12976
+ * Compute a C expression that yields a `struct Nursery *` for the receiver of
12977
+ * a `name.spawn(...)` escape-hatch call. A `ref Nursery` parameter is already a
12978
+ * pointer; any other Nursery lvalue (the async block's named local, etc.)
12979
+ * needs its address taken.
12980
+ */
12981
+ function nursery_pointer_expr(target, status) {
12982
+ if (target.node_type === "value") {
12983
+ const name = target.value;
12984
+ if (status.function_ref_params?.has(name)) return name;
12985
+ }
12986
+ const before = status.code.length;
12987
+ status.suppress_dereference = true;
12988
+ build_node(target, status);
12989
+ status.suppress_dereference = false;
12990
+ const expr = status.code.substring(before);
12991
+ status.code = status.code.substring(0, before);
12992
+ return "&" + expr;
12993
+ }
12994
+ /**
12995
+ * Build a node for use as a vtable dispatch target. The vtable lives at offset
12996
+ * 0 of the struct (`_vt`), so `_get_trait_func` needs a POINTER to the struct
12997
+ * (not the by-value struct). When the target is the implicit `self` parameter,
12998
+ * the build normally renames it to `_self` (the local by-value copy made at
12999
+ * function entry) — but for vtable dispatch we need the original `self` pointer
13000
+ * param, so emit it directly. A ref/trait/class param is already a pointer; any
13001
+ * other lvalue (local variable) gets its address taken. `&*x` is valid C and
13002
+ * simplifies to `x`, so a ref param that slipped through still lands on its
13003
+ * pointer.
13004
+ */
13005
+ function build_vtable_target(node, status) {
13006
+ if (node.node_type === "value") {
13007
+ const name = node.value;
13008
+ if (name === "self") {
13009
+ status.code += "self";
13010
+ return;
13011
+ }
13012
+ if (status.function_ref_params?.has(name) || status.class_vars?.has(name)) {
13013
+ status.code += c_function_name(name);
12288
13014
  return;
12289
13015
  }
12290
13016
  }
@@ -12585,7 +13311,9 @@ function build_access_node(node, status) {
12585
13311
  const specialized = status.structs.find((s) => s.name.startsWith(sname) && !s.is_generic && s.functions.find((f) => f.name === access_func.name));
12586
13312
  if (specialized) mono_struct_name = specialized.name;
12587
13313
  }
12588
- const target_method = (mono_struct_name ? status.structs.find((s) => s.name === mono_struct_name && !s.is_generic) : void 0)?.functions.find((f) => f.name === access_func.name);
13314
+ const target_struct_for_method = mono_struct_name ? status.structs.find((s) => s.name === mono_struct_name && !s.is_generic) : void 0;
13315
+ const target_method = target_struct_for_method?.functions.find((f) => f.name === access_func.name);
13316
+ if (node.target.node_type === "value" && target_struct_for_method && !target_struct_for_method.is_class && target_method) drop_self_written_string_field_records(status, node.target.value, scan_self_string_field_writes(target_struct_for_method, target_method));
12589
13317
  const self_offset = target_method?.params?.some((p) => p.is_self_param) ? 1 : 0;
12590
13318
  let trait_default_label = "";
12591
13319
  if (mono_struct_name && !access_func.mangled_name) {
@@ -12641,332 +13369,111 @@ function build_access_node(node, status) {
12641
13369
  const param = access_func.params[idx];
12642
13370
  if (param?.node_type === "value") {
12643
13371
  const vname = param.value;
12644
- const di = status.scoped_declarations.findIndex((d) => d.name === vname);
12645
- if ((di !== -1 ? status.scoped_declarations[di].type?.name : param.type?.name) === "string") continue;
12646
- if (di !== -1) status.scoped_declarations.splice(di, 1);
12647
- }
12648
- }
12649
- break;
12650
- }
12651
- }
12652
- }
12653
- function resolve_access_field_type(node, status) {
12654
- if (node.access.node_type !== "access_field") return void 0;
12655
- const field_name = node.access.name;
12656
- let base_type;
12657
- if (node.target.node_type === "value") {
12658
- const name = node.target.value;
12659
- const vtype = node.target.type;
12660
- if (vtype?.name) base_type = vtype;
12661
- else if (name === "self" && status.current_struct) base_type = new Type(status.current_struct.name);
12662
- else if (status.variable_types?.has(name)) base_type = status.variable_types.get(name);
12663
- } else if (node.target.node_type === "access") base_type = resolve_access_field_type(node.target, status);
12664
- if (!base_type?.name) return void 0;
12665
- return (status.structs.find((s) => s.name === base_type.name && !s.is_simple_type)?.fields.find((f) => f.name === field_name))?.type;
12666
- }
12667
- function emit_string_length(target, status) {
12668
- if (is_owned_heap_temp(target, status)) {
12669
- const id = status.label_counter = (status.label_counter ?? 0) + 1;
12670
- const tmp = `_slen_${id}`;
12671
- status.code += `({ char* ${tmp} = `;
12672
- build_node(target, status);
12673
- status.code += `; long _slr_${id} = (long)strlen(${tmp}); free(${tmp}); _slr_${id}; })`;
12674
- return;
12675
- }
12676
- status.code += "((long)strlen(";
12677
- build_node(target, status);
12678
- status.code += "))";
12679
- }
12680
- /**
12681
- * Resolve the type of an access-chain expression by walking through the
12682
- * monomorphized structs (field types and method return types). Used when a
12683
- * cached node type is stale (a generic type param like "T" that wasn't
12684
- * substituted because it belonged to a nested generic, not the enclosing one).
12685
- */
12686
- function resolve_access_type(node, status) {
12687
- const inner = node.access;
12688
- if (inner.node_type === "access_func") {
12689
- const access_func = inner;
12690
- let base_type = resolve_receiver_type(node.target, status);
12691
- if (!base_type?.name) return null;
12692
- const mono_name = mono_type_name(base_type);
12693
- const struct = status.structs.find((s) => s.name === mono_name && !s.is_generic) || status.structs.find((s) => s.name === base_type.name);
12694
- if (!struct) return null;
12695
- return struct.functions.find((f) => f.name === access_func.name || f.name === `#${access_func.name}`)?.return_type || null;
12696
- }
12697
- if (inner.node_type !== "access_field") return null;
12698
- const field_name = inner.name;
12699
- let base_type = resolve_receiver_type(node.target, status);
12700
- if (!base_type?.name) return null;
12701
- const struct = status.structs.find((s) => s.name === base_type.name);
12702
- if (!struct) return null;
12703
- return struct.fields.find((f) => f.name === field_name)?.type || null;
12704
- }
12705
- function resolve_receiver_type(node, status) {
12706
- if (node.node_type === "value") {
12707
- const name = node.value;
12708
- const vtype = node.type;
12709
- if (vtype?.name && status.structs.find((s) => s.name === vtype.name)) return vtype;
12710
- if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
12711
- return vtype?.name ? vtype : null;
12712
- }
12713
- if (node.node_type === "access") {
12714
- const resolved = resolve_access_type(node, status);
12715
- if (resolved) return resolved;
12716
- return type_from_value_node$1(node);
12717
- }
12718
- return null;
12719
- }
12720
- //#endregion
12721
- //#region ../src/build_c/build_array_values_node.ts
12722
- function build_array_values_node(node, status) {
12723
- status.code += `{`;
12724
- const elem_is_string = node.type?.name === "string";
12725
- node.values.forEach((value, i) => {
12726
- if (i > 0) status.code += ", ";
12727
- if (elem_is_string && value.node_type === "value" && value.value.length >= 2 && value.value.startsWith("\"") && value.value.endsWith("\"")) {
12728
- status.code += `nomen_strdup_wrap(`;
12729
- build_node(value, status);
12730
- status.code += `)`;
12731
- } else build_node(value, status);
12732
- });
12733
- status.code += `}`;
12734
- }
12735
- //#endregion
12736
- //#region ../src/build_c/utils/is_string_borrow.ts
12737
- /**
12738
- * Whether a value node denotes a BORROWED string — a pointer into storage the
12739
- * receiver does not own (an array element accessed via `.at()`/`.first()`, or
12740
- * `init.args.at(n)` which points into the C runtime's `argv`). Borrowed
12741
- * strings must NOT be freed by auto_free or by reassignment: freeing them
12742
- * reclaims memory owned by the container (or argv), crashing with
12743
- * "pointer being freed was not allocated". Mirrors aarch64's `heap_strings`
12744
- * ownership tracking, which only frees freshly-allocated strings.
12745
- */
12746
- function is_string_borrow(node) {
12747
- if (!node || node.node_type !== "access") return false;
12748
- const access = node.access;
12749
- if (access.node_type !== "access_func") return false;
12750
- const func = access;
12751
- return (func.name === "at" || func.name === "first") && !func.owned_return;
12752
- }
12753
- //#endregion
12754
- //#region ../src/build_c/build_auto_free.ts
12755
- function build_auto_free(status) {
12756
- free_scoped_declarations(status, status.scoped_declarations);
12757
- if (status.deferred_frees?.length) {
12758
- status.code += "\n// Deferred frees\n";
12759
- for (const d of status.deferred_frees) if (d.is_nullable) status.code += `if (${d.temp}) { ${d.struct_name}_destroy(${d.temp}); free(${d.temp}); }\n`;
12760
- else status.code += `${d.struct_name}_destroy(${d.temp}); free(${d.temp});\n`;
12761
- status.deferred_frees.length = 0;
12762
- }
12763
- status.scoped_declarations = [];
12764
- }
12765
- /**
12766
- * Emit scope-exit free/destroy code for a list of declarations. Extracted from
12767
- * build_auto_free so that break/continue can reclaim declarations from the
12768
- * current scope AND enclosing scopes (up to the loop body) before jumping —
12769
- * see build_break_node. Does NOT process deferred_frees or clear the list
12770
- * (those are scope-exit-only concerns handled by build_auto_free).
12771
- */
12772
- function free_scoped_declarations(status, decls) {
12773
- let commented = false;
12774
- for (const dec of decls) {
12775
- const struct = status.structs.find((s) => s.name === dec.type.name);
12776
- if (struct && struct.traits.includes("Disposable")) {
12777
- const trait = status.traits.find((t) => t.name === "Disposable");
12778
- const func = trait?.functions.find((f) => f.name == "dispose");
12779
- if (trait && func) {
12780
- if (!commented) {
12781
- status.code += "\n// Auto-free\n";
12782
- commented = true;
12783
- }
12784
- const cast = "(void *(*)(void *))";
12785
- const traitIndex = status.traits.indexOf(trait);
12786
- const funcIndex = trait.functions.indexOf(func);
12787
- status.code += `(${cast}_get_trait_func((void *)&${dec.name}, ${traitIndex}, ${funcIndex}))(&${dec.name});\n`;
12788
- }
12789
- }
12790
- const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved;
12791
- const is_borrowed_string = is_string_borrow(dec.value) || !!status.string_borrow_vars?.has(dec.name);
12792
- const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call");
12793
- const dec_value = dec.value;
12794
- const dec_val_is_string_literal = dec.value?.node_type === "value" && dec_value.value.length >= 2 && dec_value.value.startsWith("\"") && dec_value.value.endsWith("\"");
12795
- const dec_val_is_heap_string_var = dec.value?.node_type === "value" && !dec_val_is_string_literal && !!status.scoped_declarations.find((d) => d.name === dec_value.value);
12796
- const was_strdup_string_var = dec.declaration === "var" && !dec.type.is_view && !is_borrowed_string && (dec_val_is_string_literal || dec_val_is_heap_string_var);
12797
- const is_normalized_join_string = !!status.string_join_owned_vars?.has(dec.name);
12798
- const dec_struct = status.structs.find((s) => s.name === dec.type.name);
12799
- const is_class_var = !!dec_struct?.is_class;
12800
- const trait_class_trait = status.trait_class_locals?.get(dec.name);
12801
- if (trait_class_trait !== void 0 && !is_destructured_field_access) {
12802
- if (!commented) {
12803
- status.code += "\n// Auto-free\n";
12804
- commented = true;
12805
- }
12806
- if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${trait_class_trait}_destroy(${dec.name}); free(${dec.name}); }\n`;
12807
- else status.code += `${trait_class_trait}_destroy(${dec.name}); free(${dec.name});\n`;
12808
- }
12809
- if (!is_destructured_field_access && !is_borrowed_string && (!dec.type.is_static || value_is_heap_string || was_strdup_string_var || is_normalized_join_string) && dec.type.name === "string" && !dec.type.is_array) {
12810
- if (!commented) {
12811
- status.code += "\n// Auto-free\n";
12812
- commented = true;
12813
- }
12814
- status.code += `free(${dec.name});\n`;
12815
- }
12816
- if (!is_destructured_field_access && is_class_var && !dec.type.is_array) {
12817
- if (!commented) {
12818
- status.code += "\n// Auto-free\n";
12819
- commented = true;
12820
- }
12821
- const cls = struct ?? dec_struct;
12822
- const mono_cls_name = cls ? mono_type_name(dec.type) : void 0;
12823
- const has_destroy_fn = !!cls?.functions.find((f) => f.name === "#destroy") || !!cls?.is_class;
12824
- if (cls) {
12825
- const destroy_call = has_destroy_fn ? `${mono_cls_name}_destroy(${dec.name}); ` : "";
12826
- if (dec.type.is_nullable) status.code += `if (${dec.name}) { ${destroy_call}free(${dec.name}); }\n`;
12827
- else status.code += `${destroy_call}free(${dec.name});\n`;
12828
- } else status.code += `free(${dec.name});\n`;
12829
- }
12830
- if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && dec.type.name !== "string") {
12831
- const mono_name = mono_type_name(dec.type);
12832
- const struct_type = status.structs.find((s) => s.name === mono_name && !s.is_simple_type && !s.is_generic);
12833
- if (struct_type && struct_needs_destroy(struct_type, status)) {
12834
- if (!commented) {
12835
- status.code += "\n// Auto-free\n";
12836
- commented = true;
12837
- }
12838
- emit_struct_destroys(status, struct_type, dec.name);
12839
- }
12840
- }
12841
- if (!!status.traits.find((t) => t.name === dec.type.name) && !is_destructured_field_access && !dec.type.is_array && dec.value) {
12842
- const val_type = type_from_value_node$1(dec.value);
12843
- const concrete = val_type?.name ? status.structs.find((s) => s.name === val_type.name && !s.is_simple_type && !s.is_generic) : void 0;
12844
- if (concrete && struct_needs_destroy(concrete, status)) {
12845
- if (!commented) {
12846
- status.code += "\n// Auto-free\n";
12847
- commented = true;
12848
- }
12849
- emit_struct_destroys(status, concrete, dec.name);
12850
- }
12851
- }
12852
- if (!is_destructured_field_access && !is_class_var && !dec.type.is_array && is_nullable_struct_type(dec.type, status)) {
12853
- const inner = status.structs.find((s) => s.name === dec.type.name);
12854
- if (inner && struct_needs_destroy(inner, status)) {
12855
- if (!commented) {
12856
- status.code += "\n// Auto-free\n";
12857
- commented = true;
12858
- }
12859
- const body = capture_destroys(status, inner, dec.name, ".");
12860
- status.code += `if (${has_flag_name(dec.name)}) { ${body} }\n`;
12861
- }
12862
- }
12863
- if (!is_destructured_field_access && dec.type.is_array && status.heap_array_vars?.has(dec.name)) {
12864
- if (!commented) {
12865
- status.code += "\n// Auto-free\n";
12866
- commented = true;
12867
- }
12868
- const elem_name = dec.type.name;
12869
- const elem_is_class = !!status.structs.find((s) => s.name === elem_name)?.is_class;
12870
- const elem_is_string = elem_name === "string";
12871
- const elem_c_type = elem_is_class ? `struct ${elem_name}*` : elem_name;
12872
- if (elem_is_class) {
12873
- status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
12874
- status.code += `\t${elem_c_type}* _data = (${elem_c_type}*)((char*)${dec.name} + sizeof(struct Array_${elem_name}));\n`;
12875
- status.code += `\t${elem_name}_destroy(_data[_i]); free(_data[_i]);\n`;
12876
- status.code += `}\n`;
12877
- } else if (elem_is_string) {
12878
- status.code += `for (long _i = 0; _i < ${dec.name}->length; _i++) {\n`;
12879
- status.code += `\tchar** _data = (char**)((char*)${dec.name} + sizeof(struct Array_string));\n`;
12880
- status.code += `\tfree(_data[_i]);\n`;
12881
- status.code += `}\n`;
12882
- }
12883
- status.code += `free(${dec.name});\n`;
12884
- }
12885
- if (!is_destructured_field_access && dec.type.is_array && status.stack_array_vars?.has(dec.name)) {
12886
- if (!commented) {
12887
- status.code += "\n// Auto-free\n";
12888
- commented = true;
12889
- }
12890
- const elem_name = dec.type.name;
12891
- const elem_struct = status.structs.find((s) => s.name === elem_name);
12892
- const elem_is_class = !!elem_struct?.is_class;
12893
- const elem_is_string = elem_name === "string";
12894
- const elem_struct_type = status.structs.find((s) => s.name === elem_name && !s.is_simple_type && !s.is_generic);
12895
- const arr_len = status.stack_array_lengths?.get(dec.name) ?? "0";
12896
- if (elem_is_string) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
12897
- else if (elem_is_class) {
12898
- if (has_destroy(elem_struct)) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { if (${dec.name}[_i]) { ${elem_name}_destroy(${dec.name}[_i]); free(${dec.name}[_i]); } }\n`;
12899
- else status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { free(${dec.name}[_i]); }\n`;
12900
- } else if (elem_struct_type && struct_needs_destroy(elem_struct_type, status)) {
12901
- status.code += `for (long _i = 0; _i < ${arr_len}; _i++) {\n`;
12902
- emit_struct_destroys(status, elem_struct_type, `${dec.name}[_i]`);
12903
- status.code += `}\n`;
13372
+ const decl_hit = find_decl_in_c_scopes(status, vname);
13373
+ const tname = decl_hit?.frame[decl_hit.index].type?.name ?? param.type?.name;
13374
+ if (tname === "string") continue;
13375
+ const decl_struct = decl_hit ? status.structs.find((s) => s.name === tname && !s.is_simple_type) : void 0;
13376
+ const is_value_struct = !!decl_struct && !decl_struct.is_class;
13377
+ if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
13378
+ if (is_value_struct) {
13379
+ const prefix = `${vname}.`;
13380
+ for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
13381
+ if (!status.pending_string_releases) status.pending_string_releases = [];
13382
+ status.pending_string_releases.push(`free(${key});`);
13383
+ status.heap_string_fields.delete(key);
13384
+ }
13385
+ }
13386
+ }
12904
13387
  }
13388
+ break;
12905
13389
  }
12906
13390
  }
12907
13391
  }
12908
- /** Name-based variant of struct_needs_destroy for callers without the StructNode. */
12909
- function struct_needs_destroy_by_name(name, status) {
12910
- const struct = status.structs.find((s) => s.name === name && !s.is_simple_type && !s.is_generic);
12911
- if (!struct) return false;
12912
- return struct_needs_destroy(struct, status);
13392
+ function resolve_access_field_type(node, status) {
13393
+ if (node.access.node_type !== "access_field") return void 0;
13394
+ const field_name = node.access.name;
13395
+ let base_type;
13396
+ if (node.target.node_type === "value") {
13397
+ const name = node.target.value;
13398
+ const vtype = node.target.type;
13399
+ if (vtype?.name) base_type = vtype;
13400
+ else if (name === "self" && status.current_struct) base_type = new Type(status.current_struct.name);
13401
+ else if (status.variable_types?.has(name)) base_type = status.variable_types.get(name);
13402
+ } else if (node.target.node_type === "access") base_type = resolve_access_field_type(node.target, status);
13403
+ if (!base_type?.name) return void 0;
13404
+ return (status.structs.find((s) => s.name === base_type.name && !s.is_simple_type)?.fields.find((f) => f.name === field_name))?.type;
12913
13405
  }
12914
- /**
12915
- * Emit destroy calls for a struct variable at scope exit. Calls the struct's
12916
- * own `#destroy` first (if any), then walks each field: class-typed fields
12917
- * are destroyed + freed (pointer); nested struct fields are recursively
12918
- * destroyed via their own `#destroy`. Mirrors aarch64's
12919
- * `emit_destroy_for_decl` + `emit_field_destroys`.
12920
- */
12921
- function emit_struct_destroys(status, struct, var_expr) {
12922
- if (has_destroy(struct)) status.code += `${struct.name}_destroy(&${var_expr});\n`;
12923
- for (const field of struct.fields) {
12924
- if (field.type.is_ref) continue;
12925
- const field_struct = resolve_struct_type(field.type, status);
12926
- if (!field_struct) continue;
12927
- const field_expr = `${var_expr}.${field.name}`;
12928
- if (field_struct.is_class) {
12929
- if (has_destroy(field_struct)) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); }\n`;
12930
- } else if (is_nullable_struct_type(field.type, status)) {
12931
- if (struct_needs_destroy(field_struct, status)) {
12932
- const body = capture_destroys(status, field_struct, field_expr, ".");
12933
- status.code += `if (${field_expr}_has) { ${body} }\n`;
12934
- }
12935
- } else emit_struct_destroys(status, field_struct, field_expr);
13406
+ function emit_string_length(target, status) {
13407
+ if (is_owned_heap_temp(target, status)) {
13408
+ const id = status.label_counter = (status.label_counter ?? 0) + 1;
13409
+ const tmp = `_slen_${id}`;
13410
+ status.code += `({ char* ${tmp} = `;
13411
+ build_node(target, status);
13412
+ status.code += `; long _slr_${id} = (long)strlen(${tmp}); free(${tmp}); _slr_${id}; })`;
13413
+ return;
12936
13414
  }
13415
+ status.code += "((long)strlen(";
13416
+ build_node(target, status);
13417
+ status.code += "))";
12937
13418
  }
12938
13419
  /**
12939
- * Capture the destroy calls for a struct value as a single line (no trailing
12940
- * newline) so it can be embedded inside an `if (...) { ... }` guard. Uses
12941
- * `accessor` (`.` or `->`) for nested field expressions `.` for by-value
12942
- * locals/fields, `->` when the container is a class pointer.
13420
+ * Resolve the type of an access-chain expression by walking through the
13421
+ * monomorphized structs (field types and method return types). Used when a
13422
+ * cached node type is stale (a generic type param like "T" that wasn't
13423
+ * substituted because it belonged to a nested generic, not the enclosing one).
12943
13424
  */
12944
- function capture_destroys(status, struct, var_expr, accessor) {
12945
- const before = status.code.length;
12946
- if (has_destroy(struct)) status.code += `${struct.name}_destroy(&${var_expr}); `;
12947
- for (const field of struct.fields) {
12948
- if (field.type.is_ref) continue;
12949
- const field_struct = resolve_struct_type(field.type, status);
12950
- if (!field_struct) continue;
12951
- const field_expr = `${var_expr}${accessor}${field.name}`;
12952
- if (field_struct.is_class) {
12953
- if (has_destroy(field_struct)) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); } `;
12954
- } else if (is_nullable_struct_type(field.type, status)) {
12955
- if (struct_needs_destroy(field_struct, status)) {
12956
- const inner_before = status.code.length;
12957
- capture_destroys(status, field_struct, field_expr, accessor);
12958
- const inner_body = status.code.substring(inner_before).trim();
12959
- status.code = status.code.substring(0, inner_before);
12960
- status.code += `if (${field_expr}_has) { ${inner_body} } `;
12961
- }
12962
- } else capture_destroys(status, field_struct, field_expr, accessor);
13425
+ function resolve_access_type(node, status) {
13426
+ const inner = node.access;
13427
+ if (inner.node_type === "access_func") {
13428
+ const access_func = inner;
13429
+ let base_type = resolve_receiver_type(node.target, status);
13430
+ if (!base_type?.name) return null;
13431
+ const mono_name = mono_type_name(base_type);
13432
+ const struct = status.structs.find((s) => s.name === mono_name && !s.is_generic) || status.structs.find((s) => s.name === base_type.name);
13433
+ if (!struct) return null;
13434
+ return struct.functions.find((f) => f.name === access_func.name || f.name === `#${access_func.name}`)?.return_type || null;
12963
13435
  }
12964
- const captured = status.code.substring(before).replace(/\s+/g, " ").trim();
12965
- status.code = status.code.substring(0, before);
12966
- return captured;
13436
+ if (inner.node_type !== "access_field") return null;
13437
+ const field_name = inner.name;
13438
+ let base_type = resolve_receiver_type(node.target, status);
13439
+ if (!base_type?.name) return null;
13440
+ const struct = status.structs.find((s) => s.name === base_type.name);
13441
+ if (!struct) return null;
13442
+ return struct.fields.find((f) => f.name === field_name)?.type || null;
13443
+ }
13444
+ function resolve_receiver_type(node, status) {
13445
+ if (node.node_type === "value") {
13446
+ const name = node.value;
13447
+ const vtype = node.type;
13448
+ if (vtype?.name && status.structs.find((s) => s.name === vtype.name)) return vtype;
13449
+ if (name === "self" && status.current_struct) return new Type(status.current_struct.name);
13450
+ return vtype?.name ? vtype : null;
13451
+ }
13452
+ if (node.node_type === "access") {
13453
+ const resolved = resolve_access_type(node, status);
13454
+ if (resolved) return resolved;
13455
+ return type_from_value_node$1(node);
13456
+ }
13457
+ return null;
13458
+ }
13459
+ //#endregion
13460
+ //#region ../src/build_c/build_array_values_node.ts
13461
+ function build_array_values_node(node, status) {
13462
+ status.code += `{`;
13463
+ const elem_is_string = node.type?.name === "string";
13464
+ node.values.forEach((value, i) => {
13465
+ if (i > 0) status.code += ", ";
13466
+ if (elem_is_string && value.node_type === "value" && value.value.length >= 2 && value.value.startsWith("\"") && value.value.endsWith("\"")) {
13467
+ status.code += `nomen_strdup_wrap(`;
13468
+ build_node(value, status);
13469
+ status.code += `)`;
13470
+ } else build_node(value, status);
13471
+ });
13472
+ status.code += `}`;
12967
13473
  }
12968
13474
  //#endregion
12969
13475
  //#region ../src/build_c/build_assignment_node.ts
13476
+ let string_field_counter = 0;
12970
13477
  function build_assignment_node(node, status) {
12971
13478
  if (node.left_value.node_type === "access") {
12972
13479
  const accessNode = node.left_value;
@@ -13003,26 +13510,51 @@ function build_assignment_node(node, status) {
13003
13510
  status.code = status.code.substring(0, before_len);
13004
13511
  if (field_type?.is_nullable) status.code += `if (${field_access}) { ${field_struct.name}_destroy(${field_access}); free(${field_access}); }\n`;
13005
13512
  else status.code += `${field_struct.name}_destroy(${field_access}); free(${field_access});\n`;
13006
- if (node.right_value.node_type === "value") {
13007
- const rhs_name = node.right_value.value;
13008
- const rhs_idx = status.scoped_declarations.findIndex((d) => d.name === rhs_name);
13009
- if (rhs_idx !== -1) status.scoped_declarations.splice(rhs_idx, 1);
13010
- }
13513
+ if (node.right_value.node_type === "value") splice_decl_from_c_scopes(status, node.right_value.value);
13514
+ }
13515
+ }
13516
+ }
13517
+ if (!node.operator && node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field" && node.left_value.access.type?.name === "string" && !node.left_value.access.type?.is_ref && !node.left_value.access.type?.is_array) {
13518
+ const access_lhs = node.left_value;
13519
+ const field_access_node = access_lhs.access;
13520
+ const target_type = type_from_value_node$1(access_lhs.target);
13521
+ const target_struct = target_type?.name ? status.structs.find((s) => s.name === target_type.name && !s.is_simple_type) : null;
13522
+ const target_var = access_lhs.target.node_type === "value" ? access_lhs.target.value : "";
13523
+ const tracked_key = `${target_var}.${field_access_node.name}`;
13524
+ const old_was_heap = !!target_struct?.is_class || !!status.heap_string_fields?.has(tracked_key);
13525
+ if (target_struct && target_var && target_var !== "self") {
13526
+ const fresh_heap = is_owned_heap_temp(node.right_value, status);
13527
+ const before_len = status.code.length;
13528
+ build_node(node.left_value, status);
13529
+ const field_access = status.code.substring(before_len);
13530
+ status.code = status.code.substring(0, before_len);
13531
+ const temp = `_nomen_strfield_${string_field_counter++}`;
13532
+ status.code += `{\nchar* ${temp} = `;
13533
+ if (fresh_heap) build_node(node.right_value, status);
13534
+ else {
13535
+ status.code += `strdup(`;
13536
+ build_node(node.right_value, status);
13537
+ status.code += `)`;
13538
+ }
13539
+ status.code += `;\n`;
13540
+ if (old_was_heap) status.code += `free(${field_access});\n`;
13541
+ status.code += `${field_access} = ${temp};\n}\n`;
13542
+ if (!target_struct.is_class) {
13543
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
13544
+ status.heap_string_fields.add(tracked_key);
13011
13545
  }
13546
+ return;
13012
13547
  }
13013
13548
  }
13014
13549
  if (!node.operator && node.left_value.node_type === "value" && is_string_borrow(node.right_value)) {
13015
13550
  const lhs_name = node.left_value.value;
13016
- const lhs_decl = status.scoped_declarations.find((d) => d.name === lhs_name);
13017
- if ((lhs_decl?.type || status.variable_types?.get(lhs_name))?.name === "string") {
13551
+ const lhs_hit = find_decl_in_c_scopes(status, lhs_name);
13552
+ if (((lhs_hit ? lhs_hit.frame[lhs_hit.index] : void 0)?.type || status.variable_types?.get(lhs_name))?.name === "string") {
13018
13553
  const was_borrow = !!status.string_borrow_vars?.has(lhs_name);
13019
13554
  if (!status.string_borrow_vars) status.string_borrow_vars = /* @__PURE__ */ new Set();
13020
13555
  status.string_borrow_vars.add(lhs_name);
13021
13556
  if (!was_borrow) {
13022
- if (lhs_decl) {
13023
- const idx = status.scoped_declarations.indexOf(lhs_decl);
13024
- if (idx !== -1) status.scoped_declarations.splice(idx, 1);
13025
- }
13557
+ if (lhs_hit) lhs_hit.frame.splice(lhs_hit.index, 1);
13026
13558
  status.code += `free(${lhs_name});\n`;
13027
13559
  }
13028
13560
  }
@@ -13107,21 +13639,15 @@ function build_assignment_node(node, status) {
13107
13639
  }
13108
13640
  if (rhs_is_bare_value) {
13109
13641
  if (lhs_is_class) {
13110
- if (!node.swap) {
13111
- const rhs_name = rhs.value;
13112
- const rhs_idx = status.scoped_declarations.findIndex((d) => d.name === rhs_name);
13113
- if (rhs_idx !== -1) status.scoped_declarations.splice(rhs_idx, 1);
13114
- }
13115
- } else if (lhs_decl) {
13116
- const idx = status.scoped_declarations.indexOf(lhs_decl);
13117
- if (idx !== -1) status.scoped_declarations.splice(idx, 1);
13118
- }
13642
+ if (!node.swap) splice_decl_from_c_scopes(status, rhs.value);
13643
+ } else if (lhs_decl) splice_decl_from_c_scopes(status, lhs_name);
13119
13644
  }
13120
13645
  }
13121
13646
  }
13122
13647
  if (!node.operator && node.left_value.node_type === "value") {
13123
13648
  const lhs_name = node.left_value.value;
13124
- const lhs_decl = status.scoped_declarations.find((d) => d.name === lhs_name);
13649
+ const lhs_hit = find_decl_in_c_scopes(status, lhs_name);
13650
+ const lhs_decl = lhs_hit ? lhs_hit.frame[lhs_hit.index] : void 0;
13125
13651
  if (lhs_decl) {
13126
13652
  const lhs_struct = lhs_decl.type?.name ? status.structs.find((s) => s.name === lhs_decl.type.name && !s.is_simple_type && !s.is_class) : null;
13127
13653
  const lhs_mono = lhs_decl.type ? mono_type_name(lhs_decl.type) : void 0;
@@ -13131,9 +13657,7 @@ function build_assignment_node(node, status) {
13131
13657
  if (rhs.node_type === "value" && rhs.is_moved) {
13132
13658
  const mov_struct_type = lhs_mono_struct ?? lhs_struct;
13133
13659
  if (mov_struct_type && struct_needs_destroy_by_name(mov_struct_type.name, status)) emit_struct_destroys(status, mov_struct_type, lhs_name);
13134
- const rhs_name = rhs.value;
13135
- const rhs_idx = status.scoped_declarations.findIndex((d) => d.name === rhs_name);
13136
- if (rhs_idx !== -1) status.scoped_declarations.splice(rhs_idx, 1);
13660
+ splice_decl_from_c_scopes(status, rhs.value);
13137
13661
  } else {
13138
13662
  const struct_type = lhs_mono_struct ?? lhs_struct;
13139
13663
  const needs_destroy = struct_type ? struct_needs_destroy_by_name(struct_type.name, status) : false;
@@ -13142,12 +13666,8 @@ function build_assignment_node(node, status) {
13142
13666
  if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
13143
13667
  } else if (is_self_method_call(node, lhs_name)) {} else if (!rhs_references_var(node, lhs_name)) {
13144
13668
  if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
13145
- const idx = status.scoped_declarations.indexOf(lhs_decl);
13146
- if (idx !== -1) status.scoped_declarations.splice(idx, 1);
13147
- } else {
13148
- const idx = status.scoped_declarations.indexOf(lhs_decl);
13149
- if (idx !== -1) status.scoped_declarations.splice(idx, 1);
13150
- }
13669
+ if (lhs_hit) lhs_hit.frame.splice(lhs_hit.index, 1);
13670
+ } else if (lhs_hit) lhs_hit.frame.splice(lhs_hit.index, 1);
13151
13671
  }
13152
13672
  }
13153
13673
  }
@@ -13444,53 +13964,6 @@ function embedded_value_struct(type, status) {
13444
13964
  return s;
13445
13965
  }
13446
13966
  //#endregion
13447
- //#region ../src/build_c/utils/c_scope.ts
13448
- /**
13449
- * Begin a new C scope frame: allocate a fresh declarations array, push it onto
13450
- * c_scope_stack, and make it the active scoped_declarations. Returns the frame
13451
- * so the caller can assign it to status.scoped_declarations (mirroring the
13452
- * existing save/restore idiom). Pair with leave_c_scope at scope exit.
13453
- */
13454
- function enter_c_scope(status) {
13455
- const frame = [];
13456
- if (!status.c_scope_stack) status.c_scope_stack = [];
13457
- status.c_scope_stack.push(frame);
13458
- return frame;
13459
- }
13460
- /** Pop the current scope frame from c_scope_stack (scope-exit counterpart to enter_c_scope). */
13461
- function leave_c_scope(status) {
13462
- status.c_scope_stack?.pop();
13463
- }
13464
- /**
13465
- * Mark the current top frame as a loop body, so break/continue know how far up
13466
- * the scope stack to reclaim. Call AFTER entering the loop body scope.
13467
- */
13468
- function push_c_loop_frame(status) {
13469
- if (!status.c_scope_stack?.length) return;
13470
- if (!status.c_loop_frame_depth) status.c_loop_frame_depth = [];
13471
- status.c_loop_frame_depth.push(status.c_scope_stack.length - 1);
13472
- }
13473
- function pop_c_loop_frame(status) {
13474
- status.c_loop_frame_depth?.pop();
13475
- }
13476
- /**
13477
- * Reclaim declarations from every frame between the current scope and the
13478
- * innermost loop's body frame (inclusive), then return the loop body index.
13479
- * Used by break/continue: the freed declarations' scope-exit auto_free either
13480
- * runs on the (mutually exclusive) non-jump path or is dead code after the
13481
- * jump, so this never double-frees. The innermost frame is cleared afterwards
13482
- * so its dead post-jump auto_free emits nothing.
13483
- */
13484
- function reclaim_to_loop_body(status) {
13485
- const stack = status.c_scope_stack;
13486
- const loopDepth = status.c_loop_frame_depth;
13487
- if (!stack?.length || !loopDepth?.length) return void 0;
13488
- const loopBodyIdx = loopDepth[loopDepth.length - 1];
13489
- for (let i = stack.length - 1; i >= loopBodyIdx; i--) free_scoped_declarations(status, stack[i]);
13490
- stack[stack.length - 1].length = 0;
13491
- return loopBodyIdx;
13492
- }
13493
- //#endregion
13494
13967
  //#region ../src/build_c/utils/owning_buffer_specialize.ts
13495
13968
  /**
13496
13969
  * Detect whether a monomorphized struct is a `Buffer_<T>` whose element type
@@ -13853,7 +14326,11 @@ function build_struct_node(node, status) {
13853
14326
  status.code += `${object_name}${accessor}${field.name} = *${field.name};\n`;
13854
14327
  status.code += `${object_name}${accessor}${has_flag_name(field.name)} = ${has_flag_name(field.name)};\n`;
13855
14328
  } else {
14329
+ const field_is_class_string = is_class && field.type.name === "string" && !field.type.is_array && !field.type.is_ref;
14330
+ const value_is_fresh_heap = !!field.value && is_owned_heap_temp(field.value, status);
14331
+ const wrap_strdup = field_is_class_string && !value_is_fresh_heap;
13856
14332
  status.code += `${object_name}${accessor}${field.name} = `;
14333
+ if (wrap_strdup) status.code += `strdup(`;
13857
14334
  if (field.value) build_node(field.value, status);
13858
14335
  else {
13859
14336
  const field_struct = status.structs.find((s) => s.name === mono_struct_name(field.type, status) && !s.is_simple_type);
@@ -13861,6 +14338,7 @@ function build_struct_node(node, status) {
13861
14338
  if (field_struct && !field_struct.is_class || field_trait) status.code += `*`;
13862
14339
  status.code += field.name;
13863
14340
  }
14341
+ if (wrap_strdup) status.code += `)`;
13864
14342
  status.code += ";\n";
13865
14343
  }
13866
14344
  for (let traitName of node.traits) {
@@ -13869,7 +14347,10 @@ function build_struct_node(node, status) {
13869
14347
  status.code += `${object_name}${accessor}${field.name}`;
13870
14348
  if (field.value) {
13871
14349
  status.code += " = ";
14350
+ const wrap = is_class && field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !is_owned_heap_temp(field.value, status);
14351
+ if (wrap) status.code += "strdup(";
13872
14352
  build_node(field.value, status);
14353
+ if (wrap) status.code += ")";
13873
14354
  }
13874
14355
  status.code += ";\n";
13875
14356
  }
@@ -13965,6 +14446,14 @@ function build_struct_functions(node, status, skip_init = false) {
13965
14446
  } else status.function_ref_params.add(pname);
13966
14447
  }
13967
14448
  }
14449
+ for (const param of func.params) {
14450
+ if (param.is_self_param) continue;
14451
+ const param_struct = status.structs.find((s) => s.name === param.type.name);
14452
+ if (param.is_moved && param_struct?.is_class && !moved_param_is_consumed(func, param.name)) {
14453
+ const pname = c_function_name(param.name);
14454
+ status.scoped_declarations.push(new DeclarationNode(param.start, "private", "mov", pname, param.type));
14455
+ }
14456
+ }
13968
14457
  const func_start = status.code.length;
13969
14458
  let return_type = func.return_type.name || "void";
13970
14459
  if (return_type !== node.name && node.name.startsWith(return_type + "_")) return_type = node.name;
@@ -14007,6 +14496,10 @@ function build_struct_functions(node, status, skip_init = false) {
14007
14496
  }
14008
14497
  const owning_elem = owning_buffer_element(node, status);
14009
14498
  if (!(owning_elem && emit_owning_buffer_body(func.name, owning_elem, status) || owning_buffer_is_string_elem(node) && emit_owning_buffer_string_body(func.name, status))) for (let child of func.statements) build_node(child, status, true);
14499
+ if (func.name === "#destroy" && node.is_class) for (const field of node.fields) {
14500
+ if (field.type.is_ref || field.type.is_array) continue;
14501
+ if (field.type.name === "string") status.code += `free(self->${field.name});\n`;
14502
+ }
14010
14503
  build_auto_free(status);
14011
14504
  status.code += `}\n`;
14012
14505
  status.function_ref_params = old_ref_params;
@@ -14042,7 +14535,7 @@ function build_auto_destroy(node, status) {
14042
14535
  status.code += `${sig}\n{\n`;
14043
14536
  for (const field of node.fields) {
14044
14537
  if (field.type.is_ref) continue;
14045
- if (field.type.name === "string" && !field.type.is_array && !node.is_class) {
14538
+ if (field.type.name === "string" && !field.type.is_array) {
14046
14539
  status.code += `free(self->${field.name});\n`;
14047
14540
  continue;
14048
14541
  }
@@ -14226,7 +14719,7 @@ function build_function_node(node, status) {
14226
14719
  status.ref_class_param_types.set(pname, param.type);
14227
14720
  }
14228
14721
  } else if (!status.heap_array_vars?.has(pname)) status.function_ref_params.add(pname);
14229
- if (param.is_moved && param_struct?.is_class && node.name !== "main" && !param_is_consumed(node, param.name)) {
14722
+ if (param.is_moved && param_struct?.is_class && node.name !== "main" && !moved_param_is_consumed(node, param.name)) {
14230
14723
  const decl = new DeclarationNode(param.start, "private", "mov", pname, param.type);
14231
14724
  status.scoped_declarations.push(decl);
14232
14725
  }
@@ -14282,34 +14775,6 @@ function emit_nested_declarations(node, status) {
14282
14775
  for (let child of block.statements) if (is_struct_node(child)) build_struct_node(child, status);
14283
14776
  for (let child of block.statements) if (is_function_node(child)) build_function_node(child, status);
14284
14777
  }
14285
- function param_is_consumed(root, name) {
14286
- let consumed = false;
14287
- const refs_name = (n) => !!n && n.node_type === "value" && n.value === name;
14288
- const walk = (n) => {
14289
- if (!n || typeof n !== "object" || consumed) return;
14290
- if (n.node_type === "func_call") {
14291
- for (const p of n.params ?? []) if (refs_name(p)) consumed = true;
14292
- }
14293
- if (n.node_type === "access") {
14294
- if (n.access?.node_type === "access_func" && refs_name(n.target)) consumed = true;
14295
- for (const p of n.access?.params ?? []) if (refs_name(p)) consumed = true;
14296
- }
14297
- if (n.node_type === "array") {
14298
- for (const v of n.values ?? []) if (refs_name(v)) consumed = true;
14299
- }
14300
- if (n.node_type === "return" && refs_name(n.value)) consumed = true;
14301
- if (n.node_type === "assign" && refs_name(n.right_value)) consumed = true;
14302
- if (n.node_type === "declare" && refs_name(n.value)) consumed = true;
14303
- for (const key of Object.keys(n)) {
14304
- if (key === "node_type") continue;
14305
- const v = n[key];
14306
- if (Array.isArray(v)) for (const item of v) walk(item);
14307
- else if (v && typeof v === "object") walk(v);
14308
- }
14309
- };
14310
- for (const stmt of root.statements ?? []) walk(stmt);
14311
- return consumed;
14312
- }
14313
14778
  //#endregion
14314
14779
  //#region ../src/build_c/utils/emit_allocations.ts
14315
14780
  /**
@@ -14812,7 +15277,8 @@ function build_declaration_node(node, status) {
14812
15277
  return;
14813
15278
  }
14814
15279
  const val_is_owned_return = node.value?.node_type === "access" && node.value.access.node_type === "access_func" && !!node.value.access.owned_return;
14815
- const val_is_class_alias = is_class_type && (node.value?.node_type === "value" && !!status.class_vars?.has(node.value.value) || node.value?.node_type === "access" && !val_is_owned_return);
15280
+ const val_is_borrowing_call = node.value?.node_type === "func_call" && !!status.borrow_returning_functions?.has(node.value.name);
15281
+ const val_is_class_alias = is_class_type && (node.value?.node_type === "value" && !!status.class_vars?.has(node.value.value) || val_is_borrowing_call || node.value?.node_type === "access" && !val_is_owned_return);
14816
15282
  const val_is_string_literal = node.value?.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"");
14817
15283
  const is_borrow_only_string = node.type.name === "string" && val_is_string_literal && (node.declaration === "const" || node.declaration === "var" && !!status.c_borrow_only_strings?.has(safe_name));
14818
15284
  if (is_borrow_only_string) {
@@ -14840,11 +15306,7 @@ function build_declaration_node(node, status) {
14840
15306
  }
14841
15307
  }
14842
15308
  }
14843
- if (node.value?.node_type === "value" && node.value.is_moved && !is_class_type) {
14844
- const src_name = node.value.value;
14845
- const src_idx = status.scoped_declarations.findIndex((d) => d.name === src_name);
14846
- if (src_idx !== -1) status.scoped_declarations.splice(src_idx, 1);
14847
- }
15309
+ if (node.value?.node_type === "value" && node.value.is_moved && !is_class_type) splice_decl_from_c_scopes(status, node.value.value);
14848
15310
  if (node.type?.name) {
14849
15311
  if (!status.variable_types) status.variable_types = /* @__PURE__ */ new Map();
14850
15312
  status.variable_types.set(safe_name, node.type);
@@ -15293,9 +15755,20 @@ function build_function_call_node(node, status) {
15293
15755
  const param = node.params[idx];
15294
15756
  if (param?.node_type === "value") {
15295
15757
  const vname = param.value;
15296
- const di = status.scoped_declarations.findIndex((d) => d.name === vname);
15297
- if ((di !== -1 ? status.scoped_declarations[di].type?.name : param.type?.name) === "string") continue;
15298
- if (di !== -1) status.scoped_declarations.splice(di, 1);
15758
+ const decl_hit = find_decl_in_c_scopes(status, vname);
15759
+ const tname = decl_hit?.frame[decl_hit.index].type?.name ?? param.type?.name;
15760
+ if (tname === "string") continue;
15761
+ const decl_struct = decl_hit ? status.structs.find((s) => s.name === tname && !s.is_simple_type) : void 0;
15762
+ const is_value_struct = !!decl_struct && !decl_struct.is_class;
15763
+ if (decl_hit) decl_hit.frame.splice(decl_hit.index, 1);
15764
+ if (is_value_struct) {
15765
+ const prefix = `${vname}.`;
15766
+ for (const key of Array.from(status.heap_string_fields ?? [])) if (key.startsWith(prefix)) {
15767
+ if (!status.pending_string_releases) status.pending_string_releases = [];
15768
+ status.pending_string_releases.push(`free(${key});`);
15769
+ status.heap_string_fields.delete(key);
15770
+ }
15771
+ }
15299
15772
  if (!status.moved) status.moved = /* @__PURE__ */ new Set();
15300
15773
  status.moved.add(vname);
15301
15774
  }
@@ -15487,7 +15960,7 @@ function build_return_node(node, status) {
15487
15960
  const ret_is_null = returns_nullable_struct && (!node.value || node.value.node_type === "value" && node.value.value === "null");
15488
15961
  if (returns_nullable_struct) {
15489
15962
  if (ret_is_null) {
15490
- build_auto_free(status);
15963
+ reclaim_all_c_scopes(status);
15491
15964
  status.code += `*${ret_has} = 0;\n`;
15492
15965
  status.code += `return (struct ${status.function_return_type.name}){0};\n`;
15493
15966
  return;
@@ -15495,7 +15968,7 @@ function build_return_node(node, status) {
15495
15968
  status.code += `*${ret_has} = 1;\n`;
15496
15969
  }
15497
15970
  if (!node.value) {
15498
- build_auto_free(status);
15971
+ reclaim_all_c_scopes(status);
15499
15972
  if (status.return_assign) status.code += `${status.return_assign} = 0;\n`;
15500
15973
  else if (status.current_function_name?.toLocaleLowerCase() === "main") status.code += `return 0;\n`;
15501
15974
  else status.code += `return;\n`;
@@ -15526,8 +15999,18 @@ function build_return_node(node, status) {
15526
15999
  if (node.value.node_type === "value") {
15527
16000
  const value = node.value.value;
15528
16001
  returned_value_decl = find_decl_across_scopes(value, status);
15529
- let di = status.scoped_declarations.indexOf(returned_value_decl);
15530
- if (di !== -1) status.scoped_declarations.splice(di, 1);
16002
+ const frames = [status.scoped_declarations, ...status.c_scope_stack ?? []];
16003
+ for (const frame of frames) {
16004
+ const di = frame.indexOf(returned_value_decl);
16005
+ if (di !== -1) {
16006
+ frame.splice(di, 1);
16007
+ break;
16008
+ }
16009
+ }
16010
+ if (status.heap_string_fields?.size) {
16011
+ const prefix = `${value}.`;
16012
+ for (const key of Array.from(status.heap_string_fields)) if (key.startsWith(prefix)) status.heap_string_fields.delete(key);
16013
+ }
15531
16014
  }
15532
16015
  if (ret_type?.is_array && return_array_var && return_array_len > 0) {
15533
16016
  const elem_name = ret_type.name;
@@ -15537,7 +16020,7 @@ function build_return_node(node, status) {
15537
16020
  status.code += `_return_val->length = ${return_array_len};\n`;
15538
16021
  status.code += `${elem_c_type}* _return_data = (${elem_c_type}*)((char*)_return_val + sizeof(struct ${array_struct}));\n`;
15539
16022
  status.code += `for (long _i = 0; _i < ${return_array_len}; _i++) _return_data[_i] = ${return_array_var}[_i];\n`;
15540
- build_auto_free(status);
16023
+ reclaim_all_c_scopes(status);
15541
16024
  status.code += `return _return_val;\n`;
15542
16025
  return;
15543
16026
  }
@@ -15547,7 +16030,7 @@ function build_return_node(node, status) {
15547
16030
  status.code += `${old_return_assign} = `;
15548
16031
  build_node(node.value, status);
15549
16032
  status.code += `;\n`;
15550
- build_auto_free(status);
16033
+ reclaim_all_c_scopes(status);
15551
16034
  } else {
15552
16035
  emit_allocations(node.value, status);
15553
16036
  const ret_type = status.function_return_type || node.type;
@@ -15568,7 +16051,7 @@ function build_return_node(node, status) {
15568
16051
  build_node(node.value, status);
15569
16052
  status.join_needs_owned_string = old_join_owned;
15570
16053
  status.return_assign = old_return_assign;
15571
- build_auto_free(status);
16054
+ reclaim_all_c_scopes(status);
15572
16055
  if (string_join) status.code += any_branch_owned ? `return _return_val;\n` : `return strdup(_return_val);\n`;
15573
16056
  else status.code += `return _return_val;\n`;
15574
16057
  return;
@@ -15616,7 +16099,7 @@ function build_return_node(node, status) {
15616
16099
  if (returns_borrowed_string || returns_string_literal || returns_borrow_var) status.code += `)`;
15617
16100
  status.code += `;\n`;
15618
16101
  if (node.value.node_type === "func_call" && node.value.field_overrides?.length) emit_field_overrides("_return_val", node.value, build_node, status, "", ";\n");
15619
- build_auto_free(status);
16102
+ reclaim_all_c_scopes(status);
15620
16103
  status.code += `return _return_val;\n`;
15621
16104
  }
15622
16105
  }
@@ -15974,6 +16457,95 @@ function build_node(node, status, with_semicolon = false) {
15974
16457
  }
15975
16458
  if (with_semicolon) {
15976
16459
  if (!status.code.endsWith("}\n")) status.code += ";\n";
16460
+ if (status.pending_string_releases?.length) {
16461
+ status.code += status.pending_string_releases.join("\n") + "\n";
16462
+ status.pending_string_releases.length = 0;
16463
+ }
16464
+ }
16465
+ }
16466
+ //#endregion
16467
+ //#region ../src/build_common/scan_borrow_returns.ts
16468
+ /**
16469
+ * Functions (and methods) whose CLASS-typed return value is a BORROWED
16470
+ * reference — e.g. `func box_at = (List<Box> xs, int i, out Box) { var Box
16471
+ * got = xs.at(j); return mov got }` hands back the container's element, not a
16472
+ * fresh instance. A caller-side declaration initialized from such a call must
16473
+ * NOT be destroy-tracked (the callee's owner frees it) — mirroring the
16474
+ * syntactic borrow rules at declaration sites (field access / non-`mov out`
16475
+ * method call).
16476
+ *
16477
+ * Syntactic, build-time: a return of a local whose initializer is a field
16478
+ * access or a non-owned-return method call (`.at()`, `.first()`, …) marks the
16479
+ * enclosing function. Mixed functions (some returns fresh, some borrowed) are
16480
+ * classified as borrowing — never freeing is safe (worst case a leak), while
16481
+ * the opposite risks a double-free.
16482
+ */
16483
+ function scan_borrow_returning_functions(root) {
16484
+ const statements = root.statements ?? [];
16485
+ const class_type_names = /* @__PURE__ */ new Set();
16486
+ const result = /* @__PURE__ */ new Set();
16487
+ walk(statements, (n) => {
16488
+ if (n.node_type === "struct" && n.is_class) class_type_names.add(n.name);
16489
+ }, true, true);
16490
+ walk(statements, (n) => {
16491
+ if (n.node_type === "struct") for (const f of n.functions ?? []) scan_func(f, n.name, result, class_type_names);
16492
+ else if (n.node_type === "func") scan_func(n, void 0, result, class_type_names);
16493
+ });
16494
+ return result;
16495
+ }
16496
+ /**
16497
+ * Visit every AST node reachable from `value` — through arrays AND
16498
+ * single-node properties (an `if` node's branch blocks are node objects, not
16499
+ * statement arrays) — skipping `parent`/`scope` back-references. By default
16500
+ * does NOT descend INTO nested `func`/`struct`/`trait` declarations (a
16501
+ * function's own body must not leak into its enclosing function's
16502
+ * classification); `descend_boundaries` walks them too (used by the
16503
+ * class-name gather, which must see classes declared anywhere, including
16504
+ * inside the synthetic wrapper `main`).
16505
+ */
16506
+ function walk(value, cb, top = true, descend_boundaries = false) {
16507
+ if (!value || typeof value !== "object") return;
16508
+ if (Array.isArray(value)) {
16509
+ for (const item of value) walk(item, cb, false, descend_boundaries);
16510
+ return;
16511
+ }
16512
+ const n = value;
16513
+ const is_boundary = (n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait") && !top;
16514
+ if (typeof n.node_type === "string") cb(n);
16515
+ if (is_boundary && !descend_boundaries) return;
16516
+ for (const key of Object.keys(value)) {
16517
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
16518
+ walk(value[key], cb, false, descend_boundaries);
16519
+ }
16520
+ }
16521
+ function scan_func(func, struct_name, result, class_type_names) {
16522
+ walk(func.statements ?? [], (n) => {
16523
+ if (n.node_type === "func") scan_func(n, void 0, result, class_type_names);
16524
+ else if (n.node_type === "struct") for (const f of n.functions ?? []) scan_func(f, n.name, result, class_type_names);
16525
+ });
16526
+ if (!func.return_type?.name || !class_type_names.has(func.return_type.name)) return;
16527
+ const borrowed_locals = /* @__PURE__ */ new Set();
16528
+ let returns_borrowed = false;
16529
+ walk(func.statements ?? [], (n) => {
16530
+ if (n.node_type === "declare") {
16531
+ const decl = n;
16532
+ if (!decl.type?.name || !class_type_names.has(decl.type.name)) return;
16533
+ const value = decl.value;
16534
+ if (!value || value.node_type !== "access") return;
16535
+ const access = value;
16536
+ if (access.access.node_type === "access_field") borrowed_locals.add(decl.name);
16537
+ else if (access.access.node_type === "access_func") {
16538
+ if (!access.access.owned_return) borrowed_locals.add(decl.name);
16539
+ }
16540
+ } else if (n.node_type === "return") {
16541
+ const value = n.value;
16542
+ if (value && value.node_type === "value" && borrowed_locals.has(value.value)) returns_borrowed = true;
16543
+ }
16544
+ });
16545
+ if (returns_borrowed) {
16546
+ const sanitized = func.name.replace(/#/g, "");
16547
+ const label = struct_name ? `${struct_name}_${sanitized}` : sanitized;
16548
+ result.add(label);
15977
16549
  }
15978
16550
  }
15979
16551
  //#endregion
@@ -16018,6 +16590,7 @@ function build(root, options = {}) {
16018
16590
  reset_inline_counter();
16019
16591
  reset_decl_const_counters();
16020
16592
  status.heap_returning_functions = scan_heap_returning_functions(root);
16593
+ status.borrow_returning_functions = scan_borrow_returning_functions(root);
16021
16594
  status.inline_functions = scan_inline_candidates(root);
16022
16595
  status.heap_returning_functions.add("int_to_string");
16023
16596
  status.heap_returning_functions.add("uint_to_string");
@@ -16129,9 +16702,13 @@ function build(root, options = {}) {
16129
16702
  }
16130
16703
  if (options.audit) {
16131
16704
  status.code = status.code.replaceAll("bl _malloc\n", "bl _nomen_malloc_wrap\n");
16705
+ status.code = status.code.replaceAll("bl _calloc\n", "bl _nomen_calloc_wrap\n");
16706
+ status.code = status.code.replaceAll("bl _realloc\n", "bl _nomen_realloc_wrap\n");
16707
+ status.code = status.code.replaceAll("bl _strdup\n", "bl _nomen_strdup_wrap\n");
16132
16708
  status.code = status.code.replaceAll("bl _free\n", "bl _nomen_free_wrap\n");
16133
16709
  }
16134
16710
  } else {
16711
+ status.borrow_returning_functions = scan_borrow_returning_functions(root);
16135
16712
  set_c_typedef_mangling(build_needs_objc(root, status.platform));
16136
16713
  build_node(root, status);
16137
16714
  status.code = `typedef struct { void* ptr; long len; } nomen_view;\n` + status.code;
@@ -24361,7 +24938,9 @@ function check_return_node(ret, status) {
24361
24938
  }
24362
24939
  }
24363
24940
  if (func && borrow_depth_of(ret.value, status) !== void 0) {
24364
- if (!(!!func.return_type?.is_view && borrow_owner_of(ret.value, status) === "self")) add_error(status, `cannot return a borrowed reference — use 'mov' (with swap) to transfer ownership`, ret.value.start);
24941
+ const safe_view_from_self = !!func.return_type?.is_view && borrow_owner_of(ret.value, status) === "self";
24942
+ const explicit_mov = !!get_inner_value_node(ret.value)?.is_moved;
24943
+ if (!safe_view_from_self && !explicit_mov) add_error(status, `cannot return a borrowed reference — use 'mov' (with swap) to transfer ownership`, ret.value.start);
24365
24944
  }
24366
24945
  if (func) {
24367
24946
  if (func.return_type.name) {
@@ -28006,7 +28585,7 @@ function compile_audit_runtime(config, input_path, buildDir) {
28006
28585
  return audit_obj;
28007
28586
  }
28008
28587
  function watchPath(p, config, mode, program_args) {
28009
- chokidar.watch(p).on("all", (event, filePath) => {
28588
+ chokidar_default.watch(p).on("all", (event, filePath) => {
28010
28589
  if (shouldProcessFile(filePath)) processFile(filePath, config, mode, program_args);
28011
28590
  });
28012
28591
  }