nomen-lang 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/core/System/Buffer.nm +32 -0
  2. package/core/System/ClassBuffer.nm +24 -0
  3. package/core/System/Controls/Alert.nm +326 -0
  4. package/core/System/Controls/App.nm +147 -0
  5. package/core/System/Controls/Button.nm +119 -1
  6. package/core/System/Controls/CheckBox.nm +24 -0
  7. package/core/System/Controls/ColorPicker.nm +307 -0
  8. package/core/System/Controls/ComboBox.nm +430 -0
  9. package/core/System/Controls/Container.nm +837 -9
  10. package/core/System/Controls/Control.nm +443 -0
  11. package/core/System/Controls/DatePicker.nm +288 -0
  12. package/core/System/Controls/Form.nm +70 -0
  13. package/core/System/Controls/GroupBox.nm +267 -0
  14. package/core/System/Controls/ImageView.nm +311 -0
  15. package/core/System/Controls/LevelIndicator.nm +305 -0
  16. package/core/System/Controls/ListView.nm +633 -0
  17. package/core/System/Controls/Menu.nm +428 -0
  18. package/core/System/Controls/NumberInput.nm +56 -0
  19. package/core/System/Controls/OtpInput.nm +50 -0
  20. package/core/System/Controls/OutlineView.nm +598 -0
  21. package/core/System/Controls/Pagination.nm +142 -0
  22. package/core/System/Controls/Popover.nm +296 -0
  23. package/core/System/Controls/Progress.nm +348 -0
  24. package/core/System/Controls/Radio.nm +292 -0
  25. package/core/System/Controls/Scrollable.nm +584 -0
  26. package/core/System/Controls/SearchField.nm +326 -0
  27. package/core/System/Controls/SecureField.nm +344 -0
  28. package/core/System/Controls/Segmented.nm +380 -0
  29. package/core/System/Controls/Select.nm +340 -0
  30. package/core/System/Controls/Separator.nm +198 -0
  31. package/core/System/Controls/Sheet.nm +329 -0
  32. package/core/System/Controls/Slider.nm +315 -0
  33. package/core/System/Controls/Spinner.nm +287 -0
  34. package/core/System/Controls/SplitView.nm +509 -0
  35. package/core/System/Controls/Stepper.nm +349 -0
  36. package/core/System/Controls/Switch.nm +259 -0
  37. package/core/System/Controls/Table.nm +833 -0
  38. package/core/System/Controls/Tabs.nm +500 -0
  39. package/core/System/Controls/Text.nm +108 -11
  40. package/core/System/Controls/TextBox.nm +112 -1
  41. package/core/System/Controls/TextView.nm +329 -0
  42. package/core/System/Controls/Toggle.nm +346 -0
  43. package/core/System/Controls/Toolbar.nm +501 -0
  44. package/core/System/Controls/Tooltip.nm +76 -0
  45. package/core/System/Controls/Window.nm +1549 -91
  46. package/core/System/List.nm +34 -0
  47. package/core/System/Map.nm +21 -4
  48. package/core/System/Set.nm +14 -0
  49. package/dist/index.mjs +2556 -656
  50. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -2367,11 +2367,7 @@ function parse_operand(tok) {
2367
2367
  if (/^[A-Za-z_.$][\w.$]*@[A-Za-z0-9_]+$/.test(trimmed)) return "labelish";
2368
2368
  return null;
2369
2369
  }
