nomen-lang 0.6.5 → 0.6.7

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 +1320 -268
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -7147,14 +7147,37 @@ function call_result_type(node) {
7147
7147
  * so `null == 0` conflates them. Nullable `string` (in-band fat zero) and
7148
7148
  * nullable `func`/class values (null pointer) keep their in-band null and
7149
7149
  * are NOT nullable scalars.
7150
+ *
7151
+ * The same holds for a nullable SIMPLE enum (`Color?`) or bitset
7152
+ * (`Permissions?`): the in-band zero (the first case's tag 0 / an empty
7153
+ * bitset) is a real value, so `null` needs the companion flag. Enum-with-data
7154
+ * and generic template enums are NOT nullable scalars — the checker rejects
7155
+ * `?` on enums with associated data, so every nullable enum that reaches the
7156
+ * backends is simple.
7150
7157
  */
7151
- /** True if `type` is a nullable built-in scalar (`bool?`, `int?`, `float?`, …). */
7152
- function is_nullable_scalar_type(type) {
7158
+ /** True if the TYPE NAME is a built-in scalar, simple enum, or bitset — the
7159
+ * shapes that get the `<slot>_has` flag when nullable. */
7160
+ function is_nullable_scalar_name(name, tables) {
7161
+ if (!name) return false;
7162
+ const info = get_built_in_type(name);
7163
+ if (info) return info.kind !== "string" && info.kind !== "func";
7164
+ const enum_node = tables.enums.findLast((e) => (e.name === name || e.source_name === name) && !e.is_generic);
7165
+ if (enum_node) return !enum_node.has_associated_data;
7166
+ return tables.bitsets.some((b) => b.name === name || b.source_name === name);
7167
+ }
7168
+ /** True if `type` is a nullable built-in scalar, simple enum, or bitset. */
7169
+ function is_nullable_scalar_type(type, tables) {
7153
7170
  if (!type?.is_nullable) return false;
7154
7171
  if (type.is_array || type.is_ref || type.storage_kind === "view") return false;
7155
7172
  if (type.is_pointer) return false;
7156
- const info = type.name ? get_built_in_type(type.name) : void 0;
7157
- return !!info && info.kind !== "string" && info.kind !== "func";
7173
+ return is_nullable_scalar_name(type.name, tables);
7174
+ }
7175
+ /** True if `type` is a nullable struct (see nullable_struct.ts) or nullable scalar. */
7176
+ function is_nullable_flagged_type(type, status) {
7177
+ if (!type?.is_nullable) return false;
7178
+ if (type.name === "string") return false;
7179
+ if (type.name === "func") return false;
7180
+ return is_nullable_scalar_type(type, status) || !!status.structs.find((s) => s.name === type.name && !s.is_class && !s.is_simple_type);
7158
7181
  }
7159
7182
  //#endregion
7160
7183
  //#region ../src/build_common/nullable_struct.ts
@@ -7546,7 +7569,7 @@ function scan_self_string_field_writes(struct, method) {
7546
7569
  const scan_method = (func) => {
7547
7570
  if (visited.has(func.name)) return;
7548
7571
  visited.add(func.name);
7549
- walk$3(func.statements ?? [], (n) => {
7572
+ walk$4(func.statements ?? [], (n) => {
7550
7573
  if (n.node_type === "assign") {
7551
7574
  const lhs = n.left_value;
7552
7575
  if (lhs?.node_type !== "access") return;
@@ -7590,10 +7613,10 @@ function drop_self_written_string_field_records(status, receiver_name, fields) {
7590
7613
  * statement arrays) — skipping `parent`/`scope` back-references and NOT
7591
7614
  * descending INTO nested `func`/`struct`/`trait` declarations (a nested
7592
7615
  * function's body is a separate function, not part of this one's writes). */
7593
- function walk$3(value, cb) {
7616
+ function walk$4(value, cb) {
7594
7617
  if (!value || typeof value !== "object") return;
7595
7618
  if (Array.isArray(value)) {
7596
- for (const item of value) walk$3(item, cb);
7619
+ for (const item of value) walk$4(item, cb);
7597
7620
  return;
7598
7621
  }
7599
7622
  const n = value;
@@ -7602,7 +7625,177 @@ function walk$3(value, cb) {
7602
7625
  if (is_boundary) return;
7603
7626
  for (const key of Object.keys(value)) {
7604
7627
  if (key === "parent" || key === "scope" || key === "node_type") continue;
7605
- walk$3(value[key], cb);
7628
+ walk$4(value[key], cb);
7629
+ }
7630
+ }
7631
+ //#endregion
7632
+ //#region ../src/build_common/has_string_fields.ts
7633
+ /**
7634
+ * Whether any field (recursively, through nested owning value structs)
7635
+ * is an owned `string`. Shared by BOTH backends' owning-Buffer element
7636
+ * specialization (the slot deep-copies strdup'd strings, so a
7637
+ * string-field element is owning even when `struct_needs_destroy`
7638
+ * ignores string-only locals) — the C comment in
7639
+ * owning_buffer_specialize records the divergence this once caused.
7640
+ * `ref` and `view T` fields are skipped: a ref aliases the caller's
7641
+ * storage and a view's byte copy owns nothing.
7642
+ */
7643
+ function has_string_fields(node, status) {
7644
+ for (const field of node.fields) {
7645
+ if (field.type.is_ref) continue;
7646
+ if (field.type.is_view) continue;
7647
+ if (field.type.name === "string" && !field.type.is_array) return true;
7648
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
7649
+ if (field_struct && !field_struct.is_class && has_string_fields(field_struct, status)) return true;
7650
+ }
7651
+ return false;
7652
+ }
7653
+ /**
7654
+ * The DIRECT `string` fields of a value struct (nested struct fields are not
7655
+ * descended into — nested string fields are outside the per-field ownership
7656
+ * tracking everywhere else too). Used by the return-boundary normalization:
7657
+ * the returned struct's unrecorded fields are strdup'd so the value the
7658
+ * caller receives is uniformly heap-owned.
7659
+ */
7660
+ function direct_string_fields(node) {
7661
+ return node.fields.filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string");
7662
+ }
7663
+ //#endregion
7664
+ //#region ../src/build_common/scan_ref_string_writes.ts
7665
+ /**
7666
+ * The plain `string` fields of `func`'s `ref` value-struct parameters that the
7667
+ * function stores through with a DIRECT top-level statement — transitively
7668
+ * through top-level calls that forward the same param as their own `ref`
7669
+ * argument (callees resolved via the checker's `resolved_function` stamps).
7670
+ *
7671
+ * Both backends' string-field store paths strdup every non-heap RHS into an
7672
+ * owned copy before storing (a field is never a borrow once stored), so a
7673
+ * definitely-executed store leaves the field heap-owned. That is the fact a
7674
+ * CALL SITE transfers onto the caller's `heap_string_fields` records (the
7675
+ * add-direction mirror of `drop_self_written_string_field_records`): the
7676
+ * callee's own records are scope-local and die at return, so without the
7677
+ * transfer the stored copy leaks.
7678
+ *
7679
+ * Only DIRECT statements qualify: a store nested in a branch/loop may not
7680
+ * execute, and a record over the pre-call value would free it at the caller's
7681
+ * scope exit (possibly rodata — an invalid free). Memoized on the function
7682
+ * node; forwarding cycles are cut by a visited set (the cycle member's own
7683
+ * contribution is skipped, bounded-leak).
7684
+ */
7685
+ function scan_ref_param_string_field_writes(func, structs) {
7686
+ const stamp = func;
7687
+ if (stamp._ref_param_string_field_writes) return stamp._ref_param_string_field_writes;
7688
+ const result = /* @__PURE__ */ new Map();
7689
+ stamp._ref_param_string_field_writes = result;
7690
+ const tracked = /* @__PURE__ */ new Map();
7691
+ for (const param of func.params ?? []) {
7692
+ if (param.is_self_param) continue;
7693
+ if (!(param.is_ref || param.type.is_ref)) continue;
7694
+ if (param.type.is_array || param.type.is_view) continue;
7695
+ const fields = ref_param_writable_fields(param.type.name, structs);
7696
+ if (fields.size) tracked.set(param.name, fields);
7697
+ }
7698
+ if (tracked.size) scan_function(func, tracked, structs, result, /* @__PURE__ */ new Set());
7699
+ return result;
7700
+ }
7701
+ /** The writable plain string fields of the value struct `type_name` resolves
7702
+ * to, or the empty set (non-struct, class, or no string fields). */
7703
+ function ref_param_writable_fields(type_name, structs) {
7704
+ if (!type_name) return /* @__PURE__ */ new Set();
7705
+ const struct = structs.find((s) => s.name === type_name && !s.is_simple_type && !s.is_class);
7706
+ if (!struct) return /* @__PURE__ */ new Set();
7707
+ return new Set(direct_string_fields(struct).map((f) => f.name));
7708
+ }
7709
+ function scan_function(func, tracked, structs, result, visiting) {
7710
+ visiting.add(func);
7711
+ for (const stmt of func.statements ?? []) {
7712
+ if (stmt.node_type === "assign") {
7713
+ const lhs = stmt.left_value;
7714
+ if (lhs?.node_type === "access") {
7715
+ const access = lhs;
7716
+ if (access.access.node_type === "access_field" && access.target.node_type === "value") {
7717
+ const name = access.target.value ?? "";
7718
+ const fields = tracked.get(name);
7719
+ const field = access.access.name ?? "";
7720
+ if (fields?.has(field)) {
7721
+ if (!result.has(name)) result.set(name, /* @__PURE__ */ new Set());
7722
+ result.get(name).add(field);
7723
+ }
7724
+ }
7725
+ }
7726
+ continue;
7727
+ }
7728
+ const call = ref_forward_call(stmt);
7729
+ if (!call) continue;
7730
+ const [callee, args] = call;
7731
+ const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
7732
+ for (let i = 0; i < args.length && i < non_self_params.length; i++) {
7733
+ const arg = args[i];
7734
+ if (arg?.node_type !== "value") continue;
7735
+ const arg_name = arg.value ?? "";
7736
+ const my_fields = tracked.get(arg_name);
7737
+ if (!my_fields) continue;
7738
+ const callee_param = non_self_params[i];
7739
+ if (!(callee_param.is_ref || callee_param.type.is_ref)) continue;
7740
+ if (visiting.has(callee)) continue;
7741
+ const callee_writes = scan_ref_param_string_field_writes(callee, structs).get(callee_param.name);
7742
+ if (!callee_writes?.size) continue;
7743
+ if (!result.has(arg_name)) result.set(arg_name, /* @__PURE__ */ new Set());
7744
+ const mine = result.get(arg_name);
7745
+ for (const field of callee_writes) if (my_fields.has(field)) mine.add(field);
7746
+ }
7747
+ }
7748
+ visiting.delete(func);
7749
+ }
7750
+ /**
7751
+ * The forwarded-call shape of a top-level statement: a resolved free-function
7752
+ * call or method call, with its argument list (the receiver is NOT included).
7753
+ */
7754
+ function ref_forward_call(stmt) {
7755
+ if (stmt.node_type === "func_call") {
7756
+ const call = stmt;
7757
+ if (!call.resolved_function) return void 0;
7758
+ return [call.resolved_function, call.params ?? []];
7759
+ }
7760
+ if (stmt.node_type === "access") {
7761
+ const access = stmt;
7762
+ if (access.access.node_type !== "access_func") return void 0;
7763
+ const func_node = access.access.resolved_function;
7764
+ if (!func_node) return void 0;
7765
+ return [func_node, access.access.params ?? []];
7766
+ }
7767
+ }
7768
+ //#endregion
7769
+ //#region ../src/build_common/transfer_ref_param_records.ts
7770
+ /**
7771
+ * The add-direction mirror of drop_self_written_string_field_records: at a
7772
+ * call site whose callee takes a `ref` value-struct parameter, the callee's
7773
+ * string-field stores write through to the CALLER's variable — but the
7774
+ * callee's heap_string_fields records are scope-local and die at return, so
7775
+ * the stored copy would leak. Transfer the callee's definitely-executed
7776
+ * stores (scan_ref_param_string_field_writes) onto the caller's records for
7777
+ * the argument variable, making the caller's scope exit free them.
7778
+ *
7779
+ * `arg_is_trackable_local` must report true only for variables whose storage
7780
+ * THIS function's scope exit owns — a plain local. A `ref` param of the
7781
+ * enclosing function forwards caller storage, so a record here would free the
7782
+ * ORIGINAL caller's field at the wrong scope's exit.
7783
+ */
7784
+ function transfer_ref_param_field_records(callee, args, status, arg_is_trackable_local) {
7785
+ const writes = scan_ref_param_string_field_writes(callee, status.structs);
7786
+ if (!writes.size) return;
7787
+ const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
7788
+ for (let i = 0; i < args.length && i < non_self_params.length; i++) {
7789
+ const param = non_self_params[i];
7790
+ if (!(param.is_ref || param.type.is_ref)) continue;
7791
+ const fields = writes.get(param.name);
7792
+ if (!fields?.size) continue;
7793
+ const arg = args[i];
7794
+ if (arg?.node_type !== "value") continue;
7795
+ const name = arg.value;
7796
+ if (!name || !arg_is_trackable_local(name)) continue;
7797
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
7798
+ for (const field of fields) status.heap_string_fields.add(`${name}.${field}`);
7606
7799
  }
7607
7800
  }
7608
7801
  //#endregion
@@ -7914,7 +8107,7 @@ var AssignmentNode = class extends BaseNode {
7914
8107
  super("assign", start);
7915
8108
  this.left_value = left_value;
7916
8109
  this.right_value = right_value;
7917
- this.operator = operator;
8110
+ this.operator = operator === "=" ? void 0 : operator;
7918
8111
  }
7919
8112
  };
7920
8113
  //#endregion
@@ -7943,13 +8136,14 @@ function emit_field_overrides(var_name, value, build, status, declaration_termin
7943
8136
  const overrides = overrides_of(value);
7944
8137
  if (!overrides.length) return;
7945
8138
  status.code += declaration_terminator;
7946
- const carrier_type = var_name.startsWith("_return_val") ? void 0 : value.type;
8139
+ const carrier_type = var_name.startsWith("_return_val") || var_name.startsWith("_temp_") ? void 0 : value.type;
7947
8140
  for (const override of overrides) {
7948
8141
  const target = new ValueNode(override.value.start, var_name);
7949
8142
  if (carrier_type) target.type = carrier_type;
7950
8143
  const access_field = new AccessFieldNode(override.value.start, override.name, override.type);
7951
8144
  const access = new AccessNode(override.value.start, target, access_field);
7952
8145
  const assign = new AssignmentNode(override.value.start, access, override.value);
8146
+ if (!carrier_type) assign.raw_field_store = true;
7953
8147
  status.code += "\n";
7954
8148
  status.c_field_override_depth = (status.c_field_override_depth ?? 0) + 1;
7955
8149
  build(assign, status);
@@ -8073,38 +8267,6 @@ function hoist_field_overrides(value, build, status, declaration_terminator = ""
8073
8267
  carrier._fov_hoisted = true;
8074
8268
  }
8075
8269
  //#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
8270
  //#region ../src/build_common/string_return_analysis.ts
8109
8271
  const EMPTY_SET = /* @__PURE__ */ new Set();
8110
8272
  /**
@@ -8468,6 +8630,11 @@ function is_owned_string_branch_value(node, table) {
8468
8630
  */
8469
8631
  function is_normalized_struct_call(value, status) {
8470
8632
  if (!value) return false;
8633
+ if (value.node_type === "anon_struct") {
8634
+ const base = value.base;
8635
+ if (!base) return false;
8636
+ value = base;
8637
+ }
8471
8638
  if (value.node_type === "func_call") {
8472
8639
  const call = value;
8473
8640
  return !!status.normalized_struct_returners?.has(call.name);
@@ -8511,6 +8678,175 @@ function record_call_init_string_fields(node, status) {
8511
8678
  for (const field of fields) status.heap_string_fields.add(`${node.name}.${field.name}`);
8512
8679
  }
8513
8680
  //#endregion
8681
+ //#region ../src/build_common/init_computed_seeds.ts
8682
+ function is_plain_string_field(field) {
8683
+ return field.type.name === "string" && !field.type.is_ref && !field.type.is_array && !field.type.is_view;
8684
+ }
8685
+ /** The `(field, rhs)` of a `self.<field> = <rhs>` write, or undefined. */
8686
+ function self_string_field_write(node, string_fields) {
8687
+ if (node.node_type !== "assign") return void 0;
8688
+ const lhs = node.left_value;
8689
+ if (lhs?.node_type !== "access") return void 0;
8690
+ const access = lhs;
8691
+ if (access.access.node_type !== "access_field") return void 0;
8692
+ const target = access.target;
8693
+ if (target?.node_type !== "value" || target.value !== "self") return void 0;
8694
+ const field = access.access.name ?? "";
8695
+ if (!string_fields.has(field)) return void 0;
8696
+ return {
8697
+ field,
8698
+ rhs: node.right_value
8699
+ };
8700
+ }
8701
+ /** Visit every AST node reachable from `value` — through arrays AND
8702
+ * single-node properties — skipping `parent`/`scope` back-references and NOT
8703
+ * descending INTO nested `func`/`struct`/`trait` declarations. */
8704
+ function walk$3(value, cb) {
8705
+ if (!value || typeof value !== "object") return;
8706
+ if (Array.isArray(value)) {
8707
+ for (const item of value) walk$3(item, cb);
8708
+ return;
8709
+ }
8710
+ const n = value;
8711
+ const is_boundary = n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait";
8712
+ if (typeof n.node_type === "string") cb(n);
8713
+ if (is_boundary) return;
8714
+ for (const key of Object.keys(value)) {
8715
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
8716
+ walk$3(value[key], cb);
8717
+ }
8718
+ }
8719
+ function collect_init_string_writes(struct_node, init) {
8720
+ const string_fields = new Set(struct_node.fields.filter(is_plain_string_field).map((f) => f.name));
8721
+ const top_level = /* @__PURE__ */ new Map();
8722
+ const nested = /* @__PURE__ */ new Map();
8723
+ const add = (m, field, rhs) => {
8724
+ const list = m.get(field);
8725
+ if (list) list.push(rhs);
8726
+ else m.set(field, [rhs]);
8727
+ };
8728
+ for (const stmt of init.statements ?? []) {
8729
+ const write = self_string_field_write(stmt, string_fields);
8730
+ if (write) {
8731
+ add(top_level, write.field, write.rhs);
8732
+ continue;
8733
+ }
8734
+ walk$3(stmt, (n) => {
8735
+ const nested_write = self_string_field_write(n, string_fields);
8736
+ if (nested_write) add(nested, nested_write.field, nested_write.rhs);
8737
+ });
8738
+ }
8739
+ return {
8740
+ top_level,
8741
+ nested
8742
+ };
8743
+ }
8744
+ /**
8745
+ * The plain string fields of `struct_node` that its custom `#init` COMPUTES as
8746
+ * a definitely-heap-owning value: for EVERY `#init` overload, the field is
8747
+ * either written at least once as a DIRECT body statement with every write
8748
+ * (direct or nested) passing `is_owned_heap` — or never written and its
8749
+ * DEFAULT also passes `is_owned_heap` (the overload leaves the default in
8750
+ * place). A param-alias seed (`self.a = s`) never qualifies: the seed borrows
8751
+ * the caller's argument temp, so it must never be recorded or freed.
8752
+ *
8753
+ * Binding sites record qualifying fields (scope exit frees the computed seed;
8754
+ * a displaced store reclaims it); override sites treat them as displaced (the
8755
+ * raw override store orphans the computed buffer). A param-alias seed or a
8756
+ * conditional computed write over a rodata default leaves a path holding a
8757
+ * non-heap value — such fields stay unqualified (bounded leak on the write
8758
+ * path, never an invalid free).
8759
+ */
8760
+ function init_computed_heap_string_fields(struct_node, is_owned_heap) {
8761
+ const result = /* @__PURE__ */ new Set();
8762
+ const inits = struct_node.functions.filter((f) => f.name === "#init" && f.has_body);
8763
+ if (!inits.length) return result;
8764
+ for (const field of struct_node.fields) {
8765
+ if (!is_plain_string_field(field)) continue;
8766
+ const default_is_heap = !!field.value && is_owned_heap(field.value);
8767
+ let written_somewhere = false;
8768
+ let qualifies = true;
8769
+ for (const init of inits) {
8770
+ const writes = collect_init_string_writes(struct_node, init);
8771
+ const direct = writes.top_level.get(field.name) ?? [];
8772
+ const all = [...direct, ...writes.nested.get(field.name) ?? []];
8773
+ if (all.length === 0) {
8774
+ if (!default_is_heap) {
8775
+ qualifies = false;
8776
+ break;
8777
+ }
8778
+ continue;
8779
+ }
8780
+ written_somewhere = true;
8781
+ if (!all.every((expr) => is_owned_heap(expr))) {
8782
+ qualifies = false;
8783
+ break;
8784
+ }
8785
+ if (direct.length === 0 && !default_is_heap) {
8786
+ qualifies = false;
8787
+ break;
8788
+ }
8789
+ }
8790
+ if (qualifies && written_somewhere) result.add(field.name);
8791
+ }
8792
+ return result;
8793
+ }
8794
+ /**
8795
+ * The write-site half: does THIS custom-`#init` write to `self.<field>`
8796
+ * displace a heap-owning DEFAULT seed that must be reclaimed ahead of the
8797
+ * store? True only for the field's first write in the body (the displaced
8798
+ * value is then statically the default — later writes displace values whose
8799
+ * ownership the init cannot know, and keep the status quo). Marks the field
8800
+ * reclaimed on true so a second write never frees what the first stored.
8801
+ *
8802
+ * Sound even for conditional first writes: the free rides in the same branch
8803
+ * as the store, and the displaced value is still the default at that point.
8804
+ */
8805
+ function claim_init_default_seed_reclaim(status, field_name, is_owned_heap) {
8806
+ if (status.current_function?.name !== "#init" || !status.current_struct) return false;
8807
+ if (status.init_default_reclaimed_fields?.has(field_name)) return false;
8808
+ const field = status.current_struct.fields.find((f) => f.name === field_name);
8809
+ if (!field || !field.value || !is_plain_string_field(field)) return false;
8810
+ if (!is_owned_heap(field.value)) return false;
8811
+ if (!status.init_default_reclaimed_fields) status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
8812
+ status.init_default_reclaimed_fields.add(field_name);
8813
+ return true;
8814
+ }
8815
+ //#endregion
8816
+ //#region ../src/build_common/ctor_heap_default_fields.ts
8817
+ /**
8818
+ * Record the string fields of `struct_node` whose value after construction
8819
+ * owns heap (per the backend's `is_owned_heap_temp`): either the DEFAULT is a
8820
+ * non-literal heap expression the constructor evaluates fresh at every
8821
+ * construction, or a custom `#init` COMPUTES the field's value
8822
+ * (`init_computed_heap_string_fields` — every direct `self.<field> = <expr>`
8823
+ * write stores a heap-owning expression, which also displaces the default).
8824
+ * Recording at the ctor-binding site makes scope exit free the seeded value,
8825
+ * and a displaced store (an override, a reassignment) reclaim it via the
8826
+ * record's old_was_heap check.
8827
+ *
8828
+ * `is_owned_heap` classifies the seed EXPRESSION per backend: both treat a
8829
+ * string-typed `op` as fresh heap; for calls the C backend's blanket strdup
8830
+ * makes every non-borrow string return owned, while the aarch64 backend
8831
+ * requires the heap-returning classification (a literal-returning user
8832
+ * function leaves rodata in the field — never recorded, never freed).
8833
+ *
8834
+ * `skip` names fields whose seed is reclaimed elsewhere (the C backend's
8835
+ * explicit displaced free ahead of an override store). Value structs only —
8836
+ * a class's string fields are always heap and freed by its destroy.
8837
+ */
8838
+ function record_ctor_heap_default_fields(var_name, struct_node, status, is_owned_heap, skip) {
8839
+ const computed = init_computed_heap_string_fields(struct_node, is_owned_heap);
8840
+ for (const field of struct_node.fields) {
8841
+ if (field.type.name !== "string" || field.type.is_array) continue;
8842
+ if (field.type.is_ref || field.type.is_view) continue;
8843
+ if (skip?.has(field.name)) continue;
8844
+ if (!(!!field.value && is_owned_heap(field.value)) && !computed.has(field.name)) continue;
8845
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
8846
+ status.heap_string_fields.add(`${var_name}.${field.name}`);
8847
+ }
8848
+ }
8849
+ //#endregion
8514
8850
  //#region ../src/nodes/child_nodes.ts
8515
8851
  /** Whether `value` is an AST node (carries a string `node_type`). */
8516
8852
  function is_ast_node(value) {
@@ -8853,6 +9189,7 @@ function scan_function_literal_element_moves(func) {
8853
9189
  return count;
8854
9190
  }
8855
9191
  function walk_stmt(walk, node) {
9192
+ if (node.allocations?.length) for (const alloc of node.allocations) walk_stmt(walk, alloc);
8856
9193
  switch (node.node_type) {
8857
9194
  case "declare": {
8858
9195
  const decl = node;
@@ -11697,7 +12034,7 @@ function get_struct_size(name, status) {
11697
12034
  size = align_to(size, get_type_alignment(field.type, status));
11698
12035
  size += get_type_size(field.type, status);
11699
12036
  if (is_nullable_struct_type(field.type, status)) size += 8;
11700
- else if (is_nullable_scalar_type(field.type)) size += 1;
12037
+ else if (is_nullable_scalar_type(field.type, status)) size += 1;
11701
12038
  }
11702
12039
  size = align_to(size, 8);
11703
12040
  return size;
@@ -11749,7 +12086,7 @@ function get_field_offset_of_fields(fields, field_name, status) {
11749
12086
  if (field.name === field_name) return offset;
11750
12087
  offset += get_type_size(field.type, status);
11751
12088
  if (is_nullable_struct_type(field.type, status)) offset += 8;
11752
- else if (is_nullable_scalar_type(field.type)) offset += 1;
12089
+ else if (is_nullable_scalar_type(field.type, status)) offset += 1;
11753
12090
  }
11754
12091
  return offset;
11755
12092
  }
@@ -14891,10 +15228,21 @@ function emit_string_field_strdups_at(status, struct_name, base_reg, base_offset
14891
15228
  const off = base_offset + get_field_offset(struct_name, field.name, status);
14892
15229
  emit_asm(status, `str ${base_reg}, [sp, #-16]!\n`);
14893
15230
  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");
15231
+ if (field.type.is_nullable) {
15232
+ const skip_label = `.Lskip_nullable_strdup_${status.label_counter = (status.label_counter ?? 0) + 1}`;
15233
+ emit_asm(status, `mov x13, x0\n`);
15234
+ emit_asm(status, `cbz x0, ${skip_label}\n`);
15235
+ emit_strdup_string(status);
15236
+ emit_asm(status, `mov x13, x0\n`);
15237
+ emit_asm(status, `${skip_label}:\n`);
15238
+ emit_asm(status, `ldr ${base_reg}, [sp], #16\n`);
15239
+ emit_pair_store_to(status, base_reg, off, "x13", "x1");
15240
+ } else {
15241
+ emit_strdup_string(status);
15242
+ emit_asm(status, `mov x13, x0\n`);
15243
+ emit_asm(status, `ldr ${base_reg}, [sp], #16\n`);
15244
+ emit_pair_store_to(status, base_reg, off, "x13", "x1");
15245
+ }
14898
15246
  }
14899
15247
  }
14900
15248
  //#endregion
@@ -17404,7 +17752,7 @@ function owning_buffer_element_aarch64(node, status) {
17404
17752
  const elem_name = node.name.substring(7);
17405
17753
  const elem = status.structs.find((s) => s.name === elem_name && !s.is_simple_type && !s.is_generic);
17406
17754
  if (!elem || elem.is_class) return void 0;
17407
- if (!has_string_fields(elem, status)) return void 0;
17755
+ if (!has_string_fields(elem, status) && !struct_needs_destroy(elem, status)) return void 0;
17408
17756
  return elem;
17409
17757
  }
17410
17758
  /**
@@ -17550,6 +17898,75 @@ function collect_string_fields(elem, status, base_offset = 0) {
17550
17898
  return result;
17551
17899
  }
17552
17900
  /**
17901
+ * Collect the (offset, size) pairs of every owned CONTAINER field (`List<T>`/
17902
+ * `Buffer<T>`/… mono with a `#destroy`) the element's teardown would reclaim.
17903
+ * The owning store_T/replace_T memcpy the source's bytes into the slot, so
17904
+ * zeroing these fields of the SOURCE completes the ownership transfer: the
17905
+ * slot is the sole owner and the source's own teardown (when it still runs)
17906
+ * finds zeroed fields. Nested value struct fields recurse so their container
17907
+ * leaves are zeroed at their own flat offsets; their string fields are
17908
+ * untouched (the slot owns the strdup'd copies via collect_string_fields).
17909
+ * Mirrors the C backend's `emit_transfer_owning_fields`. `view T`/nullable
17910
+ * fields are skipped; class/enum payload fields are left alone (recorded in
17911
+ * FOLLOWUP.md).
17912
+ */
17913
+ function collect_transfer_fields(elem, status, base_offset = 0) {
17914
+ const result = [];
17915
+ let offset = 8;
17916
+ for (const field of elem.fields) {
17917
+ const size = get_type_size(field.type, status);
17918
+ if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable) {
17919
+ const field_struct = resolve_struct_type(field.type, status);
17920
+ if (field_struct && !field_struct.is_class) {
17921
+ if (has_destroy(field_struct)) result.push({
17922
+ offset: base_offset + offset,
17923
+ size
17924
+ });
17925
+ else if (struct_needs_destroy(field_struct, status)) result.push(...collect_transfer_fields(field_struct, status, base_offset + offset));
17926
+ }
17927
+ }
17928
+ offset += size;
17929
+ }
17930
+ return result;
17931
+ }
17932
+ /**
17933
+ * Emit `memset(&src.field, 0, size)` for every owned field collected by
17934
+ * `collect_transfer_fields`. `src` is the register holding the SOURCE struct
17935
+ * address (x2 inline / x21 standalone for store_T). Clobbers x0-x2.
17936
+ */
17937
+ function emit_transfer_owning_fields$1(status, src, fields) {
17938
+ for (const { offset, size } of fields) {
17939
+ emit_asm(status, `add x0, ${src}, #${offset}\n`);
17940
+ emit_asm(status, `mov x1, #0\n`);
17941
+ emit_asm(status, `mov x2, #${size}\n`);
17942
+ emit_asm(status, `bl _memset\n`);
17943
+ }
17944
+ }
17945
+ /**
17946
+ * Emit the deep-copy half of owning load_T into the sret buffer (x8): for
17947
+ * each `List<T>` field whose mono has a `copy` method, replace the
17948
+ * shallow-copied field with `<List_T>_copy(&slot.field)` (sret → the sret
17949
+ * buffer's field; self == x8 is safe — the copy reads its input before the
17950
+ * final result write). Mirrors the C backend's `emit_load_deep_copy_fields`.
17951
+ * `sret` is the register holding the returned struct's address; `slot` holds
17952
+ * the source slot's address (the copy reads from the slot, writes to sret).
17953
+ */
17954
+ function emit_load_deep_copy_fields$1(elem, status, sret, slot) {
17955
+ let offset = 8;
17956
+ for (const field of elem.fields) {
17957
+ const size = get_type_size(field.type, status);
17958
+ if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable && field.type.name !== "string") {
17959
+ const field_struct = resolve_struct_type(field.type, status);
17960
+ if (field_struct && !field_struct.is_class && field_struct.name.startsWith("List_") && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
17961
+ emit_asm(status, `add x8, ${sret}, #${offset}\n`);
17962
+ emit_asm(status, `add x0, ${slot}, #${offset}\n`);
17963
+ emit_asm(status, `bl ${field_struct.name}_copy\n`);
17964
+ }
17965
+ }
17966
+ offset += size;
17967
+ }
17968
+ }
17969
+ /**
17553
17970
  * Emit `slot.field = strdup(src.field)` guarded against a NULL source field
17554
17971
  * (e.g. JsonTree's "no text" sentinel): a NULL field is copied as NULL —
17555
17972
  * strdup(NULL) would crash. `src`/`dst` are the register names holding the
@@ -17587,6 +18004,10 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
17587
18004
  emit_owning_shift_T(elem, status, "x0");
17588
18005
  return true;
17589
18006
  }
18007
+ if (func_name === "load") {
18008
+ emit_owning_load_T(elem, status, "x0");
18009
+ return true;
18010
+ }
17590
18011
  return false;
17591
18012
  }
17592
18013
  const enum_elem = owning_buffer_enum_element_aarch64(struct_node, status);
@@ -17648,7 +18069,7 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
17648
18069
  function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
17649
18070
  const elem = owning_buffer_element_aarch64(node, status);
17650
18071
  if (elem) {
17651
- if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify") return false;
18072
+ if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify" && func_name !== "load") return false;
17652
18073
  emit_owning_standalone_struct(elem, func_name, status);
17653
18074
  return true;
17654
18075
  }
@@ -17707,6 +18128,7 @@ function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
17707
18128
  function emit_owning_standalone_struct(elem, func_name, status) {
17708
18129
  const T_SIZE = get_struct_size(elem.name, status);
17709
18130
  const string_fields = collect_string_fields(elem, status);
18131
+ const transfer_fields = collect_transfer_fields(elem, status);
17710
18132
  if (func_name === "shift") {
17711
18133
  emit_owning_shift_T(elem, status, "x19");
17712
18134
  return true;
@@ -17715,6 +18137,23 @@ function emit_owning_standalone_struct(elem, func_name, status) {
17715
18137
  emit_owning_modify_T(elem, status, T_SIZE, string_fields);
17716
18138
  return true;
17717
18139
  }
18140
+ if (func_name === "load") {
18141
+ emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
18142
+ emit_asm(status, `stp x22, x23, [sp, #-16]!\n`);
18143
+ emit_asm(status, `ldr x20, [x19, #8]\n`);
18144
+ emit_asm(status, `mov x9, #${T_SIZE}\n`);
18145
+ emit_asm(status, `madd x21, x1, x9, x20\n`);
18146
+ emit_asm(status, `mov x22, x8\n`);
18147
+ emit_asm(status, `mov x0, x22\n`);
18148
+ emit_asm(status, `mov x1, x21\n`);
18149
+ emit_asm(status, `mov x2, x9\n`);
18150
+ emit_asm(status, `bl _memcpy\n`);
18151
+ emit_load_deep_copy_fields$1(elem, status, "x22", "x21");
18152
+ emit_asm(status, `mov x0, x22\n`);
18153
+ emit_asm(status, `ldp x22, x23, [sp], #16\n`);
18154
+ emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18155
+ return true;
18156
+ }
17718
18157
  emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
17719
18158
  emit_asm(status, `str x22, [sp, #-16]!\n`);
17720
18159
  emit_asm(status, `ldr x20, [x19, #8]\n`);
@@ -17752,6 +18191,7 @@ function emit_owning_standalone_struct(elem, func_name, status) {
17752
18191
  }
17753
18192
  if (string_fields.length) emit_asm(status, `add sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
17754
18193
  } else for (const { offset: foff } of string_fields) emit_strdup_field(status, "x21", "x20", foff, status.label_counter = (status.label_counter ?? 0) + 1);
18194
+ emit_transfer_owning_fields$1(status, "x21", transfer_fields);
17755
18195
  emit_asm(status, `ldr x22, [sp], #16\n`);
17756
18196
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
17757
18197
  return true;
@@ -17953,9 +18393,35 @@ function emit_owning_shift_T(elem, status, self_reg) {
17953
18393
  emit_asm(status, `ldr x22, [sp], #16\n`);
17954
18394
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
17955
18395
  }
18396
+ /**
18397
+ * Specialized owning-value-struct load_T (inline context: x0 = self, x1 = i,
18398
+ * x8 = sret). The caller's scope-exit destroy ALWAYS reclaims a struct
18399
+ * local's container fields, so the returned copy must own independent
18400
+ * containers — a shallow return would double-free the slot's buffer at the
18401
+ * caller's teardown. String fields stay borrowed (struct-local teardown only
18402
+ * frees RECORDED heap strings, so a strdup here would leak).
18403
+ */
18404
+ function emit_owning_load_T(elem, status, self_reg) {
18405
+ const T_SIZE = get_struct_size(elem.name, status);
18406
+ emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
18407
+ emit_asm(status, `stp x22, x23, [sp, #-16]!\n`);
18408
+ emit_asm(status, `ldr x20, [${self_reg}, #8]\n`);
18409
+ emit_asm(status, `mov x9, #${T_SIZE}\n`);
18410
+ emit_asm(status, `madd x21, x1, x9, x20\n`);
18411
+ emit_asm(status, `mov x22, x8\n`);
18412
+ emit_asm(status, `mov x0, x22\n`);
18413
+ emit_asm(status, `mov x1, x21\n`);
18414
+ emit_asm(status, `mov x2, x9\n`);
18415
+ emit_asm(status, `bl _memcpy\n`);
18416
+ emit_load_deep_copy_fields$1(elem, status, "x22", "x21");
18417
+ emit_asm(status, `mov x0, x22\n`);
18418
+ emit_asm(status, `ldp x22, x23, [sp], #16\n`);
18419
+ emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18420
+ }
17956
18421
  function emit_owning_store_T(elem, status) {
17957
18422
  const T_SIZE = get_struct_size(elem.name, status);
17958
18423
  const string_fields = collect_string_fields(elem, status);
18424
+ const transfer_fields = collect_transfer_fields(elem, status);
17959
18425
  emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
17960
18426
  emit_asm(status, `str x22, [sp, #-16]!\n`);
17961
18427
  emit_asm(status, `ldr x20, [x0, #8]\n`);
@@ -17987,12 +18453,14 @@ function emit_owning_store_T(elem, status) {
17987
18453
  emit_asm(status, `${lbl}:\n`);
17988
18454
  }
17989
18455
  if (string_fields.length) emit_asm(status, `add sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
18456
+ emit_transfer_owning_fields$1(status, "x21", transfer_fields);
17990
18457
  emit_asm(status, `ldr x22, [sp], #16\n`);
17991
18458
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
17992
18459
  }
17993
18460
  function emit_owning_replace_T(elem, status) {
17994
18461
  const T_SIZE = get_struct_size(elem.name, status);
17995
18462
  const string_fields = collect_string_fields(elem, status);
18463
+ const transfer_fields = collect_transfer_fields(elem, status);
17996
18464
  emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
17997
18465
  emit_asm(status, `str x22, [sp, #-16]!\n`);
17998
18466
  emit_asm(status, `ldr x20, [x0, #8]\n`);
@@ -18007,6 +18475,7 @@ function emit_owning_replace_T(elem, status) {
18007
18475
  emit_asm(status, `mov x2, x22\n`);
18008
18476
  emit_asm(status, `bl _memcpy\n`);
18009
18477
  for (const { offset: foff } of string_fields) emit_strdup_field(status, "x21", "x20", foff, status.label_counter = (status.label_counter ?? 0) + 1);
18478
+ emit_transfer_owning_fields$1(status, "x21", transfer_fields);
18010
18479
  emit_asm(status, `ldr x22, [sp], #16\n`);
18011
18480
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18012
18481
  }
@@ -19441,6 +19910,12 @@ function plan_nir_registers(func, nir, options) {
19441
19910
  });
19442
19911
  }
19443
19912
  }
19913
+ const loop_carry_regs = /* @__PURE__ */ new Set();
19914
+ for (const [key, f] of facts) {
19915
+ if (!f.loop_blocked) continue;
19916
+ const reg = allocs.get(key);
19917
+ if (reg) loop_carry_regs.add(reg);
19918
+ }
19444
19919
  return {
19445
19920
  allocs,
19446
19921
  callee_saved,
@@ -19449,7 +19924,8 @@ function plan_nir_registers(func, nir, options) {
19449
19924
  source_keys,
19450
19925
  sites: site_allocs,
19451
19926
  pairs: slp_pairs,
19452
- region_free
19927
+ region_free,
19928
+ loop_carry_regs
19453
19929
  };
19454
19930
  }
19455
19931
  /**
@@ -19510,6 +19986,7 @@ function seed_function_allocations(func, status, options) {
19510
19986
  }]));
19511
19987
  if (status.nir_region_free.size === 0) status.nir_region_free = void 0;
19512
19988
  status.region_preseed = void 0;
19989
+ status.nir_loop_carry_regs = plan.loop_carry_regs.size > 0 ? plan.loop_carry_regs : void 0;
19513
19990
  for (const param of func.params) {
19514
19991
  const reg = plan.allocs.get(param.name);
19515
19992
  if (!reg || !reg.startsWith("x")) continue;
@@ -19633,6 +20110,8 @@ function build_function_node$1(node, status) {
19633
20110
  status.heap_class_arrays = void 0;
19634
20111
  const old_heap_array_vars = status.heap_array_vars;
19635
20112
  status.heap_array_vars = void 0;
20113
+ const old_heap_string_fields = status.heap_string_fields;
20114
+ status.heap_string_fields = void 0;
19636
20115
  const old_heap_cleanup_stack = status.heap_cleanup_stack;
19637
20116
  status.heap_cleanup_stack = void 0;
19638
20117
  const old_trait_class_frames = status.trait_class_frames;
@@ -19661,7 +20140,7 @@ function build_function_node$1(node, status) {
19661
20140
  status.code = "";
19662
20141
  node.return_type = resolve_mono_type(node.return_type, status);
19663
20142
  const return_struct = !node.return_type.is_view && !node.return_type.is_array && (!!status.structs.find((s) => s.name === node.return_type.name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(node.return_type.name, status) !== void 0);
19664
- const nullable_scalar_ret = is_nullable_scalar_type(node.return_type) && !node.return_type.is_ref;
20143
+ const nullable_scalar_ret = is_nullable_scalar_type(node.return_type, status) && !node.return_type.is_ref;
19665
20144
  const uses_sret = return_struct || nullable_scalar_ret;
19666
20145
  if (uses_sret) status.struct_return_buffer = "x8";
19667
20146
  status.function_return_type = node.return_type;
@@ -19748,7 +20227,7 @@ function build_function_node$1(node, status) {
19748
20227
  first_pass_slot += 2;
19749
20228
  continue;
19750
20229
  }
19751
- if (is_nullable_scalar_type(param.type)) {
20230
+ if (is_nullable_scalar_type(param.type, status)) {
19752
20231
  first_pass_slot += 2;
19753
20232
  continue;
19754
20233
  }
@@ -19860,11 +20339,13 @@ function build_function_node$1(node, status) {
19860
20339
  }]));
19861
20340
  if (status.nir_region_free.size === 0) status.nir_region_free = void 0;
19862
20341
  status.region_preseed = void 0;
20342
+ status.nir_loop_carry_regs = plan.loop_carry_regs.size > 0 ? plan.loop_carry_regs : void 0;
19863
20343
  } else {
19864
20344
  fn_allocs = plan_function_promotions(node, plan_nir);
19865
20345
  status.nir_site_allocs = void 0;
19866
20346
  status.nir_region_free = void 0;
19867
20347
  status.region_preseed = void 0;
20348
+ status.nir_loop_carry_regs = void 0;
19868
20349
  }
19869
20350
  } else status.nir_site_allocs = void 0;
19870
20351
  status.register_allocations = fn_allocs && fn_allocs.size > 0 ? fn_allocs : void 0;
@@ -19913,7 +20394,7 @@ function build_function_node$1(node, status) {
19913
20394
  param_idx += 2;
19914
20395
  continue;
19915
20396
  }
19916
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref && !param.type.is_array) {
20397
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref && !param.type.is_array) {
19917
20398
  const size = aarch64_size(param.type.name);
19918
20399
  const offset = allocate_stack_space(status, size, size);
19919
20400
  status.stack_offsets.set(param.name, offset);
@@ -20245,6 +20726,7 @@ function build_function_node$1(node, status) {
20245
20726
  }
20246
20727
  status.scoped_declarations = old_scoped_declarations;
20247
20728
  status.moved = old_moved;
20729
+ status.heap_string_fields = old_heap_string_fields;
20248
20730
  status.heap_strings = old_heap_strings;
20249
20731
  status.heap_string_arrays = old_heap_string_arrays;
20250
20732
  status.heap_owned_string_arrays = old_heap_owned_string_arrays;
@@ -20328,7 +20810,7 @@ function init_enum_shorthand_field_default(node, field, base_reg, status) {
20328
20810
  * struct base register (`x0` for auto-init, `x19` for custom-init).
20329
20811
  */
20330
20812
  function init_nullable_field_default(node, field, base_reg, status) {
20331
- const scalar_nullable = is_nullable_scalar_type(field.type);
20813
+ const scalar_nullable = is_nullable_scalar_type(field.type, status);
20332
20814
  if (!is_nullable_struct_type(field.type, status) && !scalar_nullable) return false;
20333
20815
  if (!field.value) return false;
20334
20816
  const offset = get_field_offset(node.name, field.name, status);
@@ -20358,6 +20840,41 @@ function init_nullable_field_default(node, field, base_reg, status) {
20358
20840
  emit_asm(status, `str x9, [${base_reg}, #${has_offset}]\n`);
20359
20841
  return true;
20360
20842
  }
20843
+ /**
20844
+ * Initialize a field whose default is a non-literal EXPRESSION (a string op
20845
+ * like `"de" + "fault"`, a call returning a non-struct, a grouped expr, …):
20846
+ * evaluate it and store the result into the field slot. x0 carries the
20847
+ * result — a fat string as the (x0, x1) pair, a by-value struct as the sret
20848
+ * temp's address. A class's plain string field is strdup'd when the result
20849
+ * is not itself fresh heap (the field is freed unconditionally at destroy);
20850
+ * a value struct's heap-owning default is stored raw — the ctor-binding
20851
+ * sites record it (record_ctor_heap_default_fields) so scope exit frees it.
20852
+ */
20853
+ function init_expr_field_default(node, field, offset, base_reg, status) {
20854
+ build_node(field.value, status);
20855
+ ensure_newline(status);
20856
+ const t = field.type;
20857
+ if (t.name === "string" && !t.is_ref && !t.is_view && !t.is_array) {
20858
+ if (node.is_class && !status.last_result_is_heap) emit_strdup_string(status);
20859
+ emit_pair_store_to(status, base_reg, offset, "x0", "x1");
20860
+ return;
20861
+ }
20862
+ const field_struct = status.structs.find((s) => s.name === t.name && !s.is_simple_type);
20863
+ if (field_struct?.is_class) {
20864
+ emit_typed_store(status, "x0", base_reg, offset, 8);
20865
+ return;
20866
+ }
20867
+ if (field_struct) {
20868
+ const field_size = get_struct_size(t.name, status);
20869
+ const words = Math.ceil(field_size / 8);
20870
+ for (let w = 0; w < words; w++) {
20871
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
20872
+ emit_asm(status, `str x9, [${base_reg}, #${offset + w * 8}]\n`);
20873
+ }
20874
+ return;
20875
+ }
20876
+ emit_typed_store(status, "x0", base_reg, offset, get_type_size(t, status));
20877
+ }
20361
20878
  /** Store the scalar in x0 into `[base_reg + offset]` at its natural width. */
20362
20879
  function emit_scalar_store_from_x0(offset, type, base_reg, status) {
20363
20880
  const name = type.name;
@@ -20635,7 +21152,7 @@ function build_init_function(node, status) {
20635
21152
  const slot = ctor_slot;
20636
21153
  const field_is_view = !!field.type.is_view && !field.type.is_array;
20637
21154
  const field_is_fat_string = field.type.name === "string" && !field.type.is_view && !field.type.is_ref && !field.type.is_array;
20638
- const field_is_nullable_scalar = is_nullable_scalar_type(field.type);
21155
+ const field_is_nullable_scalar = is_nullable_scalar_type(field.type, status);
20639
21156
  if (field_is_view || field_is_fat_string || field_is_nullable_scalar) ctor_slot += 2;
20640
21157
  else ctor_slot += 1;
20641
21158
  let src_reg;
@@ -20783,19 +21300,16 @@ function build_init_function(node, status) {
20783
21300
  }
20784
21301
  }
20785
21302
  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
- }
21303
+ } else if (field.value.node_type === "func_call" && status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
21304
+ build_node(field.value, status);
21305
+ ensure_newline(status);
21306
+ if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type).is_class) emit_typed_store(status, "x0", "x19", offset, 8);
21307
+ else {
21308
+ const field_size = get_struct_size(field.type.name, status);
21309
+ const words = Math.ceil(field_size / 8);
21310
+ for (let w = 0; w < words; w++) {
21311
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
21312
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
20799
21313
  }
20800
21314
  }
20801
21315
  } else if (field.value.node_type === "func" && field.type.name === "func") {
@@ -20806,7 +21320,7 @@ function build_init_function(node, status) {
20806
21320
  ensure_newline(status);
20807
21321
  emit_descriptor_address(status, "x1", materialize_lambda_descriptor_a64(field.value, status));
20808
21322
  emit_typed_store(status, "x1", "x19", offset, 8);
20809
- }
21323
+ } else init_expr_field_default(node, field, offset, "x19", status);
20810
21324
  }
20811
21325
  for (const trait_name of node.traits) {
20812
21326
  const trait = status.traits.find((t) => t.name === trait_name);
@@ -20816,9 +21330,9 @@ function build_init_function(node, status) {
20816
21330
  if (node.fields.find((nf) => nf.name === field.name)) continue;
20817
21331
  const offset = get_field_offset(node.name, field.name, status);
20818
21332
  const val = field.value.node_type === "value" ? field.value.value : void 0;
20819
- if (val === "null" && (is_nullable_scalar_type(field.type) || is_nullable_struct_type(field.type, status))) {
21333
+ if (val === "null" && (is_nullable_scalar_type(field.type, status) || is_nullable_struct_type(field.type, status))) {
20820
21334
  const has_off = get_field_has_offset(node.name, field.name, status);
20821
- if (is_nullable_scalar_type(field.type)) emit_asm(status, `strb wzr, [x19, #${has_off}]\n`);
21335
+ if (is_nullable_scalar_type(field.type, status)) emit_asm(status, `strb wzr, [x19, #${has_off}]\n`);
20822
21336
  else emit_asm(status, `str xzr, [x19, #${has_off}]\n`);
20823
21337
  continue;
20824
21338
  }
@@ -20959,7 +21473,7 @@ function build_custom_init_function(node, func, status) {
20959
21473
  param_idx += 2;
20960
21474
  continue;
20961
21475
  }
20962
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref && !param.type.is_array) {
21476
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref && !param.type.is_array) {
20963
21477
  const size = aarch64_size(param.type.name);
20964
21478
  const offset = allocate_stack_space(status, size, size);
20965
21479
  status.stack_offsets.set(param.name, offset);
@@ -21026,6 +21540,10 @@ function build_custom_init_function(node, func, status) {
21026
21540
  status.current_function = func;
21027
21541
  const old_init_assigned_fields = status.init_assigned_fields;
21028
21542
  status.init_assigned_fields = /* @__PURE__ */ new Set();
21543
+ const old_init_default_reclaimed = status.init_default_reclaimed_fields;
21544
+ status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
21545
+ const old_init_heap_string_fields = status.heap_string_fields;
21546
+ status.heap_string_fields = void 0;
21029
21547
  emit_asm(status, `str xzr, [x19]\n`);
21030
21548
  if (node.traits.length > 0) {
21031
21549
  emit_asm(status, `adrp x9, _${node.name}_traits@PAGE\n`);
@@ -21083,22 +21601,19 @@ function build_custom_init_function(node, func, status) {
21083
21601
  }
21084
21602
  }
21085
21603
  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
- }
21604
+ } else if (field.value.node_type === "func_call" && status.structs.find((s) => s.name === field.type.name && !s.is_simple_type)) {
21605
+ build_node(field.value, status);
21606
+ ensure_newline(status);
21607
+ if (status.structs.find((s) => s.name === field.type.name && !s.is_simple_type).is_class) emit_typed_store(status, "x0", "x19", offset, 8);
21608
+ else {
21609
+ const field_size = get_struct_size(field.type.name, status);
21610
+ const words = Math.ceil(field_size / 8);
21611
+ for (let w = 0; w < words; w++) {
21612
+ emit_asm(status, `ldr x9, [x0, #${w * 8}]\n`);
21613
+ emit_asm(status, `str x9, [x19, #${offset + w * 8}]\n`);
21099
21614
  }
21100
21615
  }
21101
- }
21616
+ } else init_expr_field_default(node, field, offset, "x19", status);
21102
21617
  }
21103
21618
  status.buffer_data_cache = void 0;
21104
21619
  status.array_ptr_cache = void 0;
@@ -21108,6 +21623,8 @@ function build_custom_init_function(node, func, status) {
21108
21623
  status.raw_param_reloads = old_raw_param_reloads;
21109
21624
  status.current_function = old_current_function;
21110
21625
  status.init_assigned_fields = old_init_assigned_fields;
21626
+ status.init_default_reclaimed_fields = old_init_default_reclaimed;
21627
+ status.heap_string_fields = old_init_heap_string_fields;
21111
21628
  }
21112
21629
  const init_claims = status.callee_saved_regs_used;
21113
21630
  const init_loop_regs = init_claims ? [...init_claims].sort() : [];
@@ -21247,7 +21764,7 @@ function build_struct_functions$1(node, status) {
21247
21764
  status.function_view_params = /* @__PURE__ */ new Set();
21248
21765
  status.struct_return_buffer = void 0;
21249
21766
  const return_struct = !func.return_type?.is_array && (!!status.structs.find((s) => s.name === func.return_type?.name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(func.return_type?.name, status) !== void 0);
21250
- const nullable_scalar_ret = !!func.return_type && is_nullable_scalar_type(func.return_type) && !func.return_type.is_ref;
21767
+ const nullable_scalar_ret = !!func.return_type && is_nullable_scalar_type(func.return_type, status) && !func.return_type.is_ref;
21251
21768
  const uses_sret = return_struct || nullable_scalar_ret;
21252
21769
  let return_buffer_stack_offset;
21253
21770
  status.current_function_name = func.name;
@@ -21274,7 +21791,7 @@ function build_struct_functions$1(node, status) {
21274
21791
  slot_idx += 2;
21275
21792
  continue;
21276
21793
  }
21277
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref) {
21794
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref) {
21278
21795
  if (param.declaration === "var") status.function_param_vars.add(param.name);
21279
21796
  slot_idx += 2;
21280
21797
  continue;
@@ -21357,7 +21874,7 @@ function build_struct_functions$1(node, status) {
21357
21874
  second_slot_idx += 2;
21358
21875
  continue;
21359
21876
  }
21360
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref) {
21877
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref) {
21361
21878
  const size = aarch64_size(param.type.name);
21362
21879
  const offset = allocate_stack_space(status, size, size);
21363
21880
  status.stack_offsets.set(param.name, offset);
@@ -21814,6 +22331,8 @@ function collect_allocations(node, options) {
21814
22331
  case "func_call": {
21815
22332
  const call = node;
21816
22333
  for (const param of call.params) result.push(...collect_allocations(param, options));
22334
+ const base = node.base;
22335
+ if (base) result.push(...collect_allocations(base, options));
21817
22336
  break;
21818
22337
  }
21819
22338
  case "access": {
@@ -21861,6 +22380,12 @@ function collect_allocations(node, options) {
21861
22380
  result.push(...collect_allocations(range.right_value, options));
21862
22381
  break;
21863
22382
  }
22383
+ case "anon_struct": {
22384
+ const anon = node;
22385
+ if (anon.base) result.push(...collect_allocations(anon.base, options));
22386
+ for (const field of anon.fields) result.push(...collect_allocations(field.value, options));
22387
+ break;
22388
+ }
21864
22389
  case "array": {
21865
22390
  const arr = node;
21866
22391
  for (const val of arr.values) result.push(...collect_allocations(val, options));
@@ -22808,11 +23333,11 @@ function build_constructor_params(fc, param_regs, status) {
22808
23333
  let nullable_scalar = false;
22809
23334
  if (fc.nullable_param_indices?.includes(i)) {
22810
23335
  const cp = ctor_callee_params?.[i];
22811
- if (cp && is_nullable_scalar_type(cp.type)) nullable_scalar = true;
22812
- else if (is_nullable_scalar_type(p.type)) nullable_scalar = true;
23336
+ if (cp && is_nullable_scalar_type(cp.type, status)) nullable_scalar = true;
23337
+ else if (is_nullable_scalar_type(p.type, status)) nullable_scalar = true;
22813
23338
  else if (!cp) {
22814
23339
  const field = (status.structs.find((s) => s.name === fc.name && !s.is_simple_type)?.fields.filter((f) => f.value == null))?.[i];
22815
- if (field && is_nullable_scalar_type(field.type)) nullable_scalar = true;
23340
+ if (field && is_nullable_scalar_type(field.type, status)) nullable_scalar = true;
22816
23341
  }
22817
23342
  }
22818
23343
  const is_pair = pt === "string" || lit || is_view || nullable_scalar;
@@ -22839,7 +23364,7 @@ function build_constructor_params(fc, param_regs, status) {
22839
23364
  if (arg.node_type === "value" && arg.value === "null") {
22840
23365
  emit_asm(status, `mov x0, #0\n`);
22841
23366
  emit_asm(status, `mov x1, #0\n`);
22842
- } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type)) {
23367
+ } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type, status)) {
22843
23368
  const vname = arg.value;
22844
23369
  emit_var_load(status, "x0", vname, aarch64_size(arg.type.name));
22845
23370
  emit_var_load(status, "x1", has_flag_name(vname), 8);
@@ -23229,7 +23754,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23229
23754
  emit_var_store(status, "x0", node.name, 8);
23230
23755
  return;
23231
23756
  }
23232
- if (is_nullable_scalar_type(node.type)) {
23757
+ if (is_nullable_scalar_type(node.type, status)) {
23233
23758
  if (status.function_return_label) {
23234
23759
  const offset = allocate_stack_space(status, 16, 16);
23235
23760
  status.stack_offsets.set(node.name, offset);
@@ -23240,14 +23765,23 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23240
23765
  }
23241
23766
  const init_value = node.value;
23242
23767
  const init_type = call_result_type(init_value) ?? type_from_value_node$1(init_value);
23243
- const value_is_nullable_call = init_value.node_type === "func_call" && is_nullable_scalar_type(init_type);
23244
- if (init_value.node_type === "access" && is_nullable_scalar_type(init_type)) {
23768
+ const value_is_nullable_call = init_value.node_type === "func_call" && is_nullable_scalar_type(init_type, status);
23769
+ const value_is_nullable_method = init_value.node_type === "access" && init_value.access.node_type === "access_func" && is_nullable_scalar_type(init_type, status);
23770
+ const value_is_nullable_field = init_value.node_type === "access" && init_value.access.node_type === "access_field" && is_nullable_scalar_type(init_type, status);
23771
+ if (value_is_nullable_method) {
23245
23772
  emit_init_value$1(init_value, nir_init, status);
23246
23773
  ensure_newline(status);
23247
23774
  emit_asm(status, `ldr x9, [x0]\n`);
23248
23775
  emit_asm(status, `str x9, [x29, #${offset}]\n`);
23249
23776
  emit_asm(status, `ldr x9, [x0, #8]\n`);
23250
23777
  emit_asm(status, `str x9, [x29, #${offset + 8}]\n`);
23778
+ } else if (value_is_nullable_field) {
23779
+ emit_init_value$1(init_value, nir_init, status);
23780
+ ensure_newline(status);
23781
+ emit_var_store(status, "x0", node.name, 8);
23782
+ load_nullable_has(init_value, "x9", status);
23783
+ ensure_newline(status);
23784
+ emit_asm(status, `str x9, [x29, #${offset + 8}]\n`);
23251
23785
  } else if (value_is_nullable_call) {
23252
23786
  const old_buffer = status.struct_return_buffer;
23253
23787
  const old_buffer_var = status.struct_return_buffer_var;
@@ -23269,8 +23803,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23269
23803
  }
23270
23804
  return;
23271
23805
  }
23272
- emit_data(status, `${node.name}: .space 16\n`);
23273
- status.stack_offsets.set(has_flag_name(node.name), -1);
23806
+ const global_is_null = !node.value || node.value.node_type === "value" && node.value.value === "null";
23807
+ if (global_is_null) emit_data(status, `${node.name}: .space 8\n`);
23808
+ else {
23809
+ emit_data(status, `${node.name}: ${aarch64_type(node.type.name)} ${get_raw_value$1(node.value, status)}\n`);
23810
+ if (aarch64_size(node.type.name) < 8) emit_data(status, `.p2align 3\n`);
23811
+ }
23812
+ emit_data(status, `${has_flag_name(node.name)}: .quad ${global_is_null ? 0 : 1}\n`);
23274
23813
  return;
23275
23814
  }
23276
23815
  const directive = aarch64_type(node.type.name);
@@ -23893,6 +24432,8 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23893
24432
  if (outgoing > 0) emit_asm(status, `add sp, sp, #${outgoing}\n`);
23894
24433
  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
24434
  build_swap_params(func_call, status);
24435
+ const ctor_struct = status.structs.find((s) => s.name === func_call.name && !s.is_simple_type && !s.is_class);
24436
+ if (ctor_struct) record_ctor_heap_default_fields(node.name, ctor_struct, status, (seed) => is_owned_heap_temp(seed, status));
23896
24437
  } 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
24438
  const old_buffer = status.struct_return_buffer;
23898
24439
  const old_buffer_var = status.struct_return_buffer_var;
@@ -24770,14 +25311,18 @@ const INT_CALLER_SAVED_EXT = [
24770
25311
  function can_share_claimed_register(status, name, reg) {
24771
25312
  const shared = status.nir_alloc_shared;
24772
25313
  if (!shared || !status.register_allocations) return false;
25314
+ if (!shared.source_keys.has(name)) return false;
24773
25315
  if (status.region_pinned?.get(reg)) return false;
24774
- const cand_keys = shared.source_keys.get(name) ?? [name];
25316
+ if (status.nir_loop_carry_regs?.has(reg)) return false;
25317
+ const cand_keys = shared.source_keys.get(name);
24775
25318
  let occupants = 0;
24776
25319
  for (const [occupant, occupant_reg] of status.register_allocations) {
24777
25320
  if (occupant_reg !== reg || occupant === name) continue;
24778
25321
  occupants++;
24779
25322
  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;
25323
+ const occ_keys = shared.source_keys.get(occupant);
25324
+ if (!occ_keys) return false;
25325
+ 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
25326
  }
24782
25327
  return occupants > 0;
24783
25328
  }
@@ -24954,6 +25499,7 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
24954
25499
  else used_x.add(r);
24955
25500
  if (status.nir_caller_saved_claimed) for (const r of status.nir_caller_saved_claimed) used_x.add(r);
24956
25501
  if (status.region_pinned) for (const r of status.region_pinned.keys()) used_x.add(r);
25502
+ const loop_carry_regs = status.nir_loop_carry_regs;
24957
25503
  const site_regs = /* @__PURE__ */ new Set();
24958
25504
  if (status.nir_site_allocs) for (const s of status.nir_site_allocs.values()) site_regs.add(s.reg);
24959
25505
  const float_pool = options?.call_free === true ? [...FLOAT_CALLEE_SAVED, ...FLOAT_CALLER_SAVED_EXT] : FLOAT_CALLEE_SAVED;
@@ -24977,6 +25523,10 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
24977
25523
  d_idx++;
24978
25524
  continue;
24979
25525
  }
25526
+ if (loop_carry_regs?.has(reg0) || loop_carry_regs?.has(reg1)) {
25527
+ d_idx++;
25528
+ continue;
25529
+ }
24980
25530
  if (used_d.has(reg0)) {
24981
25531
  if (!can_share_claimed_register(status, v.name, reg0)) {
24982
25532
  d_idx++;
@@ -25024,6 +25574,10 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
25024
25574
  d_idx++;
25025
25575
  continue;
25026
25576
  }
25577
+ if (loop_carry_regs?.has(float_pool[d_idx])) {
25578
+ d_idx++;
25579
+ continue;
25580
+ }
25027
25581
  if (!used_d.has(float_pool[d_idx])) break;
25028
25582
  if (can_share_claimed_register(status, v.name, float_pool[d_idx])) break;
25029
25583
  d_idx++;
@@ -25046,6 +25600,10 @@ function promote_loop_locals(status, scoped_declarations, sources, options) {
25046
25600
  x_idx++;
25047
25601
  continue;
25048
25602
  }
25603
+ if (loop_carry_regs?.has(x_pool[x_idx])) {
25604
+ x_idx++;
25605
+ continue;
25606
+ }
25049
25607
  if (!used_x.has(x_pool[x_idx])) break;
25050
25608
  if (can_share_claimed_register(status, v.name, x_pool[x_idx])) break;
25051
25609
  x_idx++;
@@ -25357,23 +25915,33 @@ function build_if_else_node$1(node, status, nir) {
25357
25915
  ensure_newline(status);
25358
25916
  const pre_cache = status.buffer_data_cache;
25359
25917
  const pre_array_cache = status.array_ptr_cache;
25918
+ const pre_heap_string_fields = status.heap_string_fields;
25360
25919
  if (node.else_branch) {
25361
25920
  const then_frame = enter_scope_frame(status);
25362
25921
  status.buffer_data_cache = new Map(pre_cache);
25363
25922
  status.array_ptr_cache = new Map(pre_array_cache);
25923
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25364
25924
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
25925
+ const then_fields = status.heap_string_fields;
25365
25926
  exit_scope_frame(status, then_frame);
25366
25927
  emit_asm(status, `b end_${label}\n`);
25367
25928
  emit_asm(status, `else_${label}:\n`);
25368
25929
  const else_frame = enter_scope_frame(status);
25369
25930
  status.buffer_data_cache = new Map(pre_cache);
25370
25931
  status.array_ptr_cache = new Map(pre_array_cache);
25932
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25371
25933
  build_block_with_cursor$1(node.else_branch, nir?.else_branch, status);
25934
+ const else_fields = status.heap_string_fields;
25372
25935
  exit_scope_frame(status, else_frame);
25936
+ const merged = new Set(pre_heap_string_fields ?? []);
25937
+ for (const key of then_fields ?? []) merged.add(key);
25938
+ for (const key of else_fields ?? []) merged.add(key);
25939
+ status.heap_string_fields = merged;
25373
25940
  } else if (node.if_branch) {
25374
25941
  const then_frame = enter_scope_frame(status);
25375
25942
  status.buffer_data_cache = new Map(pre_cache);
25376
25943
  status.array_ptr_cache = new Map(pre_array_cache);
25944
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25377
25945
  build_block_with_cursor$1(node.if_branch, nir?.then_branch, status);
25378
25946
  exit_scope_frame(status, then_frame);
25379
25947
  }
@@ -25541,6 +26109,95 @@ function ctor_return_owned_string_fields(return_struct, call, heap_strings) {
25541
26109
  }
25542
26110
  return skip;
25543
26111
  }
26112
+ /** The OVERRIDE value of a string field, when the field is overridden. */
26113
+ function override_value_for_field(overrides, field_name) {
26114
+ return overrides?.find((o) => o.name === field_name)?.value;
26115
+ }
26116
+ /**
26117
+ * The skip set for an OVERRIDE return (`return [ .. <base>, f = v, ... ]` in
26118
+ * its checked forms: a ctor call with `field_overrides`, or a base-bearing
26119
+ * anonymous struct literal). The overrides were applied to the return temp
26120
+ * BEFORE this analysis runs, so an overridden field holds the OVERRIDE
26121
+ * value's pair: it stays raw only when that value owns heap (a transferred
26122
+ * heap local, a fresh non-borrow expression). A non-overridden field keeps
26123
+ * the base's value: with `base_uniformly_owned` (a forwarded registered
26124
+ * normalizing base) it transfers raw; otherwise it follows `base_skip` (the
26125
+ * plain constructor-argument ownership analysis).
26126
+ */
26127
+ function override_return_string_fields(return_struct, overrides, heap_strings, opts) {
26128
+ const skip = /* @__PURE__ */ new Set();
26129
+ for (const field of direct_string_fields(return_struct)) {
26130
+ const override = override_value_for_field(overrides, field.name);
26131
+ if (override) {
26132
+ if (tuple_return_owned_element(override, heap_strings)) skip.add(field.name);
26133
+ } else if (opts.base_uniformly_owned || opts.base_skip?.has(field.name)) skip.add(field.name);
26134
+ }
26135
+ return skip;
26136
+ }
26137
+ /**
26138
+ * The overridden string fields whose CONSTRUCTOR-SEEDED value owns heap and
26139
+ * is therefore displaced un-freed by a raw override store: a non-literal
26140
+ * default expression (the auto-constructor evaluates it fresh per
26141
+ * construction — `var string a = "de" + "fault"` allocates) is owned by the
26142
+ * constructed struct alone, so an override replacing the field orphans the
26143
+ * buffer unless it is reclaimed ahead of the store. Literal defaults are
26144
+ * rodata (never freed) and value-node defaults are consts/borrows (not
26145
+ * owned); a custom `#init`'s `self.<field> = <param>` seeds alias the
26146
+ * caller's argument temps in the common case, but a COMPUTED seed
26147
+ * (`self.a = s + "!"`, classified by `is_init_seed_heap`) owns heap — those
26148
+ * fields are displaced too.
26149
+ */
26150
+ function override_displaced_string_fields(struct_node, overrides, is_init_seed_heap) {
26151
+ if (!overrides?.length) return [];
26152
+ const computed = init_computed_heap_string_fields(struct_node, is_init_seed_heap);
26153
+ const displaced = [];
26154
+ for (const override of overrides) {
26155
+ const field = struct_node.fields.find((f) => f.name === override.name);
26156
+ if (!field) continue;
26157
+ if (field.type.name !== "string" || field.type.is_array || field.type.is_view) continue;
26158
+ if (computed.has(override.name)) {
26159
+ displaced.push(override.name);
26160
+ continue;
26161
+ }
26162
+ const seed = field.value;
26163
+ if (!seed || seed.node_type === "value") continue;
26164
+ if (is_string_borrow(seed)) continue;
26165
+ displaced.push(override.name);
26166
+ }
26167
+ return displaced;
26168
+ }
26169
+ /**
26170
+ * The aarch64 counterpart of override_displaced_string_fields: the
26171
+ * overridden string fields whose ctor-seeded value is a non-literal
26172
+ * expression that produced a HEAP value — a non-literal DEFAULT, or a
26173
+ * custom `#init`'s computed seed (`init_computed_heap_string_fields` under
26174
+ * the same `is_owned_heap` classifier). The extra `is_owned_heap` check
26175
+ * (the aarch64 is_owned_heap_temp) matters because the aarch64 backend does
26176
+ * NOT strdup every string return: a literal-returning user function leaves
26177
+ * RODATA in the seeded field, which must never be freed. Used at the
26178
+ * expression-temp override boundaries (no heap_string_fields records exist
26179
+ * there, so the stores stay raw and the seed needs an explicit reclaim).
26180
+ */
26181
+ function ctor_heap_displaced_string_fields(struct_node, overrides, is_owned_heap) {
26182
+ if (!overrides?.length) return [];
26183
+ const computed = init_computed_heap_string_fields(struct_node, is_owned_heap);
26184
+ const displaced = [];
26185
+ for (const override of overrides) {
26186
+ const field = struct_node.fields.find((f) => f.name === override.name);
26187
+ if (!field) continue;
26188
+ if (field.type.name !== "string" || field.type.is_array || field.type.is_view) continue;
26189
+ if (computed.has(override.name)) {
26190
+ displaced.push(override.name);
26191
+ continue;
26192
+ }
26193
+ const seed = field.value;
26194
+ if (!seed || seed.node_type === "value") continue;
26195
+ if (is_string_borrow(seed)) continue;
26196
+ if (!is_owned_heap(seed)) continue;
26197
+ displaced.push(override.name);
26198
+ }
26199
+ return displaced;
26200
+ }
25544
26201
  //#endregion
25545
26202
  //#region ../src/build_aarch64/build_return_node.ts
25546
26203
  let temp_counter$1 = 0;
@@ -25616,7 +26273,7 @@ function build_return_node$1(node, status, nir_value) {
25616
26273
  hoist_field_overrides(node.value, build_node, status);
25617
26274
  if (node.from_inline) return;
25618
26275
  const returns_nullable_struct = is_nullable_struct_type(status.function_return_type, status);
25619
- const returns_nullable_scalar = is_nullable_scalar_type(status.function_return_type);
26276
+ const returns_nullable_scalar = is_nullable_scalar_type(status.function_return_type, status);
25620
26277
  if ((returns_nullable_struct || returns_nullable_scalar) && (!node.value || node.value.node_type === "value" && node.value.value === "null")) {
25621
26278
  if (status.return_buffer_stack_offset !== void 0) {
25622
26279
  const struct_size = get_struct_size(status.function_return_type.name, status);
@@ -25723,7 +26380,7 @@ function build_return_node$1(node, status, nir_value) {
25723
26380
  if (node.value?.node_type === "anon_struct") {
25724
26381
  const anon = node.value;
25725
26382
  const struct_size = get_struct_size(anon.type.name, status);
25726
- const temp_name = `_anon_ret_${temp_counter$1++}`;
26383
+ const temp_name = `_return_val_anon_${temp_counter$1++}`;
25727
26384
  const offset = allocate_stack_space(status, struct_size);
25728
26385
  status.stack_offsets.set(temp_name, offset);
25729
26386
  if (!status.variable_types) status.variable_types = /* @__PURE__ */ new Map();
@@ -25733,6 +26390,14 @@ function build_return_node$1(node, status, nir_value) {
25733
26390
  emit_var_address(status, "x1", temp_name);
25734
26391
  emit_struct_copy("x0", "x1", 0, struct_size, status);
25735
26392
  if (anon.base.is_moved) mark_moved_if_struct(anon.base, status);
26393
+ if (anon.base.node_type === "func_call" && is_normalized_struct_call(anon.base, status)) {
26394
+ const base_struct = status.structs.find((s) => s.name === anon.type.name && !s.is_simple_type && !s.is_class);
26395
+ if (base_struct) for (const field of direct_string_fields(base_struct)) {
26396
+ if (!anon.fields.some((o) => o.name === field.name)) continue;
26397
+ emit_asm(status, `ldp x0, x1, [x29, #${offset + get_field_offset(base_struct.name, field.name, status)}]\n`);
26398
+ emit_asm(status, `bl _nomen_free_wrap\n`);
26399
+ }
26400
+ }
25736
26401
  emit_field_overrides(temp_name, anon, build_node, status);
25737
26402
  emit_var_address(status, "x0", temp_name);
25738
26403
  } else {
@@ -25768,7 +26433,7 @@ function build_return_node$1(node, status, nir_value) {
25768
26433
  if (!is_str_lit && !is_numeric && raw_value !== "null" && !status.heap_strings?.has(raw_value)) needs_borrow_strdup = true;
25769
26434
  } else if (is_container_borrow_access(node.value)) needs_borrow_strdup = true;
25770
26435
  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;
26436
+ 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
26437
  else if ((node.value.node_type === "match" || node.value.node_type === "switch") && !return_join_owned_string) needs_borrow_strdup = true;
25773
26438
  }
25774
26439
  if (needs_borrow_strdup) {
@@ -25777,6 +26442,15 @@ function build_return_node$1(node, status, nir_value) {
25777
26442
  ensure_newline(status);
25778
26443
  status.last_result_is_heap = true;
25779
26444
  }
26445
+ 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) {
26446
+ emit_strdup_string(status);
26447
+ ensure_newline(status);
26448
+ status.last_result_is_heap = true;
26449
+ if (borrow_fn_name) {
26450
+ status.heap_returning_functions?.add(borrow_fn_name);
26451
+ if (borrow_mangled !== void 0) status.heap_returning_functions?.add(borrow_mangled);
26452
+ }
26453
+ }
25780
26454
  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
26455
  if (status.last_result_is_heap && current_return_is_string(status)) {
25782
26456
  if (!status.heap_returning_functions) status.heap_returning_functions = /* @__PURE__ */ new Set();
@@ -25789,7 +26463,7 @@ function build_return_node$1(node, status, nir_value) {
25789
26463
  if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
25790
26464
  emit_typed_store(status, "x0", "x8", 0, aarch64_size(status.function_return_type.name));
25791
26465
  const value_type = node.value ? type_from_value_node$1(node.value) : void 0;
25792
- if (node.value && value_type?.is_nullable && is_nullable_scalar_type(value_type)) {
26466
+ if (node.value && value_type?.is_nullable && is_nullable_scalar_type(value_type, status)) {
25793
26467
  load_nullable_has(node.value, "x9", status);
25794
26468
  ensure_newline(status);
25795
26469
  emit_asm(status, `str x9, [x8, #8]\n`);
@@ -25837,10 +26511,19 @@ function build_return_node$1(node, status, nir_value) {
25837
26511
  }
25838
26512
  emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
25839
26513
  }
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);
26514
+ if (node.value?.node_type === "func_call" && node.value.name === ret_struct.name && !ret_struct.name.startsWith("_")) {
26515
+ const call = node.value;
26516
+ 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
26517
  emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
25843
26518
  }
26519
+ {
26520
+ const anon = node.value?.node_type === "anon_struct" ? node.value : void 0;
26521
+ 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;
26522
+ if (forwarded_base && node.value.name !== ret_struct.name && is_normalized_struct_call(forwarded_base, status)) {
26523
+ const skip_fields = override_return_string_fields(ret_struct, anon ? anon.fields : node.value.field_overrides, status.heap_strings, { base_uniformly_owned: true });
26524
+ emit_string_field_strdups_at(status, ret_struct.name, "x8", 0, skip_fields);
26525
+ }
26526
+ }
25844
26527
  if (node.value?.node_type === "func_call" && String(node.value.name ?? "").startsWith("_Tuple_") && node.value.type?.name?.startsWith("_Tuple_")) {
25845
26528
  const tuple_call = node.value;
25846
26529
  const skip_fields = /* @__PURE__ */ new Set();
@@ -25943,6 +26626,8 @@ function build_switch_node$1(node, status, nir) {
25943
26626
  const old_scoped_declarations = enter_scope_frame(status);
25944
26627
  const pre_cache = status.buffer_data_cache;
25945
26628
  const pre_array_cache = status.array_ptr_cache;
26629
+ const pre_heap_string_fields = status.heap_string_fields;
26630
+ const case_field_sets = [];
25946
26631
  for (let i = 0; i < node.cases.length; i++) {
25947
26632
  status.scoped_declarations = [];
25948
26633
  build_node(node.cases[i].condition, status);
@@ -25951,7 +26636,9 @@ function build_switch_node$1(node, status, nir) {
25951
26636
  else emit_asm(status, `beq end_switch_${label}\n`);
25952
26637
  status.buffer_data_cache = new Map(pre_cache);
25953
26638
  status.array_ptr_cache = new Map(pre_array_cache);
26639
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25954
26640
  build_block_with_cursor$1(node.cases[i].branch, nir?.arms[i]?.branch, status);
26641
+ case_field_sets.push(status.heap_string_fields);
25955
26642
  emit_asm(status, `b end_switch_${label}\n`);
25956
26643
  emit_asm(status, `sw_next_${label}_${i}:\n`);
25957
26644
  }
@@ -25959,8 +26646,15 @@ function build_switch_node$1(node, status, nir) {
25959
26646
  status.scoped_declarations = [];
25960
26647
  status.buffer_data_cache = new Map(pre_cache);
25961
26648
  status.array_ptr_cache = new Map(pre_array_cache);
26649
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
25962
26650
  build_block_with_cursor$1(node.else_branch, nir?.otherwise ?? void 0, status);
26651
+ case_field_sets.push(status.heap_string_fields);
25963
26652
  }
26653
+ if (case_field_sets.length > 1) {
26654
+ const merged = new Set(pre_heap_string_fields ?? []);
26655
+ for (const set of case_field_sets) for (const key of set) merged.add(key);
26656
+ status.heap_string_fields = merged;
26657
+ } else if (case_field_sets.length === 1) status.heap_string_fields = case_field_sets[0];
25964
26658
  status.buffer_data_cache = pre_cache;
25965
26659
  status.array_ptr_cache = pre_array_cache;
25966
26660
  emit_asm(status, `end_switch_${label}:\n`);
@@ -29399,7 +30093,7 @@ function build_function_call_node$1(node, status) {
29399
30093
  if (!node.nullable_param_indices?.includes(i)) continue;
29400
30094
  const arg_type = node.params[i].type;
29401
30095
  const callee_param = node.resolved_function?.params?.[i];
29402
- if (is_nullable_scalar_type(arg_type) || callee_param && is_nullable_scalar_type(callee_param.type)) nullable_scalar_arg_set.add(i);
30096
+ if (is_nullable_scalar_type(arg_type, status) || callee_param && is_nullable_scalar_type(callee_param.type, status)) nullable_scalar_arg_set.add(i);
29403
30097
  }
29404
30098
  const arg_slot = [];
29405
30099
  let total_slots = 0;
@@ -29497,7 +30191,7 @@ function build_function_call_node$1(node, status) {
29497
30191
  if (arg.node_type === "value" && arg.value === "null") {
29498
30192
  emit_asm(status, `mov x0, #0\n`);
29499
30193
  emit_asm(status, `mov x1, #0\n`);
29500
- } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type)) {
30194
+ } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type, status)) {
29501
30195
  const vname = arg.value;
29502
30196
  emit_var_load(status, "x0", vname, aarch64_size(param_type.name));
29503
30197
  emit_var_load(status, "x1", has_flag_name(vname), 8);
@@ -29611,7 +30305,7 @@ function build_function_call_node$1(node, status) {
29611
30305
  } else if (!is_struct && node.type?.name && !status.call_x8_preset) {
29612
30306
  const return_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
29613
30307
  const return_enum_size = get_enum_sret_size(node.type?.name, status);
29614
- const nullable_scalar_ret = is_nullable_scalar_type(node.type);
30308
+ const nullable_scalar_ret = is_nullable_scalar_type(node.type, status);
29615
30309
  if (return_struct || return_enum_size !== void 0 || nullable_scalar_ret) {
29616
30310
  const nullable_ret = is_nullable_struct_type(node.type, status);
29617
30311
  const struct_size = return_struct ? get_struct_size(node.type.name, status) : nullable_scalar_ret ? 8 : return_enum_size;
@@ -29628,6 +30322,12 @@ function build_function_call_node$1(node, status) {
29628
30322
  if (build_inline_function(inline_candidate, status)) return;
29629
30323
  }
29630
30324
  emit_asm(status, `bl ${func_name}\n`);
30325
+ {
30326
+ const callee = node.resolved_function;
30327
+ if (callee && !callee.params?.some((p) => p.is_self_param)) transfer_ref_param_field_records(callee, node.params, status, (name) => {
30328
+ return !!status.scoped_declarations?.some((d) => d.name === name) && !is_local_ref_var(name, status);
30329
+ });
30330
+ }
29631
30331
  if (is_float_type$1(node.type.name)) emit_asm(status, `fmov x0, d0\n`);
29632
30332
  if (outgoing_size > 0) emit_asm(status, `add sp, sp, #${outgoing_size}\n`);
29633
30333
  if (status.buffer_data_cache) {
@@ -29661,7 +30361,7 @@ function build_function_call_node$1(node, status) {
29661
30361
  if (!is_struct && node.type?.name && !status.call_x8_preset) {
29662
30362
  const return_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
29663
30363
  const return_enum_size = get_enum_sret_size(node.type?.name, status);
29664
- const nullable_scalar_ret = is_nullable_scalar_type(node.type);
30364
+ const nullable_scalar_ret = is_nullable_scalar_type(node.type, status);
29665
30365
  if (return_struct || return_enum_size !== void 0 || nullable_scalar_ret) {
29666
30366
  const temp_name = `_call_ret_${temp_counter - 1}`;
29667
30367
  emit_asm(status, `add x0, x29, #${status.stack_offsets.get(temp_name)}\n`);
@@ -29678,6 +30378,11 @@ function build_function_call_node$1(node, status) {
29678
30378
  const offset = status.stack_offsets.get(temp_addr);
29679
30379
  emit_asm(status, `add x0, x29, #${offset}\n`);
29680
30380
  if (node.field_overrides?.length) {
30381
+ const ctor_struct = status.structs.find((s) => s.name === node.name && !s.is_simple_type && !s.is_class);
30382
+ if (ctor_struct) for (const fname of ctor_heap_displaced_string_fields(ctor_struct, node.field_overrides, (seed) => is_owned_heap_temp(seed, status))) {
30383
+ emit_asm(status, `ldr x0, [x29, #${offset + get_field_offset(ctor_struct.name, fname, status)}]\n`);
30384
+ emit_free(status);
30385
+ }
29681
30386
  emit_field_overrides(temp_addr, node, build_node, status);
29682
30387
  emit_asm(status, `add x0, x29, #${offset}\n`);
29683
30388
  }
@@ -30531,7 +31236,7 @@ function build_struct_body(node, status) {
30531
31236
  status.code += `];\n`;
30532
31237
  } else {
30533
31238
  status.code += `${field_c_type(field.type, status)} ${field.name};\n`;
30534
- if (is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type)) status.code += `unsigned char ${has_flag_name(field.name)};\n`;
31239
+ if (is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) status.code += `unsigned char ${has_flag_name(field.name)};\n`;
30535
31240
  }
30536
31241
  for (let traitName of node.traits) {
30537
31242
  const trait = status.traits.find((n) => n.name === traitName);
@@ -30659,12 +31364,12 @@ function build_operation_node$1(node, status) {
30659
31364
  if (left_type?.is_nullable && left_type.name === "string") {
30660
31365
  status.code += `({ nomen_string _nss_${ns_tmp_counter} = `;
30661
31366
  build_node$1(node.left_value, status);
30662
- status.code += `; _nss_${ns_tmp_counter}.ptr ? _nss_${ns_tmp_counter} : `;
31367
+ status.code += `; _nss_${ns_tmp_counter}.ptr ? nomen_str_dup(_nss_${ns_tmp_counter}) : nomen_str_dup(`;
30663
31368
  build_node$1(node.right_value, status);
30664
- status.code += `; })`;
31369
+ status.code += `); })`;
30665
31370
  ns_tmp_counter++;
30666
- } else if (is_nullable_scalar_type(left_type)) {
30667
- if (node.left_value.node_type === "func_call" && is_nullable_scalar_type(node.left_value.type)) {
31371
+ } else if (is_nullable_scalar_type(left_type, status)) {
31372
+ if (node.left_value.node_type === "func_call" && is_nullable_scalar_type(node.left_value.type, status)) {
30668
31373
  const id = ns_tmp_counter++;
30669
31374
  const flag = `_nsh_${id}`;
30670
31375
  const val = `_nsv_${id}`;
@@ -30726,8 +31431,8 @@ function build_operation_node$1(node, status) {
30726
31431
  else status.code += `((`;
30727
31432
  build_node$1(nullable_side, status);
30728
31433
  status.code += `.ptr != 0))`;
30729
- } else if (is_nullable_scalar_type(type_from_value_node$1(nullable_side)) && type?.is_nullable && type.name !== "string") {
30730
- if (nullable_side.node_type === "func_call" && is_nullable_scalar_type(nullable_side.type)) {
31434
+ } else if (is_nullable_scalar_type(type_from_value_node$1(nullable_side), status) && type?.is_nullable && type.name !== "string") {
31435
+ if (nullable_side.node_type === "func_call" && is_nullable_scalar_type(nullable_side.type, status)) {
30731
31436
  const id = ns_tmp_counter++;
30732
31437
  const flag = `_nsh_${id}`;
30733
31438
  const val = `_nsv_${id}`;
@@ -30891,7 +31596,7 @@ function is_null_comparison(node) {
30891
31596
  }
30892
31597
  function is_nullable_struct_side(node, status) {
30893
31598
  const t = type_from_value_node$1(node);
30894
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
31599
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
30895
31600
  }
30896
31601
  /**
30897
31602
  * Build the companion `_has` flag expression for a nullable-struct or
@@ -30909,8 +31614,8 @@ function is_nullable_struct_side(node, status) {
30909
31614
  function build_nullable_has(node, status) {
30910
31615
  if (node.node_type === "value") {
30911
31616
  const name = node.value;
30912
- if (status.function_ref_params?.has(name) && (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type))) return has_flag_name(name);
30913
- if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type)) return has_flag_name(name);
31617
+ if (status.function_ref_params?.has(name) && (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status))) return has_flag_name(name);
31618
+ if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status)) return has_flag_name(name);
30914
31619
  }
30915
31620
  const saved = begin_code_scratch(status);
30916
31621
  build_node$1(node, status);
@@ -32575,7 +33280,7 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
32575
33280
  if (rhs_is_null) status.code += `${flag} = 0`;
32576
33281
  else {
32577
33282
  const call_ret = call_result_type(node.right_value);
32578
- if (!!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret))) {
33283
+ if (!!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret, status))) {
32579
33284
  const old = status.current_nullable_call_flag;
32580
33285
  status.current_nullable_call_flag = flag;
32581
33286
  status.code += `${lhs_expr} = `;
@@ -32665,7 +33370,7 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
32665
33370
  status.code += `;\n`;
32666
33371
  return;
32667
33372
  }
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) {
33373
+ 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
33374
  const access_lhs = node.left_value;
32670
33375
  const field_access_node = access_lhs.access;
32671
33376
  let target_type = type_from_value_node$1(access_lhs.target);
@@ -32953,7 +33658,10 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
32953
33658
  const needs_destroy = struct_type ? struct_needs_destroy_by_name(struct_type.name, status) : false;
32954
33659
  const rhs_is_free_factory = node.right_value.node_type === "func_call" && !rhs_references_var(node, lhs_name);
32955
33660
  if (is_fresh_constructor(node, status) || rhs_is_free_factory) {
32956
- if (struct_type) release_recorded_string_fields(status, struct_type, lhs_name);
33661
+ if (struct_type) {
33662
+ release_recorded_string_fields(status, struct_type, lhs_name);
33663
+ 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) ?? []));
33664
+ }
32957
33665
  if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
32958
33666
  } else if (is_self_method_call(node, lhs_name)) {} else if (!rhs_references_var(node, lhs_name)) {
32959
33667
  if (needs_destroy) emit_struct_destroys(status, struct_type, lhs_name);
@@ -32998,6 +33706,12 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
32998
33706
  }
32999
33707
  }
33000
33708
  }
33709
+ 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))) {
33710
+ const saved_seed_len = begin_code_scratch(status);
33711
+ build_node$1(node.left_value, status);
33712
+ const seed_access = end_code_scratch(status, saved_seed_len);
33713
+ status.code += `free(${seed_access}.ptr);\n`;
33714
+ }
33001
33715
  build_node$1(node.left_value, status);
33002
33716
  if (node.operator) status.code += ` ${node.operator.slice(0, -1)}= `;
33003
33717
  else status.code += " = ";
@@ -33006,7 +33720,15 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
33006
33720
  else emit_rhs_value$1(node.right_value, nir_rhs, status);
33007
33721
  if (node.left_value.node_type === "value" && has_field_overrides(node.right_value)) {
33008
33722
  const lname = node.left_value.value;
33009
- emit_field_overrides(lname, node.right_value, build_node$1, status, ";\n", ";\n");
33723
+ const rhs_call = node.right_value.node_type === "func_call" ? node.right_value : void 0;
33724
+ if (rhs_call?.field_overrides?.length && rhs_call.name === rhs_call.type?.name) {
33725
+ const override_struct = status.structs.find((s) => s.name === rhs_call.name && !s.is_simple_type && !s.is_class);
33726
+ if (override_struct) {
33727
+ status.code += `;\n`;
33728
+ 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`;
33729
+ }
33730
+ }
33731
+ 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
33732
  }
33011
33733
  if (node.swap) {
33012
33734
  status.code += `;\n`;
@@ -33028,11 +33750,11 @@ function lhs_nullable_struct_type(node, status) {
33028
33750
  if (node.left_value.node_type === "value") {
33029
33751
  const name = node.left_value.value;
33030
33752
  const t = status.scoped_declarations.find((d) => d.name === name)?.type || status.variable_types?.get(name);
33031
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
33753
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
33032
33754
  }
33033
33755
  if (node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field") {
33034
33756
  const field_type = node.left_value.access.type;
33035
- return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type);
33757
+ return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type, status);
33036
33758
  }
33037
33759
  return false;
33038
33760
  }
@@ -33045,7 +33767,7 @@ function lhs_nullable_struct_type(node, status) {
33045
33767
  function nullable_value_flag_expr(value, status) {
33046
33768
  const t = type_from_value_node$1(value);
33047
33769
  if (!t?.is_nullable) return void 0;
33048
- if (!is_nullable_struct_type(t, status) && !is_nullable_scalar_type(t)) return void 0;
33770
+ if (!is_nullable_struct_type(t, status) && !is_nullable_scalar_type(t, status)) return void 0;
33049
33771
  if (value.node_type === "value") return has_flag_name(value.value);
33050
33772
  if (value.node_type === "access" && value.access.node_type === "access_field") {
33051
33773
  const saved = begin_code_scratch(status);
@@ -33776,32 +34498,37 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
33776
34498
  status.stack_array_lengths.set(safe_name, len_text);
33777
34499
  }
33778
34500
  }
33779
- if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type)) {
34501
+ if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status)) {
33780
34502
  const flag = has_flag_name(safe_name);
33781
34503
  const is_null = !node.value || node.value.node_type === "value" && node.value.value === "null";
33782
- if (!status.current_function && is_nullable_scalar_type(node.type)) {
33783
- status.code += `;\nunsigned char ${flag} = ${is_null ? 0 : 1}`;
33784
- if (is_null) return;
33785
- } else {
33786
- status.code += `;\nunsigned char ${flag} = 0`;
33787
- if (node.value && !is_null) {
33788
- const call_ret = call_result_type(node.value);
33789
- const value_is_nullable_call = !!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret));
33790
- status.code += `;\n${safe_name} = `;
33791
- if (value_is_nullable_call) {
33792
- const old = status.current_nullable_call_flag;
33793
- status.current_nullable_call_flag = flag;
33794
- emit_init_value(node.value, nir_init, status);
33795
- status.current_nullable_call_flag = old;
33796
- record_call_init_string_fields(node, status);
33797
- } else {
33798
- const rhs_flag = nullable_value_flag_expr(node.value, status);
33799
- emit_init_value(node.value, nir_init, status);
33800
- status.code += `;\n${flag} = ${rhs_flag ?? "1"}`;
33801
- }
34504
+ if (!status.current_function && is_nullable_scalar_type(node.type, status)) {
34505
+ if (is_null) {
34506
+ status.code += `;\nunsigned char ${flag} = 0`;
34507
+ return;
33802
34508
  }
34509
+ status.code += ` = `;
34510
+ emit_init_value(node.value, nir_init, status);
34511
+ status.code += `;\nunsigned char ${flag} = 1`;
33803
34512
  return;
33804
34513
  }
34514
+ status.code += `;\nunsigned char ${flag} = 0`;
34515
+ if (node.value && !is_null) {
34516
+ const call_ret = call_result_type(node.value);
34517
+ const value_is_nullable_call = !!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret, status));
34518
+ status.code += `;\n${safe_name} = `;
34519
+ if (value_is_nullable_call) {
34520
+ const old = status.current_nullable_call_flag;
34521
+ status.current_nullable_call_flag = flag;
34522
+ emit_init_value(node.value, nir_init, status);
34523
+ status.current_nullable_call_flag = old;
34524
+ record_call_init_string_fields(node, status);
34525
+ } else {
34526
+ const rhs_flag = nullable_value_flag_expr(node.value, status);
34527
+ emit_init_value(node.value, nir_init, status);
34528
+ status.code += `;\n${flag} = ${rhs_flag ?? "1"}`;
34529
+ }
34530
+ }
34531
+ return;
33805
34532
  }
33806
34533
  if (node.value) {
33807
34534
  const is_heap_literal_init = node.is_heap_array_literal || node.type.storage_kind === "heap_array" && (node.value.node_type === "array" || node.value.node_type === "range");
@@ -33913,8 +34640,20 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
33913
34640
  status.code += `${field_access} = `;
33914
34641
  emit_swap_value$1(node.swap, nir_swap, status);
33915
34642
  }
33916
- if (has_field_overrides(node.value)) emit_field_overrides(safe_name, node.value, build_node$1, status, ";\n", ";\n");
33917
34643
  record_call_init_string_fields(node, status);
34644
+ 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;
34645
+ if (override_ctor_call) {
34646
+ const override_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
34647
+ if (override_struct) {
34648
+ status.code += `;\n`;
34649
+ 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`;
34650
+ }
34651
+ }
34652
+ if (node.value?.node_type === "func_call" && node.value.name === node.type?.name && !is_normalized_struct_call(node.value, status)) {
34653
+ const ctor_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
34654
+ 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) ?? []));
34655
+ }
34656
+ if (has_field_overrides(node.value)) emit_field_overrides(safe_name, node.value, build_node$1, status, override_ctor_call ? "" : ";\n", ";\n");
33918
34657
  }
33919
34658
  }
33920
34659
  }
@@ -34207,21 +34946,32 @@ function build_if_else_node(node, status, nir) {
34207
34946
  status.scoped_declarations = enter_c_scope(status);
34208
34947
  const old_deferred_frees = status.deferred_frees;
34209
34948
  status.deferred_frees = [];
34949
+ const pre_heap_string_fields = status.heap_string_fields;
34210
34950
  emit_allocations(node.condition, status);
34211
34951
  status.code += `if (`;
34212
34952
  build_condition(node.condition, status);
34213
34953
  status.code += `) {\n`;
34954
+ let then_fields;
34214
34955
  if (node.if_branch) {
34215
34956
  build_block_with_cursor(node.if_branch, nir?.then_branch, status);
34216
34957
  build_auto_free(status);
34217
34958
  }
34959
+ then_fields = status.heap_string_fields;
34218
34960
  leave_c_scope(status);
34219
34961
  if (node.else_branch) {
34220
34962
  status.scoped_declarations = enter_c_scope(status);
34221
34963
  status.deferred_frees = [];
34964
+ status.heap_string_fields = pre_heap_string_fields;
34222
34965
  status.code += `} else {\n`;
34223
34966
  build_block_with_cursor(node.else_branch, nir?.else_branch, status);
34224
34967
  build_auto_free(status);
34968
+ const else_fields = status.heap_string_fields;
34969
+ if (else_fields !== then_fields) {
34970
+ const merged = new Set(pre_heap_string_fields ?? []);
34971
+ for (const key of then_fields ?? []) merged.add(key);
34972
+ for (const key of else_fields ?? []) merged.add(key);
34973
+ status.heap_string_fields = merged;
34974
+ }
34225
34975
  leave_c_scope(status);
34226
34976
  }
34227
34977
  status.code += `}\n`;
@@ -34395,7 +35145,7 @@ function build_return_node(node, status, nir_value) {
34395
35145
  emit_nursery_joins_on_return_c(status);
34396
35146
  reclaim_all_c_scopes(status);
34397
35147
  status.code += `*${ret_has} = 0;\n`;
34398
- if (status.function_return_type && is_nullable_scalar_type(status.function_return_type)) status.code += `return (${c_type(status.function_return_type.name)})0;\n`;
35148
+ if (status.function_return_type && is_nullable_scalar_type(status.function_return_type, status)) status.code += `return (${c_type(status.function_return_type.name)})0;\n`;
34399
35149
  else status.code += `return (struct ${status.function_return_type.name}){0};\n`;
34400
35150
  return;
34401
35151
  }
@@ -34558,14 +35308,31 @@ function build_return_node(node, status, nir_value) {
34558
35308
  if (!returns_string_zero && !returns_struct_zero && !returns_view_value && (returns_borrowed_string || returns_string_literal || returns_borrow_var)) status.code += `)`;
34559
35309
  status.code += `;\n`;
34560
35310
  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`;
35311
+ 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;
35312
+ 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)) {
35313
+ const overrides = node.value.node_type === "func_call" ? node.value.field_overrides : node.value.fields;
35314
+ for (const field of direct_string_fields(return_struct)) {
35315
+ if (!overrides?.some((o) => o.name === field.name)) continue;
35316
+ status.code += `free(_return_val.${field.name}.ptr);\n`;
35317
+ }
35318
+ }
35319
+ 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
35320
  if (has_field_overrides(node.value)) emit_field_overrides("_return_val", node.value, build_node$1, status, "", ";\n");
34562
35321
  const bare_local_return = node.value.node_type === "value" && node.value.value !== "0" && node.value.value !== "null" && !!returned_value_decl;
34563
35322
  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
35323
  if (returned_recorded_string_fields?.has(field.name)) continue;
34565
35324
  status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
34566
35325
  }
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);
35326
+ 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("_")) {
35327
+ const call = node.value;
35328
+ 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);
35329
+ for (const field of direct_string_fields(return_struct)) {
35330
+ if (skip.has(field.name)) continue;
35331
+ status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
35332
+ }
35333
+ }
35334
+ 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)) {
35335
+ 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
35336
  for (const field of direct_string_fields(return_struct)) {
34570
35337
  if (skip.has(field.name)) continue;
34571
35338
  status.code += `_return_val.${field.name} = nomen_str_dup(_return_val.${field.name});\n`;
@@ -34611,7 +35378,7 @@ function nullable_flag_expr_of_value(value, status) {
34611
35378
  if (!value) return void 0;
34612
35379
  const t = type_from_value_node$1(value);
34613
35380
  if (!t?.is_nullable) return void 0;
34614
- if (!(is_nullable_struct_type(t, status) || is_nullable_scalar_type(t))) return void 0;
35381
+ if (!(is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status))) return void 0;
34615
35382
  if (value.node_type === "value") return has_flag_name(value.value);
34616
35383
  if (value.node_type === "access") {
34617
35384
  const saved = begin_code_scratch(status);
@@ -34623,9 +35390,12 @@ function nullable_flag_expr_of_value(value, status) {
34623
35390
  //#region ../src/build_c/build_switch_node.ts
34624
35391
  function build_switch_node(node, status, nir) {
34625
35392
  const old_scoped_declarations = status.scoped_declarations;
35393
+ const pre_heap_string_fields = status.heap_string_fields;
35394
+ const case_field_sets = [];
34626
35395
  for (let i = 0; i < node.cases.length; i++) {
34627
35396
  const c = node.cases[i];
34628
35397
  status.scoped_declarations = enter_c_scope(status);
35398
+ status.heap_string_fields = pre_heap_string_fields;
34629
35399
  const saved_code = begin_code_scratch(status);
34630
35400
  build_node$1(c.condition, status);
34631
35401
  let cond_code = end_code_scratch(status, saved_code);
@@ -34665,17 +35435,28 @@ function build_switch_node(node, status, nir) {
34665
35435
  status.code += `if (${cond_code}) {\n`;
34666
35436
  build_block_with_cursor(c.branch, nir?.arms[i]?.branch, status);
34667
35437
  build_auto_free(status);
35438
+ case_field_sets.push(status.heap_string_fields);
34668
35439
  leave_c_scope(status);
34669
35440
  }
34670
35441
  if (node.else_branch) {
34671
35442
  status.code += "} else ";
34672
35443
  status.scoped_declarations = enter_c_scope(status);
35444
+ status.heap_string_fields = pre_heap_string_fields;
34673
35445
  status.code += `{\n`;
34674
35446
  build_block_with_cursor(node.else_branch, nir?.otherwise ?? void 0, status);
34675
35447
  build_auto_free(status);
35448
+ case_field_sets.push(status.heap_string_fields);
34676
35449
  status.code += `}\n`;
34677
35450
  leave_c_scope(status);
34678
35451
  } else if (node.cases.length > 0) status.code += "}\n";
35452
+ if (case_field_sets.length > 1) {
35453
+ const first = case_field_sets[0];
35454
+ if (!case_field_sets.every((s) => s === first)) {
35455
+ const merged = new Set(pre_heap_string_fields ?? []);
35456
+ for (const set of case_field_sets) for (const key of set ?? []) merged.add(key);
35457
+ status.heap_string_fields = merged;
35458
+ }
35459
+ }
34679
35460
  status.scoped_declarations = old_scoped_declarations;
34680
35461
  }
34681
35462
  //#endregion
@@ -35039,20 +35820,78 @@ function has_owning_fields(node, status) {
35039
35820
  }
35040
35821
  /**
35041
35822
  * The names of Buffer methods that get specialized for owning value structs.
35042
- * load_T and move_T are NOT specialized: load_T returns a shallow copy (a
35043
- * borrow — the caller's struct local is not destroyed, so no double-free);
35044
- * move_T transfers ownership (the slot is zeroed, the caller takes the
35045
- * pointers).
35823
+ * load_T IS specialized when the element has owning container fields (a
35824
+ * `List<T>` member): the returned copy must own independent containers, or
35825
+ * the caller's scope-exit destroy would free the slot's buffer (double-free
35826
+ * at container teardown). String fields stay BORROWED on load (struct-local
35827
+ * teardown only frees RECORDED heap strings, so strdup'ing them would leak);
35828
+ * container fields have no such borrow convention — a struct local's
35829
+ * container fields are ALWAYS destroyed — so they must be deep-copied.
35830
+ * move_T is NOT specialized: it transfers ownership (the slot is zeroed, the
35831
+ * caller takes the pointers).
35046
35832
  */
35047
35833
  const OWNING_BUFFER_METHODS = /* @__PURE__ */ new Set([
35048
35834
  "store",
35049
35835
  "replace",
35836
+ "load",
35050
35837
  "modify",
35051
35838
  "shift",
35052
35839
  "destroy",
35053
35840
  "#destroy"
35054
35841
  ]);
35055
35842
  /**
35843
+ * Emit the transfer half of owning store_T/replace_T: the slot bytes were
35844
+ * already memcpy'd from the source, so zeroing each owned CONTAINER field
35845
+ * of the SOURCE completes the move — the slot is the sole owner, and the
35846
+ * source's own teardown (when it still runs: an unstamped generic-body
35847
+ * local) finds zeroed fields and reclaims nothing. Nested value struct
35848
+ * fields recurse so their container leaves are zeroed at their own paths;
35849
+ * their string fields are untouched (the slot aliases them — the pre-existing
35850
+ * nested-string convention). Class/enum payload fields are left alone
35851
+ * (recorded in FOLLOWUP.md).
35852
+ */
35853
+ function emit_transfer_owning_fields(elem, src, status, prefix = "") {
35854
+ for (const field of elem.fields) {
35855
+ if (field.type.is_ref || field.type.is_view) continue;
35856
+ if (field.type.is_array || field.type.is_nullable) continue;
35857
+ const field_struct = resolve_struct_type(field.type, status);
35858
+ if (!field_struct || field_struct.is_class) continue;
35859
+ const src_field = `${prefix}${src}${arrow(src)}${field.name}`;
35860
+ if (has_destroy(field_struct)) status.code += `memset((void*)&${src_field}, 0, sizeof(${src_field}));\n`;
35861
+ else if (struct_needs_destroy(field_struct, status)) emit_transfer_owning_fields(field_struct, src_field, status, "");
35862
+ }
35863
+ }
35864
+ /**
35865
+ * Emit the deep-copy half of owning load_T: for each field whose type has a
35866
+ * monomorphized `copy` helper that yields an independent value (a `List<T>`
35867
+ * field — `<List_T>_copy` deep-copies slots per the owning store contract —
35868
+ * or an enum-with-data field whose string payloads `<E>_copy` duplicates),
35869
+ * overwrite the shallow-copied bytes with an owned copy. Fields without a
35870
+ * copyable shape (nested owning user structs, class fields) stay shallow —
35871
+ * recorded in FOLLOWUP.md ("remaining shallow container-field shapes").
35872
+ */
35873
+ function emit_load_deep_copy_fields(elem, dst, status) {
35874
+ for (const field of elem.fields) {
35875
+ if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
35876
+ if (field.type.is_nullable) continue;
35877
+ if (field.type.name === "string") continue;
35878
+ const field_struct = resolve_struct_type(field.type, status);
35879
+ if (field_struct && !field_struct.is_class && field_struct.name.startsWith("List_") && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
35880
+ status.code += `${dst}.${field.name} = ${field_struct.name}_copy(&${dst}.${field.name});\n`;
35881
+ continue;
35882
+ }
35883
+ const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
35884
+ if (field_enum && enum_copy_is_deep(field_enum, status)) status.code += `${dst}.${field.name} = ${field_enum.name}_copy(${dst}.${field.name});\n`;
35885
+ }
35886
+ }
35887
+ /** Whether `<E>_copy` yields a fully independent value: no case payload is a
35888
+ * class/trait reference (those are shared pointers the copy cannot
35889
+ * duplicate — mirroring owning_buffer_enum_element's gate). */
35890
+ function enum_copy_is_deep(elem, status) {
35891
+ for (const c of elem.cases) for (const p of c.params) if (status.structs.find((s) => s.name === p.type.name)?.is_class || status.traits.find((t) => t.name === p.type.name)) return false;
35892
+ return true;
35893
+ }
35894
+ /**
35056
35895
  * A `Buffer<string>` owns an independent heap copy of each slot (strdup on
35057
35896
  * store_T, free+strdup on replace_T, per-slot free on #destroy). The element
35058
35897
  * is a primitive, so this is a separate path from the value-struct
@@ -35215,6 +36054,13 @@ function emit_owning_buffer_body(func_name, elem, status) {
35215
36054
  status.code += `struct ${elem.name} _old = _slots[i];\n`;
35216
36055
  status.code += `_slots[i] = (*val);\n`;
35217
36056
  emit_deep_copy_fields(elem, "_slots[i]", "val", status, "_old");
36057
+ emit_transfer_owning_fields(elem, "val", status);
36058
+ return true;
36059
+ }
36060
+ if (func_name === "load") {
36061
+ status.code += `struct ${elem.name} _loaded = ((struct ${elem.name} *)(unsigned long long)self->data)[i];\n`;
36062
+ emit_load_deep_copy_fields(elem, "_loaded", status);
36063
+ status.code += `return _loaded;\n`;
35218
36064
  return true;
35219
36065
  }
35220
36066
  if (func_name === "modify") {
@@ -35229,6 +36075,7 @@ function emit_owning_buffer_body(func_name, elem, status) {
35229
36075
  status.code += `${elem.name}_destroy(&_slots[i]);\n`;
35230
36076
  status.code += `_slots[i] = (*val);\n`;
35231
36077
  emit_deep_copy_fields(elem, "_slots[i]", "val", status);
36078
+ emit_transfer_owning_fields(elem, "val", status);
35232
36079
  return true;
35233
36080
  }
35234
36081
  if (func_name === "shift") {
@@ -35255,12 +36102,13 @@ function emit_owning_buffer_body(func_name, elem, status) {
35255
36102
  return false;
35256
36103
  }
35257
36104
  /**
35258
- * For each string field on the element struct, overwrite the shallow-copied
35259
- * pointer in `dst` with a fresh strdup of `src`'s field. This breaks the
35260
- * pointer aliasing between the source and the slot so each can be destroyed
35261
- * independently. For nested owning value struct fields, recursively call the
35262
- * nested struct's destroy + deep-copy (the nested destroy frees the
35263
- * shallow-copied owned sub-fields, then the deep-copy re-strdup's them).
36105
+ * For each DIRECT string field on the element struct, overwrite the
36106
+ * shallow-copied pointer in `dst` with a fresh strdup of `src`'s field. This
36107
+ * breaks the pointer aliasing between the source and the slot so each can be
36108
+ * destroyed independently. Nested value struct fields are NOT walked: their
36109
+ * string fields stay aliased (the pre-existing convention — a spliced
36110
+ * `move` donor has no teardown, so the slot is the sole owner), and their
36111
+ * container leaves are transferred by `emit_transfer_owning_fields`.
35264
36112
  *
35265
36113
  * `old_expr` (optional) is the slot's PRE-copy value expression. When the
35266
36114
  * source field pointer equals the old slot field pointer, the slot already
@@ -35278,12 +36126,6 @@ function emit_deep_copy_fields(elem, dst, src, status, old_expr) {
35278
36126
  const src_field = `${src}${arrow(src)}${field.name}`;
35279
36127
  if (old_expr) status.code += `${dst}.${field.name} = (${src_field}.ptr && ${src_field}.ptr != ${old_expr}.${field.name}.ptr) ? nomen_str_dup(${src_field}) : ${src_field};\n`;
35280
36128
  else status.code += `${dst}.${field.name} = ${src_field}.ptr ? nomen_str_dup(${src_field}) : ${src_field};\n`;
35281
- } else if (field.type.name && !field.type.is_array) {
35282
- const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
35283
- if (field_struct && !field_struct.is_class && struct_needs_destroy(field_struct, status)) {
35284
- status.code += `${field_struct.name}_destroy(&${dst}.${field.name});\n`;
35285
- emit_deep_copy_fields(field_struct, `${dst}.${field.name}`, `${src}${arrow(src)}${field.name}`, status);
35286
- }
35287
36129
  }
35288
36130
  }
35289
36131
  }
@@ -35431,7 +36273,7 @@ function build_struct_node(node, status) {
35431
36273
  const ctor_return = is_class ? `struct ${node.name}*` : `struct ${node.name}`;
35432
36274
  const field_params = node.fields.filter((f) => f.value == null).map((f) => {
35433
36275
  let decl = c_param_decl(f.type, f.name, status);
35434
- if (is_nullable_struct_type(f.type, status) || is_nullable_scalar_type(f.type)) decl += `, unsigned char ${has_flag_name(f.name)}`;
36276
+ if (is_nullable_struct_type(f.type, status) || is_nullable_scalar_type(f.type, status)) decl += `, unsigned char ${has_flag_name(f.name)}`;
35435
36277
  return decl;
35436
36278
  }).join(", ");
35437
36279
  const ctor_sig = (init) => {
@@ -35442,7 +36284,7 @@ function build_struct_node(node, status) {
35442
36284
  is_ref: p.is_ref || p.type.is_ref,
35443
36285
  declaration: p.declaration
35444
36286
  });
35445
- if (!p.is_variadic && (is_nullable_struct_type(p.type, status) || is_nullable_scalar_type(p.type))) decl += `, unsigned char ${has_flag_name(p.name)}`;
36287
+ if (!p.is_variadic && (is_nullable_struct_type(p.type, status) || is_nullable_scalar_type(p.type, status))) decl += `, unsigned char ${has_flag_name(p.name)}`;
35446
36288
  return decl;
35447
36289
  }).join(", ");
35448
36290
  const label = is_overloaded(node, "#init") ? mangled_label(init, node.name) : `${node.name}_init`;
@@ -35458,6 +36300,10 @@ function build_struct_node(node, status) {
35458
36300
  if (node.traits.length) status.code += `self${accessor}_vt = &_${node.name}_traits;\n`;
35459
36301
  const old_init_assigned_fields = status.init_assigned_fields;
35460
36302
  status.init_assigned_fields = /* @__PURE__ */ new Set();
36303
+ const old_init_default_reclaimed = status.init_default_reclaimed_fields;
36304
+ status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
36305
+ const old_init_heap_string_fields = status.heap_string_fields;
36306
+ status.heap_string_fields = void 0;
35461
36307
  for (const field of node.fields) if (field.value) {
35462
36308
  status.init_assigned_fields?.add(`self.${field.name}`);
35463
36309
  if (field.type.is_view && !field.type.is_array && !is_nullable_struct_type(field.type, status)) {
@@ -35466,7 +36312,7 @@ function build_struct_node(node, status) {
35466
36312
  build_node$1(field.value, status);
35467
36313
  status.code += `; self${accessor}${field.name} = (nomen_view){ _p.ptr, _p.len }; }\n`;
35468
36314
  } else status.code += `self${accessor}${field.name} = (nomen_view){0};\n`;
35469
- } else if (is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type)) {
36315
+ } else if (is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) {
35470
36316
  if (field.value.node_type === "value" && field.value.value === "null") status.code += `self${accessor}${has_flag_name(field.name)} = 0;\n`;
35471
36317
  else {
35472
36318
  status.code += `self${accessor}${field.name} = `;
@@ -35525,6 +36371,8 @@ function build_struct_node(node, status) {
35525
36371
  status.current_struct = old_current_struct;
35526
36372
  status.current_function = old_current_function;
35527
36373
  status.init_assigned_fields = old_init_assigned_fields;
36374
+ status.init_default_reclaimed_fields = old_init_default_reclaimed;
36375
+ status.heap_string_fields = old_init_heap_string_fields;
35528
36376
  status.function_return_type = old_return_type;
35529
36377
  }
35530
36378
  build_struct_functions(node, status, true);
@@ -35542,7 +36390,7 @@ function build_struct_node(node, status) {
35542
36390
  status.code += `; ${object_name}${accessor}${field.name} = (nomen_view){ _p.ptr, _p.len }; }\n`;
35543
36391
  } else status.code += `${object_name}${accessor}${field.name} = (nomen_view){0};\n`;
35544
36392
  } else if (field.type.storage_kind === "stack_array" && field.type.length) status.code += `memcpy(${object_name}${accessor}${field.name}, ${field.name}, sizeof(${object_name}${accessor}${field.name}));\n`;
35545
- else if ((is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type)) && field.value) {
36393
+ else if ((is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) && field.value) {
35546
36394
  if (field.value.node_type === "value" && field.value.value === "null") status.code += `${object_name}${accessor}${has_flag_name(field.name)} = 0;\n`;
35547
36395
  else {
35548
36396
  status.code += `${object_name}${accessor}${field.name} = `;
@@ -35552,7 +36400,7 @@ function build_struct_node(node, status) {
35552
36400
  } else if (is_nullable_struct_type(field.type, status)) {
35553
36401
  status.code += `${object_name}${accessor}${field.name} = *${field.name};\n`;
35554
36402
  status.code += `${object_name}${accessor}${has_flag_name(field.name)} = ${has_flag_name(field.name)};\n`;
35555
- } else if (is_nullable_scalar_type(field.type)) {
36403
+ } else if (is_nullable_scalar_type(field.type, status)) {
35556
36404
  status.code += `${object_name}${accessor}${field.name} = ${field.name};\n`;
35557
36405
  status.code += `${object_name}${accessor}${has_flag_name(field.name)} = ${has_flag_name(field.name)};\n`;
35558
36406
  } else if (!field.type.is_ref && !field.type.is_view && status.enums.find((e) => e.name === field.type.name && e.has_associated_data)) {
@@ -35690,7 +36538,7 @@ function build_struct_functions(node, status, skip_init = false) {
35690
36538
  status.force_heap_strings = scan_force_heap_strings(func.statements ?? [], status.structs);
35691
36539
  status.function_return_type = func.return_type;
35692
36540
  const old_nullable_ret_has = status.nullable_ret_has_param;
35693
- if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type)) status.nullable_ret_has_param = "_ret_has";
36541
+ if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type, status)) status.nullable_ret_has_param = "_ret_has";
35694
36542
  else status.nullable_ret_has_param = void 0;
35695
36543
  const self_param = func.params[0]?.is_self_param ? func.params[0] : null;
35696
36544
  status.self_is_ref = !!self_param?.is_ref || self_param?.declaration === "var";
@@ -35747,9 +36595,9 @@ function build_struct_functions(node, status, skip_init = false) {
35747
36595
  for (let i = 0; i < func.params.length; i++) {
35748
36596
  if (i > 0) status.code += ", ";
35749
36597
  build_parameter_node(func.params[i], status);
35750
- if (!func.params[i].is_self_param && (is_nullable_struct_type(func.params[i].type, status) || is_nullable_scalar_type(func.params[i].type))) status.code += `, unsigned char ${has_flag_name(c_function_name(func.params[i].name))}`;
36598
+ if (!func.params[i].is_self_param && (is_nullable_struct_type(func.params[i].type, status) || is_nullable_scalar_type(func.params[i].type, status))) status.code += `, unsigned char ${has_flag_name(c_function_name(func.params[i].name))}`;
35751
36599
  }
35752
- if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type)) {
36600
+ if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type, status)) {
35753
36601
  if (func.params.length > 0) status.code += `, `;
35754
36602
  status.code += `unsigned char *_ret_has`;
35755
36603
  }
@@ -36441,6 +37289,8 @@ function build_function_node(node, status) {
36441
37289
  status.heap_strings = /* @__PURE__ */ new Set();
36442
37290
  const old_owned_string_vars = status.owned_string_vars;
36443
37291
  status.owned_string_vars = /* @__PURE__ */ new Set();
37292
+ const old_heap_string_fields = status.heap_string_fields;
37293
+ status.heap_string_fields = void 0;
36444
37294
  const old_heap_array_vars = status.heap_array_vars;
36445
37295
  status.heap_array_vars = void 0;
36446
37296
  const old_stack_array_lengths = status.stack_array_lengths;
@@ -36508,9 +37358,9 @@ function build_function_node(node, status) {
36508
37358
  first_param = false;
36509
37359
  if (node.params[i].is_variadic) status.code += `long _${node.params[i].name}_len, `;
36510
37360
  build_parameter_node(node.params[i], status);
36511
- if ((is_nullable_struct_type(node.params[i].type, status) || is_nullable_scalar_type(node.params[i].type)) && !node.params[i].is_self_param) status.code += `, unsigned char ${has_flag_name(c_function_name(node.params[i].name))}`;
37361
+ if ((is_nullable_struct_type(node.params[i].type, status) || is_nullable_scalar_type(node.params[i].type, status)) && !node.params[i].is_self_param) status.code += `, unsigned char ${has_flag_name(c_function_name(node.params[i].name))}`;
36512
37362
  }
36513
- if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type)) {
37363
+ if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type, status)) {
36514
37364
  if (!first_param) status.code += ", ";
36515
37365
  status.code += `unsigned char *_ret_has`;
36516
37366
  }
@@ -36563,7 +37413,7 @@ function build_function_node(node, status) {
36563
37413
  const old_variable_types = status.variable_types;
36564
37414
  status.variable_types = /* @__PURE__ */ new Map();
36565
37415
  const old_nullable_ret_has = status.nullable_ret_has_param;
36566
- if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type)) status.nullable_ret_has_param = "_ret_has";
37416
+ if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type, status)) status.nullable_ret_has_param = "_ret_has";
36567
37417
  else status.nullable_ret_has_param = void 0;
36568
37418
  const old_function_name = status.current_function_name;
36569
37419
  status.current_function_name = emission_label(node);
@@ -36658,6 +37508,7 @@ function build_function_node(node, status) {
36658
37508
  status.aliased_class_sources = old_aliased_class_sources;
36659
37509
  status.string_borrow_vars = old_string_borrow_vars;
36660
37510
  status.moved_string_vars = old_moved_string_vars;
37511
+ status.heap_string_fields = old_heap_string_fields;
36661
37512
  status.heap_strings = old_heap_strings;
36662
37513
  status.owned_string_vars = old_owned_string_vars;
36663
37514
  status.heap_array_vars = old_heap_array_vars;
@@ -36949,7 +37800,7 @@ function build_function_call_node(node, status) {
36949
37800
  const lambda_ret_c = lambda_arg_temps.size > 0 ? c_return_type(node.type, status) : void 0;
36950
37801
  const lambda_ret_tmp = lambda_ret_c && lambda_ret_c !== "void" ? next_lambda_ret_temp() : void 0;
36951
37802
  if (lambda_ret_tmp) status.code += `${lambda_ret_c} ${lambda_ret_tmp} = `;
36952
- const saved_call = (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type)) && !status.current_nullable_call_flag ? begin_code_scratch(status) : void 0;
37803
+ const saved_call = (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status)) && !status.current_nullable_call_flag ? begin_code_scratch(status) : void 0;
36953
37804
  let wrap_tmp;
36954
37805
  status.code += `${func_name}(${is_closure_callee ? "NULL" : ""}`;
36955
37806
  if (is_closure_callee && node.params.length > 0) status.code += ", ";
@@ -36990,7 +37841,7 @@ function build_function_call_node(node, status) {
36990
37841
  }
36991
37842
  if (node.nullable_param_indices?.includes(i) && node.params[i].node_type === "value" && node.params[i].value === "null") {
36992
37843
  const callee_null_param_type = callee_params?.[i]?.type ?? param_type;
36993
- if (is_nullable_scalar_type(callee_null_param_type)) status.code += `0, 0`;
37844
+ if (is_nullable_scalar_type(callee_null_param_type, status)) status.code += `0, 0`;
36994
37845
  else status.code += `(void *)&(struct ${mono_type_name(callee_null_param_type)}){0}, 0`;
36995
37846
  continue;
36996
37847
  }
@@ -37076,7 +37927,7 @@ function build_function_call_node(node, status) {
37076
37927
  emit_nullable_arg_flag(node.params[i], status);
37077
37928
  }
37078
37929
  }
37079
- if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type)) {
37930
+ if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status)) {
37080
37931
  const flag_name = status.current_nullable_call_flag;
37081
37932
  const arg_list_empty = node.params.length === 0 && !is_closure_callee;
37082
37933
  if (flag_name) status.code += `${arg_list_empty ? "" : ", "}&${flag_name}`;
@@ -37090,6 +37941,12 @@ function build_function_call_node(node, status) {
37090
37941
  const call_text = end_code_scratch(status, saved_call);
37091
37942
  status.code += `({ unsigned char _nsd_${wrap_tmp} = 0; ${call_text}; })`;
37092
37943
  }
37944
+ {
37945
+ const callee = node.resolved_function;
37946
+ if (callee && !callee.params?.some((p) => p.is_self_param)) transfer_ref_param_field_records(callee, node.params, status, (name) => {
37947
+ 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);
37948
+ });
37949
+ }
37093
37950
  if (node.name.startsWith("_string_interpolate_")) status.interpolate_string_counts.add(node.params.length - 1);
37094
37951
  if (node.swap_params?.size) for (const [idx, swap_expr] of node.swap_params) {
37095
37952
  const source = node.params[idx];
@@ -37179,13 +38036,13 @@ function emit_nullable_arg_flag(arg, status) {
37179
38036
  }
37180
38037
  if (arg.node_type === "value") {
37181
38038
  const vn = arg;
37182
- if (is_nullable_struct_type(vn.type, status) || is_nullable_scalar_type(vn.type)) status.code += `${has_flag_name(vn.value)}`;
38039
+ if (is_nullable_struct_type(vn.type, status) || is_nullable_scalar_type(vn.type, status)) status.code += `${has_flag_name(vn.value)}`;
37183
38040
  else status.code += `1`;
37184
38041
  return;
37185
38042
  }
37186
38043
  if (arg.node_type === "access" && arg.access.node_type === "access_field") {
37187
38044
  const t = type_from_value_node$1(arg);
37188
- if (is_nullable_struct_type(t, status) || is_nullable_scalar_type(t)) {
38045
+ if (is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status)) {
37189
38046
  const saved = begin_code_scratch(status);
37190
38047
  build_node$1(arg, status);
37191
38048
  const expr = end_code_scratch(status, saved);
@@ -37497,6 +38354,10 @@ function build_access_node$1(node, status) {
37497
38354
  return;
37498
38355
  }
37499
38356
  if (target_type.is_array && access_field.name === "length") {
38357
+ if (node.target.node_type === "value" && !!status.current_function?.params.some((p) => !p.is_self_param && p.name === node.target.value) && target_type.length) {
38358
+ build_node$1(target_type.length, status);
38359
+ return;
38360
+ }
37500
38361
  const type = c_type(target_type.name);
37501
38362
  status.code += "(sizeof(";
37502
38363
  build_node$1(node.target, status);
@@ -37865,6 +38726,10 @@ function build_access_node$1(node, status) {
37865
38726
  }
37866
38727
  }
37867
38728
  }
38729
+ {
38730
+ const ref_method = target_method ?? trait_default_func;
38731
+ 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));
38732
+ }
37868
38733
  const method_callee_params = (target_method ?? trait_default_func)?.params?.filter((p) => !p.is_self_param);
37869
38734
  const lambda_arg_temps = /* @__PURE__ */ new Map();
37870
38735
  for (let i = 0; i < access_func.params.length; i++) {
@@ -37979,13 +38844,13 @@ function build_access_node$1(node, status) {
37979
38844
  build_node$1(access_func.params[i], status);
37980
38845
  status.suppress_dereference = false;
37981
38846
  }
37982
- if (target_param && !target_param.is_self_param && (is_nullable_struct_type(target_param.type, status) || is_nullable_scalar_type(target_param.type))) {
38847
+ if (target_param && !target_param.is_self_param && (is_nullable_struct_type(target_param.type, status) || is_nullable_scalar_type(target_param.type, status))) {
37983
38848
  status.code += `, `;
37984
38849
  emit_nullable_arg_flag(access_func.params[i], status);
37985
38850
  }
37986
38851
  }
37987
38852
  const method_ret_type = (target_method ?? trait_default_func)?.return_type ?? access_func.type;
37988
- if (is_nullable_struct_type(method_ret_type, status) || is_nullable_scalar_type(method_ret_type)) {
38853
+ if (is_nullable_struct_type(method_ret_type, status) || is_nullable_scalar_type(method_ret_type, status)) {
37989
38854
  const flag_name = status.current_nullable_call_flag;
37990
38855
  status.code += `, `;
37991
38856
  status.code += flag_name ? `&${flag_name}` : `(unsigned char *)&(unsigned char){0}`;
@@ -39730,11 +40595,11 @@ function is_nullable_struct_assignment(node, status) {
39730
40595
  if (node.left_value.node_type === "value") {
39731
40596
  const name = node.left_value.value;
39732
40597
  const t = status.scoped_declarations.find((d) => d.name === name)?.type || status.variable_types?.get(name);
39733
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
40598
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
39734
40599
  }
39735
40600
  if (node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field") {
39736
40601
  const field_type = node.left_value.access.type;
39737
- return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type);
40602
+ return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type, status);
39738
40603
  }
39739
40604
  return false;
39740
40605
  }
@@ -39789,7 +40654,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
39789
40654
  const flag_name = has_flag_name(name);
39790
40655
  const decl_type = status.scoped_declarations.find((d) => d.name === name)?.type || status.variable_types?.get(name);
39791
40656
  const type_name = decl_type?.name || "";
39792
- const is_scalar = is_nullable_scalar_type(decl_type);
40657
+ const is_scalar = is_nullable_scalar_type(decl_type, status);
39793
40658
  const scalar_size = is_scalar ? aarch64_size(type_name) : 0;
39794
40659
  const reclaimable = !(node.right_value.node_type === "value" && node.right_value.value === name) && !!status.heap_string_fields?.size && Array.from(status.heap_string_fields).some((k) => k.startsWith(`${name}.`));
39795
40660
  if (rhs_is_null) {
@@ -39808,9 +40673,11 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
39808
40673
  }
39809
40674
  }
39810
40675
  };
39811
- const value_is_nullable_call = node.right_value.node_type === "func_call" && (is_nullable_struct_type(node.right_value.type, status) || is_nullable_scalar_type(node.right_value.type));
40676
+ const value_is_nullable_call = node.right_value.node_type === "func_call" && (is_nullable_struct_type(node.right_value.type, status) || is_nullable_scalar_type(node.right_value.type, status));
39812
40677
  const rhs_value_type = call_result_type(node.right_value) ?? type_from_value_node$1(node.right_value);
39813
- if (node.right_value.node_type === "access" && rhs_value_type?.is_nullable && (is_nullable_struct_type(rhs_value_type, status) || is_nullable_scalar_type(rhs_value_type))) {
40678
+ const value_is_nullable_access = node.right_value.node_type === "access" && rhs_value_type?.is_nullable && (is_nullable_struct_type(rhs_value_type, status) || is_nullable_scalar_type(rhs_value_type, status));
40679
+ const rhs_is_field_read = value_is_nullable_access && is_nullable_scalar_type(rhs_value_type, status) && node.right_value.access.node_type === "access_field";
40680
+ if (value_is_nullable_access && !rhs_is_field_read) {
39814
40681
  if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
39815
40682
  retarget_records();
39816
40683
  emit_rhs_value(node.right_value, nir_rhs, status);
@@ -39827,6 +40694,17 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
39827
40694
  emit_var_store(status, "x9", flag_name, 8);
39828
40695
  return;
39829
40696
  }
40697
+ if (rhs_is_field_read) {
40698
+ if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
40699
+ retarget_records();
40700
+ emit_rhs_value(node.right_value, nir_rhs, status);
40701
+ ensure_newline(status);
40702
+ emit_var_store(status, "x0", name, scalar_size);
40703
+ load_nullable_has(node.right_value, "x9", status);
40704
+ ensure_newline(status);
40705
+ emit_var_store(status, "x9", flag_name, 8);
40706
+ return;
40707
+ }
39830
40708
  if (value_is_nullable_call) {
39831
40709
  if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
39832
40710
  retarget_records();
@@ -39843,7 +40721,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
39843
40721
  status.call_x8_preset = old_preset;
39844
40722
  return;
39845
40723
  }
39846
- const rhs_is_nullable_value = !!rhs_value_type?.is_nullable && (is_nullable_struct_type(rhs_value_type, status) || is_nullable_scalar_type(rhs_value_type));
40724
+ const rhs_is_nullable_value = !!rhs_value_type?.is_nullable && (is_nullable_struct_type(rhs_value_type, status) || is_nullable_scalar_type(rhs_value_type, status));
39847
40725
  let rhs_flag_slot;
39848
40726
  if (rhs_is_nullable_value) {
39849
40727
  load_nullable_has(node.right_value, "x9", status);
@@ -39880,7 +40758,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
39880
40758
  const field_offset = get_field_offset(target_type.name, field_name, status);
39881
40759
  const has_offset = get_field_has_offset(target_type.name, field_name, status);
39882
40760
  const struct_size = get_struct_size(type_name, status);
39883
- const field_is_scalar = is_nullable_scalar_type(field_access.type);
40761
+ const field_is_scalar = is_nullable_scalar_type(field_access.type, status);
39884
40762
  get_source_address(access.target, status);
39885
40763
  ensure_newline(status);
39886
40764
  emit_asm(status, `str x0, [sp, #-16]!\n`);
@@ -39891,7 +40769,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
39891
40769
  return;
39892
40770
  }
39893
40771
  const rhs_t = call_result_type(node.right_value) ?? type_from_value_node$1(node.right_value);
39894
- const rhs_is_nullable_value = !!rhs_t?.is_nullable && (is_nullable_struct_type(rhs_t, status) || is_nullable_scalar_type(rhs_t));
40772
+ const rhs_is_nullable_value = !!rhs_t?.is_nullable && (is_nullable_struct_type(rhs_t, status) || is_nullable_scalar_type(rhs_t, status));
39895
40773
  let rhs_flag_slot;
39896
40774
  if (rhs_is_nullable_value) {
39897
40775
  load_nullable_has(node.right_value, "x10", status);
@@ -40283,6 +41161,11 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40283
41161
  emit_var_address(status, "x1", name);
40284
41162
  emit_struct_copy("x0", "x1", 0, struct_size, status);
40285
41163
  }
41164
+ if (node.right_value.node_type === "func_call" && !paramReg && !is_local_ref_var(name, status)) {
41165
+ const rhs_call = node.right_value;
41166
+ const rhs_ctor_struct = status.structs.find((s) => s.name === rhs_call.name && !s.is_simple_type && !s.is_class);
41167
+ 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) ?? []));
41168
+ }
40286
41169
  if (has_field_overrides(node.right_value)) emit_field_overrides(name, node.right_value, build_node, status);
40287
41170
  build_swap(node, status, nir_swap);
40288
41171
  return;
@@ -40503,12 +41386,13 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40503
41386
  const move_source = rhs_is_string_var && node.last_use_move && move_on_last_use_enabled() ? rhs_value.value : void 0;
40504
41387
  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
41388
  const borrow_needs_dup = !node.swap && size === 16 && is_string_borrow(node.right_value) && !!status.force_heap_strings?.has(name);
41389
+ const move_source_owns_heap = move_source !== void 0 && !!status.heap_strings?.has(move_source);
40506
41390
  if (rhs_is_view_into_owned) {
40507
41391
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
40508
41392
  status.heap_strings.add(name);
40509
- } else if (rhs_is_string_var && move_source === void 0 || borrow_needs_dup) emit_strdup_string(status);
41393
+ } else if (rhs_is_string_var && (move_source === void 0 || !move_source_owns_heap) || borrow_needs_dup) emit_strdup_string(status);
40510
41394
  if (rhs_is_string_var || borrow_needs_dup) {
40511
- if (move_source !== void 0) status.heap_strings?.delete(move_source);
41395
+ if (move_source !== void 0 && move_source_owns_heap) status.heap_strings?.delete(move_source);
40512
41396
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
40513
41397
  status.heap_strings.add(name);
40514
41398
  } else if (status.last_result_is_heap) {
@@ -40586,7 +41470,7 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40586
41470
  emit_asm(status, `str xzr, [x0, #${offset + 8}]\n`);
40587
41471
  }
40588
41472
  }
40589
- } else if (field_is_struct_string(target_type, field_type, status) && !node.operator && (() => {
41473
+ } else if (field_is_struct_string(target_type, field_type, status) && !node.operator && !node.raw_field_store && (() => {
40590
41474
  const st = field_is_struct_string(target_type, field_type, status);
40591
41475
  const tv = access.target.node_type === "value" ? access.target.value : "";
40592
41476
  return st.target_is_class || tv !== "" && tv !== "self";
@@ -40614,12 +41498,16 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40614
41498
  ensure_newline(status);
40615
41499
  }
40616
41500
  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);
41501
+ if (rhs_is_null_literal) {} else if (!rhs_is_heap) {
41502
+ const rhs_type_nullable = !!node.right_value.type?.is_nullable;
41503
+ if (field_type?.is_nullable || rhs_type_nullable) {
41504
+ const skip_dup = `.Lskip_field_dup_${status.label_counter = (status.label_counter ?? 0) + 1}`;
41505
+ emit_asm(status, `cbz x0, ${skip_dup}\n`);
41506
+ emit_strdup(status);
41507
+ emit_asm(status, `${skip_dup}:\n`);
41508
+ } else emit_strdup(status);
40620
41509
  rhs_is_heap = true;
40621
41510
  }
40622
- if (rhs_is_null_literal) {} else if (is_class_target && !rhs_is_heap) emit_strdup(status);
40623
41511
  mark_moved_if_struct(node.right_value, status);
40624
41512
  if (field_type?.name === "string") {
40625
41513
  emit_asm(status, `stp x0, x1, [sp, #-16]!\n`);
@@ -40805,6 +41693,14 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
40805
41693
  get_base_address(access, status, "x0");
40806
41694
  emit_asm(status, `str x0, [sp, #-16]!\n`);
40807
41695
  }
41696
+ 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))) {
41697
+ if (base_reg !== void 0) emit_asm(status, `ldr x0, [${base_reg}, #${offset}]\n`);
41698
+ else {
41699
+ emit_asm(status, `ldr x0, [sp]\n`);
41700
+ emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
41701
+ }
41702
+ emit_free(status);
41703
+ }
40808
41704
  emit_rhs_value(node.right_value, nir_rhs, status);
40809
41705
  ensure_newline(status);
40810
41706
  mark_moved_if_struct(node.right_value, status);
@@ -41698,7 +42594,7 @@ function build_operation_node(node, status) {
41698
42594
  if (node.op === "??") {
41699
42595
  const left_t = type_from_value_node$1(node.left_value);
41700
42596
  const left_is_nullable_struct = is_nullable_struct_type(left_t, status);
41701
- const left_is_nullable_scalar = is_nullable_scalar_type(left_t);
42597
+ const left_is_nullable_scalar = is_nullable_scalar_type(left_t, status);
41702
42598
  if (left_is_nullable_scalar && node.left_value.node_type === "func_call") {
41703
42599
  build_operand(node.left_value, "x0", status);
41704
42600
  ensure_newline(status);
@@ -41750,11 +42646,11 @@ function build_operation_node(node, status) {
41750
42646
  if ((node.op === "==" || node.op === "!=") && is_null_literal$1(node.left_value) !== is_null_literal$1(node.right_value)) {
41751
42647
  const nullable_flagged_side = (n) => {
41752
42648
  const t = type_from_value_node$1(n);
41753
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
42649
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
41754
42650
  };
41755
42651
  const nullable_side = nullable_flagged_side(node.left_value) ? node.left_value : node.right_value;
41756
42652
  const side_type = type_from_value_node$1(nullable_side);
41757
- if (is_nullable_struct_type(side_type, status) || is_nullable_scalar_type(side_type)) {
42653
+ if (is_nullable_struct_type(side_type, status) || is_nullable_scalar_type(side_type, status)) {
41758
42654
  load_nullable_has(nullable_side, "x1", status);
41759
42655
  ensure_newline(status);
41760
42656
  emit_asm(status, `cmp x1, #0\n`);
@@ -42272,7 +43168,7 @@ function load_nullable_has(node, target_reg, status) {
42272
43168
  get_source_address(access.target, status);
42273
43169
  ensure_newline(status);
42274
43170
  if (target_reg !== "x0") emit_asm(status, `mov ${target_reg}, x0\n`);
42275
- const scalar_field = is_nullable_scalar_type(access.access.type);
43171
+ const scalar_field = is_nullable_scalar_type(access.access.type, status);
42276
43172
  const wreg = target_reg.replace("x", "w");
42277
43173
  const load_op = scalar_field ? `ldrb ${wreg}` : `ldr ${target_reg}`;
42278
43174
  if (has_off === 0) emit_asm(status, `${load_op}, [${target_reg}]\n`);
@@ -44474,7 +45370,7 @@ function build_access_method(node, access_func, status) {
44474
45370
  const access_callee_params = access_callee_method?.params?.filter((p) => !p.is_self_param);
44475
45371
  const return_struct = !access_func.type.is_view && !access_func.type.is_array && (!!status.structs.find((s) => s.name === access_func.type.name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(access_func.type.name, status) !== void 0);
44476
45372
  const method_ret_type = access_callee_method?.return_type ?? access_func.type;
44477
- const nullable_scalar_ret = is_nullable_scalar_type(method_ret_type) && !method_ret_type.is_ref;
45373
+ const nullable_scalar_ret = is_nullable_scalar_type(method_ret_type, status) && !method_ret_type.is_ref;
44478
45374
  let temp_addr = "";
44479
45375
  let temp_offset = 0;
44480
45376
  if (return_struct || nullable_scalar_ret) {
@@ -44594,7 +45490,7 @@ function build_access_method(node, access_func, status) {
44594
45490
  for (let i = 0; i < access_func.params.length; i++) {
44595
45491
  if ((access_func.ref_param_indices ?? []).includes(i)) continue;
44596
45492
  const cp = access_callee_params?.[i];
44597
- if (cp && is_nullable_scalar_type(cp.type) || is_nullable_scalar_type(access_func.params[i].type)) nullable_scalar_arg_set.add(i);
45493
+ if (cp && is_nullable_scalar_type(cp.type, status) || is_nullable_scalar_type(access_func.params[i].type, status)) nullable_scalar_arg_set.add(i);
44598
45494
  }
44599
45495
  const arg_slot = [];
44600
45496
  let total_arg_slots = 0;
@@ -44649,7 +45545,7 @@ function build_access_method(node, access_func, status) {
44649
45545
  if (arg.node_type === "value" && arg.value === "null") {
44650
45546
  emit_asm(status, `mov x0, #0\n`);
44651
45547
  emit_asm(status, `mov x1, #0\n`);
44652
- } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type)) {
45548
+ } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type, status)) {
44653
45549
  const vname = arg.value;
44654
45550
  emit_var_load(status, "x0", vname, aarch64_size(arg.type.name));
44655
45551
  emit_var_load(status, "x1", has_flag_name(vname), 8);
@@ -44856,7 +45752,10 @@ function build_access_method(node, access_func, status) {
44856
45752
  }
44857
45753
  if (node.target.node_type === "value" && target_struct && !target_struct.is_class && !trait_target) {
44858
45754
  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));
45755
+ if (target_method) {
45756
+ drop_self_written_string_field_records(status, node.target.value, scan_self_string_field_writes(target_struct, target_method));
45757
+ 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));
45758
+ }
44860
45759
  }
44861
45760
  if (method_name.endsWith("_to_string") && method_name !== "string_to_string") status.last_result_is_heap = true;
44862
45761
  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 +46794,14 @@ function gather_normalized_struct_returners(root, status) {
45895
46794
  if (!return_normalizing.has(fn.name)) continue;
45896
46795
  const ret_struct = structs.find((st) => st.name === fn.return_type?.name && !st.is_simple_type && !st.is_class);
45897
46796
  for (const ret of direct_returns(fn)) {
45898
- const value = ret.value ?? void 0;
46797
+ let value = ret.value ?? void 0;
46798
+ if (value?.node_type === "anon_struct") {
46799
+ const base = value.base;
46800
+ if (base?.node_type === "func_call") value = base;
46801
+ }
45899
46802
  if (value && value.node_type === "value") continue;
45900
46803
  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;
46804
+ 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
46805
  return_normalizing.delete(fn.name);
45903
46806
  changed = true;
45904
46807
  break;
@@ -46130,7 +47033,7 @@ function build(root, options = {}) {
46130
47033
  status.code = status.code + "\n" + status.c_runtime_defs;
46131
47034
  status.c_runtime_defs = void 0;
46132
47035
  }
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;
47036
+ 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
47037
  if (options.audit) {
46135
47038
  status.code = wrap_c_allocators(status.code);
46136
47039
  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);
@@ -48906,13 +49809,33 @@ function can_class_int_coerce(target, value, status) {
48906
49809
  }
48907
49810
  //#endregion
48908
49811
  //#region ../src/check/utils/check_type_exists.ts
49812
+ /**
49813
+ * Containers whose elements live in raw T-sized slots (the `#arch` generic
49814
+ * bodies size each slot from the type name alone). A flagged-nullable
49815
+ * element type (`List<int?>`) has no per-slot null flag there, so `0`/`false`
49816
+ * would read back as null — rejected instead of silently misbehaving.
49817
+ * Element types with an in-band null (`string?`, class/trait `?`) stay legal.
49818
+ */
49819
+ const RAW_ELEMENT_CONTAINERS = /* @__PURE__ */ new Set([
49820
+ "Array",
49821
+ "Buffer",
49822
+ "List",
49823
+ "Set",
49824
+ "Map",
49825
+ "Graph",
49826
+ "LinkedList",
49827
+ "Tree",
49828
+ "Arena"
49829
+ ]);
48909
49830
  function check_type_exists(type, status, start) {
49831
+ if (type.is_nullable && type.is_array && !type.is_ref && !type.is_pointer && is_nullable_scalar_name(type.name, status)) add_error(status, `Nullable element type '${type.name}?' is not supported in an array: element storage has no per-element null flag`, start);
48910
49832
  if (type.name === "tuple" && type.tuple_types?.length) {
48911
49833
  let ok = true;
48912
49834
  for (const elem of type.tuple_types) if (!check_type_exists(elem, status, start)) ok = false;
48913
49835
  return ok;
48914
49836
  }
48915
49837
  if (type.name === "anon_enum" && type.enum_cases?.length) {
49838
+ if (type.is_nullable && type.enum_cases.some((c) => c.types.length > 0)) add_error(status, `Nullable type '${type_name(type)}' is not supported: enums with associated data have no separate null flag`, start);
48916
49839
  let ok = true;
48917
49840
  for (const c of type.enum_cases) for (const t of c.types) if (!check_type_exists(t, status, start)) ok = false;
48918
49841
  return ok;
@@ -48929,19 +49852,26 @@ function check_type_exists(type, status, start) {
48929
49852
  }
48930
49853
  }
48931
49854
  type.name = declared.name;
49855
+ if (type.is_nullable) {
49856
+ if (status.enums.findLast((e) => (e.name === declared.name || e.source_name === declared.name) && !e.is_generic)?.has_associated_data) add_error(status, `Nullable type '${type_name(type)}' is not supported: enums with associated data have no separate null flag`, start);
49857
+ }
48932
49858
  } else if (!status.types.includes(type.name)) {
48933
49859
  add_error(status, `Unknown type: ${type_name(type)}`, start);
48934
49860
  return false;
48935
49861
  }
48936
49862
  if (type.tuple_types) for (const elem of type.tuple_types) check_type_exists(elem, status, start);
48937
- if (type.type_args) for (const arg of type.type_args) {
48938
- if (arg.name === "func") {
48939
- add_error(status, `func types cannot be used as type arguments ('${type.name}<...>')`, start);
48940
- continue;
49863
+ if (type.type_args) {
49864
+ if (RAW_ELEMENT_CONTAINERS.has(type.name)) {
49865
+ for (const arg of type.type_args) if (is_nullable_flagged_type(arg, status)) add_error(status, `Nullable element type '${type_name(arg)}' is not supported in '${type.name}<...>': container element storage has no per-element null flag`, start);
48941
49866
  }
48942
- check_type_exists(arg, status, start);
48943
- }
48944
- else {
49867
+ for (const arg of type.type_args) {
49868
+ if (arg.name === "func") {
49869
+ add_error(status, `func types cannot be used as type arguments ('${type.name}<...>')`, start);
49870
+ continue;
49871
+ }
49872
+ check_type_exists(arg, status, start);
49873
+ }
49874
+ } else {
48945
49875
  const struct = status.structs.findLast((s) => s.name === type.name);
48946
49876
  if (struct?.is_generic) {
48947
49877
  if (!struct.type_params.every((tp) => status.type_params.includes(tp))) add_error(status, `Generic type '${type.name}' requires type arguments (expected <${struct.type_params.join(", ")}>)`, start);
@@ -51469,6 +52399,21 @@ function check_struct_node(struct, status) {
51469
52399
  status.values.length = values_length_before_fields;
51470
52400
  rewrite_generic_buffer_fields(struct, status);
51471
52401
  mark_owning_auto_init_params(struct, status);
52402
+ if (!struct.is_library) for (const field of struct.fields) {
52403
+ if (field.declaration !== "move") continue;
52404
+ if (!field.type.name || field.type.is_ref || field.type.is_view) continue;
52405
+ if (field.type.is_nullable) continue;
52406
+ if (is_class_type$1(field.type.name, status)) continue;
52407
+ if (status.traits.find((t) => t.name === field.type.name)) continue;
52408
+ if (!is_owning_struct_type_requiring_move(new Type(mono_type_name(field.type)), status)) continue;
52409
+ if (!status.warnings) status.warnings = [];
52410
+ status.warnings.push({
52411
+ 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`,
52412
+ start: field.start,
52413
+ line: 0,
52414
+ column: 0
52415
+ });
52416
+ }
51472
52417
  status.types.push(struct.name);
51473
52418
  status.structs.push(struct);
51474
52419
  for (let func of struct.functions) {
@@ -51649,6 +52594,11 @@ function signature_matches(struct_func, trait_func, substitution) {
51649
52594
  function types_match(a, b) {
51650
52595
  return a.name === b.name && !!a.is_ref === !!b.is_ref && !!a.is_view === !!b.is_view && !!a.is_array === !!b.is_array;
51651
52596
  }
52597
+ /** The user-visible spelling of a type (`List<int>`, `Map<string, int>`). */
52598
+ function type_display(t) {
52599
+ if (!t.type_args?.length) return t.name ?? "?";
52600
+ return `${t.name}<${t.type_args.map(type_display).join(", ")}>`;
52601
+ }
51652
52602
  //#endregion
51653
52603
  //#region ../src/check/utils/auto_derive.ts
51654
52604
  function struct_has_function(struct, name) {
@@ -51800,20 +52750,57 @@ function build_hash(struct, fields) {
51800
52750
  }
51801
52751
  /**
51802
52752
  * Whether `struct` is eligible for a synthesized `copy` method: a value
51803
- * struct whose only ownership is its DIRECT string fields — the same set
51804
- * the pass-by-value argument convention covers. A struct that additionally
51805
- * owns non-string heap (a resource-releasing `#destroy`, a class field, a
51806
- * nested requiring-move struct) cannot be deep-copied by "byte copy +
51807
- * strdup the strings" — its copy stays unwritten (move/swap remains the
51808
- * escape hatch) — and a struct with no string fields is already soundly
51809
- * byte-copyable, so it needs no method.
52753
+ * struct whose every field is independently deep-copyable — direct string
52754
+ * fields (strdup'd), scalars/arrays (byte copies), `List<T>` fields whose
52755
+ * element is itself deep-copyable (the library `List.copy` deep-copies slots
52756
+ * via the owning store contract), and nested copyable structs (recursing
52757
+ * through their own `copy`). Class fields, enum-with-data fields (their
52758
+ * stores transfer payload ownership), class/trait-carrying `List` elements,
52759
+ * and resource-releasing `#destroy`s cannot be deep-copied by synthesis —
52760
+ * the copy stays unwritten (move/swap remains the escape hatch) — and a
52761
+ * struct with nothing to deep-copy (no string, `List`, or nested-copyable
52762
+ * field) is already soundly byte-copyable, so it needs no method.
51810
52763
  */
51811
52764
  function struct_is_copyable(struct, status) {
51812
52765
  if (struct.is_class || struct.is_generic || struct.is_simple_type) return false;
51813
- if (direct_string_fields(struct).length === 0) return false;
51814
- if (struct_owns_non_string_heap(struct, status, /* @__PURE__ */ new Set())) return false;
52766
+ if (struct_owns_resource(struct, status, /* @__PURE__ */ new Set())) return false;
52767
+ if (!struct_needs_copy(struct, status)) return false;
51815
52768
  return struct_copy_ctor_args(struct) !== null;
51816
52769
  }
52770
+ /** Whether any field of `struct` actually needs the synthesized deep copy:
52771
+ * a direct string field, a deep-copyable `List<T>` field, or a nested
52772
+ * copyable struct field. A struct of flat values byte-copies soundly
52773
+ * without a method. */
52774
+ function struct_needs_copy(struct, status) {
52775
+ if (direct_string_fields(struct).length > 0) return true;
52776
+ for (const field of struct.fields) {
52777
+ if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
52778
+ if (field_copy_call(field.type, status)) return true;
52779
+ }
52780
+ return false;
52781
+ }
52782
+ /**
52783
+ * Whether `struct` owns a resource a synthesized copy CANNOT duplicate: a
52784
+ * `#destroy` that releases something (a raw block / extern free), or a field
52785
+ * whose type has no deep-copy story (a class instance, a `List` of
52786
+ * class/trait elements, a nested struct with such a field). Fields that DO
52787
+ * have a deep-copy story (strings, nested copyables, copyable `List`s,
52788
+ * payload-copyable enums) don't block synthesis. Mirrors the old
52789
+ * `struct_owns_non_string_heap` gate but understands container deep copies.
52790
+ */
52791
+ function struct_owns_resource(struct, status, visited) {
52792
+ if (visited.has(struct.name)) return false;
52793
+ visited.add(struct.name);
52794
+ const destroy = struct.functions.find((f) => f.name === "#destroy");
52795
+ if (destroy && destroy_releases(destroy)) return true;
52796
+ for (const field of struct.fields) {
52797
+ if (field.type.is_ref || field.type.is_view) continue;
52798
+ if (field.type.name === "string" && !field.type.is_array) continue;
52799
+ if (field.type.is_nullable) return true;
52800
+ if (!field_copyable(field.type, status)) return true;
52801
+ }
52802
+ return false;
52803
+ }
51817
52804
  /**
51818
52805
  * How a synthesized `copy` constructs the fresh value: the zero-argument
51819
52806
  * `#init` overload when one exists (auto or user), else the bodyless AUTO
@@ -51829,14 +52816,49 @@ function struct_copy_ctor_args(struct) {
51829
52816
  if (!auto) return null;
51830
52817
  return auto.params.filter((p) => !p.is_self_param).map((p) => p.name);
51831
52818
  }
51832
- /** Whether `type` names a struct that has (or will get) a `copy` method.
51833
- * Order-independent: it re-evaluates the eligibility predicate instead of
51834
- * looking for an already-synthesized method, so an outer struct nested in
51835
- * the same pre-pass resolves a later-declared field struct. */
51836
- function field_type_is_copyable(type, status) {
52819
+ /** Whether `type` has (or will get) a deep-copy story: a struct with a
52820
+ * synthesized `copy`, a `List<T>` whose element is deep-copyable (the
52821
+ * library `List.copy` deep-copies slots via the owning store contract), or
52822
+ * a primitive / plain (payload-free) enum. Order-independent: it
52823
+ * re-evaluates the eligibility predicate instead of looking for an
52824
+ * already-synthesized method, so an outer struct nested in the same
52825
+ * pre-pass resolves a later-declared field struct. */
52826
+ function field_copyable(type, status) {
52827
+ if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
52828
+ if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
52829
+ const nested_struct = status.structs.find((s) => s.name === type.name);
52830
+ if (nested_struct) {
52831
+ if (nested_struct.is_simple_type) return true;
52832
+ return struct_is_copyable(nested_struct, status);
52833
+ }
52834
+ const en = status.enums.find((e) => e.name === type.name);
52835
+ if (en) return !en.has_associated_data;
52836
+ return !status.traits.some((t) => t.name === type.name);
52837
+ }
52838
+ /** Whether a `List<T>` element type is deep-copyable (see
52839
+ * `field_copyable`). */
52840
+ function element_copyable(type, status) {
51837
52841
  if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
52842
+ if (type.name === "string") return true;
52843
+ if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
51838
52844
  const nested = status.structs.find((s) => s.name === type.name);
51839
- return !!nested && struct_is_copyable(nested, status);
52845
+ if (nested) {
52846
+ if (nested.is_simple_type) return true;
52847
+ return !nested.is_class && struct_is_copyable(nested, status);
52848
+ }
52849
+ const en = status.enums.find((e) => e.name === type.name);
52850
+ if (en) return !en.has_associated_data;
52851
+ return !status.traits.some((t) => t.name === type.name);
52852
+ }
52853
+ /** Whether `build_copy` must route the field through a `.copy()` call (a
52854
+ * nested copyable struct, or a `List<T>` field): everything else byte
52855
+ * copies through plain field assignment (strings strdup via the field-write
52856
+ * path, primitives/plain enums are flat values). */
52857
+ function field_copy_call(type, status) {
52858
+ if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
52859
+ if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
52860
+ const nested_struct = status.structs.find((s) => s.name === type.name);
52861
+ return !!nested_struct && !nested_struct.is_simple_type && struct_is_copyable(nested_struct, status);
51840
52862
  }
51841
52863
  /**
51842
52864
  * Synthesize `pub func copy(self) -> Struct` for owning value structs: a
@@ -51852,8 +52874,8 @@ function build_copy(struct, status) {
51852
52874
  const ctor = new FunctionCallNode(-1, struct.name, new Type(struct.name), ctor_args.map((name) => field_access("self", name)));
51853
52875
  const statements = [new DeclarationNode(-1, "private", "var", "c", new Type(struct.name), ctor)];
51854
52876
  for (const field of struct.fields) {
51855
- const right = field_type_is_copyable(field.type, status) ? method_call(field_access("self", field.name), "copy") : field_access("self", field.name);
51856
- statements.push(new AssignmentNode(-1, field_access("c", field.name), right, "="));
52877
+ const right = field_copy_call(field.type, status) ? method_call(field_access("self", field.name), "copy") : field_access("self", field.name);
52878
+ statements.push(new AssignmentNode(-1, field_access("c", field.name), right));
51857
52879
  }
51858
52880
  statements.push(new ReturnNode(-1, new ValueNode(-1, "c")));
51859
52881
  return new FunctionNode(-1, "pub", "copy", new Type(struct.name), [self_param(struct)], statements);
@@ -52932,7 +53954,10 @@ function monomorphize(generic_struct, type_args, status) {
52932
53954
  mono_field.type = constructed;
52933
53955
  }
52934
53956
  }
52935
- substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, status.enums);
53957
+ substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, {
53958
+ enums: status.enums,
53959
+ bitsets: status.bitsets
53960
+ });
52936
53961
  }
52937
53962
  mono_field.is_library = field.is_library;
52938
53963
  mono_field.is_readonly = field.is_readonly;
@@ -52967,7 +53992,10 @@ function monomorphize(generic_struct, type_args, status) {
52967
53992
  for (const func of generic_struct.functions) {
52968
53993
  if (func.name === "#init") continue;
52969
53994
  const cloned = clone_node(func);
52970
- substitute_raw_types(cloned, substitution, status.structs, status.traits, status.enums);
53995
+ substitute_raw_types(cloned, substitution, status.structs, status.traits, {
53996
+ enums: status.enums,
53997
+ bitsets: status.bitsets
53998
+ });
52971
53999
  fold_substituted_constant_ifs(cloned.statements);
52972
54000
  rename_local_labels(cloned, mono_name);
52973
54001
  const retype_roots = [...cloned.statements, ...cloned.allocations ?? []];
@@ -52980,7 +54008,10 @@ function monomorphize(generic_struct, type_args, status) {
52980
54008
  param.type = substitute_type(param.type, substitution);
52981
54009
  substitute_param_signature(param, substitution);
52982
54010
  if (param.type.type_args?.length) param.type = materialize_generic_enum_type(param.type, status);
52983
- if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, status.enums);
54011
+ if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, {
54012
+ enums: status.enums,
54013
+ bitsets: status.bitsets
54014
+ });
52984
54015
  }
52985
54016
  retype_param_references(retype_roots, cloned.params);
52986
54017
  retype_local_references(retype_roots);
@@ -53008,7 +54039,10 @@ function monomorphize(generic_struct, type_args, status) {
53008
54039
  if (variadic_init_unsupported(generic_init)) continue;
53009
54040
  if (!generic_init.statements.some((s) => s.node_type !== "raw")) continue;
53010
54041
  const cloned = clone_node(generic_init);
53011
- substitute_raw_types(cloned, substitution, status.structs, status.traits, status.enums);
54042
+ substitute_raw_types(cloned, substitution, status.structs, status.traits, {
54043
+ enums: status.enums,
54044
+ bitsets: status.bitsets
54045
+ });
53012
54046
  fold_substituted_constant_ifs(cloned.statements);
53013
54047
  rename_local_labels(cloned, mono_name);
53014
54048
  cloned.return_type = new Type(mono_name);
@@ -53016,7 +54050,10 @@ function monomorphize(generic_struct, type_args, status) {
53016
54050
  for (const param of cloned.params) {
53017
54051
  param.type = substitute_type(param.type, substitution);
53018
54052
  substitute_param_signature(param, substitution);
53019
- if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, status.enums);
54053
+ if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, {
54054
+ enums: status.enums,
54055
+ bitsets: status.bitsets
54056
+ });
53020
54057
  }
53021
54058
  const cloned_self = cloned.params.find((p) => p.is_self_param);
53022
54059
  if (cloned_self) cloned_self.type = new Type(mono_name);
@@ -53521,7 +54558,7 @@ function derive_annotations_for_access_func(access_node, fc, status) {
53521
54558
  const p = func.params[i];
53522
54559
  if (p.is_self_param) continue;
53523
54560
  if (!p.type?.is_nullable) continue;
53524
- if (status.structs.find((s) => s.name === p.type.name && !s.is_class) || is_nullable_scalar_type(p.type)) nullable_indices.push(i - (self_offset >= 0 ? self_offset + 1 : 0));
54561
+ if (status.structs.find((s) => s.name === p.type.name && !s.is_class) || is_nullable_scalar_type(p.type, status)) nullable_indices.push(i - (self_offset >= 0 ? self_offset + 1 : 0));
53525
54562
  }
53526
54563
  if (nullable_indices.length > 0) fc.nullable_param_indices = nullable_indices;
53527
54564
  if (func.return_constraint && !fc.return_bounds) {
@@ -53900,7 +54937,7 @@ function fold_substituted_constant_ifs(statements) {
53900
54937
  statements.splice(i, 1, ...kept);
53901
54938
  }
53902
54939
  }
53903
- function substitute_raw_types(func, substitution, structs, traits, enums) {
54940
+ function substitute_raw_types(func, substitution, structs, traits, tables) {
53904
54941
  const deref_params = /* @__PURE__ */ new Set();
53905
54942
  for (const param of func.params) {
53906
54943
  const resolved = substitution.get(param.type.name);
@@ -53909,7 +54946,7 @@ function substitute_raw_types(func, substitution, structs, traits, enums) {
53909
54946
  const s = structs.find((x) => x.name === resolved);
53910
54947
  if (s && !s.is_simple_type && !s.is_class) deref_params.add(param.name);
53911
54948
  }
53912
- for (const stmt of func.statements) substitute_raw_in_node(stmt, substitution, structs, deref_params, traits, enums);
54949
+ for (const stmt of func.statements) substitute_raw_in_node(stmt, substitution, structs, deref_params, traits, tables);
53913
54950
  }
53914
54951
  function rename_local_labels(node, prefix) {
53915
54952
  if (node.node_type === "raw") {
@@ -53942,7 +54979,7 @@ function raw_type_alignment(name) {
53942
54979
  function raw_align_to(offset, alignment) {
53943
54980
  return Math.ceil(offset / alignment) * alignment;
53944
54981
  }
53945
- function raw_type_size(name, structs, enums) {
54982
+ function raw_type_size(name, structs, tables) {
53946
54983
  switch (name) {
53947
54984
  case "bool":
53948
54985
  case "int8":
@@ -53953,13 +54990,13 @@ function raw_type_size(name, structs, enums) {
53953
54990
  case "int32":
53954
54991
  case "uint32": return 4;
53955
54992
  }
53956
- const enum_node = enums?.find((e) => e.name === name && !e.is_generic);
54993
+ const enum_node = tables.enums.find((e) => e.name === name && !e.is_generic);
53957
54994
  if (enum_node) {
53958
54995
  if (!enum_node.has_associated_data) return 8;
53959
54996
  let max_payload = 0;
53960
54997
  for (const c of enum_node.cases) {
53961
54998
  let case_size = 0;
53962
- for (const p of c.params) case_size += raw_type_size(p.type.name, structs, enums);
54999
+ for (const p of c.params) case_size += raw_type_size(p.type.name, structs, tables);
53963
55000
  max_payload = Math.max(max_payload, case_size);
53964
55001
  }
53965
55002
  return 8 + Math.ceil(max_payload / 8) * 8;
@@ -53974,9 +55011,9 @@ function raw_type_size(name, structs, enums) {
53974
55011
  size += 16;
53975
55012
  continue;
53976
55013
  }
53977
- size += raw_type_size(field.type.name, structs, enums);
55014
+ size += raw_type_size(field.type.name, structs, tables);
53978
55015
  if (field.type.is_nullable && structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class)) size += 8;
53979
- else if (is_nullable_scalar_type(field.type)) size += 1;
55016
+ else if (is_nullable_scalar_type(field.type, tables)) size += 1;
53980
55017
  }
53981
55018
  return raw_align_to(size, 8);
53982
55019
  }
@@ -54021,12 +55058,15 @@ function raw_block_is_pure_asm(value) {
54021
55058
  function is_reference_type_arg(t, structs, traits) {
54022
55059
  return !!traits.some((tr) => tr.name === t.name) || !!structs.find((s) => s.name === t.name && s.is_class);
54023
55060
  }
54024
- function substitute_raw_in_node(node, substitution, structs, deref_params = /* @__PURE__ */ new Set(), traits = [], enums = []) {
55061
+ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @__PURE__ */ new Set(), traits = [], tables = {
55062
+ enums: [],
55063
+ bitsets: []
55064
+ }) {
54025
55065
  if (node.node_type === "value") {
54026
55066
  const vn = node;
54027
55067
  for (const [param, type] of substitution) {
54028
55068
  if (vn.value === `${param}_SIZE`) {
54029
- vn.value = String(raw_type_size(type, structs, enums));
55069
+ vn.value = String(raw_type_size(type, structs, tables));
54030
55070
  vn.type = new Type("int", true);
54031
55071
  return;
54032
55072
  }
@@ -54051,7 +55091,7 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
54051
55091
  else if (struct_node) c_type_name = `struct ${type}`;
54052
55092
  else c_type_name = raw_c_type_name(type);
54053
55093
  value = value.replace(new RegExp(`\\b${param}\\b`, "g"), c_type_name);
54054
- const size = raw_type_size(type, structs, enums);
55094
+ const size = raw_type_size(type, structs, tables);
54055
55095
  const size_expr = raw_block_is_pure_asm(value) ? String(size) : `sizeof(${c_type_name})`;
54056
55096
  value = value.replace(new RegExp(`\\b${param}_SIZE\\b`, "g"), size_expr);
54057
55097
  value = value.replace(new RegExp(`\\b${param}_destroy\\b`, "g"), `${type}_destroy`);
@@ -54087,22 +55127,22 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
54087
55127
  if (any_node.func_return_type?.name) any_node.func_return_type = substitute_type(any_node.func_return_type, substitution);
54088
55128
  }
54089
55129
  if (any_node.allocations && Array.isArray(any_node.allocations)) {
54090
- for (const child of any_node.allocations) if (child && typeof child === "object" && "node_type" in child) substitute_raw_in_node(child, substitution, structs, deref_params, traits, enums);
55130
+ for (const child of any_node.allocations) if (child && typeof child === "object" && "node_type" in child) substitute_raw_in_node(child, substitution, structs, deref_params, traits, tables);
54091
55131
  }
54092
55132
  if (any_node.statements && Array.isArray(any_node.statements)) {
54093
- for (const child of any_node.statements) if (child && typeof child === "object" && "node_type" in child) substitute_raw_in_node(child, substitution, structs, deref_params, traits, enums);
55133
+ for (const child of any_node.statements) if (child && typeof child === "object" && "node_type" in child) substitute_raw_in_node(child, substitution, structs, deref_params, traits, tables);
54094
55134
  }
54095
55135
  if (any_node.params && Array.isArray(any_node.params)) {
54096
- for (const child of any_node.params) if (child && typeof child === "object" && "node_type" in child) substitute_raw_in_node(child, substitution, structs, deref_params, traits, enums);
55136
+ for (const child of any_node.params) if (child && typeof child === "object" && "node_type" in child) substitute_raw_in_node(child, substitution, structs, deref_params, traits, tables);
54097
55137
  }
54098
- if (any_node.value && any_node.value.node_type) substitute_raw_in_node(any_node.value, substitution, structs, deref_params, traits, enums);
54099
- if (any_node.left_value?.node_type) substitute_raw_in_node(any_node.left_value, substitution, structs, deref_params, traits, enums);
54100
- if (any_node.right_value?.node_type) substitute_raw_in_node(any_node.right_value, substitution, structs, deref_params, traits, enums);
54101
- if (any_node.target?.node_type) substitute_raw_in_node(any_node.target, substitution, structs, deref_params, traits, enums);
54102
- if (any_node.access?.node_type) substitute_raw_in_node(any_node.access, substitution, structs, deref_params, traits, enums);
54103
- if (any_node.swap?.node_type) substitute_raw_in_node(any_node.swap, substitution, structs, deref_params, traits, enums);
54104
- if (any_node.condition?.node_type) substitute_raw_in_node(any_node.condition, substitution, structs, deref_params, traits, enums);
54105
- if (any_node.index?.node_type) substitute_raw_in_node(any_node.index, substitution, structs, deref_params, traits, enums);
55138
+ if (any_node.value && any_node.value.node_type) substitute_raw_in_node(any_node.value, substitution, structs, deref_params, traits, tables);
55139
+ if (any_node.left_value?.node_type) substitute_raw_in_node(any_node.left_value, substitution, structs, deref_params, traits, tables);
55140
+ if (any_node.right_value?.node_type) substitute_raw_in_node(any_node.right_value, substitution, structs, deref_params, traits, tables);
55141
+ if (any_node.target?.node_type) substitute_raw_in_node(any_node.target, substitution, structs, deref_params, traits, tables);
55142
+ if (any_node.access?.node_type) substitute_raw_in_node(any_node.access, substitution, structs, deref_params, traits, tables);
55143
+ if (any_node.swap?.node_type) substitute_raw_in_node(any_node.swap, substitution, structs, deref_params, traits, tables);
55144
+ if (any_node.condition?.node_type) substitute_raw_in_node(any_node.condition, substitution, structs, deref_params, traits, tables);
55145
+ if (any_node.index?.node_type) substitute_raw_in_node(any_node.index, substitution, structs, deref_params, traits, tables);
54106
55146
  }
54107
55147
  function specialize_function(generic_func, call_node, status) {
54108
55148
  const substitution = /* @__PURE__ */ new Map();
@@ -55121,7 +56161,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
55121
56161
  for (let [i, param] of node.params.entries()) {
55122
56162
  const func_param = func.params[i + self_offset];
55123
56163
  if (func_param && func_param.type.is_nullable && !func_param.is_self_param && !func_param.type.is_ref && !func_param.type.is_array) {
55124
- if (status.structs.find((st) => st.name === func_param.type.name && !st.is_class && !st.is_simple_type) || is_nullable_scalar_type(func_param.type)) {
56164
+ if (status.structs.find((st) => st.name === func_param.type.name && !st.is_class && !st.is_simple_type) || is_nullable_scalar_type(func_param.type, status)) {
55125
56165
  if (!node.nullable_param_indices) node.nullable_param_indices = [];
55126
56166
  if (!node.nullable_param_indices.includes(i)) node.nullable_param_indices.push(i);
55127
56167
  }
@@ -55252,7 +56292,14 @@ function check_function_call(node, status, func, target_type, self_value, self_p
55252
56292
  const named = status.functions.findLast((f) => f.name === param_value);
55253
56293
  if (named) param.resolved_function = named;
55254
56294
  }
55255
- if (param_type.is_array && param_type.length && !func_param.type.length) func_param.type.length = param_type.length;
56295
+ if (param_type.is_array && param_type.length && !func_param.is_variadic) {
56296
+ if (!func_param.type.length) func_param.type.length = param_type.length;
56297
+ else {
56298
+ const stamped = String(func_param.type.length.value);
56299
+ const incoming = String(param_type.length.value);
56300
+ 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);
56301
+ }
56302
+ }
55256
56303
  let arg_value;
55257
56304
  if (param.node_type === "value") {
55258
56305
  const vn = param;
@@ -56603,7 +57650,7 @@ function check_as_array_or_inferred_tuple(array, status, forced_tuple_types) {
56603
57650
  array.type.is_array = true;
56604
57651
  array.type.is_nullable = old_expected.is_nullable;
56605
57652
  array.type.type_args = old_expected.type_args;
56606
- array.type.length = old_expected.length;
57653
+ if (!old_expected.length || String(old_expected.length.value) === String(array.values.length)) array.type.length = old_expected.length;
56607
57654
  } else if (!array.type.name) {
56608
57655
  const first_type = value_types[0];
56609
57656
  if (first_type) {
@@ -57954,6 +59001,11 @@ function check_switch_node(switch_node, status) {
57954
59001
  let branch_statuses = [];
57955
59002
  for (let switch_case of switch_node.cases) {
57956
59003
  check_node(switch_case.condition, status);
59004
+ if (status.allocations.length) {
59005
+ switch_case.condition.allocations ??= [];
59006
+ switch_case.condition.allocations.push(...status.allocations);
59007
+ status.allocations.length = 0;
59008
+ }
57957
59009
  const condition_type = type_from_value_node(switch_case.condition, status);
57958
59010
  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
59011
  let case_status = clone_status(status);