0%

Section 03: Type-Directed Desugar + Validation (DOMINANT)

Goal

See frontmatter. Proposal Phase 3 (desugar) + Phase 4 (validation). This is where BUG-04-129 is actually fixed.

Phase-placement invariant — ABSOLUTE

The desugar runs in ori_types (type checker), NOT ori_canon. Per typeck.md §EX-17 + canon.md §2 rows 3-4 (currently marked Target-only). Rationale (load-bearing): AIMS must see the post-desugar pure-reassignment IR; a canon-phase desugar would let CanExpr::Assign{target: Index/Field} reach AIMS, which emits RC ops on the mutation expression instead of the COW updated reassignment — violating canon.md §7.1 Invariant 5 (unified model). A canon-phase or ARC-phase desugar is BANNED.

Implementation Sketch

After type inference resolves the AssignTarget’s root + per-step receiver types, walk the chain right-to-left (assignment point → root binding): each index step wraps the inner value in receiver.updated(key: k, value: inner); each field step wraps it in { ...receiver, field: inner }. Intermediate reads use index per level. The whole target becomes root = <wrapped> (pure reassignment). Compound assignment composes: the parse-time compound expand produces target = target op rhs, then this desugar rewrites the index/field target. Side-effecting index/key expressions are hoisted to per-step let temporaries so they evaluate once.

03.1 Type-Directed Desugar + IndexSet Resolution + Validation + Diagnostics

  • TDD-first: failing matrix BEFORE implementation — target shape (x[i] / x.f / x.f[i] / x[i].f / x.f.g[i].h) × root binding (mutable / $-immutable / parameter / loop var) × operator (= / += / -= / *= / /= / %= / **=) × index expr (literal / variable / side-effecting f()) × backend (interpreter / LLVM). Each cell positive + negative pin. Side-effect cell (load-bearing): arr[f()] += 1 with a dbg!-counted f() — reject any desugar that double-evaluates.
  • Index-step desugar: recv[k] = vrecv = recv.updated(key: k, value: v) in ori_types (resolve IndexSet<Key,Value> on recv’s type). Hoist k to a temporary when side-effecting.
    • Ori Tests: tests/spec/expressions/index_assignment_simple.ori, tests/spec/expressions/map_assignment.ori
  • Field-step desugar: recv.f = vrecv = { ...recv, f: v } (resolve recv’s struct type; field must exist). No trait needed.
    • Ori Tests: tests/spec/expressions/field_assignment.ori
  • Nested + mixed chains: right-to-left wrapping per proposal §Mixed Chains; intermediate reads via index. Cover grid[x][y][z]=v, state.items[i]=x, list[i].name=x, game.levels[i].enemies[j].hp=0.
    • Ori Tests: tests/spec/expressions/nested_index_assignment.ori, tests/spec/expressions/mixed_chain_assignment.ori
  • Compound assignment: verify two-step desugar (list[i] += 1list[i] = list[i] + 1list = list.updated(...)) for all compound ops × all target forms; single evaluation of the target/index expressions.
    • Ori Tests: tests/spec/expressions/compound_index_assignment.ori
  • Validation + diagnostics (proposal §Type Checking Rules + §Error Cases): root-binding mutability (reject $/param/loop-var); field membership; Index (non-final) + IndexSet (final) resolution with agreeing Value types; RHS assignability; key-type match. Allocate E-codes in ori_diagnostic (structured construction, never format! strings, per diagnostic.md); each error gets a #compile_fail pin.
    • WHERE: ori_types/src/check/ (assignment-target checking; impl-method/desugar entry — verify exact module against the shipped tree, NOT a guessed path) + ori_diagnostic/src/error_code/mod.rs.
    • Ori Tests: tests/compile-fail/index_assignment_errors.ori (immutable, parameter, loop-var, str-no-IndexSet, no-field, value-mismatch, key-mismatch)
  • Dual-execution parity + BUG-04-129 close: interpreter == LLVM for every desugared form; ori build repro.ori + ori run repro.ori both correct (E4003 + E6080 gone). Remove the now-unreachable E4003 (ori_arc) + E6080 (ori_eval) guards OR convert to internal-invariant assertions (no valid program reaches them). ORI_CHECK_LEAKS=1 zero leaks.
  • Verify: full matrix green debug + release, both backends; ./test-all.sh.

Intelligence Reconnaissance

2026-06-01 — feature-mode scaffold; proposal is the research artifact. Desugar contract: read .claude/rules/typeck.md §EX-17 + canon.md §2 rows 3-4 directly (not graph-indexed).

  • scripts/intel-query.sh file-symbols "ori_canon/src/lower/expr" --repo ori — where ExprKind::Assign{target,value} lowers as-is today (the E4003 path) [ori:ori_canon/src/lower/expr.rs].
  • scripts/intel-query.sh callers "index_assignment_not_supported" --repo ori — the E6080 eval guard to retire [ori:ori_eval/src/interpreter/can_eval/control_flow.rs].
  • Verify the assignment-checking module against the shipped tree [ori:ori_types/src/check/bodies/]ori_types/src/infer/methods/ does NOT exist; impl-method body checking lives under check/bodies/ (provenance: roadmap §15D supersession recorded in index.md).

Spec References

  • Proposal §Desugaring (all cases), §Type Checking Rules, §Error Cases, §Implementation Note (type-directed).
  • typeck.md §EX-17 (the desugar contract: list[i]=v → list.updated(...), state.f=v → {...state, f:v}, Spec §13.6.2 index assignment).
  • canon.md §2 rows 3-4 (Target-only → shipped here, type-checker phase).
  • BUG-04-129 (the symptom this closes).

Tests

tests/spec/expressions/{index_assignment_simple,map_assignment,field_assignment,nested_index_assignment,mixed_chain_assignment,compound_index_assignment}.ori + tests/compile-fail/index_assignment_errors.ori + Rust unit tests in ori_types.