2370
- /**
2371
- * Validate emitted assembly. Returns every problem found (empty array when
2372
- * clean). Never throws — malformed input IS the input here.
2373
- */
2374
- function validate_asm(code) {
2370
+ function validate_asm(code, options = {}) {
2375
2371
  const errors = [];
2376
2372
  const lines = code.split("\n");
2377
2373
  const labels = /* @__PURE__ */ new Set();
@@ -2457,7 +2453,7 @@ function validate_asm(code) {
2457
2453
  if ((op === "b" || op.startsWith("b.") || op === "cbz" || op === "cbnz") && labelish_count > 0) {
2458
2454
  const target = operands.find((o) => o.kind === "label").name;
2459
2455
  const num = /^(\d+)([fb])$/.exec(target);
2460
- if (!(num ? (numeric_labels.get(num[1]) ?? []).some((line) => num[2] === "f" ? line > i : line < i) : labels.has(target))) err(`branch to undefined label '${target}'`);
2456
+ if (!(num ? (numeric_labels.get(num[1]) ?? []).some((line) => num[2] === "f" ? line > i : line < i) : labels.has(target)) && !options.allow_external_branch_targets) err(`branch to undefined label '${target}'`);
2461
2457
  }
2462
2458
  }
2463
2459
  return errors;
@@ -2795,8 +2791,8 @@ function describe$1(operands) {
2795
2791
  * function entry (bl-target / globl / boundary-heuristic labels). Validation
2796
2792
  * errors are returned alongside — callers should check them first.
2797
2793
  */
2798
- function lift_functions(code) {
2799
- const errors = validate_asm(code);
2794
+ function lift_functions(code, options = {}) {
2795
+ const errors = validate_asm(code, options);
2800
2796
  const lines = [];
2801
2797
  const functions = [];
2802
2798
  const lines_split = code.split("\n");
@@ -4318,20 +4314,42 @@ function convert_loop_invariant_branches(code) {
4318
4314
  * ldp/stp pairs) → the x17 scratch: `mov x17, #big` (+ `add x17, x29, x17`
4319
4315
  * for the pair forms, which have no register-offset encoding).
4320
4316
  *
4317
+ * The same limits bite RECEIVER-relative field accesses: `[xN, #big]` where
4318
+ * xN holds a struct pointer (x19 in methods), and `add xD, xN, #big` for the
4319
+ * address-of form. A struct past ~4-7 KB of fields crosses the encodings the
4320
+ * moment any field sits far enough into the body (scaled x-loads reach 32760,
4321
+ * but sub-word scaled forms and `add` top out at 4095, ldur/stur at 255, and
4322
+ * ldp/stp at 504). These rewrite exactly like the frame forms, except the
4323
+ * base register is a live receiver that must survive:
4324
+ *
4325
+ * - `add xD, xN, #big` with xD != xN → the destination stages its own
4326
+ * address (`mov xD, #big; add xD, xN, xD`); with xD == xN the offset goes
4327
+ * through x17 (staging into the destination would destroy the base).
4328
+ * - loads whose destination is an x-register distinct from the base stage
4329
+ * their own address; everything else goes through x17, including pair
4330
+ * forms (`mov x17, #big; add x17, xN, x17; ldp a, b, [x17]`).
4331
+ * - Scaled accesses rewrite only past their TRUE encodable range, so in-range
4332
+ * code (e.g. `str x3, [x19, #4104]`) stays single-instruction.
4333
+ *
4321
4334
  * x17 is safe as the universal scratch here because this pass runs BEFORE the
4322
4335
  * optimization pipeline: the passes that claim x16/x17 (loop promotion,
4323
4336
  * select registers, pointer walks) validate their claim against the function
4324
4337
  * text and refuse when x17 already appears — losing an optimization on
4325
4338
  * large-frame functions, which is the correct trade. Raw `#arch: aarch64`
4326
4339
  * bodies in the core library never hold a live value in x17 across an
4327
- * out-of-range `[x29, #imm]` access (the corpus has none: such accesses did
4328
- * not assemble before this pass existed). x17 is never live across a `bl`
4329
- * by the backend's own convention, and the shims contain no calls.
4340
+ * out-of-range access (the corpus has none: such accesses did not assemble
4341
+ * before this pass existed). x17 is never live across a `bl` by the backend's
4342
+ * own convention, and the shims contain no calls. The receiver register is
4343
+ * never x17 (emitters keep receivers in x0–x16/x19–x28), so staging into x17
4344
+ * cannot clobber the base; a base-x17 line is left untouched rather than
4345
+ * mis-staged.
4330
4346
  */
4331
4347
  /** First scratch that no emitter or raw body relies on across statements. */
4332
4348
  const SCRATCH = "x17";
4333
- /** The imm12 ceiling shared by `add imm` and the scaled load/store forms. */
4349
+ /** The imm12 ceiling shared by `add imm` and the byte-scaled load/store. */
4334
4350
  const IMM12_MAX = 4095;
4351
+ /** simm9 ceiling (non-negative side) for the unscaled ldur/stur family. */
4352
+ const SIMM9_MAX = 255;
4335
4353
  /** mov (movz/movk expanded) that materializes any non-negative constant. */
4336
4354
  function emit_mov_imm(reg, imm) {
4337
4355
  if (imm <= 65535) return [`mov ${reg}, #${imm}`];
@@ -4366,6 +4384,44 @@ const SP_ADJ_RE = /^(sub|add) sp, sp, #(\d+)$/;
4366
4384
  /** True for full 64-bit integer registers usable as their own address scratch
4367
4385
  * (xzr excluded: `ldr xzr` still performs the load, and xzr can't stage). */
4368
4386
  const IS_X_REG = /^x([0-9]|[12][0-9]|3[01])$/;
4387
+ const X_BASE = "(x(?:[0-9]|[12][0-9]|3[01]))";
4388
+ const MEM_RECV_RE = new RegExp(`^(ldr|ldrb|ldrh|ldrsb|ldrsh|ldrsw|ldur|ldurb|ldurh|ldursb|ldursh|ldursw|str|strb|strh|stur|sturb|sturh) ([a-z][a-z0-9]*), \\[${X_BASE}, #(\\d+)\\](\\s*\\/\\/.*)?$`);
4389
+ const PAIR_RECV_RE = new RegExp(`^(ldp|stp) ([^,]+), ([^,]+), \\[${X_BASE}, #(\\d+)\\](\\s*\\/\\/.*)?$`);
4390
+ const ADD_RECV_RE = new RegExp(`^add ${X_BASE}, ${X_BASE}, #(\\d+)(\\s*\\/\\/.*)?$`);
4391
+ /** Register-offset spelling of the unscaled ldur/stur family: the unscaled
4392
+ * encodings have no register-offset form, so an out-of-range unscaled access
4393
+ * rewrites to its scaled mnemonic (identical semantics — the immediate is
4394
+ * non-negative, so the missing sign-extension reach is unused). */
4395
+ const SCALED_OF = {
4396
+ ldur: "ldr",
4397
+ stur: "str",
4398
+ ldurb: "ldrb",
4399
+ sturb: "strb",
4400
+ ldurh: "ldrh",
4401
+ sturh: "strh",
4402
+ ldursb: "ldrsb",
4403
+ ldursh: "ldrsh",
4404
+ ldursw: "ldrsw"
4405
+ };
4406
+ /** Largest non-negative immediate a SCALED load/store encodes: imm12 scaled
4407
+ * by the access width (byte 4095 … quad 65520). The width comes from the
4408
+ * mnemonic's suffix where present, else the destination register's class.
4409
+ */
4410
+ function scaled_imm_max(op, reg) {
4411
+ if (op.endsWith("b")) return IMM12_MAX;
4412
+ if (op.endsWith("h")) return IMM12_MAX * 2;
4413
+ if (op === "ldrsw") return IMM12_MAX * 4;
4414
+ if (reg.startsWith("q")) return IMM12_MAX * 16;
4415
+ if (reg.startsWith("x") || reg.startsWith("d")) return IMM12_MAX * 8;
4416
+ if (reg.startsWith("w") || reg.startsWith("s")) return IMM12_MAX * 4;
4417
+ if (reg.startsWith("h")) return IMM12_MAX * 2;
4418
+ return IMM12_MAX;
4419
+ }
4420
+ /** Largest non-negative immediate a pair form encodes: simm7 scaled by the
4421
+ * element size (8-byte pairs 504, 16-byte pairs 1008). */
4422
+ function pair_imm_max(reg) {
4423
+ return 63 * (reg.startsWith("q") ? 16 : reg.startsWith("w") || reg.startsWith("s") ? 4 : 8);
4424
+ }
4369
4425
  function rewrite_large_frame_offsets(asm) {
4370
4426
  const lines = asm.split("\n");
4371
4427
  const out = [];
@@ -4403,6 +4459,42 @@ function rewrite_large_frame_offsets(asm) {
4403
4459
  out.push(`${op} ${a}, ${b}, [${SCRATCH}]${comment ?? ""}`);
4404
4460
  continue;
4405
4461
  }
4462
+ const add_recv = ADD_RECV_RE.exec(line.trim());
4463
+ if (add_recv && Number(add_recv[3]) > IMM12_MAX) {
4464
+ const [, dest, base, imm_s, comment] = add_recv;
4465
+ const imm = Number(imm_s);
4466
+ if (base !== SCRATCH) {
4467
+ const stage = dest !== base ? dest : SCRATCH;
4468
+ for (const l of emit_mov_imm(stage, imm)) out.push(l);
4469
+ out.push(`add ${dest}, ${base}, ${stage}${comment ?? ""}`);
4470
+ continue;
4471
+ }
4472
+ }
4473
+ const mem_recv = MEM_RECV_RE.exec(line.trim());
4474
+ if (mem_recv) {
4475
+ const [, op, reg, base, imm_s, comment] = mem_recv;
4476
+ const imm = Number(imm_s);
4477
+ if (imm > (op.startsWith("ldur") || op.startsWith("stur") ? SIMM9_MAX : scaled_imm_max(op, reg)) && base !== SCRATCH) {
4478
+ const is_load = op.startsWith("ldr");
4479
+ const emit_op = SCALED_OF[op] ?? op;
4480
+ if (is_load && IS_X_REG.test(reg) && reg !== base) {
4481
+ for (const l of emit_mov_imm(reg, imm)) out.push(l);
4482
+ out.push(`${emit_op} ${reg}, [${base}, ${reg}]${comment ?? ""}`);
4483
+ } else {
4484
+ for (const l of emit_mov_imm(SCRATCH, imm)) out.push(l);
4485
+ out.push(`${emit_op} ${reg}, [${base}, ${SCRATCH}]${comment ?? ""}`);
4486
+ }
4487
+ continue;
4488
+ }
4489
+ }
4490
+ const pair_recv = PAIR_RECV_RE.exec(line.trim());
4491
+ if (pair_recv && Number(pair_recv[5]) > pair_imm_max(pair_recv[2]) && pair_recv[4] !== SCRATCH) {
4492
+ const [, op, a, b, base, imm_s, comment] = pair_recv;
4493
+ for (const l of emit_mov_imm(SCRATCH, Number(imm_s))) out.push(l);
4494
+ out.push(`add ${SCRATCH}, ${base}, ${SCRATCH}`);
4495
+ out.push(`${op} ${a}, ${b}, [${SCRATCH}]${comment ?? ""}`);
4496
+ continue;
4497
+ }
4406
4498
  out.push(line);
4407
4499
  }
4408
4500
  return out.join("\n");
@@ -6526,6 +6618,31 @@ var AccessFunctionCallNode = class extends BaseNode {
6526
6618
  */
6527
6619
  is_nursery_spawn;
6528
6620
  /**
6621
+ * Set during checking when this `.on_<event>(closure)` call was
6622
+ * checker-interpreted as a handler registration (docs/HANDLERS.md): the
6623
+ * closure body was hoisted into a hidden method on the enclosing struct
6624
+ * and the site checked into a dispatch arm on the host's synthesized
6625
+ * `handle_clicks`. The registration itself emits NO code — both backends
6626
+ * return as soon as they see the stamp.
6627
+ */
6628
+ is_handler_registration;
6629
+ /**
6630
+ * Set during checking when this `.on_<event>(closure)` call was
6631
+ * checker-interpreted as a COMPOSITE EVENT registration (docs/HANDLERS.md
6632
+ * Stage 3): the closure was hoisted into a hidden method on the app and
6633
+ * the params were REWRITTEN to the enrollment call — `receiver.on_<event>(id)`
6634
+ * — which resolves as the composite's own declared event API. The stamp
6635
+ * makes the re-entered ordinary resolution skip the desugar hook.
6636
+ */
6637
+ is_handler_enrollment;
6638
+ /**
6639
+ * Set during checking when a `ref T x = container.at(i)` binding was
6640
+ * rewritten to the element-borrow primitive (`slot_ptr` — docs/HANDLERS.md
6641
+ * Stage 4): the call RETURNS the slot pointer, so the ref-binding build
6642
+ * emits it bare (no `&`).
6643
+ */
6644
+ is_element_slot_load;
6645
+ /**
6529
6646
  * RETIRED with the pre-migration launch dispatch: `.start()`/`.detach()`
6530
6647
  * are ordinary methods on the library `Thread` class now (docs/ASYNC.md).
6531
6648
  * The flags remain only because monomorphized clones can carry them from
@@ -6949,23 +7066,19 @@ function find_enum(name, status) {
6949
7066
  /**
6950
7067
  * Whether `name` is a monomorphized mv-container struct whose
6951
7068
  * `<Mono>_copy` is safe to call from the mv-`Buffer.load_T`
6952
- * specialization — `List_<T>` / `Buffer_<T>` (and their bare generics).
6953
- * These own a heap slab their `#destroy` frees unconditionally, and `copy`
6954
- * yields an independent slab, so a returned element's field must be
6955
- * deep-copied or the caller's unconditional container-field teardown
6956
- * double-frees the slot's slab.
7069
+ * specialization — `List_<T>` / `Buffer_<T>` / `Map_<K,V>` / `Set_<T>`
7070
+ * (and their bare generics). These own heap slabs their `#destroy` frees
7071
+ * unconditionally, and `copy` yields an independent slab, so a returned
7072
+ * element's field must be deep-copied or the caller's unconditional
7073
+ * container-field teardown double-frees the slot's slab.
6957
7074
  *
6958
- * `Map`/`Set` are deliberately EXCLUDED from the automatic deep copy: their
6959
- * monomorphized `copy` (which composes several `Buffer.copy` calls) is
6960
- * verified standalone on both backends, but invoking it from the load
6961
- * specialization corrupts the returned struct on aarch64 (a codegen
6962
- * interaction still open — see FOLLOWUP.md). A `Map`/`Set` FIELD reached
6963
- * through a container element's `load` therefore stays shallow (the
6964
- * pre-existing bounded gap); an EXPLICIT `.copy()` on the mv struct or
6965
- * the Map/Set itself works on both backends.
7075
+ * `Map`/`Set` compose their `copy` from member `Buffer.copy` calls (and a
7076
+ * zero-pair `Map()`/`Set()` ctor); the aarch64 deferred-ctor marshalling
7077
+ * that crashed that composition is fixed (see build_constructor_params),
7078
+ * so they ride the same deep copy.
6966
7079
  */
6967
7080
  function is_load_deep_copy_container(name) {
6968
- return name === "List" || name === "Buffer" || name.startsWith("List_") || name.startsWith("Buffer_");
7081
+ return name === "List" || name === "Buffer" || name === "Map" || name === "Set" || name.startsWith("List_") || name.startsWith("Buffer_") || name.startsWith("Map_") || name.startsWith("Set_");
6969
7082
  }
6970
7083
  function mono_type_name(type_or_name, type_args) {
6971
7084
  const name = typeof type_or_name === "string" ? type_or_name : type_or_name.name;
@@ -8779,6 +8892,89 @@ function check_raw_arch_coverage(func, arch, platform) {
8779
8892
  has_other_arch
8780
8893
  };
8781
8894
  }
8895
+ function find_use_c_mixed_body(func) {
8896
+ let has_use_c = false;
8897
+ const drops = [];
8898
+ for (const stmt of func.statements) {
8899
+ if (stmt.node_type !== "raw") {
8900
+ drops.push({
8901
+ dropped: "statement",
8902
+ start: stmt.start
8903
+ });
8904
+ continue;
8905
+ }
8906
+ let arches = null;
8907
+ for (const line of stmt.value.split("\n")) {
8908
+ const trimmed = line.trim();
8909
+ if (trimmed.startsWith("#arch:")) arches = trimmed.substring(6).split(",").map((a) => a.trim()).filter((a) => a.length > 0);
8910
+ else if (trimmed.length > 0) break;
8911
+ }
8912
+ if (!arches) {
8913
+ drops.push({
8914
+ dropped: "raw asm",
8915
+ start: stmt.start
8916
+ });
8917
+ continue;
8918
+ }
8919
+ if (arches.includes("aarch64")) {
8920
+ drops.push({
8921
+ dropped: "raw asm",
8922
+ start: stmt.start
8923
+ });
8924
+ continue;
8925
+ }
8926
+ if (arches.includes("aarch64_use_c")) {
8927
+ has_use_c = true;
8928
+ continue;
8929
+ }
8930
+ }
8931
+ if (!has_use_c) return find_nested_use_c_block(func.statements);
8932
+ return drops[0] ?? null;
8933
+ }
8934
+ /**
8935
+ * Descend into nested control-flow statements and report the first
8936
+ * block-scope `aarch64_use_c` raw block. Nested functions are boundaries —
8937
+ * their bodies get their own check pass (their own find_use_c_mixed_body
8938
+ * run). `#scope: file` blocks are exempt: file-scope C hoists to the
8939
+ * companion's file scope and emits wherever the carrier function sits (the
8940
+ * blessed pattern for delegate IMPs and registries).
8941
+ */
8942
+ function find_nested_use_c_block(statements) {
8943
+ let found = null;
8944
+ const visit = (value) => {
8945
+ if (found || !value || typeof value !== "object") return;
8946
+ if (Array.isArray(value)) {
8947
+ for (const item of value) visit(item);
8948
+ return;
8949
+ }
8950
+ const n = value;
8951
+ if (typeof n.node_type !== "string") return;
8952
+ if (n.node_type === "func" || n.node_type === "struct" || n.node_type === "trait") return;
8953
+ if (n.node_type === "raw") {
8954
+ const raw = n;
8955
+ let arches = null;
8956
+ let scope_file = false;
8957
+ for (const line of raw.value.split("\n")) {
8958
+ const trimmed = line.trim();
8959
+ if (trimmed.startsWith("#scope:")) scope_file = trimmed.substring(7).trim() === "file";
8960
+ else if (trimmed.startsWith("#arch:")) arches = trimmed.substring(6).split(",").map((a) => a.trim()).filter((a) => a.length > 0);
8961
+ else if (trimmed.length > 0) break;
8962
+ }
8963
+ if (arches?.includes("aarch64_use_c") && !scope_file) found = {
8964
+ dropped: "nested block",
8965
+ start: n.start
8966
+ };
8967
+ return;
8968
+ }
8969
+ const record = value;
8970
+ for (const key of Object.keys(record)) {
8971
+ if (key === "parent" || key === "scope" || key === "node_type") continue;
8972
+ visit(record[key]);
8973
+ }
8974
+ };
8975
+ for (const stmt of statements) visit(stmt);
8976
+ return found;
8977
+ }
8782
8978
  //#endregion
8783
8979
  //#region ../src/nodes/AssignmentNode.ts
8784
8980
  var AssignmentNode = class extends BaseNode {
@@ -9317,6 +9513,24 @@ function struct_needs_destroy(struct, table) {
9317
9513
  return has_destroy(struct) || has_struct_fields_with_destroy(struct, table) || struct_has_owning_enum_field(struct, table) || struct_has_owned_func_field(struct) || owns_strings_via_destroy(struct) && struct_has_direct_string_fields(struct) || struct_has_trait_declared_destroy_fields(struct, table);
9318
9514
  }
9319
9515
  /**
9516
+ * Whether a container-element load may replace this field's shallow bytes
9517
+ * with `<Mono>_copy(&field)` (the deep-copy half of mv load_T): any
9518
+ * monomorphized, non-class struct that owns resources (needs destroy at
9519
+ * scope exit) AND has a `copy` method. The containers (`List`/`Buffer`/
9520
+ * `Map`/`Set`) always qualify (their library `copy` yields an independent
9521
+ * slab), and nested mv user structs qualify once every field passed the
9522
+ * auto-derive copy gate (their synthesized `copy` deep-copies member
9523
+ * containers/strings). A flat value struct byte-copies soundly
9524
+ * (needs_destroy false) and stays untouched; an owning struct WITHOUT a
9525
+ * copy (Box/class/nullable/func fields) stays shallow — the recorded gap
9526
+ * in FOLLOWUP.md. The borrow-carrier reclaim walkers use the SAME
9527
+ * predicate to free exactly the deep-copied fields — keep all call sites
9528
+ * on this one definition.
9529
+ */
9530
+ function field_load_deep_copy(struct, table) {
9531
+ return !struct.is_class && !struct.is_generic && struct_needs_destroy(struct, table) && struct.functions.some((f) => f.name === "copy");
9532
+ }
9533
+ /**
9320
9534
  * Whether a class or OWNING struct has a `func`-typed field. Such a field may
9321
9535
  * hold a capturing-closure descriptor (`owned = 1`, a heap env + descriptor),
9322
9536
  * which the scope-exit / generated destroy must reclaim. A non-mv value
@@ -9328,6 +9542,81 @@ function struct_has_owned_func_field(struct) {
9328
9542
  return struct.fields.some((f) => !f.type.is_ref && f.type.name === "func" && !f.type.is_array);
9329
9543
  }
9330
9544
  /**
9545
+ * Whether a container-element bitwise load (`.at(i)` / `.first()` / `load_T`
9546
+ * — the borrow accessor family) aliases destroy-worthy state that neither
9547
+ * the mv `load_T` deep-copy nor the binding's string strdup repair
9548
+ * privatizes. Such a binding must be classified as a PURE BORROW — no
9549
+ * scope-exit destroy and no strdup repair: the slot keeps sole ownership of
9550
+ * everything the load aliased (the recorded `List<Own>.at()`
9551
+ * double-release: a `#destroy` over a non-string field — an AppKit handle —
9552
+ * ran on the borrowed copy AND on the slot, and a nested `#destroy` struct
9553
+ * without a copy method did the same one level down).
9554
+ *
9555
+ * Privatized by the load + binding repair, and therefore fine to keep
9556
+ * destroy-tracked: direct `string` fields (strdup'd at the binding),
9557
+ * non-nullable `Box<T>` fields (cloned by load_T), nested structs whose
9558
+ * `copy` deep-copies (field_load_deep_copy), and enum fields whose payloads
9559
+ * are all deep-copyable (strdup'd in place). NOT privatizable, and
9560
+ * therefore aliasing: a user `#destroy` body (release semantics — a bitwise
9561
+ * copy cannot clone the resource), a nested destroy-worthy struct without a
9562
+ * copy method, a nullable Box/class field (the load's clone arm skips
9563
+ * nullable fields), an enum field whose payloads reference classes/traits,
9564
+ * an owned func field (the shared closure descriptor), and trait-declared
9565
+ * destroy fields (outside the strdup repair's field walk).
9566
+ *
9567
+ * The accessor-name set is kept in sync with
9568
+ * `is_container_borrow_accessor_name` (string_return_analysis.ts), which
9569
+ * cannot be imported here (it pulls the build_c utils graph into a module
9570
+ * the backends' gather passes import directly).
9571
+ */
9572
+ function element_load_aliases_destroy_state(struct, table) {
9573
+ if (has_destroy(struct)) return true;
9574
+ if (struct_has_owned_func_field(struct)) return true;
9575
+ if (struct_has_trait_declared_destroy_fields(struct, table)) return true;
9576
+ for (const field of struct.fields) {
9577
+ if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
9578
+ const field_enum = table.enums?.find((e) => e.name === field.type.name && e.has_associated_data);
9579
+ if (field_enum) {
9580
+ for (const c of field_enum.cases) for (const p of c.params) {
9581
+ if (table.traits?.some((t) => t.name === p.type.name)) return true;
9582
+ if (table.structs.find((s) => s.name === p.type.name)?.is_class) return true;
9583
+ }
9584
+ continue;
9585
+ }
9586
+ const field_struct = resolve_struct_type(field.type, table);
9587
+ if (!field_struct) continue;
9588
+ if (field_struct.is_class) {
9589
+ if (is_box_type(field_struct.name, table.structs) && !field.type.is_nullable) continue;
9590
+ return true;
9591
+ }
9592
+ if (struct_needs_destroy(field_struct, table) && !field_load_deep_copy(field_struct, table)) return true;
9593
+ }
9594
+ return false;
9595
+ }
9596
+ /**
9597
+ * Whether a DECLARATION is the `List<Own>.at()` pure borrow: bound (non-`mv`,
9598
+ * non-`ref`) to a container borrow accessor call (`at`/`at_or_panic`/`first`/
9599
+ * `slice`/`get_or`/`get_or_panic`/`load`, without `owned_return`) whose
9600
+ * value-struct element aliases destroy-worthy state. Both backends gate the
9601
+ * binding's scope-exit destroy and the string strdup repair on this — see
9602
+ * element_load_aliases_destroy_state.
9603
+ */
9604
+ function is_pure_borrow_element_load(decl, table) {
9605
+ const value = decl.value;
9606
+ if (!value || value.node_type !== "access") return false;
9607
+ const access = value.access;
9608
+ if (!access || access.node_type !== "access_func") return false;
9609
+ const func = access;
9610
+ if (func.owned_return) return false;
9611
+ const name = func.name ?? "";
9612
+ if (!(name === "at" || name === "at_or_panic" || name === "first" || name === "slice" || name === "get_or" || name === "get_or_panic" || name === "load")) return false;
9613
+ const decl_type = decl.type;
9614
+ if (!decl_type) return false;
9615
+ const struct_type = resolve_struct_type(decl_type, table);
9616
+ if (!struct_type || struct_type.is_class || struct_type.is_generic) return false;
9617
+ return element_load_aliases_destroy_state(struct_type, table);
9618
+ }
9619
+ /**
9331
9620
  * Whether a struct needs an auto-generated `<Struct>_destroy` function.
9332
9621
  * Broader than `struct_needs_destroy`: also returns true for structs with
9333
9622
  * `string` fields, because a Buffer<T> for such a struct deep-copies the
@@ -14054,6 +14343,7 @@ function visit$1(node, result, structs) {
14054
14343
  if (a.left_value.node_type === "value") {
14055
14344
  if (is_fresh_heap_string(a.right_value)) result.add(a.left_value.value);
14056
14345
  else if (!a.operator && is_owned_string_var_rhs(a.right_value)) result.add(a.left_value.value);
14346
+ else if (!a.operator && is_moved_string_var_rhs(a.right_value)) result.add(a.left_value.value);
14057
14347
  }
14058
14348
  visit$1(a.right_value, result, structs);
14059
14349
  break;
@@ -14110,6 +14400,13 @@ function is_fresh_heap_string(node) {
14110
14400
  }
14111
14401
  return false;
14112
14402
  }
14403
+ function is_moved_string_var_rhs(node) {
14404
+ if (!node || node.node_type !== "value") return false;
14405
+ const vn = node;
14406
+ if (!vn.is_moved) return false;
14407
+ const type = vn.type;
14408
+ return !!type && type.name === "string" && !type.is_view && !type.is_array;
14409
+ }
14113
14410
  function is_owned_string_var_rhs(node) {
14114
14411
  if (!node || node.node_type !== "value") return false;
14115
14412
  const vn = node;
@@ -14393,49 +14690,6 @@ function build_foreign$1(node, status, struct_node) {
14393
14690
  emit_asm(status, `\n`);
14394
14691
  }
14395
14692
  //#endregion
14396
- //#region ../src/build_aarch64/utils/raw_reload.ts
14397
- /** Width-aware slot reload — mirrors the prologue's store width so the
14398
- * register holds exactly the value a body read of the slot would produce
14399
- * (sub-word stores truncate; a same-width load zero-extends like the
14400
- * emitters' own slot reads). */
14401
- function raw_slot_reload_line(reg, offset, size) {
14402
- const wreg = reg.replace("x", "w");
14403
- if (size === 1) return `ldrb ${wreg}, [x29, #${offset}]`;
14404
- if (size === 2) return `ldrh ${wreg}, [x29, #${offset}]`;
14405
- if (size === 4) return `ldr ${wreg}, [x29, #${offset}]`;
14406
- return `ldr ${reg}, [x29, #${offset}]`;
14407
- }
14408
- /** Capture status.code.length as the entry point and install the plan. */
14409
- function install_raw_reload_plan(status, lines) {
14410
- const plan = {
14411
- entry_marker: asm_code_len(status),
14412
- lines
14413
- };
14414
- status.raw_param_reloads = plan;
14415
- return plan;
14416
- }
14417
- /**
14418
- * The reload lines a raw block needs: none at the function's entry point
14419
- * (the registers still hold the entry values), otherwise the lines for the
14420
- * ABI registers the block's text references. Registers are matched against
14421
- * comment-stripped instruction text only — a mention in a `//` comment, a
14422
- * label, or a data directive doesn't force a reload (same discipline as
14423
- * build_inline_method's count_x19_reads).
14424
- */
14425
- function raw_reload_lines_for(code, plan, current_length) {
14426
- if (!plan || plan.lines.length === 0 || current_length === plan.entry_marker) return [];
14427
- const referenced = /* @__PURE__ */ new Set();
14428
- for (const raw_line of code.split("\n")) {
14429
- const comment = raw_line.indexOf("//");
14430
- const line = (comment === -1 ? raw_line : raw_line.slice(0, comment)).trim();
14431
- if (!line || line.endsWith(":") || line.startsWith(".")) continue;
14432
- for (const match of line.matchAll(/\b[wx][0-9]\b/g)) referenced.add(match[0]);
14433
- }
14434
- const out = [];
14435
- for (const entry of plan.lines) if (referenced.has(entry.reg) || referenced.has(entry.reg.replace("x", "w"))) out.push(entry.asm);
14436
- return out;
14437
- }
14438
- //#endregion
14439
14693
  //#region ../src/build_aarch64/utils/aarch64_size.ts
14440
14694
  /**
14441
14695
  * Storage width of a value of `type` in bytes, derived from the
@@ -14792,6 +15046,382 @@ function emit_enum_payload_binding_load(status, base_reg, payload_off, slot, typ
14792
15046
  }
14793
15047
  }
14794
15048
  //#endregion
15049
+ //#region ../src/build_aarch64/utils/c_companion.ts
15050
+ /**
15051
+ * The label a `aarch64_use_c` function's C body is emitted under — the same
15052
+ * shape the aarch64 side derives for thunks and call sites.
15053
+ */
15054
+ function companion_function_label(func, struct_name, status) {
15055
+ if (struct_name) {
15056
+ const struct = status.structs.find((s) => s.name === struct_name);
15057
+ if (struct && is_overloaded(struct, func.name)) return mangled_label(func, struct_name);
15058
+ return `${struct_name}_${func.name.replace(/#/g, "")}`;
15059
+ }
15060
+ return func.name === "main" ? "_nomen_main" : func.name;
15061
+ }
15062
+ /**
15063
+ * Mach-O reserves leading-`L` symbols for assembler-local labels: clang's
15064
+ * assembler refuses `bl L…` to an undefined symbol (local labels can't be
15065
+ * external), and clang's compiler drops L-prefixed `__asm__` bindings from
15066
+ * the object symbol table entirely. A struct named `LevelIndicator` (or any
15067
+ * L-leading function) with `aarch64_use_c` methods therefore binds its
15068
+ * companion symbols under a `_`-prefixed alias — and build.ts rewrites the
15069
+ * asm-side references to match.
15070
+ */
15071
+ function sanitize_companion_label(label) {
15072
+ return label.startsWith("L") ? `_${label}` : label;
15073
+ }
15074
+ /**
15075
+ * Generate the complete C companion file: includes, struct typedefs, and
15076
+ * function definitions for every collected `aarch64_use_c` function.
15077
+ */
15078
+ function generate_companion(functions, status) {
15079
+ let out = "";
15080
+ if (functions.length > 0 && (status.platform === "macos" || status.platform === "ios")) {
15081
+ out += `#import <Foundation/Foundation.h>\n`;
15082
+ out += `#include <objc/runtime.h>\n`;
15083
+ out += `#include <objc/message.h>\n`;
15084
+ if (status.platform === "macos") out += `#import <Cocoa/Cocoa.h>\n`;
15085
+ else out += `#import <UIKit/UIKit.h>\n`;
15086
+ }
15087
+ out += `#include <stdint.h>\n`;
15088
+ out += `#include <stdlib.h>\n`;
15089
+ out += `typedef struct { void* ptr; long len; } nomen_view;\n`;
15090
+ out += `typedef struct { char* ptr; long len; } nomen_string;\n`;
15091
+ out += `#include <stdio.h>\n`;
15092
+ out += `#include <string.h>\n`;
15093
+ out += `#include <regex.h>\n`;
15094
+ if (status.file_scope_c?.includes("pthread") || status.emit_mode === "system") out += `#include <pthread.h>\n`;
15095
+ out += "\n";
15096
+ if (status.audit) {
15097
+ out += `void *nomen_malloc_wrap(unsigned long);\n`;
15098
+ out += `void *nomen_calloc_wrap(unsigned long, unsigned long);\n`;
15099
+ out += `void *nomen_realloc_wrap(void *, unsigned long);\n`;
15100
+ out += `void nomen_free_wrap(void *);\n`;
15101
+ out += `void *nomen_strdup_wrap(const char *);\n`;
15102
+ out += `void nomen_audit_check(void);\n`;
15103
+ out += "\n";
15104
+ }
15105
+ if (status.emit_mode === "system") {
15106
+ out += runtime_declarations(POOL_HEADER_C + FIBER_HEADER_C);
15107
+ out += "\n";
15108
+ } else {
15109
+ if (!status.pool_runtime_emitted) {
15110
+ out += POOL_HEADER_C;
15111
+ status.pool_runtime_emitted = true;
15112
+ }
15113
+ if (!status.fiber_runtime_emitted) {
15114
+ out += FIBER_HEADER_C;
15115
+ status.fiber_runtime_emitted = true;
15116
+ }
15117
+ }
15118
+ const enum_nodes = status.enums.filter((e) => !e.is_generic);
15119
+ const struct_nodes = status.structs.filter((s) => !s.is_simple_type && !s.is_generic);
15120
+ const node_kind = /* @__PURE__ */ new Map();
15121
+ for (const e of enum_nodes) node_kind.set(e.name, "enum");
15122
+ for (const st of struct_nodes) node_kind.set(st.name, "struct");
15123
+ for (const b of status.bitsets) node_kind.set(b.name, "bitset");
15124
+ const deps = /* @__PURE__ */ new Map();
15125
+ for (const e of enum_nodes) {
15126
+ const d = /* @__PURE__ */ new Set();
15127
+ for (const c of e.cases) for (const p of c.params) if (node_kind.has(p.type.name) && p.type.name !== e.name) d.add(p.type.name);
15128
+ deps.set(e.name, d);
15129
+ }
15130
+ for (const st of struct_nodes) {
15131
+ const d = /* @__PURE__ */ new Set();
15132
+ for (const f of st.fields) {
15133
+ if (f.type.is_view) continue;
15134
+ const field_type = mono_struct_name(f.type, status);
15135
+ if (status.structs.find((s) => s.name === field_type && !s.is_simple_type)?.is_class) continue;
15136
+ if (node_kind.has(field_type) && field_type !== st.name) d.add(field_type);
15137
+ }
15138
+ deps.set(st.name, d);
15139
+ }
15140
+ const emitted = /* @__PURE__ */ new Set();
15141
+ const visiting = /* @__PURE__ */ new Set();
15142
+ const node_list = [
15143
+ ...enum_nodes,
15144
+ ...struct_nodes,
15145
+ ...status.bitsets
15146
+ ];
15147
+ const by_name = new Map(node_list.map((n) => [n.name, n]));
15148
+ const visit = (name) => {
15149
+ if (emitted.has(name) || visiting.has(name)) return;
15150
+ visiting.add(name);
15151
+ for (const dep of deps.get(name) ?? []) visit(dep);
15152
+ visiting.delete(name);
15153
+ emitted.add(name);
15154
+ const node = by_name.get(name);
15155
+ if (!node) return;
15156
+ if (node_kind.get(name) === "enum") out += generate_enum_definition(node, status);
15157
+ else if (node_kind.get(name) === "bitset") out += `typedef unsigned long ${nm(name)};\n`;
15158
+ else out += generate_struct_definition(node, status);
15159
+ };
15160
+ for (const n of node_list) visit(n.name);
15161
+ out += "\n";
15162
+ if (status.file_scope_c) {
15163
+ out += status.file_scope_c;
15164
+ out += "\n";
15165
+ }
15166
+ for (const entry of functions) out += generate_c_function(entry.func, entry.struct_name, entry.raw_code, status, entry.bridge_c_abi);
15167
+ return out;
15168
+ }
15169
+ /**
15170
+ * Prefix a Nomen type's C TYPEDEF name with `nm_`. The companion file `#import`s
15171
+ * the platform frameworks (Foundation/Cocoa/UIKit), which drag in a large set of
15172
+ * system typedefs (e.g. macOS `MacTypes.h` defines `typedef long Size`). If we
15173
+ * emitted Nomen's own `typedef struct Size {...} Size;` the typedef name would
15174
+ * collide; mangling only the typedef (`typedef struct Size {...} nm_Size;`)
15175
+ * sidesteps it while leaving the struct TAG (`Size`) untouched.
15176
+ *
15177
+ * Keeping the original tag matters: generated C (spawn/trampoline infra in
15178
+ * build_spawn_node / build_nursery_spawn) and `#arch: aarch64_use_c` raw bodies
15179
+ * reference Nomen types as `struct Foo` (the tag), so an unchanged tag means
15180
+ * none of that code needs to know about mangling. Codegen-generated references
15181
+ * here (fields, params, returns) use the mangled typedef instead.
15182
+ *
15183
+ * `nm_` (no leading underscore: the C standard reserves `_`+lowercase
15184
+ * identifiers at file scope). This is purely cosmetic — the aarch64 assembly
15185
+ * never references type names (only function labels, bridged via `__asm__`).
15186
+ */
15187
+ function nm(name) {
15188
+ return "nm_" + name;
15189
+ }
15190
+ /**
15191
+ * The C type for a field/param/return of the given Nomen type, applying `nm_`
15192
+ * to user-defined struct/enum types and falling back to `c_type` for primitives.
15193
+ * Generic structs lower to opaque 8-byte pointers (their element type lives in
15194
+ * the Nomen `Type`, not in the C layout).
15195
+ */
15196
+ /**
15197
+ * The C type for a value stored BY VALUE in an enum payload: classes and
15198
+ * traits are heap/shared references (pointers, and never have a companion
15199
+ * typedef), everything else follows `companion_type`.
15200
+ */
15201
+ function payload_type(typeName, status) {
15202
+ if (status.structs.find((s) => s.name === typeName)?.is_class) return `struct ${typeName} *`;
15203
+ if (status.traits.find((t) => t.name === typeName)) return `struct ${typeName} *`;
15204
+ return companion_type(typeName, status);
15205
+ }
15206
+ function companion_type(typeName, status) {
15207
+ const struct = status.structs.find((s) => s.name === typeName);
15208
+ if (struct?.is_generic) return "void *";
15209
+ if (struct && !struct.is_simple_type) return nm(typeName);
15210
+ if (!struct && status.enums.find((e) => e.name === typeName)) return nm(typeName);
15211
+ if (status.bitsets.find((b) => b.name === typeName)) return nm(typeName);
15212
+ return c_type(typeName);
15213
+ }
15214
+ /**
15215
+ * Emit an enum's C type definition. Mirrors the C backend's `build_enum_node`
15216
+ * (typedef for simple enums; tag + tagged-union struct for enums with associated
15217
+ * data) but with `nm_`-prefixed TYPEDEF names and no constructor functions (those
15218
+ * are emitted as assembly in the aarch64 path; the companion only needs types so
15219
+ * struct fields / function signatures can reference them). The struct tag keeps
15220
+ * the original Nomen name (see `nm`).
15221
+ */
15222
+ function generate_enum_definition(node, status) {
15223
+ let out = "";
15224
+ if (node.has_associated_data) {
15225
+ out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)}_tag;\n`;
15226
+ out += `struct ${node.name};\n`;
15227
+ out += `typedef struct ${node.name}\n{\n`;
15228
+ out += `${nm(node.name)}_tag tag;\n`;
15229
+ out += `union {\n`;
15230
+ for (const c of node.cases) out += `struct { ${c.params.map((p) => `${payload_type(p.type.name, status)} ${p.name}`).join("; ")}${c.params.length ? ";" : ""} } _${c.name};\n`;
15231
+ out += `} _data;\n`;
15232
+ out += `} ${nm(node.name)};\n`;
15233
+ } else out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)};\n`;
15234
+ return out;
15235
+ }
15236
+ function generate_struct_definition(struct, status) {
15237
+ let out = `typedef struct ${struct.name}\n{\n`;
15238
+ out += `void *_vt;\n`;
15239
+ for (const field of struct.fields) {
15240
+ const member_type = field.type.is_view ? "nomen_view" : payload_type(mono_struct_name(field.type, status), status);
15241
+ out += `${member_type} ${field.name};\n`;
15242
+ }
15243
+ for (const traitName of struct.traits) {
15244
+ const trait = status.traits.find((t) => t.name === traitName);
15245
+ if (!trait) continue;
15246
+ for (const field of trait.fields.filter((f) => !struct.fields.find((nf) => nf.name === f.name))) {
15247
+ const member_type = field.type.is_view ? "nomen_view" : payload_type(mono_struct_name(field.type, status), status);
15248
+ out += `${member_type} ${field.name};\n`;
15249
+ }
15250
+ }
15251
+ out += `} ${nm(struct.name)};\n`;
15252
+ return out;
15253
+ }
15254
+ function generate_c_function(func, struct_name, raw_code, status, bridge_c_abi = false) {
15255
+ const func_label = companion_function_label(func, struct_name, status);
15256
+ const is_class_init = func.name === "#init" && !!(struct_name && status.structs.find((s) => s.name === struct_name));
15257
+ const return_type = is_class_init ? "void" : func.return_type?.name || "void";
15258
+ const return_struct = is_class_init ? void 0 : status.structs.find((s) => s.name === return_type && !s.is_simple_type);
15259
+ let return_prefix = return_struct ? nm(return_type) : companion_type(return_type, status);
15260
+ return_prefix += ` `;
15261
+ const return_enum_size = return_struct ? void 0 : get_enum_sret_size(return_type, status);
15262
+ const return_struct_size = return_struct ? get_struct_size(return_type, status) : 0;
15263
+ const symbol_label = sanitize_companion_label(return_struct && return_struct_size <= 16 || return_enum_size !== void 0 && return_enum_size <= 16 || bridge_c_abi ? `${func_label}_c` : func_label);
15264
+ const params = func.params;
15265
+ let param_list = "";
15266
+ for (let i = 0; i < params.length; i++) {
15267
+ if (i > 0) param_list += ", ";
15268
+ param_list += generate_c_param(params[i], status);
15269
+ }
15270
+ if (params.length === 0) param_list += `void`;
15271
+ let out = `// ${func_label}\n`;
15272
+ out += `${return_prefix}${symbol_label}(${param_list}) __asm__("${symbol_label}");\n`;
15273
+ out += `${return_prefix}${symbol_label}(${param_list})\n{\n`;
15274
+ if (func.name === "#init" && struct_name) {
15275
+ const ctor_struct = status.structs.find((s) => s.name === struct_name);
15276
+ if (ctor_struct && !ctor_struct.is_simple_type) {
15277
+ if (ctor_struct.traits.length > 0) {
15278
+ out += `extern void *${struct_name}_traits;\n`;
15279
+ out += `self->_vt = (void *)&${struct_name}_traits;\n`;
15280
+ } else if (!ctor_struct.is_class) out += `self->_vt = (void *)1;\n`;
15281
+ for (const field of ctor_struct.fields) {
15282
+ if (!field.value) continue;
15283
+ let seeded;
15284
+ if (field.value.node_type === "func_call" && field.value.params?.length === 0) seeded = emit_zero_ctor_field_default(field.type.name, field.type.type_args, `self->${field.name}`, status, /* @__PURE__ */ new Set());
15285
+ if (!seeded) seeded = emit_ctor_field_default(ctor_struct, field);
15286
+ if (!seeded) {
15287
+ if (!status.build_errors) status.build_errors = [];
15288
+ status.build_errors.push({
15289
+ message: `mv struct '${struct_name}' has a use_c constructor and field '${field.name}' has a non-literal default — assign it in the #init body`,
15290
+ start: func.start
15291
+ });
15292
+ continue;
15293
+ }
15294
+ out += seeded;
15295
+ }
15296
+ }
15297
+ }
15298
+ const self_param = params[0];
15299
+ if (struct_name && self_param?.is_self_param && !self_param?.is_ref && func.name !== "#destroy" && func.name !== "#init") {
15300
+ const struct = status.structs.find((s) => s.name === struct_name);
15301
+ if (struct && !struct.is_simple_type) out += `${nm(struct_name)} _self = *self;\n`;
15302
+ }
15303
+ out += raw_code;
15304
+ out += `\n}\n\n`;
15305
+ return out;
15306
+ }
15307
+ /**
15308
+ * The C statement seeding a defaulted field in a companion constructor, or
15309
+ * `undefined` when the default is not a literal shape. Mirrors the ASM-side
15310
+ * ctor's default seeding for the shapes a `use_c` constructor can express:
15311
+ * string literals borrow static storage (a function-static C literal — never
15312
+ * strdup'd, matching the ASM literal-pair store), scalar/bool literals store
15313
+ * directly. Nullable and computed defaults are rejected — the caller turns
15314
+ * that into a build error naming the field.
15315
+ */
15316
+ function emit_ctor_field_default(struct_node, field) {
15317
+ const value = field.value?.node_type === "value" ? field.value.value : void 0;
15318
+ if (value === void 0 || typeof value !== "string") return void 0;
15319
+ if (field.type.is_nullable) return void 0;
15320
+ if (field.type.name === "string" && !field.type.is_view) {
15321
+ const length = string_literal_length(value);
15322
+ const label = `_${struct_node.name}_${field.name}_default`;
15323
+ return `static const char ${label}[] = ${escape_asciz(value)};\nself->${field.name} = (nomen_string){ (char *)${label}, ${length} };\n`;
15324
+ }
15325
+ if (value === "true" || value === "false") return `self->${field.name} = ${value === "true" ? 1 : 0};\n`;
15326
+ if (/^-?\d+$/.test(value)) return `self->${field.name} = ${value};\n`;
15327
+ }
15328
+ /**
15329
+ * The zero-arg CONSTRUCTOR-call field default (`var List<ClickBinding>
15330
+ * clicks = List<ClickBinding>()`) in a use_c ctor's companion prologue:
15331
+ * the System containers' zero-arg init is exactly stamp/traits + zeroed
15332
+ * fields (every container field default is a literal zero or a nested
15333
+ * container ctor call), so seed that shape DIRECTLY. A companion→asm call
15334
+ * (`List_T_init(&self->f)`) is not available here — method labels are
15335
+ * file-local outside system builds, so the companion object could not link
15336
+ * the reference. Returns undefined for anything unsupported (a custom
15337
+ * zero-arg init could have side effects; the caller's build error names
15338
+ * the field). `seen` guards the container nesting (List → Buffer).
15339
+ */
15340
+ function emit_zero_ctor_field_default(type_name, type_args, target, status, seen) {
15341
+ const resolved = type_args?.length && status.structs.some((s) => s.name === mono_type_name(type_name, type_args)) ? mono_type_name(type_name, type_args) : type_name;
15342
+ if (!is_load_deep_copy_container(resolved)) return void 0;
15343
+ const struct = status.structs.find((s) => s.name === resolved);
15344
+ if (!struct || struct.is_generic || struct.is_simple_type || struct.is_class) return void 0;
15345
+ if (struct.functions.some((f) => f.name === "#init" && f.has_body)) return void 0;
15346
+ if (seen.has(resolved)) return "";
15347
+ seen.add(resolved);
15348
+ let out = "";
15349
+ if (struct.traits.length > 0) {
15350
+ out += `extern void *${resolved}_traits;\n`;
15351
+ out += `${target}._vt = (void *)&${resolved}_traits;\n`;
15352
+ } else out += `${target}._vt = (void *)1;\n`;
15353
+ for (const f of struct.fields) {
15354
+ if (f.type.is_nullable) return void 0;
15355
+ const fv = f.value?.node_type === "value" ? f.value.value : void 0;
15356
+ if (f.value?.node_type === "func_call") {
15357
+ const nested = emit_zero_ctor_field_default(f.type.name, f.type.type_args, `${target}.${f.name}`, status, seen);
15358
+ if (nested === void 0) return void 0;
15359
+ out += nested;
15360
+ continue;
15361
+ }
15362
+ if (typeof fv !== "string") return void 0;
15363
+ if (f.type.name === "string" && !f.type.is_view && fv.startsWith("\"")) return;
15364
+ if (fv !== "0") return void 0;
15365
+ out += `${target}.${f.name} = 0;\n`;
15366
+ }
15367
+ return out;
15368
+ }
15369
+ function generate_c_param(param, status) {
15370
+ const struct_type = status.structs.find((s) => s.name === param.type.name);
15371
+ const trait_type = status.traits.find((t) => t.name === param.type.name);
15372
+ const is_struct = (param.is_self_param || struct_type || trait_type) && !struct_type?.is_simple_type;
15373
+ let out = "";
15374
+ if (param.is_variadic) out += `long _${param.name}_len, `;
15375
+ out += companion_type(param.type.name, status);
15376
+ if (is_struct || param.declaration === "var" || param.type.is_ref || param.type.is_array) out += ` *`;
15377
+ else out += ` `;
15378
+ out += param.name;
15379
+ return out;
15380
+ }
15381
+ //#endregion
15382
+ //#region ../src/build_aarch64/utils/raw_reload.ts
15383
+ /** Width-aware slot reload — mirrors the prologue's store width so the
15384
+ * register holds exactly the value a body read of the slot would produce
15385
+ * (sub-word stores truncate; a same-width load zero-extends like the
15386
+ * emitters' own slot reads). */
15387
+ function raw_slot_reload_line(reg, offset, size) {
15388
+ const wreg = reg.replace("x", "w");
15389
+ if (size === 1) return `ldrb ${wreg}, [x29, #${offset}]`;
15390
+ if (size === 2) return `ldrh ${wreg}, [x29, #${offset}]`;
15391
+ if (size === 4) return `ldr ${wreg}, [x29, #${offset}]`;
15392
+ return `ldr ${reg}, [x29, #${offset}]`;
15393
+ }
15394
+ /** Capture status.code.length as the entry point and install the plan. */
15395
+ function install_raw_reload_plan(status, lines) {
15396
+ const plan = {
15397
+ entry_marker: asm_code_len(status),
15398
+ lines
15399
+ };
15400
+ status.raw_param_reloads = plan;
15401
+ return plan;
15402
+ }
15403
+ /**
15404
+ * The reload lines a raw block needs: none at the function's entry point
15405
+ * (the registers still hold the entry values), otherwise the lines for the
15406
+ * ABI registers the block's text references. Registers are matched against
15407
+ * comment-stripped instruction text only — a mention in a `//` comment, a
15408
+ * label, or a data directive doesn't force a reload (same discipline as
15409
+ * build_inline_method's count_x19_reads).
15410
+ */
15411
+ function raw_reload_lines_for(code, plan, current_length) {
15412
+ if (!plan || plan.lines.length === 0 || current_length === plan.entry_marker) return [];
15413
+ const referenced = /* @__PURE__ */ new Set();
15414
+ for (const raw_line of code.split("\n")) {
15415
+ const comment = raw_line.indexOf("//");
15416
+ const line = (comment === -1 ? raw_line : raw_line.slice(0, comment)).trim();
15417
+ if (!line || line.endsWith(":") || line.startsWith(".")) continue;
15418
+ for (const match of line.matchAll(/\b[wx][0-9]\b/g)) referenced.add(match[0]);
15419
+ }
15420
+ const out = [];
15421
+ for (const entry of plan.lines) if (referenced.has(entry.reg) || referenced.has(entry.reg.replace("x", "w"))) out.push(entry.asm);
15422
+ return out;
15423
+ }
15424
+ //#endregion
14795
15425
  //#region ../src/build_aarch64/build_raw_node.ts
14796
15426
  function build_raw_node$1(node, status) {
14797
15427
  const { should_emit, code, is_c, scope } = parse_raw_directives(node.value, "aarch64", status.platform);
@@ -14801,8 +15431,11 @@ function build_raw_node$1(node, status) {
14801
15431
  if (!status.file_scope_c) status.file_scope_c = "";
14802
15432
  status.file_scope_c += `${code}\n`;
14803
15433
  } else {
14804
- if (!status.c_companion) status.c_companion = "";
14805
- status.c_companion += `${code}\n`;
15434
+ if (!status.build_errors) status.build_errors = [];
15435
+ status.build_errors.push({
15436
+ message: `aarch64 cannot splice an inline C raw block into a Nomen body — a C raw block is emitted only as a function's whole body; move this block into its own aarch64_use_c function and pass values as parameters`,
15437
+ start: node.start
15438
+ });
14806
15439
  }
14807
15440
  } else {
14808
15441
  for (const line of raw_reload_lines_for(code, status.raw_param_reloads, asm_code_len(status))) emit_asm(status, `${line}\n`);
@@ -14853,6 +15486,16 @@ function check_c_fallback(func, struct_name, status) {
14853
15486
  else raw_code += parsed.code + "\n";
14854
15487
  }
14855
15488
  }
15489
+ let float_bridge = has_float_abi_divergence(func);
15490
+ if (float_bridge && func.params.some((p) => p.is_variadic)) {
15491
+ if (!status.build_errors) status.build_errors = [];
15492
+ const label = struct_name ? `${struct_name}.${func.name.replace(/#/g, "")}` : func.name;
15493
+ status.build_errors.push({
15494
+ message: `Function ${label} mixes variadic params with floats — no aarch64_use_c ABI thunk for that shape`,
15495
+ start: func.start
15496
+ });
15497
+ float_bridge = false;
15498
+ }
14856
15499
  if (is_c_fallback) {
14857
15500
  if (file_scope_code) {
14858
15501
  if (!status.file_scope_c) status.file_scope_c = "";
@@ -14862,38 +15505,84 @@ function check_c_fallback(func, struct_name, status) {
14862
15505
  status.c_companion_functions.push({
14863
15506
  func,
14864
15507
  struct_name,
14865
- raw_code
15508
+ raw_code,
15509
+ bridge_c_abi: float_bridge
14866
15510
  });
14867
- const return_type = func.name === "#init" && !!struct_name ? "" : func.return_type?.name || "";
15511
+ const is_ctor = func.name === "#init" && !!struct_name;
15512
+ const return_type = is_ctor ? "" : func.return_type?.name || "";
14868
15513
  const return_struct = status.structs.find((s) => s.name === return_type && !s.is_simple_type && !s.is_class);
14869
15514
  const enum_sret_size = get_enum_sret_size(return_type, status);
15515
+ let sret_size;
14870
15516
  if (return_struct) {
14871
15517
  const struct_size = get_struct_size(return_type, status);
14872
- if (struct_size <= 16) emit_struct_return_thunk(func, struct_name, status, struct_size);
15518
+ if (struct_size <= 16) sret_size = struct_size;
14873
15519
  } else if (enum_sret_size !== void 0) {
14874
- if (enum_sret_size <= 16) emit_struct_return_thunk(func, struct_name, status, enum_sret_size);
15520
+ if (enum_sret_size <= 16) sret_size = enum_sret_size;
14875
15521
  }
15522
+ if (float_bridge) emit_c_abi_thunk(func, struct_name, status, is_ctor ? void 0 : sret_size);
15523
+ else if (!is_ctor && sret_size !== void 0) emit_c_abi_thunk(func, struct_name, status, sret_size);
14876
15524
  return true;
14877
15525
  }
14878
15526
  return false;
14879
15527
  }
14880
- function emit_struct_return_thunk(func, struct_name, status, struct_size) {
15528
+ /**
15529
+ * Does this parameter type ride the C ABI's d-register file? Nomen `float*`
15530
+ * types are 64-bit doubles (`c_type: "double"`).
15531
+ */
15532
+ function is_float_type_name(name) {
15533
+ return name.startsWith("float") || name.startsWith("ufloat");
15534
+ }
15535
+ /** Any float param, or a float return? (see emit_c_abi_thunk case 2) */
15536
+ function has_float_abi_divergence(func) {
15537
+ if (is_float_type_name(func.return_type?.name || "")) return true;
15538
+ return func.params.some((p) => !p.is_variadic && is_float_type_name(p.type.name));
15539
+ }
15540
+ function emit_c_abi_thunk(func, struct_name, status, struct_size) {
14881
15541
  const func_label = get_func_label(func, struct_name, status);
14882
- const c_label = `${func_label}_c`;
14883
- const words = Math.ceil(struct_size / 8);
15542
+ const c_label = sanitize_companion_label(`${func_label}_c`);
15543
+ const words = struct_size !== void 0 ? Math.ceil(struct_size / 8) : 0;
15544
+ const return_is_float = is_float_type_name(func.return_type?.name || "");
14884
15545
  emit_asm(status, `.p2align 2\n`);
14885
15546
  emit_asm(status, `${func_label}:\n`);
14886
15547
  emit_asm(status, `stp x29, x30, [sp, #-16]!\n`);
14887
- emit_asm(status, `sub sp, sp, #16\n`);
14888
- emit_asm(status, `str x8, [sp, #0]\n`);
14889
- emit_asm(status, `bl ${c_label}\n`);
14890
- emit_asm(status, `ldr x8, [sp, #0]\n`);
14891
- for (let i = 0; i < words; i++) {
14892
- const reg = i === 0 ? "x0" : i === 1 ? "x1" : `x${i}`;
14893
- if (i === 0) emit_asm(status, `str ${reg}, [x8]\n`);
14894
- else emit_asm(status, `str ${reg}, [x8, #${i * 8}]\n`);
15548
+ emit_asm(status, `mov x29, sp\n`);
15549
+ if (struct_size !== void 0) {
15550
+ emit_asm(status, `sub sp, sp, #16\n`);
15551
+ emit_asm(status, `str x8, [sp, #0]\n`);
15552
+ }
15553
+ let in_slot = 0;
15554
+ let out_x = 0;
15555
+ let out_d = 0;
15556
+ const x_moves = [];
15557
+ const d_moves = [];
15558
+ for (const param of func.params) {
15559
+ if (is_float_type_name(param.type.name)) {
15560
+ d_moves.push({
15561
+ out: out_d++,
15562
+ in: in_slot
15563
+ });
15564
+ in_slot += 1;
15565
+ continue;
15566
+ }
15567
+ const slots = param.type.name === "string" || param.type.is_view ? 2 : 1;
15568
+ x_moves.push({
15569
+ out: out_x,
15570
+ in: in_slot
15571
+ });
15572
+ out_x += slots;
15573
+ in_slot += slots;
14895
15574
  }
14896
- emit_asm(status, `add sp, sp, #16\n`);
15575
+ for (const m of d_moves) emit_asm(status, `fmov d${m.out}, x${m.in}\n`);
15576
+ for (let i = x_moves.length - 1; i >= 0; i--) {
15577
+ const { out, in: inc } = x_moves[i];
15578
+ if (out !== inc) emit_asm(status, `mov x${out}, x${inc}\n`);
15579
+ }
15580
+ emit_asm(status, `bl ${c_label}\n`);
15581
+ if (struct_size !== void 0) {
15582
+ emit_asm(status, `ldr x8, [sp, #0]\n`);
15583
+ for (let i = 0; i < words; i++) emit_asm(status, `str ${i === 0 ? "x0" : i === 1 ? "x1" : `x${i}`}, [x8, #${i * 8}]\n`);
15584
+ emit_asm(status, `add sp, sp, #16\n`);
15585
+ } else if (return_is_float) emit_asm(status, `fmov x0, d0\n`);
14897
15586
  emit_asm(status, `ldp x29, x30, [sp], #16\n`);
14898
15587
  emit_asm(status, `ret\n`);
14899
15588
  }
@@ -16875,9 +17564,11 @@ function is_borrow_accessor_carrier_stamp(decl) {
16875
17564
  * them; the `_recv_N` carrier skips its destroy entirely (it must not free the
16876
17565
  * ALIASED string fields), so each expression use leaked the copied containers.
16877
17566
  * Free exactly the fields the load deep-copied — the same predicate — and only
16878
- * those: plain strings, nested structs and enum fields stay aliased/untouched
16879
- * (they were not deep-copied, so freeing them would be an invalid free).
16880
- * Mirrors the C backend's `emit_load_owned_copies_destroy`.
17567
+ * those: plain strings and nested structs stay aliased/untouched (they were
17568
+ * not deep-copied, so freeing them would be an invalid free). Enum-with-data
17569
+ * fields ride the same gate the load's strdup arm used (enum_copy_is_deep) —
17570
+ * their copied payloads are freed here. Mirrors the C backend's
17571
+ * `emit_load_owned_copies_destroy`.
16881
17572
  */
16882
17573
  function emit_borrow_carrier_owned_free(status, elem, decl_name) {
16883
17574
  let offset = 8;
@@ -16899,10 +17590,18 @@ function emit_borrow_carrier_owned_free(status, elem, decl_name) {
16899
17590
  continue;
16900
17591
  }
16901
17592
  const field_struct = resolve_struct_type(field.type, status);
16902
- 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")) {
17593
+ if (field_struct && field_load_deep_copy(field_struct, status)) {
16903
17594
  emit_var_address(status, "x0", decl_name);
16904
17595
  emit_asm(status, `add x0, x0, #${offset}\n`);
16905
17596
  emit_asm(status, `bl ${field_struct.name}_destroy\n`);
17597
+ offset += size;
17598
+ continue;
17599
+ }
17600
+ const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
17601
+ if (field_enum && enum_copy_is_deep$1(field_enum, status)) {
17602
+ emit_var_address(status, "x0", decl_name);
17603
+ emit_asm(status, `add x0, x0, #${offset}\n`);
17604
+ emit_enum_payload_frees_at(status, field_enum.name, "x0");
16906
17605
  }
16907
17606
  }
16908
17607
  offset += size;
@@ -17214,6 +17913,12 @@ function emit_field_destroys_at(status, struct_type, root_slot, acc, root_is_cla
17214
17913
  const offset = get_field_offset_of_fields(fields, field.name, status);
17215
17914
  const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
17216
17915
  const field_acc = acc + offset;
17916
+ if (field.type.is_array && field_struct && !field_struct.is_class) {
17917
+ const elem_size = get_struct_size(field_struct.name, status);
17918
+ const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
17919
+ for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, field_struct, () => emit_slot_base(status, root_slot, root_is_class, field_acc), i * elem_size);
17920
+ continue;
17921
+ }
17217
17922
  if (field_struct) {
17218
17923
  if (field_struct.is_class && !field.type.is_ref) {
17219
17924
  emit_slot_base(status, root_slot, root_is_class, field_acc);
@@ -17398,6 +18103,10 @@ function release_records_for_scope_exit(status, decl, moved) {
17398
18103
  * before.
17399
18104
  */
17400
18105
  function emit_moved_local_cleanup(status, decl) {
18106
+ if (decl.type.is_array && decl.type.length) {
18107
+ emit_fixed_array_local_destroys(status, decl);
18108
+ return;
18109
+ }
17401
18110
  const owns_flag = status.mv_owns_flag?.get(decl.name);
17402
18111
  if (owns_flag !== void 0 && decl.type.name === "string" && !decl.type.is_array && status.heap_strings?.has(decl.name)) {
17403
18112
  const skip = `.Lskip_mvowns_${status.label_counter = (status.label_counter ?? 0) + 1}`;
@@ -17515,6 +18224,31 @@ function emit_heap_array_decl_destroy(status, decl_name) {
17515
18224
  }
17516
18225
  return false;
17517
18226
  }
18227
+ /**
18228
+ * A fixed-size array LOCAL of value-struct elements: destroy each element in
18229
+ * place (elem_size stride from the var's frame slot). `resolve_decl_struct`
18230
+ * resolves the ELEMENT struct by name — array-ness is a Type flag — so the
18231
+ * single-struct walk would reclaim element 0's fields only and leak the rest
18232
+ * (one label per extra element on a conformer row). Returns true when the
18233
+ * declaration was an array (handled or nothing to do); the caller `continue`s
18234
+ * so the single-struct path never mistreats the row.
18235
+ */
18236
+ function emit_fixed_array_local_destroys(status, decl) {
18237
+ if (!decl.type.is_array || !decl.type.length) return false;
18238
+ const arr_len = parseInt(decl.type.length.value || "0");
18239
+ const elem_struct = is_struct_type$4(decl.type.name, status);
18240
+ if (arr_len > 0 && elem_struct && !elem_struct.is_class && (has_destroy(elem_struct) || struct_needs_destroy(elem_struct, status))) {
18241
+ const elem_size = get_struct_size(elem_struct.name, status);
18242
+ const was_moved = status.moved?.has(decl.name) ?? false;
18243
+ status.moved?.delete(decl.name);
18244
+ for (let i = 0; i < arr_len; i++) {
18245
+ emit_var_address(status, "x0", decl.name);
18246
+ emit_destroy_for_decl(status, decl.name, elem_struct.name, i * elem_size, decl.type.type_args, false);
18247
+ }
18248
+ if (was_moved) status.moved?.add(decl.name);
18249
+ }
18250
+ return true;
18251
+ }
17518
18252
  function emit_destroy_for_decl(status, decl_name, decl_type_name, addr_offset, type_args, is_nullable) {
17519
18253
  const moved = status.moved ?? /* @__PURE__ */ new Set();
17520
18254
  release_records_for_scope_exit(status, {
@@ -17654,6 +18388,13 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
17654
18388
  continue;
17655
18389
  }
17656
18390
  const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
18391
+ if (field.type.is_array && field_struct && !field_struct.is_class) {
18392
+ const elem_size = get_struct_size(field_struct.name, status);
18393
+ const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
18394
+ const actual_base = base_offset !== void 0 ? base_offset + offset : offset;
18395
+ for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, field_struct, () => emit_base_ptr(status, decl_name, is_class_parent), actual_base + i * elem_size);
18396
+ continue;
18397
+ }
17657
18398
  if (field_struct) {
17658
18399
  if (field_struct.is_class && !field.type.is_ref) {
17659
18400
  if (decl_name) emit_base_ptr(status, decl_name, is_class_parent);
@@ -17688,7 +18429,7 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
17688
18429
  emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
17689
18430
  }
17690
18431
  }
17691
- if (field_has_explicit_destroy) emit_nested_field_destroys(status, field_struct, decl_name, base_offset !== void 0 ? base_offset + offset : offset, is_class_parent);
18432
+ if (field_has_explicit_destroy) emit_nested_field_destroys(status, field_struct, () => emit_base_ptr(status, decl_name, is_class_parent), base_offset !== void 0 ? base_offset + offset : offset);
17692
18433
  }
17693
18434
  } else if (free_strings && field.type.name === "string" && !field.type.is_array && !field.type.is_ref && !field.type.is_view) {
17694
18435
  const actual_offset = base_offset !== void 0 ? base_offset + offset : offset;
@@ -17697,6 +18438,9 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
17697
18438
  emit_free(status);
17698
18439
  } else if (field.type.name === "func" && !field.type.is_array && (struct_type.is_class || struct_type.is_owning === true)) {
17699
18440
  const actual_offset = base_offset !== void 0 ? base_offset + offset : offset;
18441
+ const func_field_borrow = !!decl_name && !!status.func_field_borrow_decls?.has(decl_name);
18442
+ if (func_field_borrow) status.func_field_borrow_decls?.delete(decl_name);
18443
+ if (func_field_borrow) continue;
17700
18444
  if (decl_name) emit_base_ptr(status, decl_name, is_class_parent);
17701
18445
  const id = status.label_counter = (status.label_counter ?? 0) + 1;
17702
18446
  const skip = `.Lfield_closure_done_${id}`;
@@ -17723,37 +18467,38 @@ function emit_field_destroys(status, struct_type, decl_name, base_offset, is_cla
17723
18467
  const elem_size = get_struct_size(field.type.name, status);
17724
18468
  const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
17725
18469
  const actual_base = base_offset !== void 0 ? base_offset + offset : offset;
17726
- for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, elem_struct, decl_name, actual_base + i * elem_size, is_class_parent);
18470
+ for (let i = 0; i < arr_len; i++) emit_destroy_for_array_elem(status, elem_struct, () => emit_base_ptr(status, decl_name, is_class_parent), actual_base + i * elem_size);
17727
18471
  }
17728
18472
  }
17729
18473
  }
17730
18474
  }
17731
- function emit_nested_field_destroys(status, struct_type, decl_name, base_offset, is_class_parent) {
18475
+ function emit_nested_field_destroys(status, struct_type, emit_base, base_offset) {
17732
18476
  for (const field of struct_type.fields) {
17733
18477
  const offset = get_field_offset_of_fields(struct_type.fields, field.name, status);
17734
18478
  const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
17735
18479
  if (field_struct) {
17736
18480
  if (has_destroy(field_struct)) {
17737
- emit_base_ptr(status, decl_name, is_class_parent);
18481
+ emit_base();
17738
18482
  emit_asm(status, `add x0, x0, #${base_offset + offset}\n`);
17739
18483
  emit_asm(status, `bl ${resolve_struct_name(field_struct.name, field.type.type_args, status)}_destroy\n`);
17740
18484
  }
17741
- emit_nested_field_destroys(status, field_struct, decl_name, base_offset + offset, is_class_parent);
18485
+ emit_nested_field_destroys(status, field_struct, emit_base, base_offset + offset);
17742
18486
  }
17743
18487
  }
17744
18488
  }
17745
- function emit_destroy_for_array_elem(status, struct_type, decl_name, elem_offset, is_class_parent) {
17746
- if (has_destroy(struct_type)) {
17747
- emit_base_ptr(status, decl_name, is_class_parent);
18489
+ function emit_destroy_for_array_elem(status, struct_type, emit_base, elem_offset) {
18490
+ if (has_destroy(struct_type) || struct_type.is_class || struct_needs_auto_destroy(struct_type, status)) {
18491
+ emit_base();
17748
18492
  emit_asm(status, `add x0, x0, #${elem_offset}\n`);
17749
18493
  emit_asm(status, `bl ${struct_type.name}_destroy\n`);
18494
+ if (struct_type.is_class || struct_type.is_owning === true) return;
17750
18495
  }
17751
18496
  let offset = 8;
17752
18497
  for (const field of struct_type.fields) {
17753
18498
  const field_struct = is_struct_type$4(resolve_struct_name(field.type.name, field.type.type_args, status), status) || is_struct_type$4(field.type.name, status);
17754
18499
  if (field_struct) {
17755
18500
  const field_size = get_type_size(field.type, status);
17756
- emit_nested_field_destroys(status, field_struct, decl_name, elem_offset + offset, is_class_parent);
18501
+ emit_nested_field_destroys(status, field_struct, emit_base, elem_offset + offset);
17757
18502
  offset += field_size;
17758
18503
  } else offset += get_type_size(field.type, status);
17759
18504
  }
@@ -17924,6 +18669,8 @@ function emit_destroy_for_scope(status, declarations_before) {
17924
18669
  continue;
17925
18670
  }
17926
18671
  if (is_field_struct_borrow(decl)) continue;
18672
+ if (is_pure_borrow_element_load(decl, status)) continue;
18673
+ if (emit_fixed_array_local_destroys(status, decl)) continue;
17927
18674
  const resolved_decl = resolve_decl_struct(decl, status);
17928
18675
  if (!resolved_decl) continue;
17929
18676
  const struct_type = resolved_decl.struct_type;
@@ -18048,6 +18795,8 @@ function emit_destroy_for_scope(status, declarations_before) {
18048
18795
  continue;
18049
18796
  }
18050
18797
  if (is_field_struct_borrow(decl)) continue;
18798
+ if (is_pure_borrow_element_load(decl, status)) continue;
18799
+ if (emit_fixed_array_local_destroys(status, decl)) continue;
18051
18800
  const resolved_decl = resolve_decl_struct(decl, status);
18052
18801
  if (!resolved_decl) continue;
18053
18802
  const struct_type = resolved_decl.struct_type;
@@ -18188,6 +18937,7 @@ function emit_owning_field_strdups_at(status, struct_name, base_reg, base_offset
18188
18937
  if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
18189
18938
  const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
18190
18939
  if (!field_struct) continue;
18940
+ if (field_load_deep_copy(field_struct, status)) continue;
18191
18941
  if (!(has_destroy(field_struct) ? owns_strings_uniformly(field_struct) : struct_needs_auto_destroy(field_struct, status))) continue;
18192
18942
  const nested_off = base_offset + get_field_offset(struct_name, field.name, status);
18193
18943
  if (is_nullable_struct_type(field.type, status)) {
@@ -21107,10 +21857,14 @@ function emit_transfer_owning_fields$1(status, src, fields) {
21107
21857
  }
21108
21858
  /**
21109
21859
  * Emit the deep-copy half of mv load_T into the sret buffer (x8): for
21110
- * each `List<T>` field whose mono has a `copy` method, replace the
21111
- * shallow-copied field with `<List_T>_copy(&slot.field)` (sret → the sret
21860
+ * each field that `field_load_deep_copy` accepts (the containers, and
21861
+ * nested mv user structs whose auto-derived `copy` is deep), replace the
21862
+ * shallow-copied field with `<Mono>_copy(&slot.field)` (sret → the sret
21112
21863
  * buffer's field; self == x8 is safe — the copy reads its input before the
21113
- * final result write). Mirrors the C backend's `emit_load_deep_copy_fields`.
21864
+ * final result write); a `Box<T>` field is cloned, and an enum-with-data
21865
+ * field whose payloads are all deep-copyable gets its string payloads
21866
+ * strdup'd in place (the enum_copy_is_deep gate). Mirrors the C backend's
21867
+ * `emit_load_deep_copy_fields`.
21114
21868
  * `sret` is the register holding the returned struct's address; `slot` holds
21115
21869
  * the source slot's address (the copy reads from the slot, writes to sret).
21116
21870
  */
@@ -21134,11 +21888,15 @@ function emit_load_deep_copy_fields$1(elem, status, sret, slot) {
21134
21888
  continue;
21135
21889
  }
21136
21890
  const field_struct = resolve_struct_type(field.type, status);
21137
- 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")) {
21891
+ if (field_struct && field_load_deep_copy(field_struct, status)) {
21138
21892
  emit_asm(status, `add x8, ${sret}, #${offset}\n`);
21139
21893
  emit_asm(status, `add x0, ${slot}, #${offset}\n`);
21140
21894
  emit_asm(status, `bl ${field_struct.name}_copy\n`);
21895
+ offset += size;
21896
+ continue;
21141
21897
  }
21898
+ const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
21899
+ if (field_enum && enum_copy_is_deep$1(field_enum, status)) emit_enum_payload_strdups_at(status, field_enum.name, sret, offset);
21142
21900
  }
21143
21901
  offset += size;
21144
21902
  }
@@ -21246,12 +22004,20 @@ function emit_owning_buffer_inline_aarch64(struct_node, func_name, status) {
21246
22004
  function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
21247
22005
  const elem = owning_buffer_element_aarch64(node, status);
21248
22006
  if (elem) {
21249
- if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify" && func_name !== "load") return false;
22007
+ if (func_name !== "store" && func_name !== "replace" && func_name !== "shift" && func_name !== "modify" && func_name !== "load" && func_name !== "alloc") return false;
22008
+ if (func_name === "alloc") {
22009
+ emit_owning_alloc_T(elem, status);
22010
+ return true;
22011
+ }
21250
22012
  emit_owning_standalone_struct(elem, func_name, status);
21251
22013
  return true;
21252
22014
  }
21253
22015
  const enum_elem = owning_buffer_enum_element_aarch64(node, status);
21254
22016
  if (enum_elem) {
22017
+ if (func_name === "alloc") {
22018
+ emit_enum_alloc_T(enum_elem, status);
22019
+ return true;
22020
+ }
21255
22021
  if (func_name === "store") {
21256
22022
  emit_enum_store_T(enum_elem, status, "x19");
21257
22023
  return true;
@@ -21271,6 +22037,10 @@ function emit_owning_buffer_standalone_aarch64(node, func_name, status) {
21271
22037
  return false;
21272
22038
  }
21273
22039
  if (owning_buffer_is_string_elem_aarch64(node)) {
22040
+ if (func_name === "alloc") {
22041
+ emit_string_alloc_T(status);
22042
+ return true;
22043
+ }
21274
22044
  if (func_name === "store") {
21275
22045
  emit_string_store_T(status, "x19");
21276
22046
  return true;
@@ -21338,6 +22108,7 @@ function emit_owning_standalone_struct(elem, func_name, status) {
21338
22108
  emit_asm(status, `madd x4, x1, x22, xzr\n`);
21339
22109
  emit_asm(status, `add x20, x20, x4\n`);
21340
22110
  emit_asm(status, `mov x21, x2\n`);
22111
+ if (func_name === "store") emit_displaced_owned_reclaim_a64(status, elem, "x20", "x21");
21341
22112
  if (func_name === "replace") emit_guarded_slot_destroy(status, elem, "x20");
21342
22113
  if (func_name === "store" && string_fields.length) {
21343
22114
  emit_asm(status, `sub sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
@@ -21621,6 +22392,7 @@ function emit_owning_store_T(elem, status) {
21621
22392
  emit_asm(status, `madd x4, x1, x22, xzr\n`);
21622
22393
  emit_asm(status, `add x20, x20, x4\n`);
21623
22394
  emit_asm(status, `mov x21, x2\n`);
22395
+ emit_displaced_owned_reclaim_a64(status, elem, "x20", "x21");
21624
22396
  if (string_fields.length) {
21625
22397
  emit_asm(status, `sub sp, sp, #${Math.ceil(string_fields.length * 8 / 16) * 16}\n`);
21626
22398
  string_fields.forEach((f, i) => {
@@ -21692,6 +22464,86 @@ function emit_owning_replace_T(elem, status) {
21692
22464
  * `#destroy` body would fire on empty cap slots. `slot_reg` must hold the
21693
22465
  * slot ADDRESS and is clobbered only by the destroy call itself.
21694
22466
  */
22467
+ /**
22468
+ * Specialized `alloc` for `Buffer<string>`: free each old slot's ptr half
22469
+ * (zeroed slots free NULL — a no-op), free the slab, calloc the exact fresh
22470
+ * slab. x19 = self, x1 = size; returns cap.
22471
+ */
22472
+ function emit_string_alloc_T(status) {
22473
+ const lbl = (s) => `.Lsalloc_${s}_${status.label_counter = (status.label_counter ?? 0) + 1}`;
22474
+ const fresh = lbl("fresh");
22475
+ const drop = lbl("drop");
22476
+ const loop = lbl("loop");
22477
+ const skip = lbl("skip");
22478
+ emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
22479
+ emit_asm(status, `str x22, [sp, #-16]!\n`);
22480
+ emit_asm(status, `mov x22, x1\n`);
22481
+ emit_asm(status, `ldr x20, [x19, #8]\n`);
22482
+ emit_asm(status, `cbz x20, ${fresh}\n`);
22483
+ emit_asm(status, `ldr x21, [x19, #16]\n`);
22484
+ emit_asm(status, `cbz x21, ${drop}\n`);
22485
+ emit_asm(status, `${loop}:\n`);
22486
+ emit_asm(status, `sub x21, x21, #1\n`);
22487
+ emit_asm(status, `lsl x9, x21, #4\n`);
22488
+ emit_asm(status, `add x9, x20, x9\n`);
22489
+ emit_asm(status, `ldr x0, [x9]\n`);
22490
+ emit_asm(status, `cbz x0, ${skip}\n`);
22491
+ emit_free(status);
22492
+ emit_asm(status, `${skip}:\n`);
22493
+ emit_asm(status, `cbnz x21, ${loop}\n`);
22494
+ emit_asm(status, `${drop}:\n`);
22495
+ emit_asm(status, `mov x0, x20\n`);
22496
+ emit_free(status);
22497
+ emit_asm(status, `${fresh}:\n`);
22498
+ emit_asm(status, `mov x0, x22\n`);
22499
+ emit_asm(status, `mov x1, #16\n`);
22500
+ emit_asm(status, status.audit ? `bl _nomen_calloc_wrap\n` : `bl _calloc\n`);
22501
+ emit_asm(status, `str x0, [x19, #8]\n`);
22502
+ emit_asm(status, `str x22, [x19, #16]\n`);
22503
+ emit_asm(status, `ldr x0, [x19, #16]\n`);
22504
+ emit_asm(status, `ldr x22, [sp], #16\n`);
22505
+ emit_asm(status, `ldp x20, x21, [sp], #16\n`);
22506
+ }
22507
+ /**
22508
+ * Specialized `alloc` for a Buffer whose element is an enum-with-data: free
22509
+ * each old slot's payloads in place, free the slab, calloc the exact fresh
22510
+ * slab. x19 = self, x1 = size; returns cap.
22511
+ */
22512
+ function emit_enum_alloc_T(en, status) {
22513
+ const E_SIZE = get_enum_size(en.name, status);
22514
+ const lbl = (s) => `.Lealloc_${s}_${status.label_counter = (status.label_counter ?? 0) + 1}`;
22515
+ const fresh = lbl("fresh");
22516
+ const drop = lbl("drop");
22517
+ const loop = lbl("loop");
22518
+ emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
22519
+ emit_asm(status, `str x22, [sp, #-16]!\n`);
22520
+ emit_asm(status, `str x23, [sp, #-16]!\n`);
22521
+ emit_asm(status, `mov x22, x1\n`);
22522
+ emit_asm(status, `ldr x20, [x19, #8]\n`);
22523
+ emit_asm(status, `cbz x20, ${fresh}\n`);
22524
+ emit_asm(status, `ldr x21, [x19, #16]\n`);
22525
+ emit_asm(status, `cbz x21, ${drop}\n`);
22526
+ emit_asm(status, `mov x23, x21\n`);
22527
+ emit_asm(status, `${loop}:\n`);
22528
+ emit_asm(status, `sub x23, x23, #1\n`);
22529
+ emit_asm(status, `mov x9, #${E_SIZE}\n`);
22530
+ emit_asm(status, `madd x10, x23, x9, x20\n`);
22531
+ emit_enum_payload_frees_at(status, en.name, "x10", 0);
22532
+ emit_asm(status, `cbnz x23, ${loop}\n`);
22533
+ emit_asm(status, `${drop}:\n`);
22534
+ emit_asm(status, `mov x0, x20\n`);
22535
+ emit_free(status);
22536
+ emit_asm(status, `${fresh}:\n`);
22537
+ emit_asm(status, `mov x0, x22\n`);
22538
+ emit_asm(status, `mov x1, #${E_SIZE}\n`);
22539
+ emit_asm(status, status.audit ? `bl _nomen_calloc_wrap\n` : `bl _calloc\n`);
22540
+ emit_asm(status, `str x0, [x19, #8]\n`);
22541
+ emit_asm(status, `str x22, [x19, #16]\n`);
22542
+ emit_asm(status, `ldr x0, [x19, #16]\n`);
22543
+ emit_asm(status, `ldr x23, [sp], #16\n`);
22544
+ emit_asm(status, `ldr x22, [sp], #16\n`);
22545
+ emit_asm(status, `ldp x20, x21, [sp], #16\n`);
22546
+ }
21695
22547
  function emit_guarded_slot_destroy(status, elem, slot_reg) {
21696
22548
  const skip = `.Lown_slot_skip_${status.label_counter = (status.label_counter ?? 0) + 1}`;
21697
22549
  emit_asm(status, `ldr x9, [${slot_reg}]\n`);
@@ -21701,6 +22553,279 @@ function emit_guarded_slot_destroy(status, elem, slot_reg) {
21701
22553
  emit_asm(status, `${skip}:\n`);
21702
22554
  }
21703
22555
  /**
22556
+ * The container monos whose `<T>_destroy` reclaims the whole container
22557
+ * (slab + per-element owned state): the core value-slot containers. A user
22558
+ * mv struct must NOT take this path (its destroy would free its string
22559
+ * fields too — the string machinery owns those) — such fields recurse.
22560
+ */
22561
+ const CONTAINER_MONO_PREFIXES_A64 = [
22562
+ "Buffer_",
22563
+ "List_",
22564
+ "Map_",
22565
+ "Set_",
22566
+ "Array_"
22567
+ ];
22568
+ function is_container_mono_name_a64(name) {
22569
+ return CONTAINER_MONO_PREFIXES_A64.some((p) => name.startsWith(p));
22570
+ }
22571
+ /** Offsets (relative to the container struct start) of every owned slab
22572
+ * pointer inside a container mono: the `data` words of the Buffer-shaped
22573
+ * structs reachable through nested struct fields. The displaced-reclaim
22574
+ * guard compares ALL of them — a byte-copy round-trip aliases every slab
22575
+ * pointer; any differing pointer means the displaced container's slabs are
22576
+ * exclusively owned. */
22577
+ function container_data_offsets_a64(fs, status, base = 0) {
22578
+ const out = [];
22579
+ let offset = 8;
22580
+ for (const field of fs.fields) {
22581
+ const size = get_type_size(field.type, status);
22582
+ if (field.name === "data") {
22583
+ out.push(base + offset);
22584
+ offset += size;
22585
+ continue;
22586
+ }
22587
+ if (!field.type.is_ref && !field.type.is_view && !field.type.is_array) {
22588
+ if (field.type.name === "string" && !field.type.is_array) {
22589
+ offset += aarch64_size("string");
22590
+ continue;
22591
+ }
22592
+ const sub = resolve_struct_type(field.type, status);
22593
+ if (sub && !sub.is_class && is_container_mono_name_a64(sub.name)) {
22594
+ out.push(...container_data_offsets_a64(sub, status, base + offset));
22595
+ offset += size;
22596
+ continue;
22597
+ }
22598
+ }
22599
+ offset += size;
22600
+ }
22601
+ return out;
22602
+ }
22603
+ /**
22604
+ * Collect the owned POINTER leaves of an element struct whose displaced
22605
+ * copies a store onto an occupied slot must reclaim: containers, Box/class
22606
+ * fields, func descriptors, enum-with-data payloads. Plain string fields are
22607
+ * EXCLUDED (store_T's string machinery owns their displaced-old reclaim, and
22608
+ * a whole-struct destroy here would double-free them); plain nested mv
22609
+ * structs recurse so their pointer leaves are reached.
22610
+ */
22611
+ function collect_displaced_leaves_a64(elem, status, base = 0) {
22612
+ const leaves = [];
22613
+ let offset = 8;
22614
+ for (const field of elem.fields) {
22615
+ const size = get_type_size(field.type, status);
22616
+ if (!field.type.is_ref && !field.type.is_view && !field.type.is_array) {
22617
+ if (field.type.name === "string" && !field.type.is_array) {
22618
+ offset += aarch64_size("string");
22619
+ continue;
22620
+ }
22621
+ const box_mono = box_field_mono(field.type, status);
22622
+ if (box_mono) {
22623
+ leaves.push({
22624
+ offset: base + offset,
22625
+ kind: "box",
22626
+ destroy: box_mono.name,
22627
+ identities: [0]
22628
+ });
22629
+ offset += size;
22630
+ continue;
22631
+ }
22632
+ const fs = resolve_struct_type(field.type, status);
22633
+ if (fs?.is_class) {
22634
+ leaves.push({
22635
+ offset: base + offset,
22636
+ kind: "class",
22637
+ destroy: fs.name,
22638
+ identities: [0]
22639
+ });
22640
+ offset += size;
22641
+ continue;
22642
+ }
22643
+ if (field.type.name === "func") {
22644
+ leaves.push({
22645
+ offset: base + offset,
22646
+ kind: "func",
22647
+ identities: [0]
22648
+ });
22649
+ offset += size;
22650
+ continue;
22651
+ }
22652
+ const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
22653
+ if (field_enum) {
22654
+ leaves.push({
22655
+ offset: base + offset,
22656
+ kind: "enum",
22657
+ enum_node: field_enum,
22658
+ identities: []
22659
+ });
22660
+ offset += size;
22661
+ continue;
22662
+ }
22663
+ if (fs && !fs.is_class) {
22664
+ if (is_container_mono_name_a64(fs.name)) {
22665
+ leaves.push({
22666
+ offset: base + offset,
22667
+ kind: "container",
22668
+ destroy: fs.name,
22669
+ identities: container_data_offsets_a64(fs, status)
22670
+ });
22671
+ offset += size;
22672
+ continue;
22673
+ }
22674
+ if (struct_needs_destroy(fs, status) || has_string_fields(fs, status)) {
22675
+ leaves.push(...collect_displaced_leaves_a64(fs, status, base + offset));
22676
+ offset += size;
22677
+ continue;
22678
+ }
22679
+ }
22680
+ }
22681
+ offset += size;
22682
+ }
22683
+ return leaves;
22684
+ }
22685
+ /**
22686
+ * Emit the DISPLACED-OWNED reclaim for a store onto an occupied slot (the
22687
+ * residual Buffer hole in FOLLOWUP.md — store's fresh-slot contract leaked
22688
+ * every owned pointer field of the previous occupant except strings, which
22689
+ * the B1 displaced-old free already handles). Runs BEFORE the slot memcpy
22690
+ * (both identity sides are live): per leaf, the slot's field is destroyed IN
22691
+ * PLACE only when the incoming value does not alias it — the load→modify→store
22692
+ * round-trip (a byte-copied alias) keeps the slot's own state, which the
22693
+ * subsequent memcpy then replaces wholesale.
22694
+ *
22695
+ * `slot_reg` holds &slot[i]; `val_reg` holds the incoming value's address.
22696
+ * Both must be callee-saved (the destroys call).
22697
+ */
22698
+ function emit_displaced_owned_reclaim_a64(status, elem, slot_reg, val_reg) {
22699
+ const leaves = collect_displaced_leaves_a64(elem, status);
22700
+ for (const leaf of leaves) {
22701
+ const next = `.Ldisp_done_${status.label_counter = (status.label_counter ?? 0) + 1}`;
22702
+ if (leaf.kind === "container") {
22703
+ if (leaf.identities.length) {
22704
+ emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset + leaf.identities[0]}]\n`);
22705
+ emit_asm(status, `cbz x9, ${next}\n`);
22706
+ }
22707
+ for (const id of leaf.identities) {
22708
+ emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset + id}]\n`);
22709
+ emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset + id}]\n`);
22710
+ emit_asm(status, `cmp x9, x10\n`);
22711
+ emit_asm(status, `b.eq ${next}\n`);
22712
+ }
22713
+ if (slot_reg === "x0") {
22714
+ if (leaf.offset !== 0) emit_asm(status, `add x0, x0, #${leaf.offset}\n`);
22715
+ } else emit_asm(status, `add x0, ${slot_reg}, #${leaf.offset}\n`);
22716
+ emit_asm(status, `bl ${leaf.destroy}_destroy\n`);
22717
+ } else if (leaf.kind === "box" || leaf.kind === "class") {
22718
+ emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset}]\n`);
22719
+ emit_asm(status, `cbz x9, ${next}\n`);
22720
+ emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset}]\n`);
22721
+ emit_asm(status, `cmp x9, x10\n`);
22722
+ emit_asm(status, `b.eq ${next}\n`);
22723
+ emit_asm(status, `mov x0, x9\n`);
22724
+ emit_asm(status, `bl ${leaf.destroy}_destroy\n`);
22725
+ emit_asm(status, `ldr x0, [${slot_reg}, #${leaf.offset}]\n`);
22726
+ emit_free(status);
22727
+ } else if (leaf.kind === "func") {
22728
+ const no_destroy = `.Ldisp_noown_${status.label_counter = (status.label_counter ?? 0) + 1}`;
22729
+ emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset}]\n`);
22730
+ emit_asm(status, `cbz x9, ${next}\n`);
22731
+ emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset}]\n`);
22732
+ emit_asm(status, `cmp x9, x10\n`);
22733
+ emit_asm(status, `b.eq ${next}\n`);
22734
+ emit_asm(status, `ldr w11, [x9, #16]\n`);
22735
+ emit_asm(status, `cbz w11, ${next}\n`);
22736
+ emit_asm(status, `str x9, [sp, #-16]!\n`);
22737
+ emit_asm(status, `ldr x10, [x9, #24]\n`);
22738
+ emit_asm(status, `cbz x10, ${no_destroy}\n`);
22739
+ emit_asm(status, `ldr x0, [x9, #8]\n`);
22740
+ emit_asm(status, `blr x10\n`);
22741
+ emit_asm(status, `${no_destroy}:\n`);
22742
+ emit_asm(status, `ldr x0, [sp]\n`);
22743
+ emit_asm(status, `ldr x0, [x0, #8]\n`);
22744
+ emit_free(status);
22745
+ emit_asm(status, `ldr x0, [sp], #16\n`);
22746
+ emit_free(status);
22747
+ } else if (leaf.kind === "enum" && leaf.enum_node) {
22748
+ const en = leaf.enum_node;
22749
+ const owning_cases = en.cases.filter((c) => c.params.some((p) => p.type.name === "string" || enum_payload_is_reference_a64(p, status)));
22750
+ if (!owning_cases.length) continue;
22751
+ emit_asm(status, `ldr w9, [${slot_reg}, #${leaf.offset}]\n`);
22752
+ for (const c of owning_cases) {
22753
+ const idx = get_enum_case_index(en.name, c.name, status);
22754
+ const case_next = `.Ldisp_case_${status.label_counter = (status.label_counter ?? 0) + 1}`;
22755
+ emit_asm(status, `cmp w9, #${idx}\n`);
22756
+ emit_asm(status, `b.ne ${case_next}\n`);
22757
+ emit_asm(status, `ldr w10, [${val_reg}, #${leaf.offset}]\n`);
22758
+ emit_asm(status, `cmp w10, #${idx}\n`);
22759
+ emit_asm(status, `b.ne ${next}_reclaim\n`);
22760
+ for (const p of c.params) {
22761
+ if (p.type.name !== "string" && !enum_payload_is_reference_a64(p, status)) continue;
22762
+ const poff = get_enum_payload_offset(en.name, c.name, p.name, status);
22763
+ emit_asm(status, `ldr x9, [${slot_reg}, #${leaf.offset + poff}]\n`);
22764
+ emit_asm(status, `ldr x10, [${val_reg}, #${leaf.offset + poff}]\n`);
22765
+ emit_asm(status, `cmp x9, x10\n`);
22766
+ emit_asm(status, `b.eq ${next}\n`);
22767
+ }
22768
+ emit_asm(status, `${case_next}:\n`);
22769
+ }
22770
+ emit_asm(status, `b ${next}_reclaim\n`);
22771
+ emit_asm(status, `${next}_reclaim:\n`);
22772
+ emit_enum_payload_frees_at(status, en.name, slot_reg, leaf.offset);
22773
+ }
22774
+ emit_asm(status, `${next}:\n`);
22775
+ }
22776
+ }
22777
+ /** The aarch64-side payload reference check (mirrors the C reclaim's). */
22778
+ function enum_payload_is_reference_a64(p, status) {
22779
+ return status.structs.some((s) => s.name === p.type.name && s.is_class) || status.traits.some((t) => t.name === p.type.name);
22780
+ }
22781
+ /**
22782
+ * Specialized `alloc` for a Buffer whose element type owns heap data:
22783
+ * per-element destroy of the OLD slab (guarded on the constructed stamp —
22784
+ * grown-but-unwritten and moved-out slots are zeroed), free the slab, then
22785
+ * calloc the exact fresh one. The raw body dropped the old pointer — the
22786
+ * second Buffer hole in FOLLOWUP.md. x19 = self, x1 = size; returns cap.
22787
+ */
22788
+ function emit_owning_alloc_T(elem, status) {
22789
+ const T_SIZE = get_struct_size(elem.name, status);
22790
+ const lbl = (s) => `.Lalloc_${s}_${status.label_counter = (status.label_counter ?? 0) + 1}`;
22791
+ const fresh = lbl("fresh");
22792
+ const drop = lbl("drop");
22793
+ const loop = lbl("loop");
22794
+ const skip = lbl("skip");
22795
+ const done = lbl("done");
22796
+ emit_asm(status, `stp x20, x21, [sp, #-16]!\n`);
22797
+ emit_asm(status, `str x22, [sp, #-16]!\n`);
22798
+ emit_asm(status, `mov x22, x1\n`);
22799
+ emit_asm(status, `ldr x20, [x19, #8]\n`);
22800
+ emit_asm(status, `cbz x20, ${fresh}\n`);
22801
+ emit_asm(status, `ldr x21, [x19, #16]\n`);
22802
+ emit_asm(status, `cbz x21, ${drop}\n`);
22803
+ emit_asm(status, `${loop}:\n`);
22804
+ emit_asm(status, `mov x9, #${T_SIZE}\n`);
22805
+ emit_asm(status, `madd x10, x21, x9, x20\n`);
22806
+ emit_asm(status, `sub x10, x10, x9\n`);
22807
+ emit_asm(status, `ldr x9, [x10]\n`);
22808
+ emit_asm(status, `cbz x9, ${skip}\n`);
22809
+ emit_asm(status, `mov x0, x10\n`);
22810
+ emit_asm(status, `bl ${elem.name}_destroy\n`);
22811
+ emit_asm(status, `${skip}:\n`);
22812
+ emit_asm(status, `sub x21, x21, #1\n`);
22813
+ emit_asm(status, `cbnz x21, ${loop}\n`);
22814
+ emit_asm(status, `${drop}:\n`);
22815
+ emit_asm(status, `mov x0, x20\n`);
22816
+ emit_free(status);
22817
+ emit_asm(status, `${fresh}:\n`);
22818
+ emit_asm(status, `mov x0, x22\n`);
22819
+ emit_asm(status, `mov x1, #${T_SIZE}\n`);
22820
+ emit_asm(status, status.audit ? `bl _nomen_calloc_wrap\n` : `bl _calloc\n`);
22821
+ emit_asm(status, `str x0, [x19, #8]\n`);
22822
+ emit_asm(status, `str x22, [x19, #16]\n`);
22823
+ emit_asm(status, `ldr x0, [x19, #16]\n`);
22824
+ emit_asm(status, `${done}:\n`);
22825
+ emit_asm(status, `ldr x22, [sp], #16\n`);
22826
+ emit_asm(status, `ldp x20, x21, [sp], #16\n`);
22827
+ }
22828
+ /**
21704
22829
  * Emit a specialized #destroy for a Buffer whose element type is a mv
21705
22830
  * value struct. Per-element T_destroy, then free the slab. Called from
21706
22831
  * build_struct_node instead of build_destroy_function when the element owns.
@@ -23362,7 +24487,7 @@ function build_function_node$1(node, status) {
23362
24487
  const old_code = status.code;
23363
24488
  status.code = "";
23364
24489
  node.return_type = resolve_mono_type(node.return_type, status);
23365
- 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);
24490
+ const return_struct = !node.return_type.is_view && !node.return_type.is_array && !node.return_type.is_pointer && (!!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);
23366
24491
  const nullable_scalar_ret = is_nullable_scalar_type(node.return_type, status) && !node.return_type.is_ref;
23367
24492
  const uses_sret = return_struct || nullable_scalar_ret;
23368
24493
  const old_struct_return_buffer = status.struct_return_buffer;
@@ -24514,18 +25639,40 @@ function build_init_function(node, status) {
24514
25639
  }
24515
25640
  if (field.type.is_array && field.type.length && (field.type.length.start ?? -1) >= 0) {
24516
25641
  const elem_is_fat_string = field.type.name === "string";
24517
- const element_size = elem_is_fat_string ? 16 : aarch64_size(field.type.name);
25642
+ const elem_struct = elem_is_fat_string ? void 0 : status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class && !s.is_generic);
25643
+ const element_size = elem_is_fat_string ? 16 : elem_struct ? get_struct_size(field.type.name, status) : aarch64_size(field.type.name);
24518
25644
  const length = parseInt(field.type.length.value || "0");
24519
- for (let e = 0; e < length; e++) {
24520
- const byte_offset = e * element_size;
24521
- if (elem_is_fat_string) {
24522
- emit_asm(status, `ldp x9, x10, [${src_reg}, #${byte_offset}]\n`);
24523
- emit_asm(status, `stp x9, x10, [x19, #${offset + byte_offset}]\n`);
24524
- } else {
24525
- emit_asm(status, load_element(src_reg, byte_offset, element_size));
24526
- emit_asm(status, store_element("x19", offset + byte_offset, element_size));
25645
+ const detach_fields = elem_struct && owns_strings_uniformly(elem_struct) ? layout_fields(elem_struct, status).filter((f) => !f.type.is_ref && !f.type.is_view && !f.type.is_array && f.type.name === "string") : [];
25646
+ const elem_field_offsets = detach_fields.map((f) => get_field_offset_of_fields(layout_fields(elem_struct, status), f.name, status));
25647
+ const copy_elements = () => {
25648
+ for (let e = 0; e < length; e++) {
25649
+ const byte_offset = e * element_size;
25650
+ if (elem_is_fat_string) {
25651
+ emit_asm(status, `ldp x9, x10, [${src_reg}, #${byte_offset}]\n`);
25652
+ emit_asm(status, `stp x9, x10, [x19, #${offset + byte_offset}]\n`);
25653
+ } else if (elem_struct) {
25654
+ const words = Math.ceil(element_size / 8);
25655
+ for (let w = 0; w < words; w++) {
25656
+ emit_asm(status, `ldr x9, [${src_reg}, #${byte_offset + w * 8}]\n`);
25657
+ emit_asm(status, `str x9, [x19, #${offset + byte_offset + w * 8}]\n`);
25658
+ }
25659
+ for (const sfo of elem_field_offsets) {
25660
+ const slot_off = offset + byte_offset + sfo;
25661
+ emit_asm(status, `ldr x0, [x19, #${slot_off}]\n`);
25662
+ const skip = `.arr_elem_strdup_skip_${status.label_counter = (status.label_counter ?? 0) + 1}`;
25663
+ emit_asm(status, `cbz x0, ${skip}\n`);
25664
+ emit_strdup(status);
25665
+ emit_asm(status, `str x0, [x19, #${slot_off}]\n`);
25666
+ emit_asm(status, `${skip}:\n`);
25667
+ }
25668
+ } else {
25669
+ emit_asm(status, load_element(src_reg, byte_offset, element_size));
25670
+ emit_asm(status, store_element("x19", offset + byte_offset, element_size));
25671
+ }
24527
25672
  }
24528
- }
25673
+ };
25674
+ if (detach_fields.length > 0) with_live_arg_regs_spilled(status, param_regs.slice(slot - 1), copy_elements);
25675
+ else copy_elements();
24529
25676
  } else if (!field.type.is_ref && status.enums.find((e) => e.name === field.type.name && e.has_associated_data)) {
24530
25677
  const enum_name = field.type.name;
24531
25678
  const enum_size = get_enum_size(enum_name, status);
@@ -24580,7 +25727,13 @@ function build_init_function(node, status) {
24580
25727
  }
24581
25728
  emit_asm(status, `mov x1, #0\n`);
24582
25729
  } else if (/^(\+|-)*\d+$/.test(val)) emit_asm(status, `ldr x1, =${val}\n`);
24583
- else if (val.startsWith("\"")) {
25730
+ else if (/^(\+|-)*\d+\.\d+$/.test(val)) {
25731
+ const label = `_fdefault_${func_name}_${field.name}`;
25732
+ const data = `${label}: .double ${val}\n.p2align 3\n`;
25733
+ status.function_data = (status.function_data ?? "") + data;
25734
+ emit_asm(status, `adr x1, ${label}\n`);
25735
+ emit_asm(status, `ldr x1, [x1]\n`);
25736
+ } else if (val.startsWith("\"")) {
24584
25737
  const label = `_str_${func_name}_${field.name}`;
24585
25738
  status.strings.set(label, val);
24586
25739
  emit_asm(status, `adr x1, ${label}\n`);
@@ -24678,6 +25831,10 @@ function build_init_function(node, status) {
24678
25831
  emit_asm(status, `ldr x19, [sp], #16\n`);
24679
25832
  emit_asm(status, `ldp x29, x30, [sp], #16\n`);
24680
25833
  emit_asm(status, `ret\n`);
25834
+ if (status.function_data) {
25835
+ emit_asm(status, status.function_data);
25836
+ status.function_data = void 0;
25837
+ }
24681
25838
  status.stack_size = old_stack_size;
24682
25839
  status.stack_offsets = old_stack_offsets;
24683
25840
  }
@@ -24708,6 +25865,8 @@ function build_custom_init_function(node, func, status) {
24708
25865
  status.register_allocations = void 0;
24709
25866
  status.nir_site_allocs = void 0;
24710
25867
  status.callee_saved_regs_used = void 0;
25868
+ const old_struct_param_slots = status.function_struct_param_slots;
25869
+ status.function_struct_param_slots = /* @__PURE__ */ new Set();
24711
25870
  status.scoped_declarations = [];
24712
25871
  status.heap_strings = /* @__PURE__ */ new Set();
24713
25872
  status.stack_size = 0;
@@ -24826,6 +25985,9 @@ function build_custom_init_function(node, func, status) {
24826
25985
  const size = aarch64_size(param.type.name);
24827
25986
  const offset = allocate_stack_space(status, size, size);
24828
25987
  status.stack_offsets.set(param.name, offset);
25988
+ if (!param.type.is_ref && !param.type.is_array && !param.is_variadic) {
25989
+ if (!!status.structs.find((s) => s.name === param.type.name && !s.is_simple_type) || !!status.traits.find((t) => t.name === param.type.name) || !!status.enums.find((e) => e.name === param.type.name && e.has_associated_data)) status.function_struct_param_slots.add(param.name);
25990
+ }
24829
25991
  if (param_idx < 8) {
24830
25992
  const reg = param_regs[param_idx];
24831
25993
  if (size === 1) emit_asm(status, `strb ${reg.replace("x", "w")}, [x29, #${offset}]\n`);
@@ -24886,7 +26048,13 @@ function build_custom_init_function(node, func, status) {
24886
26048
  }
24887
26049
  emit_asm(status, `mov x1, #0\n`);
24888
26050
  } else if (/^(\+|-)*\d+$/.test(val)) emit_asm(status, `ldr x1, =${val}\n`);
24889
- else if (val.startsWith("\"")) {
26051
+ else if (/^(\+|-)*\d+\.\d+$/.test(val)) {
26052
+ const label = `_fdefault_${func_name}_${field.name}`;
26053
+ const data = `${label}: .double ${val}\n.p2align 3\n`;
26054
+ status.function_data = (status.function_data ?? "") + data;
26055
+ emit_asm(status, `adr x1, ${label}\n`);
26056
+ emit_asm(status, `ldr x1, [x1]\n`);
26057
+ } else if (val.startsWith("\"")) {
24890
26058
  const label = `_str_${func_name}_${field.name}`;
24891
26059
  status.strings.set(label, val);
24892
26060
  emit_asm(status, `adr x1, ${label}\n`);
@@ -24979,6 +26147,7 @@ function build_custom_init_function(node, func, status) {
24979
26147
  status.init_assigned_fields = old_init_assigned_fields;
24980
26148
  status.init_default_reclaimed_fields = old_init_default_reclaimed;
24981
26149
  status.heap_string_fields = old_init_heap_string_fields;
26150
+ status.function_struct_param_slots = old_struct_param_slots;
24982
26151
  }
24983
26152
  const init_claims = status.callee_saved_regs_used;
24984
26153
  const init_loop_regs = init_claims ? [...init_claims].sort() : [];
@@ -25004,6 +26173,10 @@ function build_custom_init_function(node, func, status) {
25004
26173
  emit_asm(status, `ldr x19, [sp], #16\n`);
25005
26174
  emit_asm(status, `ldp x29, x30, [sp], #16\n`);
25006
26175
  emit_asm(status, `ret\n`);
26176
+ if (status.function_data) {
26177
+ emit_asm(status, status.function_data);
26178
+ status.function_data = void 0;
26179
+ }
25007
26180
  status.scoped_declarations = old_scoped_declarations;
25008
26181
  status.heap_strings = old_heap_strings;
25009
26182
  status.trait_class_frames = old_trait_class_frames;
@@ -25036,6 +26209,8 @@ function build_struct_functions$1(node, status) {
25036
26209
  }
25037
26210
  const old_scoped_declarations = status.scoped_declarations;
25038
26211
  const old_heap_strings = status.heap_strings;
26212
+ const old_variable_types = status.variable_types;
26213
+ status.variable_types = /* @__PURE__ */ new Map();
25039
26214
  const old_trait_class_frames = status.trait_class_frames;
25040
26215
  status.trait_class_frames = void 0;
25041
26216
  const old_moved = status.moved;
@@ -25122,7 +26297,7 @@ function build_struct_functions$1(node, status) {
25122
26297
  status.function_view_params = /* @__PURE__ */ new Set();
25123
26298
  status.function_param_types = /* @__PURE__ */ new Map();
25124
26299
  status.struct_return_buffer = void 0;
25125
- 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);
26300
+ const return_struct = !func.return_type?.is_array && !func.return_type?.is_pointer && (!!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);
25126
26301
  const nullable_scalar_ret = !!func.return_type && is_nullable_scalar_type(func.return_type, status) && !func.return_type.is_ref;
25127
26302
  const uses_sret = return_struct || nullable_scalar_ret;
25128
26303
  let return_buffer_stack_offset;
@@ -25454,6 +26629,7 @@ function build_struct_functions$1(node, status) {
25454
26629
  emit_asm(status, `ret\n`);
25455
26630
  status.scoped_declarations = old_scoped_declarations;
25456
26631
  status.heap_strings = old_heap_strings;
26632
+ status.variable_types = old_variable_types;
25457
26633
  status.trait_class_frames = old_trait_class_frames;
25458
26634
  status.moved = old_moved;
25459
26635
  status.mv_owns_flag = old_mv_owns_flag;
@@ -25505,6 +26681,8 @@ function build_trait_functions(node, status) {
25505
26681
  status.emitted_trait_funcs.add(trait_func_label);
25506
26682
  const old_scoped_declarations = status.scoped_declarations;
25507
26683
  const old_heap_strings = status.heap_strings;
26684
+ const old_variable_types = status.variable_types;
26685
+ status.variable_types = /* @__PURE__ */ new Map();
25508
26686
  const old_heap_string_arrays = status.heap_string_arrays;
25509
26687
  status.heap_string_arrays = void 0;
25510
26688
  const old_heap_class_arrays = status.heap_class_arrays;
@@ -25694,6 +26872,7 @@ function build_trait_functions(node, status) {
25694
26872
  emit_asm(status, `ret\n`);
25695
26873
  status.scoped_declarations = old_scoped_declarations;
25696
26874
  status.heap_strings = old_heap_strings;
26875
+ status.variable_types = old_variable_types;
25697
26876
  status.heap_string_arrays = old_heap_string_arrays;
25698
26877
  status.heap_class_arrays = old_heap_class_arrays;
25699
26878
  status.heap_array_vars = old_heap_array_vars;
@@ -27616,6 +28795,23 @@ function scaled_load_instr(name, size) {
27616
28795
  }
27617
28796
  //#endregion
27618
28797
  //#region ../src/build_aarch64/naked_inline.ts
28798
+ /**
28799
+ * Storage width of a built-in scalar type name: 1/2/4 for the sub-word ints,
28800
+ * bool, and char; 8 for everything else (words, floats-as-bits, references).
28801
+ * `null` for anything that is not a built-in scalar (structs, enums, strings,
28802
+ * views) — callers keep their existing behavior for those.
28803
+ */
28804
+ function scalar_byte_width(type_name) {
28805
+ const info = get_built_in_type(type_name);
28806
+ if (!info || info.bytes === void 0) return 8;
28807
+ if (info.bytes === 1 || info.bytes === 2 || info.bytes === 4) return info.bytes;
28808
+ return 8;
28809
+ }
28810
+ /** The W-register alias of an X register ("x19" → "w19") — sub-word loads
28811
+ * zero-extend into the full register, so the caller sees a clean scalar. */
28812
+ function w_alias(reg) {
28813
+ return reg.startsWith("x") ? `w${reg.slice(1)}` : reg;
28814
+ }
27619
28815
  const NAKED_MAX_STATEMENTS = 4;
27620
28816
  const NAKED_SCRATCH = ["x9", "x10"];
27621
28817
  /** Byte size of a pointer element — must be a power of two ≤ 8 to be naked. */
@@ -27759,7 +28955,11 @@ function emit_naked_expr(node, dst, ctx) {
27759
28955
  if (next === null) return false;
27760
28956
  struct_name = next;
27761
28957
  }
27762
- ctx.push(`ldr ${dst}, [x19, #${offset}]`);
28958
+ const width = scalar_byte_width(struct_name);
28959
+ if (width === 1) ctx.push(`ldrb ${w_alias(dst)}, [x19, #${offset}]`);
28960
+ else if (width === 2) ctx.push(`ldrh ${w_alias(dst)}, [x19, #${offset}]`);
28961
+ else if (width === 4) ctx.push(`ldr ${w_alias(dst)}, [x19, #${offset}]`);
28962
+ else ctx.push(`ldr ${dst}, [x19, #${offset}]`);
27763
28963
  return true;
27764
28964
  }
27765
28965
  if (nt === "index") {
@@ -29256,10 +30456,10 @@ function find_var_size$2(name, status) {
29256
30456
  */
29257
30457
  function current_return_is_string(status) {
29258
30458
  const rt = status.function_return_type;
29259
- if (rt) return rt.name === "string" && !rt.is_view;
30459
+ if (rt) return rt.name === "string" && !rt.is_view && !rt.is_pointer;
29260
30460
  if (status.current_struct && status.current_function_name) {
29261
30461
  const frt = status.current_struct.functions.find((f) => f.name === status.current_function_name)?.return_type;
29262
- return !!frt && frt.name === "string" && !frt.is_view;
30462
+ return !!frt && frt.name === "string" && !frt.is_view && !frt.is_pointer;
29263
30463
  }
29264
30464
  return false;
29265
30465
  }
@@ -29329,6 +30529,7 @@ function build_return_node$1(node, status, nir_value) {
29329
30529
  continue;
29330
30530
  }
29331
30531
  if (is_field_struct_borrow(decl)) continue;
30532
+ if (is_pure_borrow_element_load(decl, status)) continue;
29332
30533
  emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
29333
30534
  }
29334
30535
  emit_heap_slots_cleanup_for_return(status);
@@ -29349,6 +30550,7 @@ function build_return_node$1(node, status, nir_value) {
29349
30550
  continue;
29350
30551
  }
29351
30552
  if (is_field_struct_borrow(decl)) continue;
30553
+ if (is_pure_borrow_element_load(decl, status)) continue;
29352
30554
  emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
29353
30555
  }
29354
30556
  emit_heap_slots_cleanup_for_return(status);
@@ -29456,6 +30658,7 @@ function build_return_node$1(node, status, nir_value) {
29456
30658
  continue;
29457
30659
  }
29458
30660
  if (is_field_struct_borrow(decl)) continue;
30661
+ if (is_pure_borrow_element_load(decl, status)) continue;
29459
30662
  emit_destroy_for_decl(status, decl.name, decl.type.name, void 0, decl.type.type_args, decl.type.is_nullable);
29460
30663
  }
29461
30664
  emit_heap_slots_cleanup_for_return(status);
@@ -32290,6 +33493,7 @@ function build_cast_node$1(node, status) {
32290
33493
  const is_from_float = is_float_type$1(from);
32291
33494
  const is_to_float = is_float_type$1(to);
32292
33495
  if (is_from_float && !is_to_float) {
33496
+ emit_asm(status, `fmov d0, x0\n`);
32293
33497
  emit_asm(status, `fcvtzs x0, d0\n`);
32294
33498
  emit_asm(status, `fmov d0, x0\n`);
32295
33499
  return;
@@ -32800,7 +34004,8 @@ const OWNING_BUFFER_METHODS = /* @__PURE__ */ new Set([
32800
34004
  "modify",
32801
34005
  "shift",
32802
34006
  "destroy",
32803
- "#destroy"
34007
+ "#destroy",
34008
+ "alloc"
32804
34009
  ]);
32805
34010
  /**
32806
34011
  * Emit the transfer half of mv store_T/replace_T: the slot bytes were
@@ -32829,18 +34034,16 @@ function emit_transfer_owning_fields(elem, src, status, prefix = "") {
32829
34034
  }
32830
34035
  }
32831
34036
  /**
32832
- * Emit the deep-copy half of mv load_T: for each field whose type has a
32833
- * monomorphized `copy` helper that yields an independent value (a `List<T>`
32834
- * /`Buffer<T>`/`Map`/`Set` field — `<Mono>_copy` deep-copies the slots or
32835
- * member buffers — or an enum-with-data field whose string payloads
32836
- * `<E>_copy` duplicates), overwrite the shallow-copied bytes with an owned
32837
- * copy. A struct local's CONTAINER fields are reclaimed unconditionally at
32838
- * scope exit, so a shallow return would double-free the slot's slab. B1
32839
- * cutover: an OWNING struct's plain string fields are freed unconditionally
32840
- * too (the same unconditional teardown), so they are strdup'd here as well —
32841
- * the loaded copy owns independent bytes and the slot keeps its own. Fields
32842
- * without a copyable shape (class fields, nested mv user structs without
32843
- * a synthesized copy) stay shallow — recorded in FOLLOWUP.md.
34037
+ * Emit the deep-copy half of mv load_T: for each field that
34038
+ * `field_load_deep_copy` accepts (a `List<T>`/`Buffer<T>`/`Map`/`Set` field
34039
+ * whose `<Mono>_copy` deep-copies the slots or member buffers, a nested mv
34040
+ * user struct whose auto-derived `copy` is deep, or a `Box<T>` wrapper that
34041
+ * is cloned), overwrite the shallow-copied bytes with an owned copy; enum-
34042
+ * with-data fields whose payloads are all deep-copyable ride `<E>_copy`.
34043
+ * A struct local's CONTAINER fields are reclaimed unconditionally at scope
34044
+ * exit, so a shallow return would double-free the slot's slab. Fields
34045
+ * without a copyable shape (class fields, nested mv structs that need
34046
+ * destroy but have no `copy`) stay shallow — recorded in FOLLOWUP.md.
32844
34047
  */
32845
34048
  function emit_load_deep_copy_fields(elem, dst, status) {
32846
34049
  for (const field of elem.fields) {
@@ -32854,7 +34057,7 @@ function emit_load_deep_copy_fields(elem, dst, status) {
32854
34057
  continue;
32855
34058
  }
32856
34059
  const field_struct = resolve_struct_type(field.type, status);
32857
- 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")) {
34060
+ if (field_struct && field_load_deep_copy(field_struct, status)) {
32858
34061
  status.code += `${dst}.${field.name} = ${field_struct.name}_copy(&${dst}.${field.name});\n`;
32859
34062
  continue;
32860
34063
  }
@@ -32884,7 +34087,7 @@ function emit_load_owned_copies_destroy(status, elem, var_expr) {
32884
34087
  continue;
32885
34088
  }
32886
34089
  const field_struct = resolve_struct_type(field.type, status);
32887
- 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")) {
34090
+ if (field_struct && field_load_deep_copy(field_struct, status)) {
32888
34091
  status.code += `${field_struct.name}_destroy(&${var_expr}.${field.name});\n`;
32889
34092
  continue;
32890
34093
  }
@@ -32982,6 +34185,17 @@ function emit_owning_buffer_enum_body(func_name, elem, status) {
32982
34185
  }
32983
34186
  return true;
32984
34187
  }
34188
+ if (func_name === "alloc") {
34189
+ status.code += `if (self->data) {\n`;
34190
+ status.code += `${E}* _old_slots = ${slots};\n`;
34191
+ status.code += `for (int _i = 0; _i < self->cap; _i++) { ${E}_free_payloads(&_old_slots[_i]); }\n`;
34192
+ status.code += `free(_old_slots);\n`;
34193
+ status.code += `}\n`;
34194
+ status.code += `self->cap = size;\n`;
34195
+ status.code += `self->data = (unsigned long long)calloc(size, sizeof(${E}));\n`;
34196
+ status.code += `return self->cap;\n`;
34197
+ return true;
34198
+ }
32985
34199
  if (func_name === "destroy") {
32986
34200
  status.code += `if (self->data) {\n`;
32987
34201
  status.code += `${E}* _slots = ${slots};\n`;
@@ -33032,6 +34246,17 @@ function emit_owning_buffer_string_body(func_name, status) {
33032
34246
  status.code += `}\n`;
33033
34247
  return true;
33034
34248
  }
34249
+ if (func_name === "alloc") {
34250
+ status.code += `if (self->data) {\n`;
34251
+ status.code += `nomen_string* _old_slots = (nomen_string*)self->data;\n`;
34252
+ status.code += `for (int _i = 0; _i < self->cap; _i++) { free(_old_slots[_i].ptr); }\n`;
34253
+ status.code += `free(_old_slots);\n`;
34254
+ status.code += `}\n`;
34255
+ status.code += `self->cap = size;\n`;
34256
+ status.code += `self->data = (unsigned long long)calloc(size, sizeof(nomen_string));\n`;
34257
+ status.code += `return self->cap;\n`;
34258
+ return true;
34259
+ }
33035
34260
  if (func_name === "destroy") {
33036
34261
  status.code += `if (self->data) {\n`;
33037
34262
  status.code += `nomen_string* _slots = (nomen_string*)self->data;\n`;
@@ -33060,6 +34285,7 @@ function emit_owning_buffer_body(func_name, elem, status) {
33060
34285
  if (func_name === "store") {
33061
34286
  status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
33062
34287
  status.code += `struct ${elem.name} _old = _slots[i];\n`;
34288
+ emit_reclaim_displaced_owned(elem, "_slots[i]", "val", status);
33063
34289
  status.code += `_slots[i] = (*val);\n`;
33064
34290
  emit_deep_copy_fields(elem, "_slots[i]", "val", status, "_old");
33065
34291
  emit_transfer_owning_fields(elem, "val", status);
@@ -33072,6 +34298,17 @@ function emit_owning_buffer_body(func_name, elem, status) {
33072
34298
  status.code += `return _loaded;\n`;
33073
34299
  return true;
33074
34300
  }
34301
+ if (func_name === "alloc") {
34302
+ status.code += `if (self->data) {\n`;
34303
+ status.code += `${Tptr}_old_slots = ${Tcast}(unsigned long long)self->data;\n`;
34304
+ status.code += `for (int _i = 0; _i < self->cap; _i++) { if (_old_slots[_i]._vt) { ${elem.name}_destroy(&_old_slots[_i]); } }\n`;
34305
+ status.code += `free(_old_slots);\n`;
34306
+ status.code += `}\n`;
34307
+ status.code += `self->cap = size;\n`;
34308
+ status.code += `self->data = (unsigned long long)calloc(size, sizeof(struct ${elem.name}));\n`;
34309
+ status.code += `return self->cap;\n`;
34310
+ return true;
34311
+ }
33075
34312
  if (func_name === "modify") {
33076
34313
  status.code += `${Tptr}_slots = ${Tcast}(unsigned long long)self->data;\n`;
33077
34314
  status.code += `struct ${elem.name} _old = _slots[i];\n`;
@@ -33114,6 +34351,130 @@ function emit_owning_buffer_body(func_name, elem, status) {
33114
34351
  return false;
33115
34352
  }
33116
34353
  /**
34354
+ * The container monos whose `<T>_destroy` reclaims the whole container
34355
+ * (slab + per-element owned state): the core value-slot containers. A user
34356
+ * mv struct must NOT take this path (its destroy would free its string
34357
+ * fields too — the string machinery owns those) — such fields recurse.
34358
+ */
34359
+ const CONTAINER_MONO_PREFIXES = [
34360
+ "Buffer_",
34361
+ "List_",
34362
+ "Map_",
34363
+ "Set_",
34364
+ "Array_"
34365
+ ];
34366
+ let c_disp_closure_counter = 0;
34367
+ function is_container_mono_name(name) {
34368
+ return CONTAINER_MONO_PREFIXES.some((p) => name.startsWith(p));
34369
+ }
34370
+ /**
34371
+ * Member paths (relative to the container struct start) of every owned slab
34372
+ * pointer inside a container mono: the `data` words of the Buffer-shaped
34373
+ * structs reachable through nested struct fields (`Buffer` → `.data`,
34374
+ * `List` → `.items.data`, `Map` → `.keys.data` / `.values.data` / …). The
34375
+ * store_T displaced-reclaim guard compares ALL of them: the incoming value
34376
+ * aliases the displaced container only when every slab pointer is shared
34377
+ * (a byte-copy round-trip); any differing pointer means the displaced
34378
+ * container's slabs are exclusively owned and can be reclaimed.
34379
+ */
34380
+ function container_data_paths(struct, status, prefix = "") {
34381
+ const paths = [];
34382
+ for (const field of struct.fields) {
34383
+ if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
34384
+ if (field.type.name === "string" && !field.type.is_array) continue;
34385
+ if (field.name === "data") {
34386
+ paths.push(`${prefix}.${field.name}`);
34387
+ continue;
34388
+ }
34389
+ const fs = resolve_struct_type(field.type, status);
34390
+ if (fs && !fs.is_class && is_container_mono_name(fs.name)) paths.push(...container_data_paths(fs, status, `${prefix}.${field.name}`));
34391
+ }
34392
+ return paths;
34393
+ }
34394
+ /**
34395
+ * Reclaim the DISPLACED slot value's owned pointer state on a store onto an
34396
+ * occupied slot — the residual Buffer hole (FOLLOWUP.md): store_T's
34397
+ * fresh-slot contract leaked every owned field of the previous occupant
34398
+ * except the plain string fields the B1 displaced-old reclaim already
34399
+ * handles. Runs BEFORE the slot overwrite (the guard reads both the slot's
34400
+ * and the incoming value's identity pointers live), per owned leaf:
34401
+ *
34402
+ * - container fields: `<T>_destroy` in place when NO slab pointer aliases
34403
+ * the incoming value's (a byte-copy round-trip keeps the slot's own);
34404
+ * - Box / class fields: destroy + free the displaced wrapper when the
34405
+ * incoming pointer differs;
34406
+ * - func fields: the free-if-owned arm when the descriptor differs;
34407
+ * - enum-with-data fields: `<E>_free_payloads` when the tag differs, or
34408
+ * the same case's payload pointers all differ;
34409
+ * - nested mv struct fields: recurse (their strings stay out — the string
34410
+ * machinery owns them).
34411
+ */
34412
+ function emit_reclaim_displaced_owned(elem, slot_path, val_path, status) {
34413
+ for (const field of elem.fields) {
34414
+ if (field.type.is_ref || field.type.is_view || field.type.is_array) continue;
34415
+ if (field.type.name === "string" && !field.type.is_array) continue;
34416
+ const slot_f = `${slot_path}.${field.name}`;
34417
+ const val_f = `${val_path}${arrow(val_path)}${field.name}`;
34418
+ const box_mono = box_field_mono(field.type, status);
34419
+ if (box_mono) {
34420
+ status.code += `if (${slot_f} && ${slot_f} != ${val_f}) { ${box_mono.name}_destroy(${slot_f}); free(${slot_f}); }\n`;
34421
+ continue;
34422
+ }
34423
+ const fs = resolve_struct_type(field.type, status);
34424
+ if (fs?.is_class) {
34425
+ status.code += `if (${slot_f} && ${slot_f} != ${val_f}) { ${fs.name}_destroy(${slot_f}); free(${slot_f}); }\n`;
34426
+ continue;
34427
+ }
34428
+ if (field.type.name === "func") {
34429
+ const c = `_disp_closure_${c_disp_closure_counter++}`;
34430
+ status.code += `if (${slot_f} && ${slot_f} != ${val_f}) {\n`;
34431
+ status.code += `struct nomen_closure *${c} = (struct nomen_closure *)${slot_f};\n`;
34432
+ status.code += `if (${c}->owned) { if (${c}->destroy_env) ${c}->destroy_env(${c}->env); free(${c}->env); free(${c}); }\n`;
34433
+ status.code += `}\n`;
34434
+ continue;
34435
+ }
34436
+ const field_enum = status.enums.find((e) => e.name === field.type.name && e.has_associated_data);
34437
+ if (field_enum) {
34438
+ emit_displaced_enum_reclaim(field_enum, slot_f, val_f, status);
34439
+ continue;
34440
+ }
34441
+ if (fs && !fs.is_class) {
34442
+ if (is_container_mono_name(fs.name)) {
34443
+ const data_paths = container_data_paths(fs, status);
34444
+ if (!data_paths.length) continue;
34445
+ const guard = [`${slot_f}${data_paths[0]}`, ...data_paths.map((p) => `${slot_f}${p} != ${val_f}${p}`)].join(" && ");
34446
+ status.code += `if (${guard}) { ${fs.name}_destroy(&${slot_f}); }\n`;
34447
+ } else if (struct_needs_destroy(fs, status) || has_string_fields(fs, status)) emit_reclaim_displaced_owned(fs, slot_f, val_f, status);
34448
+ }
34449
+ }
34450
+ }
34451
+ /**
34452
+ * The enum-with-data arm of the displaced reclaim: the payload compare must
34453
+ * be tag-scoped (payloads of different cases never alias — distinct
34454
+ * allocations), so per owning case: a differing tag reclaims outright; the
34455
+ * same tag reclaims only when every payload pointer differs (the round-trip
34456
+ * identity keeps the slot's payloads).
34457
+ */
34458
+ function emit_displaced_enum_reclaim(elem, slot_f, val_f, status) {
34459
+ const owning_cases = elem.cases.filter((c) => c.params.some((p) => p.type.name === "string" || payload_is_reference$3(p.type, status)));
34460
+ if (!owning_cases.length) return;
34461
+ const reclaim = `${elem.name}_free_payloads(&${slot_f});`;
34462
+ const guards = owning_cases.map((c) => {
34463
+ const compares = c.params.filter((p) => p.type.name === "string" || payload_is_reference$3(p.type, status)).map((p) => {
34464
+ const slot_p = `${slot_f}._data._${c.name}.${p.name}`;
34465
+ const val_p = `${val_f}._data._${c.name}.${p.name}`;
34466
+ return p.type.name === "string" ? `${slot_p}.ptr != ${val_p}.ptr` : `${slot_p} != ${val_p}`;
34467
+ });
34468
+ return `else if (${slot_f}.tag == ${elem.name}_${c.name}) { if (${compares.join(" && ")}) { ${reclaim} } }`;
34469
+ });
34470
+ status.code += `if (${slot_f}.tag != ${val_f}.tag) { ${reclaim} }\n`;
34471
+ status.code += `${guards.join(" ")}\n`;
34472
+ }
34473
+ /** The C-side payload reference check (mirrors the enum reclaim's shape). */
34474
+ function payload_is_reference$3(type, status) {
34475
+ return status.structs.some((s) => s.name === type.name && s.is_class) || status.traits.some((t) => t.name === type.name);
34476
+ }
34477
+ /**
33117
34478
  * For each DIRECT string field on the element struct, overwrite the
33118
34479
  * shallow-copied pointer in `dst` with a fresh strdup of `src`'s field. This
33119
34480
  * breaks the pointer aliasing between the source and the slot so each can be
@@ -33434,7 +34795,7 @@ function free_scoped_declarations(status, decls, persist_string_field_records =
33434
34795
  status.code += `(${cast}_get_trait_func((void *)&${cname}, ${traitIndex}, ${funcIndex}))(&${cname});\n`;
33435
34796
  }
33436
34797
  }
33437
- const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved && dec.force_owned_string !== true || is_borrow_accessor_carrier(dec);
34798
+ const is_destructured_field_access = dec.value?.node_type === "access" && dec.value.access.node_type === "access_field" && !dec.value.is_moved && dec.force_owned_string !== true || is_borrow_accessor_carrier(dec) || is_pure_borrow_element_load(dec, status);
33438
34799
  const is_borrowed_string = is_string_borrow(dec.value) && !status.c_owned_borrow_inits?.has(dec) || !!status.string_borrow_vars?.has(dec.name);
33439
34800
  const value_is_heap_string = dec.type.name === "string" && (dec.value?.node_type === "access" && dec.value.access.node_type === "access_func" || dec.value?.node_type === "func_call" || !!dec.value && !dec.type.is_view && is_view_value(dec.value, status));
33440
34801
  const dec_value = dec.value;
@@ -33604,6 +34965,7 @@ function emit_struct_destroys(status, struct, var_expr) {
33604
34965
  continue;
33605
34966
  }
33606
34967
  if ((struct.is_class === true || struct.is_owning === true) && field.type.name === "func" && !field.type.is_array) {
34968
+ if (status.func_field_borrow_decls?.delete(var_expr)) continue;
33607
34969
  const f = `((struct nomen_closure *)${var_expr}.${field.name})`;
33608
34970
  status.code += `if (${f} && ${f}->owned) { if (${f}->destroy_env) ${f}->destroy_env(${f}->env); free(${f}->env); free(${f}); }\n`;
33609
34971
  continue;
@@ -33611,6 +34973,15 @@ function emit_struct_destroys(status, struct, var_expr) {
33611
34973
  const field_struct = resolve_struct_type(field.type, status);
33612
34974
  if (!field_struct) continue;
33613
34975
  const field_expr = `${var_expr}.${field.name}`;
34976
+ if (field.type.is_array && !field_struct.is_class) {
34977
+ const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
34978
+ if (arr_len > 0 && (has_destroy(field_struct) || struct_needs_destroy(field_struct, status) || struct_needs_auto_destroy(field_struct, status))) {
34979
+ status.code += `for (long _i = 0; _i < ${arr_len}; _i++) {\n`;
34980
+ emit_struct_destroys(status, field_struct, `${field_expr}[_i]`);
34981
+ status.code += `}\n`;
34982
+ }
34983
+ continue;
34984
+ }
33614
34985
  if (field_struct.is_class) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); }\n`;
33615
34986
  else if (is_nullable_struct_type(field.type, status)) {
33616
34987
  if (struct_needs_destroy(field_struct, status)) {
@@ -33658,6 +35029,15 @@ function capture_destroys(status, struct, var_expr, accessor) {
33658
35029
  const field_struct = resolve_struct_type(field.type, status);
33659
35030
  if (!field_struct) continue;
33660
35031
  const field_expr = `${var_expr}${accessor}${field.name}`;
35032
+ if (field.type.is_array && !field_struct.is_class) {
35033
+ const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
35034
+ if (arr_len > 0 && (has_destroy(field_struct) || struct_needs_destroy(field_struct, status) || struct_needs_auto_destroy(field_struct, status))) {
35035
+ status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { `;
35036
+ emit_struct_destroys(status, field_struct, `${field_expr}[_i]`);
35037
+ status.code += `} `;
35038
+ }
35039
+ continue;
35040
+ }
33661
35041
  if (field_struct.is_class) status.code += `if (${field_expr}) { ${field_struct.name}_destroy(${field_expr}); free(${field_expr}); } `;
33662
35042
  else if (is_nullable_struct_type(field.type, status)) {
33663
35043
  if (struct_needs_destroy(field_struct, status)) {
@@ -35027,7 +36407,11 @@ function build_assignment_node$1(node, status, nir_rhs, nir_swap) {
35027
36407
  } else if (!node.swap && rhs.is_moved) {
35028
36408
  const src_name = rhs.value;
35029
36409
  if (string_var_owns_heap(status, src_name)) {
35030
- splice_decl_from_c_scopes(status, src_name);
36410
+ const src_flag = status.c_mv_owns_flags?.get(src_name);
36411
+ if (src_flag !== void 0) {
36412
+ if (!status.pending_mv_owns_sets) status.pending_mv_owns_sets = [];
36413
+ status.pending_mv_owns_sets.push(`${src_flag} = 1;`);
36414
+ } else splice_decl_from_c_scopes(status, src_name);
35031
36415
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
35032
36416
  status.heap_strings.add(lhs_name);
35033
36417
  } else {
@@ -35893,11 +37277,13 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
35893
37277
  status.code += `;\n`;
35894
37278
  return;
35895
37279
  }
35896
- if (node.type.is_ref && !node.type.is_array) {
37280
+ const value_struct_ref_alias = node.is_ref_binding === true && !node.type.is_ref && !!status.structs.find((s) => s.name === (mono_name ?? node.type.name) && !s.is_class && !s.is_simple_type && !s.is_generic);
37281
+ if ((node.type.is_ref || value_struct_ref_alias) && !node.type.is_array) {
35897
37282
  if (mono_struct) status.code += `struct ${mono_name} *${safe_name}`;
35898
37283
  else status.code += `${c_type(node.type.name)} *${safe_name}`;
35899
37284
  if (node.value) {
35900
- status.code += ` = &`;
37285
+ if (!(node.value.node_type === "access" && node.value.access.is_element_slot_load === true)) status.code += ` = &`;
37286
+ else status.code += ` = `;
35901
37287
  build_node$1(node.value, status);
35902
37288
  }
35903
37289
  status.code += `;\n`;
@@ -36215,7 +37601,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
36215
37601
  const binding_struct = status.structs.find((s) => s.name === node.type?.name && !s.is_simple_type && !s.is_class);
36216
37602
  const init_access = node.value?.node_type === "access" ? node.value.access : void 0;
36217
37603
  const is_borrow_accessor_init = init_access?.node_type === "access_func" && is_container_borrow_accessor_name(init_access.name ?? "");
36218
- if (binding_struct && owns_strings_uniformly(binding_struct) && is_borrow_accessor_init && !is_borrow_accessor_carrier(node)) {
37604
+ if (binding_struct && owns_strings_uniformly(binding_struct) && is_borrow_accessor_init && !is_borrow_accessor_carrier(node) && !is_pure_borrow_element_load(node, status)) {
36219
37605
  if (!status.code.endsWith(";\n")) status.code += `;\n`;
36220
37606
  const emit_strdups = (s, prefix, guard) => {
36221
37607
  const lines = [];
@@ -36229,6 +37615,7 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
36229
37615
  }
36230
37616
  const field_struct = resolve_struct_type(field.type, status);
36231
37617
  if (!field_struct || field_struct.is_class) continue;
37618
+ if (field_load_deep_copy(field_struct, status)) continue;
36232
37619
  if (!owns_strings_uniformly(field_struct)) continue;
36233
37620
  const nested_guard = is_nullable_struct_type(field.type, status) ? `${lhs}_has` : void 0;
36234
37621
  lines.push(...emit_strdups(field_struct, path, nested_guard));
@@ -36242,6 +37629,10 @@ function build_declaration_node$1(node, status, nir_init, nir_swap) {
36242
37629
  };
36243
37630
  for (const line of emit_strdups(binding_struct, "")) status.code += line;
36244
37631
  }
37632
+ if (binding_struct && is_borrow_accessor_init && !is_borrow_accessor_carrier(node)) {
37633
+ if (!status.func_field_borrow_decls) status.func_field_borrow_decls = /* @__PURE__ */ new Set();
37634
+ status.func_field_borrow_decls.add(safe_name);
37635
+ }
36245
37636
  }
36246
37637
  const override_ctor_call = node.value?.node_type === "func_call" && node.value.field_overrides?.length && node.type?.name && node.value.name === node.type.name ? node.value : void 0;
36247
37638
  if (override_ctor_call) {
@@ -36896,10 +38287,10 @@ function build_return_node(node, status, nir_value) {
36896
38287
  const ret_type = status.function_return_type || node.type;
36897
38288
  const mono_ret_name = mono_type_name(ret_type);
36898
38289
  const return_struct = status.structs.find((s) => s.name === mono_ret_name && !s.is_simple_type);
36899
- const is_struct = !!return_struct;
38290
+ const is_struct = !!return_struct && !ret_type.is_pointer;
36900
38291
  const return_is_class = !!return_struct?.is_class;
36901
38292
  const return_trait = status.traits.find((t) => t.name === ret_type.name);
36902
- const type_prefix = ret_type.is_array ? `struct Array_${ret_type.name}* ` : ret_type.is_view ? "nomen_view " : is_struct ? return_is_class ? `struct ${mono_ret_name}* ` : `struct ${mono_ret_name} ` : return_trait ? `struct ${ret_type.name}* ` : c_type(ret_type.name || "int");
38293
+ const type_prefix = ret_type.is_array ? `struct Array_${ret_type.name}* ` : ret_type.is_pointer ? `${status.traits.find((t) => t.name === ret_type.name) ? `struct ${ret_type.name}` : pointer_element_c_type(ret_type, status)}* ` : ret_type.is_view ? "nomen_view " : is_struct ? return_is_class ? `struct ${mono_ret_name}* ` : `struct ${mono_ret_name} ` : return_trait ? `struct ${ret_type.name}* ` : c_type(ret_type.name || "int");
36903
38294
  if (node.value.node_type === "match" || node.value.node_type === "switch" || node.value.node_type === "if") {
36904
38295
  const join_branch_values = collect_expression_branch_values(node.value);
36905
38296
  const first_branch_type = join_branch_values.length ? type_from_value_node$1(join_branch_values[0])?.name : void 0;
@@ -36937,10 +38328,10 @@ function build_return_node(node, status, nir_value) {
36937
38328
  }
36938
38329
  status.code += `${type_prefix} _return_val = `;
36939
38330
  if (return_trait && !ret_type.is_view) status.code += `(struct ${ret_type.name} *)`;
36940
- let returns_borrowed_string = ret_type.name === "string" && !ret_type.is_view && node.value.node_type === "access";
36941
- const returns_string_literal = ret_type.name === "string" && !ret_type.is_view && node.value.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"");
38331
+ let returns_borrowed_string = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_pointer && node.value.node_type === "access";
38332
+ const returns_string_literal = ret_type.name === "string" && !ret_type.is_view && !ret_type.is_pointer && node.value.node_type === "value" && node.value.value.length >= 2 && node.value.value.startsWith("\"") && node.value.value.endsWith("\"");
36942
38333
  let returns_borrow_var = false;
36943
- if (ret_type.name === "string" && !ret_type.is_view && node.value.node_type === "value" && !returns_string_literal) {
38334
+ if (ret_type.name === "string" && !ret_type.is_view && !ret_type.is_pointer && node.value.node_type === "value" && !returns_string_literal) {
36944
38335
  const var_name = node.value.value;
36945
38336
  if (!returned_value_decl) returns_borrow_var = true;
36946
38337
  else if (is_string_borrow(returned_value_decl.value) || !!status.string_borrow_vars?.has(var_name)) returns_borrow_var = true;
@@ -37618,6 +39009,8 @@ function build_struct_node(node, status) {
37618
39009
  status.init_default_reclaimed_fields = /* @__PURE__ */ new Set();
37619
39010
  const old_init_heap_string_fields = status.heap_string_fields;
37620
39011
  status.heap_string_fields = void 0;
39012
+ const old_init_variable_types = status.variable_types;
39013
+ status.variable_types = /* @__PURE__ */ new Map();
37621
39014
  for (const field of node.fields) if (field.value) {
37622
39015
  status.init_assigned_fields?.add(`self.${field.name}`);
37623
39016
  if (field.type.is_view && !field.type.is_array && !is_nullable_struct_type(field.type, status)) {
@@ -37707,6 +39100,7 @@ function build_struct_node(node, status) {
37707
39100
  status.init_assigned_fields = old_init_assigned_fields;
37708
39101
  status.init_default_reclaimed_fields = old_init_default_reclaimed;
37709
39102
  status.heap_string_fields = old_init_heap_string_fields;
39103
+ status.variable_types = old_init_variable_types;
37710
39104
  status.function_return_type = old_return_type;
37711
39105
  }
37712
39106
  build_struct_functions(node, status, true);
@@ -37724,8 +39118,21 @@ function build_struct_node(node, status) {
37724
39118
  build_node$1(field.value, status);
37725
39119
  status.code += `; ${object_name}${accessor}${field.name} = (nomen_view){ _p.ptr, _p.len }; }\n`;
37726
39120
  } else status.code += `${object_name}${accessor}${field.name} = (nomen_view){0};\n`;
37727
- } 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`;
37728
- else if ((is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) && field.value) {
39121
+ } else if (field.type.storage_kind === "stack_array" && field.type.length) {
39122
+ status.code += `memcpy(${object_name}${accessor}${field.name}, ${field.name}, sizeof(${object_name}${accessor}${field.name}));\n`;
39123
+ const elem_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class && !s.is_generic);
39124
+ if (elem_struct && owns_strings_uniformly(elem_struct)) {
39125
+ const arr_len = parseInt(field.type.length.value || "0");
39126
+ for (const sf of class_destroy_fields(elem_struct, status)) {
39127
+ if (sf.type.is_ref || sf.type.is_view || sf.type.is_array) continue;
39128
+ if (sf.type.name !== "string") continue;
39129
+ for (let e = 0; e < arr_len; e++) {
39130
+ const slot = `${object_name}${accessor}${field.name}[${e}].${sf.name}`;
39131
+ status.code += `${slot}.ptr = ${slot}.ptr ? nomen_str_dup(${slot}).ptr : ${slot}.ptr;\n`;
39132
+ }
39133
+ }
39134
+ }
39135
+ } else if ((is_nullable_struct_type(field.type, status) || is_nullable_scalar_type(field.type, status)) && field.value) {
37729
39136
  if (field.value.node_type === "value" && field.value.value === "null") status.code += `${object_name}${accessor}${has_flag_name(field.name)} = 0;\n`;
37730
39137
  else {
37731
39138
  status.code += `${object_name}${accessor}${field.name} = `;
@@ -37869,6 +39276,8 @@ function build_struct_functions(node, status, skip_init = false) {
37869
39276
  const old_ref_class_params = status.ref_class_params;
37870
39277
  const old_ref_class_param_types = status.ref_class_param_types;
37871
39278
  const old_scoped_declarations = status.scoped_declarations;
39279
+ const old_variable_types = status.variable_types;
39280
+ status.variable_types = /* @__PURE__ */ new Map();
37872
39281
  const old_borrow_only = status.c_borrow_only_strings;
37873
39282
  const old_force_heap = status.force_heap_strings;
37874
39283
  const old_heap_array_vars = status.heap_array_vars;
@@ -37931,7 +39340,10 @@ function build_struct_functions(node, status, skip_init = false) {
37931
39340
  const returns_array_data = (func.return_type.type_args?.length ?? 0) > 0 && return_type === "Array";
37932
39341
  const emit_label = is_overloaded(node, func.name) ? mangled_label(func, node.name) : `${node.name}_${func.name.replace(/#/g, "")}`;
37933
39342
  if (func.return_type.is_array) status.code += `void* ${emit_label}(`;
37934
- else if (returns_array_data) {
39343
+ else if (func.return_type.is_pointer) {
39344
+ const elem_c = status.traits.find((t) => t.name === func.return_type.name) ? `struct ${func.return_type.name}` : pointer_element_c_type(func.return_type, status);
39345
+ status.code += `${elem_c}* ${emit_label}(`;
39346
+ } else if (returns_array_data) {
37935
39347
  const elem_type = c_type(func.return_type.type_args[0].name);
37936
39348
  status.code += `${elem_type}* ${emit_label}(`;
37937
39349
  } else if (return_type.startsWith("Array_")) status.code += `void* ${emit_label}(`;
@@ -37996,6 +39408,7 @@ function build_struct_functions(node, status, skip_init = false) {
37996
39408
  status.current_function_name = old_function_name;
37997
39409
  status.current_function = old_current_function;
37998
39410
  status.function_view_params = old_view_params;
39411
+ status.variable_types = old_variable_types;
37999
39412
  }
38000
39413
  status.current_struct = old_current_struct;
38001
39414
  for (let traitName of node.traits) {
@@ -38066,6 +39479,11 @@ function build_auto_destroy(node, status) {
38066
39479
  const field_struct = status.structs.find((s) => s.name === mono_name && !s.is_simple_type);
38067
39480
  if (process.env.NOMEN_DEBUG_DESTROY) console.error("AUTODESTROY-LOOKUP:", node.name, field.name, mono_name, "found:", !!field_struct, "nstructs:", status.structs.length);
38068
39481
  if (!field_struct) continue;
39482
+ if (field.type.is_array && !field_struct.is_class) {
39483
+ const arr_len = field.type.length ? parseInt(field.type.length.value || "0") : 0;
39484
+ if (arr_len > 0 && (field_struct.functions.find((f) => f.name === "#destroy") || struct_needs_auto_destroy(field_struct, status))) status.code += `for (long _i = 0; _i < ${arr_len}; _i++) { ${field_struct.name}_destroy(&self->${field.name}[_i]); }\n`;
39485
+ continue;
39486
+ }
38069
39487
  if (field_struct.is_class) {
38070
39488
  const field_has_destroy = !!field_struct.functions.find((f) => f.name === "#destroy");
38071
39489
  const field_is_owned = field.declaration === "mv" || node.is_owning === true;
@@ -39013,7 +40431,7 @@ function emit_boxed_value_struct_c(status, arg, box_struct) {
39013
40431
  }
39014
40432
  const unrecorded = string_fields.filter((f) => !recorded.has(f.name));
39015
40433
  if (arg.node_type === "func_call" && arg.name === box_struct.name) {
39016
- status.code += `(void *)({ struct ${box_struct.name} *_box = (struct ${box_struct.name} *)nomen_malloc_wrap(sizeof(struct ${box_struct.name})); *_box = `;
40434
+ status.code += `(void *)({ struct ${box_struct.name} *_box = (struct ${box_struct.name} *)malloc(sizeof(struct ${box_struct.name})); *_box = `;
39017
40435
  build_node$1(arg, status);
39018
40436
  status.code += `; `;
39019
40437
  for (const f of unrecorded) status.code += `_box->${f.name} = nomen_str_dup(_box->${f.name}); `;
@@ -39028,7 +40446,7 @@ function emit_boxed_value_struct_c(status, arg, box_struct) {
39028
40446
  const addr_text = end_code_scratch(status, saved_arg);
39029
40447
  status.code += `(void *)({ struct ${box_struct.name} *_src = (struct ${box_struct.name} *)${addr_text}; `;
39030
40448
  for (const f of unrecorded) status.code += `_src->${f.name} = nomen_str_dup(_src->${f.name}); `;
39031
- status.code += `struct ${box_struct.name} *_box = (struct ${box_struct.name} *)nomen_malloc_wrap(sizeof(struct ${box_struct.name})); `;
40449
+ status.code += `struct ${box_struct.name} *_box = (struct ${box_struct.name} *)malloc(sizeof(struct ${box_struct.name})); `;
39032
40450
  status.code += `*_box = *_src; `;
39033
40451
  status.code += `(void *)_box; })`;
39034
40452
  }
@@ -39837,6 +41255,7 @@ function build_access_node$1(node, status) {
39837
41255
  }
39838
41256
  case "access_func": {
39839
41257
  const access_func = node.access;
41258
+ if (access_func.is_handler_registration) return;
39840
41259
  if (access_func.is_nursery_spawn) {
39841
41260
  build_nursery_spawn$1(access_func, nursery_pointer_expr(node.target, status), status);
39842
41261
  return;
@@ -40599,7 +42018,7 @@ void **_get_trait_func(void **obj, int trait_index, int func_index)
40599
42018
  let range = Array.from({ length }, (_, i) => i);
40600
42019
  let declaration = `nomen_string _string_interpolate_${length}(nomen_string pattern, ${range.map((n) => `nomen_string arg${n + 1}`).join(", ")})`;
40601
42020
  status.headers += `${declaration};\n`;
40602
- if (status.emit_mode !== "user") status.code += `${declaration}
42021
+ if (status.emit_mode !== "user" || length > 7) status.code += `${declaration}
40603
42022
  {
40604
42023
  int length = snprintf(NULL, 0, pattern.ptr, ${range.map((n) => `arg${n + 1}.ptr`).join(", ")});
40605
42024
  char *str = malloc(length + 1);
@@ -42374,6 +43793,17 @@ function emit_struct_constructor_to_slot(fc, slot_addr, status) {
42374
43793
  function build_constructor_params(fc, param_regs, status) {
42375
43794
  mark_tuple_literal_move_owners(fc, status, (name) => mark_local_moved_flag(status, name));
42376
43795
  const variadic_idx = fc.variadic_param_index;
43796
+ if (variadic_idx === void 0 && fc.params.length === 0) {
43797
+ const resolved = fc.resolved_function ?? status.structs.find((s) => s.name === fc.name)?.functions.find((f) => f.name === "#init");
43798
+ const variadic_param = resolved?.params?.find((p) => p.is_variadic);
43799
+ if (resolved && variadic_param && resolved.params.filter((p) => !p.is_self_param && !p.is_variadic).length === 0) {
43800
+ const elem_type_name = variadic_param.type.tuple_types?.length ? variadic_param.type.tuple_types[0].name : variadic_param.type.name || "int";
43801
+ const arr_offset = allocate_stack_space(status, 8 + (status.structs.find((s) => s.name === elem_type_name && !s.is_simple_type) ? get_struct_size(elem_type_name, status) : elem_type_name === "string" ? 16 : 8), 16);
43802
+ emit_asm(status, `str xzr, [x29, #${arr_offset}]\n`);
43803
+ emit_asm(status, `mov x1, #0\n`);
43804
+ emit_asm(status, `add x2, x29, #${arr_offset + 8}\n`);
43805
+ }
43806
+ }
42377
43807
  if (variadic_idx !== void 0 && fc.params[variadic_idx] && fc.params[variadic_idx].node_type === "array") {
42378
43808
  const arr = fc.params[variadic_idx];
42379
43809
  const elem_type_name = arr.type.name || "int";
@@ -42906,6 +44336,26 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
42906
44336
  }
42907
44337
  return;
42908
44338
  }
44339
+ const value_struct_ref_alias = node.is_ref_binding === true && !node.type.is_ref && !!status.structs.find((s) => s.name === node.type.name && !s.is_class && !s.is_simple_type && !s.is_generic);
44340
+ if (node.type.is_ref || value_struct_ref_alias) {
44341
+ const offset = allocate_stack_space(status, 8);
44342
+ status.stack_offsets.set(node.name, offset);
44343
+ if (node.value) {
44344
+ const init_is_slot_load = node.value.node_type === "access" && node.value.access.is_element_slot_load === true;
44345
+ if (node.value.node_type === "value") emit_var_address(status, "x0", node.value.value);
44346
+ else if (init_is_slot_load) {
44347
+ build_node(node.value, status);
44348
+ ensure_newline(status);
44349
+ } else {
44350
+ emit_init_value(node.value, nir_init, status);
44351
+ ensure_newline(status);
44352
+ }
44353
+ emit_var_store(status, "x0", node.name, 8);
44354
+ }
44355
+ if (!status.function_ref_params) status.function_ref_params = /* @__PURE__ */ new Set();
44356
+ status.function_ref_params.add(node.name);
44357
+ return;
44358
+ }
42909
44359
  if (node.type.is_pointer) {
42910
44360
  const offset = allocate_stack_space(status, 8);
42911
44361
  status.stack_offsets.set(node.name, offset);
@@ -43150,7 +44600,8 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
43150
44600
  status.alias_owns_flag?.set(node.name, flag_offset);
43151
44601
  }
43152
44602
  }
43153
- if (!is_borrowed_class_ref && !is_field_struct_borrow(node) && struct_type && struct_needs_destroy(struct_type, status)) track_struct_decl(status, node.name, node.type.name, node.type.type_args, node.type.is_nullable);
44603
+ const pure_borrow_element_load = !is_borrowed_class_ref && is_pure_borrow_element_load(node, status);
44604
+ if (!is_borrowed_class_ref && !is_field_struct_borrow(node) && !pure_borrow_element_load && struct_type && struct_needs_destroy(struct_type, status)) track_struct_decl(status, node.name, node.type.name, node.type.type_args, node.type.is_nullable);
43154
44605
  if (status.enums.find((e) => e.name === node.type.name && e.has_associated_data)) {
43155
44606
  const enum_size = get_enum_size(node.type.name, status);
43156
44607
  if (status.function_return_label) {
@@ -43602,9 +45053,13 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
43602
45053
  status.stack_offsets.set(node.name, offset);
43603
45054
  } else emit_data(status, `${node.name}: .space 8\n`);
43604
45055
  if (node.value) {
43605
- if (node.value.node_type === "value") {
45056
+ const init_is_slot_load = node.value.node_type === "access" && node.value.access.is_element_slot_load === true;
45057
+ if (node.value.node_type === "value" && !init_is_slot_load) {
43606
45058
  const src_name = node.value.value;
43607
45059
  emit_var_address(status, "x0", src_name);
45060
+ } else if (init_is_slot_load) {
45061
+ build_node(node.value, status);
45062
+ ensure_newline(status);
43608
45063
  } else {
43609
45064
  emit_init_value(node.value, nir_init, status);
43610
45065
  ensure_newline(status);
@@ -43788,13 +45243,18 @@ function build_declaration_node(node, status, nir_init, nir_swap) {
43788
45243
  const init_access = node.value?.node_type === "access" ? node.value.access : void 0;
43789
45244
  const is_borrow_accessor_init = init_access?.node_type === "access_func" && is_container_borrow_accessor_name(init_access.name ?? "");
43790
45245
  const is_compiler_carrier = is_compiler_temp_name$2(node.name);
43791
- if (binding_struct && owns_strings_uniformly(binding_struct) && is_borrow_accessor_init) {
45246
+ const pure_borrow_init = is_borrow_accessor_init && is_pure_borrow_element_load(node, status);
45247
+ if (binding_struct && owns_strings_uniformly(binding_struct) && is_borrow_accessor_init && !pure_borrow_init) {
43792
45248
  if (is_compiler_carrier) node.borrow_accessor_carrier = true;
43793
45249
  else {
43794
45250
  emit_var_address(status, "x2", node.name);
43795
45251
  emit_owning_field_strdups_at(status, binding_struct.name, "x2");
43796
45252
  }
43797
45253
  }
45254
+ if (binding_struct && is_borrow_accessor_init && !is_compiler_carrier) {
45255
+ if (!status.func_field_borrow_decls) status.func_field_borrow_decls = /* @__PURE__ */ new Set();
45256
+ status.func_field_borrow_decls.add(node.name);
45257
+ }
43798
45258
  }
43799
45259
  if (node.value.is_moved) mark_moved_if_struct(node.value, status);
43800
45260
  else if (node.value.node_type === "anon_struct") {
@@ -45062,9 +46522,9 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
45062
46522
  if (self_param.value !== name) mark_moved_if_struct(self_param, status);
45063
46523
  }
45064
46524
  }
45065
- const lhs_decl = status.scoped_declarations.find((d) => d.name === name);
45066
- const lhs_type_name = lhs_decl?.type?.name ?? status.variable_types?.get(name)?.name ?? "";
45067
- if (rhs_is_struct && !rhs_struct && !paramReg && !is_local_ref_var(name, status) && lhs_type_name) emit_destroy_for_decl(status, name, lhs_type_name, void 0, lhs_decl?.type?.type_args, lhs_decl?.type?.is_nullable);
46525
+ const lhs_decl_type = status.scoped_declarations.find((d) => d.name === name)?.type ?? status.variable_types?.get(name);
46526
+ const lhs_type_name = lhs_decl_type?.name ?? "";
46527
+ if (rhs_is_struct && !rhs_struct && !paramReg && !is_local_ref_var(name, status) && lhs_type_name) emit_destroy_for_decl(status, name, lhs_type_name, void 0, lhs_decl_type?.type_args, lhs_decl_type?.is_nullable);
45068
46528
  get_source_address(node.right_value, status, nir_rhs);
45069
46529
  ensure_newline(status);
45070
46530
  if (paramReg && is_mutable_param(name, status)) {
@@ -45378,7 +46838,12 @@ function build_assignment_node(node, status, nir_rhs, nir_swap) {
45378
46838
  else if (status.last_result_is_heap) {
45379
46839
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
45380
46840
  status.heap_strings.add(name);
45381
- } else if (explicit_move_source !== void 0 && status.heap_strings?.delete(explicit_move_source)) status.heap_strings.add(name);
46841
+ } else if (explicit_move_source !== void 0 && status.heap_strings?.has(explicit_move_source)) {
46842
+ if (!status.moved) status.moved = /* @__PURE__ */ new Set();
46843
+ status.moved.add(explicit_move_source);
46844
+ mark_local_moved_flag(status, explicit_move_source);
46845
+ status.heap_strings.add(name);
46846
+ }
45382
46847
  if (lhs_is_heap && size === 16 && !status.heap_strings?.has(name)) {
45383
46848
  emit_strdup_string(status);
45384
46849
  if (!status.heap_strings) status.heap_strings = /* @__PURE__ */ new Set();
@@ -48391,6 +49856,7 @@ function build_access_node(node, status) {
48391
49856
  break;
48392
49857
  case "access_func": {
48393
49858
  const access_func = node.access;
49859
+ if (access_func.is_handler_registration) return;
48394
49860
  if (access_func.is_nursery_spawn) {
48395
49861
  build_nursery_spawn(node, access_func, status);
48396
49862
  return;
@@ -49501,7 +50967,7 @@ function build_access_method(node, access_func, status) {
49501
50967
  const method_ret_type = access_callee_method?.return_type ?? access_func.type;
49502
50968
  const resolved_ret_type = resolve_mono_type(method_ret_type, status);
49503
50969
  const ret_type_name = resolved_ret_type.name || access_func.type.name;
49504
- const return_struct = !access_func.type.is_view && !access_func.type.is_array && !resolved_ret_type.is_view && !resolved_ret_type.is_array && (!!status.structs.find((s) => s.name === ret_type_name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(ret_type_name, status) !== void 0);
50970
+ const return_struct = !access_func.type.is_view && !access_func.type.is_array && !resolved_ret_type.is_view && !resolved_ret_type.is_array && !resolved_ret_type.is_pointer && !access_func.type.is_pointer && (!!status.structs.find((s) => s.name === ret_type_name && !s.is_simple_type && !s.is_class) || get_enum_sret_size(ret_type_name, status) !== void 0);
49505
50971
  if (process.env.NOMEN_DBG_SRET) __require("node:fs").appendFileSync("/var/folders/j7/hblq8b2n0t5258cz04tslkg80000gp/T/opencode/nom_asm/dbg.log", `access method=${method_name} ret=${access_func.type.name} mono=${mono_struct_name} return_struct=${return_struct}\n`);
49506
50972
  const nullable_scalar_ret = is_nullable_scalar_type(method_ret_type, status) && !method_ret_type.is_ref;
49507
50973
  let temp_addr = "";
@@ -49569,7 +51035,7 @@ function build_access_method(node, access_func, status) {
49569
51035
  emit_asm(status, `ldr x0, [x0]\n`);
49570
51036
  emit_asm(status, `add x0, x0, #8\n`);
49571
51037
  } else if (target_type.is_array && (status.function_array_params?.has(name) || status.function_variadic_params?.has(name)) && !is_local_ref_var(name, status)) emit_asm(status, `ldr x0, [x0]\n`);
49572
- else if (target_is_simple && !target_type.is_array && (target_type.name !== "string" || has_stack_offset) && !is_local_ref_var(name, status) && !method_self_is_ref) {
51038
+ else if (target_is_simple && !target_type.is_array && (target_type.name !== "string" || has_stack_offset) && !method_self_is_ref && (!is_local_ref_var(name, status) || target_type.name !== "string")) {
49573
51039
  const size = aarch64_size(target_type.name);
49574
51040
  const signed = is_signed_type(target_type.name);
49575
51041
  if (size === 1) emit_asm(status, signed ? `ldrsb x0, [x0]\n` : `ldrb w0, [x0]\n`);
@@ -50044,269 +51510,6 @@ function resolve_access_type(node, status) {
50044
51510
  return struct.fields.find((f) => f.name === field_name)?.type || null;
50045
51511
  }
50046
51512
  //#endregion
50047
- //#region ../src/build_aarch64/utils/c_companion.ts
50048
- /**
50049
- * Generate the complete C companion file: includes, struct typedefs, and
50050
- * function definitions for every collected `aarch64_use_c` function.
50051
- */
50052
- function generate_companion(functions, status) {
50053
- let out = "";
50054
- if (functions.length > 0 && (status.platform === "macos" || status.platform === "ios")) {
50055
- out += `#import <Foundation/Foundation.h>\n`;
50056
- out += `#include <objc/runtime.h>\n`;
50057
- out += `#include <objc/message.h>\n`;
50058
- if (status.platform === "macos") out += `#import <Cocoa/Cocoa.h>\n`;
50059
- else out += `#import <UIKit/UIKit.h>\n`;
50060
- }
50061
- out += `#include <stdint.h>\n`;
50062
- out += `#include <stdlib.h>\n`;
50063
- out += `typedef struct { void* ptr; long len; } nomen_view;\n`;
50064
- out += `typedef struct { char* ptr; long len; } nomen_string;\n`;
50065
- out += `#include <stdio.h>\n`;
50066
- out += `#include <string.h>\n`;
50067
- out += `#include <regex.h>\n`;
50068
- if (status.file_scope_c?.includes("pthread") || status.emit_mode === "system") out += `#include <pthread.h>\n`;
50069
- out += "\n";
50070
- if (status.audit) {
50071
- out += `void *nomen_malloc_wrap(unsigned long);\n`;
50072
- out += `void *nomen_calloc_wrap(unsigned long, unsigned long);\n`;
50073
- out += `void *nomen_realloc_wrap(void *, unsigned long);\n`;
50074
- out += `void nomen_free_wrap(void *);\n`;
50075
- out += `void *nomen_strdup_wrap(const char *);\n`;
50076
- out += `void nomen_audit_check(void);\n`;
50077
- out += "\n";
50078
- }
50079
- if (status.emit_mode === "system") {
50080
- out += runtime_declarations(POOL_HEADER_C + FIBER_HEADER_C);
50081
- out += "\n";
50082
- } else {
50083
- if (!status.pool_runtime_emitted) {
50084
- out += POOL_HEADER_C;
50085
- status.pool_runtime_emitted = true;
50086
- }
50087
- if (!status.fiber_runtime_emitted) {
50088
- out += FIBER_HEADER_C;
50089
- status.fiber_runtime_emitted = true;
50090
- }
50091
- }
50092
- const enum_nodes = status.enums.filter((e) => !e.is_generic);
50093
- const struct_nodes = status.structs.filter((s) => !s.is_simple_type && !s.is_generic);
50094
- const node_kind = /* @__PURE__ */ new Map();
50095
- for (const e of enum_nodes) node_kind.set(e.name, "enum");
50096
- for (const st of struct_nodes) node_kind.set(st.name, "struct");
50097
- for (const b of status.bitsets) node_kind.set(b.name, "bitset");
50098
- const deps = /* @__PURE__ */ new Map();
50099
- for (const e of enum_nodes) {
50100
- const d = /* @__PURE__ */ new Set();
50101
- for (const c of e.cases) for (const p of c.params) if (node_kind.has(p.type.name) && p.type.name !== e.name) d.add(p.type.name);
50102
- deps.set(e.name, d);
50103
- }
50104
- for (const st of struct_nodes) {
50105
- const d = /* @__PURE__ */ new Set();
50106
- for (const f of st.fields) {
50107
- if (f.type.is_view) continue;
50108
- if (node_kind.has(f.type.name) && f.type.name !== st.name) d.add(f.type.name);
50109
- }
50110
- deps.set(st.name, d);
50111
- }
50112
- const emitted = /* @__PURE__ */ new Set();
50113
- const visiting = /* @__PURE__ */ new Set();
50114
- const node_list = [
50115
- ...enum_nodes,
50116
- ...struct_nodes,
50117
- ...status.bitsets
50118
- ];
50119
- const by_name = new Map(node_list.map((n) => [n.name, n]));
50120
- const visit = (name) => {
50121
- if (emitted.has(name) || visiting.has(name)) return;
50122
- visiting.add(name);
50123
- for (const dep of deps.get(name) ?? []) visit(dep);
50124
- visiting.delete(name);
50125
- emitted.add(name);
50126
- const node = by_name.get(name);
50127
- if (!node) return;
50128
- if (node_kind.get(name) === "enum") out += generate_enum_definition(node, status);
50129
- else if (node_kind.get(name) === "bitset") out += `typedef unsigned long ${nm(name)};\n`;
50130
- else out += generate_struct_definition(node, status);
50131
- };
50132
- for (const n of node_list) visit(n.name);
50133
- out += "\n";
50134
- if (status.file_scope_c) {
50135
- out += status.file_scope_c;
50136
- out += "\n";
50137
- }
50138
- for (const entry of functions) out += generate_c_function(entry.func, entry.struct_name, entry.raw_code, status);
50139
- return out;
50140
- }
50141
- /**
50142
- * Prefix a Nomen type's C TYPEDEF name with `nm_`. The companion file `#import`s
50143
- * the platform frameworks (Foundation/Cocoa/UIKit), which drag in a large set of
50144
- * system typedefs (e.g. macOS `MacTypes.h` defines `typedef long Size`). If we
50145
- * emitted Nomen's own `typedef struct Size {...} Size;` the typedef name would
50146
- * collide; mangling only the typedef (`typedef struct Size {...} nm_Size;`)
50147
- * sidesteps it while leaving the struct TAG (`Size`) untouched.
50148
- *
50149
- * Keeping the original tag matters: generated C (spawn/trampoline infra in
50150
- * build_spawn_node / build_nursery_spawn) and `#arch: aarch64_use_c` raw bodies
50151
- * reference Nomen types as `struct Foo` (the tag), so an unchanged tag means
50152
- * none of that code needs to know about mangling. Codegen-generated references
50153
- * here (fields, params, returns) use the mangled typedef instead.
50154
- *
50155
- * `nm_` (no leading underscore: the C standard reserves `_`+lowercase
50156
- * identifiers at file scope). This is purely cosmetic — the aarch64 assembly
50157
- * never references type names (only function labels, bridged via `__asm__`).
50158
- */
50159
- function nm(name) {
50160
- return "nm_" + name;
50161
- }
50162
- /**
50163
- * The C type for a field/param/return of the given Nomen type, applying `nm_`
50164
- * to user-defined struct/enum types and falling back to `c_type` for primitives.
50165
- * Generic structs lower to opaque 8-byte pointers (their element type lives in
50166
- * the Nomen `Type`, not in the C layout).
50167
- */
50168
- /**
50169
- * The C type for a value stored BY VALUE in an enum payload: classes and
50170
- * traits are heap/shared references (pointers, and never have a companion
50171
- * typedef), everything else follows `companion_type`.
50172
- */
50173
- function payload_type(typeName, status) {
50174
- if (status.structs.find((s) => s.name === typeName)?.is_class) return `struct ${typeName} *`;
50175
- if (status.traits.find((t) => t.name === typeName)) return `struct ${typeName} *`;
50176
- return companion_type(typeName, status);
50177
- }
50178
- function companion_type(typeName, status) {
50179
- const struct = status.structs.find((s) => s.name === typeName);
50180
- if (struct?.is_generic) return "void *";
50181
- if (struct && !struct.is_simple_type) return nm(typeName);
50182
- if (!struct && status.enums.find((e) => e.name === typeName)) return nm(typeName);
50183
- if (status.bitsets.find((b) => b.name === typeName)) return nm(typeName);
50184
- return c_type(typeName);
50185
- }
50186
- /**
50187
- * Emit an enum's C type definition. Mirrors the C backend's `build_enum_node`
50188
- * (typedef for simple enums; tag + tagged-union struct for enums with associated
50189
- * data) but with `nm_`-prefixed TYPEDEF names and no constructor functions (those
50190
- * are emitted as assembly in the aarch64 path; the companion only needs types so
50191
- * struct fields / function signatures can reference them). The struct tag keeps
50192
- * the original Nomen name (see `nm`).
50193
- */
50194
- function generate_enum_definition(node, status) {
50195
- let out = "";
50196
- if (node.has_associated_data) {
50197
- out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)}_tag;\n`;
50198
- out += `struct ${node.name};\n`;
50199
- out += `typedef struct ${node.name}\n{\n`;
50200
- out += `${nm(node.name)}_tag tag;\n`;
50201
- out += `union {\n`;
50202
- for (const c of node.cases) out += `struct { ${c.params.map((p) => `${payload_type(p.type.name, status)} ${p.name}`).join("; ")}${c.params.length ? ";" : ""} } _${c.name};\n`;
50203
- out += `} _data;\n`;
50204
- out += `} ${nm(node.name)};\n`;
50205
- } else out += `typedef enum { ${node.cases.map((c) => `${node.name}_${c.name}`).join(", ")} } ${nm(node.name)};\n`;
50206
- return out;
50207
- }
50208
- function generate_struct_definition(struct, status) {
50209
- let out = `typedef struct ${struct.name}\n{\n`;
50210
- out += `void *_vt;\n`;
50211
- for (const field of struct.fields) out += `${field.type.is_view ? "nomen_view" : payload_type(field.type.name, status)} ${field.name};\n`;
50212
- for (const traitName of struct.traits) {
50213
- const trait = status.traits.find((t) => t.name === traitName);
50214
- if (!trait) continue;
50215
- for (const field of trait.fields.filter((f) => !struct.fields.find((nf) => nf.name === f.name))) out += `${field.type.is_view ? "nomen_view" : payload_type(field.type.name, status)} ${field.name};\n`;
50216
- }
50217
- out += `} ${nm(struct.name)};\n`;
50218
- return out;
50219
- }
50220
- function generate_c_function(func, struct_name, raw_code, status) {
50221
- let func_label;
50222
- if (struct_name) {
50223
- const struct = status.structs.find((s) => s.name === struct_name);
50224
- if (struct && is_overloaded(struct, func.name)) func_label = mangled_label(func, struct_name);
50225
- else func_label = `${struct_name}_${func.name.replace(/#/g, "")}`;
50226
- } else func_label = func.name === "main" ? "_nomen_main" : func.name;
50227
- const is_class_init = func.name === "#init" && !!(struct_name && status.structs.find((s) => s.name === struct_name));
50228
- const return_type = is_class_init ? "void" : func.return_type?.name || "void";
50229
- const return_struct = is_class_init ? void 0 : status.structs.find((s) => s.name === return_type && !s.is_simple_type);
50230
- let return_prefix = return_struct ? nm(return_type) : companion_type(return_type, status);
50231
- return_prefix += ` `;
50232
- const return_enum_size = return_struct ? void 0 : get_enum_sret_size(return_type, status);
50233
- const return_struct_size = return_struct ? get_struct_size(return_type, status) : 0;
50234
- const symbol_label = return_struct && return_struct_size <= 16 || return_enum_size !== void 0 && return_enum_size <= 16 ? `${func_label}_c` : func_label;
50235
- const params = func.params;
50236
- let param_list = "";
50237
- for (let i = 0; i < params.length; i++) {
50238
- if (i > 0) param_list += ", ";
50239
- param_list += generate_c_param(params[i], status);
50240
- }
50241
- if (params.length === 0) param_list += `void`;
50242
- let out = `// ${func_label}\n`;
50243
- out += `${return_prefix}${symbol_label}(${param_list}) __asm__("${symbol_label}");\n`;
50244
- out += `${return_prefix}${symbol_label}(${param_list})\n{\n`;
50245
- if (func.name === "#init" && struct_name) {
50246
- const ctor_struct = status.structs.find((s) => s.name === struct_name);
50247
- if (ctor_struct && !ctor_struct.is_simple_type) {
50248
- if (ctor_struct.traits.length > 0) {
50249
- out += `extern void *${struct_name}_traits;\n`;
50250
- out += `self->_vt = (void *)&${struct_name}_traits;\n`;
50251
- } else if (!ctor_struct.is_class) out += `self->_vt = (void *)1;\n`;
50252
- for (const field of ctor_struct.fields) {
50253
- if (!field.value) continue;
50254
- const seeded = emit_ctor_field_default(ctor_struct, field);
50255
- if (!seeded) {
50256
- if (!status.build_errors) status.build_errors = [];
50257
- status.build_errors.push({
50258
- message: `mv struct '${struct_name}' has a use_c constructor and field '${field.name}' has a non-literal default — assign it in the #init body`,
50259
- start: func.start
50260
- });
50261
- continue;
50262
- }
50263
- out += seeded;
50264
- }
50265
- }
50266
- }
50267
- const self_param = params[0];
50268
- if (struct_name && self_param?.is_self_param && !self_param?.is_ref && func.name !== "#destroy" && func.name !== "#init") {
50269
- const struct = status.structs.find((s) => s.name === struct_name);
50270
- if (struct && !struct.is_simple_type) out += `${nm(struct_name)} _self = *self;\n`;
50271
- }
50272
- out += raw_code;
50273
- out += `\n}\n\n`;
50274
- return out;
50275
- }
50276
- /**
50277
- * The C statement seeding a defaulted field in a companion constructor, or
50278
- * `undefined` when the default is not a literal shape. Mirrors the ASM-side
50279
- * ctor's default seeding for the shapes a `use_c` constructor can express:
50280
- * string literals borrow static storage (a function-static C literal — never
50281
- * strdup'd, matching the ASM literal-pair store), scalar/bool literals store
50282
- * directly. Nullable and computed defaults are rejected — the caller turns
50283
- * that into a build error naming the field.
50284
- */
50285
- function emit_ctor_field_default(struct_node, field) {
50286
- const value = field.value?.node_type === "value" ? field.value.value : void 0;
50287
- if (value === void 0 || typeof value !== "string") return void 0;
50288
- if (field.type.is_nullable) return void 0;
50289
- if (field.type.name === "string" && !field.type.is_view) {
50290
- const length = string_literal_length(value);
50291
- const label = `_${struct_node.name}_${field.name}_default`;
50292
- return `static const char ${label}[] = ${escape_asciz(value)};\nself->${field.name} = (nomen_string){ (char *)${label}, ${length} };\n`;
50293
- }
50294
- if (value === "true" || value === "false") return `self->${field.name} = ${value === "true" ? 1 : 0};\n`;
50295
- if (/^-?\d+$/.test(value)) return `self->${field.name} = ${value};\n`;
50296
- }
50297
- function generate_c_param(param, status) {
50298
- const struct_type = status.structs.find((s) => s.name === param.type.name);
50299
- const trait_type = status.traits.find((t) => t.name === param.type.name);
50300
- const is_struct = (param.is_self_param || struct_type || trait_type) && !struct_type?.is_simple_type;
50301
- let out = "";
50302
- if (param.is_variadic) out += `long _${param.name}_len, `;
50303
- out += companion_type(param.type.name, status);
50304
- if (is_struct || param.declaration === "var" || param.type.is_ref || param.type.is_array) out += ` *`;
50305
- else out += ` `;
50306
- out += param.name;
50307
- return out;
50308
- }
50309
- //#endregion
50310
51513
  //#region ../src/build_aarch64/utils/scan_heap_returns.ts
50311
51514
  const KNOWN_HEAP_RETURNING = /* @__PURE__ */ new Set([]);
50312
51515
  function scan_heap_returning_functions(root) {
@@ -50532,7 +51735,7 @@ function fold_asm_constants(asm) {
50532
51735
  i++;
50533
51736
  continue;
50534
51737
  }
50535
- const m = /^(\w+)\s*(.*)$/.exec(line);
51738
+ const m = /^([A-Za-z.][\w.]*)\s*(.*)$/.exec(line);
50536
51739
  if (!m) {
50537
51740
  clear();
50538
51741
  out.push(raw_line);
@@ -51161,7 +52364,9 @@ function build(root, options = {}) {
51161
52364
  for (const [label, value] of status.float_literals) status.code += `${label}: .double ${value}\n`;
51162
52365
  }
51163
52366
  const emit_mode = status.emit_mode ?? "all";
51164
- const interpolate_lengths = emit_mode === "user" ? [] : emit_mode === "system" ? Array.from(/* @__PURE__ */ new Set([
52367
+ let interpolate_lengths;
52368
+ if (emit_mode === "user") interpolate_lengths = Array.from(status.interpolate_string_counts).filter((n) => n > 7);
52369
+ else if (emit_mode === "system") interpolate_lengths = Array.from(/* @__PURE__ */ new Set([
51165
52370
  ...status.interpolate_string_counts,
51166
52371
  1,
51167
52372
  2,
@@ -51170,15 +52375,21 @@ function build(root, options = {}) {
51170
52375
  5,
51171
52376
  6,
51172
52377
  7
51173
- ])).sort((a, b) => a - b) : Array.from(status.interpolate_string_counts);
52378
+ ])).sort((a, b) => a - b);
52379
+ else interpolate_lengths = Array.from(status.interpolate_string_counts);
51174
52380
  for (const length of interpolate_lengths) {
51175
52381
  status.code += `\n.p2align 2\n`;
51176
52382
  if (emit_mode === "system") status.code += `.globl _string_interpolate_${length}\n`;
51177
52383
  status.code += `_string_interpolate_${length}:\n`;
51178
52384
  status.code += `stp x29, x30, [sp, #-16]!\n`;
51179
52385
  status.code += `mov x29, sp\n`;
51180
- status.code += `sub sp, sp, #80\n`;
51181
- status.code += `str x0, [sp, #72]\n`;
52386
+ const args_area = length * 8;
52387
+ const len_off = args_area;
52388
+ const buf_off = args_area + 8;
52389
+ const pat_off = args_area + 16;
52390
+ const frame = Math.ceil((args_area + 24) / 16) * 16;
52391
+ status.code += `sub sp, sp, #${frame}\n`;
52392
+ status.code += `str x0, [sp, #${pat_off}]\n`;
51182
52393
  const pairRegs = [
51183
52394
  ["x2", "x3"],
51184
52395
  ["x4", "x5"],
@@ -51191,7 +52402,7 @@ function build(root, options = {}) {
51191
52402
  }
51192
52403
  status.code += `mov x0, xzr\n`;
51193
52404
  status.code += `mov x1, xzr\n`;
51194
- status.code += `ldr x2, [sp, #72]\n`;
52405
+ status.code += `ldr x2, [sp, #${pat_off}]\n`;
51195
52406
  const variadicRegs = [
51196
52407
  "x3",
51197
52408
  "x4",
@@ -51202,17 +52413,17 @@ function build(root, options = {}) {
51202
52413
  for (let i = 0; i < length && i < variadicRegs.length; i++) status.code += `ldr ${variadicRegs[i]}, [sp, #${i * 8}]\n`;
51203
52414
  status.code += `bl _snprintf\n`;
51204
52415
  status.code += `add x0, x0, #1\n`;
51205
- status.code += `str x0, [sp, #56]\n`;
52416
+ status.code += `str x0, [sp, #${len_off}]\n`;
51206
52417
  emit_malloc(status);
51207
- status.code += `str x0, [sp, #64]\n`;
51208
- status.code += `ldr x0, [sp, #64]\n`;
51209
- status.code += `ldr x1, [sp, #56]\n`;
51210
- status.code += `ldr x2, [sp, #72]\n`;
52418
+ status.code += `str x0, [sp, #${buf_off}]\n`;
52419
+ status.code += `ldr x0, [sp, #${buf_off}]\n`;
52420
+ status.code += `ldr x1, [sp, #${len_off}]\n`;
52421
+ status.code += `ldr x2, [sp, #${pat_off}]\n`;
51211
52422
  for (let i = 0; i < length && i < variadicRegs.length; i++) status.code += `ldr ${variadicRegs[i]}, [sp, #${i * 8}]\n`;
51212
52423
  status.code += `bl _snprintf\n`;
51213
52424
  status.code += `mov x1, x0\n`;
51214
- status.code += `ldr x0, [sp, #64]\n`;
51215
- status.code += `add sp, sp, #80\n`;
52425
+ status.code += `ldr x0, [sp, #${buf_off}]\n`;
52426
+ status.code += `add sp, sp, #${frame}\n`;
51216
52427
  status.code += `ldp x29, x30, [sp], #16\n`;
51217
52428
  status.code += `ret\n`;
51218
52429
  }
@@ -51239,7 +52450,7 @@ function build(root, options = {}) {
51239
52450
  status.code = status.code.replaceAll("bl _strdup\n", "bl _nomen_strdup_wrap\n");
51240
52451
  status.code = status.code.replaceAll("bl _free\n", "bl _nomen_free_wrap\n");
51241
52452
  }
51242
- const { result: lifted } = lift_functions(status.code);
52453
+ const { result: lifted } = lift_functions(status.code, { allow_external_branch_targets: status.emit_mode === "user" });
51243
52454
  const lift_errors = lifted.errors;
51244
52455
  if (lift_errors.length > 0) {
51245
52456
  if (!status.build_errors) status.build_errors = [];
@@ -51275,6 +52486,10 @@ function build(root, options = {}) {
51275
52486
  if (options.arch === "aarch64") ensure_weak_runtime_a64(status);
51276
52487
  let companion;
51277
52488
  if (status.c_companion_functions && status.c_companion_functions.length > 0 || status.file_scope_c) companion = generate_companion(status.c_companion_functions ?? [], status);
52489
+ if (options.arch === "aarch64" && companion && status.c_companion_functions) for (const entry of status.c_companion_functions) {
52490
+ const label = companion_function_label(entry.func, entry.struct_name, status);
52491
+ if (label.startsWith("L")) status.code = status.code.replace(new RegExp(`\\b${label}`, "g"), `_${label}`);
52492
+ }
51278
52493
  if (companion && options.audit) companion = wrap_c_allocators(companion);
51279
52494
  if (status.headers.includes("struct Point") && !status.headers.includes("typedef struct Point") && process.env.NOMEN_DBG) console.error("DBG build.ts: headers has forward Point but no typedef. len=", status.headers.length);
51280
52495
  return {
@@ -52894,6 +54109,10 @@ function clone_node(node) {
52894
54109
  case "access_field": {
52895
54110
  const n = node;
52896
54111
  const c = new AccessFieldNode(n.start, n.name, n.type ? clone_type$2(n.type) : void 0);
54112
+ c.is_destructure = n.is_destructure;
54113
+ c.destructure_index = n.destructure_index;
54114
+ c.is_destructure_rename = n.is_destructure_rename;
54115
+ c.destructure_move = n.destructure_move;
52897
54116
  c.allocations = n.allocations?.map(clone_node);
52898
54117
  return c;
52899
54118
  }
@@ -52918,6 +54137,8 @@ function clone_node(node) {
52918
54137
  } : void 0;
52919
54138
  c.inferred_array_length = n.inferred_array_length;
52920
54139
  c.is_nursery_spawn = n.is_nursery_spawn;
54140
+ c.is_handler_registration = n.is_handler_registration;
54141
+ c.is_handler_enrollment = n.is_handler_enrollment;
52921
54142
  c.is_thread_start = n.is_thread_start;
52922
54143
  c.is_thread_detach = n.is_thread_detach;
52923
54144
  c.is_fiber_start = n.is_fiber_start;
@@ -53689,7 +54910,9 @@ function type_from_value_node(node, status) {
53689
54910
  t.is_nullable = true;
53690
54911
  return t;
53691
54912
  }
53692
- return type_from_value(vn.value, status);
54913
+ const t = type_from_value(vn.value, status);
54914
+ if (t.name) return t;
54915
+ return vn.type?.name ? vn.type : t;
53693
54916
  }
53694
54917
  case "access": return type_from_value_node(node.access, status);
53695
54918
  case "array": return node.type;
@@ -53751,8 +54974,7 @@ function borrow_depth_of(node, status) {
53751
54974
  return Math.max(base?.borrow_depth ?? 0, status.scope_depth);
53752
54975
  }
53753
54976
  } else if (access.access.node_type === "access_func") {
53754
- if (is_borrowed_return(type_from_value_node(access, status), status)) {
53755
- if (access.access.owned_return) return void 0;
54977
+ if (is_borrowed_return(type_from_value_node(access, status), status) && !access.access.owned_return || is_element_load_pure_borrow(access, status)) {
53756
54978
  if (access.target.node_type === "value") {
53757
54979
  const recv = status.values.findLast((v) => v.name === access.target.value);
53758
54980
  if (recv) return recv.borrow_depth ?? recv.decl_depth ?? status.scope_depth;
@@ -53784,8 +55006,7 @@ function borrow_owner_of(node, status) {
53784
55006
  if (t?.name && is_owning_ref_type(t.name, status)) return ultimate_owner(access.target, status);
53785
55007
  if (t?.is_view) return ultimate_owner(access.target, status);
53786
55008
  } else if (access.access.node_type === "access_func") {
53787
- if (is_borrowed_return(type_from_value_node(access, status), status)) {
53788
- if (access.access.owned_return) return void 0;
55009
+ if (is_borrowed_return(type_from_value_node(access, status), status) && !access.access.owned_return || is_element_load_pure_borrow(access, status)) {
53789
55010
  if (access.target.node_type === "value") {
53790
55011
  const recv = status.values.findLast((v) => v.name === access.target.value);
53791
55012
  if (recv) return recv.borrowed_from ?? recv.name;
@@ -53811,6 +55032,26 @@ function is_borrowed_return(t, status) {
53811
55032
  return !!t.name && is_owning_ref_type(t.name, status);
53812
55033
  }
53813
55034
  /**
55035
+ * The `List<Own>.at()` pure borrow (FOLLOWUP.md): a container borrow accessor
55036
+ * (`list.at(i)` — the accessor family, non-`mv out`) whose value-struct
55037
+ * element aliases destroy-worthy state the load cannot privatize. The result
55038
+ * is a borrow of the slot's resources — rooted at the container exactly like
55039
+ * a class-typed method return, so a `ref self` call on the container
55040
+ * invalidates it, it cannot escape its scope, and it cannot transfer through
55041
+ * `mv`. Deliberately name-scoped to the accessor family: any method may
55042
+ * return a `#destroy`-bearing struct by value as an OWNED transfer.
55043
+ */
55044
+ function is_element_load_pure_borrow(access, status) {
55045
+ const func_access = access.access;
55046
+ if (func_access.owned_return) return false;
55047
+ if (!is_container_borrow_accessor_name(func_access.name ?? "")) return false;
55048
+ const t = type_from_value_node(access, status);
55049
+ if (!t?.name || t.is_view || t.is_array) return false;
55050
+ const elem = status.structs.find((s) => s.name === t.name && !s.is_simple_type && !s.is_generic);
55051
+ if (!elem || elem.is_class) return false;
55052
+ return element_load_aliases_destroy_state(elem, status);
55053
+ }
55054
+ /**
53814
55055
  * Resolve an access target down to its ultimate mv variable name. Recurses
53815
55056
  * through field paths so `z.animals` → `z` (and `a.b.c` → `a`).
53816
55057
  */
@@ -55770,6 +57011,7 @@ function sanitize_type_name(t) {
55770
57011
  function clone_type$1(t) {
55771
57012
  const new_t = new Type(t.name, t.is_static, t.is_array, t.length);
55772
57013
  new_t.is_ref = t.is_ref;
57014
+ new_t.is_view = t.is_view;
55773
57015
  new_t.is_nullable = t.is_nullable;
55774
57016
  new_t.is_return_type = t.is_return_type;
55775
57017
  new_t.type_args = t.type_args?.map(clone_type$1);
@@ -55826,6 +57068,178 @@ function materialize_tuple_type(type, status) {
55826
57068
  return new_type;
55827
57069
  }
55828
57070
  //#endregion
57071
+ //#region ../src/check/utils/view_fields.ts
57072
+ /**
57073
+ * Whether the named struct (or any value struct embedded in its fields,
57074
+ * recursively) declares a `view T` field. A struct with view fields carries
57075
+ * non-mv borrows inside its bytes, which drives the borrow rules:
57076
+ * copies are sound (a pair copy aliases nothing owned), but escapes
57077
+ * (returning, outer-scope assignment) are checked against where those
57078
+ * borrows were taken.
57079
+ */
57080
+ function struct_has_view_fields(type_name, status) {
57081
+ if (!type_name) return false;
57082
+ const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type);
57083
+ return !!struct && has_view_fields(struct, status, /* @__PURE__ */ new Set());
57084
+ }
57085
+ function has_view_fields(struct, status, visited) {
57086
+ if (visited.has(struct.name)) return false;
57087
+ visited.add(struct.name);
57088
+ for (const field of struct.fields) {
57089
+ if (field.type.is_view) return true;
57090
+ if (field.type.is_ref || field.type.is_array) continue;
57091
+ const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
57092
+ if (field_struct && has_view_fields(field_struct, status, visited)) return true;
57093
+ }
57094
+ return false;
57095
+ }
57096
+ /**
57097
+ * The root variable an access chain bottoms out at (`line.text` → "line",
57098
+ * `a.b.c` → "a", bare `x` → "x"). Returns undefined when the chain does not
57099
+ * bottom out in a plain name (e.g. a call receiver).
57100
+ */
57101
+ function root_var_of(node) {
57102
+ if (node.node_type === "value") {
57103
+ const name = node.value;
57104
+ return name === "null" ? void 0 : name;
57105
+ }
57106
+ if (node.node_type === "access") return root_var_of(node.target);
57107
+ }
57108
+ /**
57109
+ * Where a view value stored into a field borrows from.
57110
+ *
57111
+ * - A `.slice()`-style view expression: borrow_depth/borrow_owner give the
57112
+ * scope depth and owner variable (the slice's receiver).
57113
+ * - A bare variable of owned type (`view string f = doc`): borrowing it is
57114
+ * the identity on fat strings, but it creates a real dependency — record
57115
+ * the source's own declaration depth and name so reassignment of the
57116
+ * source invalidates the field.
57117
+ * - A literal / const: static storage that outlives everything — no borrow.
57118
+ *
57119
+ * Returns undefined for sources that impose no dependency.
57120
+ */
57121
+ function view_source_borrow_info(node, status) {
57122
+ if (node.node_type === "value" && node.value.startsWith("\"")) return void 0;
57123
+ const depth = borrow_depth_of(node, status);
57124
+ const owner = borrow_owner_of(node, status);
57125
+ if (depth !== void 0 || owner !== void 0) return {
57126
+ depth: depth ?? status.scope_depth,
57127
+ owner
57128
+ };
57129
+ if (node.node_type === "value") {
57130
+ const decl = status.values.findLast((v) => v.name === node.value);
57131
+ if (decl && !decl.is_null) return {
57132
+ depth: decl.decl_depth ?? 0,
57133
+ owner: decl.name
57134
+ };
57135
+ const alloc = status.allocations.findLast((d) => d.name === node.value);
57136
+ if (alloc?.value) return view_source_borrow_info(alloc.value, status);
57137
+ }
57138
+ }
57139
+ /**
57140
+ * Borrow info contributed by a constructor call's `view T` arguments (e.g.
57141
+ * `Line(doc.slice(0, 5), 0, 5)`): merges every view-typed argument's borrow
57142
+ * into one entry per distinct owner (the tightest depth wins). Returns
57143
+ * undefined when the call passes no dependent views.
57144
+ */
57145
+ function ctor_call_view_borrow(call, status) {
57146
+ const indices = call.view_param_indices ?? [];
57147
+ let merged;
57148
+ for (const i of indices) {
57149
+ let arg = call.params[i];
57150
+ if (!arg) continue;
57151
+ if (arg.node_type === "value") {
57152
+ const name = arg.value;
57153
+ const hoisted = call.allocations?.findLast((d) => d.name === name) ?? status.allocations.findLast((d) => d.name === name);
57154
+ if (hoisted?.value) arg = hoisted.value;
57155
+ }
57156
+ const info = view_source_borrow_info(arg, status);
57157
+ if (!info) continue;
57158
+ if (!merged) merged = /* @__PURE__ */ new Map();
57159
+ merge_borrow_info(merged, info);
57160
+ }
57161
+ return merged;
57162
+ }
57163
+ /** Fold one borrow into the per-owner map, keeping the tightest depth. */
57164
+ function merge_borrow_info(map, info) {
57165
+ const key = info.owner ?? "";
57166
+ const existing = map.get(key);
57167
+ if (!existing) {
57168
+ map.set(key, { ...info });
57169
+ return;
57170
+ }
57171
+ if (info.depth !== void 0 && (existing.depth === void 0 || info.depth < existing.depth)) existing.depth = info.depth;
57172
+ }
57173
+ /**
57174
+ * Record that view values were stored into fields of the struct variable
57175
+ * `root`: the instance now carries borrows rooted at each owner with the
57176
+ * given scope depths. Depth tracking keeps the tightest (deepest-rooted)
57177
+ * borrow so escape checks stay conservative across multiple fields. Also
57178
+ * clears any stale invalidation for the root — the fresh store re-points a
57179
+ * field (approximation: any view-field store refreshes the whole instance).
57180
+ */
57181
+ function merge_view_borrows_into_var(root, infos, status) {
57182
+ if (!root || !infos || infos.size === 0) return;
57183
+ const sv = status.values.findLast((v) => v.name === root);
57184
+ if (!sv) return;
57185
+ sv.has_view_borrows = true;
57186
+ if (!sv.view_field_owners) sv.view_field_owners = /* @__PURE__ */ new Set();
57187
+ for (const [owner, info] of infos) {
57188
+ if (owner) sv.view_field_owners.add(owner);
57189
+ if (info.depth !== void 0) sv.borrow_depth = sv.borrow_depth === void 0 ? info.depth : Math.min(sv.borrow_depth, info.depth);
57190
+ }
57191
+ status.invalidated_view_structs?.delete(root);
57192
+ }
57193
+ /**
57194
+ * Propagate view-field borrow state when a struct value is copied
57195
+ * (`b = a`, `var Line b = a`): the copy aliases the same sources, so it
57196
+ * depends on exactly the same owners and inherits the tightest depth.
57197
+ */
57198
+ function propagate_view_borrows(target, source) {
57199
+ if (!source.has_view_borrows) return;
57200
+ target.has_view_borrows = true;
57201
+ if (source.view_field_owners?.size) {
57202
+ if (!target.view_field_owners) target.view_field_owners = /* @__PURE__ */ new Set();
57203
+ for (const owner of source.view_field_owners) target.view_field_owners.add(owner);
57204
+ }
57205
+ if (source.borrow_depth !== void 0 && (target.borrow_depth === void 0 || source.borrow_depth < target.borrow_depth)) target.borrow_depth = source.borrow_depth;
57206
+ }
57207
+ /**
57208
+ * Whether every recorded view-field borrow of the value roots at `self`
57209
+ * (the receiver) — the same re-rooting convention that lets a `slice`
57210
+ * method return its view: the caller re-roots the borrow at the call-site
57211
+ * receiver.
57212
+ */
57213
+ function view_borrows_root_at_self(sv) {
57214
+ const owners = sv.view_field_owners;
57215
+ return !!owners?.size && [...owners].every((o) => o === "self");
57216
+ }
57217
+ /**
57218
+ * Whether a value of this type can carry a non-mv view borrow: the type
57219
+ * itself is a `view`, a `ref` (a borrow by definition — keep the existing
57220
+ * taint), a struct declaring (transitively) `view T` fields, or a generic
57221
+ * instantiation with such an argument. Anything else (primitives, owned
57222
+ * strings, containers of owned values) owns its storage outright, so a call
57223
+ * producing it does not propagate its view arguments' borrows — tainting it
57224
+ * is a false positive (e.g. returning `slice_string(view)` from a function
57225
+ * returning owned `string`). Callers pass an unset/unknown type through as
57226
+ * carrying, preserving today's behavior where nothing is known.
57227
+ */
57228
+ function type_can_carry_view_borrow(type, status) {
57229
+ if (!type || !type.name) return true;
57230
+ if (type.is_view || type.is_ref) return true;
57231
+ if (struct_has_view_fields(type.name, status)) return true;
57232
+ return (type.type_args ?? []).some((t) => type_can_carry_view_borrow(t, status));
57233
+ }
57234
+ /**
57235
+ * Whether reading view fields of `root` is currently rejected because their
57236
+ * source was mutated (reassigned / ref-mutated).
57237
+ */
57238
+ function view_fields_invalidated(root, status) {
57239
+ if (!root) return false;
57240
+ return !!status.invalidated_view_structs?.has(root);
57241
+ }
57242
+ //#endregion
55829
57243
  //#region ../src/check/utils/anon_enum.ts
55830
57244
  /**
55831
57245
  * Canonical, order-independent name for an auto-generated anonymous enum
@@ -55863,6 +57277,8 @@ function contains_unresolved_param(t, status) {
55863
57277
  */
55864
57278
  function get_or_create_anon_enum(cases, status) {
55865
57279
  if (cases.some((c) => c.types.some((t) => contains_unresolved_param(t, status)))) return null;
57280
+ for (const c of cases) for (const t of c.types) if (t.is_view) add_error(status, `enum case payloads cannot be 'view' — materialize the value ('.to_string()') or re-slice from the source on use`, t.start ?? 0);
57281
+ else if (t.name && struct_has_view_fields(t.name, status)) add_error(status, `enum case payloads cannot be '${t.name}' — structs with 'view' fields are scope-local; store the fields (the views) instead`, t.start ?? 0);
55866
57282
  const sorted = [...cases].sort((a, b) => a.name.localeCompare(b.name));
55867
57283
  const name = anon_enum_name(sorted);
55868
57284
  const root = status.stack[0];
@@ -56084,22 +57500,27 @@ function spawn_arg_region_class(arg_type, moved, status) {
56084
57500
  * (`nursery_frozen_roots`): `check_async_block_node` pushes one, block
56085
57501
  * clones SHARE the innermost set (the freeze lasts to the block exit, not
56086
57502
  * to the construction's branch exit), and the set dies with the block's
56087
- * cloned status — the enclosing scope never sees it.
57503
+ * cloned status — the enclosing scope never sees it. Each entry records the
57504
+ * spawn that lent the root (the task label), so the write gate can name it.
56088
57505
  */
56089
- function freeze_root(root, status) {
57506
+ function freeze_root(root, status, task_label) {
56090
57507
  if (!status.nursery_frozen_roots?.length) return;
56091
- status.nursery_frozen_roots[status.nursery_frozen_roots.length - 1].add(root);
57508
+ status.nursery_frozen_roots[status.nursery_frozen_roots.length - 1].set(root, task_label);
56092
57509
  }
56093
57510
  /**
56094
- * The R3 write gate: `'m' is borrowed by a task until this 'async' block
56095
- * exits`. Called from every mutation path with the ROOT variable's name.
56096
- * Returns true when the name is frozen (the error has been added).
57511
+ * The R3 write gate: `'m' is borrowed by the 'worker' spawn until this
57512
+ * 'async' block exits`. Called from every mutation path with the ROOT
57513
+ * variable's name. Returns true when the name is frozen (the error has been
57514
+ * added). The message names the spawn that lent the root — the disclosure
57515
+ * is retroactive (the lend itself rides the unspelled read-only default
57516
+ * flavor), so it must at least be precise.
56097
57517
  */
56098
57518
  function enforce_root_not_frozen(name, status, start) {
56099
57519
  const frames = status.nursery_frozen_roots;
56100
57520
  if (!frames?.length) return false;
56101
- if (frames[frames.length - 1].has(name)) {
56102
- add_error(status, `'${name}' is borrowed by a task until this 'async' block exits — it is read-only here (mv a copy in, or mv it in at the spawn)`, start);
57521
+ const lender = frames[frames.length - 1].get(name);
57522
+ if (lender !== void 0) {
57523
+ add_error(status, `'${name}' is borrowed by ${lender} until this 'async' block exits — it is read-only here (mv a copy in, or mv it in at the spawn)`, start);
56103
57524
  return true;
56104
57525
  }
56105
57526
  return false;
@@ -56109,7 +57530,7 @@ function enforce_root_not_frozen(name, status, start) {
56109
57530
  * (shared class/trait since tranche 1; mv value structs and views since
56110
57531
  * R1). Called from the Sendable validation site.
56111
57532
  */
56112
- function enforce_spawn_arg_region(param, arg_type, status, is_ref_borrow = false) {
57533
+ function enforce_spawn_arg_region(param, arg_type, status, is_ref_borrow = false, task_label = "a task") {
56113
57534
  if ((status.nursery_depth ?? 0) === 0) return;
56114
57535
  if ((is_ref_borrow ? "borrow" : spawn_arg_region_class(arg_type, false, status)) !== "borrow") return;
56115
57536
  const join_depth = innermost_nursery_depth(status);
@@ -56127,7 +57548,7 @@ function enforce_spawn_arg_region(param, arg_type, status, is_ref_borrow = false
56127
57548
  else add_error(status, `this task shares a temporary ${arg_type.name ?? ""} that dies before the 'async' join — bind it to a local in the 'async' block's scope and pass the local`, param.start);
56128
57549
  return;
56129
57550
  }
56130
- if (root !== void 0 && arg_type.name !== "Task") freeze_root(root, status);
57551
+ if (root !== void 0 && arg_type.name !== "Task") freeze_root(root, status, task_label);
56131
57552
  }
56132
57553
  /**
56133
57554
  * N2 / R4 — a detached task owns its captures. Called from the access-func
@@ -56208,178 +57629,6 @@ function boxing_owns_error() {
56208
57629
  return `boxing owns its instance — mv the value, or copy it ('.copy()')`;
56209
57630
  }
56210
57631
  //#endregion
56211
- //#region ../src/check/utils/view_fields.ts
56212
- /**
56213
- * Whether the named struct (or any value struct embedded in its fields,
56214
- * recursively) declares a `view T` field. A struct with view fields carries
56215
- * non-mv borrows inside its bytes, which drives the borrow rules:
56216
- * copies are sound (a pair copy aliases nothing owned), but escapes
56217
- * (returning, outer-scope assignment) are checked against where those
56218
- * borrows were taken.
56219
- */
56220
- function struct_has_view_fields(type_name, status) {
56221
- if (!type_name) return false;
56222
- const struct = status.structs.find((s) => s.name === type_name && !s.is_simple_type);
56223
- return !!struct && has_view_fields(struct, status, /* @__PURE__ */ new Set());
56224
- }
56225
- function has_view_fields(struct, status, visited) {
56226
- if (visited.has(struct.name)) return false;
56227
- visited.add(struct.name);
56228
- for (const field of struct.fields) {
56229
- if (field.type.is_view) return true;
56230
- if (field.type.is_ref || field.type.is_array) continue;
56231
- const field_struct = status.structs.find((s) => s.name === field.type.name && !s.is_simple_type && !s.is_class);
56232
- if (field_struct && has_view_fields(field_struct, status, visited)) return true;
56233
- }
56234
- return false;
56235
- }
56236
- /**
56237
- * The root variable an access chain bottoms out at (`line.text` → "line",
56238
- * `a.b.c` → "a", bare `x` → "x"). Returns undefined when the chain does not
56239
- * bottom out in a plain name (e.g. a call receiver).
56240
- */
56241
- function root_var_of(node) {
56242
- if (node.node_type === "value") {
56243
- const name = node.value;
56244
- return name === "null" ? void 0 : name;
56245
- }
56246
- if (node.node_type === "access") return root_var_of(node.target);
56247
- }
56248
- /**
56249
- * Where a view value stored into a field borrows from.
56250
- *
56251
- * - A `.slice()`-style view expression: borrow_depth/borrow_owner give the
56252
- * scope depth and owner variable (the slice's receiver).
56253
- * - A bare variable of owned type (`view string f = doc`): borrowing it is
56254
- * the identity on fat strings, but it creates a real dependency — record
56255
- * the source's own declaration depth and name so reassignment of the
56256
- * source invalidates the field.
56257
- * - A literal / const: static storage that outlives everything — no borrow.
56258
- *
56259
- * Returns undefined for sources that impose no dependency.
56260
- */
56261
- function view_source_borrow_info(node, status) {
56262
- if (node.node_type === "value" && node.value.startsWith("\"")) return void 0;
56263
- const depth = borrow_depth_of(node, status);
56264
- const owner = borrow_owner_of(node, status);
56265
- if (depth !== void 0 || owner !== void 0) return {
56266
- depth: depth ?? status.scope_depth,
56267
- owner
56268
- };
56269
- if (node.node_type === "value") {
56270
- const decl = status.values.findLast((v) => v.name === node.value);
56271
- if (decl && !decl.is_null) return {
56272
- depth: decl.decl_depth ?? 0,
56273
- owner: decl.name
56274
- };
56275
- const alloc = status.allocations.findLast((d) => d.name === node.value);
56276
- if (alloc?.value) return view_source_borrow_info(alloc.value, status);
56277
- }
56278
- }
56279
- /**
56280
- * Borrow info contributed by a constructor call's `view T` arguments (e.g.
56281
- * `Line(doc.slice(0, 5), 0, 5)`): merges every view-typed argument's borrow
56282
- * into one entry per distinct owner (the tightest depth wins). Returns
56283
- * undefined when the call passes no dependent views.
56284
- */
56285
- function ctor_call_view_borrow(call, status) {
56286
- const indices = call.view_param_indices ?? [];
56287
- let merged;
56288
- for (const i of indices) {
56289
- let arg = call.params[i];
56290
- if (!arg) continue;
56291
- if (arg.node_type === "value") {
56292
- const name = arg.value;
56293
- const hoisted = call.allocations?.findLast((d) => d.name === name) ?? status.allocations.findLast((d) => d.name === name);
56294
- if (hoisted?.value) arg = hoisted.value;
56295
- }
56296
- const info = view_source_borrow_info(arg, status);
56297
- if (!info) continue;
56298
- if (!merged) merged = /* @__PURE__ */ new Map();
56299
- merge_borrow_info(merged, info);
56300
- }
56301
- return merged;
56302
- }
56303
- /** Fold one borrow into the per-owner map, keeping the tightest depth. */
56304
- function merge_borrow_info(map, info) {
56305
- const key = info.owner ?? "";
56306
- const existing = map.get(key);
56307
- if (!existing) {
56308
- map.set(key, { ...info });
56309
- return;
56310
- }
56311
- if (info.depth !== void 0 && (existing.depth === void 0 || info.depth < existing.depth)) existing.depth = info.depth;
56312
- }
56313
- /**
56314
- * Record that view values were stored into fields of the struct variable
56315
- * `root`: the instance now carries borrows rooted at each owner with the
56316
- * given scope depths. Depth tracking keeps the tightest (deepest-rooted)
56317
- * borrow so escape checks stay conservative across multiple fields. Also
56318
- * clears any stale invalidation for the root — the fresh store re-points a
56319
- * field (approximation: any view-field store refreshes the whole instance).
56320
- */
56321
- function merge_view_borrows_into_var(root, infos, status) {
56322
- if (!root || !infos || infos.size === 0) return;
56323
- const sv = status.values.findLast((v) => v.name === root);
56324
- if (!sv) return;
56325
- sv.has_view_borrows = true;
56326
- if (!sv.view_field_owners) sv.view_field_owners = /* @__PURE__ */ new Set();
56327
- for (const [owner, info] of infos) {
56328
- if (owner) sv.view_field_owners.add(owner);
56329
- if (info.depth !== void 0) sv.borrow_depth = sv.borrow_depth === void 0 ? info.depth : Math.min(sv.borrow_depth, info.depth);
56330
- }
56331
- status.invalidated_view_structs?.delete(root);
56332
- }
56333
- /**
56334
- * Propagate view-field borrow state when a struct value is copied
56335
- * (`b = a`, `var Line b = a`): the copy aliases the same sources, so it
56336
- * depends on exactly the same owners and inherits the tightest depth.
56337
- */
56338
- function propagate_view_borrows(target, source) {
56339
- if (!source.has_view_borrows) return;
56340
- target.has_view_borrows = true;
56341
- if (source.view_field_owners?.size) {
56342
- if (!target.view_field_owners) target.view_field_owners = /* @__PURE__ */ new Set();
56343
- for (const owner of source.view_field_owners) target.view_field_owners.add(owner);
56344
- }
56345
- if (source.borrow_depth !== void 0 && (target.borrow_depth === void 0 || source.borrow_depth < target.borrow_depth)) target.borrow_depth = source.borrow_depth;
56346
- }
56347
- /**
56348
- * Whether every recorded view-field borrow of the value roots at `self`
56349
- * (the receiver) — the same re-rooting convention that lets a `slice`
56350
- * method return its view: the caller re-roots the borrow at the call-site
56351
- * receiver.
56352
- */
56353
- function view_borrows_root_at_self(sv) {
56354
- const owners = sv.view_field_owners;
56355
- return !!owners?.size && [...owners].every((o) => o === "self");
56356
- }
56357
- /**
56358
- * Whether a value of this type can carry a non-mv view borrow: the type
56359
- * itself is a `view`, a `ref` (a borrow by definition — keep the existing
56360
- * taint), a struct declaring (transitively) `view T` fields, or a generic
56361
- * instantiation with such an argument. Anything else (primitives, owned
56362
- * strings, containers of owned values) owns its storage outright, so a call
56363
- * producing it does not propagate its view arguments' borrows — tainting it
56364
- * is a false positive (e.g. returning `slice_string(view)` from a function
56365
- * returning owned `string`). Callers pass an unset/unknown type through as
56366
- * carrying, preserving today's behavior where nothing is known.
56367
- */
56368
- function type_can_carry_view_borrow(type, status) {
56369
- if (!type || !type.name) return true;
56370
- if (type.is_view || type.is_ref) return true;
56371
- if (struct_has_view_fields(type.name, status)) return true;
56372
- return (type.type_args ?? []).some((t) => type_can_carry_view_borrow(t, status));
56373
- }
56374
- /**
56375
- * Whether reading view fields of `root` is currently rejected because their
56376
- * source was mutated (reassigned / ref-mutated).
56377
- */
56378
- function view_fields_invalidated(root, status) {
56379
- if (!root) return false;
56380
- return !!status.invalidated_view_structs?.has(root);
56381
- }
56382
- //#endregion
56383
57632
  //#region ../src/check/check_declaration_node.ts
56384
57633
  /**
56385
57634
  * Record the may-be-null state of a freshly-declared struct's nullable
@@ -56563,9 +57812,16 @@ function check_declaration_node(decl, status) {
56563
57812
  if (!is_view_keyword && !declared_type_is_view && decl.type.is_view && decl.value) add_error(status, `binding a view requires the 'view' keyword (e.g. 'view name = ...' or 'var view name = ...')`, decl.value.start);
56564
57813
  const ref_src = is_ref_keyword ? ref_alias_source_name(decl.value) : void 0;
56565
57814
  if (is_ref_keyword) {
56566
- if (decl.type.name && !is_class_type$1(decl.type.name, status)) add_error(status, `'ref' binds an alias to a heap-resident instance (a Box<T>); use 'view' for a slice of '${decl.type.name}'`, decl.start);
56567
- if (decl.value && !ref_src && decl.value.node_type === "value") add_error(status, `'ref' requires an existing binding to alias, got an owned value`, decl.value.start);
56568
- if (decl.value && !ref_src && decl.value.node_type !== "value") add_error(status, `'ref' requires an existing binding to alias, got an owned value`, decl.value.start);
57815
+ const slot_load = element_slot_load(decl.value, status);
57816
+ if (slot_load) {
57817
+ slot_load.name = "slot_ptr";
57818
+ slot_load.params = [slot_load.params[0]];
57819
+ slot_load.is_element_slot_load = true;
57820
+ } else {
57821
+ if (decl.type.name && !is_class_type$1(decl.type.name, status)) add_error(status, `'ref' binds an alias to a heap-resident instance (a Box<T>); use 'view' for a slice of '${decl.type.name}'`, decl.start);
57822
+ if (decl.value && !ref_src && decl.value.node_type === "value") add_error(status, `'ref' requires an existing binding to alias, got an owned value`, decl.value.start);
57823
+ if (decl.value && !ref_src && decl.value.node_type !== "value") add_error(status, `'ref' requires an existing binding to alias, got an owned value`, decl.value.start);
57824
+ }
56569
57825
  if (decl.value && decl.value.is_moved) add_error(status, `'ref' binds an alias — 'mv' cannot transfer through it`, decl.value.start);
56570
57826
  }
56571
57827
  if (declaration === "mv" && !decl.swap && decl.value?.node_type === "value" && !decl.value.is_moved) decl.value.is_moved = true;
@@ -56592,7 +57848,10 @@ function check_declaration_node(decl, status) {
56592
57848
  if (!decl.value.is_moved && !temp_rhs) add_error(status, `cannot copy '${field_type.name}' out of field '${field_name}' by value — it owns heap resources; use 'mv ... swap <replacement>'${field_type.name === "string" ? ", or .copy() for a deep copy" : ""}`, decl.value.start);
56593
57849
  else if (!decl.swap) {
56594
57850
  if (!access_field.destructure_move && !temp_rhs) add_error(status, `mv out of a field requires a swap to revalidate it`, decl.value.start);
56595
- else {
57851
+ else if (temp_rhs && access_field.destructure_move && field_type.name === "string" && !field_type.is_array && !field_type.is_view) {
57852
+ decl.value.is_moved = void 0;
57853
+ access_field.destructure_move = void 0;
57854
+ } else {
56596
57855
  decl.value.is_moved = true;
56597
57856
  decl.swap = synthesized_swap_value(field_type, decl.value.start);
56598
57857
  if (check_node(decl.swap, status)) check_type_and_value_match(field_type, type_from_value_node(decl.swap, status), void 0, status, decl.swap.start, "swap");
@@ -56803,6 +58062,23 @@ function ref_alias_source_name(value) {
56803
58062
  return value_from_value_node(value) || void 0;
56804
58063
  }
56805
58064
  }
58065
+ /**
58066
+ * The ELEMENT BORROW shape (docs/HANDLERS.md Stage 4): a ref-binding
58067
+ * initializer that is `container.at(i)` — a container element read whose
58068
+ * container exposes the `slot_ptr` primitive (the `List<T>` mono). Returns
58069
+ * the inner AccessFunctionCallNode to rewrite, or undefined when the
58070
+ * initializer isn't an element load.
58071
+ */
58072
+ function element_slot_load(value, status) {
58073
+ if (!value || value.node_type !== "access") return void 0;
58074
+ const access = value;
58075
+ if (access.access.node_type !== "access_func") return void 0;
58076
+ const call = access.access;
58077
+ if (call.name !== "at" || call.params.length !== 1) return void 0;
58078
+ const target_type = type_from_value_node(access.target, status);
58079
+ if (!status.structs.find((s) => s.name === target_type.name || s.name === mono_type_name(target_type))?.functions.some((f) => f.name === "slot_ptr")) return void 0;
58080
+ return call;
58081
+ }
56806
58082
  //#endregion
56807
58083
  //#region ../src/check/utils/check_init_fields.ts
56808
58084
  const checked_structs = /* @__PURE__ */ new WeakSet();
@@ -57563,6 +58839,28 @@ function struct_copy_ctor_args(struct) {
57563
58839
  if (!auto) return null;
57564
58840
  return auto.params.filter((p) => !p.is_self_param).map((p) => p.name);
57565
58841
  }
58842
+ /**
58843
+ * The element type args of a mv-container type (`List<T>` / `Buffer<T>` /
58844
+ * `Set<T>` / `Map<K,V>`), in either spelling: the generic annotation
58845
+ * (`Map<string, int>` — type_args on the type, the shape seen by the derive
58846
+ * pre-pass which runs before per-field mono rewriting) or the monomorphized
58847
+ * name (`Map_string_int` — the registered mono carries the flattened args).
58848
+ * Undefined for non-container types.
58849
+ */
58850
+ function container_element_args(type, status) {
58851
+ if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return;
58852
+ if (type.type_args?.length) {
58853
+ if ([
58854
+ "List",
58855
+ "Buffer",
58856
+ "Set",
58857
+ "Map"
58858
+ ].includes(type.name)) return type.type_args;
58859
+ return;
58860
+ }
58861
+ const mono = status.structs.find((s) => s.name === type.name && !s.is_generic);
58862
+ if (mono && is_load_deep_copy_container(mono.name) && mono.source_type_args?.length) return mono.source_type_args;
58863
+ }
57566
58864
  /** Whether `type` has (or will get) a deep-copy story: a struct with a
57567
58865
  * synthesized `copy`, a `List<T>` whose element is deep-copyable (the
57568
58866
  * library `List.copy` deep-copies slots via the mv store contract), or
@@ -57575,6 +58873,8 @@ function field_copyable(type, status, visited) {
57575
58873
  if (visited?.has(type.name)) return false;
57576
58874
  if (is_box_type(type.name, status.structs)) return false;
57577
58875
  if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
58876
+ const elem_args = container_element_args(type, status);
58877
+ if (elem_args) return elem_args.every((arg) => element_copyable(arg, status));
57578
58878
  const nested_struct = status.structs.find((s) => s.name === type.name);
57579
58879
  if (nested_struct) {
57580
58880
  if (nested_struct.is_simple_type) return true;
@@ -57593,6 +58893,8 @@ function element_copyable(type, status) {
57593
58893
  if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
57594
58894
  if (type.name === "string") return true;
57595
58895
  if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
58896
+ const elem_args = container_element_args(type, status);
58897
+ if (elem_args) return elem_args.every((arg) => element_copyable(arg, status));
57596
58898
  const nested = status.structs.find((s) => s.name === type.name);
57597
58899
  if (nested) {
57598
58900
  if (nested.is_simple_type) return true;
@@ -57609,6 +58911,8 @@ function element_copyable(type, status) {
57609
58911
  function field_copy_call(type, status) {
57610
58912
  if (!type.name || type.is_array || type.is_ref || type.is_view || type.is_nullable) return false;
57611
58913
  if (type.name === "List" && type.type_args?.length === 1) return element_copyable(type.type_args[0], status);
58914
+ const elem_args = container_element_args(type, status);
58915
+ if (elem_args) return elem_args.every((arg) => element_copyable(arg, status));
57612
58916
  const nested_struct = status.structs.find((s) => s.name === type.name);
57613
58917
  return !!nested_struct && !nested_struct.is_simple_type && struct_is_copyable(nested_struct, status);
57614
58918
  }
@@ -58139,7 +59443,7 @@ function check_function_parameter_node(param, status, is_library_scope = false)
58139
59443
  }
58140
59444
  if (status.values.some((v) => v.name === param.name && v.declaration !== "const" && !(is_library_scope && v.is_global))) add_error(status, `Parameter already declared: ${param.name}`, param.start);
58141
59445
  status.values.push({
58142
- declaration: param.declaration === "var" || is_pass_by_value_owning_struct(param.type, status) ? "var" : param.declaration,
59446
+ declaration: param.declaration === "var" || param.is_variadic || is_pass_by_value_owning_struct(param.type, status) ? "var" : param.declaration,
58143
59447
  name: param.name,
58144
59448
  type: param.type,
58145
59449
  is_set: true,
@@ -58272,6 +59576,8 @@ function check_function_node(func, status) {
58272
59576
  }
58273
59577
  if (func.return_type.name && !foreign_supported_type(func.return_type)) add_error(status, `foreign return type '${func.return_type.name}' is unsupported (scalars and string only)`, func.return_type_start ?? func.start);
58274
59578
  }
59579
+ const mixed_body = find_use_c_mixed_body(func);
59580
+ if (mixed_body) add_error(status, mixed_body.dropped === "statement" ? `Function '${func.name}' mixes '#arch: aarch64_use_c' blocks with Nomen statements — the aarch64 backend replaces the whole body with the raw blocks and would drop the statements` : mixed_body.dropped === "raw asm" ? `Function '${func.name}' mixes '#arch: aarch64_use_c' blocks with inline raw code — the aarch64 backend replaces the whole body with the raw blocks and would drop the assembly` : `Function '${func.name}' has a '#arch: aarch64_use_c' block nested inside control flow — the aarch64 backend emits the body as assembly and silently drops block-scope C; move the block into its own aarch64_use_c function and pass values as parameters`, mixed_body.start);
58275
59581
  if (is_generic_func(func)) {
58276
59582
  func.is_generic = true;
58277
59583
  return;
@@ -58294,6 +59600,10 @@ function check_function_node(func, status) {
58294
59600
  if (func.return_type.is_pointer && !func.is_library) add_error(function_status, `'ptr' return types are reserved for the System library — raw pointer manipulation is not available to user code`, func.return_type_start ?? func.start);
58295
59601
  }
58296
59602
  instantiate_generic_type(func.return_type, function_status);
59603
+ if (func.name !== "#init" && !func.return_type.is_view && !func.return_type.is_ref && struct_has_view_fields(func.return_type.name, function_status)) {
59604
+ add_error(function_status, `cannot return a '${func.return_type.name}' — structs with 'view' fields are scope-local; return the fields (the views) instead`, func.return_type_start ?? func.start);
59605
+ func.view_bundle_return_rejected = true;
59606
+ }
58297
59607
  }
58298
59608
  if (func.is_foreign) return;
58299
59609
  check_block_node(func, function_status);
@@ -58524,6 +59834,8 @@ function synthesize_lambda_name(func, status) {
58524
59834
  */
58525
59835
  function validate_spawn_args_sendable(call, status, reason) {
58526
59836
  const callee_params = call.resolved_function?.params?.filter((p) => !p.is_self_param);
59837
+ const callee_name = call.resolved_function?.name;
59838
+ const task_label = callee_name ? `the '${callee_name}' spawn` : "a spawn";
58527
59839
  for (let i = 0; i < call.params.length; i++) {
58528
59840
  const param = call.params[i];
58529
59841
  let arg_type = type_from_value_node(param, status);
@@ -58536,7 +59848,7 @@ function validate_spawn_args_sendable(call, status, reason) {
58536
59848
  if (status.structs.find((st_) => st_.name === arg_type.name && !st_.is_class && !st_.is_simple_type)) {
58537
59849
  if (!call.region_borrowed_args) call.region_borrowed_args = [];
58538
59850
  if (!call.region_borrowed_args.includes(i)) call.region_borrowed_args.push(i);
58539
- enforce_spawn_arg_region(param, arg_type, status, true);
59851
+ enforce_spawn_arg_region(param, arg_type, status, true, task_label);
58540
59852
  continue;
58541
59853
  }
58542
59854
  }
@@ -58550,15 +59862,15 @@ function validate_spawn_args_sendable(call, status, reason) {
58550
59862
  }
58551
59863
  if (!call.region_borrowed_args) call.region_borrowed_args = [];
58552
59864
  if (!call.region_borrowed_args.includes(i)) call.region_borrowed_args.push(i);
58553
- if (inside_nursery) enforce_spawn_arg_region(param, arg_type, status);
59865
+ if (inside_nursery) enforce_spawn_arg_region(param, arg_type, status, false, task_label);
58554
59866
  continue;
58555
59867
  }
58556
59868
  if (inside_nursery && arg_type.is_view && !arg_type.is_array) {
58557
- enforce_spawn_arg_region(param, arg_type, status);
59869
+ enforce_spawn_arg_region(param, arg_type, status, false, task_label);
58558
59870
  continue;
58559
59871
  }
58560
59872
  if (is_sendable_type(arg_type.name, status)) {
58561
- enforce_spawn_arg_region(param, arg_type, status);
59873
+ enforce_spawn_arg_region(param, arg_type, status, false, task_label);
58562
59874
  continue;
58563
59875
  }
58564
59876
  add_error(status, `Spawn argument of type ${arg_type.name || "<unknown>"} is not Sendable${reason ?? " — a shared class/trait reference crossing a task boundary must be marked Sendable; mv it in (a `mv` parameter), share it as a checked region borrow inside an 'async' block, or mediate it through Mutex/Channel/Atomic<T>"}`, param.start);
@@ -58822,14 +60134,21 @@ function monomorphize(generic_struct, type_args, status) {
58822
60134
  const mono_struct = new StructNode(generic_struct.start, generic_struct.visibility, mono_name, generic_struct.traits, mono_fields, []);
58823
60135
  mono_struct.source_type_args = flat_args;
58824
60136
  mono_struct.is_class = generic_struct.is_class;
58825
- mono_struct.is_owning = generic_struct.is_owning || mono_fields.some((f) => type_owns_heap(f.type, status));
60137
+ const derived_owning = mono_fields.some((f) => type_owns_heap(f.type, status));
60138
+ mono_struct.is_owning = generic_struct.is_owning || derived_owning;
60139
+ if (derived_owning && !generic_struct.is_owning && !generic_struct.is_class) {
60140
+ const param_list = generic_struct.type_params.join(", ");
60141
+ add_error(status, `'${generic_struct.name}<${param_list}>' instantiates to an owning type — a substituted field owns heap memory --> mark the generic \`mv struct ${generic_struct.name}<${param_list}>\``, generic_struct.start);
60142
+ }
58826
60143
  mono_struct.is_library = generic_struct.is_library;
58827
60144
  mono_struct.trait_args = (generic_struct.trait_args ?? []).map((args) => args?.map((t) => substitute_type(t, substitution)));
58828
60145
  status.structs.push(mono_struct);
58829
60146
  status.types.push(mono_name);
58830
60147
  const cloned_methods = [];
60148
+ const has_ref_type_arg = (generic_struct.name === "Map" || generic_struct.name === "Set") && flat_args.some((arg) => status.traits.some((tr) => tr.name === arg.name) || status.structs.some((s) => s.name === arg.name && s.is_class));
58831
60149
  for (const func of generic_struct.functions) {
58832
60150
  if (func.name === "#init") continue;
60151
+ if (func.name === "copy" && has_ref_type_arg) continue;
58833
60152
  const cloned = clone_node(func);
58834
60153
  substitute_raw_types(cloned, substitution, status.structs, status.traits, {
58835
60154
  enums: status.enums,
@@ -61097,6 +62416,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
61097
62416
  if ((func_param.type.name ? status.structs.find((st) => st.name === func_param.type.name && !st.is_class && !st.is_simple_type) : void 0) || func_param.type.name === "string") param.type = func_param.type;
61098
62417
  }
61099
62418
  const param_type = type_from_value_node(param, status);
62419
+ if (param_type && !param_type.is_view && !param_type.is_ref && param_type.name && struct_has_view_fields(param_type.name, status)) add_error(status, `cannot pass a '${param_type.name}' as an argument — structs with 'view' fields are scope-local; pass the fields (the views) instead`, param.start);
61100
62420
  const param_value = value_from_value_node(param);
61101
62421
  const has_ref_keyword = node.ref_param_indices?.includes(i) ?? false;
61102
62422
  if (has_ref_keyword && param.node_type === "value") invalidate_token_facts(param.value, status);
@@ -61161,14 +62481,12 @@ function check_function_call(node, status, func, target_type, self_value, self_p
61161
62481
  } else if (!!func_param && !!status.traits.find((t) => t.name === func_param.type.name) && func_param.is_moved && is_value_struct_conformer(field_type.name, func_param.type.name, status)) add_error(status, `boxing owns its instance — mv the value, or copy it ('.copy()')`, param.start);
61162
62482
  }
61163
62483
  if (func_param.is_moved && !has_mov_keyword && !node.swap_params?.has(i) && param_is_owning_struct && param.node_type === "access" && param.access.node_type === "access_field") {
61164
- const read_root = param.target;
61165
- const root_name = read_root.node_type === "value" ? read_root.value : void 0;
61166
- if (!(read_root.node_type === "access" && read_root.access.node_type === "access_func" || !!root_name && (root_name.startsWith("_recv_") || root_name.startsWith("_tuple_dst_")))) {
61167
- const field_name = param.access.name;
61168
- add_error(status, `cannot copy field '${field_name}' into parameter '${func_param.name}' by value — it owns heap resources; use 'mv ... swap <replacement>' or .copy()`, param.start);
61169
- }
62484
+ const field_name = param.access.name;
62485
+ add_error(status, `cannot copy field '${field_name}' into parameter '${func_param.name}' by value — it owns heap resources; use 'mv ... swap <replacement>' or .copy()`, param.start);
61170
62486
  }
61171
- if (func_param.is_moved && !node.swap_params?.has(i) && func_param.type.name && is_owning_ref_type(func_param.type.name, status)) {
62487
+ const param_struct = func_param.type.name ? status.structs.find((s) => s.name === func_param.type.name && !s.is_simple_type && !s.is_generic) : void 0;
62488
+ const param_takes_ownership = func_param.type.name && is_owning_ref_type(func_param.type.name, status) || !!param_struct && !param_struct.is_class && param_struct.is_owning === true;
62489
+ if (func_param.is_moved && !node.swap_params?.has(i) && param_takes_ownership) {
61172
62490
  if (borrow_depth_of(param, status) !== void 0) add_error(status, `Cannot mv a borrowed value into mv parameter '${func_param.name}' — it would create shared ownership (the destination frees the pointer on destroy, leaving the borrow's source dangling). Pass an owned value: a fresh constructor, an owned local, or a 'mv out T' accessor result (e.g. .pop() or items.move_T(i)).`, param.start);
61173
62491
  }
61174
62492
  const swap_expr = node.swap_params?.get(i);
@@ -61410,8 +62728,7 @@ function check_function_call(node, status, func, target_type, self_value, self_p
61410
62728
  if (param.node_type === "access" && maybe_mark_borrow_to_string_arg(param, func_param, func, i, node, status)) continue;
61411
62729
  const callee_param_is_trait = !!func_param && !!status.traits.find((t) => t.name === func_param.type.name);
61412
62730
  if (callee_param_is_trait && !func_param.type.is_array && is_value_struct_conformer(param_type.name, func_param.type.name, status)) {
61413
- if (func_param.type.is_ref) add_error(status, `a 'ref ${func_param.type.name}' parameter cannot take value struct '${param_type.name}' — boxing owns its instance — mv the value, or copy it ('.copy()')`, param.start);
61414
- else if (func_param.is_moved) {
62731
+ if (func_param.is_moved) {
61415
62732
  if (!node.move_owned_param_indices) node.move_owned_param_indices = [];
61416
62733
  if (!node.move_owned_param_indices.includes(i)) node.move_owned_param_indices.push(i);
61417
62734
  }
@@ -61635,6 +62952,516 @@ function is_library_regex_func(func, status) {
61635
62952
  return !!regex_struct && regex_struct.functions.includes(func);
61636
62953
  }
61637
62954
  //#endregion
62955
+ //#region ../src/check/utils/handlers.ts
62956
+ /**
62957
+ * Handler registrations: `.on_<event>(closure)` desugars into owner-dispatched
62958
+ * methods (docs/HANDLERS.md, Stage 1 — the root desugar).
62959
+ *
62960
+ * `self.<field>.on_click(func () { ... })` on a control receiver is
62961
+ * checker-interpreted (the name-keyed precedent is the Fiber.start_on
62962
+ * rewrite): no code executes at the site. The closure body is HOISTED into a
62963
+ * hidden method on the enclosing struct — `#on_click_N = (ref self, ref
62964
+ * Window win) { body }` — an ordinary method both backends already lower, and
62965
+ * the registration checks into a dispatch ARM appended to the host's
62966
+ * synthesized `handle_events` method:
62967
+ *
62968
+ * ```
62969
+ * func handle_events = (ref self, ref Window win) {
62970
+ * nomen_window_clicks_init(self.<field>.handle)
62971
+ * if self.<field>.take_click() { self.#on_click_N(ref win) }
62972
+ * }
62973
+ * ```
62974
+ *
62975
+ * `App` declares a default-empty `handle_events`; `Window.run` calls
62976
+ * `app.handle_events(ref win)` before `frame`, so the borrow of `self` is
62977
+ * taken by the dispatcher at dispatch time — the closure captures nothing
62978
+ * (HANDLERS.md, "The insight"). The arm's handle expression re-evaluates the
62979
+ * remembered field path against final fields each frame; nothing is captured,
62980
+ * so there is no staleness.
62981
+ *
62982
+ * Checker rules (one clear error each, HANDLERS.md "Checker rules"):
62983
+ * - the receiver must be a `self`-rooted field path (the arm re-evaluates
62984
+ * it, so a local receiver has nothing to re-evaluate);
62985
+ * - the closure takes no parameters (v1);
62986
+ * - no upvalue captures — the hoisted method has no env;
62987
+ * - host reachability — dispatch reaches hosts through static field paths
62988
+ * from the run root, so the enclosing struct must conform to `App` or be
62989
+ * a (transitive) field of one (library composites are trusted).
62990
+ */
62991
+ /** The reserved member-name family on control receivers (HANDLERS.md). */
62992
+ const HANDLER_EVENT = /^on_[a-z][a-z0-9_]*$/;
62993
+ /** The one pump slot's name on handler hosts. */
62994
+ const PUMP_NAME = "handle_events";
62995
+ /**
62996
+ * Per-check synthesis state, keyed by the host struct (one pump per host).
62997
+ * Module-level (not on CheckStatus) because `clone_status` copies a fixed
62998
+ * field list — state recorded inside a branch clone (a registration nested
62999
+ * in an `if`) must survive the clone's discard. The per-check entry in
63000
+ * `pending_pumps` dies with the check that created it: `finalize` deletes by
63001
+ * root identity, and an aborted compile (parse errors, no check pass) never
63002
+ * creates an entry.
63003
+ */
63004
+ const dispatcher_states = /* @__PURE__ */ new WeakMap();
63005
+ /** Hosts whose pump awaits its final check, per compile root. */
63006
+ const pending_hosts = /* @__PURE__ */ new Map();
63007
+ /** Per-app up-call synthesis state (see host_states for why module-level). */
63008
+ const run_handler_states = /* @__PURE__ */ new WeakMap();
63009
+ /** Apps whose synthesized `run_handler` awaits its check (see pending_hosts). */
63010
+ const pending_run_handlers = /* @__PURE__ */ new Map();
63011
+ /** Per-app tick-forward state (see host_states for why module-level). */
63012
+ const tick_states = /* @__PURE__ */ new WeakMap();
63013
+ /** Apps whose pump awaits its synthesized tick forwards (see pending_hosts). */
63014
+ const pending_tick_apps = /* @__PURE__ */ new Map();
63015
+ /**
63016
+ * The `.on_<event>(closure)` hook, called from check_access_function_node
63017
+ * BEFORE ordinary method resolution. Returns:
63018
+ * - `undefined` — not a handler registration (name family or receiver does
63019
+ * not match); the call resolves ordinarily;
63020
+ * - `false` — a handler registration that failed a checker rule (an error
63021
+ * has been added);
63022
+ * - `true` — desugared: the site is stamped `is_handler_registration` (the
63023
+ * backends emit nothing for it) and the arm is synthesized.
63024
+ */
63025
+ function try_check_handler_registration(target_type, target, node, status) {
63026
+ if (node.is_handler_enrollment) return void 0;
63027
+ if (!HANDLER_EVENT.test(node.name)) return void 0;
63028
+ const receiver = status.structs.find((s) => s.name === target_type.name);
63029
+ if (!receiver) return void 0;
63030
+ const fail = (message) => {
63031
+ add_error(status, message, node.start);
63032
+ return false;
63033
+ };
63034
+ const composite = try_check_composite_event(receiver, target_type, target, node, status, fail);
63035
+ if (composite !== void 0) return composite;
63036
+ return try_check_control_drain(receiver, target_type, target, node, status, fail);
63037
+ }
63038
+ /**
63039
+ * The control-carried event path (docs/HANDLERS.md, "Registered control
63040
+ * events"): the event comes from the CONTROL's own source (the click ring,
63041
+ * the window's first responder, the control's text), the synthesized arm
63042
+ * polls that source's drain primitive, and the hidden method hoists onto
63043
+ * the App conformer the way the click desugar does. Config comes from
63044
+ * and everything here is event-agnostic.
63045
+ */
63046
+ function try_check_control_drain(receiver, target_type, target, node, status, fail) {
63047
+ const drain = `take_${node.name.slice(3)}`;
63048
+ if (!receiver.functions.some((f) => f.name === drain && f.params.length === 1)) return fail(`'${receiver.name}' doesn't emit ${node.name} events (no ${drain} drain)`);
63049
+ if (node.params.length !== 1 || node.params[0].node_type !== "func") return fail(`.${node.name} expects one closure argument — .${node.name}(func () { ... })`);
63050
+ const closure = node.params[0];
63051
+ if (closure.params.length > 0) return fail("handler closures take no parameters — the body sees `self` and the dispatch `win`");
63052
+ const fields = self_rooted_field_path(target);
63053
+ if (!fields) return fail(`.${node.name} needs a self-rooted field receiver — store the control in a field and register from self.<field>.${node.name}(...): the dispatch arm re-evaluates the path, so a local receiver has nothing to re-evaluate`);
63054
+ const host = status.stack.findLast((n) => n.node_type === "func")?.scope;
63055
+ if (!host || host.node_type !== "struct") return fail(`.${node.name} registers a handler on the enclosing struct — it cannot appear outside one`);
63056
+ if (!host.traits.includes("App") && !host.is_library && !reachable_from_an_app(host, status)) return fail(`handlers dispatch from the run root — '${host.name}' is not owned by the app`);
63057
+ let state = dispatcher_states.get(host);
63058
+ if (!state) {
63059
+ if (host.functions.some((f) => f.name === PUMP_NAME)) return fail(`'${host.name}' already declares ${PUMP_NAME} — the .${node.name} desugar synthesizes it (the name is reserved on handler hosts)`);
63060
+ const func = new FunctionNode(node.start, "pub", PUMP_NAME, new Type(""), [ref_self_param(host), win_param()]);
63061
+ host.functions.push(func);
63062
+ state = {
63063
+ func,
63064
+ next_index: /* @__PURE__ */ new Map()
63065
+ };
63066
+ dispatcher_states.set(host, state);
63067
+ const root = status.stack[0];
63068
+ let hosts = pending_hosts.get(root);
63069
+ if (!hosts) {
63070
+ hosts = [];
63071
+ pending_hosts.set(root, hosts);
63072
+ }
63073
+ hosts.push(host);
63074
+ }
63075
+ let index_state = state.next_index.get(node.name);
63076
+ if (index_state === void 0) index_state = 1;
63077
+ else index_state += 1;
63078
+ state.next_index.set(node.name, index_state);
63079
+ const index = index_state;
63080
+ const hidden = `#${node.name}_${index}`;
63081
+ const hoisted = new FunctionNode(closure.start, "private", `#${node.name}_${index}`, new Type(""), [ref_self_param(host), win_param()], closure.statements);
63082
+ host.functions.push(hoisted);
63083
+ hoisted.is_closure = true;
63084
+ check_function_node(hoisted, status);
63085
+ const upvalues = hoisted.captures ?? [];
63086
+ hoisted.is_closure = void 0;
63087
+ hoisted.captures = void 0;
63088
+ if (upvalues.length) return fail("handler closures see `self` and the dispatch `win` — copy what you need into fields");
63089
+ state.func.statements.push(arm_dispatch_statement(host, fields, drain, hidden, node.start));
63090
+ node.is_handler_registration = true;
63091
+ node.type = new Type("");
63092
+ return true;
63093
+ }
63094
+ /**
63095
+ * Composite event registration (docs/HANDLERS.md Stage 3 — id up-calls).
63096
+ * `self.<field>.on_<event>(closure)` on a composite host — a struct that
63097
+ * DECLARES the event API — enrolls the app handler for the composite's
63098
+ * up-call:
63099
+ *
63100
+ * - the closure hoists into a hidden method on the app —
63101
+ * `#on_<event>_N = (ref self, ref Window win) { body }`;
63102
+ * - the app's synthesized `run_handler` gains the dispatch arm
63103
+ * `if id == N { self.#on_<event>_N(ref win) }` — the concrete method the
63104
+ * composite's `owner.run_handler(id, ref win)` callback hops into;
63105
+ * - the registration site REWRITES to the enrollment call
63106
+ * `receiver.on_<event>(N)` — the composite's own declared API stores the
63107
+ * id (the one thing tier 2 stores: an int, never a handler);
63108
+ * - the app's dispatch pump gains the per-frame owner forward
63109
+ * `self.<fields>.tick(ref self, ref win)` at finalize — registration is
63110
+ * register-and-forget, the app never writes the forwarding call.
63111
+ *
63112
+ * The composite opts in by declaration: the event exists when the receiver
63113
+ * declares `func on_<event> = (ref self, int id)`. Without it the name
63114
+ * resolves ordinarily ("Function not found") — the family is not reserved
63115
+ * on composites, only on controls.
63116
+ *
63117
+ * The closure rules match the click desugar (no parameters, no upvalue
63118
+ * captures, self-rooted field receiver), and the enclosing host must be the
63119
+ * App conformer — the up-call dispatches through the app's `run_handler`.
63120
+ * Returns undefined when this isn't a declared composite event.
63121
+ */
63122
+ function try_check_composite_event(receiver, target_type, target, node, status, fail) {
63123
+ if (!receiver.functions.find((f) => f.name === node.name && f.params.length === 2 && f.params[0].is_self_param && !f.params[1].is_self_param && f.params[1].type.name === "int" && !f.params[1].type.is_ref)) return void 0;
63124
+ if (node.params.length !== 1 || node.params[0].node_type !== "func") return fail(`.${node.name} expects one closure argument — .${node.name}(func () { ... })`);
63125
+ const closure = node.params[0];
63126
+ if (closure.params.length > 0) return fail("handler closures take no parameters — the body sees `self` and the dispatch `win`");
63127
+ const fields = self_rooted_field_path(target);
63128
+ if (!fields) return fail(`.${node.name} needs a self-rooted field receiver — store the composite in a field and register from self.<field>.${node.name}(...)`);
63129
+ const host = status.stack.findLast((n) => n.node_type === "func")?.scope;
63130
+ if (!host || host.node_type !== "struct") return fail(`.${node.name} registers a handler on the enclosing struct — it cannot appear outside one`);
63131
+ if (!host.traits.includes("App")) return fail(`up-call handlers dispatch through the app's run_handler — register .${node.name} from the app, not from '${host.name}'`);
63132
+ if (host.functions.some((f) => f.name === "run_handler")) return fail(`'${host.name}' already declares run_handler — the .${node.name} desugar synthesizes it (the name is reserved on the app)`);
63133
+ const root = status.stack[0];
63134
+ let state = run_handler_states.get(host);
63135
+ if (!state) {
63136
+ const run_handler = new FunctionNode(node.start, "pub", "run_handler", new Type(""), [
63137
+ ref_self_param(host),
63138
+ new ParameterNode(node.start, "id", new Type("int")),
63139
+ win_param()
63140
+ ]);
63141
+ host.functions.push(run_handler);
63142
+ state = {
63143
+ run_handler,
63144
+ next_id: 1
63145
+ };
63146
+ run_handler_states.set(host, state);
63147
+ let apps = pending_run_handlers.get(root);
63148
+ if (!apps) {
63149
+ apps = [];
63150
+ pending_run_handlers.set(root, apps);
63151
+ }
63152
+ apps.push(host);
63153
+ }
63154
+ const id = state.next_id++;
63155
+ const hoisted = new FunctionNode(closure.start, "private", `#up_${node.name}_${id}`, new Type(""), [ref_self_param(host), win_param()], closure.statements);
63156
+ host.functions.push(hoisted);
63157
+ hoisted.is_closure = true;
63158
+ check_function_node(hoisted, status);
63159
+ const upvalues = hoisted.captures ?? [];
63160
+ hoisted.is_closure = void 0;
63161
+ hoisted.captures = void 0;
63162
+ if (upvalues.length) return fail("handler closures see `self` and the dispatch `win` — copy what you need into fields");
63163
+ state.run_handler.statements.push(up_call_arm(host, node.name, id, node.start));
63164
+ let ticks = tick_states.get(host);
63165
+ if (!ticks) {
63166
+ ticks = {
63167
+ entries: [],
63168
+ seen: /* @__PURE__ */ new Set()
63169
+ };
63170
+ tick_states.set(host, ticks);
63171
+ const tick_root = status.stack[0];
63172
+ let apps = pending_tick_apps.get(tick_root);
63173
+ if (!apps) {
63174
+ apps = [];
63175
+ pending_tick_apps.set(tick_root, apps);
63176
+ }
63177
+ apps.push(host);
63178
+ }
63179
+ const path_key = fields.join(".");
63180
+ if (!ticks.seen.has(path_key)) {
63181
+ ticks.seen.add(path_key);
63182
+ ticks.entries.push({
63183
+ fields,
63184
+ composite: receiver
63185
+ });
63186
+ }
63187
+ node.is_handler_enrollment = true;
63188
+ node.params = [new ValueNode(node.start, String(id), new Type("int"))];
63189
+ return check_access_function_node(target_type, target, node, status);
63190
+ }
63191
+ /**
63192
+ * `if id == N { self.#on_<event>_N(ref win) }` — one arm of the
63193
+ * synthesized `run_handler`.
63194
+ */
63195
+ function up_call_arm(host, event, id, start) {
63196
+ const condition = new OperationNode(start, "==", new ValueNode(start, "id", new Type("int")), new ValueNode(start, String(id), new Type("int")));
63197
+ const dispatch = new AccessFunctionCallNode(start, `#up_${event}_${id}`);
63198
+ dispatch.ref_param_indices = [0];
63199
+ dispatch.params = [new ValueNode(start, "win")];
63200
+ return new IfElseNode(start, condition, new BranchNode(start, [new AccessNode(start, new ValueNode(start, "self", new Type(host.name)), dispatch)]));
63201
+ }
63202
+ /**
63203
+ * Check every host's synthesized `handle_events`, after first synthesizing
63204
+ * the FORWARDING wiring (Stage 2, HANDLERS.md "Handler hosts beyond the
63205
+ * root"): a struct with registrations is a handler host — not just the App.
63206
+ * Dispatch reaches non-root hosts down static field paths: every struct on a
63207
+ * path from an App conformer to a host gets a `handle_events` (its own arms
63208
+ * if it registers, pure forwarding if it doesn't) that forwards to each
63209
+ * host-reaching field — `self.<field>.handle_events(ref win)` — direct
63210
+ * calls, no trait objects. Dispatch order is children-first, then own arms
63211
+ * (most specific first, HANDLERS.md "user-authored composites").
63212
+ *
63213
+ * Runs once, after the whole tree has been checked: the arms reference the
63214
+ * hidden `#on_click_N` methods (checked at their registration sites), the
63215
+ * drain methods (`take_click` et al) and the `Window` type are resolved, and
63216
+ * the full field graph (gathered upfront) decides reachability. Checking the
63217
+ * bodies also stamps every node for the build.
63218
+ */
63219
+ function finalize_handler_hosts(status) {
63220
+ const root = status.stack[0];
63221
+ const hosts = pending_hosts.get(root);
63222
+ const up_call_apps = pending_run_handlers.get(root);
63223
+ const tick_apps = pending_tick_apps.get(root);
63224
+ if (!hosts && !up_call_apps && !tick_apps) return;
63225
+ pending_hosts.delete(root);
63226
+ pending_tick_apps.delete(root);
63227
+ const host_set = new Set(hosts ?? []);
63228
+ const to_check = [];
63229
+ for (const host of hosts ?? []) {
63230
+ const state = dispatcher_states.get(host);
63231
+ if (state) to_check.push(state.func);
63232
+ }
63233
+ const reaches_memo = /* @__PURE__ */ new Map();
63234
+ const reaches_host = (s, stack) => {
63235
+ if (host_set.has(s)) return true;
63236
+ const memo = reaches_memo.get(s);
63237
+ if (memo !== void 0) return memo;
63238
+ if (stack.has(s)) return false;
63239
+ stack.add(s);
63240
+ let result = false;
63241
+ for (const field of s.fields) {
63242
+ for (const t of reachable_field_targets(field, status)) if (reaches_host(t, stack)) {
63243
+ result = true;
63244
+ break;
63245
+ }
63246
+ if (result) break;
63247
+ }
63248
+ stack.delete(s);
63249
+ reaches_memo.set(s, result);
63250
+ return result;
63251
+ };
63252
+ for (const s of status.structs) {
63253
+ const forwards = [];
63254
+ for (const f of s.fields) {
63255
+ const t = forwardable_field_type(f, status);
63256
+ if (t && reaches_host(t, /* @__PURE__ */ new Set())) forwards.push(forward_statement(s, f.name));
63257
+ }
63258
+ for (const f of s.fields) {
63259
+ const elem = forwardable_list_element(f, status);
63260
+ if (elem && reaches_host(elem, /* @__PURE__ */ new Set())) forwards.push(...list_forward_loop(s, f.name, elem, status));
63261
+ }
63262
+ if (forwards.length === 0) continue;
63263
+ const is_app = s.traits.includes("App");
63264
+ const is_host = host_set.has(s);
63265
+ if (!is_app && !is_host && !reachable_from_an_app(s, status)) continue;
63266
+ let state = dispatcher_states.get(s);
63267
+ if (!state) {
63268
+ if (s.functions.some((f) => f.name === PUMP_NAME)) continue;
63269
+ const pump = new FunctionNode(-1, "pub", PUMP_NAME, new Type(""), [ref_self_param(s), win_param()]);
63270
+ s.functions.push(pump);
63271
+ state = {
63272
+ func: pump,
63273
+ next_index: /* @__PURE__ */ new Map()
63274
+ };
63275
+ dispatcher_states.set(s, state);
63276
+ to_check.push(pump);
63277
+ }
63278
+ state.func.statements.unshift(...forwards);
63279
+ }
63280
+ for (const app of tick_apps ?? []) {
63281
+ const ticks = tick_states.get(app);
63282
+ if (!ticks) continue;
63283
+ let state = dispatcher_states.get(app);
63284
+ if (!state) {
63285
+ if (app.functions.some((f) => f.name === PUMP_NAME)) continue;
63286
+ const pump = new FunctionNode(-1, "pub", PUMP_NAME, new Type(""), [ref_self_param(app), win_param()]);
63287
+ app.functions.push(pump);
63288
+ state = {
63289
+ func: pump,
63290
+ next_index: /* @__PURE__ */ new Map()
63291
+ };
63292
+ dispatcher_states.set(app, state);
63293
+ to_check.push(pump);
63294
+ }
63295
+ for (const entry of ticks.entries) {
63296
+ if (!entry.composite.functions.some((f) => f.name === "tick" && f.params.length === 3 && f.params[0].is_self_param)) continue;
63297
+ const tick_call = new AccessFunctionCallNode(-1, "tick");
63298
+ tick_call.ref_param_indices = [0, 1];
63299
+ tick_call.params = [new ValueNode(-1, "self", new Type(app.name)), new ValueNode(-1, "win")];
63300
+ state.func.statements.push(new AccessNode(-1, arm_receiver(app, entry.fields, -1), tick_call));
63301
+ }
63302
+ }
63303
+ for (const hc of to_check) check_function_node(hc, status);
63304
+ if (up_call_apps) {
63305
+ pending_run_handlers.delete(root);
63306
+ for (const app of up_call_apps) {
63307
+ const state = run_handler_states.get(app);
63308
+ if (!state) continue;
63309
+ check_function_node(state.run_handler, status);
63310
+ }
63311
+ }
63312
+ }
63313
+ /**
63314
+ * The field types dispatch may FORWARD through: direct, owned, concrete
63315
+ * struct fields. Boxes/containers/monos/generics are element-dispatch
63316
+ * territory (B5, Stage 4) and stop the walk; nullable/borrowed/array fields
63317
+ * have no single static target.
63318
+ */
63319
+ function forwardable_field_type(field, status) {
63320
+ const type = field.type;
63321
+ if (!type.name || type.is_ref || type.is_view || type.is_array || type.is_nullable) return;
63322
+ if (is_box_type(type.name, status.structs)) return void 0;
63323
+ const s = status.structs.find((st) => st.name === type.name);
63324
+ if (!s) return void 0;
63325
+ if (s.source_type_args?.length || s.type_params.length > 0) return void 0;
63326
+ return s;
63327
+ }
63328
+ /**
63329
+ * The struct types a field makes REACHABLE for host-dispatch purposes: the
63330
+ * direct struct field target, plus (Stage 4) the element struct of a
63331
+ * `List<Row>` field — dynamic trees are dispatchable, so a composite type
63332
+ * hosted in a list is owned by the app exactly like a direct field.
63333
+ */
63334
+ function reachable_field_targets(field, status) {
63335
+ const targets = [];
63336
+ const direct = forwardable_field_type(field, status);
63337
+ if (direct) targets.push(direct);
63338
+ const elem = forwardable_list_element(field, status);
63339
+ if (elem) targets.push(elem);
63340
+ return targets;
63341
+ }
63342
+ /** Whether some App conformer reaches `target` down forwardable field paths. */
63343
+ function reachable_from_an_app(target, status) {
63344
+ for (const s of status.structs) {
63345
+ if (!s.traits.includes("App")) continue;
63346
+ if (path_exists(s, target, status, /* @__PURE__ */ new Set())) return true;
63347
+ }
63348
+ return false;
63349
+ }
63350
+ function path_exists(s, target, status, stack) {
63351
+ if (s === target) return true;
63352
+ if (stack.has(s)) return false;
63353
+ stack.add(s);
63354
+ for (const field of s.fields) for (const t of reachable_field_targets(field, status)) if (path_exists(t, target, status, stack)) return true;
63355
+ stack.delete(s);
63356
+ return false;
63357
+ }
63358
+ /** `self.<field>.handle_events(ref win)` — one hop down the ownership path. */
63359
+ function forward_statement(host, field) {
63360
+ const receiver = new AccessNode(-1, new ValueNode(-1, "self", new Type(host.name)), new AccessFieldNode(-1, field));
63361
+ const dispatch = new AccessFunctionCallNode(-1, "handle_events");
63362
+ dispatch.ref_param_indices = [0];
63363
+ dispatch.params = [new ValueNode(-1, "win")];
63364
+ return new AccessNode(-1, receiver, dispatch);
63365
+ }
63366
+ /**
63367
+ * The element struct a `List<Row>` field dispatches to, when the field is a
63368
+ * forwardable dynamic tree (either spelling of the mono). Containers of
63369
+ * trait/erased elements stay B5 territory — concrete rows only.
63370
+ */
63371
+ function forwardable_list_element(field, status) {
63372
+ const t = field.type;
63373
+ if (t.is_ref || t.is_view || t.is_array || t.is_nullable) return void 0;
63374
+ let elem_name;
63375
+ if (t.name === "List" && t.type_args?.length === 1) elem_name = t.type_args[0].name;
63376
+ else if (t.name.startsWith("List_")) elem_name = status.structs.find((s) => s.name === t.name && s.source_type_args?.length)?.source_type_args?.[0]?.name;
63377
+ if (!elem_name) return void 0;
63378
+ return status.structs.find((s) => s.name === elem_name && !s.is_generic);
63379
+ }
63380
+ /**
63381
+ * The dynamic-tree dispatch body for one `List<Row>` field (docs/HANDLERS.md
63382
+ * Stage 4):
63383
+ *
63384
+ * ```
63385
+ * var int _rows_i_N = 0
63386
+ * while _rows_i_N < self.rows.length; _rows_i_N += 1 {
63387
+ * ref Row _row = self.rows.at(_rows_i_N)
63388
+ * _row.handle_events(ref win)
63389
+ * }
63390
+ * ```
63391
+ *
63392
+ * The element borrow binds the SLOT (the checker rewrites `.at` to
63393
+ * `slot_ptr`), so each row's own arms run in place — state persists across
63394
+ * frames. The bounded-loop condition discharges `.at`'s bounds for the
63395
+ * borrow, and any list mutation (`ref self` on the list) invalidates the
63396
+ * borrow through the ordinary rules.
63397
+ */
63398
+ function list_forward_loop(host, field, elem, status) {
63399
+ const start = -1;
63400
+ const index = `_${field}_i_${status.var_name_counter.value++}`;
63401
+ const index_value = () => new ValueNode(start, index, new Type("int"));
63402
+ const list_access = () => new AccessNode(start, new ValueNode(start, "self", new Type(host.name)), new AccessFieldNode(start, field));
63403
+ const declaration = new DeclarationNode(start, "private", "var", index, new Type("int"), new ValueNode(start, "0", new Type("int")));
63404
+ const element_borrow = new DeclarationNode(start, "private", "ref", `_${field}_row`, new Type(elem.name), new AccessNode(start, list_access(), new AccessFunctionCallNode(start, "at", new Type(""), [index_value()])));
63405
+ element_borrow.is_ref_binding = true;
63406
+ const dispatch = new AccessFunctionCallNode(start, "handle_events");
63407
+ dispatch.ref_param_indices = [0];
63408
+ dispatch.params = [new ValueNode(start, "win")];
63409
+ const call = new AccessNode(start, new ValueNode(start, `_${field}_row`, new Type(elem.name)), dispatch);
63410
+ const condition = new OperationNode(start, "<", index_value(), new AccessNode(start, list_access(), new AccessFieldNode(start, "length")));
63411
+ const update = new AssignmentNode(start, index_value(), new ValueNode(start, "1", new Type("int")), "+=");
63412
+ return [declaration, new WhileLoopNode(start, condition, [element_borrow, call], update)];
63413
+ }
63414
+ /** `ref self` — the hoisted method's receiver (the host instance). */
63415
+ function ref_self_param(host) {
63416
+ const param = new ParameterNode(-1, "self", new Type(host.name));
63417
+ param.is_self_param = true;
63418
+ param.is_ref = true;
63419
+ param.declaration = "var";
63420
+ return param;
63421
+ }
63422
+ /** `ref Window win` — the dispatch window, lent by the run loop per frame. */
63423
+ function win_param() {
63424
+ const type = new Type("Window");
63425
+ type.is_ref = true;
63426
+ return new ParameterNode(-1, "win", type);
63427
+ }
63428
+ /**
63429
+ * The registration receiver's field path when it is `self`-rooted:
63430
+ * `self.add_btn` → `["add_btn"]`, `self.pager.prev` → `["pager", "prev"]`.
63431
+ * Undefined for any other shape (a bare local, a call result, …).
63432
+ */
63433
+ function self_rooted_field_path(target) {
63434
+ const fields = [];
63435
+ let current = target;
63436
+ while (current.node_type === "access") {
63437
+ const access = current;
63438
+ if (access.access.node_type !== "access_field") return void 0;
63439
+ fields.unshift(access.access.name);
63440
+ current = access.target;
63441
+ }
63442
+ if (current.node_type !== "value" || current.value !== "self") return;
63443
+ if (fields.length === 0) return void 0;
63444
+ return fields;
63445
+ }
63446
+ /** The receiver path re-emitted for the arm: `self.a.b` (handle expression). */
63447
+ function arm_receiver(host, fields, start) {
63448
+ let receiver = new ValueNode(start, "self", new Type(host.name));
63449
+ for (const field of fields) receiver = new AccessNode(start, receiver, new AccessFieldNode(start, field));
63450
+ return receiver;
63451
+ }
63452
+ /**
63453
+ * `if <drain>(self.<path>.handle) { self.<hidden>(ref win) }` — one arm of
63454
+ * the synthesized dispatcher.
63455
+ */
63456
+ function arm_dispatch_statement(host, fields, drain, hidden, start) {
63457
+ const condition = new AccessFunctionCallNode(start, drain);
63458
+ const condition_call = new AccessNode(start, arm_receiver(host, fields, start), condition);
63459
+ const dispatch = new AccessFunctionCallNode(start, hidden);
63460
+ dispatch.ref_param_indices = [0];
63461
+ dispatch.params = [new ValueNode(start, "win")];
63462
+ return new IfElseNode(start, condition_call, new BranchNode(start, [new AccessNode(start, new ValueNode(start, "self", new Type(host.name)), dispatch)]));
63463
+ }
63464
+ //#endregion
61638
63465
  //#region ../src/check/check_access_node.ts
61639
63466
  function check_access_node(node, status) {
61640
63467
  if (!check_node(node.target, status)) return false;
@@ -61902,6 +63729,8 @@ function check_access_field_node(target_type, target, node, status) {
61902
63729
  return true;
61903
63730
  }
61904
63731
  function check_access_function_node(target_type, target, node, status) {
63732
+ const handler = try_check_handler_registration(target_type, target, node, status);
63733
+ if (handler !== void 0) return handler;
61905
63734
  if (target_type.name === "Nursery" && node.name === "start") return check_nursery_spawn(node, status);
61906
63735
  if (target_type.name === "Fiber" && node.name === "start_on") {
61907
63736
  if (!check_spawn_start_on(target, target_type, node, status)) return false;
@@ -62022,6 +63851,13 @@ function check_access_function_node(target_type, target, node, status) {
62022
63851
  }
62023
63852
  }
62024
63853
  if (!func) {
63854
+ if (node.name === "copy" && node.params.length === 0) {
63855
+ const container_is = (prefix) => target_type.name === prefix || target_type.name.startsWith(`${prefix}_`) || !!struct?.name.startsWith(`${prefix}_`);
63856
+ if ((container_is("Map") || container_is("Set") ? target_type.type_args ?? struct?.source_type_args : void 0)?.some((arg) => arg.name && is_owning_ref_type(arg.name, status))) {
63857
+ add_error(status, container_is("Map") ? "Map.copy() is not available when the key or value type is a class or trait — its store is a ClassBuffer with no copy, so the copy would share the stored instances (a double free). Keep one mv map and index into it, or rebuild the map with fresh instances." : "Set.copy() is not available when the element type is a class or trait — its store is a ClassBuffer with no copy, so the copy would share the stored instances (a double free). Keep one mv set and index into it, or rebuild the set with fresh instances.", node.start);
63858
+ return false;
63859
+ }
63860
+ }
62025
63861
  add_error(status, `Function not found: ${target_type.name}.${node.name}`, node.start);
62026
63862
  return false;
62027
63863
  }
@@ -62724,8 +64560,10 @@ function check_assignment_node(assign, status) {
62724
64560
  add_error(status, `Unknown variable: ${left_value_name}`, assign.left_value.start);
62725
64561
  return false;
62726
64562
  } else if (left_value.is_ref_binding && assign.left_value.node_type === "value") {
62727
- add_error(status, `'${left_value_name}' is a ref — reassign the owner, not the alias`, assign.left_value.start);
62728
- return false;
64563
+ left_value.class_alias_of = void 0;
64564
+ left_value.ref_source = void 0;
64565
+ left_value.is_ref_binding = void 0;
64566
+ left_value.declaration = "var";
62729
64567
  } else if (assign.left_value.node_type === "access" && left_value.is_ref_binding) {
62730
64568
  if (left_value.ref_source) enforce_root_not_frozen(left_value.ref_source, status, assign.left_value.start);
62731
64569
  if (!(left_value.ref_source && is_var_binding(left_value.ref_source, status))) {
@@ -62818,7 +64656,8 @@ function check_assignment_node(assign, status) {
62818
64656
  } else check_type_and_value_match(type_from_value_node(assign.left_value, status), type_from_value_node(assign.right_value, status), value_from_value_node(assign.right_value), status, assign.right_value.start, "assignment");
62819
64657
  if (assign.left_value.node_type === "access" && assign.left_value.access.node_type === "access_field") {
62820
64658
  const lhs_access = assign.left_value;
62821
- if (lhs_access.access.type?.is_view) {
64659
+ const lhs_field = lhs_access.access;
64660
+ if (lhs_field.type?.is_view) {
62822
64661
  const root = root_var_of(lhs_access.target);
62823
64662
  const rhs = assign.right_value;
62824
64663
  let infos;
@@ -62829,6 +64668,18 @@ function check_assignment_node(assign, status) {
62829
64668
  const info = view_source_borrow_info(rhs, status);
62830
64669
  if (info) merge_view_borrows_into_var(root, /* @__PURE__ */ new Map([[info.owner ?? "", info]]), status);
62831
64670
  else if (!infos?.size && root && view_fields_invalidated(root, status)) status.invalidated_view_structs?.delete(root);
64671
+ } else if (lhs_field.type?.name && struct_has_view_fields(lhs_field.type.name, status)) {
64672
+ const root = root_var_of(lhs_access.target);
64673
+ const rhs_name = value_from_value_node(assign.right_value);
64674
+ const rhs_sv = rhs_name !== void 0 ? status.values.findLast((v) => v.name === rhs_name) : void 0;
64675
+ if (root && rhs_sv?.has_view_borrows) {
64676
+ const infos = /* @__PURE__ */ new Map();
64677
+ for (const owner of rhs_sv.view_field_owners ?? []) infos.set(owner, {
64678
+ owner,
64679
+ depth: rhs_sv.borrow_depth
64680
+ });
64681
+ merge_view_borrows_into_var(root, infos, status);
64682
+ }
62832
64683
  }
62833
64684
  }
62834
64685
  const lhs_is_owning_field_store = assign.left_value.node_type === "access" && assign.left_value.access.node_type === "access_field" && lhs_field_access_owning(assign.left_value, status);
@@ -63045,7 +64896,7 @@ function check_async_block_node(node, status) {
63045
64896
  const block_status = clone_status(status);
63046
64897
  block_status.nursery_depth = (status.nursery_depth ?? 0) + 1;
63047
64898
  block_status.nursery_scope_depths = [...status.nursery_scope_depths ?? [], status.scope_depth + 1];
63048
- block_status.nursery_frozen_roots = [...status.nursery_frozen_roots ?? [], /* @__PURE__ */ new Set()];
64899
+ block_status.nursery_frozen_roots = [...status.nursery_frozen_roots ?? [], /* @__PURE__ */ new Map()];
63049
64900
  if (node.nursery_name) block_status.values.push({
63050
64901
  declaration: "var",
63051
64902
  name: node.nursery_name,
@@ -63154,6 +65005,8 @@ function check_enum_node(node, status) {
63154
65005
  for (let i = 0; i < node.cases.length; i++) for (let j = i + 1; j < node.cases.length; j++) if (node.cases[i].name === node.cases[j].name) add_error(status, `Duplicate enum case: ${node.cases[j].name}`, node.cases[j].params[0]?.start || node.start);
63155
65006
  for (const tp of node.type_params) if (node.type_params.filter((t) => t === tp).length > 1) add_error(status, `Duplicate type parameter: ${tp}`, node.start);
63156
65007
  for (let c of node.cases) for (let p of c.params) {
65008
+ if (p.type.is_view) add_error(status, `enum case payloads cannot be 'view' — materialize the value ('.to_string()') or re-slice from the source on use`, p.start || node.start);
65009
+ else if (p.type.name && struct_has_view_fields(p.type.name, status)) add_error(status, `enum case payloads cannot be '${p.type.name}' — structs with 'view' fields are scope-local; store the fields (the views) instead`, p.start || node.start);
63157
65010
  if (node.type_params.includes(p.type.name)) continue;
63158
65011
  if (!status.types.includes(p.type.name)) add_error(status, `Unknown type: ${p.type.name}`, p.start);
63159
65012
  }
@@ -63969,6 +65822,7 @@ function check_return_node(ret, status) {
63969
65822
  ret.type = new Type("void");
63970
65823
  return;
63971
65824
  }
65825
+ if (func?.return_type && !func.return_type.is_view && func.return_type.name && struct_has_view_fields(func.return_type.name, status) && !func.view_bundle_return_rejected) add_error(status, `cannot return a '${func.return_type.name}' — structs with 'view' fields are scope-local; return the fields (the views) instead`, ret.value.start);
63972
65826
  const old_expected_type = status.expected_type;
63973
65827
  if (func?.return_type?.name && func.return_type.name !== "?") status.expected_type = func.return_type;
63974
65828
  const old_allow_null = status.allow_null_value;
@@ -63983,8 +65837,11 @@ function check_return_node(ret, status) {
63983
65837
  ret.type = type_from_value_node(ret.value, status);
63984
65838
  if (ret.value.node_type === "access" && ret.value.access.node_type === "access_field") {
63985
65839
  const af = ret.value.access;
63986
- const base_type = type_from_value_node(ret.value.target, status);
63987
- if (base_type.name) {
65840
+ const base_target = ret.value.target;
65841
+ const base_type = type_from_value_node(base_target, status);
65842
+ const base_name = base_target.node_type === "value" ? base_target.value : "";
65843
+ const base_is_binding = !!base_name && status.values.some((v) => v.name === base_name);
65844
+ if (base_type.name && base_is_binding) {
63988
65845
  const field = status.structs.findLast((s) => s.name === mono_type_name(base_type) && !s.is_generic)?.fields.find((f) => f.name === af.name);
63989
65846
  if (field?.type.name && !field.type.is_array && !field.type.is_view && !field.type.is_ref && (is_class_type(field.type.name, status) || is_owning_struct_type_requiring_move(new Type(mono_type_name(field.type)), status))) add_error(status, `cannot copy '${field.type.name}' out of field '${af.name}' by value — it owns heap resources; use 'mv ... swap <replacement>'${field.type.name === "string" ? ", or .copy() for a deep copy" : ""}`, ret.value.start);
63990
65847
  }
@@ -64199,8 +66056,9 @@ function check_value_node(node, status) {
64199
66056
  return true;
64200
66057
  }
64201
66058
  if (node.value.startsWith(".") && node.value.length > 1 && !node.value.startsWith("..")) return check_enum_shorthand(node, status);
64202
- node.type = type_from_value(node.value, status);
64203
- if (!node.type.name) {
66059
+ const resolved = type_from_value(node.value, status);
66060
+ if (resolved.name) node.type = resolved;
66061
+ else if (!node.type?.name) {
64204
66062
  add_error(status, `Unknown value: ${node.value}`, node.start);
64205
66063
  return false;
64206
66064
  }
@@ -64611,6 +66469,7 @@ function check(root, options) {
64611
66469
  };
64612
66470
  stamp_literal_element_moves(root);
64613
66471
  check_node(root, status);
66472
+ finalize_handler_hosts(status);
64614
66473
  unwrap_unsafe_blocks(root);
64615
66474
  if (!status.errors.length) emit_warnings(root, status);
64616
66475
  return {
@@ -66229,7 +68088,7 @@ function parse_function(visibility, status, name_override, is_inline, is_foreign
66229
68088
  let name = name_override || consume(status);
66230
68089
  if (!name_override && name === "#") {
66231
68090
  const next = consume(status);
66232
- if (next === "init" || next === "destroy" || next.startsWith("op_")) name = `#${next}`;
68091
+ if (next === "init" || next === "destroy" || next === "spawn" || next === "weak" || next === "handle" || next.startsWith("op_")) name = `#${next}`;
66233
68092
  }
66234
68093
  const op_internal = {
66235
68094
  "#op_add": "add",
@@ -66296,7 +68155,7 @@ function parse_function(visibility, status, name_override, is_inline, is_foreign
66296
68155
  parse_statement(status);
66297
68156
  expect("}", status);
66298
68157
  status.stack.pop();
66299
- if (name !== "#init" && name !== "#destroy" && name !== "#spawn") check_missing_return(func, status);
68158
+ if (name !== "#init" && name !== "#destroy" && name !== "#spawn" && name !== "#weak" && name !== "#handle") check_missing_return(func, status);
66300
68159
  }
66301
68160
  switch (parent.node_type) {
66302
68161
  case "root":
@@ -66498,6 +68357,10 @@ function parse_struct(visibility, status, is_owning = false) {
66498
68357
  struct.functions.unshift(func);
66499
68358
  }
66500
68359
  add_to_parent(struct, "Struct", status);
68360
+ if (struct.functions.some((f) => f.name === "#handle")) {
68361
+ struct.functions = struct.functions.filter((f) => f.name !== "#handle");
68362
+ add_to_parent(synthesize_handle_struct(struct, start, visibility), "Struct", status);
68363
+ }
66501
68364
  }
66502
68365
  status.namespace = old_namespace;
66503
68366
  }
@@ -66514,6 +68377,34 @@ function parse_optional_type_param_bound(status) {
66514
68377
  while (accept("+", status)) bounds.push(consume(status));
66515
68378
  return bounds;
66516
68379
  }
68380
+ function synthesize_handle_struct(struct, start, visibility) {
68381
+ if (struct.type_params.length > 0) throw new Error(`#handle is not supported on generic struct '${struct.name}' yet`);
68382
+ const handle = new StructNode(start, visibility, `${struct.name}Handle`);
68383
+ handle.is_owning = false;
68384
+ handle.fields.push(new DeclarationNode(start, "internal", "var", "handle", new Type("uint64")));
68385
+ const init = new FunctionNode(start, visibility, "#init", new Type(handle.name));
68386
+ const self_param = new ParameterNode(start, "self", new Type(handle.name));
68387
+ self_param.is_self_param = true;
68388
+ self_param.declaration = "var";
68389
+ init.params = [self_param, new ParameterNode(start, "from", new Type(struct.name))];
68390
+ init.is_static = false;
68391
+ init.has_body = true;
68392
+ init.statements = [new AssignmentNode(start, new AccessNode(start, new ValueNode(start, "self", new Type(handle.name)), new AccessFieldNode(start, "handle", new Type("uint64"))), new AccessNode(start, new ValueNode(start, "from", new Type(struct.name)), new AccessFieldNode(start, "handle", new Type("uint64"))))];
68393
+ handle.functions.push(init);
68394
+ for (const func of struct.functions) {
68395
+ if (func.visibility !== "pub") continue;
68396
+ if (func.name.startsWith("#")) continue;
68397
+ const receiver = func.params[0];
68398
+ if (!receiver || !receiver.is_self_param) continue;
68399
+ if (receiver.is_ref) continue;
68400
+ const clone = clone_node(func);
68401
+ clone.start = start;
68402
+ clone.visibility = "pub";
68403
+ clone.params[0].type = new Type(handle.name);
68404
+ handle.functions.push(clone);
68405
+ }
68406
+ return handle;
68407
+ }
66517
68408
  //#endregion
66518
68409
  //#region ../src/parse/utils/class_retired_error.ts
66519
68410
  /**
@@ -66813,6 +68704,11 @@ function parse_visibility(visibility, status) {
66813
68704
  consume(status);
66814
68705
  consume(status);
66815
68706
  parse_function(visibility, status, "#weak");
68707
+ } else if (next2 === "handle") {
68708
+ consume(status);
68709
+ consume(status);
68710
+ consume(status);
68711
+ parse_function(visibility, status, "#handle");
66816
68712
  } else {
66817
68713
  add_error(status, `Expected #init, #destroy, #spawn or #weak after pub`, get_index(status));
66818
68714
  consume(status);
@@ -66974,8 +68870,12 @@ function parse_statement(status) {
66974
68870
  consume(status);
66975
68871
  consume(status);
66976
68872
  parse_destroy(default_visibility(status), status, "#weak");
68873
+ } else if (next === "handle") {
68874
+ consume(status);
68875
+ consume(status);
68876
+ parse_destroy(default_visibility(status), status, "#handle");
66977
68877
  } else {
66978
- add_error(status, `Expected #init, #destroy, #spawn or #weak`, get_index(status));
68878
+ add_error(status, `Expected #init, #destroy, #spawn, #weak or #handle`, get_index(status));
66979
68879
  consume(status);
66980
68880
  }
66981
68881
  break;