0%

Section 01: IndexSet Trait + updated Method + ARC COW

Goal

  • See frontmatter. Proposal Phase 1.
  • The updated method is the desugar target for index assignment (list[i] = x → list = list.updated(key: i, value: x)); it ships before the section-03 desugar consumes it.

Implementation Sketch

  • IndexSet<Key, Value> is a prelude trait (companion to read-only Index).
  • The updated method is publicly callable (functional style: list.updated(key: 0, value: x)) independent of the assignment sugar.
  • The contract is value-semantic: updated returns a NEW collection, observably distinct from the receiver.
  • Built-in impls are compiler-provided intrinsics; for all three types ([T] / [T, max N] / {K:V}) the existing ARC copy-on-write mutates the backing buffer in place at refcount==1 (provable uniqueness) and allocates-then-rebinds otherwise — observably identical to the always-copy path (Swift Array/Dictionary COW under ARC).
  • Map in-place COW is live in the runtime: ori_rt/src/map/cow.rs cow_insert_existing / cow_insert_new carry a unique-fast-path (ori_rc_is_unique) that overwrites in place at refcount==1 — maps are NOT always-allocate.
  • str deliberately omits IndexSet (codepoint replacement may change byte length).

01.1 IndexSet Trait + updated Method + ARC COW Implementation

  • TDD-first: failing test matrix BEFORE implementation — updated on [int] / [str] / {str:int} / [int, max N] × {in-bounds, out-of-bounds, refcount==1 in-place, refcount>1 copy} × {interpreter, LLVM}. OOB semantics per type: [T].updated panics, [T, max N].updated panics (key >= length), {K:V}.updated inserts. Negative pin: str.updated direct-resolution failure (str implements Index, not IndexSet).
    • [T, max N].updated OOB cell (parallels [T]): positive pin — [1, 2, 3].to_fixed<3>().updated(key: 1, value: 9) yields [1, 9, 3] in both backends; negative pin — assert_panics(() -> fixed.updated(key: 5, value: 0)) for key >= length (in-bounds-of-capacity but out-of-bounds-of-length still panics, matching [T] index semantics, NOT the fixed-list try_push overflow path).
      • Ori Tests: tests/spec/traits/index_set/fixed_oob.ori
  • Define IndexSet<Key, Value> trait in compiler_repo/library/std/prelude.ori with @updated (self, key: Key, value: Value) -> Self.
    • Ori Tests: tests/spec/traits/index_set/basic.ori
  • Register IndexSet trait definition + built-in derived/impl signatures in ori_types/src/check/registration/ (mirror how Index is registered).
    • Rust Tests: ori_types/src/check/registration/tests.rs
  • Register updated as a built-in method on [T] + {K: V} in ori_eval method dispatch + ori_registry (per .claude/rules/registry.md — methods on builtin types live in the registry). Map registration carries the K: Eq + Hashable bound (mirrors Index / map-key constraints). [T, max N] is NOT a separate registration surface: it erases to TypeTag::List at registration/resolution, so the [T] (List-tag) registration in ori_registry/src/defs/list/mod.rs (registered under tag: TypeTag::List) covers fixed-lists — registering updated on [T, max N] separately would be redundant.
    • Rust Tests: ori_types/src/infer/expr/methods/tests.rs (companion to resolve_builtin_method at ori_types/src/infer/expr/methods/mod.rs; the resolver lives in ori_types, NOT ori_eval/src/method_dispatch/) — registry resolution of updated succeeds for each type: [int].updatedIndexSet<int, int>, {str: int}.updatedIndexSet<str, int>, [int, max 4].updatedIndexSet<int, int> resolves via the List tag (assert resolve_builtin_method returns the registered impl + correct key/value types per type).
    • Rust Tests: ori_types/src/check/registration/tests.rs — non-hashable map-key negative pin: a {K: V} where K does not satisfy Eq + Hashable rejects updated resolution (paralleling E2031 non-hashable map-key handling); positive pin: hashable K resolves.
    • Ori Tests: tests/spec/traits/index_set/updated_method.ori
  • Implement updated ARC copy-on-write in ori_patterns / ori_eval value model: for [T] / [T, max N] AND {K:V} refcount==1 → mutate backing buffer in place (list: ori_rt/src/list/cow.rs; map: ori_rt/src/map/cow.rs unique-fast-path), refcount>1 → clone-then-mutate + rebind. List/fixed-list out-of-bounds panics; map inserts-or-replaces.
    • Rust Tests: ori_patterns/src/value/tests.rs — COW behavior matrix (positive: in-place fires at rc==1 for list/fixed-list AND map; negative: aliased list/map forces copy, alias unchanged)
    • Ori Tests: tests/spec/traits/index_set/cow_behavior.ori
  • Register updated AIMS builtin ownership contract in ori_arc so AIMS can prove the value-flow rather than emit RC ops on the inserted value. Add updated’s param/return ownership + borrow contract to BuiltinOwnershipSets / seed_builtin_contracts (ori_arc/src/aims/builtins/mod.rs) and the borrow-builtin surface: self is consumed-and-returned (the return aliases or replaces the receiver’s storage), value is moved into the collection (ownership transferred — its RC must NOT be dropped after insert). Without the interprocedural contract AIMS may emit an erroneous RC dec on the inserted value (leak at rc==1 in-place; double-free at rc>1 copy).
    • 3-param contract constructor required: updated is 3-param (self, key, value); the existing cow_receiver_only_contract (ori_arc/src/aims/builtins/mod.rs) marks ONLY the receiver and cannot express the value-param ownership-transfer — seed_builtin_contracts MUST NOT default updated into it (param-length mismatch). Add a new 3-param parameterized contract constructor (or specialized memory contract) that models self consumed-returned + value moved-in + key borrowed.
    • Uniform COW contract across all three types: [T] / [T, max N] AND {K:V} share one value-flow shape — in-place-at-rc==1 / clone-at-rc>1, the return aliases-or-replaces receiver storage (no list-vs-map asymmetry; the runtime map unique-fast-path at ori_rt/src/map/cow.rs:106 is the in-place path). The seeded contract models the single COW value-flow (self consumed-returned, value moved-in) so AIMS proves all three without parallel emission paths.
    • Rust Tests: ori_arc/src/aims/builtins/tests.rs — contract seeded for updated on [T] / [T, max N] / {K:V}; value-param ownership-transfer asserted (no RC dec on the moved value).
    • Ori Tests: tests/spec/traits/index_set/arc_insert.ori — insert an ARC-managed element ([str] / {str: [int]}) under updated; ORI_CHECK_LEAKS=1 reports zero leaks debug + release, interpreter == AOT.
  • LLVM updated codegen for the three built-in types; verify interpreter == AOT (dual-execution parity); ORI_CHECK_LEAKS=1 zero leaks debug + release.
  • Verify: matrix green debug + release, both backends; cargo st tests/spec/traits/index_set/.

