nomen-lang 0.6.5 → 0.6.6

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 +795 -97
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -7546,7 +7546,7 @@ function scan_self_string_field_writes(struct, method) {
7546
7546
  const scan_method = (func) => {
7547
7547
  if (visited.has(func.name)) return;
7548
7548
  visited.add(func.name);
7549
- walk$3(func.statements ?? [], (n) => {
7549
+ walk$4(func.statements ?? [], (n) => {
7550
7550
  if (n.node_type === "assign") {
7551
7551
  const lhs = n.left_value;
7552
7552
  if (lhs?.node_type !== "access") return;
@@ -7590,10 +7590,10 @@ function drop_self_written_string_field_records(status, receiver_name, fields) {
7590
7590
  * statement arrays) — skipping `parent`/`scope` back-references and NOT
7591
7591
  * descending INTO nested `func`/`struct`/`trait` declarations (a nested
7592
7592
  * function's body is a separate function, not part of this one's writes). */
7593
- function walk$3(value, cb) {
7593
+ function walk$4(value, cb) {
7594
7594
  if (!value || typeof value !== "object") return;
7595
7595
  if (Array.isArray(value)) {
7596
- for (const item of value) walk$3(item, cb);
7596
+ for (const item of value) walk$4(item, cb);
7597
7597
  return;
7598
7598
  }
7599
7599
  const n = value;
@@ -7602,7 +7602,177 @@ function walk$3(value, cb) {
7602
7602
  if (is_boundary) return;
7603
7603
  for (const key of Object.keys(value)) {
7604
7604
  if (key === "parent" || key === "scope" || key === "node_type") continue;
7605
- walk$3(value[key], cb);
7605
+ walk$4(value[key], cb);
7606
+ }
7607
+ }
7608
+ //#endregion
7609
+ //#region ../src/build_common/has_string_fields.ts
7610
+ /**
7611
+ * Whether any field (recursively, through nested owning value structs)
7612
+ * is an owned `string`. Shared by BOTH backends' owning-Buffer element
7613
+ * specialization (the slot deep-copies strdup'd strings, so a
7614
+ * string-field element is owning even when `struct_needs_destroy`
7615
+ * ignores string-only locals) — the C comment in
7616
+ * owning_buffer_specialize records the divergence this once caused.
7617
+ * `ref` and `view T` fields are skipped: a ref aliases the caller's
7618
+ * storage and a view's byte copy owns nothing.
7619
+ */
7620
+ function has_string_fields(node, status) {
7621
+ for (const field of node.fields) {
7622
+ if (field.type.is_ref) continue;
7623
+ if (field.type.is_view) continue;
7624
+ if (field.type.name === "string" && !field.type.is_array) return true;
7625
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
7626
+ if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
7627
+ }
7628
+ return false;
7629
+ }
7630
+ /**
7631
+ * The DIRECT `string` fields of a value struct (nested struct fields are not
7632
+ * descended into — nested string fields are outside the per-field ownership
7633
+ * tracking everywhere else too). Used by the return-boundary normalization:
7634
+ * the returned struct's unrecorded fields are strdup'd so the value the
7635
+ * caller receives is uniformly heap-owned.
7636
+ */
7637
+ function direct_string_fields(node) {
7638
+ return node.fields.filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string");
7639
+ }
7640
+ //#endregion
7641
+ //#region ../src/build_common/scan_ref_string_writes.ts
7642
+ /**
7643
+ * The plain `string` fields of `func`'s `ref` value-struct parameters that the
7644
+ * function stores through with a DIRECT top-level statement — transitively
7645
+ * through top-level calls that forward the same param as their own `ref`
7646
+ * argument (callees resolved via the checker's `resolved_function` stamps).
7647
+ *
7648
+ * Both backends' string-field store paths strdup every non-heap RHS into an
7649
+ * owned copy before storing (a field is never a borrow once stored), so a
7650
+ * definitely-executed store leaves the field heap-owned. That is the fact a
7651
+ * CALL SITE transfers onto the caller's `heap_string_fields` records (the
7652
+ * add-direction mirror of `drop_self_written_string_field_records`): the
7653
+ * callee's own records are scope-local and die at return, so without the
7654
+ * transfer the stored copy leaks.
7655
+ *
7656
+ * Only DIRECT statements qualify: a store nested in a branch/loop may not
7657
+ * execute, and a record over the pre-call value would free it at the caller's
7658
+ * scope exit (possibly rodata — an invalid free). Memoized on the function
7659
+ * node; forwarding cycles are cut by a visited set (the cycle member's own
7660
+ * contribution is skipped, bounded-leak).
7661
+ */
7662
+ function scan_ref_param_string_field_writes(func, structs) {
7663
+ const stamp = func;
7664
+ if (stamp._ref_param_string_field_writes) return stamp._ref_param_string_field_writes;
7665
+ const result = /* @__PURE__ */ new Map();
7666
+ stamp._ref_param_string_field_writes = result;
7667
+ const tracked = /* @__PURE__ */ new Map();
7668
+ for (const param of func.params ?? []) {
7669
+ if (param.is_self_param) continue;
7670
+ if (!(param.is_ref || param.type.is_ref)) continue;
7671
+ if (param.type.is_array || param.type.is_view) continue;
7672
+ const fields = ref_param_writable_fields(param.type.name, structs);
7673
+ if (fields.size) tracked.set(param.name, fields);
7674
+ }
7675
+ if (tracked.size) scan_function(func, tracked, structs, result, /* @__PURE__ */ new Set());
7676
+ return result;
7677
+ }
7678
+ /** The writable plain string fields of the value struct `type_name` resolves
7679
+ * to, or the empty set (non-struct, class, or no string fields). */
7680
+ function ref_param_writable_fields(type_name, structs) {
7681
+ if (!type_name) return /* @__PURE__ */ new Set();
7682
+ const struct = structs.find((s) => s.name === type_name && !s.is_simple_type && !s.is_class);
7683
+ if (!struct) return /* @__PURE__ */ new Set();
7684
+ return new Set(direct_string_fields(struct).map((f) => f.name));
7685
+ }
7686
+ function scan_function(func, tracked, structs, result, visiting) {
7687
+ visiting.add(func);
7688
+ for (const stmt of func.statements ?? []) {
7689
+ if (stmt.node_type === "assign") {
7690
+ const lhs = stmt.left_value;
7691
+ if (lhs?.node_type === "access") {
7692
+ const access = lhs;
7693
+ if (access.access.node_type === "access_field" && access.target.node_type === "value") {
7694
+ const name = access.target.value ?? "";
7695
+ const fields = tracked.get(name);
7696
+ const field = access.access.name ?? "";
7697
+ if (fields?.has(field)) {
7698
+ if (!result.has(name)) result.set(name, /* @__PURE__ */ new Set());
7699
+ result.get(name).add(field);
7700
+ }
7701
+ }
7702
+ }
7703
+ continue;
7704
+ }
7705
+ const call = ref_forward_call(stmt);
7706
+ if (!call) continue;
7707
+ const [callee, args] = call;
7708
+ const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
7709
+ for (let i = 0; i < args.length && i < non_self_params.length; i++) {
7710
+ const arg = args[i];
7711
+ if (arg?.node_type !== "value") continue;
7712
+ const arg_name = arg.value ?? "";
7713
+ const my_fields = tracked.get(arg_name);
7714
+ if (!my_fields) continue;
7715
+ const callee_param = non_self_params[i];
7716
+ if (!(callee_param.is_ref || callee_param.type.is_ref)) continue;
7717
+ if (visiting.has(callee)) continue;
7718
+ const callee_writes = scan_ref_param_string_field_writes(callee, structs).get(callee_param.name);
7719
+ if (!callee_writes?.size) continue;
7720
+ if (!result.has(arg_name)) result.set(arg_name, /* @__PURE__ */ new Set());
7721
+ const mine = result.get(arg_name);
7722
+ for (const field of callee_writes) if (my_fields.has(field)) mine.add(field);
7723
+ }
7724
+ }
7725
+ visiting.delete(func);
7726
+ }
7727
+ /**
7728
+ * The forwarded-call shape of a top-level statement: a resolved free-function
7729
+ * call or method call, with its argument list (the receiver is NOT included).
7730
+ */
7731
+ function ref_forward_call(stmt) {
7732
+ if (stmt.node_type === "func_call") {
7733
+ const call = stmt;
7734
+ if (!call.resolved_function) return void 0;
7735
+ return [call.resolved_function, call.params ?? []];
7736
+ }
7737
+ if (stmt.node_type === "access") {
7738
+ const access = stmt;
7739
+ if (access.access.node_type !== "access_func") return void 0;
7740
+ const func_node = access.access.resolved_function;
7741
+ if (!func_node) return void 0;
7742
+ return [func_node, access.access.params ?? []];
7743
+ }
7744
+ }
7745
+ //#endregion
7746
+ //#region ../src/build_common/transfer_ref_param_records.ts
7747
+ /**
7748
+ * The add-direction mirror of drop_self_written_string_field_records: at a
7749
+ * call site whose callee takes a `ref` value-struct parameter, the callee's
7750
+ * string-field stores write through to the CALLER's variable — but the
7751
+ * callee's heap_string_fields records are scope-local and die at return, so
7752
+ * the stored copy would leak. Transfer the callee's definitely-executed
7753
+ * stores (scan_ref_param_string_field_writes) onto the caller's records for
7754
+ * the argument variable, making the caller's scope exit free them.
7755
+ *
7756
+ * `arg_is_trackable_local` must report true only for variables whose storage
7757
+ * THIS function's scope exit owns — a plain local. A `ref` param of the
7758
+ * enclosing function forwards caller storage, so a record here would free the
7759
+ * ORIGINAL caller's field at the wrong scope's exit.
7760
+ */
7761
+ function transfer_ref_param_field_records(callee, args, status, arg_is_trackable_local) {
7762
+ const writes = scan_ref_param_string_field_writes(callee, status.structs);
7763
+ if (!writes.size) return;
7764
+ const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
7765
+ for (let i = 0; i < args.length && i < non_self_params.length; i++) {
7766
+ const param = non_self_params[i];
7767
+ if (!(param.is_ref || param.type.is_ref)) continue;
7768
+ const fields = writes.get(param.name);
7769
+ if (!fields?.size) continue;
7770
+ const arg = args[i];
7771
+ if (arg?.node_type !== "value") continue;
7772
+ const name = arg.value;
7773
+ if (!name || !arg_is_trackable_local(name)) continue;
7774
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
7775
+ for (const field of fields) status.heap_string_fields.add(`${name}.${field}`);
7606
7776
  }
7607
7777
  }
7608
7778
  //#endregion
@@ -7943,13 +8113,14 @@ function emit_field_overrides(var_name, value, build, status, declaration_termin
7943
8113
  const overrides = overrides_of(value);
7944
8114
  if (!overrides.length) return;
7945
8115
  status.code += declaration_terminator;
7946
- const carrier_type = var_name.startsWith("_return_val") ? void 0 : value.type;
8116
+ const carrier_type = var_name.startsWith("_return_val") || var_name.startsWith("_temp_") ? void 0 : value.type;
7947
8117
  for (const override of overrides) {
7948
8118
  const target = new ValueNode(override.value.start, var_name);
7949
8119
  if (carrier_type) target.type = carrier_type;
7950
8120
  const access_field = new AccessFieldNode(override.value.start, override.name, override.type);
7951
8121
  const access = new AccessNode(override.value.start, target, access_field);
7952
8122
  const assign = new AssignmentNode(override.value.start, access, override.value);
8123
+ if (!carrier_type) assign.raw_field_store = true;
7953
8124
  status.code += "\n";
7954
8125
  status.c_field_override_depth = (status.c_field_override_depth ?? 0) + 1;
7955
8126
  build(assign, status);
@@ -8073,38 +8244,6 @@ function hoist_field_overrides(value, build, status, declaration_terminator = ""
8073
8244
  carrier._fov_hoisted = true;
8074
8245
  }
8075
8246
  //#endregion
8076
- //#region ../src/build_common/has_string_fields.ts
8077
- /**
8078
- * Whether any field (recursively, through nested owning value structs)
8079
- * is an owned `string`. Shared by BOTH backends' owning-Buffer element
8080
- * specialization (the slot deep-copies strdup'd strings, so a
8081
- * string-field element is owning even when `struct_needs_destroy`
8082
- * ignores string-only locals) — the C comment in
8083
- * owning_buffer_specialize records the divergence this once caused.
8084
- * `ref` and `view T` fields are skipped: a ref aliases the caller's
8085
- * storage and a view's byte copy owns nothing.
8086
- */
8087
- function has_string_fields(node, status) {
8088
- for (const field of node.fields) {
8089
- if (field.type.is_ref) continue;
8090
- if (field.type.is_view) continue;
8091
- if (field.type.name === "string" && !field.type.is_array) return true;
8092
- const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
8093
- if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
8094
- }
8095
- return false;
8096
- }
8097
- /**
8098
- * The DIRECT `string` fields of a value struct (nested struct fields are not
8099
- * descended into — nested string fields are outside the per-field ownership
8100
- * tracking everywhere else too). Used by the return-boundary normalization:
8101
- * the returned struct's unrecorded fields are strdup'd so the value the
8102
- * caller receives is uniformly heap-owned.
8103
- */
8104
- function direct_string_fields(node) {
8105
- return node.fields.filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string");
8106
- }
8107
- //#endregion
8108
8247
  //#region ../src/build_common/string_return_analysis.ts
8109
8248
  const EMPTY_SET = /* @__PURE__ */ new Set();
8110
8249
  /**
@@ -8468,6 +8607,11 @@ function is_owned_string_branch_value(node, table) {
8468
8607
  */
8469
8608
  function is_normalized_struct_call(value, status) {
8470
8609
  if (!value) return false;
8610
+ if (value.node_type === "anon_struct") {
8611
+ const base = value.base;
8612
+ if (!base) return false;
8613
+ value = base;
8614
+ }
8471
8615
  if (value.node_type === "func_call") {
8472
8616
  const call = value;
8473
8617
  return !!status.normalized_struct_returners?.has(call.name);
@@ -8511,6 +8655,175 @@ function record_call_init_string_fields(node, status) {
8511
8655
  for (const field of fields) status.heap_string_fields.add(`${node.name}.${field.name}`);
8512
8656
  }
8513
8657
  //#endregion
8658
+ //#region ../src/build_common/init_computed_seeds.ts
8659
+ function is_plain_string_field(field) {
8660
+ return field.type.name === "string" && !field.type.is_ref && !field.type.is_array && !field.type.is_view;
8661
+ }
8662
+ /** The `(field, rhs)` of a `self.<field> = <rhs>` write, or undefined. */
8663
+ function self_string_field_write(node, string_fields) {
8664
+ if (node.node_type !== "assign") return void 0;
8665
+ const lhs = node.left_value;
8666
+ if (lhs?.node_type !== "access") return void 0;
8667
+ const access = lhs;
8668
+ if (access.access.node_type !== "access_field") return void 0;
8669
+ const target = access.target;
8670
+ if (target?.node_type !== "value" || target.value !== "self") return void 0;
8671
+ const field = access.access.name ?? "";
8672
+ if (!string_fields.has(field)) return void 0;
8673
+ return {
8674
+ field,
8675
+ rhs: node.right_value
8676
+ };
8677
+ }
8678
+ /** Visit every AST node reachable from `value` — through arrays AND
8679
+ * single-node properties — skipping `parent`/`scope` back-references and NOT
8680
+ * descending INTO nested `func`/`struct`/`trait` declarations. */
8681
+ function walk$3(value, cb) {
8682
+ if (!value || typeof value !== "object") return;
8683
+ if (Array.isArray(value)) {
8684
+ for (const item of value) walk$3(item, cb);
8685
+ return;
8686
+ }
8687
+ const n = value;
8688
+ const is_boundary = n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait";
8689
+ if (typeof n.node_type === "string") cb(n);
8690
+ if (is_boundary) return;
8691
+ for (const key of Object.keys(value)) {
8692
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
8693
+ walk$3(value[key], cb);
8694
+ }
8695
+ }
8696
+ function collect_init_string_writes(struct_node, init) {
8697
+ const string_fields = new Set(struct_node.fields.filter(is_plain_string_field).map((f) => f.name));
8698
+ const top_level = /* @__PURE__ */ new Map();
8699
+ const nested = /* @__PURE__ */ new Map();
8700
+ const add = (m, field, rhs) => {
8701
+ const list = m.get(field);
8702
+ if (list) list.push(rhs);
8703
+ else m.set(field, [rhs]);
8704
+ };
8705
+ for (const stmt of init.statements ?? []) {
8706
+ const write = self_string_field_write(stmt, string_fields);
8707
+ if (write) {
8708
+ add(top_level, write.field, write.rhs);
8709
+ continue;
8710
+ }
8711
+ walk$3(stmt, (n) => {
8712
+ const nested_write = self_string_field_write(n, string_fields);
8713
+ if (nested_write) add(nested, nested_write.field, nested_write.rhs);
8714
+ });
8715
+ }
8716
+ return {
8717
+ top_level,
8718
+ nested
8719
+ };
8720
+ }
8721
+ /**
8722
+ * The plain string fields of `struct_node` that its custom `#init` COMPUTES as
8723
+ * a definitely-heap-owning value: for EVERY `#init` overload, the field is
8724
+ * either written at least once as a DIRECT body statement with every write
8725
+ * (direct or nested) passing `is_owned_heap` — or never written and its
8726
+ * DEFAULT also passes `is_owned_heap` (the overload leaves the default in
8727
+ * place). A param-alias seed (`self.a = s`) never qualifies: the seed borrows
8728
+ * the caller's argument temp, so it must never be recorded or freed.
8729
+ *
8730
+ * Binding sites record qualifying fields (scope exit frees the computed seed;
8731
+ * a displaced store reclaims it); override sites treat them as displaced (the
8732
+ * raw override store orphans the computed buffer). A param-alias seed or a
8733
+ * conditional computed write over a rodata default leaves a path holding a
8734
+ * non-heap value — such fields stay unqualified (bounded leak on the write
8735
+ * path, never an invalid free).
8736
+ */
8737
+ function init_computed_heap_string_fields(struct_node, is_owned_heap) {
8738
+ const result = /* @__PURE__ */ new Set();
8739
+ const inits = struct_node.functions.filter((f) => f.name === "#init" && f.has_body);
8740
+ if (!inits.length) return result;
8741
+ for (const field of struct_node.fields) {
8742
+ if (!is_plain_string_field(field)) continue;
8743
+ const default_is_heap = !!field.value && is_owned_heap(field.value);
8744
+ let written_somewhere = false;
8745
+ let qualifies = true;
8746
+ for (const init of inits) {
8747
+ const writes = collect_init_string_writes(struct_node, init);
8748
+ const direct = writes.top_level.get(field.name) ?? [];
8749
+ const all = [...direct, ...writes.nested.get(field.name) ?? []];
8750
+ if (all.length === 0) {
8751
+ if (!default_is_heap) {
8752
+ qualifies = false;
8753
+ break;
8754
+ }
8755
+ continue;
8756
+ }
8757
+ written_somewhere = true;
8758
+ if (!all.every((expr) => is_owned_heap(expr))) {
8759
+ qualifies = false;
8760
+ break;
8761
+ }
8762
+ if (direct.length === 0 && !default_is_heap) {
8763
+ qualifies = false;
8764
+ break;
8765
+ }
8766
+ }
8767
+ if (qualifies && written_somewhere) result.add(field.name);
8768
+ }
8769
+ return result;
8770
+ }
8771
+ /**
8772
+ * The write-site half: does THIS custom-`#init` write to `self.<field>`
8773
+ * displace a heap-owning DEFAULT seed that must be reclaimed ahead of the
8774
+ * store? True only for the field's first write in the body (the displaced
8775
+ * value is then statically the default — later writes displace values whose
8776
+ * ownership the init cannot know, and keep the status quo). Marks the field
8777
+ * reclaimed on true so a second write never frees what the first stored.
8778
+ *
8779
+ * Sound even for conditional first writes: the free rides in the same branch
8780
+ * as the store, and the displaced value is still the default at that point.
8781
+ */
8782
+ function claim_init_default_seed_reclaim(status, field_name, is_owned_heap) {
8783
+ if (status.current_function?.name !== "#init" || !status.current_struct) return false;
8784
+ if (status.init_default_reclaimed_fields?.has(field_name)) return false;
8785
+ const field = status.current_struct.fields.find((f) => f.name === field_name);
8786
+ if (!field || !field.value || !is_plain_string_field(field)) return false;
8787
+ if (!is_owned_heap(field.value)) return false;
8788
+ if (!status.init_default_reclaimed_fields) status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
8789
+ status.init_default_reclaimed_fields.add(field_name);
8790
+ return true;
8791
+ }
8792
+ //#endregion
8793
+ //#region ../src/build_common/ctor_heap_default_fields.ts
8794
+ /**
8795
+ * Record the string fields of `struct_node` whose value after construction
8796
+ * owns heap (per the backend's `is_owned_heap_temp`): either the DEFAULT is a
8797
+ * non-literal heap expression the constructor evaluates fresh at every
8798
+ * construction, or a custom `#init` COMPUTES the field's value
8799
+ * (`init_computed_heap_string_fields` — every direct `self.<field> = <expr>`
8800
+ * write stores a heap-owning expression, which also displaces the default).
8801
+ * Recording at the ctor-binding site makes scope exit free the seeded value,
8802
+ * and a displaced store (an override, a reassignment) reclaim it via the
8803
+ * record's old_was_heap check.
8804
+ *
8805
+ * `is_owned_heap` classifies the seed EXPRESSION per backend: both treat a
8806
+ * string-typed `op` as fresh heap; for calls the C backend's blanket strdup
8807
+ * makes every non-borrow string return owned, while the aarch64 backend
8808
+ * requires the heap-returning classification (a literal-returning user
8809
+ * function leaves rodata in the field — never recorded, never freed).
8810
+ *
8811
+ * `skip` names fields whose seed is reclaimed elsewhere (the C backend's
8812
+ * explicit displaced free ahead of an override store). Value structs only —
8813
+ * a class's string fields are always heap and freed by its destroy.
8814
+ */
8815
+ function record_ctor_heap_default_fields(var_name, struct_node, status, is_owned_heap, skip) {
8816
+ const computed = init_computed_heap_string_fields(struct_node, is_owned_heap);
8817
+ for (const field of struct_node.fields) {
8818
+ if (field.type.name !== "string" || field.type.is_array) continue;
8819
+ if (field.type.is_ref || field.type.is_view) continue;
8820
+ if (skip?.has(field.name)) continue;
8821
+ if (!(!!field.value && is_owned_heap(field.value)) && !computed.has(field.name)) continue;
8822
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
8823
+ status.heap_string_fields.add(`${var_name}.${field.name}`);
8824
+ }
8825
+ }
8826
+ //#endregion
8514
8827
  //#region ../src/nodes/child_nodes.ts
8515
8828
  /** Whether `value` is an AST node (carries a string `node_type`). */
8516
8829
  function is_ast_node(value) {
@@ -8853,6 +9166,7 @@ function scan_function_literal_element_moves(func) {
8853
9166
  return count;
8854
9167
  }
8855
9168
  function walk_stmt(walk, node) {
9169
+ if (node.allocations?.length) for (const alloc of node.allocations) walk_stmt(walk, alloc);
8856
9170
  switch (node.node_type) {
8857
9171
  case "declare": {
8858
9172
  const decl = node;
@@ -14891,10 +15205,21 @@ function emit_string_field_strdups_at(status, struct_name, base_reg, base_offset
14891
15205
  const off = base_offset + get_field_offset(struct_name, field.name, status);
14892
15206
  emit_asm(status, `str ${base_reg}, [sp, #-16]!\n`);
14893
15207
  emit_string_pair_load_at(status, base_reg, off);
14894
- emit_strdup_string(status);
14895
- emit_asm(status, `mov x13, x0\n`);
14896
- emit_asm(status, `ldr ${base_reg}, [sp], #16\n`);
14897
- emit_pair_store_to(status, base_reg, off, "x13", "x1");
15208
+ if (field.type.is_nullable) {
15209
+ const skip_label = `.Lskip_nullable_strdup_${status.label_counter = (status.label_counter ?? 0) + 1}`;
15210
+ emit_asm(status, `mov x13, x0\n`);
15211
+ emit_asm(status, `cbz x0, ${skip_label}\n`);
15212
+ emit_strdup_string(status);
15213
+ emit_asm(status, `mov x13, x0\n`);
15214
+ emit_asm(status, `${skip_label}:\n`);
15215
+ emit_asm(status, `ldr ${base_reg}, [sp], #16\n`);
15216
+ emit_pair_store_to(status, base_reg, off, "x13", "x1");
15217
+ } else {
15218
+ emit_strdup_string(status);
15219
+ emit_asm(status, `mov x13, x0\n`);
15220
+ emit_asm(status, `ldr ${base_reg}, [sp], #16\n`);
15221
+ emit_pair_store_to(status, base_reg, off, "x13", "x1");
15222
+ }
14898
15223
  }
14899
15224
  }
14900
15225
  //#endregion
@@ -19441,6 +19766,12 @@ function plan_nir_registers(func, nir, options) {
19441
19766
  });
19442
19767
  }
19443
19768
  }
19769
+ const loop_carry_regs = /* @__PURE__ */ new Set();
19770
+ for (const [key, f] of facts) {
19771
+ if (!f.loop_blocked) continue;
19772
+ const reg = allocs.get(key);
19773
+ if (reg) loop_carry_regs.add(reg);
19774
+ }
19444
19775
  return {
19445
19776
  allocs,
19446
19777
  callee_saved,
@@ -19449,7 +19780,8 @@ function plan_nir_registers(func, nir, options) {
19449
19780
  source_keys,
19450
19781
  sites: site_allocs,
19451
19782
  pairs: slp_pairs,
19452
- region_free
19783
+ region_free,
19784
+ loop_carry_regs
19453
19785
  };
19454
19786
  }
19455
19787
  /**
@@ -19510,6 +19842,7 @@ function seed_function_allocations(func, status, options) {
19510
19842
  }]));
19511
19843
  if (status.nir_region_free.size === 0) status.nir_region_free = void 0;
19512
19844
  status.region_preseed = void 0;
19845
+ status.nir_loop_carry_regs = plan.loop_carry_regs.size > 0 ? plan.loop_carry_regs : void 0;
19513
19846
  for (const param of func.params) {
19514
19847
  const reg = plan.allocs.get(param.name);
19515
19848
  if (!reg || !reg.startsWith("x")) continue;
@@ -19633,6 +19966,8 @@ function build_function_node$1(node, status) {
19633
19966
  status.heap_class_arrays = void 0;
19634
19967
  const old_heap_array_vars = status.heap_array_vars;
19635
19968
  status.heap_array_vars = void 0;
19969
+ const old_heap_string_fields = status.heap_string_fields;
19970
+ status.heap_string_fields = void 0;
19636
19971
  const old_heap_cleanup_stack = status.heap_cleanup_stack;
19637
19972
  status.heap_cleanup_stack = void 0;
19638
19973
  const old_trait_class_frames = status.trait_class_frames;
@@ -19860,11 +20195,13 @@ function build_function_node$1(node, status) {
19860
20195
  }]));
19861
20196
  if (status.nir_region_free.size === 0) status.nir_region_free = void 0;
19862
20197
  status.region_preseed = void 0;
20198
+ status.nir_loop_carry_regs = plan.loop_carry_regs.size > 0 ? plan.loop_carry_regs : void 0;
19863
20199
  } else {
19864
20200
  fn_allocs = plan_function_promotions(node, plan_nir);
19865
20201
  status.nir_site_allocs = void 0;
19866
20202
  status.nir_region_free = void 0;
19867
20203
  status.region_preseed = void 0;
20204
+ status.nir_loop_carry_regs = void 0;
19868
20205
  }
19869
20206
  } else status.nir_site_allocs = void 0;
19870
20207
  status.register_allocations = fn_allocs && fn_allocs.size > 0 ? fn_allocs : void 0;
@@ -20245,6 +20582,7 @@ function build_function_node$1(node, status) {
20245
20582
  }
20246
20583
  status.scoped_declarations = old_scoped_declarations;
20247
20584
  status.moved = old_moved;
20585
+ status.heap_string_fields = old_heap_string_fields;
20248
20586
  status.heap_strings = old_heap_strings;
20249
20587
  status.heap_string_arrays = old_heap_string_arrays;
20250
20588
  status.heap_owned_string_arrays = old_heap_owned_string_arrays;
@@ -20358,6 +20696,41 @@ function init_nullable_field_default(node, field, base_reg, status) {
20358
20696
  emit_asm(status, `str x9, [${base_reg}, #${has_offset}]\n`);
20359
20697
  return true;
20360
20698
  }
20699
+ /**
20700
+ * Initialize a field whose default is a non-literal EXPRESSION (a string op
20701
+ * like `"de" + "fault"`, a call returning a non-struct, a grouped expr, …):
20702
+ * evaluate it and store the result into the field slot. x0 carries the
20703
+ * result — a fat string as the (x0, x1) pair, a by-value struct as the sret
20704
+ * temp's address. A class's plain string field is strdup'd when the result
20705
+ * is not itself fresh heap (the field is freed unconditionally at destroy);
20706
+ * a value struct's heap-owning default is stored raw — the ctor-binding
20707
+ * sites record it (record_ctor_heap_default_fields) so scope exit frees it.
20708
+ */
20709
+ function init_expr_field_default(node, field, offset, base_reg, status) {
20710
+ build_node(field.value, status);
20711
+ ensure_newline(status);
20712
+ const t = field.type;
20713
+ if (t.name === "string" && !t.is_ref && !t.is_view && !t.is_array) {
20714
+ if (node.is_class && !status.last_result_is_heap) emit_strdup_string(status);
20715
+ emit_pair_store_to(status, base_reg, offset, "x0", "x1");
20716
+ return;
20717
+ }
20718
+ const field_struct = status.structs.find((s) => s.name === t.name && !s.is_simple_type);
20719
+ if (field_struct?.is_class) {
20720
+ emit_typed_store(status, "x0", base_reg, offset, 8);
20721
+ return;
20722
+ }
20723
+ if (field_struct) {
20724
+ const field_size = get_struct_size(t.name, status);
20725
+ const words = Math.ceil(field_size / 8);
20726
+ for (let w = 0; w < words; w++) {
20727
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20728
+ emit_asm(status, `str x9, [${base_reg}, #${offset + w * 8}]\n`);
20729
+ }
20730
+ return;
20731
+ }
20732
+ emit_typed_store(status, "x0", base_reg, offset, get_type_size(t, status));
20733
+ }
20361
20734
  /** Store the scalar in x0 into `[base_reg + offset]` at its natural width. */
20362
20735
  function emit_scalar_store_from_x0(offset, type, base_reg, status) {
20363
20736
  const name = type.name;
@@ -20783,19 +21156,16 @@ function build_init_function(node, status) {
20783
21156
  }
20784
21157
  }
20785
21158
  emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
20786
- } else if (field.value.node_type === "func_call") {
20787
- const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
20788
- if (field_struct) {
20789
- build_node(field.value, status);
20790
- ensure_newline(status);
20791
- if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
20792
- else {
20793
- const field_size = get_struct_size(field.type.name, status);
20794
- const words = Math.ceil(field_size / 8);
20795
- for (let w = 0; w < words; w++) {
20796
- emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20797
- emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20798
- }
21159
+ } else if (field.value.node_type === "func_call" && status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
21160
+ build_node(field.value, status);
21161
+ ensure_newline(status);
21162
+ if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type).is_class) emit_typed_store(status, "x0", "x19", offset, 8);
21163
+ else {
21164
+ const field_size = get_struct_size(field.type.name, status);
21165
+ const words = Math.ceil(field_size / 8);
21166
+ for (let w = 0; w < words; w++) {
21167
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
21168
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20799
21169
  }
20800
21170
  }
20801
21171
  } else if (field.value.node_type === "func" && field.type.name === "func") {
@@ -20806,7 +21176,7 @@ function build_init_function(node, status) {
20806
21176
  ensure_newline(status);
20807
21177
  emit_descriptor_address(status, "x1", materialize_lambda_descriptor_a64(field.value, status));
20808
21178
  emit_typed_store(status, "x1", "x19", offset, 8);
20809
- }
21179
+ } else init_expr_field_default(node, field, offset, "x19", status);
20810
21180
  }
20811
21181
  for (const trait_name of node.traits) {
20812
21182
  const trait = status.traits.find((t) => t.name === trait_name);
@@ -21026,6 +21396,10 @@ function build_custom_init_function(node, func, status) {
21026
21396
  status.current_function = func;
21027
21397
  const old_init_assigned_fields = status.init_assigned_fields;
21028
21398
  status.init_assigned_fields = /* @__PURE__ */ new Set();
21399
+ const old_init_default_reclaimed = status.init_default_reclaimed_fields;
21400
+ status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
21401
+ const old_init_heap_string_fields = status.heap_string_fields;
21402
+ status.heap_string_fields = void 0;
21029
21403
  emit_asm(status, `str xzr, [x19]\n`);
21030
21404
  if (node.traits.length > 0) {
21031
21405
  emit_asm(status, `adrp x9, _${node.name}_traits@PAGE\n`);
@@ -21083,22 +21457,19 @@ function build_custom_init_function(node, func, status) {
21083
21457
  }
21084
21458
  }
21085
21459
  emit_typed_store(status, "x1", "x19", offset, get_type_size(field.type, status));
21086
- } else if (field.value.node_type === "func_call") {
21087
- const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type);
21088
- if (field_struct) {
21089
- build_node(field.value, status);
21090
- ensure_newline(status);
21091
- if (field_struct.is_class) emit_typed_store(status, "x0", "x19", offset, 8);
21092
- else {
21093
- const field_size = get_struct_size(field.type.name, status);
21094
- const words = Math.ceil(field_size / 8);
21095
- for (let w = 0; w < words; w++) {
21096
- emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
21097
- emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
21098
- }
21460
+ } else if (field.value.node_type === "func_call" && status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
21461
+ build_node(field.value, status);
21462
+ ensure_newline(status);
21463
+ if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type).is_class) emit_typed_store(status, "x0", "x19", offset, 8);
21464
+ else {
21465
+ const field_size = get_struct_size(field.type.name, status);
21466
+ const words = Math.ceil(field_size / 8);
21467
+ for (let w = 0; w < words; w++) {
21468
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
21469
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
21099
21470
  }
21100
21471
  }
21101
- }
21472
+ } else init_expr_field_default(node, field, offset, "x19", status);
21102
21473
  }
21103
21474
  status.buffer_data_cache = void 0;
21104
21475
  status.array_ptr_cache = void 0;
@@ -21108,6 +21479,8 @@ function build_custom_init_function(node, func, status) {
21108
21479
  status.raw_param_reloads = old_raw_param_reloads;
21109
21480
  status.current_function = old_current_function;
21110
21481
  status.init_assigned_fields = old_init_assigned_fields;
21482
+ status.init_default_reclaimed_fields = old_init_default_reclaimed;
21483
+ status.heap_string_fields = old_init_heap_string_fields;
21111
21484
  }
21112
21485
  const init_claims = status.callee_saved_regs_used;
21113
21486
  const init_loop_regs = init_claims ? [...init_claims].sort() : [];
@@ -21814,6 +22187,8 @@ function collect_allocations(node, options) {
21814
22187
  case "func_call": {
21815
22188
  const call = node;
21816
22189
  for (const param of call.params) result.push(...collect_allocations(param, options));
22190
+ const base = node.base;
22191
+ if (base) result.push(...collect_allocations(base, options));
21817
22192
  break;
21818
22193
  }
21819
22194
  case "access": {
@@ -21861,6 +22236,12 @@ function collect_allocations(node, options) {
21861
22236
  result.push(...collect_allocations(range.right_value, options));
21862
22237
  break;
21863
22238
  }
22239
+ case "anon_struct": {
22240
+ const anon = node;
22241
+ if (anon.base) result.push(...collect_allocations(anon.base, options));
22242
+ for (const field of anon.fields) result.push(...collect_allocations(field.value, options));
22243
+ break;
22244
+ }
21864
22245
  case "array": {
21865
22246
  const arr = node;
21866
22247
  for (const val of arr.values) result.push(...collect_allocations(val, options));
@@ -23893,6 +24274,8 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23893
24274
  if (outgoing > 0) emit_asm(status, `add sp, sp, #${outgoing}\n`);
23894
24275
  if (func_call.move_param_indices?.length) for (const idx of func_call.move_param_indices) mark_moved_if_struct(func_call.params[idx], status);
23895
24276
  build_swap_params(func_call, status);
24277
+ const ctor_struct = status.structs.find((s) => s.name === func_call.name && !s.is_simple_type && !s.is_class);
24278
+ if (ctor_struct) record_ctor_heap_default_fields(node.name, ctor_struct, status, (seed) => is_owned_heap_temp(seed, status));
23896
24279
  } 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) {
23897
24280
  const old_buffer = status.struct_return_buffer;
23898
24281
  const old_buffer_var = status.struct_return_buffer_var;
@@ -24770,14 +25153,18 @@ const INT_CALLER_SAVED_EXT = [
24770
25153
  function can_share_claimed_register(status, name, reg) {
24771
25154
  const shared = status.nir_alloc_shared;
24772
25155
  if (!shared || !status.register_allocations) return false;
25156
+ if (!shared.source_keys.has(name)) return false;
24773
25157
  if (status.region_pinned?.get(reg)) return false;
24774
- const cand_keys = shared.source_keys.get(name) ?? [name];
25158
+ if (status.nir_loop_carry_regs?.has(reg)) return false;
25159
+ const cand_keys = shared.source_keys.get(name);
24775
25160
  let occupants = 0;
24776
25161
  for (const [occupant, occupant_reg] of status.register_allocations) {
24777
25162
  if (occupant_reg !== reg || occupant === name) continue;
24778
25163
  occupants++;
24779
25164
  if (shared.pinned.has(occupant)) return false;
24780
- for (const occ_key of shared.source_keys.get(occupant) ?? [occupant]) for (const cand_key of cand_keys) if (shared.adj.get(cand_key)?.has(occ_key)) return false;
25165
+ const occ_keys = shared.source_keys.get(occupant);
25166
+ if (!occ_keys) return false;
25167
+ for (const occ_key of occ_keys) for (const cand_key of cand_keys) if (shared.adj.get(cand_key)?.has(occ_key)) return false;
24781
25168
  }
24782
25169
  return occupants > 0;
24783
25170
  }
@@ -24954,6 +25341,7 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
24954
25341
  else used_x.add(r);
24955
25342
  if (status.nir_caller_saved_claimed) for (const r of status.nir_caller_saved_claimed) used_x.add(r);
24956
25343
  if (status.region_pinned) for (const r of status.region_pinned.keys()) used_x.add(r);
25344
+ const loop_carry_regs = status.nir_loop_carry_regs;
24957
25345
  const site_regs = /* @__PURE__ */ new Set();
24958
25346
  if (status.nir_site_allocs) for (const s of status.nir_site_allocs.values()) site_regs.add(s.reg);
24959
25347
  const float_pool = options?.call_free === true ? [...FLOAT_CALLEE_SAVED, ...FLOAT_CALLER_SAVED_EXT] : FLOAT_CALLEE_SAVED;
@@ -24977,6 +25365,10 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
24977
25365
  d_idx++;
24978
25366
  continue;
24979
25367
  }
25368
+ if (loop_carry_regs?.has(reg0) || loop_carry_regs?.has(reg1)) {
25369
+ d_idx++;
25370
+ continue;
25371
+ }
24980
25372
  if (used_d.has(reg0)) {
24981
25373
  if (!can_share_claimed_register(status, v.name, reg0)) {
24982
25374
  d_idx++;
@@ -25024,6 +25416,10 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
25024
25416
  d_idx++;
25025
25417
  continue;
25026
25418
  }
25419
+ if (loop_carry_regs?.has(float_pool[d_idx])) {
25420
+ d_idx++;
25421
+ continue;
25422
+ }
25027
25423
  if (!used_d.has(float_pool[d_idx])) break;
25028
25424
  if (can_share_claimed_register(status, v.name, float_pool[d_idx])) break;
25029
25425
  d_idx++;
@@ -25046,6 +25442,10 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
25046
25442
  x_idx++;
25047
25443
  continue;
25048
25444
  }
25445
+ if (loop_carry_regs?.has(x_pool[x_idx])) {
25446
+ x_idx++;
25447
+ continue;
25448
+ }
25049
25449
  if (!used_x.has(x_pool[x_idx])) break;
25050
25450
  if (can_share_claimed_register(status, v.name, x_pool[x_idx])) break;
25051
25451
  x_idx++;
@@ -25357,23 +25757,33 @@ function build_if_else_node$1(node, status, nir) {
25357
25757
  ensure_newline(status);
25358
25758
  const pre_cache = status.buffer_data_cache;
25359
25759
  const pre_array_cache = status.array_ptr_cache;
25760
+ const pre_heap_string_fields = status.heap_string_fields;
25360
25761
  if (node.else_branch) {
25361
25762
  const then_frame = enter_scope_frame(status);
25362
25763
  status.buffer_data_cache = new Map(pre_cache);
25363
25764
  status.array_ptr_cache = new Map(pre_array_cache);
25765
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25364
25766
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
25767
+ const then_fields = status.heap_string_fields;
25365
25768
  exit_scope_frame(status, then_frame);
25366
25769
  emit_asm(status, `b end_${label}\n`);
25367
25770
  emit_asm(status, `else_${label}:\n`);
25368
25771
  const else_frame = enter_scope_frame(status);
25369
25772
  status.buffer_data_cache = new Map(pre_cache);
25370
25773
  status.array_ptr_cache = new Map(pre_array_cache);
25774
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25371
25775
  build_block_with_cursor$1(node.else_branch, nir?.else_branch, status);
25776
+ const else_fields = status.heap_string_fields;
25372
25777
  exit_scope_frame(status, else_frame);
25778
+ const merged = new Set(pre_heap_string_fields ?? []);
25779
+ for (const key of then_fields ?? []) merged.add(key);
25780
+ for (const key of else_fields ?? []) merged.add(key);
25781
+ status.heap_string_fields = merged;
25373
25782
  } else if (node.if_branch) {
25374
25783
  const then_frame = enter_scope_frame(status);
25375
25784
  status.buffer_data_cache = new Map(pre_cache);
25376
25785
  status.array_ptr_cache = new Map(pre_array_cache);
25786
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25377
25787
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
25378
25788
  exit_scope_frame(status, then_frame);
25379
25789
  }
@@ -25541,6 +25951,95 @@ function ctor_return_owned_string_fields(return_struct, call, heap_strings) {
25541
25951
  }
25542
25952
  return skip;
25543
25953
  }
25954
+ /** The OVERRIDE value of a string field, when the field is overridden. */
25955
+ function override_value_for_field(overrides, field_name) {
25956
+ return overrides?.find((o) => o.name === field_name)?.value;
25957
+ }
25958
+ /**
25959
+ * The skip set for an OVERRIDE return (`return [ .. <base>, f = v, ... ]` in
25960
+ * its checked forms: a ctor call with `field_overrides`, or a base-bearing
25961
+ * anonymous struct literal). The overrides were applied to the return temp
25962
+ * BEFORE this analysis runs, so an overridden field holds the OVERRIDE
25963
+ * value's pair: it stays raw only when that value owns heap (a transferred
25964
+ * heap local, a fresh non-borrow expression). A non-overridden field keeps
25965
+ * the base's value: with `base_uniformly_owned` (a forwarded registered
25966
+ * normalizing base) it transfers raw; otherwise it follows `base_skip` (the
25967
+ * plain constructor-argument ownership analysis).
25968
+ */
25969
+ function override_return_string_fields(return_struct, overrides, heap_strings, opts) {
25970
+ const skip = /* @__PURE__ */ new Set();
25971
+ for (const field of direct_string_fields(return_struct)) {
25972
+ const override = override_value_for_field(overrides, field.name);
25973
+ if (override) {
25974
+ if (tuple_return_owned_element(override, heap_strings)) skip.add(field.name);
25975
+ } else if (opts.base_uniformly_owned || opts.base_skip?.has(field.name)) skip.add(field.name);
25976
+ }
25977
+ return skip;
25978
+ }
25979
+ /**
25980
+ * The overridden string fields whose CONSTRUCTOR-SEEDED value owns heap and
25981
+ * is therefore displaced un-freed by a raw override store: a non-literal
25982
+ * default expression (the auto-constructor evaluates it fresh per
25983
+ * construction — `var string a = "de" + "fault"` allocates) is owned by the
25984
+ * constructed struct alone, so an override replacing the field orphans the
25985
+ * buffer unless it is reclaimed ahead of the store. Literal defaults are
25986
+ * rodata (never freed) and value-node defaults are consts/borrows (not
25987
+ * owned); a custom `#init`'s `self.<field> = <param>` seeds alias the
25988
+ * caller's argument temps in the common case, but a COMPUTED seed
25989
+ * (`self.a = s + "!"`, classified by `is_init_seed_heap`) owns heap — those
25990
+ * fields are displaced too.
25991
+ */
25992
+ function override_displaced_string_fields(struct_node, overrides, is_init_seed_heap) {
25993
+ if (!overrides?.length) return [];
25994
+ const computed = init_computed_heap_string_fields(struct_node, is_init_seed_heap);
25995
+ const displaced = [];
25996
+ for (const override of overrides) {
25997
+ const field = struct_node.fields.find((f) => f.name === override.name);
25998
+ if (!field) continue;
25999
+ if (field.type.name !== "string" || field.type.is_array || field.type.is_view) continue;
26000
+ if (computed.has(override.name)) {
26001
+ displaced.push(override.name);
26002
+ continue;
26003
+ }
26004
+ const seed = field.value;
26005
+ if (!seed || seed.node_type === "value") continue;
26006
+ if (is_string_borrow(seed)) continue;
26007
+ displaced.push(override.name);
26008
+ }
26009
+ return displaced;
26010
+ }
26011
+ /**
26012
+ * The aarch64 counterpart of override_displaced_string_fields: the
26013
+ * overridden string fields whose ctor-seeded value is a non-literal
26014
+ * expression that produced a HEAP value — a non-literal DEFAULT, or a
26015
+ * custom `#init`'s computed seed (`init_computed_heap_string_fields` under
26016
+ * the same `is_owned_heap` classifier). The extra `is_owned_heap` check
26017
+ * (the aarch64 is_owned_heap_temp) matters because the aarch64 backend does
26018
+ * NOT strdup every string return: a literal-returning user function leaves
26019
+ * RODATA in the seeded field, which must never be freed. Used at the
26020
+ * expression-temp override boundaries (no heap_string_fields records exist
26021
+ * there, so the stores stay raw and the seed needs an explicit reclaim).
26022
+ */
26023
+ function ctor_heap_displaced_string_fields(struct_node, overrides, is_owned_heap) {
26024
+ if (!overrides?.length) return [];
26025
+ const computed = init_computed_heap_string_fields(struct_node, is_owned_heap);
26026
+ const displaced = [];
26027
+ for (const override of overrides) {
26028
+ const field = struct_node.fields.find((f) => f.name === override.name);
26029
+ if (!field) continue;
26030
+ if (field.type.name !== "string" || field.type.is_array || field.type.is_view) continue;
26031
+ if (computed.has(override.name)) {
26032
+ displaced.push(override.name);
26033
+ continue;
26034
+ }
26035
+ const seed = field.value;
26036
+ if (!seed || seed.node_type === "value") continue;
26037
+ if (is_string_borrow(seed)) continue;
26038
+ if (!is_owned_heap(seed)) continue;
26039
+ displaced.push(override.name);
26040
+ }
26041
+ return displaced;
26042
+ }
25544
26043
  //#endregion
25545
26044
  //#region ../src/build_aarch64/build_return_node.ts
25546
26045
  let temp_counter$1 = 0;
@@ -25723,7 +26222,7 @@ function build_return_node$1(node, status, nir_value) {
25723
26222
  if (node.value?.node_type === "anon_struct") {
25724
26223
  const anon = node.value;
25725
26224
  const struct_size = get_struct_size(anon.type.name, status);
25726
- const temp_name = `_anon_ret_${temp_counter$1++}`;
26225
+ const temp_name = `_return_val_anon_${temp_counter$1++}`;
25727
26226
  const offset = allocate_stack_space(status, struct_size);
25728
26227
  status.stack_offsets.set(temp_name, offset);
25729
26228
  if (!status.variable_types) status.variable_types = /* @__PURE__ */ new Map();
@@ -25733,6 +26232,14 @@ function build_return_node$1(node, status, nir_value) {
25733
26232
  emit_var_address(status, "x1", temp_name);
25734
26233
  emit_struct_copy("x0", "x1", 0, struct_size, status);
25735
26234
  if (anon.base.is_moved) mark_moved_if_struct(anon.base, status);
26235
+ if (anon.base.node_type === "func_call" && is_normalized_struct_call(anon.base, status)) {
26236
+ const base_struct = status.structs.find((s) => s.name === anon.type.name && !s.is_simple_type && !s.is_class);
26237
+ if (base_struct) for (const field of direct_string_fields(base_struct)) {
26238
+ if (!anon.fields.some((o) => o.name === field.name)) continue;
26239
+ emit_asm(status, `ldp x0, x1, [x29, #${offset + get_field_offset(base_struct.name, field.name, status)}]\n`);
26240
+ emit_asm(status, `bl _nomen_free_wrap\n`);
26241
+ }
26242
+ }
25736
26243
  emit_field_overrides(temp_name, anon, build_node, status);
25737
26244
  emit_var_address(status, "x0", temp_name);
25738
26245
  } else {
@@ -25768,7 +26275,7 @@ function build_return_node$1(node, status, nir_value) {
25768
26275
  if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
25769
26276
  } else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
25770
26277
  else if (node.value.node_type === "access" && node.value.access.node_type === "access_field") needs_borrow_strdup = true;
25771
- else if (status.current_function_is_closure && (node.value.node_type === "func_call" || node.value.node_type === "access" && node.value.access.node_type === "access_func") && !status.last_result_is_heap && !is_container_borrow_access(node.value)) needs_borrow_strdup = true;
26278
+ else if ((node.value.node_type === "func_call" || node.value.node_type === "access" && node.value.access.node_type === "access_func") && !status.last_result_is_heap && !is_container_borrow_access(node.value)) needs_borrow_strdup = true;
25772
26279
  else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
25773
26280
  }
25774
26281
  if (needs_borrow_strdup) {
@@ -25777,6 +26284,15 @@ function build_return_node$1(node, status, nir_value) {
25777
26284
  ensure_newline(status);
25778
26285
  status.last_result_is_heap = true;
25779
26286
  }
26287
+ if (!needs_borrow_strdup && current_return_is_string(status) && !is_container_borrow_access(node.value) && (node.value.node_type === "func_call" || node.value.node_type === "access" && node.value.access.node_type === "access_func") && !status.last_result_is_heap && status.heap_string_fields?.size) {
26288
+ emit_strdup_string(status);
26289
+ ensure_newline(status);
26290
+ status.last_result_is_heap = true;
26291
+ if (borrow_fn_name) {
26292
+ status.heap_returning_functions?.add(borrow_fn_name);
26293
+ if (borrow_mangled !== void 0) status.heap_returning_functions?.add(borrow_mangled);
26294
+ }
26295
+ }
25780
26296
  if (node.value.node_type === "access" && node.value.access.node_type === "access_func" && node.value.access.name === "move" && current_return_is_string(status)) status.last_result_is_heap = true;
25781
26297
  if (status.last_result_is_heap && current_return_is_string(status)) {
25782
26298
  if (!status.heap_returning_functions) status.heap_returning_functions = /* @__PURE__ */ new Set();
@@ -25837,10 +26353,19 @@ function build_return_node$1(node, status, nir_value) {
25837
26353
  }
25838
26354
  emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
25839
26355
  }
25840
- if (node.value?.node_type === "func_call" && node.value.name === ret_struct.name && !ret_struct.name.startsWith("_") && !node.value.field_overrides?.length) {
25841
- const skip_fields = ctor_return_owned_string_fields(ret_struct, node.value, status.heap_strings);
26356
+ if (node.value?.node_type === "func_call" && node.value.name === ret_struct.name && !ret_struct.name.startsWith("_")) {
26357
+ const call = node.value;
26358
+ const skip_fields = call.field_overrides?.length ? override_return_string_fields(ret_struct, call.field_overrides, status.heap_strings, { base_skip: ctor_return_owned_string_fields(ret_struct, call, status.heap_strings) }) : ctor_return_owned_string_fields(ret_struct, call, status.heap_strings);
25842
26359
  emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
25843
26360
  }
26361
+ {
26362
+ const anon = node.value?.node_type === "anon_struct" ? node.value : void 0;
26363
+ const forwarded_base = node.value?.node_type === "func_call" && node.value.field_overrides?.length ? node.value : anon?.base && anon.base.node_type === "func_call" ? anon.base : void 0;
26364
+ if (forwarded_base && node.value.name !== ret_struct.name && is_normalized_struct_call(forwarded_base, status)) {
26365
+ const skip_fields = override_return_string_fields(ret_struct, anon ? anon.fields : node.value.field_overrides, status.heap_strings, { base_uniformly_owned: true });
26366
+ emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
26367
+ }
26368
+ }
25844
26369
  if (node.value?.node_type === "func_call" && String(node.value.name ?? "").startsWith("_Tuple_") && node.value.type?.name?.startsWith("_Tuple_")) {
25845
26370
  const tuple_call = node.value;
25846
26371
  const skip_fields = /* @__PURE__ */ new Set();
@@ -25943,6 +26468,8 @@ function build_switch_node$1(node, status, nir) {
25943
26468
  const old_scoped_declarations = enter_scope_frame(status);
25944
26469
  const pre_cache = status.buffer_data_cache;
25945
26470
  const pre_array_cache = status.array_ptr_cache;
26471
+ const pre_heap_string_fields = status.heap_string_fields;
26472
+ const case_field_sets = [];
25946
26473
  for (let i = 0; i < node.cases.length; i++) {
25947
26474
  status.scoped_declarations = [];
25948
26475
  build_node(node.cases[i].condition, status);
@@ -25951,7 +26478,9 @@ function build_switch_node$1(node, status, nir) {
25951
26478
  else emit_asm(status, `beq end_switch_${label}\n`);
25952
26479
  status.buffer_data_cache = new Map(pre_cache);
25953
26480
  status.array_ptr_cache = new Map(pre_array_cache);
26481
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25954
26482
  build_block_with_cursor$1(node.cases[i].branch, nir?.arms[i]?.branch, status);
26483
+ case_field_sets.push(status.heap_string_fields);
25955
26484
  emit_asm(status, `b end_switch_${label}\n`);
25956
26485
  emit_asm(status, `sw_next_${label}_${i}:\n`);
25957
26486
  }
@@ -25959,8 +26488,15 @@ function build_switch_node$1(node, status, nir) {
25959
26488
  status.scoped_declarations = [];
25960
26489
  status.buffer_data_cache = new Map(pre_cache);
25961
26490
  status.array_ptr_cache = new Map(pre_array_cache);
26491
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25962
26492
  build_block_with_cursor$1(node.else_branch, nir?.otherwise ?? void 0, status);
26493
+ case_field_sets.push(status.heap_string_fields);
25963
26494
  }
26495
+ if (case_field_sets.length > 1) {
26496
+ const merged = new Set(pre_heap_string_fields ?? []);
26497
+ for (const set of case_field_sets) for (const key of set) merged.add(key);
26498
+ status.heap_string_fields = merged;
26499
+ } else if (case_field_sets.length === 1) status.heap_string_fields = case_field_sets[0];
25964
26500
  status.buffer_data_cache = pre_cache;
25965
26501
  status.array_ptr_cache = pre_array_cache;
25966
26502
  emit_asm(status, `end_switch_${label}:\n`);
@@ -29628,6 +30164,12 @@ function build_function_call_node$1(node, status) {
29628
30164
  if (build_inline_function(inline_candidate, status)) return;
29629
30165
  }
29630
30166
  emit_asm(status, `bl ${func_name}\n`);
30167
+ {
30168
+ const callee = node.resolved_function;
30169
+ if (callee && !callee.params?.some((p) => p.is_self_param)) transfer_ref_param_field_records(callee, node.params, status, (name) => {
30170
+ return !!status.scoped_declarations?.some((d) => d.name === name) && !is_local_ref_var(name, status);
30171
+ });
30172
+ }
29631
30173
  if (is_float_type$1(node.type.name)) emit_asm(status, `fmov x0, d0\n`);
29632
30174
  if (outgoing_size > 0) emit_asm(status, `add sp, sp, #${outgoing_size}\n`);
29633
30175
  if (status.buffer_data_cache) {
@@ -29678,6 +30220,11 @@ function build_function_call_node$1(node, status) {
29678
30220
  const offset = status.stack_offsets.get(temp_addr);
29679
30221
  emit_asm(status, `add x0, x29, #${offset}\n`);
29680
30222
  if (node.field_overrides?.length) {
30223
+ const ctor_struct = status.structs.find((s) => s.name === node.name && !s.is_simple_type && !s.is_class);
30224
+ if (ctor_struct) for (const fname of ctor_heap_displaced_string_fields(ctor_struct, node.field_overrides, (seed) => is_owned_heap_temp(seed, status))) {
30225
+ emit_asm(status, `ldr x0, [x29, #${offset + get_field_offset(ctor_struct.name, fname, status)}]\n`);
30226
+ emit_free(status);
30227
+ }
29681
30228
  emit_field_overrides(temp_addr, node, build_node, status);
29682
30229
  emit_asm(status, `add x0, x29, #${offset}\n`);
29683
30230
  }
@@ -30659,9 +31206,9 @@ function build_operation_node$1(node, status) {
30659
31206
  if (left_type?.is_nullable && left_type.name === "string") {
30660
31207
  status.code += `({ nomen_string _nss_${ns_tmp_counter} = `;
30661
31208
  build_node$1(node.left_value, status);
30662
- status.code += `; _nss_${ns_tmp_counter}.ptr ? _nss_${ns_tmp_counter} : `;
31209
+ status.code += `; _nss_${ns_tmp_counter}.ptr ? nomen_str_dup(_nss_${ns_tmp_counter}) : nomen_str_dup(`;
30663
31210
  build_node$1(node.right_value, status);
30664
- status.code += `; })`;
31211
+ status.code += `); })`;
30665
31212
  ns_tmp_counter++;
30666
31213
  } else if (is_nullable_scalar_type(left_type)) {
30667
31214
  if (node.left_value.node_type === "func_call" && is_nullable_scalar_type(node.left_value.type)) {
@@ -32665,7 +33212,7 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
32665
33212
  status.code += `;\n`;
32666
33213
  return;
32667
33214
  }
32668
- 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) {
33215
+ 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 && !node.raw_field_store) {
32669
33216
  const access_lhs = node.left_value;
32670
33217
  const field_access_node = access_lhs.access;
32671
33218
  let target_type = type_from_value_node$1(access_lhs.target);
@@ -32953,7 +33500,10 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
32953
33500
  const needs_destroy = struct_type ? struct_needs_destroy_by_name(struct_type.name, status) : false;
32954
33501
  const rhs_is_free_factory = node.right_value.node_type === "func_call" && !rhs_references_var(node, lhs_name);
32955
33502
  if (is_fresh_constructor(node, status) || rhs_is_free_factory) {
32956
- if (struct_type) release_recorded_string_fields(status, struct_type, lhs_name);
33503
+ if (struct_type) {
33504
+ release_recorded_string_fields(status, struct_type, lhs_name);
33505
+ if (node.right_value.node_type === "func_call" && node.right_value.name === struct_type.name) record_ctor_heap_default_fields(lhs_name, struct_type, status, (seed) => is_owned_heap_temp$1(seed, status), new Set(node.right_value.field_overrides?.map((o) => o.name) ?? []));
33506
+ }
32957
33507
  if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
32958
33508
  } else if (is_self_method_call(node, lhs_name)) {} else if (!rhs_references_var(node, lhs_name)) {
32959
33509
  if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
@@ -32998,6 +33548,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
32998
33548
  }
32999
33549
  }
33000
33550
  }
33551
+ if (!node.operator && node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field" && node.left_value.target.node_type === "value" && node.left_value.target.value === "self" && status.current_function?.name === "#init" && claim_init_default_seed_reclaim(status, node.left_value.access.name, (expr) => is_owned_heap_temp$1(expr, status))) {
33552
+ const saved_seed_len = begin_code_scratch(status);
33553
+ build_node$1(node.left_value, status);
33554
+ const seed_access = end_code_scratch(status, saved_seed_len);
33555
+ status.code += `free(${seed_access}.ptr);\n`;
33556
+ }
33001
33557
  build_node$1(node.left_value, status);
33002
33558
  if (node.operator) status.code += ` ${node.operator.slice(0, -1)}= `;
33003
33559
  else status.code += " = ";
@@ -33006,7 +33562,15 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
33006
33562
  else emit_rhs_value$1(node.right_value, nir_rhs, status);
33007
33563
  if (node.left_value.node_type === "value" && has_field_overrides(node.right_value)) {
33008
33564
  const lname = node.left_value.value;
33009
- emit_field_overrides(lname, node.right_value, build_node$1, status, ";\n", ";\n");
33565
+ const rhs_call = node.right_value.node_type === "func_call" ? node.right_value : void 0;
33566
+ if (rhs_call?.field_overrides?.length && rhs_call.name === rhs_call.type?.name) {
33567
+ const override_struct = status.structs.find((s) => s.name === rhs_call.name && !s.is_simple_type && !s.is_class);
33568
+ if (override_struct) {
33569
+ status.code += `;\n`;
33570
+ for (const name of override_displaced_string_fields(override_struct, rhs_call.field_overrides, (expr) => is_owned_heap_temp$1(expr, status))) status.code += `free(${lname}.${name}.ptr);\n`;
33571
+ }
33572
+ }
33573
+ emit_field_overrides(lname, node.right_value, build_node$1, status, rhs_call?.field_overrides?.length && rhs_call.name === rhs_call.type?.name ? "" : ";\n", ";\n");
33010
33574
  }
33011
33575
  if (node.swap) {
33012
33576
  status.code += `;\n`;
@@ -33913,8 +34477,20 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
33913
34477
  status.code += `${field_access} = `;
33914
34478
  emit_swap_value$1(node.swap, nir_swap, status);
33915
34479
  }
33916
- if (has_field_overrides(node.value)) emit_field_overrides(safe_name, node.value, build_node$1, status, ";\n", ";\n");
33917
34480
  record_call_init_string_fields(node, status);
34481
+ const override_ctor_call = node.value?.node_type === "func_call" && node.value.field_overrides?.length && node.type?.name && node.value.name === node.type.name ? node.value : void 0;
34482
+ if (override_ctor_call) {
34483
+ const override_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
34484
+ if (override_struct) {
34485
+ status.code += `;\n`;
34486
+ for (const name of override_displaced_string_fields(override_struct, override_ctor_call.field_overrides, (expr) => is_owned_heap_temp$1(expr, status))) status.code += `free(${safe_name}.${name}.ptr);\n`;
34487
+ }
34488
+ }
34489
+ if (node.value?.node_type === "func_call" && node.value.name === node.type?.name && !is_normalized_struct_call(node.value, status)) {
34490
+ const ctor_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
34491
+ if (ctor_struct) record_ctor_heap_default_fields(safe_name, ctor_struct, status, (seed) => is_owned_heap_temp$1(seed, status), new Set(node.value.field_overrides?.map((o) => o.name) ?? []));
34492
+ }
34493
+ if (has_field_overrides(node.value)) emit_field_overrides(safe_name, node.value, build_node$1, status, override_ctor_call ? "" : ";\n", ";\n");
33918
34494
  }
33919
34495
  }
33920
34496
  }
@@ -34207,21 +34783,32 @@ function build_if_else_node(node, status, nir) {
34207
34783
  status.scoped_declarations = enter_c_scope(status);
34208
34784
  const old_deferred_frees = status.deferred_frees;
34209
34785
  status.deferred_frees = [];
34786
+ const pre_heap_string_fields = status.heap_string_fields;
34210
34787
  emit_allocations(node.condition, status);
34211
34788
  status.code += `if (`;
34212
34789
  build_condition(node.condition, status);
34213
34790
  status.code += `) {\n`;
34791
+ let then_fields;
34214
34792
  if (node.if_branch) {
34215
34793
  build_block_with_cursor(node.if_branch, nir?.then_branch, status);
34216
34794
  build_auto_free(status);
34217
34795
  }
34796
+ then_fields = status.heap_string_fields;
34218
34797
  leave_c_scope(status);
34219
34798
  if (node.else_branch) {
34220
34799
  status.scoped_declarations = enter_c_scope(status);
34221
34800
  status.deferred_frees = [];
34801
+ status.heap_string_fields = pre_heap_string_fields;
34222
34802
  status.code += `} else {\n`;
34223
34803
  build_block_with_cursor(node.else_branch, nir?.else_branch, status);
34224
34804
  build_auto_free(status);
34805
+ const else_fields = status.heap_string_fields;
34806
+ if (else_fields !== then_fields) {
34807
+ const merged = new Set(pre_heap_string_fields ?? []);
34808
+ for (const key of then_fields ?? []) merged.add(key);
34809
+ for (const key of else_fields ?? []) merged.add(key);
34810
+ status.heap_string_fields = merged;
34811
+ }
34225
34812
  leave_c_scope(status);
34226
34813
  }
34227
34814
  status.code += `}\n`;
@@ -34558,14 +35145,31 @@ function build_return_node(node, status, nir_value) {
34558
35145
  if (!returns_string_zero && !returns_struct_zero && !returns_view_value && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `)`;
34559
35146
  status.code += `;\n`;
34560
35147
  if (node.value.node_type === "access" && node.value.access?.node_type === "access_field" && !returns_string_zero && !returns_struct_zero && !returns_view_value && ret_type.name !== "string" && !ret_type.is_view && !ret_type.is_array && status.enums.find((e) => e.name === ret_type.name && e.has_associated_data)) status.code += `_return_val = ${ret_type.name}_copy(_return_val);\n`;
35148
+ const forwarded_override_base = node.value?.node_type === "func_call" && node.value.field_overrides?.length ? node.value : node.value?.node_type === "anon_struct" && node.value.base ? node.value.base : void 0;
35149
+ if (is_struct && !return_is_class && return_struct && forwarded_override_base?.node_type === "func_call" && node.value.name !== return_struct.name && is_normalized_struct_call(forwarded_override_base, status)) {
35150
+ const overrides = node.value.node_type === "func_call" ? node.value.field_overrides : node.value.fields;
35151
+ for (const field of direct_string_fields(return_struct)) {
35152
+ if (!overrides?.some((o) => o.name === field.name)) continue;
35153
+ status.code += `free(_return_val.${field.name}.ptr);\n`;
35154
+ }
35155
+ }
35156
+ if (is_struct && !return_is_class && return_struct && node.value?.node_type === "func_call" && node.value.name === return_struct.name) for (const name of override_displaced_string_fields(return_struct, node.value.field_overrides, (expr) => is_owned_heap_temp$1(expr, status))) status.code += `free(_return_val.${name}.ptr);\n`;
34561
35157
  if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
34562
35158
  const bare_local_return = node.value.node_type === "value" && node.value.value !== "0" && node.value.value !== "null" && !!returned_value_decl;
34563
35159
  if (is_struct && !return_is_class && !ret_type.is_view && !ret_type.is_array && return_struct && !returns_struct_zero && bare_local_return) for (const field of direct_string_fields(return_struct)) {
34564
35160
  if (returned_recorded_string_fields?.has(field.name)) continue;
34565
35161
  status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
34566
35162
  }
34567
- if (is_struct && !return_is_class && !ret_type.is_view && !ret_type.is_array && return_struct && !returns_struct_zero && node.value?.node_type === "func_call" && node.value.name === return_struct.name && !return_struct.name.startsWith("_") && !node.value.field_overrides?.length) {
34568
- const skip = ctor_return_owned_string_fields(return_struct, node.value, status.heap_strings);
35163
+ if (is_struct && !return_is_class && !ret_type.is_view && !ret_type.is_array && return_struct && !returns_struct_zero && node.value?.node_type === "func_call" && node.value.name === return_struct.name && !return_struct.name.startsWith("_")) {
35164
+ const call = node.value;
35165
+ const skip = call.field_overrides?.length ? override_return_string_fields(return_struct, call.field_overrides, status.heap_strings, { base_skip: ctor_return_owned_string_fields(return_struct, call, status.heap_strings) }) : ctor_return_owned_string_fields(return_struct, call, status.heap_strings);
35166
+ for (const field of direct_string_fields(return_struct)) {
35167
+ if (skip.has(field.name)) continue;
35168
+ status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
35169
+ }
35170
+ }
35171
+ if (is_struct && !return_is_class && !ret_type.is_view && !ret_type.is_array && return_struct && !returns_struct_zero && forwarded_override_base?.node_type === "func_call" && node.value.name !== return_struct.name && is_normalized_struct_call(forwarded_override_base, status)) {
35172
+ const skip = override_return_string_fields(return_struct, node.value.node_type === "func_call" ? node.value.field_overrides : node.value.fields, status.heap_strings, { base_uniformly_owned: true });
34569
35173
  for (const field of direct_string_fields(return_struct)) {
34570
35174
  if (skip.has(field.name)) continue;
34571
35175
  status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
@@ -34623,9 +35227,12 @@ function nullable_flag_expr_of_value(value, status) {
34623
35227
  //#region ../src/build_c/build_switch_node.ts
34624
35228
  function build_switch_node(node, status, nir) {
34625
35229
  const old_scoped_declarations = status.scoped_declarations;
35230
+ const pre_heap_string_fields = status.heap_string_fields;
35231
+ const case_field_sets = [];
34626
35232
  for (let i = 0; i < node.cases.length; i++) {
34627
35233
  const c = node.cases[i];
34628
35234
  status.scoped_declarations = enter_c_scope(status);
35235
+ status.heap_string_fields = pre_heap_string_fields;
34629
35236
  const saved_code = begin_code_scratch(status);
34630
35237
  build_node$1(c.condition, status);
34631
35238
  let cond_code = end_code_scratch(status, saved_code);
@@ -34665,17 +35272,28 @@ function build_switch_node(node, status, nir) {
34665
35272
  status.code += `if (${cond_code}) {\n`;
34666
35273
  build_block_with_cursor(c.branch, nir?.arms[i]?.branch, status);
34667
35274
  build_auto_free(status);
35275
+ case_field_sets.push(status.heap_string_fields);
34668
35276
  leave_c_scope(status);
34669
35277
  }
34670
35278
  if (node.else_branch) {
34671
35279
  status.code += "} else ";
34672
35280
  status.scoped_declarations = enter_c_scope(status);
35281
+ status.heap_string_fields = pre_heap_string_fields;
34673
35282
  status.code += `{\n`;
34674
35283
  build_block_with_cursor(node.else_branch, nir?.otherwise ?? void 0, status);
34675
35284
  build_auto_free(status);
35285
+ case_field_sets.push(status.heap_string_fields);
34676
35286
  status.code += `}\n`;
34677
35287
  leave_c_scope(status);
34678
35288
  } else if (node.cases.length > 0) status.code += "}\n";
35289
+ if (case_field_sets.length > 1) {
35290
+ const first = case_field_sets[0];
35291
+ if (!case_field_sets.every((s) => s === first)) {
35292
+ const merged = new Set(pre_heap_string_fields ?? []);
35293
+ for (const set of case_field_sets) for (const key of set ?? []) merged.add(key);
35294
+ status.heap_string_fields = merged;
35295
+ }
35296
+ }
34679
35297
  status.scoped_declarations = old_scoped_declarations;
34680
35298
  }
34681
35299
  //#endregion
@@ -35458,6 +36076,10 @@ function build_struct_node(node, status) {
35458
36076
  if (node.traits.length) status.code += `self${accessor}_vt = &_${node.name}_traits;\n`;
35459
36077
  const old_init_assigned_fields = status.init_assigned_fields;
35460
36078
  status.init_assigned_fields = /* @__PURE__ */ new Set();
36079
+ const old_init_default_reclaimed = status.init_default_reclaimed_fields;
36080
+ status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
36081
+ const old_init_heap_string_fields = status.heap_string_fields;
36082
+ status.heap_string_fields = void 0;
35461
36083
  for (const field of node.fields) if (field.value) {
35462
36084
  status.init_assigned_fields?.add(`self.${field.name}`);
35463
36085
  if (field.type.is_view && !field.type.is_array && !is_nullable_struct_type(field.type, status)) {
@@ -35525,6 +36147,8 @@ function build_struct_node(node, status) {
35525
36147
  status.current_struct = old_current_struct;
35526
36148
  status.current_function = old_current_function;
35527
36149
  status.init_assigned_fields = old_init_assigned_fields;
36150
+ status.init_default_reclaimed_fields = old_init_default_reclaimed;
36151
+ status.heap_string_fields = old_init_heap_string_fields;
35528
36152
  status.function_return_type = old_return_type;
35529
36153
  }
35530
36154
  build_struct_functions(node, status, true);
@@ -36441,6 +37065,8 @@ function build_function_node(node, status) {
36441
37065
  status.heap_strings = /* @__PURE__ */ new Set();
36442
37066
  const old_owned_string_vars = status.owned_string_vars;
36443
37067
  status.owned_string_vars = /* @__PURE__ */ new Set();
37068
+ const old_heap_string_fields = status.heap_string_fields;
37069
+ status.heap_string_fields = void 0;
36444
37070
  const old_heap_array_vars = status.heap_array_vars;
36445
37071
  status.heap_array_vars = void 0;
36446
37072
  const old_stack_array_lengths = status.stack_array_lengths;
@@ -36658,6 +37284,7 @@ function build_function_node(node, status) {
36658
37284
  status.aliased_class_sources = old_aliased_class_sources;
36659
37285
  status.string_borrow_vars = old_string_borrow_vars;
36660
37286
  status.moved_string_vars = old_moved_string_vars;
37287
+ status.heap_string_fields = old_heap_string_fields;
36661
37288
  status.heap_strings = old_heap_strings;
36662
37289
  status.owned_string_vars = old_owned_string_vars;
36663
37290
  status.heap_array_vars = old_heap_array_vars;
@@ -37090,6 +37717,12 @@ function build_function_call_node(node, status) {
37090
37717
  const call_text = end_code_scratch(status, saved_call);
37091
37718
  status.code += `({ unsigned char _nsd_${wrap_tmp} = 0; ${call_text}; })`;
37092
37719
  }
37720
+ {
37721
+ const callee = node.resolved_function;
37722
+ if (callee && !callee.params?.some((p) => p.is_self_param)) transfer_ref_param_field_records(callee, node.params, status, (name) => {
37723
+ return !!find_decl_in_c_scopes(status, name) && !status.function_ref_params?.has(name) && !status.ref_class_params?.has(name) && !status.class_vars?.has(name);
37724
+ });
37725
+ }
37093
37726
  if (node.name.startsWith("_string_interpolate_")) status.interpolate_string_counts.add(node.params.length - 1);
37094
37727
  if (node.swap_params?.size) for (const [idx, swap_expr] of node.swap_params) {
37095
37728
  const source = node.params[idx];
@@ -37497,6 +38130,10 @@ function build_access_node$1(node, status) {
37497
38130
  return;
37498
38131
  }
37499
38132
  if (target_type.is_array && access_field.name === "length") {
38133
+ if (node.target.node_type === "value" && !!status.current_function?.params.some((p) => !p.is_self_param && p.name === node.target.value) && target_type.length) {
38134
+ build_node$1(target_type.length, status);
38135
+ return;
38136
+ }
37500
38137
  const type = c_type(target_type.name);
37501
38138
  status.code += "(sizeof(";
37502
38139
  build_node$1(node.target, status);
@@ -37865,6 +38502,10 @@ function build_access_node$1(node, status) {
37865
38502
  }
37866
38503
  }
37867
38504
  }
38505
+ {
38506
+ const ref_method = target_method ?? trait_default_func;
38507
+ if (ref_method) transfer_ref_param_field_records(ref_method, access_func.params ?? [], status, (name) => !!find_decl_in_c_scopes(status, name) && !status.function_ref_params?.has(name) && !status.ref_class_params?.has(name) && !status.class_vars?.has(name));
38508
+ }
37868
38509
  const method_callee_params = (target_method ?? trait_default_func)?.params?.filter((p) => !p.is_self_param);
37869
38510
  const lambda_arg_temps = /* @__PURE__ */ new Map();
37870
38511
  for (let i = 0; i < access_func.params.length; i++) {
@@ -40283,6 +40924,11 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40283
40924
  emit_var_address(status, "x1", name);
40284
40925
  emit_struct_copy("x0", "x1", 0, struct_size, status);
40285
40926
  }
40927
+ if (node.right_value.node_type === "func_call" && !paramReg && !is_local_ref_var(name, status)) {
40928
+ const rhs_call = node.right_value;
40929
+ const rhs_ctor_struct = status.structs.find((s) => s.name === rhs_call.name && !s.is_simple_type && !s.is_class);
40930
+ if (rhs_ctor_struct) record_ctor_heap_default_fields(name, rhs_ctor_struct, status, (seed) => is_owned_heap_temp(seed, status), new Set(rhs_call.field_overrides?.map((o) => o.name) ?? []));
40931
+ }
40286
40932
  if (has_field_overrides(node.right_value)) emit_field_overrides(name, node.right_value, build_node, status);
40287
40933
  build_swap(node, status, nir_swap);
40288
40934
  return;
@@ -40503,12 +41149,13 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40503
41149
  const move_source = rhs_is_string_var && node.last_use_move && move_on_last_use_enabled() ? rhs_value.value : void 0;
40504
41150
  const explicit_move_source = !node.swap && !!rhs_value && rhs_value.is_moved && size === 16 && rhs_value.type?.name === "string" && !rhs_value.type?.is_view ? rhs_value.value : void 0;
40505
41151
  const borrow_needs_dup = !node.swap && size === 16 && is_string_borrow(node.right_value) && !!status.force_heap_strings?.has(name);
41152
+ const move_source_owns_heap = move_source !== void 0 && !!status.heap_strings?.has(move_source);
40506
41153
  if (rhs_is_view_into_owned) {
40507
41154
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
40508
41155
  status.heap_strings.add(name);
40509
- } else if (rhs_is_string_var && move_source === void 0 || borrow_needs_dup) emit_strdup_string(status);
41156
+ } else if (rhs_is_string_var && (move_source === void 0 || !move_source_owns_heap) || borrow_needs_dup) emit_strdup_string(status);
40510
41157
  if (rhs_is_string_var || borrow_needs_dup) {
40511
- if (move_source !== void 0) status.heap_strings?.delete(move_source);
41158
+ if (move_source !== void 0 && move_source_owns_heap) status.heap_strings?.delete(move_source);
40512
41159
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
40513
41160
  status.heap_strings.add(name);
40514
41161
  } else if (status.last_result_is_heap) {
@@ -40586,7 +41233,7 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40586
41233
  emit_asm(status, `str xzr, [x0, #${offset + 8}]\n`);
40587
41234
  }
40588
41235
  }
40589
- } else if (field_is_struct_string(target_type, field_type, status) && !node.operator && (() => {
41236
+ } else if (field_is_struct_string(target_type, field_type, status) && !node.operator && !node.raw_field_store && (() => {
40590
41237
  const st = field_is_struct_string(target_type, field_type, status);
40591
41238
  const tv = access.target.node_type === "value" ? access.target.value : "";
40592
41239
  return st.target_is_class || tv !== "" && tv !== "self";
@@ -40614,12 +41261,16 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40614
41261
  ensure_newline(status);
40615
41262
  }
40616
41263
  let rhs_is_heap = status.last_result_is_heap;
40617
- const rhs_value_node = node.right_value.node_type === "value" ? node.right_value : void 0;
40618
- if (!rhs_is_heap && !rhs_is_null_literal && !!rhs_value_node && typeof rhs_value_node.value === "string" && rhs_value_node.type?.name === "string" && !!status.heap_strings?.has(rhs_value_node.value)) {
40619
- emit_strdup(status);
41264
+ if (rhs_is_null_literal) {} else if (!rhs_is_heap) {
41265
+ const rhs_type_nullable = !!node.right_value.type?.is_nullable;
41266
+ if (field_type?.is_nullable || rhs_type_nullable) {
41267
+ const skip_dup = `.Lskip_field_dup_${status.label_counter = (status.label_counter ?? 0) + 1}`;
41268
+ emit_asm(status, `cbz x0, ${skip_dup}\n`);
41269
+ emit_strdup(status);
41270
+ emit_asm(status, `${skip_dup}:\n`);
41271
+ } else emit_strdup(status);
40620
41272
  rhs_is_heap = true;
40621
41273
  }
40622
- if (rhs_is_null_literal) {} else if (is_class_target && !rhs_is_heap) emit_strdup(status);
40623
41274
  mark_moved_if_struct(node.right_value, status);
40624
41275
  if (field_type?.name === "string") {
40625
41276
  emit_asm(status, `stp x0, x1, [sp, #-16]!\n`);
@@ -40805,6 +41456,14 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40805
41456
  get_base_address(access, status, "x0");
40806
41457
  emit_asm(status, `str x0, [sp, #-16]!\n`);
40807
41458
  }
41459
+ if (!node.operator && access.target.node_type === "value" && access.target.value === "self" && status.current_function?.name === "#init" && claim_init_default_seed_reclaim(status, field_name, (expr) => is_owned_heap_temp(expr, status))) {
41460
+ if (base_reg !== void 0) emit_asm(status, `ldr x0, [${base_reg}, #${offset}]\n`);
41461
+ else {
41462
+ emit_asm(status, `ldr x0, [sp]\n`);
41463
+ emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
41464
+ }
41465
+ emit_free(status);
41466
+ }
40808
41467
  emit_rhs_value(node.right_value, nir_rhs, status);
40809
41468
  ensure_newline(status);
40810
41469
  mark_moved_if_struct(node.right_value, status);
@@ -44856,7 +45515,10 @@ function build_access_method(node, access_func, status) {
44856
45515
  }
44857
45516
  if (node.target.node_type === "value" && target_struct && !target_struct.is_class && !trait_target) {
44858
45517
  const target_method = target_struct.functions.find((f) => f.name === access_func.name);
44859
- if (target_method) drop_self_written_string_field_records(status, node.target.value, scan_self_string_field_writes(target_struct, target_method));
45518
+ if (target_method) {
45519
+ drop_self_written_string_field_records(status, node.target.value, scan_self_string_field_writes(target_struct, target_method));
45520
+ transfer_ref_param_field_records(target_method, access_func.params ?? [], status, (name) => !!status.scoped_declarations?.some((d) => d.name === name) && !is_local_ref_var(name, status));
45521
+ }
44860
45522
  }
44861
45523
  if (method_name.endsWith("_to_string") && method_name !== "string_to_string") status.last_result_is_heap = true;
44862
45524
  if (trait_target && trait_func?.return_type?.name === "string" && !trait_func.return_type?.is_view && trait_method_all_conformers_owned(trait_target, access_func.name, status)) status.last_result_is_heap = true;
@@ -45895,10 +46557,14 @@ function gather_normalized_struct_returners(root, status) {
45895
46557
  if (!return_normalizing.has(fn.name)) continue;
45896
46558
  const ret_struct = structs.find((st) => st.name === fn.return_type?.name && !st.is_simple_type && !st.is_class);
45897
46559
  for (const ret of direct_returns(fn)) {
45898
- const value = ret.value ?? void 0;
46560
+ let value = ret.value ?? void 0;
46561
+ if (value?.node_type === "anon_struct") {
46562
+ const base = value.base;
46563
+ if (base?.node_type === "func_call") value = base;
46564
+ }
45899
46565
  if (value && value.node_type === "value") continue;
45900
46566
  if (value && value.node_type === "func_call" && return_normalizing.has(value.name ?? "")) continue;
45901
- if (value && value.node_type === "func_call" && !!ret_struct && value.name === ret_struct.name && !ret_struct.name.startsWith("_") && !value.field_overrides?.length) continue;
46567
+ if (value && value.node_type === "func_call" && !!ret_struct && !ret_struct.name.startsWith("_") && (value.name === ret_struct.name || !!value.field_overrides?.length && return_normalizing.has(value.name ?? ""))) continue;
45902
46568
  return_normalizing.delete(fn.name);
45903
46569
  changed = true;
45904
46570
  break;
@@ -46130,7 +46796,7 @@ function build(root, options = {}) {
46130
46796
  status.code = status.code + "\n" + status.c_runtime_defs;
46131
46797
  status.c_runtime_defs = void 0;
46132
46798
  }
46133
- status.code = "typedef struct { void* ptr; long len; } nomen_view;\ntypedef struct { char* ptr; long len; } nomen_string;\n#define nomen_str_lit(s, n) ((nomen_string){ (s), (long)(n) })\n#include <string.h>\n" + (options.audit ? `void *nomen_strdup_wrap(const char *);\n` : "") + "static nomen_string nomen_str_dup(nomen_string s) {\n nomen_string r = { strdup(s.ptr), s.len };\n return r;\n}\n" + status.code;
46799
+ status.code = "typedef struct { void* ptr; long len; } nomen_view;\ntypedef struct { char* ptr; long len; } nomen_string;\n#define nomen_str_lit(s, n) ((nomen_string){ (s), (long)(n) })\n#include <string.h>\n" + (options.audit ? `void *nomen_strdup_wrap(const char *);\n` : "") + "static nomen_string nomen_str_dup(nomen_string s) {\n if (!s.ptr) return s;\n nomen_string r = { strdup(s.ptr), s.len };\n return r;\n}\n" + status.code;
46134
46800
  if (options.audit) {
46135
46801
  status.code = wrap_c_allocators(status.code);
46136
46802
  status.headers = "void *nomen_malloc_wrap(unsigned long);\nvoid *nomen_calloc_wrap(unsigned long, unsigned long);\nvoid *nomen_realloc_wrap(void *, unsigned long);\nvoid nomen_free_wrap(void *);\nvoid *nomen_strdup_wrap(const char *);\nvoid nomen_audit_check(void);\n" + wrap_c_allocators(status.headers);
@@ -51469,6 +52135,21 @@ function check_struct_node(struct, status) {
51469
52135
  status.values.length = values_length_before_fields;
51470
52136
  rewrite_generic_buffer_fields(struct, status);
51471
52137
  mark_owning_auto_init_params(struct, status);
52138
+ if (!struct.is_library) for (const field of struct.fields) {
52139
+ if (field.declaration !== "move") continue;
52140
+ if (!field.type.name || field.type.is_ref || field.type.is_view) continue;
52141
+ if (field.type.is_nullable) continue;
52142
+ if (is_class_type$1(field.type.name, status)) continue;
52143
+ if (status.traits.find((t) => t.name === field.type.name)) continue;
52144
+ if (!is_owning_struct_type_requiring_move(new Type(mono_type_name(field.type)), status)) continue;
52145
+ if (!status.warnings) status.warnings = [];
52146
+ status.warnings.push({
52147
+ message: `'move' on field '${field.name}' is redundant: '${type_display(field.type)}' is a value struct — its ownership is derived from the type, so plain 'var' behaves identically`,
52148
+ start: field.start,
52149
+ line: 0,
52150
+ column: 0
52151
+ });
52152
+ }
51472
52153
  status.types.push(struct.name);
51473
52154
  status.structs.push(struct);
51474
52155
  for (let func of struct.functions) {
@@ -51649,6 +52330,11 @@ function signature_matches(struct_func, trait_func, substitution) {
51649
52330
  function types_match(a, b) {
51650
52331
  return a.name === b.name && !!a.is_ref === !!b.is_ref && !!a.is_view === !!b.is_view && !!a.is_array === !!b.is_array;
51651
52332
  }
52333
+ /** The user-visible spelling of a type (`List<int>`, `Map<string, int>`). */
52334
+ function type_display(t) {
52335
+ if (!t.type_args?.length) return t.name ?? "?";
52336
+ return `${t.name}<${t.type_args.map(type_display).join(", ")}>`;
52337
+ }
51652
52338
  //#endregion
51653
52339
  //#region ../src/check/utils/auto_derive.ts
51654
52340
  function struct_has_function(struct, name) {
@@ -55252,7 +55938,14 @@ function check_function_call(node, status, func, target_type, self_value, self_p
55252
55938
  const named = status.functions.findLast((f) => f.name === param_value);
55253
55939
  if (named) param.resolved_function = named;
55254
55940
  }
55255
- if (param_type.is_array && param_type.length && !func_param.type.length) func_param.type.length = param_type.length;
55941
+ if (param_type.is_array && param_type.length && !func_param.is_variadic) {
55942
+ if (!func_param.type.length) func_param.type.length = param_type.length;
55943
+ else {
55944
+ const stamped = String(func_param.type.length.value);
55945
+ const incoming = String(param_type.length.value);
55946
+ if (stamped !== incoming) add_error(status, `Call passes ${incoming} elements to array parameter '${func_param.name}' whose compile-time length is ${stamped} — the raw array ABI carries no runtime length, so one parameter serves one compile-time length; use Array<${func_param.type.name}> for varying lengths`, param.start);
55947
+ }
55948
+ }
55256
55949
  let arg_value;
55257
55950
  if (param.node_type === "value") {
55258
55951
  const vn = param;
@@ -56603,7 +57296,7 @@ function check_as_array_or_inferred_tuple(array, status, forced_tuple_types) {
56603
57296
  array.type.is_array = true;
56604
57297
  array.type.is_nullable = old_expected.is_nullable;
56605
57298
  array.type.type_args = old_expected.type_args;
56606
- array.type.length = old_expected.length;
57299
+ if (!old_expected.length || String(old_expected.length.value) === String(array.values.length)) array.type.length = old_expected.length;
56607
57300
  } else if (!array.type.name) {
56608
57301
  const first_type = value_types[0];
56609
57302
  if (first_type) {
@@ -57954,6 +58647,11 @@ function check_switch_node(switch_node, status) {
57954
58647
  let branch_statuses = [];
57955
58648
  for (let switch_case of switch_node.cases) {
57956
58649
  check_node(switch_case.condition, status);
58650
+ if (status.allocations.length) {
58651
+ switch_case.condition.allocations ??= [];
58652
+ switch_case.condition.allocations.push(...status.allocations);
58653
+ status.allocations.length = 0;
58654
+ }
57957
58655
  const condition_type = type_from_value_node(switch_case.condition, status);
57958
58656
  if (!is_bool_condition(condition_type)) add_error(status, `Switch case condition must be a bool, not ${type_name(condition_type)}`, switch_case.condition.start);
57959
58657
  let case_status = clone_status(status);