nomen-lang 0.6.6 → 0.6.8

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.
package/dist/index.mjs CHANGED
@@ -6811,6 +6811,27 @@ function find_enum(name, status) {
6811
6811
  }
6812
6812
  //#endregion
6813
6813
  //#region ../src/build_common/mono_name.ts
6814
+ /**
6815
+ * Whether `name` is a monomorphized owning-container struct whose
6816
+ * `<Mono>_copy` is safe to call from the owning-`Buffer.load_T`
6817
+ * specialization — `List_<T>` / `Buffer_<T>` (and their bare generics).
6818
+ * These own a heap slab their `#destroy` frees unconditionally, and `copy`
6819
+ * yields an independent slab, so a returned element's field must be
6820
+ * deep-copied or the caller's unconditional container-field teardown
6821
+ * double-frees the slot's slab.
6822
+ *
6823
+ * `Map`/`Set` are deliberately EXCLUDED from the automatic deep copy: their
6824
+ * monomorphized `copy` (which composes several `Buffer.copy` calls) is
6825
+ * verified standalone on both backends, but invoking it from the load
6826
+ * specialization corrupts the returned struct on aarch64 (a codegen
6827
+ * interaction still open — see FOLLOWUP.md). A `Map`/`Set` FIELD reached
6828
+ * through a container element's `load` therefore stays shallow (the
6829
+ * pre-existing bounded gap); an EXPLICIT `.copy()` on the owning struct or
6830
+ * the Map/Set itself works on both backends.
6831
+ */
6832
+ function is_load_deep_copy_container(name) {
6833
+ return name === "List" || name === "Buffer" || name.startsWith("List_") || name.startsWith("Buffer_");
6834
+ }
6814
6835
  function mono_type_name(type_or_name, type_args) {
6815
6836
  const name = typeof type_or_name === "string" ? type_or_name : type_or_name.name;
6816
6837
  const args = typeof type_or_name === "string" ? type_args : type_or_name.type_args;
@@ -7147,14 +7168,37 @@ function call_result_type(node) {
7147
7168
  * so `null == 0` conflates them. Nullable `string` (in-band fat zero) and
7148
7169
  * nullable `func`/class values (null pointer) keep their in-band null and
7149
7170
  * are NOT nullable scalars.
7171
+ *
7172
+ * The same holds for a nullable SIMPLE enum (`Color?`) or bitset
7173
+ * (`Permissions?`): the in-band zero (the first case's tag 0 / an empty
7174
+ * bitset) is a real value, so `null` needs the companion flag. Enum-with-data
7175
+ * and generic template enums are NOT nullable scalars — the checker rejects
7176
+ * `?` on enums with associated data, so every nullable enum that reaches the
7177
+ * backends is simple.
7150
7178
  */
7151
- /** True if `type` is a nullable built-in scalar (`bool?`, `int?`, `float?`, …). */
7152
- function is_nullable_scalar_type(type) {
7179
+ /** True if the TYPE NAME is a built-in scalar, simple enum, or bitset — the
7180
+ * shapes that get the `<slot>_has` flag when nullable. */
7181
+ function is_nullable_scalar_name(name, tables) {
7182
+ if (!name) return false;
7183
+ const info = get_built_in_type(name);
7184
+ if (info) return info.kind !== "string" && info.kind !== "func";
7185
+ const enum_node = tables.enums.findLast((e) => (e.name === name || e.source_name === name) && !e.is_generic);
7186
+ if (enum_node) return !enum_node.has_associated_data;
7187
+ return tables.bitsets.some((b) => b.name === name || b.source_name === name);
7188
+ }
7189
+ /** True if `type` is a nullable built-in scalar, simple enum, or bitset. */
7190
+ function is_nullable_scalar_type(type, tables) {
7153
7191
  if (!type?.is_nullable) return false;
7154
7192
  if (type.is_array || type.is_ref || type.storage_kind === "view") return false;
7155
7193
  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";
7194
+ return is_nullable_scalar_name(type.name, tables);
7195
+ }
7196
+ /** True if `type` is a nullable struct (see nullable_struct.ts) or nullable scalar. */
7197
+ function is_nullable_flagged_type(type, status) {
7198
+ if (!type?.is_nullable) return false;
7199
+ if (type.name === "string") return false;
7200
+ if (type.name === "func") return false;
7201
+ return is_nullable_scalar_type(type, status) || !!status.structs.find((s) => s.name === type.name && !s.is_class && !s.is_simple_type);
7158
7202
  }
7159
7203
  //#endregion
7160
7204
  //#region ../src/build_common/nullable_struct.ts
@@ -7653,10 +7697,15 @@ function direct_string_fields(node) {
7653
7697
  * callee's own records are scope-local and die at return, so without the
7654
7698
  * transfer the stored copy leaks.
7655
7699
  *
7656
- * Only DIRECT statements qualify: a store nested in a branch/loop may not
7657
- * execute, and a record over the pre-call value would free it at the caller's
7658
- * scope exit (possibly rodata — an invalid free). Memoized on the function
7659
- * node; forwarding cycles are cut by a visited set (the cycle member's own
7700
+ * Only DIRECT statements qualify for the plain transfer: a store nested in a
7701
+ * branch/loop may not execute, and a record over the pre-call value would
7702
+ * free it at the caller's scope exit (possibly rodata — an invalid free).
7703
+ * Those CONDITIONAL stores are reported separately
7704
+ * (scan_ref_param_string_field_may_writes): the callee's function builder
7705
+ * emits an ENTRY DUP for each of them (`field = strdup(field)` at function
7706
+ * entry, recorded), which makes the field heap-owned on EVERY path and the
7707
+ * may-store record sound to transfer. Memoized on the function node;
7708
+ * forwarding cycles are cut by a visited set (the cycle member's own
7660
7709
  * contribution is skipped, bounded-leak).
7661
7710
  */
7662
7711
  function scan_ref_param_string_field_writes(func, structs) {
@@ -7664,6 +7713,51 @@ function scan_ref_param_string_field_writes(func, structs) {
7664
7713
  if (stamp._ref_param_string_field_writes) return stamp._ref_param_string_field_writes;
7665
7714
  const result = /* @__PURE__ */ new Map();
7666
7715
  stamp._ref_param_string_field_writes = result;
7716
+ const tracked = tracked_ref_params(func, structs);
7717
+ if (tracked.size) scan_top_level(func.statements ?? [], tracked, structs, result);
7718
+ return result;
7719
+ }
7720
+ /**
7721
+ * The CONDITIONAL (`may-executed`) ref-param field stores: direct stores
7722
+ * nested in if/else branches, switch cases, and loop bodies. Forwarded
7723
+ * calls from a nested position are deliberately EXCLUDED: a helper's store
7724
+ * builds with its own (fresh) record set, so it cannot reclaim the
7725
+ * displaced entry-dup — dup'ing for a forwarded store would trade the
7726
+ * old bounded leak for a new one. Those chains keep the documented
7727
+ * bounded-leak posture (the stored copy leaks, never an invalid free).
7728
+ * The function builder entry-dups these fields (see
7729
+ * ref_param_entry_dup_fields), making the may-store record transferable.
7730
+ */
7731
+ function scan_ref_param_string_field_may_writes(func, structs) {
7732
+ const stamp = func;
7733
+ if (stamp._ref_param_string_field_may_writes) return stamp._ref_param_string_field_may_writes;
7734
+ const result = /* @__PURE__ */ new Map();
7735
+ stamp._ref_param_string_field_may_writes = result;
7736
+ const tracked = tracked_ref_params(func, structs);
7737
+ if (tracked.size) scan_nested(func.statements ?? [], tracked, result);
7738
+ return result;
7739
+ }
7740
+ /**
7741
+ * The fields a function's builder must ENTRY DUP: the conditional
7742
+ * (may-executed) ref-param string-field stores that are NOT also
7743
+ * definitely-executed (a must-store already leaves the field heap-owned on
7744
+ * every path — dup'ing it too would just add a pointless strdup per call).
7745
+ * The dup runs at callee entry on both backends and records the field, so a
7746
+ * call site may transfer these as heap records soundly.
7747
+ */
7748
+ function ref_param_entry_dup_fields(func, structs) {
7749
+ const must = scan_ref_param_string_field_writes(func, structs);
7750
+ const may = scan_ref_param_string_field_may_writes(func, structs);
7751
+ const result = /* @__PURE__ */ new Map();
7752
+ for (const [param, fields] of may) {
7753
+ const dup = /* @__PURE__ */ new Set();
7754
+ for (const field of fields) if (!must.get(param)?.has(field)) dup.add(field);
7755
+ if (dup.size) result.set(param, dup);
7756
+ }
7757
+ return result;
7758
+ }
7759
+ /** The tracked ref params of `func`: name -> writable plain string fields. */
7760
+ function tracked_ref_params(func, structs) {
7667
7761
  const tracked = /* @__PURE__ */ new Map();
7668
7762
  for (const param of func.params ?? []) {
7669
7763
  if (param.is_self_param) continue;
@@ -7672,8 +7766,7 @@ function scan_ref_param_string_field_writes(func, structs) {
7672
7766
  const fields = ref_param_writable_fields(param.type.name, structs);
7673
7767
  if (fields.size) tracked.set(param.name, fields);
7674
7768
  }
7675
- if (tracked.size) scan_function(func, tracked, structs, result, /* @__PURE__ */ new Set());
7676
- return result;
7769
+ return tracked;
7677
7770
  }
7678
7771
  /** The writable plain string fields of the value struct `type_name` resolves
7679
7772
  * to, or the empty set (non-struct, class, or no string fields). */
@@ -7683,46 +7776,91 @@ function ref_param_writable_fields(type_name, structs) {
7683
7776
  if (!struct) return /* @__PURE__ */ new Set();
7684
7777
  return new Set(direct_string_fields(struct).map((f) => f.name));
7685
7778
  }
7686
- function scan_function(func, tracked, structs, result, visiting) {
7687
- visiting.add(func);
7688
- for (const stmt of func.statements ?? []) {
7779
+ /**
7780
+ * The top-level (definitely-executed) walk: direct `param.field = …`
7781
+ * statements and top-level forwarded calls only — nothing nested.
7782
+ */
7783
+ function scan_top_level(statements, tracked, structs, result) {
7784
+ for (const stmt of statements) {
7689
7785
  if (stmt.node_type === "assign") {
7690
- const lhs = stmt.left_value;
7691
- if (lhs?.node_type === "access") {
7692
- const access = lhs;
7693
- if (access.access.node_type === "access_field" && access.target.node_type === "value") {
7694
- const name = access.target.value ?? "";
7695
- const fields = tracked.get(name);
7696
- const field = access.access.name ?? "";
7697
- if (fields?.has(field)) {
7698
- if (!result.has(name)) result.set(name, /* @__PURE__ */ new Set());
7699
- result.get(name).add(field);
7700
- }
7701
- }
7702
- }
7786
+ record_direct_store(stmt, tracked, result);
7703
7787
  continue;
7704
7788
  }
7705
7789
  const call = ref_forward_call(stmt);
7706
- if (!call) continue;
7707
- const [callee, args] = call;
7708
- const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
7709
- for (let i = 0; i < args.length && i < non_self_params.length; i++) {
7710
- const arg = args[i];
7711
- if (arg?.node_type !== "value") continue;
7712
- const arg_name = arg.value ?? "";
7713
- const my_fields = tracked.get(arg_name);
7714
- if (!my_fields) continue;
7715
- const callee_param = non_self_params[i];
7716
- if (!(callee_param.is_ref || callee_param.type.is_ref)) continue;
7717
- if (visiting.has(callee)) continue;
7718
- const callee_writes = scan_ref_param_string_field_writes(callee, structs).get(callee_param.name);
7719
- if (!callee_writes?.size) continue;
7720
- if (!result.has(arg_name)) result.set(arg_name, /* @__PURE__ */ new Set());
7721
- const mine = result.get(arg_name);
7722
- for (const field of callee_writes) if (my_fields.has(field)) mine.add(field);
7723
- }
7724
- }
7725
- visiting.delete(func);
7790
+ if (call) merge_forwarded(call, tracked, structs, result);
7791
+ }
7792
+ }
7793
+ /**
7794
+ * The nested walk: direct stores at ANY depth below the top level are
7795
+ * may-stores. Forwarded calls are skipped everywhere (their contributions
7796
+ * would need cross-function displaced-value knowledge the callee's fresh
7797
+ * record set cannot carry — see the may-scan doc).
7798
+ */
7799
+ function scan_nested(statements, tracked, result) {
7800
+ for (const stmt of statements) {
7801
+ if (stmt.node_type === "assign") {
7802
+ record_direct_store(stmt, tracked, result);
7803
+ continue;
7804
+ }
7805
+ for (const nested of nested_statement_lists(stmt)) scan_nested(nested, tracked, result);
7806
+ }
7807
+ }
7808
+ /** Record a direct `param.<field> = …` store into `result`. */
7809
+ function record_direct_store(stmt, tracked, result) {
7810
+ const lhs = stmt.left_value;
7811
+ if (lhs?.node_type !== "access") return;
7812
+ const access = lhs;
7813
+ if (access.access.node_type !== "access_field" || access.target.node_type !== "value") return;
7814
+ const name = access.target.value ?? "";
7815
+ const fields = tracked.get(name);
7816
+ const field = access.access.name ?? "";
7817
+ if (!fields?.has(field)) return;
7818
+ if (!result.has(name)) result.set(name, /* @__PURE__ */ new Set());
7819
+ result.get(name).add(field);
7820
+ }
7821
+ /** Merge a forwarded call's callee stores for the forwarded param: the
7822
+ * callee's MUST stores at a top-level position. */
7823
+ function merge_forwarded(call, tracked, structs, result) {
7824
+ const [callee, args] = call;
7825
+ const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
7826
+ for (let i = 0; i < args.length && i < non_self_params.length; i++) {
7827
+ const arg = args[i];
7828
+ if (arg?.node_type !== "value") continue;
7829
+ const arg_name = arg.value ?? "";
7830
+ const my_fields = tracked.get(arg_name);
7831
+ if (!my_fields) continue;
7832
+ const callee_param = non_self_params[i];
7833
+ if (!(callee_param.is_ref || callee_param.type.is_ref)) continue;
7834
+ const source = scan_ref_param_string_field_writes(callee, structs).get(callee_param.name);
7835
+ if (!source?.size) continue;
7836
+ if (!result.has(arg_name)) result.set(arg_name, /* @__PURE__ */ new Set());
7837
+ const mine = result.get(arg_name);
7838
+ for (const field of source) if (my_fields.has(field)) mine.add(field);
7839
+ }
7840
+ }
7841
+ /** The statement lists nested inside `stmt` (branch bodies, loop bodies) —
7842
+ * the same walk scan_reassigned_vars uses. */
7843
+ function nested_statement_lists(stmt) {
7844
+ const branch_of = (b) => b?.statements ?? [];
7845
+ const lists = [];
7846
+ if (stmt.node_type === "while" || stmt.node_type === "for") {
7847
+ const nested = stmt.statements;
7848
+ if (nested) lists.push(nested);
7849
+ return lists;
7850
+ }
7851
+ if (stmt.node_type === "if") {
7852
+ const n = stmt;
7853
+ if (n.if_branch) lists.push(branch_of(n.if_branch));
7854
+ if (n.else_branch) lists.push(branch_of(n.else_branch));
7855
+ return lists;
7856
+ }
7857
+ if (stmt.node_type === "switch" || stmt.node_type === "match") {
7858
+ const n = stmt;
7859
+ for (const c of n.cases ?? []) if (c.branch) lists.push(branch_of(c.branch));
7860
+ if (n.else_branch) lists.push(branch_of(n.else_branch));
7861
+ return lists;
7862
+ }
7863
+ return lists;
7726
7864
  }
7727
7865
  /**
7728
7866
  * The forwarded-call shape of a top-level statement: a resolved free-function
@@ -7753,6 +7891,12 @@ function ref_forward_call(stmt) {
7753
7891
  * stores (scan_ref_param_string_field_writes) onto the caller's records for
7754
7892
  * the argument variable, making the caller's scope exit free them.
7755
7893
  *
7894
+ * The callee's CONDITIONAL stores transfer too
7895
+ * (ref_param_entry_dup_fields): the callee's builder entry-dups those
7896
+ * fields (`field = strdup(field)` at entry, recorded), so the field is
7897
+ * heap-owned on EVERY path out of the call — the not-taken path holds the
7898
+ * dup — and a caller-side record is sound.
7899
+ *
7756
7900
  * `arg_is_trackable_local` must report true only for variables whose storage
7757
7901
  * THIS function's scope exit owns — a plain local. A `ref` param of the
7758
7902
  * enclosing function forwards caller storage, so a record here would free the
@@ -7760,19 +7904,22 @@ function ref_forward_call(stmt) {
7760
7904
  */
7761
7905
  function transfer_ref_param_field_records(callee, args, status, arg_is_trackable_local) {
7762
7906
  const writes = scan_ref_param_string_field_writes(callee, status.structs);
7763
- if (!writes.size) return;
7907
+ const dups = ref_param_entry_dup_fields(callee, status.structs);
7908
+ if (!writes.size && !dups.size) return;
7764
7909
  const non_self_params = (callee.params ?? []).filter((p) => !p.is_self_param);
7765
7910
  for (let i = 0; i < args.length && i < non_self_params.length; i++) {
7766
7911
  const param = non_self_params[i];
7767
7912
  if (!(param.is_ref || param.type.is_ref)) continue;
7768
7913
  const fields = writes.get(param.name);
7769
- if (!fields?.size) continue;
7914
+ const dup_fields = dups.get(param.name);
7915
+ if (!fields?.size && !dup_fields?.size) continue;
7770
7916
  const arg = args[i];
7771
7917
  if (arg?.node_type !== "value") continue;
7772
7918
  const name = arg.value;
7773
7919
  if (!name || !arg_is_trackable_local(name)) continue;
7774
7920
  if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
7775
- for (const field of fields) status.heap_string_fields.add(`${name}.${field}`);
7921
+ for (const field of fields ?? []) status.heap_string_fields.add(`${name}.${field}`);
7922
+ for (const field of dup_fields ?? []) status.heap_string_fields.add(`${name}.${field}`);
7776
7923
  }
7777
7924
  }
7778
7925
  //#endregion
@@ -8084,7 +8231,7 @@ var AssignmentNode = class extends BaseNode {
8084
8231
  super("assign", start);
8085
8232
  this.left_value = left_value;
8086
8233
  this.right_value = right_value;
8087
- this.operator = operator;
8234
+ this.operator = operator === "=" ? void 0 : operator;
8088
8235
  }
8089
8236
  };
8090
8237
  //#endregion
@@ -8265,7 +8412,7 @@ function is_container_borrow_accessor_name(name) {
8265
8412
  * site (see build_return_node), so it counts as owned for classification.
8266
8413
  */
8267
8414
  function is_call_site_borrow_accessor(fn_name) {
8268
- return fn_name === "at" || fn_name === "first";
8415
+ return fn_name === "at" || fn_name === "first" || fn_name === "load";
8269
8416
  }
8270
8417
  /**
8271
8418
  * Whether `node` is a container/buffer borrow accessor CALL (an
@@ -12011,7 +12158,7 @@ function get_struct_size(name, status) {
12011
12158
  size = align_to(size, get_type_alignment(field.type, status));
12012
12159
  size += get_type_size(field.type, status);
12013
12160
  if (is_nullable_struct_type(field.type, status)) size += 8;
12014
- else if (is_nullable_scalar_type(field.type)) size += 1;
12161
+ else if (is_nullable_scalar_type(field.type, status)) size += 1;
12015
12162
  }
12016
12163
  size = align_to(size, 8);
12017
12164
  return size;
@@ -12063,7 +12210,7 @@ function get_field_offset_of_fields(fields, field_name, status) {
12063
12210
  if (field.name === field_name) return offset;
12064
12211
  offset += get_type_size(field.type, status);
12065
12212
  if (is_nullable_struct_type(field.type, status)) offset += 8;
12066
- else if (is_nullable_scalar_type(field.type)) offset += 1;
12213
+ else if (is_nullable_scalar_type(field.type, status)) offset += 1;
12067
12214
  }
12068
12215
  return offset;
12069
12216
  }
@@ -17729,7 +17876,7 @@ function owning_buffer_element_aarch64(node, status) {
17729
17876
  const elem_name = node.name.substring(7);
17730
17877
  const elem = status.structs.find((s) => s.name === elem_name && !s.is_simple_type && !s.is_generic);
17731
17878
  if (!elem || elem.is_class) return void 0;
17732
- if (!has_string_fields(elem, status)) return void 0;
17879
+ if (!has_string_fields(elem, status) && !struct_needs_destroy(elem, status)) return void 0;
17733
17880
  return elem;
17734
17881
  }
17735
17882
  /**
@@ -17875,6 +18022,75 @@ function collect_string_fields(elem, status, base_offset = 0) {
17875
18022
  return result;
17876
18023
  }
17877
18024
  /**
18025
+ * Collect the (offset, size) pairs of every owned CONTAINER field (`List<T>`/
18026
+ * `Buffer<T>`/… mono with a `#destroy`) the element's teardown would reclaim.
18027
+ * The owning store_T/replace_T memcpy the source's bytes into the slot, so
18028
+ * zeroing these fields of the SOURCE completes the ownership transfer: the
18029
+ * slot is the sole owner and the source's own teardown (when it still runs)
18030
+ * finds zeroed fields. Nested value struct fields recurse so their container
18031
+ * leaves are zeroed at their own flat offsets; their string fields are
18032
+ * untouched (the slot owns the strdup'd copies via collect_string_fields).
18033
+ * Mirrors the C backend's `emit_transfer_owning_fields`. `view T`/nullable
18034
+ * fields are skipped; class/enum payload fields are left alone (recorded in
18035
+ * FOLLOWUP.md).
18036
+ */
18037
+ function collect_transfer_fields(elem, status, base_offset = 0) {
18038
+ const result = [];
18039
+ let offset = 8;
18040
+ for (const field of elem.fields) {
18041
+ const size = get_type_size(field.type, status);
18042
+ if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable) {
18043
+ const field_struct = resolve_struct_type(field.type, status);
18044
+ if (field_struct && !field_struct.is_class) {
18045
+ if (has_destroy(field_struct)) result.push({
18046
+ offset: base_offset + offset,
18047
+ size
18048
+ });
18049
+ else if (struct_needs_destroy(field_struct, status)) result.push(...collect_transfer_fields(field_struct, status, base_offset + offset));
18050
+ }
18051
+ }
18052
+ offset += size;
18053
+ }
18054
+ return result;
18055
+ }
18056
+ /**
18057
+ * Emit `memset(&src.field, 0, size)` for every owned field collected by
18058
+ * `collect_transfer_fields`. `src` is the register holding the SOURCE struct
18059
+ * address (x2 inline / x21 standalone for store_T). Clobbers x0-x2.
18060
+ */
18061
+ function emit_transfer_owning_fields$1(status, src, fields) {
18062
+ for (const { offset, size } of fields) {
18063
+ emit_asm(status, `add x0, ${src}, #${offset}\n`);
18064
+ emit_asm(status, `mov x1, #0\n`);
18065
+ emit_asm(status, `mov x2, #${size}\n`);
18066
+ emit_asm(status, `bl _memset\n`);
18067
+ }
18068
+ }
18069
+ /**
18070
+ * Emit the deep-copy half of owning load_T into the sret buffer (x8): for
18071
+ * each `List<T>` field whose mono has a `copy` method, replace the
18072
+ * shallow-copied field with `<List_T>_copy(&slot.field)` (sret → the sret
18073
+ * buffer's field; self == x8 is safe — the copy reads its input before the
18074
+ * final result write). Mirrors the C backend's `emit_load_deep_copy_fields`.
18075
+ * `sret` is the register holding the returned struct's address; `slot` holds
18076
+ * the source slot's address (the copy reads from the slot, writes to sret).
18077
+ */
18078
+ function emit_load_deep_copy_fields$1(elem, status, sret, slot) {
18079
+ let offset = 8;
18080
+ for (const field of elem.fields) {
18081
+ const size = get_type_size(field.type, status);
18082
+ if (!field.type.is_ref && !field.type.is_view && !field.type.is_array && !field.type.is_nullable && field.type.name !== "string") {
18083
+ const field_struct = resolve_struct_type(field.type, status);
18084
+ if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
18085
+ emit_asm(status, `add x8, ${sret}, #${offset}\n`);
18086
+ emit_asm(status, `add x0, ${slot}, #${offset}\n`);
18087
+ emit_asm(status, `bl ${field_struct.name}_copy\n`);
18088
+ }
18089
+ }
18090
+ offset += size;
18091
+ }
18092
+ }
18093
+ /**
17878
18094
  * Emit `slot.field = strdup(src.field)` guarded against a NULL source field
17879
18095
  * (e.g. JsonTree's "no text" sentinel): a NULL field is copied as NULL —
17880
18096
  * strdup(NULL) would crash. `src`/`dst` are the register names holding the
@@ -17912,6 +18128,10 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
17912
18128
  emit_owning_shift_T(elem, status, "x0");
17913
18129
  return true;
17914
18130
  }
18131
+ if (func_name === "load") {
18132
+ emit_owning_load_T(elem, status, "x0");
18133
+ return true;
18134
+ }
17915
18135
  return false;
17916
18136
  }
17917
18137
  const enum_elem = owning_buffer_enum_element_aarch64(struct_node, status);
@@ -17973,7 +18193,7 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
17973
18193
  function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
17974
18194
  const elem = owning_buffer_element_aarch64(node, status);
17975
18195
  if (elem) {
17976
- if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify") return false;
18196
+ if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify" && func_name !== "load") return false;
17977
18197
  emit_owning_standalone_struct(elem, func_name, status);
17978
18198
  return true;
17979
18199
  }
@@ -18032,6 +18252,7 @@ function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
18032
18252
  function emit_owning_standalone_struct(elem, func_name, status) {
18033
18253
  const T_SIZE = get_struct_size(elem.name, status);
18034
18254
  const string_fields = collect_string_fields(elem, status);
18255
+ const transfer_fields = collect_transfer_fields(elem, status);
18035
18256
  if (func_name === "shift") {
18036
18257
  emit_owning_shift_T(elem, status, "x19");
18037
18258
  return true;
@@ -18040,6 +18261,23 @@ function emit_owning_standalone_struct(elem, func_name, status) {
18040
18261
  emit_owning_modify_T(elem, status, T_SIZE, string_fields);
18041
18262
  return true;
18042
18263
  }
18264
+ if (func_name === "load") {
18265
+ emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
18266
+ emit_asm(status, `stp x22, x23, [sp, #-16]!\n`);
18267
+ emit_asm(status, `ldr x20, [x19, #8]\n`);
18268
+ emit_asm(status, `mov x9, #${T_SIZE}\n`);
18269
+ emit_asm(status, `madd x21, x1, x9, x20\n`);
18270
+ emit_asm(status, `mov x22, x8\n`);
18271
+ emit_asm(status, `mov x0, x22\n`);
18272
+ emit_asm(status, `mov x1, x21\n`);
18273
+ emit_asm(status, `mov x2, x9\n`);
18274
+ emit_asm(status, `bl _memcpy\n`);
18275
+ emit_load_deep_copy_fields$1(elem, status, "x22", "x21");
18276
+ emit_asm(status, `mov x0, x22\n`);
18277
+ emit_asm(status, `ldp x22, x23, [sp], #16\n`);
18278
+ emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18279
+ return true;
18280
+ }
18043
18281
  emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
18044
18282
  emit_asm(status, `str x22, [sp, #-16]!\n`);
18045
18283
  emit_asm(status, `ldr x20, [x19, #8]\n`);
@@ -18077,6 +18315,7 @@ function emit_owning_standalone_struct(elem, func_name, status) {
18077
18315
  }
18078
18316
  if (string_fields.length) emit_asm(status, `add sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
18079
18317
  } else for (const { offset: foff } of string_fields) emit_strdup_field(status, "x21", "x20", foff, status.label_counter = (status.label_counter ?? 0) + 1);
18318
+ emit_transfer_owning_fields$1(status, "x21", transfer_fields);
18080
18319
  emit_asm(status, `ldr x22, [sp], #16\n`);
18081
18320
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18082
18321
  return true;
@@ -18278,9 +18517,35 @@ function emit_owning_shift_T(elem, status, self_reg) {
18278
18517
  emit_asm(status, `ldr x22, [sp], #16\n`);
18279
18518
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18280
18519
  }
18520
+ /**
18521
+ * Specialized owning-value-struct load_T (inline context: x0 = self, x1 = i,
18522
+ * x8 = sret). The caller's scope-exit destroy ALWAYS reclaims a struct
18523
+ * local's container fields, so the returned copy must own independent
18524
+ * containers — a shallow return would double-free the slot's buffer at the
18525
+ * caller's teardown. String fields stay borrowed (struct-local teardown only
18526
+ * frees RECORDED heap strings, so a strdup here would leak).
18527
+ */
18528
+ function emit_owning_load_T(elem, status, self_reg) {
18529
+ const T_SIZE = get_struct_size(elem.name, status);
18530
+ emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
18531
+ emit_asm(status, `stp x22, x23, [sp, #-16]!\n`);
18532
+ emit_asm(status, `ldr x20, [${self_reg}, #8]\n`);
18533
+ emit_asm(status, `mov x9, #${T_SIZE}\n`);
18534
+ emit_asm(status, `madd x21, x1, x9, x20\n`);
18535
+ emit_asm(status, `mov x22, x8\n`);
18536
+ emit_asm(status, `mov x0, x22\n`);
18537
+ emit_asm(status, `mov x1, x21\n`);
18538
+ emit_asm(status, `mov x2, x9\n`);
18539
+ emit_asm(status, `bl _memcpy\n`);
18540
+ emit_load_deep_copy_fields$1(elem, status, "x22", "x21");
18541
+ emit_asm(status, `mov x0, x22\n`);
18542
+ emit_asm(status, `ldp x22, x23, [sp], #16\n`);
18543
+ emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18544
+ }
18281
18545
  function emit_owning_store_T(elem, status) {
18282
18546
  const T_SIZE = get_struct_size(elem.name, status);
18283
18547
  const string_fields = collect_string_fields(elem, status);
18548
+ const transfer_fields = collect_transfer_fields(elem, status);
18284
18549
  emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
18285
18550
  emit_asm(status, `str x22, [sp, #-16]!\n`);
18286
18551
  emit_asm(status, `ldr x20, [x0, #8]\n`);
@@ -18312,12 +18577,14 @@ function emit_owning_store_T(elem, status) {
18312
18577
  emit_asm(status, `${lbl}:\n`);
18313
18578
  }
18314
18579
  if (string_fields.length) emit_asm(status, `add sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
18580
+ emit_transfer_owning_fields$1(status, "x21", transfer_fields);
18315
18581
  emit_asm(status, `ldr x22, [sp], #16\n`);
18316
18582
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18317
18583
  }
18318
18584
  function emit_owning_replace_T(elem, status) {
18319
18585
  const T_SIZE = get_struct_size(elem.name, status);
18320
18586
  const string_fields = collect_string_fields(elem, status);
18587
+ const transfer_fields = collect_transfer_fields(elem, status);
18321
18588
  emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
18322
18589
  emit_asm(status, `str x22, [sp, #-16]!\n`);
18323
18590
  emit_asm(status, `ldr x20, [x0, #8]\n`);
@@ -18332,6 +18599,7 @@ function emit_owning_replace_T(elem, status) {
18332
18599
  emit_asm(status, `mov x2, x22\n`);
18333
18600
  emit_asm(status, `bl _memcpy\n`);
18334
18601
  for (const { offset: foff } of string_fields) emit_strdup_field(status, "x21", "x20", foff, status.label_counter = (status.label_counter ?? 0) + 1);
18602
+ emit_transfer_owning_fields$1(status, "x21", transfer_fields);
18335
18603
  emit_asm(status, `ldr x22, [sp], #16\n`);
18336
18604
  emit_asm(status, `ldp x20, x21, [sp], #16\n`);
18337
18605
  }
@@ -19996,7 +20264,7 @@ function build_function_node$1(node, status) {
19996
20264
  status.code = "";
19997
20265
  node.return_type = resolve_mono_type(node.return_type, status);
19998
20266
  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);
19999
- const nullable_scalar_ret = is_nullable_scalar_type(node.return_type) && !node.return_type.is_ref;
20267
+ const nullable_scalar_ret = is_nullable_scalar_type(node.return_type, status) && !node.return_type.is_ref;
20000
20268
  const uses_sret = return_struct || nullable_scalar_ret;
20001
20269
  if (uses_sret) status.struct_return_buffer = "x8";
20002
20270
  status.function_return_type = node.return_type;
@@ -20083,7 +20351,7 @@ function build_function_node$1(node, status) {
20083
20351
  first_pass_slot += 2;
20084
20352
  continue;
20085
20353
  }
20086
- if (is_nullable_scalar_type(param.type)) {
20354
+ if (is_nullable_scalar_type(param.type, status)) {
20087
20355
  first_pass_slot += 2;
20088
20356
  continue;
20089
20357
  }
@@ -20250,7 +20518,7 @@ function build_function_node$1(node, status) {
20250
20518
  param_idx += 2;
20251
20519
  continue;
20252
20520
  }
20253
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref && !param.type.is_array) {
20521
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref && !param.type.is_array) {
20254
20522
  const size = aarch64_size(param.type.name);
20255
20523
  const offset = allocate_stack_space(status, size, size);
20256
20524
  status.stack_offsets.set(param.name, offset);
@@ -20467,6 +20735,7 @@ function build_function_node$1(node, status) {
20467
20735
  } else status.nir_emit_ctx = void 0;
20468
20736
  const old_raw_param_reloads = status.raw_param_reloads;
20469
20737
  install_raw_reload_plan(status, raw_reload_plan_lines);
20738
+ emit_ref_param_entry_dups$1(node, status);
20470
20739
  try {
20471
20740
  build_block_node$1(node, status);
20472
20741
  } finally {
@@ -20612,6 +20881,49 @@ function build_function_node$1(node, status) {
20612
20881
  status.return_buffer_stack_offset = void 0;
20613
20882
  status.function_return_type = void 0;
20614
20883
  }
20884
+ /**
20885
+ * Entry-dup the ref-param string fields the body stores through
20886
+ * CONDITIONALLY (see ref_param_entry_dup_fields): load the field pair's
20887
+ * ptr half, strdup it, store the fresh pointer back — with the field
20888
+ * recorded in heap_string_fields — so the field is heap-owned on every
20889
+ * path out of the function and the call-site record transfer is sound.
20890
+ * Mirrors the C backend's emit_ref_param_entry_dups; the len half is
20891
+ * unchanged by a dup, so only the ptr half is rewritten. A nullable
20892
+ * string field guards the dup (strdup(NULL) traps; a null pair stores as
20893
+ * null and every reclaim path no-ops on free(NULL)).
20894
+ */
20895
+ function emit_ref_param_entry_dups$1(node, status) {
20896
+ const dups = ref_param_entry_dup_fields(node, status.structs);
20897
+ if (!dups.size) return;
20898
+ for (const param of node.params ?? []) {
20899
+ if (param.is_self_param) continue;
20900
+ if (!(param.is_ref || param.type.is_ref)) continue;
20901
+ const fields = dups.get(param.name);
20902
+ if (!fields?.size) continue;
20903
+ const struct = status.structs.find((s) => s.name === param.type.name && !s.is_simple_type && !s.is_class);
20904
+ for (const field of fields) {
20905
+ const offset = get_field_offset(param.type.name, field, status);
20906
+ const paramReg = status.function_param_regs?.get(param.name);
20907
+ if (paramReg) emit_asm(status, `mov x0, ${paramReg}\n`);
20908
+ else if (is_local_ref_var(param.name, status)) emit_deref_var_address(status, "x0", param.name);
20909
+ else emit_var_address(status, "x0", param.name);
20910
+ emit_asm(status, `str x0, [sp, #-16]!\n`);
20911
+ emit_asm(status, `ldr x0, [x0, #${offset}]\n`);
20912
+ const nullable = !!struct?.fields.find((f) => f.name === field)?.type.is_nullable;
20913
+ let skip_label;
20914
+ if (nullable) {
20915
+ skip_label = `.Lentry_dup_${status.label_counter = (status.label_counter ?? 0) + 1}`;
20916
+ emit_asm(status, `cbz x0, ${skip_label}\n`);
20917
+ }
20918
+ emit_strdup(status);
20919
+ if (skip_label) emit_asm(status, `${skip_label}:\n`);
20920
+ emit_asm(status, `ldr x1, [sp], #16\n`);
20921
+ emit_asm(status, `str x0, [x1, #${offset}]\n`);
20922
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
20923
+ status.heap_string_fields.add(`${param.name}.${field}`);
20924
+ }
20925
+ }
20926
+ }
20615
20927
  //#endregion
20616
20928
  //#region ../src/build_aarch64/build_struct_node.ts
20617
20929
  let field_strdup_guard_counter = 0;
@@ -20666,7 +20978,7 @@ function init_enum_shorthand_field_default(node, field, base_reg, status) {
20666
20978
  * struct base register (`x0` for auto-init, `x19` for custom-init).
20667
20979
  */
20668
20980
  function init_nullable_field_default(node, field, base_reg, status) {
20669
- const scalar_nullable = is_nullable_scalar_type(field.type);
20981
+ const scalar_nullable = is_nullable_scalar_type(field.type, status);
20670
20982
  if (!is_nullable_struct_type(field.type, status) && !scalar_nullable) return false;
20671
20983
  if (!field.value) return false;
20672
20984
  const offset = get_field_offset(node.name, field.name, status);
@@ -21008,7 +21320,7 @@ function build_init_function(node, status) {
21008
21320
  const slot = ctor_slot;
21009
21321
  const field_is_view = !!field.type.is_view && !field.type.is_array;
21010
21322
  const field_is_fat_string = field.type.name === "string" && !field.type.is_view && !field.type.is_ref && !field.type.is_array;
21011
- const field_is_nullable_scalar = is_nullable_scalar_type(field.type);
21323
+ const field_is_nullable_scalar = is_nullable_scalar_type(field.type, status);
21012
21324
  if (field_is_view || field_is_fat_string || field_is_nullable_scalar) ctor_slot += 2;
21013
21325
  else ctor_slot += 1;
21014
21326
  let src_reg;
@@ -21186,9 +21498,9 @@ function build_init_function(node, status) {
21186
21498
  if (node.fields.find((nf) => nf.name === field.name)) continue;
21187
21499
  const offset = get_field_offset(node.name, field.name, status);
21188
21500
  const val = field.value.node_type === "value" ? field.value.value : void 0;
21189
- if (val === "null" && (is_nullable_scalar_type(field.type) || is_nullable_struct_type(field.type, status))) {
21501
+ if (val === "null" && (is_nullable_scalar_type(field.type, status) || is_nullable_struct_type(field.type, status))) {
21190
21502
  const has_off = get_field_has_offset(node.name, field.name, status);
21191
- if (is_nullable_scalar_type(field.type)) emit_asm(status, `strb wzr, [x19, #${has_off}]\n`);
21503
+ if (is_nullable_scalar_type(field.type, status)) emit_asm(status, `strb wzr, [x19, #${has_off}]\n`);
21192
21504
  else emit_asm(status, `str xzr, [x19, #${has_off}]\n`);
21193
21505
  continue;
21194
21506
  }
@@ -21329,7 +21641,7 @@ function build_custom_init_function(node, func, status) {
21329
21641
  param_idx += 2;
21330
21642
  continue;
21331
21643
  }
21332
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref && !param.type.is_array) {
21644
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref && !param.type.is_array) {
21333
21645
  const size = aarch64_size(param.type.name);
21334
21646
  const offset = allocate_stack_space(status, size, size);
21335
21647
  status.stack_offsets.set(param.name, offset);
@@ -21620,7 +21932,7 @@ function build_struct_functions$1(node, status) {
21620
21932
  status.function_view_params = /* @__PURE__ */ new Set();
21621
21933
  status.struct_return_buffer = void 0;
21622
21934
  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);
21623
- const nullable_scalar_ret = !!func.return_type && is_nullable_scalar_type(func.return_type) && !func.return_type.is_ref;
21935
+ const nullable_scalar_ret = !!func.return_type && is_nullable_scalar_type(func.return_type, status) && !func.return_type.is_ref;
21624
21936
  const uses_sret = return_struct || nullable_scalar_ret;
21625
21937
  let return_buffer_stack_offset;
21626
21938
  status.current_function_name = func.name;
@@ -21647,7 +21959,7 @@ function build_struct_functions$1(node, status) {
21647
21959
  slot_idx += 2;
21648
21960
  continue;
21649
21961
  }
21650
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref) {
21962
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref) {
21651
21963
  if (param.declaration === "var") status.function_param_vars.add(param.name);
21652
21964
  slot_idx += 2;
21653
21965
  continue;
@@ -21730,7 +22042,7 @@ function build_struct_functions$1(node, status) {
21730
22042
  second_slot_idx += 2;
21731
22043
  continue;
21732
22044
  }
21733
- if (is_nullable_scalar_type(param.type) && !param.type.is_ref) {
22045
+ if (is_nullable_scalar_type(param.type, status) && !param.type.is_ref) {
21734
22046
  const size = aarch64_size(param.type.name);
21735
22047
  const offset = allocate_stack_space(status, size, size);
21736
22048
  status.stack_offsets.set(param.name, offset);
@@ -23189,11 +23501,11 @@ function build_constructor_params(fc, param_regs, status) {
23189
23501
  let nullable_scalar = false;
23190
23502
  if (fc.nullable_param_indices?.includes(i)) {
23191
23503
  const cp = ctor_callee_params?.[i];
23192
- if (cp && is_nullable_scalar_type(cp.type)) nullable_scalar = true;
23193
- else if (is_nullable_scalar_type(p.type)) nullable_scalar = true;
23504
+ if (cp && is_nullable_scalar_type(cp.type, status)) nullable_scalar = true;
23505
+ else if (is_nullable_scalar_type(p.type, status)) nullable_scalar = true;
23194
23506
  else if (!cp) {
23195
23507
  const field = (status.structs.find((s) => s.name === fc.name && !s.is_simple_type)?.fields.filter((f) => f.value == null))?.[i];
23196
- if (field && is_nullable_scalar_type(field.type)) nullable_scalar = true;
23508
+ if (field && is_nullable_scalar_type(field.type, status)) nullable_scalar = true;
23197
23509
  }
23198
23510
  }
23199
23511
  const is_pair = pt === "string" || lit || is_view || nullable_scalar;
@@ -23220,7 +23532,7 @@ function build_constructor_params(fc, param_regs, status) {
23220
23532
  if (arg.node_type === "value" && arg.value === "null") {
23221
23533
  emit_asm(status, `mov x0, #0\n`);
23222
23534
  emit_asm(status, `mov x1, #0\n`);
23223
- } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type)) {
23535
+ } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type, status)) {
23224
23536
  const vname = arg.value;
23225
23537
  emit_var_load(status, "x0", vname, aarch64_size(arg.type.name));
23226
23538
  emit_var_load(status, "x1", has_flag_name(vname), 8);
@@ -23610,7 +23922,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23610
23922
  emit_var_store(status, "x0", node.name, 8);
23611
23923
  return;
23612
23924
  }
23613
- if (is_nullable_scalar_type(node.type)) {
23925
+ if (is_nullable_scalar_type(node.type, status)) {
23614
23926
  if (status.function_return_label) {
23615
23927
  const offset = allocate_stack_space(status, 16, 16);
23616
23928
  status.stack_offsets.set(node.name, offset);
@@ -23621,14 +23933,23 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23621
23933
  }
23622
23934
  const init_value = node.value;
23623
23935
  const init_type = call_result_type(init_value) ?? type_from_value_node$1(init_value);
23624
- const value_is_nullable_call = init_value.node_type === "func_call" && is_nullable_scalar_type(init_type);
23625
- if (init_value.node_type === "access" && is_nullable_scalar_type(init_type)) {
23936
+ const value_is_nullable_call = init_value.node_type === "func_call" && is_nullable_scalar_type(init_type, status);
23937
+ const value_is_nullable_method = init_value.node_type === "access" && init_value.access.node_type === "access_func" && is_nullable_scalar_type(init_type, status);
23938
+ const value_is_nullable_field = init_value.node_type === "access" && init_value.access.node_type === "access_field" && is_nullable_scalar_type(init_type, status);
23939
+ if (value_is_nullable_method) {
23626
23940
  emit_init_value$1(init_value, nir_init, status);
23627
23941
  ensure_newline(status);
23628
23942
  emit_asm(status, `ldr x9, [x0]\n`);
23629
23943
  emit_asm(status, `str x9, [x29, #${offset}]\n`);
23630
23944
  emit_asm(status, `ldr x9, [x0, #8]\n`);
23631
23945
  emit_asm(status, `str x9, [x29, #${offset + 8}]\n`);
23946
+ } else if (value_is_nullable_field) {
23947
+ emit_init_value$1(init_value, nir_init, status);
23948
+ ensure_newline(status);
23949
+ emit_var_store(status, "x0", node.name, 8);
23950
+ load_nullable_has(init_value, "x9", status);
23951
+ ensure_newline(status);
23952
+ emit_asm(status, `str x9, [x29, #${offset + 8}]\n`);
23632
23953
  } else if (value_is_nullable_call) {
23633
23954
  const old_buffer = status.struct_return_buffer;
23634
23955
  const old_buffer_var = status.struct_return_buffer_var;
@@ -23650,8 +23971,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23650
23971
  }
23651
23972
  return;
23652
23973
  }
23653
- emit_data(status, `${node.name}: .space 16\n`);
23654
- status.stack_offsets.set(has_flag_name(node.name), -1);
23974
+ const global_is_null = !node.value || node.value.node_type === "value" && node.value.value === "null";
23975
+ if (global_is_null) emit_data(status, `${node.name}: .space 8\n`);
23976
+ else {
23977
+ emit_data(status, `${node.name}: ${aarch64_type(node.type.name)} ${get_raw_value$1(node.value, status)}\n`);
23978
+ if (aarch64_size(node.type.name) < 8) emit_data(status, `.p2align 3\n`);
23979
+ }
23980
+ emit_data(status, `${has_flag_name(node.name)}: .quad ${global_is_null ? 0 : 1}\n`);
23655
23981
  return;
23656
23982
  }
23657
23983
  const directive = aarch64_type(node.type.name);
@@ -23839,6 +24165,14 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23839
24165
  if (element_size === 1) emit_asm(status, `strb w0, [x9, #${slot}]\n`);
23840
24166
  else if (element_size === 4) emit_asm(status, `str w0, [x9, #${slot}]\n`);
23841
24167
  else emit_asm(status, `str x0, [x9, #${slot}]\n`);
24168
+ } else if (element_size > 8 && struct_element && !struct_element.is_class) {
24169
+ emit_struct_address_param(value, status);
24170
+ ensure_newline(status);
24171
+ emit_asm(status, `ldr x9, [x29, #${offset}]\n`);
24172
+ emit_asm(status, `mov x1, x0\n`);
24173
+ emit_asm(status, `add x0, x9, #${slot}\n`);
24174
+ emit_asm(status, `mov x2, #${element_size}\n`);
24175
+ emit_asm(status, `bl _memcpy\n`);
23842
24176
  } else {
23843
24177
  emit_init_value$1(value, nir_array_element(nir_init, array_literal_node, i), status);
23844
24178
  ensure_newline(status);
@@ -23878,6 +24212,14 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23878
24212
  if (element_size === 1) emit_asm(status, `strb w0, [x9, #${slot}]\n`);
23879
24213
  else if (element_size === 4) emit_asm(status, `str w0, [x9, #${slot}]\n`);
23880
24214
  else emit_asm(status, `str x0, [x9, #${slot}]\n`);
24215
+ } else if (element_size > 8 && struct_element && !struct_element.is_class) {
24216
+ emit_struct_address_param(value, status);
24217
+ ensure_newline(status);
24218
+ emit_asm(status, `ldr x9, [${node.name}]\n`);
24219
+ emit_asm(status, `mov x1, x0\n`);
24220
+ emit_asm(status, `add x0, x9, #${slot}\n`);
24221
+ emit_asm(status, `mov x2, #${element_size}\n`);
24222
+ emit_asm(status, `bl _memcpy\n`);
23881
24223
  } else {
23882
24224
  emit_init_value$1(value, nir_array_element(nir_init, array_literal_node, i), status);
23883
24225
  ensure_newline(status);
@@ -23984,6 +24326,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
23984
24326
  if (element_size === 1) emit_asm(status, `strb w0, [x29, #${slot_offset}]\n`);
23985
24327
  else if (element_size === 4) emit_asm(status, `str w0, [x29, #${slot_offset}]\n`);
23986
24328
  else emit_asm(status, `str x0, [x29, #${slot_offset}]\n`);
24329
+ } else if (element_size > 8 && struct_element && !struct_element.is_class) {
24330
+ emit_struct_address_param(value, status);
24331
+ ensure_newline(status);
24332
+ emit_asm(status, `mov x1, x0\n`);
24333
+ emit_asm(status, `add x0, x29, #${slot_offset}\n`);
24334
+ emit_asm(status, `mov x2, #${element_size}\n`);
24335
+ emit_asm(status, `bl _memcpy\n`);
23987
24336
  } else {
23988
24337
  emit_init_value$1(value, nir_array_element(nir_init, array_values, i), status);
23989
24338
  ensure_newline(status);
@@ -24009,6 +24358,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
24009
24358
  if (element_size === 1) emit_asm(status, `strb w0, [${node.name} + ${i * element_size}]\n`);
24010
24359
  else if (element_size === 4) emit_asm(status, `str w0, [${node.name} + ${i * element_size}]\n`);
24011
24360
  else emit_asm(status, `str x0, [${node.name} + ${i * element_size}]\n`);
24361
+ } else if (element_size > 8 && struct_element && !struct_element.is_class) {
24362
+ emit_struct_address_param(value, status);
24363
+ ensure_newline(status);
24364
+ emit_asm(status, `mov x1, x0\n`);
24365
+ emit_global_slot_addr(status, node.name, i * element_size);
24366
+ emit_asm(status, `mov x2, #${element_size}\n`);
24367
+ emit_asm(status, `bl _memcpy\n`);
24012
24368
  } else {
24013
24369
  emit_init_value$1(value, nir_array_element(nir_init, array_values, i), status);
24014
24370
  ensure_newline(status);
@@ -26115,7 +26471,7 @@ function build_return_node$1(node, status, nir_value) {
26115
26471
  hoist_field_overrides(node.value, build_node, status);
26116
26472
  if (node.from_inline) return;
26117
26473
  const returns_nullable_struct = is_nullable_struct_type(status.function_return_type, status);
26118
- const returns_nullable_scalar = is_nullable_scalar_type(status.function_return_type);
26474
+ const returns_nullable_scalar = is_nullable_scalar_type(status.function_return_type, status);
26119
26475
  if ((returns_nullable_struct || returns_nullable_scalar) && (!node.value || node.value.node_type === "value" && node.value.value === "null")) {
26120
26476
  if (status.return_buffer_stack_offset !== void 0) {
26121
26477
  const struct_size = get_struct_size(status.function_return_type.name, status);
@@ -26305,7 +26661,7 @@ function build_return_node$1(node, status, nir_value) {
26305
26661
  if (status.return_buffer_stack_offset !== void 0) emit_asm(status, `ldr x8, [x29, #${status.return_buffer_stack_offset}]\n`);
26306
26662
  emit_typed_store(status, "x0", "x8", 0, aarch64_size(status.function_return_type.name));
26307
26663
  const value_type = node.value ? type_from_value_node$1(node.value) : void 0;
26308
- if (node.value && value_type?.is_nullable && is_nullable_scalar_type(value_type)) {
26664
+ if (node.value && value_type?.is_nullable && is_nullable_scalar_type(value_type, status)) {
26309
26665
  load_nullable_has(node.value, "x9", status);
26310
26666
  ensure_newline(status);
26311
26667
  emit_asm(status, `str x9, [x8, #8]\n`);
@@ -29935,7 +30291,7 @@ function build_function_call_node$1(node, status) {
29935
30291
  if (!node.nullable_param_indices?.includes(i)) continue;
29936
30292
  const arg_type = node.params[i].type;
29937
30293
  const callee_param = node.resolved_function?.params?.[i];
29938
- if (is_nullable_scalar_type(arg_type) || callee_param && is_nullable_scalar_type(callee_param.type)) nullable_scalar_arg_set.add(i);
30294
+ if (is_nullable_scalar_type(arg_type, status) || callee_param && is_nullable_scalar_type(callee_param.type, status)) nullable_scalar_arg_set.add(i);
29939
30295
  }
29940
30296
  const arg_slot = [];
29941
30297
  let total_slots = 0;
@@ -30033,7 +30389,7 @@ function build_function_call_node$1(node, status) {
30033
30389
  if (arg.node_type === "value" && arg.value === "null") {
30034
30390
  emit_asm(status, `mov x0, #0\n`);
30035
30391
  emit_asm(status, `mov x1, #0\n`);
30036
- } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type)) {
30392
+ } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type, status)) {
30037
30393
  const vname = arg.value;
30038
30394
  emit_var_load(status, "x0", vname, aarch64_size(param_type.name));
30039
30395
  emit_var_load(status, "x1", has_flag_name(vname), 8);
@@ -30147,7 +30503,7 @@ function build_function_call_node$1(node, status) {
30147
30503
  } else if (!is_struct && node.type?.name && !status.call_x8_preset) {
30148
30504
  const return_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
30149
30505
  const return_enum_size = get_enum_sret_size(node.type?.name, status);
30150
- const nullable_scalar_ret = is_nullable_scalar_type(node.type);
30506
+ const nullable_scalar_ret = is_nullable_scalar_type(node.type, status);
30151
30507
  if (return_struct || return_enum_size !== void 0 || nullable_scalar_ret) {
30152
30508
  const nullable_ret = is_nullable_struct_type(node.type, status);
30153
30509
  const struct_size = return_struct ? get_struct_size(node.type.name, status) : nullable_scalar_ret ? 8 : return_enum_size;
@@ -30203,7 +30559,7 @@ function build_function_call_node$1(node, status) {
30203
30559
  if (!is_struct && node.type?.name && !status.call_x8_preset) {
30204
30560
  const return_struct = status.structs.find((s) => s.name === node.type.name && !s.is_simple_type && !s.is_class);
30205
30561
  const return_enum_size = get_enum_sret_size(node.type?.name, status);
30206
- const nullable_scalar_ret = is_nullable_scalar_type(node.type);
30562
+ const nullable_scalar_ret = is_nullable_scalar_type(node.type, status);
30207
30563
  if (return_struct || return_enum_size !== void 0 || nullable_scalar_ret) {
30208
30564
  const temp_name = `_call_ret_${temp_counter - 1}`;
30209
30565
  emit_asm(status, `add x0, x29, #${status.stack_offsets.get(temp_name)}\n`);
@@ -31078,7 +31434,7 @@ function build_struct_body(node, status) {
31078
31434
  status.code += `];\n`;
31079
31435
  } else {
31080
31436
  status.code += `${field_c_type(field.type, status)} ${field.name};\n`;
31081
- if (is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type)) status.code += `unsigned char ${has_flag_name(field.name)};\n`;
31437
+ 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`;
31082
31438
  }
31083
31439
  for (let traitName of node.traits) {
31084
31440
  const trait = status.traits.find((n) => n.name === traitName);
@@ -31210,8 +31566,8 @@ function build_operation_node$1(node, status) {
31210
31566
  build_node$1(node.right_value, status);
31211
31567
  status.code += `); })`;
31212
31568
  ns_tmp_counter++;
31213
- } else if (is_nullable_scalar_type(left_type)) {
31214
- if (node.left_value.node_type === "func_call" && is_nullable_scalar_type(node.left_value.type)) {
31569
+ } else if (is_nullable_scalar_type(left_type, status)) {
31570
+ if (node.left_value.node_type === "func_call" && is_nullable_scalar_type(node.left_value.type, status)) {
31215
31571
  const id = ns_tmp_counter++;
31216
31572
  const flag = `_nsh_${id}`;
31217
31573
  const val = `_nsv_${id}`;
@@ -31273,8 +31629,8 @@ function build_operation_node$1(node, status) {
31273
31629
  else status.code += `((`;
31274
31630
  build_node$1(nullable_side, status);
31275
31631
  status.code += `.ptr != 0))`;
31276
- } else if (is_nullable_scalar_type(type_from_value_node$1(nullable_side)) && type?.is_nullable && type.name !== "string") {
31277
- if (nullable_side.node_type === "func_call" && is_nullable_scalar_type(nullable_side.type)) {
31632
+ } else if (is_nullable_scalar_type(type_from_value_node$1(nullable_side), status) && type?.is_nullable && type.name !== "string") {
31633
+ if (nullable_side.node_type === "func_call" && is_nullable_scalar_type(nullable_side.type, status)) {
31278
31634
  const id = ns_tmp_counter++;
31279
31635
  const flag = `_nsh_${id}`;
31280
31636
  const val = `_nsv_${id}`;
@@ -31438,7 +31794,7 @@ function is_null_comparison(node) {
31438
31794
  }
31439
31795
  function is_nullable_struct_side(node, status) {
31440
31796
  const t = type_from_value_node$1(node);
31441
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
31797
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
31442
31798
  }
31443
31799
  /**
31444
31800
  * Build the companion `_has` flag expression for a nullable-struct or
@@ -31456,8 +31812,8 @@ function is_nullable_struct_side(node, status) {
31456
31812
  function build_nullable_has(node, status) {
31457
31813
  if (node.node_type === "value") {
31458
31814
  const name = node.value;
31459
- 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);
31460
- if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type)) return has_flag_name(name);
31815
+ 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);
31816
+ if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status)) return has_flag_name(name);
31461
31817
  }
31462
31818
  const saved = begin_code_scratch(status);
31463
31819
  build_node$1(node, status);
@@ -33122,7 +33478,7 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
33122
33478
  if (rhs_is_null) status.code += `${flag} = 0`;
33123
33479
  else {
33124
33480
  const call_ret = call_result_type(node.right_value);
33125
- if (!!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret))) {
33481
+ if (!!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret, status))) {
33126
33482
  const old = status.current_nullable_call_flag;
33127
33483
  status.current_nullable_call_flag = flag;
33128
33484
  status.code += `${lhs_expr} = `;
@@ -33592,11 +33948,11 @@ function lhs_nullable_struct_type(node, status) {
33592
33948
  if (node.left_value.node_type === "value") {
33593
33949
  const name = node.left_value.value;
33594
33950
  const t = status.scoped_declarations.find((d) => d.name === name)?.type || status.variable_types?.get(name);
33595
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
33951
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
33596
33952
  }
33597
33953
  if (node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field") {
33598
33954
  const field_type = node.left_value.access.type;
33599
- return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type);
33955
+ return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type, status);
33600
33956
  }
33601
33957
  return false;
33602
33958
  }
@@ -33609,7 +33965,7 @@ function lhs_nullable_struct_type(node, status) {
33609
33965
  function nullable_value_flag_expr(value, status) {
33610
33966
  const t = type_from_value_node$1(value);
33611
33967
  if (!t?.is_nullable) return void 0;
33612
- if (!is_nullable_struct_type(t, status) && !is_nullable_scalar_type(t)) return void 0;
33968
+ if (!is_nullable_struct_type(t, status) && !is_nullable_scalar_type(t, status)) return void 0;
33613
33969
  if (value.node_type === "value") return has_flag_name(value.value);
33614
33970
  if (value.node_type === "access" && value.access.node_type === "access_field") {
33615
33971
  const saved = begin_code_scratch(status);
@@ -34340,32 +34696,37 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
34340
34696
  status.stack_array_lengths.set(safe_name, len_text);
34341
34697
  }
34342
34698
  }
34343
- if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type)) {
34699
+ if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status)) {
34344
34700
  const flag = has_flag_name(safe_name);
34345
34701
  const is_null = !node.value || node.value.node_type === "value" && node.value.value === "null";
34346
- if (!status.current_function && is_nullable_scalar_type(node.type)) {
34347
- status.code += `;\nunsigned char ${flag} = ${is_null ? 0 : 1}`;
34348
- if (is_null) return;
34349
- } else {
34350
- status.code += `;\nunsigned char ${flag} = 0`;
34351
- if (node.value && !is_null) {
34352
- const call_ret = call_result_type(node.value);
34353
- const value_is_nullable_call = !!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret));
34354
- status.code += `;\n${safe_name} = `;
34355
- if (value_is_nullable_call) {
34356
- const old = status.current_nullable_call_flag;
34357
- status.current_nullable_call_flag = flag;
34358
- emit_init_value(node.value, nir_init, status);
34359
- status.current_nullable_call_flag = old;
34360
- record_call_init_string_fields(node, status);
34361
- } else {
34362
- const rhs_flag = nullable_value_flag_expr(node.value, status);
34363
- emit_init_value(node.value, nir_init, status);
34364
- status.code += `;\n${flag} = ${rhs_flag ?? "1"}`;
34365
- }
34702
+ if (!status.current_function && is_nullable_scalar_type(node.type, status)) {
34703
+ if (is_null) {
34704
+ status.code += `;\nunsigned char ${flag} = 0`;
34705
+ return;
34366
34706
  }
34707
+ status.code += ` = `;
34708
+ emit_init_value(node.value, nir_init, status);
34709
+ status.code += `;\nunsigned char ${flag} = 1`;
34367
34710
  return;
34368
34711
  }
34712
+ status.code += `;\nunsigned char ${flag} = 0`;
34713
+ if (node.value && !is_null) {
34714
+ const call_ret = call_result_type(node.value);
34715
+ const value_is_nullable_call = !!call_ret && (is_nullable_struct_type(call_ret, status) || is_nullable_scalar_type(call_ret, status));
34716
+ status.code += `;\n${safe_name} = `;
34717
+ if (value_is_nullable_call) {
34718
+ const old = status.current_nullable_call_flag;
34719
+ status.current_nullable_call_flag = flag;
34720
+ emit_init_value(node.value, nir_init, status);
34721
+ status.current_nullable_call_flag = old;
34722
+ record_call_init_string_fields(node, status);
34723
+ } else {
34724
+ const rhs_flag = nullable_value_flag_expr(node.value, status);
34725
+ emit_init_value(node.value, nir_init, status);
34726
+ status.code += `;\n${flag} = ${rhs_flag ?? "1"}`;
34727
+ }
34728
+ }
34729
+ return;
34369
34730
  }
34370
34731
  if (node.value) {
34371
34732
  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");
@@ -34790,6 +35151,7 @@ function build_if_else_node(node, status, nir) {
34790
35151
  status.code += `) {\n`;
34791
35152
  let then_fields;
34792
35153
  if (node.if_branch) {
35154
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
34793
35155
  build_block_with_cursor(node.if_branch, nir?.then_branch, status);
34794
35156
  build_auto_free(status);
34795
35157
  }
@@ -34798,7 +35160,7 @@ function build_if_else_node(node, status, nir) {
34798
35160
  if (node.else_branch) {
34799
35161
  status.scoped_declarations = enter_c_scope(status);
34800
35162
  status.deferred_frees = [];
34801
- status.heap_string_fields = pre_heap_string_fields;
35163
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
34802
35164
  status.code += `} else {\n`;
34803
35165
  build_block_with_cursor(node.else_branch, nir?.else_branch, status);
34804
35166
  build_auto_free(status);
@@ -34982,7 +35344,7 @@ function build_return_node(node, status, nir_value) {
34982
35344
  emit_nursery_joins_on_return_c(status);
34983
35345
  reclaim_all_c_scopes(status);
34984
35346
  status.code += `*${ret_has} = 0;\n`;
34985
- 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`;
35347
+ 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`;
34986
35348
  else status.code += `return (struct ${status.function_return_type.name}){0};\n`;
34987
35349
  return;
34988
35350
  }
@@ -35215,7 +35577,7 @@ function nullable_flag_expr_of_value(value, status) {
35215
35577
  if (!value) return void 0;
35216
35578
  const t = type_from_value_node$1(value);
35217
35579
  if (!t?.is_nullable) return void 0;
35218
- if (!(is_nullable_struct_type(t, status) || is_nullable_scalar_type(t))) return void 0;
35580
+ if (!(is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status))) return void 0;
35219
35581
  if (value.node_type === "value") return has_flag_name(value.value);
35220
35582
  if (value.node_type === "access") {
35221
35583
  const saved = begin_code_scratch(status);
@@ -35232,7 +35594,7 @@ function build_switch_node(node, status, nir) {
35232
35594
  for (let i = 0; i < node.cases.length; i++) {
35233
35595
  const c = node.cases[i];
35234
35596
  status.scoped_declarations = enter_c_scope(status);
35235
- status.heap_string_fields = pre_heap_string_fields;
35597
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
35236
35598
  const saved_code = begin_code_scratch(status);
35237
35599
  build_node$1(c.condition, status);
35238
35600
  let cond_code = end_code_scratch(status, saved_code);
@@ -35278,7 +35640,7 @@ function build_switch_node(node, status, nir) {
35278
35640
  if (node.else_branch) {
35279
35641
  status.code += "} else ";
35280
35642
  status.scoped_declarations = enter_c_scope(status);
35281
- status.heap_string_fields = pre_heap_string_fields;
35643
+ status.heap_string_fields = new Set(pre_heap_string_fields ?? []);
35282
35644
  status.code += `{\n`;
35283
35645
  build_block_with_cursor(node.else_branch, nir?.otherwise ?? void 0, status);
35284
35646
  build_auto_free(status);
@@ -35657,20 +36019,80 @@ function has_owning_fields(node, status) {
35657
36019
  }
35658
36020
  /**
35659
36021
  * The names of Buffer methods that get specialized for owning value structs.
35660
- * load_T and move_T are NOT specialized: load_T returns a shallow copy (a
35661
- * borrow — the caller's struct local is not destroyed, so no double-free);
35662
- * move_T transfers ownership (the slot is zeroed, the caller takes the
35663
- * pointers).
36022
+ * load_T IS specialized when the element has owning container fields (a
36023
+ * `List<T>` member): the returned copy must own independent containers, or
36024
+ * the caller's scope-exit destroy would free the slot's buffer (double-free
36025
+ * at container teardown). String fields stay BORROWED on load (struct-local
36026
+ * teardown only frees RECORDED heap strings, so strdup'ing them would leak);
36027
+ * container fields have no such borrow convention — a struct local's
36028
+ * container fields are ALWAYS destroyed — so they must be deep-copied.
36029
+ * move_T is NOT specialized: it transfers ownership (the slot is zeroed, the
36030
+ * caller takes the pointers).
35664
36031
  */
35665
36032
  const OWNING_BUFFER_METHODS = /* @__PURE__ */ new Set([
35666
36033
  "store",
35667
36034
  "replace",
36035
+ "load",
35668
36036
  "modify",
35669
36037
  "shift",
35670
36038
  "destroy",
35671
36039
  "#destroy"
35672
36040
  ]);
35673
36041
  /**
36042
+ * Emit the transfer half of owning store_T/replace_T: the slot bytes were
36043
+ * already memcpy'd from the source, so zeroing each owned CONTAINER field
36044
+ * of the SOURCE completes the move — the slot is the sole owner, and the
36045
+ * source's own teardown (when it still runs: an unstamped generic-body
36046
+ * local) finds zeroed fields and reclaims nothing. Nested value struct
36047
+ * fields recurse so their container leaves are zeroed at their own paths;
36048
+ * their string fields are untouched (the slot aliases them — the pre-existing
36049
+ * nested-string convention). Class/enum payload fields are left alone
36050
+ * (recorded in FOLLOWUP.md).
36051
+ */
36052
+ function emit_transfer_owning_fields(elem, src, status, prefix = "") {
36053
+ for (const field of elem.fields) {
36054
+ if (field.type.is_ref || field.type.is_view) continue;
36055
+ if (field.type.is_array || field.type.is_nullable) continue;
36056
+ const field_struct = resolve_struct_type(field.type, status);
36057
+ if (!field_struct || field_struct.is_class) continue;
36058
+ const src_field = `${prefix}${src}${arrow(src)}${field.name}`;
36059
+ if (has_destroy(field_struct)) status.code += `memset((void*)&${src_field}, 0, sizeof(${src_field}));\n`;
36060
+ else if (struct_needs_destroy(field_struct, status)) emit_transfer_owning_fields(field_struct, src_field, status, "");
36061
+ }
36062
+ }
36063
+ /**
36064
+ * Emit the deep-copy half of owning load_T: for each field whose type has a
36065
+ * monomorphized `copy` helper that yields an independent value (a `List<T>`
36066
+ * /`Buffer<T>`/`Map`/`Set` field — `<Mono>_copy` deep-copies the slots or
36067
+ * member buffers — or an enum-with-data field whose string payloads
36068
+ * `<E>_copy` duplicates), overwrite the shallow-copied bytes with an owned
36069
+ * copy. A struct local's CONTAINER fields are reclaimed unconditionally at
36070
+ * scope exit, so a shallow return would double-free the slot's slab. Fields
36071
+ * without a copyable shape (class fields, nested owning user structs without
36072
+ * a synthesized copy) stay shallow — recorded in FOLLOWUP.md.
36073
+ */
36074
+ function emit_load_deep_copy_fields(elem, dst, status) {
36075
+ for (const field of elem.fields) {
36076
+ if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
36077
+ if (field.type.is_nullable) continue;
36078
+ if (field.type.name === "string") continue;
36079
+ const field_struct = resolve_struct_type(field.type, status);
36080
+ if (field_struct && !field_struct.is_class && is_load_deep_copy_container(field_struct.name) && struct_needs_destroy(field_struct, status) && field_struct.functions.find((f) => f.name === "copy")) {
36081
+ status.code += `${dst}.${field.name} = ${field_struct.name}_copy(&${dst}.${field.name});\n`;
36082
+ continue;
36083
+ }
36084
+ const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
36085
+ if (field_enum && enum_copy_is_deep(field_enum, status)) status.code += `${dst}.${field.name} = ${field_enum.name}_copy(${dst}.${field.name});\n`;
36086
+ }
36087
+ }
36088
+ /** Whether `<E>_copy` yields a fully independent value: no case payload is a
36089
+ * class/trait reference (those are shared pointers the copy cannot
36090
+ * duplicate — mirroring owning_buffer_enum_element's gate). */
36091
+ function enum_copy_is_deep(elem, status) {
36092
+ 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;
36093
+ return true;
36094
+ }
36095
+ /**
35674
36096
  * A `Buffer<string>` owns an independent heap copy of each slot (strdup on
35675
36097
  * store_T, free+strdup on replace_T, per-slot free on #destroy). The element
35676
36098
  * is a primitive, so this is a separate path from the value-struct
@@ -35833,6 +36255,13 @@ function emit_owning_buffer_body(func_name, elem, status) {
35833
36255
  status.code += `struct ${elem.name} _old = _slots[i];\n`;
35834
36256
  status.code += `_slots[i] = (*val);\n`;
35835
36257
  emit_deep_copy_fields(elem, "_slots[i]", "val", status, "_old");
36258
+ emit_transfer_owning_fields(elem, "val", status);
36259
+ return true;
36260
+ }
36261
+ if (func_name === "load") {
36262
+ status.code += `struct ${elem.name} _loaded = ((struct ${elem.name} *)(unsigned long long)self->data)[i];\n`;
36263
+ emit_load_deep_copy_fields(elem, "_loaded", status);
36264
+ status.code += `return _loaded;\n`;
35836
36265
  return true;
35837
36266
  }
35838
36267
  if (func_name === "modify") {
@@ -35847,6 +36276,7 @@ function emit_owning_buffer_body(func_name, elem, status) {
35847
36276
  status.code += `${elem.name}_destroy(&_slots[i]);\n`;
35848
36277
  status.code += `_slots[i] = (*val);\n`;
35849
36278
  emit_deep_copy_fields(elem, "_slots[i]", "val", status);
36279
+ emit_transfer_owning_fields(elem, "val", status);
35850
36280
  return true;
35851
36281
  }
35852
36282
  if (func_name === "shift") {
@@ -35873,12 +36303,13 @@ function emit_owning_buffer_body(func_name, elem, status) {
35873
36303
  return false;
35874
36304
  }
35875
36305
  /**
35876
- * For each string field on the element struct, overwrite the shallow-copied
35877
- * pointer in `dst` with a fresh strdup of `src`'s field. This breaks the
35878
- * pointer aliasing between the source and the slot so each can be destroyed
35879
- * independently. For nested owning value struct fields, recursively call the
35880
- * nested struct's destroy + deep-copy (the nested destroy frees the
35881
- * shallow-copied owned sub-fields, then the deep-copy re-strdup's them).
36306
+ * For each DIRECT string field on the element struct, overwrite the
36307
+ * shallow-copied pointer in `dst` with a fresh strdup of `src`'s field. This
36308
+ * breaks the pointer aliasing between the source and the slot so each can be
36309
+ * destroyed independently. Nested value struct fields are NOT walked: their
36310
+ * string fields stay aliased (the pre-existing convention — a spliced
36311
+ * `move` donor has no teardown, so the slot is the sole owner), and their
36312
+ * container leaves are transferred by `emit_transfer_owning_fields`.
35882
36313
  *
35883
36314
  * `old_expr` (optional) is the slot's PRE-copy value expression. When the
35884
36315
  * source field pointer equals the old slot field pointer, the slot already
@@ -35896,12 +36327,6 @@ function emit_deep_copy_fields(elem, dst, src, status, old_expr) {
35896
36327
  const src_field = `${src}${arrow(src)}${field.name}`;
35897
36328
  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`;
35898
36329
  else status.code += `${dst}.${field.name} = ${src_field}.ptr ? nomen_str_dup(${src_field}) : ${src_field};\n`;
35899
- } else if (field.type.name && !field.type.is_array) {
35900
- const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_generic);
35901
- if (field_struct && !field_struct.is_class && struct_needs_destroy(field_struct, status)) {
35902
- status.code += `${field_struct.name}_destroy(&${dst}.${field.name});\n`;
35903
- emit_deep_copy_fields(field_struct, `${dst}.${field.name}`, `${src}${arrow(src)}${field.name}`, status);
35904
- }
35905
36330
  }
35906
36331
  }
35907
36332
  }
@@ -36049,7 +36474,7 @@ function build_struct_node(node, status) {
36049
36474
  const ctor_return = is_class ? `struct ${node.name}*` : `struct ${node.name}`;
36050
36475
  const field_params = node.fields.filter((f) => f.value == null).map((f) => {
36051
36476
  let decl = c_param_decl(f.type, f.name, status);
36052
- if (is_nullable_struct_type(f.type, status) || is_nullable_scalar_type(f.type)) decl += `, unsigned char ${has_flag_name(f.name)}`;
36477
+ if (is_nullable_struct_type(f.type, status) || is_nullable_scalar_type(f.type, status)) decl += `, unsigned char ${has_flag_name(f.name)}`;
36053
36478
  return decl;
36054
36479
  }).join(", ");
36055
36480
  const ctor_sig = (init) => {
@@ -36060,7 +36485,7 @@ function build_struct_node(node, status) {
36060
36485
  is_ref: p.is_ref || p.type.is_ref,
36061
36486
  declaration: p.declaration
36062
36487
  });
36063
- 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)}`;
36488
+ 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)}`;
36064
36489
  return decl;
36065
36490
  }).join(", ");
36066
36491
  const label = is_overloaded(node, "#init") ? mangled_label(init, node.name) : `${node.name}_init`;
@@ -36088,7 +36513,7 @@ function build_struct_node(node, status) {
36088
36513
  build_node$1(field.value, status);
36089
36514
  status.code += `; self${accessor}${field.name} = (nomen_view){ _p.ptr, _p.len }; }\n`;
36090
36515
  } else status.code += `self${accessor}${field.name} = (nomen_view){0};\n`;
36091
- } else if (is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type)) {
36516
+ } else if (is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) {
36092
36517
  if (field.value.node_type === "value" && field.value.value === "null") status.code += `self${accessor}${has_flag_name(field.name)} = 0;\n`;
36093
36518
  else {
36094
36519
  status.code += `self${accessor}${field.name} = `;
@@ -36166,7 +36591,7 @@ function build_struct_node(node, status) {
36166
36591
  status.code += `; ${object_name}${accessor}${field.name} = (nomen_view){ _p.ptr, _p.len }; }\n`;
36167
36592
  } else status.code += `${object_name}${accessor}${field.name} = (nomen_view){0};\n`;
36168
36593
  } 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`;
36169
- else if ((is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type)) && field.value) {
36594
+ else if ((is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) && field.value) {
36170
36595
  if (field.value.node_type === "value" && field.value.value === "null") status.code += `${object_name}${accessor}${has_flag_name(field.name)} = 0;\n`;
36171
36596
  else {
36172
36597
  status.code += `${object_name}${accessor}${field.name} = `;
@@ -36176,7 +36601,7 @@ function build_struct_node(node, status) {
36176
36601
  } else if (is_nullable_struct_type(field.type, status)) {
36177
36602
  status.code += `${object_name}${accessor}${field.name} = *${field.name};\n`;
36178
36603
  status.code += `${object_name}${accessor}${has_flag_name(field.name)} = ${has_flag_name(field.name)};\n`;
36179
- } else if (is_nullable_scalar_type(field.type)) {
36604
+ } else if (is_nullable_scalar_type(field.type, status)) {
36180
36605
  status.code += `${object_name}${accessor}${field.name} = ${field.name};\n`;
36181
36606
  status.code += `${object_name}${accessor}${has_flag_name(field.name)} = ${has_flag_name(field.name)};\n`;
36182
36607
  } else if (!field.type.is_ref && !field.type.is_view && status.enums.find((e) => e.name === field.type.name && e.has_associated_data)) {
@@ -36314,7 +36739,7 @@ function build_struct_functions(node, status, skip_init = false) {
36314
36739
  status.force_heap_strings = scan_force_heap_strings(func.statements ?? [], status.structs);
36315
36740
  status.function_return_type = func.return_type;
36316
36741
  const old_nullable_ret_has = status.nullable_ret_has_param;
36317
- if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type)) status.nullable_ret_has_param = "_ret_has";
36742
+ if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type, status)) status.nullable_ret_has_param = "_ret_has";
36318
36743
  else status.nullable_ret_has_param = void 0;
36319
36744
  const self_param = func.params[0]?.is_self_param ? func.params[0] : null;
36320
36745
  status.self_is_ref = !!self_param?.is_ref || self_param?.declaration === "var";
@@ -36371,9 +36796,9 @@ function build_struct_functions(node, status, skip_init = false) {
36371
36796
  for (let i = 0; i < func.params.length; i++) {
36372
36797
  if (i > 0) status.code += ", ";
36373
36798
  build_parameter_node(func.params[i], status);
36374
- 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))}`;
36799
+ 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))}`;
36375
36800
  }
36376
- if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type)) {
36801
+ if (is_nullable_struct_type(func.return_type, status) || is_nullable_scalar_type(func.return_type, status)) {
36377
36802
  if (func.params.length > 0) status.code += `, `;
36378
36803
  status.code += `unsigned char *_ret_has`;
36379
36804
  }
@@ -37134,9 +37559,9 @@ function build_function_node(node, status) {
37134
37559
  first_param = false;
37135
37560
  if (node.params[i].is_variadic) status.code += `long _${node.params[i].name}_len, `;
37136
37561
  build_parameter_node(node.params[i], status);
37137
- 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))}`;
37562
+ 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))}`;
37138
37563
  }
37139
- if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type)) {
37564
+ if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type, status)) {
37140
37565
  if (!first_param) status.code += ", ";
37141
37566
  status.code += `unsigned char *_ret_has`;
37142
37567
  }
@@ -37189,7 +37614,7 @@ function build_function_node(node, status) {
37189
37614
  const old_variable_types = status.variable_types;
37190
37615
  status.variable_types = /* @__PURE__ */ new Map();
37191
37616
  const old_nullable_ret_has = status.nullable_ret_has_param;
37192
- if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type)) status.nullable_ret_has_param = "_ret_has";
37617
+ if (is_nullable_struct_type(node.return_type, status) || is_nullable_scalar_type(node.return_type, status)) status.nullable_ret_has_param = "_ret_has";
37193
37618
  else status.nullable_ret_has_param = void 0;
37194
37619
  const old_function_name = status.current_function_name;
37195
37620
  status.current_function_name = emission_label(node);
@@ -37246,6 +37671,7 @@ function build_function_node(node, status) {
37246
37671
  stmts: nir.body,
37247
37672
  ast: node.statements
37248
37673
  } : void 0;
37674
+ emit_ref_param_entry_dups(node, status);
37249
37675
  build_block_node(node, status, false);
37250
37676
  status.nir_emit_ctx = old_nir_ctx;
37251
37677
  status.function_ref_params = old_ref_params;
@@ -37291,6 +37717,37 @@ function build_function_node(node, status) {
37291
37717
  status.stack_array_lengths = old_stack_array_lengths;
37292
37718
  status.moved = old_moved;
37293
37719
  }
37720
+ /**
37721
+ * Emit the entry-dup for every `ref` value-struct param string field the
37722
+ * body stores through CONDITIONALLY (see ref_param_entry_dup_fields):
37723
+ * `{ field = nomen_str_dup(field) }` at function entry, recorded in
37724
+ * heap_string_fields, so the field is heap-owned on every path out of the
37725
+ * function and the call-site record transfer is sound. Nullable string
37726
+ * fields guard the dup (a null pair stores as null — free(NULL) is a
37727
+ * no-op on every reclaim path).
37728
+ */
37729
+ function emit_ref_param_entry_dups(node, status) {
37730
+ const dups = ref_param_entry_dup_fields(node, status.structs);
37731
+ if (!dups.size) return;
37732
+ for (const param of node.params ?? []) {
37733
+ if (param.is_self_param) continue;
37734
+ if (!(param.is_ref || param.type.is_ref)) continue;
37735
+ const fields = dups.get(param.name);
37736
+ if (!fields?.size) continue;
37737
+ const struct = status.structs.find((s) => s.name === param.type.name && !s.is_simple_type && !s.is_class);
37738
+ const base = c_function_name(param.name);
37739
+ for (const field of fields) {
37740
+ const nullable = !!struct?.fields.find((f) => f.name === field)?.type.is_nullable;
37741
+ if (nullable) status.code += `if (${base}->${field}.ptr) {\n`;
37742
+ const temp = `_dup_t_${status.label_counter = (status.label_counter ?? 0) + 1}`;
37743
+ status.code += `nomen_string ${temp} = nomen_str_dup(${base}->${field});\n`;
37744
+ status.code += `${base}->${field} = ${temp};\n`;
37745
+ if (nullable) status.code += `}\n`;
37746
+ if (!status.heap_string_fields) status.heap_string_fields = /* @__PURE__ */ new Set();
37747
+ status.heap_string_fields.add(`${param.name}.${field}`);
37748
+ }
37749
+ }
37750
+ }
37294
37751
  function emit_nested_declarations(node, status) {
37295
37752
  const block = node;
37296
37753
  for (let child of block.statements) switch (child.node_type) {
@@ -37576,11 +38033,22 @@ function build_function_call_node(node, status) {
37576
38033
  const lambda_ret_c = lambda_arg_temps.size > 0 ? c_return_type(node.type, status) : void 0;
37577
38034
  const lambda_ret_tmp = lambda_ret_c && lambda_ret_c !== "void" ? next_lambda_ret_temp() : void 0;
37578
38035
  if (lambda_ret_tmp) status.code += `${lambda_ret_c} ${lambda_ret_tmp} = `;
37579
- 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;
38036
+ 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;
37580
38037
  let wrap_tmp;
37581
38038
  status.code += `${func_name}(${is_closure_callee ? "NULL" : ""}`;
37582
38039
  if (is_closure_callee && node.params.length > 0) status.code += ", ";
37583
38040
  const variadic_idx = node.variadic_param_name ? node.params.findIndex((p) => p.node_type === "array") : -1;
38041
+ if (!node.variadic_param_name) {
38042
+ const resolved = node.resolved_function ?? (is_struct ? status.structs.find((s) => s.name === node.name)?.functions.find((f) => f.name === "#init") : void 0);
38043
+ const variadic_param = resolved?.params?.find((p) => p.is_variadic);
38044
+ if (resolved && variadic_param) {
38045
+ if (node.params.length === 1 && node.params[0].node_type === "array") node.variadic_param_name = variadic_param.name;
38046
+ else if (node.params.length === 0 && resolved.params.filter((p) => !p.is_self_param && !p.is_variadic).length === 0) {
38047
+ const elem_c = c_type(variadic_param.type.name);
38048
+ status.code += `0, (${elem_c}[]){ }`;
38049
+ }
38050
+ }
38051
+ }
37584
38052
  for (let i = 0; i < node.params.length; i++) {
37585
38053
  if (i > 0) status.code += ", ";
37586
38054
  if (i === variadic_idx && node.params[i].node_type === "array") {
@@ -37617,7 +38085,7 @@ function build_function_call_node(node, status) {
37617
38085
  }
37618
38086
  if (node.nullable_param_indices?.includes(i) && node.params[i].node_type === "value" && node.params[i].value === "null") {
37619
38087
  const callee_null_param_type = callee_params?.[i]?.type ?? param_type;
37620
- if (is_nullable_scalar_type(callee_null_param_type)) status.code += `0, 0`;
38088
+ if (is_nullable_scalar_type(callee_null_param_type, status)) status.code += `0, 0`;
37621
38089
  else status.code += `(void *)&(struct ${mono_type_name(callee_null_param_type)}){0}, 0`;
37622
38090
  continue;
37623
38091
  }
@@ -37703,7 +38171,7 @@ function build_function_call_node(node, status) {
37703
38171
  emit_nullable_arg_flag(node.params[i], status);
37704
38172
  }
37705
38173
  }
37706
- if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type)) {
38174
+ if (is_nullable_struct_type(node.type, status) || is_nullable_scalar_type(node.type, status)) {
37707
38175
  const flag_name = status.current_nullable_call_flag;
37708
38176
  const arg_list_empty = node.params.length === 0 && !is_closure_callee;
37709
38177
  if (flag_name) status.code += `${arg_list_empty ? "" : ", "}&${flag_name}`;
@@ -37812,13 +38280,13 @@ function emit_nullable_arg_flag(arg, status) {
37812
38280
  }
37813
38281
  if (arg.node_type === "value") {
37814
38282
  const vn = arg;
37815
- if (is_nullable_struct_type(vn.type, status) || is_nullable_scalar_type(vn.type)) status.code += `${has_flag_name(vn.value)}`;
38283
+ if (is_nullable_struct_type(vn.type, status) || is_nullable_scalar_type(vn.type, status)) status.code += `${has_flag_name(vn.value)}`;
37816
38284
  else status.code += `1`;
37817
38285
  return;
37818
38286
  }
37819
38287
  if (arg.node_type === "access" && arg.access.node_type === "access_field") {
37820
38288
  const t = type_from_value_node$1(arg);
37821
- if (is_nullable_struct_type(t, status) || is_nullable_scalar_type(t)) {
38289
+ if (is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status)) {
37822
38290
  const saved = begin_code_scratch(status);
37823
38291
  build_node$1(arg, status);
37824
38292
  const expr = end_code_scratch(status, saved);
@@ -38620,13 +39088,13 @@ function build_access_node$1(node, status) {
38620
39088
  build_node$1(access_func.params[i], status);
38621
39089
  status.suppress_dereference = false;
38622
39090
  }
38623
- if (target_param && !target_param.is_self_param && (is_nullable_struct_type(target_param.type, status) || is_nullable_scalar_type(target_param.type))) {
39091
+ if (target_param && !target_param.is_self_param && (is_nullable_struct_type(target_param.type, status) || is_nullable_scalar_type(target_param.type, status))) {
38624
39092
  status.code += `, `;
38625
39093
  emit_nullable_arg_flag(access_func.params[i], status);
38626
39094
  }
38627
39095
  }
38628
39096
  const method_ret_type = (target_method ?? trait_default_func)?.return_type ?? access_func.type;
38629
- if (is_nullable_struct_type(method_ret_type, status) || is_nullable_scalar_type(method_ret_type)) {
39097
+ if (is_nullable_struct_type(method_ret_type, status) || is_nullable_scalar_type(method_ret_type, status)) {
38630
39098
  const flag_name = status.current_nullable_call_flag;
38631
39099
  status.code += `, `;
38632
39100
  status.code += flag_name ? `&${flag_name}` : `(unsigned char *)&(unsigned char){0}`;
@@ -40371,11 +40839,11 @@ function is_nullable_struct_assignment(node, status) {
40371
40839
  if (node.left_value.node_type === "value") {
40372
40840
  const name = node.left_value.value;
40373
40841
  const t = status.scoped_declarations.find((d) => d.name === name)?.type || status.variable_types?.get(name);
40374
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
40842
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
40375
40843
  }
40376
40844
  if (node.left_value.node_type === "access" && node.left_value.access.node_type === "access_field") {
40377
40845
  const field_type = node.left_value.access.type;
40378
- return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type);
40846
+ return is_nullable_struct_type(field_type, status) || is_nullable_scalar_type(field_type, status);
40379
40847
  }
40380
40848
  return false;
40381
40849
  }
@@ -40430,7 +40898,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
40430
40898
  const flag_name = has_flag_name(name);
40431
40899
  const decl_type = status.scoped_declarations.find((d) => d.name === name)?.type || status.variable_types?.get(name);
40432
40900
  const type_name = decl_type?.name || "";
40433
- const is_scalar = is_nullable_scalar_type(decl_type);
40901
+ const is_scalar = is_nullable_scalar_type(decl_type, status);
40434
40902
  const scalar_size = is_scalar ? aarch64_size(type_name) : 0;
40435
40903
  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}.`));
40436
40904
  if (rhs_is_null) {
@@ -40449,9 +40917,11 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
40449
40917
  }
40450
40918
  }
40451
40919
  };
40452
- 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));
40920
+ 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));
40453
40921
  const rhs_value_type = call_result_type(node.right_value) ?? type_from_value_node$1(node.right_value);
40454
- 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))) {
40922
+ 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));
40923
+ 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";
40924
+ if (value_is_nullable_access && !rhs_is_field_read) {
40455
40925
  if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
40456
40926
  retarget_records();
40457
40927
  emit_rhs_value(node.right_value, nir_rhs, status);
@@ -40468,6 +40938,17 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
40468
40938
  emit_var_store(status, "x9", flag_name, 8);
40469
40939
  return;
40470
40940
  }