Intelligence Reconnaissance

2026-06-01 — feature-mode scaffold; approved proposal index-assignment-proposal.md is the research artifact.

  • scripts/intel-query.sh symbols "Index" --repo ori --kind trait — mirror the read-side trait registration [ori:ori_types/src/check/registration/] for IndexSet.
  • scripts/intel-query.sh file-symbols "ori_eval/src/method_dispatch" --repo ori — built-in method routing surface [ori:ori_eval/src/method_dispatch/].
  • scripts/intel-query.sh similar "updated" --repo swift,rust --limit 5 — Array/Dictionary copy-on-write prior art.

Spec References

  • compiler_repo/docs/ori_lang/v2026/spec/13-variables.md §13.6.2 (index assignment) — the AUTHORITATIVE SSOT defining x = x.updated(key: i, value: v) as the index-assignment desugar contract; spec is SSOT over the proposal. The §13.6 NOTE establishes that in-place mutation under refcount==1 is an optimization observably identical to the always-copy value-semantic path.
  • Proposal index-assignment-proposal.md §The IndexSet Trait, §Standard Implementations (research artifact; subordinate to the spec).
  • index-trait-proposal.md (the read-side Index pair).
  • Semantic contract: t.updated(key:k, value:v).index(key:k) == v (not compiler-enforced).

Tests

tests/spec/traits/index_set/{basic,updated_method,cow_behavior}.ori + Rust unit tests in ori_types / ori_eval / ori_patterns / ori_llvm per items above.