40941
+ if (rhs_is_field_read) {
40942
+ if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
40943
+ retarget_records();
40944
+ emit_rhs_value(node.right_value, nir_rhs, status);
40945
+ ensure_newline(status);
40946
+ emit_var_store(status, "x0", name, scalar_size);
40947
+ load_nullable_has(node.right_value, "x9", status);
40948
+ ensure_newline(status);
40949
+ emit_var_store(status, "x9", flag_name, 8);
40950
+ return;
40951
+ }
40471
40952
  if (value_is_nullable_call) {
40472
40953
  if (reclaimable) emit_recorded_field_reclaim_for_slot(status, name, type_name);
40473
40954
  retarget_records();
@@ -40484,7 +40965,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
40484
40965
  status.call_x8_preset = old_preset;
40485
40966
  return;
40486
40967
  }
40487
- 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));
40968
+ 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));
40488
40969
  let rhs_flag_slot;
40489
40970
  if (rhs_is_nullable_value) {
40490
40971
  load_nullable_has(node.right_value, "x9", status);
@@ -40521,7 +41002,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
40521
41002
  const field_offset = get_field_offset(target_type.name, field_name, status);
40522
41003
  const has_offset = get_field_has_offset(target_type.name, field_name, status);
40523
41004
  const struct_size = get_struct_size(type_name, status);
40524
- const field_is_scalar = is_nullable_scalar_type(field_access.type);
41005
+ const field_is_scalar = is_nullable_scalar_type(field_access.type, status);
40525
41006
  get_source_address(access.target, status);
40526
41007
  ensure_newline(status);
40527
41008
  emit_asm(status, `str x0, [sp, #-16]!\n`);
@@ -40532,7 +41013,7 @@ function build_nullable_struct_assignment(node, status, nir_rhs) {
40532
41013
  return;
40533
41014
  }
40534
41015
  const rhs_t = call_result_type(node.right_value) ?? type_from_value_node$1(node.right_value);
40535
- const rhs_is_nullable_value = !!rhs_t?.is_nullable && (is_nullable_struct_type(rhs_t, status) || is_nullable_scalar_type(rhs_t));
41016
+ const rhs_is_nullable_value = !!rhs_t?.is_nullable && (is_nullable_struct_type(rhs_t, status) || is_nullable_scalar_type(rhs_t, status));
40536
41017
  let rhs_flag_slot;
40537
41018
  if (rhs_is_nullable_value) {
40538
41019
  load_nullable_has(node.right_value, "x10", status);
@@ -41175,7 +41656,11 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
41175
41656
  if (name === "self" && status.current_struct) target_type = new Type(status.current_struct.name);
41176
41657
  else if (status.variable_types?.has(name)) target_type = status.variable_types.get(name);
41177
41658
  }
41178
- const field_type = access.access.type;
41659
+ let field_type = access.access.type;
41660
+ if (!field_type?.name && target_type?.name) {
41661
+ const tf = status.structs.find((s) => s.name === target_type.name)?.fields.find((f) => f.name === field_name);
41662
+ if (tf) field_type = tf.type;
41663
+ }
41179
41664
  const field_is_struct = is_struct_type$2(field_type, status);
41180
41665
  if (access.target.node_type === "value") {
41181
41666
  const name = access.target.value;
@@ -42357,7 +42842,7 @@ function build_operation_node(node, status) {
42357
42842
  if (node.op === "??") {
42358
42843
  const left_t = type_from_value_node$1(node.left_value);
42359
42844
  const left_is_nullable_struct = is_nullable_struct_type(left_t, status);
42360
- const left_is_nullable_scalar = is_nullable_scalar_type(left_t);
42845
+ const left_is_nullable_scalar = is_nullable_scalar_type(left_t, status);
42361
42846
  if (left_is_nullable_scalar && node.left_value.node_type === "func_call") {
42362
42847
  build_operand(node.left_value, "x0", status);
42363
42848
  ensure_newline(status);
@@ -42409,11 +42894,11 @@ function build_operation_node(node, status) {
42409
42894
  if ((node.op === "==" || node.op === "!=") && is_null_literal$1(node.left_value) !== is_null_literal$1(node.right_value)) {
42410
42895
  const nullable_flagged_side = (n) => {
42411
42896
  const t = type_from_value_node$1(n);
42412
- return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t);
42897
+ return is_nullable_struct_type(t, status) || is_nullable_scalar_type(t, status);
42413
42898
  };
42414
42899
  const nullable_side = nullable_flagged_side(node.left_value) ? node.left_value : node.right_value;
42415
42900
  const side_type = type_from_value_node$1(nullable_side);
42416
- if (is_nullable_struct_type(side_type, status) || is_nullable_scalar_type(side_type)) {
42901
+ if (is_nullable_struct_type(side_type, status) || is_nullable_scalar_type(side_type, status)) {
42417
42902
  load_nullable_has(nullable_side, "x1", status);
42418
42903
  ensure_newline(status);
42419
42904
  emit_asm(status, `cmp x1, #0\n`);
@@ -42931,7 +43416,7 @@ function load_nullable_has(node, target_reg, status) {
42931
43416
  get_source_address(access.target, status);
42932
43417
  ensure_newline(status);
42933
43418
  if (target_reg !== "x0") emit_asm(status, `mov ${target_reg}, x0\n`);
42934
- const scalar_field = is_nullable_scalar_type(access.access.type);
43419
+ const scalar_field = is_nullable_scalar_type(access.access.type, status);
42935
43420
  const wreg = target_reg.replace("x", "w");
42936
43421
  const load_op = scalar_field ? `ldrb ${wreg}` : `ldr ${target_reg}`;
42937
43422
  if (has_off === 0) emit_asm(status, `${load_op}, [${target_reg}]\n`);
@@ -45133,7 +45618,7 @@ function build_access_method(node, access_func, status) {
45133
45618
  const access_callee_params = access_callee_method?.params?.filter((p) => !p.is_self_param);
45134
45619
  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);
45135
45620
  const method_ret_type = access_callee_method?.return_type ?? access_func.type;
45136
- const nullable_scalar_ret = is_nullable_scalar_type(method_ret_type) && !method_ret_type.is_ref;
45621
+ const nullable_scalar_ret = is_nullable_scalar_type(method_ret_type, status) && !method_ret_type.is_ref;
45137
45622
  let temp_addr = "";
45138
45623
  let temp_offset = 0;
45139
45624
  if (return_struct || nullable_scalar_ret) {
@@ -45253,7 +45738,7 @@ function build_access_method(node, access_func, status) {
45253
45738
  for (let i = 0; i < access_func.params.length; i++) {
45254
45739
  if ((access_func.ref_param_indices ?? []).includes(i)) continue;
45255
45740
  const cp = access_callee_params?.[i];
45256
- if (cp && is_nullable_scalar_type(cp.type) || is_nullable_scalar_type(access_func.params[i].type)) nullable_scalar_arg_set.add(i);
45741
+ 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);
45257
45742
  }
45258
45743
  const arg_slot = [];
45259
45744
  let total_arg_slots = 0;
@@ -45308,7 +45793,7 @@ function build_access_method(node, access_func, status) {
45308
45793
  if (arg.node_type === "value" && arg.value === "null") {
45309
45794
  emit_asm(status, `mov x0, #0\n`);
45310
45795
  emit_asm(status, `mov x1, #0\n`);
45311
- } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type)) {
45796
+ } else if (arg.node_type === "value" && is_nullable_scalar_type(arg.type, status)) {
45312
45797
  const vname = arg.value;
45313
45798
  emit_var_load(status, "x0", vname, aarch64_size(arg.type.name));
45314
45799
  emit_var_load(status, "x1", has_flag_name(vname), 8);
@@ -49572,13 +50057,33 @@ function can_class_int_coerce(target, value, status) {
49572
50057
  }
49573
50058
  //#endregion
49574
50059
  //#region ../src/check/utils/check_type_exists.ts
50060
+ /**
50061
+ * Containers whose elements live in raw T-sized slots (the `#arch` generic
50062
+ * bodies size each slot from the type name alone). A flagged-nullable
50063
+ * element type (`List<int?>`) has no per-slot null flag there, so `0`/`false`
50064
+ * would read back as null — rejected instead of silently misbehaving.
50065
+ * Element types with an in-band null (`string?`, class/trait `?`) stay legal.
50066
+ */
50067
+ const RAW_ELEMENT_CONTAINERS = /* @__PURE__ */ new Set([
50068
+ "Array",
50069
+ "Buffer",
50070
+ "List",
50071
+ "Set",
50072
+ "Map",
50073
+ "Graph",
50074
+ "LinkedList",
50075
+ "Tree",
50076
+ "Arena"
50077
+ ]);
49575
50078
  function check_type_exists(type, status, start) {
50079
+ 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);
49576
50080
  if (type.name === "tuple" && type.tuple_types?.length) {
49577
50081
  let ok = true;
49578
50082
  for (const elem of type.tuple_types) if (!check_type_exists(elem, status, start)) ok = false;
49579
50083
  return ok;
49580
50084
  }
49581
50085
  if (type.name === "anon_enum" && type.enum_cases?.length) {
50086
+ 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);
49582
50087
  let ok = true;
49583
50088
  for (const c of type.enum_cases) for (const t of c.types) if (!check_type_exists(t, status, start)) ok = false;
49584
50089
  return ok;
@@ -49595,19 +50100,26 @@ function check_type_exists(type, status, start) {
49595
50100
  }
49596
50101
  }
49597
50102
  type.name = declared.name;
50103
+ if (type.is_nullable) {
50104
+ 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);
50105
+ }
49598
50106
  } else if (!status.types.includes(type.name)) {
49599
50107
  add_error(status, `Unknown type: ${type_name(type)}`, start);
49600
50108
  return false;
49601
50109
  }
49602
50110
  if (type.tuple_types) for (const elem of type.tuple_types) check_type_exists(elem, status, start);
49603
- if (type.type_args) for (const arg of type.type_args) {
49604
- if (arg.name === "func") {
49605
- add_error(status, `func types cannot be used as type arguments ('${type.name}<...>')`, start);
49606
- continue;
50111
+ if (type.type_args) {
50112
+ if (RAW_ELEMENT_CONTAINERS.has(type.name)) {
50113
+ 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);
49607
50114
  }
49608
- check_type_exists(arg, status, start);
49609
- }
49610
- else {
50115
+ for (const arg of type.type_args) {
50116
+ if (arg.name === "func") {
50117
+ add_error(status, `func types cannot be used as type arguments ('${type.name}<...>')`, start);
50118
+ continue;
50119
+ }
50120
+ check_type_exists(arg, status, start);
50121
+ }
50122
+ } else {
49611
50123
  const struct = status.structs.findLast((s) => s.name === type.name);
49612
50124
  if (struct?.is_generic) {
49613
50125
  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);
@@ -52486,20 +52998,57 @@ function build_hash(struct, fields) {
52486
52998
  }
52487
52999
  /**
52488
53000
  * Whether `struct` is eligible for a synthesized `copy` method: a value
52489
- * struct whose only ownership is its DIRECT string fields — the same set
52490
- * the pass-by-value argument convention covers. A struct that additionally
52491
- * owns non-string heap (a resource-releasing `#destroy`, a class field, a
52492
- * nested requiring-move struct) cannot be deep-copied by "byte copy +
52493
- * strdup the strings" — its copy stays unwritten (move/swap remains the
52494
- * escape hatch) — and a struct with no string fields is already soundly
52495
- * byte-copyable, so it needs no method.
53001
+ * struct whose every field is independently deep-copyable — direct string
53002
+ * fields (strdup'd), scalars/arrays (byte copies), `List<T>` fields whose
53003
+ * element is itself deep-copyable (the library `List.copy` deep-copies slots
53004
+ * via the owning store contract), and nested copyable structs (recursing
53005
+ * through their own `copy`). Class fields, enum-with-data fields (their
53006
+ * stores transfer payload ownership), class/trait-carrying `List` elements,
53007
+ * and resource-releasing `#destroy`s cannot be deep-copied by synthesis —
53008
+ * the copy stays unwritten (move/swap remains the escape hatch) — and a
53009
+ * struct with nothing to deep-copy (no string, `List`, or nested-copyable
53010
+ * field) is already soundly byte-copyable, so it needs no method.
52496
53011
  */
52497
53012
  function struct_is_copyable(struct, status) {
52498
53013
  if (struct.is_class || struct.is_generic || struct.is_simple_type) return false;
52499
- if (direct_string_fields(struct).length === 0) return false;
52500
- if (struct_owns_non_string_heap(struct, status, /* @__PURE__ */ new Set())) return false;
53014
+ if (struct_owns_resource(struct, status, /* @__PURE__ */ new Set())) return false;
53015
+ if (!struct_needs_copy(struct, status)) return false;
52501
53016
  return struct_copy_ctor_args(struct) !== null;
52502
53017
  }
53018
+ /** Whether any field of `struct` actually needs the synthesized deep copy:
53019
+ * a direct string field, a deep-copyable `List<T>` field, or a nested
53020
+ * copyable struct field. A struct of flat values byte-copies soundly
53021
+ * without a method. */
53022
+ function struct_needs_copy(struct, status) {
53023
+ if (direct_string_fields(struct).length > 0) return true;
53024
+ for (const field of struct.fields) {
53025
+ if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
53026
+ if (field_copy_call(field.type, status)) return true;
53027
+ }
53028
+ return false;
53029
+ }
53030
+ /**
53031
+ * Whether `struct` owns a resource a synthesized copy CANNOT duplicate: a
53032
+ * `#destroy` that releases something (a raw block / extern free), or a field
53033
+ * whose type has no deep-copy story (a class instance, a `List` of
53034
+ * class/trait elements, a nested struct with such a field). Fields that DO
53035
+ * have a deep-copy story (strings, nested copyables, copyable `List`s,
53036
+ * payload-copyable enums) don't block synthesis. Mirrors the old
53037
+ * `struct_owns_non_string_heap` gate but understands container deep copies.
53038
+ */
53039
+ function struct_owns_resource(struct, status, visited) {
53040
+ if (visited.has(struct.name)) return false;
53041
+ visited.add(struct.name);
53042
+ const destroy = struct.functions.find((f) => f.name === "#destroy");
53043
+ if (destroy && destroy_releases(destroy)) return true;
53044
+ for (const field of struct.fields) {
53045
+ if (field.type.is_ref || field.type.is_view) continue;
53046
+ if (field.type.name === "string" && !field.type.is_array) continue;
53047
+ if (field.type.is_nullable) return true;
53048
+ if (!field_copyable(field.type, status)) return true;
53049
+ }
53050
+ return false;
53051
+ }
52503
53052
  /**
52504
53053
  * How a synthesized `copy` constructs the fresh value: the zero-argument
52505
53054
  * `#init` overload when one exists (auto or user), else the bodyless AUTO
@@ -52515,14 +53064,49 @@ function struct_copy_ctor_args(struct) {
52515
53064
  if (!auto) return null;
52516
53065
  return auto.params.filter((p) => !p.is_self_param).map((p) => p.name);
52517
53066
  }
52518
- /** Whether `type` names a struct that has (or will get) a `copy` method.
52519
- * Order-independent: it re-evaluates the eligibility predicate instead of
52520
- * looking for an already-synthesized method, so an outer struct nested in
52521
- * the same pre-pass resolves a later-declared field struct. */
52522
- function field_type_is_copyable(type, status) {
53067
+ /** Whether `type` has (or will get) a deep-copy story: a struct with a
53068
+ * synthesized `copy`, a `List<T>` whose element is deep-copyable (the
53069
+ * library `List.copy` deep-copies slots via the owning store contract), or
53070
+ * a primitive / plain (payload-free) enum. Order-independent: it
53071
+ * re-evaluates the eligibility predicate instead of looking for an
53072
+ * already-synthesized method, so an outer struct nested in the same
53073
+ * pre-pass resolves a later-declared field struct. */
53074
+ function field_copyable(type, status) {
52523
53075
  if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
53076
+ if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
53077
+ const nested_struct = status.structs.find((s) => s.name === type.name);
53078
+ if (nested_struct) {
53079
+ if (nested_struct.is_simple_type) return true;
53080
+ return struct_is_copyable(nested_struct, status);
53081
+ }
53082
+ const en = status.enums.find((e) => e.name === type.name);
53083
+ if (en) return !en.has_associated_data;
53084
+ return !status.traits.some((t) => t.name === type.name);
53085
+ }
53086
+ /** Whether a `List<T>` element type is deep-copyable (see
53087
+ * `field_copyable`). */
53088
+ function element_copyable(type, status) {
53089
+ if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
53090
+ if (type.name === "string") return true;
53091
+ if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
52524
53092
  const nested = status.structs.find((s) => s.name === type.name);
52525
- return !!nested && struct_is_copyable(nested, status);
53093
+ if (nested) {
53094
+ if (nested.is_simple_type) return true;
53095
+ return !nested.is_class && struct_is_copyable(nested, status);
53096
+ }
53097
+ const en = status.enums.find((e) => e.name === type.name);
53098
+ if (en) return !en.has_associated_data;
53099
+ return !status.traits.some((t) => t.name === type.name);
53100
+ }
53101
+ /** Whether `build_copy` must route the field through a `.copy()` call (a
53102
+ * nested copyable struct, or a `List<T>` field): everything else byte
53103
+ * copies through plain field assignment (strings strdup via the field-write
53104
+ * path, primitives/plain enums are flat values). */
53105
+ function field_copy_call(type, status) {
53106
+ if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
53107
+ if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
53108
+ const nested_struct = status.structs.find((s) => s.name === type.name);
53109
+ return !!nested_struct && !nested_struct.is_simple_type && struct_is_copyable(nested_struct, status);
52526
53110
  }
52527
53111
  /**
52528
53112
  * Synthesize `pub func copy(self) -> Struct` for owning value structs: a
@@ -52538,8 +53122,8 @@ function build_copy(struct, status) {
52538
53122
  const ctor = new FunctionCallNode(-1, struct.name, new Type(struct.name), ctor_args.map((name) => field_access("self", name)));
52539
53123
  const statements = [new DeclarationNode(-1, "private", "var", "c", new Type(struct.name), ctor)];
52540
53124
  for (const field of struct.fields) {
52541
- const right = field_type_is_copyable(field.type, status) ? method_call(field_access("self", field.name), "copy") : field_access("self", field.name);
52542
- statements.push(new AssignmentNode(-1, field_access("c", field.name), right, "="));
53125
+ const right = field_copy_call(field.type, status) ? method_call(field_access("self", field.name), "copy") : field_access("self", field.name);
53126
+ statements.push(new AssignmentNode(-1, field_access("c", field.name), right));
52543
53127
  }
52544
53128
  statements.push(new ReturnNode(-1, new ValueNode(-1, "c")));
52545
53129
  return new FunctionNode(-1, "pub", "copy", new Type(struct.name), [self_param(struct)], statements);
@@ -53618,7 +54202,10 @@ function monomorphize(generic_struct, type_args, status) {
53618
54202
  mono_field.type = constructed;
53619
54203
  }
53620
54204
  }
53621
- substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, status.enums);
54205
+ substitute_raw_in_node(mono_field.value, substitution, status.structs, void 0, status.traits, {
54206
+ enums: status.enums,
54207
+ bitsets: status.bitsets
54208
+ });
53622
54209
  }
53623
54210
  mono_field.is_library = field.is_library;
53624
54211
  mono_field.is_readonly = field.is_readonly;
@@ -53653,7 +54240,10 @@ function monomorphize(generic_struct, type_args, status) {
53653
54240
  for (const func of generic_struct.functions) {
53654
54241
  if (func.name === "#init") continue;
53655
54242
  const cloned = clone_node(func);
53656
- substitute_raw_types(cloned, substitution, status.structs, status.traits, status.enums);
54243
+ substitute_raw_types(cloned, substitution, status.structs, status.traits, {
54244
+ enums: status.enums,
54245
+ bitsets: status.bitsets
54246
+ });
53657
54247
  fold_substituted_constant_ifs(cloned.statements);
53658
54248
  rename_local_labels(cloned, mono_name);
53659
54249
  const retype_roots = [...cloned.statements, ...cloned.allocations ?? []];
@@ -53666,7 +54256,10 @@ function monomorphize(generic_struct, type_args, status) {
53666
54256
  param.type = substitute_type(param.type, substitution);
53667
54257
  substitute_param_signature(param, substitution);
53668
54258
  if (param.type.type_args?.length) param.type = materialize_generic_enum_type(param.type, status);
53669
- if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, status.enums);
54259
+ if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, {
54260
+ enums: status.enums,
54261
+ bitsets: status.bitsets
54262
+ });
53670
54263
  }
53671
54264
  retype_param_references(retype_roots, cloned.params);
53672
54265
  retype_local_references(retype_roots);
@@ -53694,7 +54287,10 @@ function monomorphize(generic_struct, type_args, status) {
53694
54287
  if (variadic_init_unsupported(generic_init)) continue;
53695
54288
  if (!generic_init.statements.some((s) => s.node_type !== "raw")) continue;
53696
54289
  const cloned = clone_node(generic_init);
53697
- substitute_raw_types(cloned, substitution, status.structs, status.traits, status.enums);
54290
+ substitute_raw_types(cloned, substitution, status.structs, status.traits, {
54291
+ enums: status.enums,
54292
+ bitsets: status.bitsets
54293
+ });
53698
54294
  fold_substituted_constant_ifs(cloned.statements);
53699
54295
  rename_local_labels(cloned, mono_name);
53700
54296
  cloned.return_type = new Type(mono_name);
@@ -53702,7 +54298,10 @@ function monomorphize(generic_struct, type_args, status) {
53702
54298
  for (const param of cloned.params) {
53703
54299
  param.type = substitute_type(param.type, substitution);
53704
54300
  substitute_param_signature(param, substitution);
53705
- if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, status.enums);
54301
+ if (param.constraint) substitute_raw_in_node(param.constraint, substitution, status.structs, void 0, status.traits, {
54302
+ enums: status.enums,
54303
+ bitsets: status.bitsets
54304
+ });
53706
54305
  }
53707
54306
  const cloned_self = cloned.params.find((p) => p.is_self_param);
53708
54307
  if (cloned_self) cloned_self.type = new Type(mono_name);
@@ -54207,7 +54806,7 @@ function derive_annotations_for_access_func(access_node, fc, status) {
54207
54806
  const p = func.params[i];
54208
54807
  if (p.is_self_param) continue;
54209
54808
  if (!p.type?.is_nullable) continue;
54210
- 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));
54809
+ 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));
54211
54810
  }
54212
54811
  if (nullable_indices.length > 0) fc.nullable_param_indices = nullable_indices;
54213
54812
  if (func.return_constraint && !fc.return_bounds) {
@@ -54586,7 +55185,7 @@ function fold_substituted_constant_ifs(statements) {
54586
55185
  statements.splice(i, 1, ...kept);
54587
55186
  }
54588
55187
  }
54589
- function substitute_raw_types(func, substitution, structs, traits, enums) {
55188
+ function substitute_raw_types(func, substitution, structs, traits, tables) {
54590
55189
  const deref_params = /* @__PURE__ */ new Set();
54591
55190
  for (const param of func.params) {
54592
55191
  const resolved = substitution.get(param.type.name);
@@ -54595,7 +55194,7 @@ function substitute_raw_types(func, substitution, structs, traits, enums) {
54595
55194
  const s = structs.find((x) => x.name === resolved);
54596
55195
  if (s && !s.is_simple_type && !s.is_class) deref_params.add(param.name);
54597
55196
  }
54598
- for (const stmt of func.statements) substitute_raw_in_node(stmt, substitution, structs, deref_params, traits, enums);
55197
+ for (const stmt of func.statements) substitute_raw_in_node(stmt, substitution, structs, deref_params, traits, tables);
54599
55198
  }
54600
55199
  function rename_local_labels(node, prefix) {
54601
55200
  if (node.node_type === "raw") {
@@ -54628,7 +55227,7 @@ function raw_type_alignment(name) {
54628
55227
  function raw_align_to(offset, alignment) {
54629
55228
  return Math.ceil(offset / alignment) * alignment;
54630
55229
  }
54631
- function raw_type_size(name, structs, enums) {
55230
+ function raw_type_size(name, structs, tables) {
54632
55231
  switch (name) {
54633
55232
  case "bool":
54634
55233
  case "int8":
@@ -54639,13 +55238,13 @@ function raw_type_size(name, structs, enums) {
54639
55238
  case "int32":
54640
55239
  case "uint32": return 4;
54641
55240
  }
54642
- const enum_node = enums?.find((e) => e.name === name && !e.is_generic);
55241
+ const enum_node = tables.enums.find((e) => e.name === name && !e.is_generic);
54643
55242
  if (enum_node) {
54644
55243
  if (!enum_node.has_associated_data) return 8;
54645
55244
  let max_payload = 0;
54646
55245
  for (const c of enum_node.cases) {
54647
55246
  let case_size = 0;
54648
- for (const p of c.params) case_size += raw_type_size(p.type.name, structs, enums);
55247
+ for (const p of c.params) case_size += raw_type_size(p.type.name, structs, tables);
54649
55248
  max_payload = Math.max(max_payload, case_size);
54650
55249
  }
54651
55250
  return 8 + Math.ceil(max_payload / 8) * 8;
@@ -54660,9 +55259,9 @@ function raw_type_size(name, structs, enums) {
54660
55259
  size += 16;
54661
55260
  continue;
54662
55261
  }
54663
- size += raw_type_size(field.type.name, structs, enums);
55262
+ size += raw_type_size(field.type.name, structs, tables);
54664
55263
  if (field.type.is_nullable && structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class)) size += 8;
54665
- else if (is_nullable_scalar_type(field.type)) size += 1;
55264
+ else if (is_nullable_scalar_type(field.type, tables)) size += 1;
54666
55265
  }
54667
55266
  return raw_align_to(size, 8);
54668
55267
  }
@@ -54707,12 +55306,15 @@ function raw_block_is_pure_asm(value) {
54707
55306
  function is_reference_type_arg(t, structs, traits) {
54708
55307
  return !!traits.some((tr) => tr.name === t.name) || !!structs.find((s) => s.name === t.name && s.is_class);
54709
55308
  }
54710
- function substitute_raw_in_node(node, substitution, structs, deref_params = /* @__PURE__ */ new Set(), traits = [], enums = []) {
55309
+ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @__PURE__ */ new Set(), traits = [], tables = {
55310
+ enums: [],
55311
+ bitsets: []
55312
+ }) {
54711
55313
  if (node.node_type === "value") {
54712
55314
  const vn = node;
54713
55315
  for (const [param, type] of substitution) {
54714
55316
  if (vn.value === `${param}_SIZE`) {
54715
- vn.value = String(raw_type_size(type, structs, enums));
55317
+ vn.value = String(raw_type_size(type, structs, tables));
54716
55318
  vn.type = new Type("int", true);
54717
55319
  return;
54718
55320
  }
@@ -54737,7 +55339,7 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
54737
55339
  else if (struct_node) c_type_name = `struct ${type}`;
54738
55340
  else c_type_name = raw_c_type_name(type);
54739
55341
  value = value.replace(new RegExp(`\\b${param}\\b`, "g"), c_type_name);
54740
- const size = raw_type_size(type, structs, enums);
55342
+ const size = raw_type_size(type, structs, tables);
54741
55343
  const size_expr = raw_block_is_pure_asm(value) ? String(size) : `sizeof(${c_type_name})`;
54742
55344
  value = value.replace(new RegExp(`\\b${param}_SIZE\\b`, "g"), size_expr);
54743
55345
  value = value.replace(new RegExp(`\\b${param}_destroy\\b`, "g"), `${type}_destroy`);
@@ -54773,22 +55375,22 @@ function substitute_raw_in_node(node, substitution, structs, deref_params = /* @
54773
55375
  if (any_node.func_return_type?.name) any_node.func_return_type = substitute_type(any_node.func_return_type, substitution);
54774
55376
  }
54775
55377
  if (any_node.allocations && Array.isArray(any_node.allocations)) {
54776
- 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);
55378
+ 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);
54777
55379
  }
54778
55380
  if (any_node.statements && Array.isArray(any_node.statements)) {
54779
- 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);
55381
+ 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);
54780
55382
  }
54781
55383
  if (any_node.params && Array.isArray(any_node.params)) {
54782
- 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);
55384
+ 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);
54783
55385
  }
54784
- if (any_node.value && any_node.value.node_type) substitute_raw_in_node(any_node.value, substitution, structs, deref_params, traits, enums);
54785
- if (any_node.left_value?.node_type) substitute_raw_in_node(any_node.left_value, substitution, structs, deref_params, traits, enums);
54786
- if (any_node.right_value?.node_type) substitute_raw_in_node(any_node.right_value, substitution, structs, deref_params, traits, enums);
54787
- if (any_node.target?.node_type) substitute_raw_in_node(any_node.target, substitution, structs, deref_params, traits, enums);
54788
- if (any_node.access?.node_type) substitute_raw_in_node(any_node.access, substitution, structs, deref_params, traits, enums);
54789
- if (any_node.swap?.node_type) substitute_raw_in_node(any_node.swap, substitution, structs, deref_params, traits, enums);
54790
- if (any_node.condition?.node_type) substitute_raw_in_node(any_node.condition, substitution, structs, deref_params, traits, enums);
54791
- if (any_node.index?.node_type) substitute_raw_in_node(any_node.index, substitution, structs, deref_params, traits, enums);
55386
+ if (any_node.value && any_node.value.node_type) substitute_raw_in_node(any_node.value, substitution, structs, deref_params, traits, tables);
55387
+ if (any_node.left_value?.node_type) substitute_raw_in_node(any_node.left_value, substitution, structs, deref_params, traits, tables);
55388
+ if (any_node.right_value?.node_type) substitute_raw_in_node(any_node.right_value, substitution, structs, deref_params, traits, tables);
55389
+ if (any_node.target?.node_type) substitute_raw_in_node(any_node.target, substitution, structs, deref_params, traits, tables);
55390
+ if (any_node.access?.node_type) substitute_raw_in_node(any_node.access, substitution, structs, deref_params, traits, tables);
55391
+ if (any_node.swap?.node_type) substitute_raw_in_node(any_node.swap, substitution, structs, deref_params, traits, tables);
55392
+ if (any_node.condition?.node_type) substitute_raw_in_node(any_node.condition, substitution, structs, deref_params, traits, tables);
55393
+ if (any_node.index?.node_type) substitute_raw_in_node(any_node.index, substitution, structs, deref_params, traits, tables);
54792
55394
  }
54793
55395
  function specialize_function(generic_func, call_node, status) {
54794
55396
  const substitution = /* @__PURE__ */ new Map();
@@ -55760,6 +56362,14 @@ function check_function_call(node, status, func, target_type, self_value, self_p
55760
56362
  }
55761
56363
  node.type = func.return_type;
55762
56364
  node.is_static = func.is_static;
56365
+ if (target_type?.type_args?.length && node.type?.type_args?.length) {
56366
+ const owner = func.scope;
56367
+ if (owner && owner.node_type === "struct" && owner.is_generic && owner.type_params.length === target_type.type_args.length) {
56368
+ const rebind = /* @__PURE__ */ new Map();
56369
+ owner.type_params.forEach((tp, i) => rebind.set(tp, target_type.type_args[i].name));
56370
+ node.type = substitute_type(node.type, rebind);
56371
+ }
56372
+ }
55763
56373
  if (node.type?.type_args?.length) instantiate_generic_type(node.type, status);
55764
56374
  if (node.type && node.type.name && self_value && is_class_type$1(node.type.name, status) && receiver_is_const(target_type, self_value, status)) {
55765
56375
  node.type = clone_type$2(node.type);
@@ -55807,7 +56417,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
55807
56417
  for (let [i, param] of node.params.entries()) {
55808
56418
  const func_param = func.params[i + self_offset];
55809
56419
  if (func_param && func_param.type.is_nullable && !func_param.is_self_param && !func_param.type.is_ref && !func_param.type.is_array) {
55810
- 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)) {
56420
+ 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)) {
55811
56421
  if (!node.nullable_param_indices) node.nullable_param_indices = [];
55812
56422
  if (!node.nullable_param_indices.includes(i)) node.nullable_param_indices.push(i);
55813
56423
  }
@@ -55943,7 +56553,10 @@ function check_function_call(node, status, func, target_type, self_value, self_p
55943
56553
  else {
55944
56554
  const stamped = String(func_param.type.length.value);
55945
56555
  const incoming = String(param_type.length.value);
55946
- if (stamped !== incoming) add_error(status, `Call passes ${incoming} elements to array parameter '${func_param.name}' whose compile-time length is ${stamped} — the raw array ABI carries no runtime length, so one parameter serves one compile-time length; use Array<${func_param.type.name}> for varying lengths`, param.start);
56556
+ if (stamped !== incoming) {
56557
+ if (is_heap_array_type(func_param.type)) func_param.type.length = void 0;
56558
+ else 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);
56559
+ }
55947
56560
  }
55948
56561
  }
55949
56562
  let arg_value;
@@ -56174,6 +56787,13 @@ function check_function_call(node, status, func, target_type, self_value, self_p
56174
56787
  if (typeof size_val === "number" && size_val > 0) record_buffer_cap(self_path, size_val, status);
56175
56788
  }
56176
56789
  }
56790
+ if (self_path && self_path !== "?" && is_bufferish && func.name === "copy") {
56791
+ const source_cap = lookup_buffer_cap(self_path, status);
56792
+ if (source_cap !== void 0) {
56793
+ const lhs_name = find_lhs_var_name(status);
56794
+ if (lhs_name) record_buffer_cap(lhs_name, source_cap, status);
56795
+ }
56796
+ }
56177
56797
  if (func.return_constraint) {
56178
56798
  const param_to_arg = /* @__PURE__ */ new Map();
56179
56799
  if (self_path && self_path !== "?") param_to_arg.set("self", path_to_node(self_path));
@@ -60227,6 +60847,68 @@ function parse_case_branch(status) {
60227
60847
  return null;
60228
60848
  }
60229
60849
  //#endregion
60850
+ //#region ../src/parse/utils/parse_embed_file.ts
60851
+ /**
60852
+ * `#embed_file("relative/path")` — a compile-time string literal whose value
60853
+ * is the contents of the file at parse time. The path resolves against the
60854
+ * package root (the directory containing package.jsonc, found by climbing
60855
+ * from the entry file), falling back to the entry file's folder; with no
60856
+ * known base it resolves against the process CWD.
60857
+ *
60858
+ * The parsed form is a plain string-literal ValueNode: escapes are encoded
60859
+ * with the source-level escape set (`\\`, `\"`, `\n`, `\r`, `\t`, and
60860
+ * three-digit octal for other control bytes — self-terminating, matching the
60861
+ * escape scanner), so every downstream consumer (length counting, C/asm
60862
+ * emission) treats it exactly like a hand-written literal.
60863
+ */
60864
+ function parse_embed_file(status) {
60865
+ const start = get_index(status);
60866
+ consume(status);
60867
+ const name = consume(status);
60868
+ if (name !== "embed_file") {
60869
+ add_error(status, `Unknown compiler directive '#${name}' — did you mean '#embed_file'?`, start);
60870
+ return new ValueNode(start, "\"\"");
60871
+ }
60872
+ if (!expect("(", status)) return new ValueNode(start, "\"\"");
60873
+ const path_token = status.tokens[status.i];
60874
+ if (!path_token || !path_token.value.startsWith("\"")) {
60875
+ add_error(status, "Expected a string literal path in #embed_file", start);
60876
+ return new ValueNode(start, "\"\"");
60877
+ }
60878
+ status.i += 1;
60879
+ const rel_path = path_token.value.slice(1, -1);
60880
+ if (!expect(")", status)) return new ValueNode(start, "\"\"");
60881
+ if (!rel_path) {
60882
+ add_error(status, "#embed_file path is empty", start);
60883
+ return new ValueNode(start, "\"\"");
60884
+ }
60885
+ const base = status.embed_root ?? process.cwd();
60886
+ const full = path.isAbsolute(rel_path) ? rel_path : path.join(base, rel_path);
60887
+ let content;
60888
+ try {
60889
+ content = fs.readFileSync(full, "utf8");
60890
+ } catch {
60891
+ add_error(status, `#embed_file could not read '${rel_path}' (resolved '${full}')`, start);
60892
+ return new ValueNode(start, "\"\"");
60893
+ }
60894
+ return new ValueNode(start, `"${escape_embedded(content)}"`);
60895
+ }
60896
+ /** Encode file contents as a source-level string literal body. */
60897
+ function escape_embedded(content) {
60898
+ let out = "";
60899
+ for (const ch of content) {
60900
+ const code = ch.charCodeAt(0);
60901
+ if (ch === "\\") out += "\\\\";
60902
+ else if (ch === "\"") out += "\\\"";
60903
+ else if (ch === "\n") out += "\\n";
60904
+ else if (ch === "\r") out += "\\r";
60905
+ else if (ch === " ") out += "\\t";
60906
+ else if (code < 32 || code === 127) out += `\\${code.toString(8).padStart(3, "0")}`;
60907
+ else out += ch;
60908
+ }
60909
+ return out;
60910
+ }
60911
+ //#endregion
60230
60912
  //#region ../src/parse/parse_expression.ts
60231
60913
  function restructure_op(start, current_op, current_prec, current_node, expression) {
60232
60914
  if (current_prec <= operator_precedence(expression.op)) {
@@ -60366,6 +61048,7 @@ function parse_primary(status, value) {
60366
61048
  expect(")", status);
60367
61049
  return node;
60368
61050
  }
61051
+ case "#": return parse_embed_file(status);
60369
61052
  case "if": return parse_if_else(status);
60370
61053
  case "match": return parse_match(status);
60371
61054
  case "switch": return parse_switch(status);
@@ -61508,7 +62191,8 @@ function parse(source, library, file_path, options) {
61508
62191
  qualified_paths: [],
61509
62192
  library,
61510
62193
  errors: [],
61511
- allow_user_raw: options?.allow_user_raw
62194
+ allow_user_raw: options?.allow_user_raw,
62195
+ embed_root: embed_root_for(file_path)
61512
62196
  };
61513
62197
  if (library) status.unsafe_boundary = user_source_length;
61514
62198
  parse_statement(status);
@@ -61585,6 +62269,23 @@ function mark_library_nodes(root, boundary) {
61585
62269
  walk(root);
61586
62270
  }
61587
62271
  /**
62272
+ * The base directory for `#embed_file` paths: the package root (the nearest
62273
+ * enclosing directory with a package.jsonc), falling back to the entry
62274
+ * file's folder. No file path — undefined, so `#embed_file` resolves
62275
+ * against the process CWD.
62276
+ */
62277
+ function embed_root_for(file_path) {
62278
+ if (!file_path) return void 0;
62279
+ let dir = path.dirname(path.resolve(file_path));
62280
+ for (let i = 0; i < 32; i++) {
62281
+ if (fs.existsSync(path.join(dir, "package.jsonc"))) return dir;
62282
+ const parent = path.dirname(dir);
62283
+ if (parent === dir) break;
62284
+ dir = parent;
62285
+ }
62286
+ return path.dirname(path.resolve(file_path));
62287
+ }
62288
+ /**
61588
62289
  * Append the library source that `source` needs, exactly as `parse` does.
61589
62290
  * Exported so tooling (e.g. the editor extension) can reproduce the combined
61590
62291
  * source that node offsets refer to.
@@ -61838,7 +62539,7 @@ function print_help() {
61838
62539
  " nomen check --in [file/folder] Parse and check only",
61839
62540
  " nomen format [--in folder] Reformat every .nm file",
61840
62541
  " nomen docs [--in file] Generate markdown documentation",
61841
- " nomen test [--in folder] Discover and run *.test.nm files",
62542
+ " nomen test [--in folder] Discover and run *.test.nm files (binary CWD = --in folder)",
61842
62543
  " nomen init <name> Scaffold a new project in ./<name>",
61843
62544
  " nomen lib-path Print the bundled System library path",
61844
62545
  "",
@@ -62428,7 +63129,7 @@ function extract_leaks(stdout) {
62428
63129
  * Compile one `*.test.nm` file (with the generated harness) and run it,
62429
63130
  * returning its parsed records and any crash info.
62430
63131
  */
62431
- function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime) {
63132
+ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime, release = false, cwd) {
62432
63133
  const start = performance.now();
62433
63134
  const source_text = fs.readFileSync(entry_path, "utf8");
62434
63135
  const harness = generate_harness(extract_test_functions(source_text), extract_bench_functions(source_text));
@@ -62455,7 +63156,8 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
62455
63156
  }
62456
63157
  const buildResult = build(parsed.root, {
62457
63158
  arch,
62458
- audit
63159
+ audit,
63160
+ optimize: release
62459
63161
  });
62460
63162
  if (buildResult.errors && buildResult.errors.length) {
62461
63163
  result.ok = false;
@@ -62490,11 +63192,19 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
62490
63192
  const outfile = path.join(buildDir, path.basename(entry_path, ".nm"));
62491
63193
  fs.writeFileSync(path.join(buildDir, "main.h"), buildResult.headers ?? "");
62492
63194
  fs.writeFileSync(codefile, buildResult.code);
63195
+ let companionfile;
63196
+ if (buildResult.companion) {
63197
+ const comp_ext = platform === "macos" || platform === "ios" ? ".m" : ".c";
63198
+ companionfile = path.join(buildDir, path.basename(entry_path, ".nm") + "_companion" + comp_ext);
63199
+ fs.writeFileSync(companionfile, buildResult.companion);
63200
+ }
62493
63201
  const link_args = [
62494
63202
  "-o",
62495
63203
  outfile,
62496
63204
  codefile
62497
63205
  ];
63206
+ if (companionfile) link_args.push(companionfile);
63207
+ if (release) link_args.unshift("-O2");
62498
63208
  if (audit_obj) link_args.push(audit_obj);
62499
63209
  if (platform === "macos" || platform === "ios") link_args.push("-framework", "CoreGraphics", "-framework", "Foundation", "-framework", "AppKit", "-lobjc");
62500
63210
  try {
@@ -62525,7 +63235,8 @@ function run_test_file(entry_path, lib_path, arch, audit = false, audit_runtime)
62525
63235
  "pipe"
62526
63236
  ],
62527
63237
  timeout: 3e4,
62528
- maxBuffer: 16777216
63238
+ maxBuffer: 16777216,
63239
+ cwd: cwd ?? process.cwd()
62529
63240
  });
62530
63241
  } catch (err) {
62531
63242
  runStdout = err.stdout ? err.stdout.toString() : "";
@@ -62607,9 +63318,10 @@ function runTests(root, options = {}) {
62607
63318
  const files = collect_test_files(root).filter((f) => !options.filter || options.filter.test(f));
62608
63319
  console.log(`Found ${files.length} test file(s)\n`);
62609
63320
  const startTime = performance.now();
63321
+ const binary_cwd = path.resolve(root);
62610
63322
  const results = [];
62611
63323
  for (const file of files) {
62612
- const result = run_test_file(file, lib, arch, options.audit, options.audit_runtime);
63324
+ const result = run_test_file(file, lib, arch, options.audit, options.audit_runtime, options.release, binary_cwd);
62613
63325
  report_file(result);
62614
63326
  results.push(result);
62615
63327
  }
@@ -62717,7 +63429,8 @@ try {
62717
63429
  arch: args.arch ?? "aarch64",
62718
63430
  filter,
62719
63431
  audit: args.audit,
62720
- audit_runtime: args.audit_runtime
63432
+ audit_runtime: args.audit_runtime,
63433
+ release: args.release
62721
63434
  });
62722
63435
  process.exit(ok ? 0 : 1);
62723
63436
  }