16%

Section 15D: Bindings & Types

Goal: Implement binding syntax changes and type system simplifications

Source: docs/ori_lang/proposals/approved/


15D.1 Function-Level Contracts: pre() / post()

Proposal: proposals/approved/checks-proposal.md (semantics), proposals/approved/block-expression-syntax.md (syntax)

Function-level pre() and post() contract declarations for defensive programming. Contracts go between the return type and =:

@divide (a: int, b: int) -> int
    pre(b != 0)
    post(r -> r * b <= a)
= a div b

// Multiple conditions
@transfer (from: Account, to: Account, amount: int) -> (Account, Account)
    pre(amount > 0 | "amount must be positive")
    pre(from.balance >= amount | "insufficient funds")
    post((f, t) -> f.balance == from.balance - amount)
    post((f, t) -> t.balance == to.balance + amount)
= {
    // ... body ...
}

Key Design Decisions

  • Function-level placement: Contracts go on the declaration between return type and =
  • Multiple declarations: Use multiple pre() / post() declarations (not list syntax)
  • | for messages: Custom messages use condition | "message" syntax
  • Scope constraints: pre() can only access function parameters and module-level bindings; post() can access the result via lambda parameter
  • Void return: Compile error if post() used on a function returning void
  • Check modes deferred: check_mode: (enforce/observe/ignore) deferred to future proposal

Implementation

  • Implement: Parser: Parse pre() and post() on function declarations (verified 2026-03-29)

    • ori_parse/src/grammar/item/function/mod.rsparse_contracts() at line 221 returns (Vec<PreContract>, Vec<PostContract>). Contracts parsed between return type and =.
    • AST: PreContract { condition, message, span } and PostContract { param, condition, message, span } in ori_ir/src/ast/items/function.rs. FunctionDef has pre_contracts and post_contracts fields.
    • Rust Tests: NEEDS TESTS — parser is implemented but no dedicated parser test exists
    • Ori Tests: tests/spec/patterns/checks.ori — not yet created
    • LLVM Support: NOT DONE
    • AOT Tests: No AOT coverage yet
  • Implement: Parser: Support | "message" custom message syntax (verified 2026-03-29)

    • PreContract has message: Option<ExprId>. PostContract has message: Option<ExprId>. Parser handles | separator.
    • Rust Tests: NEEDS TESTS — parsing works but no tests
    • Ori Tests: tests/spec/patterns/check_messages.ori — not yet created
    • LLVM Support: NOT DONE
    • AOT Tests: No AOT coverage yet
  • Implement: Type checker: Validate pre() condition is bool

    • Rust Tests: ori_types/src/check/function.rs — pre type validation
    • Ori Tests: tests/compile-fail/checks/pre_not_bool.ori
    • LLVM Support: LLVM codegen for pre type validation
    • LLVM Rust Tests: ori_llvm/tests/syntax_tests.rs — pre type validation codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Type checker: Validate post() is T -> bool lambda

    • Rust Tests: ori_types/src/check/function.rs — post type validation
    • Ori Tests: tests/compile-fail/checks/post_not_lambda.ori
    • LLVM Support: LLVM codegen for post type validation
    • LLVM Rust Tests: ori_llvm/tests/syntax_tests.rs — post type validation codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Type checker: Error when post() used on void-returning function

    • Rust Tests: ori_types/src/check/function.rs — void return error
    • Ori Tests: tests/compile-fail/checks/post_void_return.ori
    • LLVM Support: LLVM codegen for void return error
    • LLVM Rust Tests: ori_llvm/tests/syntax_tests.rs — void return error codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Scope checker: pre() can only access parameters and module-level bindings

    • Rust Tests: ori_types/src/check/scope.rs — pre scope validation
    • Ori Tests: tests/compile-fail/checks/pre_scope.ori
    • LLVM Support: LLVM codegen for pre scope validation
    • LLVM Rust Tests: ori_llvm/tests/syntax_tests.rs — pre scope validation codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Codegen: Desugar to conditional checks and panics at function entry/exit

    • Rust Tests: ori_types/src/check/checks.rs — contract desugaring
    • Ori Tests: tests/spec/patterns/checks_desugaring.ori
    • LLVM Support: LLVM codegen for contract desugaring
    • LLVM Rust Tests: ori_llvm/tests/syntax_tests.rs — contract desugaring codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Codegen: Embed source text for default error messages

    • Rust Tests: ori_types/src/check/checks.rs — source text embedding
    • Ori Tests: tests/spec/patterns/checks_error_messages.ori
    • LLVM Support: LLVM codegen for source text embedding
    • LLVM Rust Tests: ori_llvm/tests/syntax_tests.rs — source text embedding codegen
    • AOT Tests: No AOT coverage yet
  • /tpr-review passed — independent review found no critical or major issues (or all findings triaged)

  • /impl-hygiene-review passed — hygiene review clean. MUST run AFTER /tpr-review is clean.

  • Subsection close-out (15D.1) — MANDATORY before starting the next subsection. Run /improve-tooling retrospectively on THIS subsection’s debugging journey (per .claude/skills/improve-tooling/SKILL.md “Per-Subsection Workflow”): which diagnostics/ scripts you ran, where you added dbg!/tracing calls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE /commit-push using a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.1 retrospectivebuild/test/chore/ci/docs are valid; tools(...) is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.1: no tooling gaps”. Update this subsection’s status in section frontmatter to complete.

  • /sync-claude section-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.

  • Repo hygiene check — run diagnostics/repo-hygiene.sh --check and clean any detected temp files.


15D.2 as Conversion Syntax

Proposal: proposals/approved/as-conversion-proposal.md

Replace int(), float(), str(), byte() with as/as? keyword syntax backed by As<T> and TryAs<T> traits.

LLVM/AOT GAP (verified 2026-03-29): as/as? works in the interpreter but is broken in AOT compilation. lower_cast() in ori_arc/src/lower/collections/mod.rs emits Apply(dst, __cast, [val]) but __cast is NOT a registered protocol builtin and is NOT handled by the LLVM ARC emitter. AOT compilation will fail at link time or crash at runtime.

TEST COVERAGE GAP (verified 2026-03-29): tests/spec/expressions/type_conversion.ori contains comprehensive test cases for as/as? but ALL tests are commented out. Zero active tests for this feature.

// Before (special-cased positional args)
let x = int("42")
let y = float(value)

// After (consistent keyword syntax)
let x = "42" as? int
let y = value as float

Lexer

  • Implement: as keyword token (verified 2026-03-29)
    • TokenKind::As in ori_lexer/src/keywords/mod.rs line 50. Test at line 183 confirms.
    • LLVM Support: N/A — lexer token, not codegen
    • AOT Tests: No AOT coverage yet

Parser

  • Implement: Parse expression as Type as conversion expression (verified 2026-03-29)

    • ori_parse/src/grammar/expr/postfix.rs line 120 checks for TokenKind::As. Produces ExprKind::Cast { expr, ty, fallible: false }.
    • LLVM Support: LLVM codegen for as expression — BROKEN (see LLVM/AOT GAP above)
    • AOT Tests: No AOT coverage yet
  • Implement: Parse expression as? Type as fallible conversion (verified 2026-03-29)

    • Same parser function checks for ? after as to set fallible: true.
    • LLVM Support: LLVM codegen for as? expression — BROKEN (see LLVM/AOT GAP above)
    • AOT Tests: No AOT coverage yet

Type Checker

  • Implement: Type resolution for as casts (verified 2026-03-29)

    • infer_cast() in ori_types/src/infer/expr/operators.rs line 466 resolves target type and returns it.
    • [partial] Trait-based validation (As<T>) NOT implemented — any type can be cast to any type at typeck level. Runtime catches invalid casts.
  • Implement: Validate as only used with As<T> trait implementations

    • Ori Tests: tests/compile-fail/as_not_implemented.ori
  • Implement: Validate as? only used with TryAs<T> trait implementations

    • Ori Tests: tests/compile-fail/try_as_not_implemented.ori
  • Implement: Error when using as for fallible conversion (must use as?)

    • Ori Tests: tests/compile-fail/as_fallible_conversion.ori

Codegen (Evaluator)

  • Implement: Evaluate x as T with hardcoded primitive conversion logic (verified 2026-03-29)
    • eval_can_cast() at ori_eval/src/interpreter/can_eval/operators.rs line 119 handles: int->float, int->byte (range checked), int->char, float->int, byte->int, char->int, str->int (fallible), str->float (fallible), str->bool (fallible).
    • NOT via trait dispatch — hardcoded conversion logic. Works correctly for all primitive conversions.
  • Implement: Evaluate x as? T with fallible flag (verified 2026-03-29)
    • Same code path with fallible flag. Returns None on failure.

Codegen (LLVM/AOT)

  • Implement: Register __cast as protocol builtin in ori_ir/src/builtin_constants/protocol/ and handle in LLVM ARC emitter with appropriate LLVM cast instructions (sitofp, fptosi, trunc, zext)
    • LLVM Rust Tests: ori_llvm/tests/aot/conversions.rs — as/as? codegen
    • AOT Tests: No AOT coverage yet

Migration

  • Implement: Remove int(), float(), str(), byte() from parser

  • Implement: Update error messages to suggest as syntax

  • /tpr-review passed — independent review found no critical or major issues (or all findings triaged)

  • /impl-hygiene-review passed — hygiene review clean. MUST run AFTER /tpr-review is clean.

  • Subsection close-out (15D.2) — MANDATORY before starting the next subsection. Run /improve-tooling retrospectively on THIS subsection’s debugging journey (per .claude/skills/improve-tooling/SKILL.md “Per-Subsection Workflow”): which diagnostics/ scripts you ran, where you added dbg!/tracing calls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE /commit-push using a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.2 retrospectivebuild/test/chore/ci/docs are valid; tools(...) is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.2: no tooling gaps”. Update this subsection’s status in section frontmatter to complete.

  • /sync-claude section-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.

  • Repo hygiene check — run diagnostics/repo-hygiene.sh --check and clean any detected temp files.


15D.3 Simplified Bindings with $ for Immutability

Proposal: proposals/approved/simplified-bindings-proposal.md

Simplify the binding model: let x is mutable, let $x is immutable. Remove mut keyword. Module-level bindings require $ prefix.

// Before
let x = 5         // immutable
let mut x = 5     // mutable
$timeout = 30s    // config variable

// After
let x = 5         // mutable
let $x = 5        // immutable
let $timeout = 30s // module-level constant (let and $ required)

Lexer

  • Implement: Remove mut from reserved keywords (verified 2026-03-29)
    • No mut in ori_lexer/src/keywords/mod.rs. No KwMut token. Fully removed.
    • LLVM Support: N/A — lexer keyword removal, not codegen

Parser

  • Implement: Update let_expr to accept $ prefix in binding pattern (2026-02-20)

    • Rust Tests: ori_parse/src/tests/parser.rs — block let binding with $ prefix
    • Ori Tests: tests/spec/expressions/immutable_bindings.ori
    • LLVM Support: LLVM codegen for immutable binding parsing
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — immutable binding parsing codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Remove mut from let_expr grammar

    • Rust Tests: ori_parse/src/grammar/expr.rs — mut removal
    • Ori Tests: All 151 let mut occurrences migrated to let across 25 test files + AOT tests
    • LLVM Support: LLVM codegen for mut removal
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — mut removal codegen
    • AOT Tests: ori_llvm/tests/aot/mutations.rs — all 21 tests use let x (mutable-by-default) with reassignment, confirming mut removal works end-to-end in AOT
  • Implement: Update constant_decl to require let $name = expr

    • Rust Tests: ori_parse/src/grammar/decl.rs — constant declaration parsing
    • Ori Tests: tests/spec/declarations/constants.ori
    • LLVM Support: LLVM codegen for constant declaration
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — constant declaration codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Remove old const function syntax $name (params) -> Type

    • Rust Tests: ori_parse/src/grammar/decl.rs — const function removal
    • Ori Tests: tests/compile-fail/old_const_function_syntax.ori
    • LLVM Support: LLVM codegen for const function removal
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — const function removal codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Support $ prefix in destructuring patterns (2026-02-20)

    • Rust Tests: ori_parse/src/grammar/expr/primary.rsparse_binding_pattern handles $ for Name, Tuple, Struct, List
    • Ori Tests: tests/spec/expressions/immutable_bindings.ori — tuple, struct, list destructuring with $
    • LLVM Support: LLVM codegen for destructure immutable
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — destructure immutable codegen
    • AOT Tests: No AOT coverage yet
  • Fix: List rest binding ..rest tracks $ mutability (2026-02-20)

    • IR: BindingPattern::List.rest changed from Option<Name> to Option<(Name, bool)> (ori_ir/src/ast/patterns/binding.rs:31)
    • IR: CanBindingPattern::List.rest changed to Option<(Name, bool)> (ori_ir/src/canon/patterns.rs:32)
    • Parser: parse_binding_pattern handles $ before rest identifier (ori_parse/src/grammar/expr/primary.rs:1386)
    • Type checker: bind_pattern() uses bind_with_mutability() for rest (ori_types/src/infer/expr/sequences.rs:489)
    • Evaluator: bind_can_pattern() uses per-binding rest_mutable (ori_eval/src/interpreter/can_eval.rs:758)
    • Canon: lowering passes through (Name, bool) tuple (ori_canon/src/lower/patterns.rs:261)
    • Formatter: emits $ prefix on immutable rest bindings (ori_fmt/src/formatter/patterns.rs:183)
    • Grammar: grammar.ebnf updated to allow [ "$" ] on rest identifier (line 581)

Semantic Analysis

  • Implement: Track $ modifier separately from identifier name (2026-02-20)

    • Rust Tests: ori_types/src/infer/env/mod.rsTypeEnvInner::mutability FxHashMap tracks per-binding mutability
    • Ori Tests: tests/spec/expressions/mutable_vs_immutable.ori — verifies $ bindings preserve immutability
    • LLVM Support: N/A — mutability is a type-checker concern, not codegen
    • LLVM Rust Tests: N/A — mutability is a type-checker concern, not codegen
  • Implement: Prevent $x and x coexisting in same scope

    • Rust Tests: ori_types/src/check/binding.rs — same-name conflict detection
    • Ori Tests: tests/compile-fail/dollar_and_non_dollar_conflict.ori
    • LLVM Support: LLVM codegen for same-name conflict detection
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — same-name conflict detection codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Enforce module-level bindings require $ prefix

    • Rust Tests: ori_types/src/check/module.rs — module binding immutability
    • Ori Tests: tests/compile-fail/module_level_mutable.ori
    • LLVM Support: LLVM codegen for module binding immutability
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — module binding immutability codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Enforce $-prefixed bindings cannot be reassigned (2026-02-20)

    • Rust Tests: ori_types/src/infer/expr/operators.rs — immutability check in infer_assign
    • Ori Tests: tests/compile-fail/assign_to_immutable.ori, assign_to_immutable_in_loop.ori, assign_to_immutable_destructured.ori
    • LLVM Support: N/A — compile-time type error (E2039) prevents codegen
    • LLVM Rust Tests: N/A — compile-time type error prevents codegen

Imports

  • Implement: Require $ in import statements for immutable bindings

    • Rust Tests: ori_types/src/check/import.rs — import with dollar
    • Ori Tests: tests/spec/modules/import_immutable.ori
    • LLVM Support: LLVM codegen for import with dollar
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — import with dollar codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Error when importing $x as x or vice versa

    • Rust Tests: ori_types/src/check/import.rs — import modifier mismatch
    • Ori Tests: tests/compile-fail/import_dollar_mismatch.ori
    • LLVM Support: LLVM codegen for import modifier mismatch
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — import modifier mismatch codegen
    • AOT Tests: No AOT coverage yet

Shadowing

  • Implement: Allow shadowing to change mutability
    • Rust Tests: ori_types/src/check/binding.rs — shadowing mutability change
    • Ori Tests: tests/spec/expressions/shadow_mutability.ori
    • LLVM Support: LLVM codegen for shadowing mutability change
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — shadowing mutability change codegen
    • AOT Tests: No AOT coverage yet

Error Messages

  • Implement: Clear error for reassignment to immutable binding (2026-02-20)

    • Rust Tests: ori_types/src/type_error/check_error/mod.rsAssignToImmutable variant + formatting
    • Ori Tests: tests/compile-fail/assign_to_immutable.ori (verifies “cannot assign to immutable binding” message)
    • LLVM Support: N/A — compile-time type error prevents codegen
    • LLVM Rust Tests: N/A — compile-time type error prevents codegen
  • Implement: Clear error for module-level mutable binding

    • Rust Tests: ori_diagnostic/src/problem.rs — module mutable error
    • Ori Tests: tests/compile-fail/module_mutable_message.ori
    • LLVM Support: LLVM codegen for module mutable error
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — module mutable error codegen
    • AOT Tests: No AOT coverage yet
  • Implement: Migration hint for old let mut syntax

    • Rust Tests: ori_diagnostic/src/problem.rs — let mut migration hint
    • Ori Tests: tests/compile-fail/let_mut_migration.ori
    • LLVM Support: LLVM codegen for let mut migration hint
    • LLVM Rust Tests: ori_llvm/tests/binding_tests.rs — let mut migration hint codegen
    • AOT Tests: No AOT coverage yet
  • /tpr-review passed — independent review found no critical or major issues (or all findings triaged)

  • /impl-hygiene-review passed — hygiene review clean. MUST run AFTER /tpr-review is clean.

  • Subsection close-out (15D.3) — MANDATORY before starting the next subsection. Run /improve-tooling retrospectively on THIS subsection’s debugging journey (per .claude/skills/improve-tooling/SKILL.md “Per-Subsection Workflow”): which diagnostics/ scripts you ran, where you added dbg!/tracing calls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE /commit-push using a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.3 retrospectivebuild/test/chore/ci/docs are valid; tools(...) is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.3: no tooling gaps”. Update this subsection’s status in section frontmatter to complete.

  • /sync-claude section-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.

  • Repo hygiene check — run diagnostics/repo-hygiene.sh --check and clean any detected temp files.


15D.4 Remove dyn Keyword for Trait Objects

Proposal: proposals/approved/remove-dyn-keyword-proposal.md

Remove the dyn keyword for trait objects. Trait names used directly as types mean “any value implementing this trait.”

// Before
@process (item: dyn Printable) -> void = ...
let items: [dyn Serializable] = ...

// After
@process (item: Printable) -> void = ...
let items: [Serializable] = ...

Implementation

  • Implement: Remove "dyn" type from grammar type production

    • Rust Tests: ori_parse/src/grammar/ty.rs — dyn removal tests
    • Ori Tests: tests/spec/types/trait_objects.ori
    • LLVM Support: LLVM codegen for trait objects without dyn
    • LLVM Rust Tests: ori_llvm/tests/trait_object_tests.rs
    • AOT Tests: No AOT coverage yet
  • Implement: Parser recognizes trait name in type position as trait object

    • Rust Tests: ori_parse/src/grammar/ty.rs — trait-as-type parsing
    • Ori Tests: tests/spec/types/trait_objects.ori
    • LLVM Support: LLVM codegen for trait-as-type
    • LLVM Rust Tests: ori_llvm/tests/trait_object_tests.rs
    • AOT Tests: No AOT coverage yet
  • Implement: Type checker distinguishes item: Trait (trait object) vs <T: Trait> (generic bound)

    • Rust Tests: ori_types/src/check/trait_objects.rs
    • Ori Tests: tests/spec/types/trait_vs_bound.ori
    • LLVM Support: LLVM codegen for trait object vs bound distinction
    • LLVM Rust Tests: ori_llvm/tests/trait_object_tests.rs
    • AOT Tests: No AOT coverage yet
  • Implement: Object safety validation with clear error messages

    • Rust Tests: ori_types/src/check/object_safety.rs
    • Ori Tests: tests/compile-fail/non_object_safe_trait.ori
    • LLVM Support: LLVM codegen for object safety validation
    • LLVM Rust Tests: ori_llvm/tests/trait_object_tests.rs
    • AOT Tests: No AOT coverage yet
  • Implement: Error if dyn keyword is used (helpful migration message)

    • Rust Tests: ori_parse/src/grammar/ty.rs — dyn keyword error
    • Ori Tests: tests/compile-fail/dyn_keyword_removed.ori
  • /tpr-review passed — independent review found no critical or major issues (or all findings triaged)

  • /impl-hygiene-review passed — hygiene review clean. MUST run AFTER /tpr-review is clean.

  • Subsection close-out (15D.4) — MANDATORY before starting the next subsection. Run /improve-tooling retrospectively on THIS subsection’s debugging journey (per .claude/skills/improve-tooling/SKILL.md “Per-Subsection Workflow”): which diagnostics/ scripts you ran, where you added dbg!/tracing calls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE /commit-push using a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.4 retrospectivebuild/test/chore/ci/docs are valid; tools(...) is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.4: no tooling gaps”. Update this subsection’s status in section frontmatter to complete.

  • /sync-claude section-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.

  • Repo hygiene check — run diagnostics/repo-hygiene.sh --check and clean any detected temp files.


15D.5 Index and Field Assignment

Proposal: proposals/approved/index-assignment-proposal.md

Supersedes: bug-tracker/section-04-codegen-llvm.md::BUG-04-070 (E4003 ARC-lowering symptom), bug-tracker/section-07-tooling-cli.md::BUG-07-016 (typeck-layer naming of the same fix). Both bugs are symptoms of the unimplemented typeck.md §EX-17 desugar; §15D.5’s 5-phase plan IS the fix.

Extend assignment targets to support index expressions (list[i] = x), field access (state.name = x), mixed chains (state.items[i] = x, list[i].name = x), and compound assignment on all forms (list[i] += 1). All forms desugar to copy-on-write reassignment via IndexSet trait (for index) or struct spread (for fields).

let list = [1, 2, 3]
list[0] = 10                          // list = list.updated(key: 0, value: 10)

let state = GameState { score: 0, level: 1 }
state.score = 100                     // state = { ...state, score: 100 }
state.items[i] = new_item             // mixed chain
list[i].name = "new"                  // index then field

list[0] += 5                          // compound: list[0] = list[0] + 5

Phase 1: IndexSet Trait and updated Method

  • Implement: Define IndexSet<Key, Value> trait in prelude — @updated (self, key: Key, value: Value) -> Self

    • Rust Tests: ori_types/src/infer/ — IndexSet trait resolution
    • Ori Tests: tests/spec/traits/index_set/basic.ori
  • Implement: Register updated as built-in method on [T], {K: V}, [T, max N] in evaluator

    • Rust Tests: ori_eval/src/method_dispatch/ — updated method dispatch
    • Ori Tests: tests/spec/traits/index_set/updated_method.ori
  • Implement: updated with ARC-aware copy-on-write in ori_patterns/ori_eval

    • Rust Tests: ori_patterns/src/value/ — copy-on-write behavior
    • Ori Tests: tests/spec/traits/index_set/cow_behavior.ori

Phase 2: Parser Changes

  • Implement: Extend parser to accept assignment_target (identifier + index/field chains) on LHS of = and compound operators

    • Rust Tests: ori_parse/src/grammar/expr/ — assignment target parsing
    • Ori Tests: tests/spec/expressions/index_assignment_syntax.ori
  • Implement: Emit AST node capturing chain of index/field accesses in assignment target

    • Rust Tests: ori_ir/src/ast/expr.rs — AssignTarget AST node
    • Ori Tests: tests/spec/expressions/field_assignment_syntax.ori

Phase 3: Type-Directed Desugaring

  • Implement: Desugar [key] steps to updated() calls (requires IndexSet trait resolution)

    • Rust Tests: ori_types/src/infer/expr/ — index assignment desugaring
    • Ori Tests: tests/spec/expressions/index_assignment_desugar.ori
  • Implement: Desugar .field steps to struct spread reconstruction (requires struct type info)

    • Rust Tests: ori_types/src/infer/expr/ — field assignment desugaring
    • Ori Tests: tests/spec/expressions/field_assignment_desugar.ori
  • Implement: Handle nested cases, mixed field-index chains, and compound assignment

    • Rust Tests: ori_types/src/infer/expr/ — mixed chain desugaring
    • Ori Tests: tests/spec/expressions/mixed_chain_assignment.ori

Phase 4: Type Checker Integration

  • Implement: Validate mutability of root binding (not $, not parameter, not loop variable)

    • Rust Tests: ori_types/src/infer/expr/ — mutability validation
    • Ori Tests: tests/spec/expressions/assignment_mutability.ori
  • Implement: Validate field names against struct types in assignment chains

    • Rust Tests: ori_types/src/infer/expr/ — field validation
    • Ori Tests: tests/compile-fail/assignment/invalid_field.ori
  • Implement: Validate key and value types against IndexSet impl

    • Rust Tests: ori_types/src/infer/expr/ — IndexSet type validation
    • Ori Tests: tests/compile-fail/assignment/type_mismatch.ori
  • Implement: Emit diagnostics for all error cases (immutable binding, parameter, loop var, missing IndexSet, field mismatch, type mismatch)

    • Rust Tests: ori_diagnostic/ — assignment error diagnostics
    • Ori Tests: tests/compile-fail/assignment/all_errors.ori

Phase 5: LLVM Support

  • LLVM Support: LLVM codegen for index assignment desugaring

    • LLVM Rust Tests: ori_llvm/tests/ — index assignment codegen
    • AOT Tests: No AOT coverage yet
  • LLVM Support: LLVM codegen for field assignment desugaring

    • LLVM Rust Tests: ori_llvm/tests/ — field assignment codegen
    • AOT Tests: No AOT coverage yet
  • LLVM Support: LLVM codegen for compound assignment on extended targets

    • LLVM Rust Tests: ori_llvm/tests/ — compound assignment codegen
    • AOT Tests: No AOT coverage yet
  • /tpr-review passed — independent review found no critical or major issues (or all findings triaged)

  • /impl-hygiene-review passed — hygiene review clean. MUST run AFTER /tpr-review is clean.

  • Subsection close-out (15D.5) — MANDATORY before starting the next subsection. Run /improve-tooling retrospectively on THIS subsection’s debugging journey (per .claude/skills/improve-tooling/SKILL.md “Per-Subsection Workflow”): which diagnostics/ scripts you ran, where you added dbg!/tracing calls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE /commit-push using a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.5 retrospectivebuild/test/chore/ci/docs are valid; tools(...) is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.5: no tooling gaps”. Update this subsection’s status in section frontmatter to complete.

  • /sync-claude section-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.

  • Repo hygiene check — run diagnostics/repo-hygiene.sh --check and clean any detected temp files.


15D.6 Mutable Self

Proposal: proposals/approved/mutable-self-proposal.md

Make self a mutable binding in method bodies with implicit mutation propagation back to the caller. Extends the existing field/index assignment desugaring pattern to method calls.

impl Cursor {
    @advance (self) -> void = {
        self.pos += 1
    }
}

cursor.advance()   // cursor is updated via desugaring

Phase 1: Self Mutability

  • Implement: Make self a mutable binding in method bodies (type checker)

    • Rust Tests: ori_types/src/infer/ — self mutability in methods
    • Ori Tests: tests/spec/methods/mutable_self_basic.ori
  • Implement: Mutation detection dataflow analysis (classify methods as mutating/non-mutating)

    • Rust Tests: ori_types/src/infer/ — mutation classification
    • Ori Tests: tests/spec/methods/mutation_detection.ori

Phase 2: Call-Site Desugaring

  • Implement: Void-returning mutating methods desugar to -> Self + implicit reassignment

    • Rust Tests: ori_types/src/infer/expr/ — void method desugaring
    • Ori Tests: tests/spec/methods/mutable_self_void.ori
  • Implement: Value-returning mutating methods desugar to -> (Self, T) + tuple split

    • Rust Tests: ori_types/src/infer/expr/ — value method desugaring
    • Ori Tests: tests/spec/methods/mutable_self_value_return.ori
  • Implement: No desugaring for -> Self methods (already return Self)

    • Rust Tests: ori_types/src/infer/expr/ — Self return carve-out
    • Ori Tests: tests/spec/methods/mutable_self_returns_self.ori
  • Implement: Nested field mutation cascading (self.inner.advance())

    • Rust Tests: ori_types/src/infer/expr/ — nested mutation desugaring
    • Ori Tests: tests/spec/methods/mutable_self_nested.ori

Phase 3: Caller Validation

  • Implement: Error when calling mutating method on immutable ($) binding

    • Rust Tests: ori_types/src/infer/expr/ — immutable receiver error
    • Ori Tests: tests/compile-fail/methods/mutating_on_immutable.ori
  • Implement: Diagnostics for mutation-related errors

    • Rust Tests: ori_diagnostic/ — mutation error diagnostics
    • Ori Tests: tests/compile-fail/methods/mutation_errors.ori

Phase 4: Trait Integration

  • Implement: Infer mutation classification across trait implementations

    • Rust Tests: ori_types/src/infer/ — trait mutation consistency
    • Ori Tests: tests/spec/traits/mutable_self_trait.ori
  • Implement: Conservative mutating classification for trait objects without local impls

    • Rust Tests: ori_types/src/infer/ — trait object mutation
    • Ori Tests: tests/spec/traits/mutable_self_trait_object.ori

Phase 5: Extension Support

  • Implement: Extension methods follow same mutation propagation rules
    • Rust Tests: ori_types/src/infer/ — extension mutation propagation
    • Ori Tests: tests/spec/methods/mutable_self_extension.ori

Phase 6: Evaluator

  • Implement: Evaluator support for mutable self in method bodies

    • Rust Tests: ori_eval/src/ — mutable self evaluation
    • Ori Tests: tests/spec/methods/mutable_self_eval.ori
  • Implement: Evaluator call-site desugaring (implicit reassignment)

    • Rust Tests: ori_eval/src/ — call-site desugaring evaluation
    • Ori Tests: tests/spec/methods/mutable_self_propagation.ori

Phase 7: LLVM Support

  • LLVM Support: LLVM codegen for mutable self desugaring

    • LLVM Rust Tests: ori_llvm/tests/ — mutable self codegen
    • AOT Tests: tests/spec/methods/mutable_self_aot.ori
  • LLVM Support: COW integration for self-mutating methods

    • LLVM Rust Tests: ori_llvm/tests/ — mutable self COW codegen
    • AOT Tests: tests/spec/methods/mutable_self_cow.ori
  • /tpr-review passed — independent review found no critical or major issues (or all findings triaged)

  • /impl-hygiene-review passed — hygiene review clean. MUST run AFTER /tpr-review is clean.

  • Subsection close-out (15D.6) — MANDATORY before starting the next subsection. Run /improve-tooling retrospectively on THIS subsection’s debugging journey (per .claude/skills/improve-tooling/SKILL.md “Per-Subsection Workflow”): which diagnostics/ scripts you ran, where you added dbg!/tracing calls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE /commit-push using a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.6 retrospectivebuild/test/chore/ci/docs are valid; tools(...) is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.6: no tooling gaps”. Update this subsection’s status in section frontmatter to complete.

  • /sync-claude section-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.

  • Repo hygiene check — run diagnostics/repo-hygiene.sh --check and clean any detected temp files.


15D.7 Section Completion Checklist

  • All implementation items have checkboxes marked [ ]
  • All spec docs updated
  • CLAUDE.md updated with syntax changes
  • Migration tools working
  • All tests pass: ./test-all.sh
  • /tpr-review passed — independent Codex review found no critical or major issues (or all findings triaged)
  • /impl-hygiene-review passed — implementation hygiene review clean (phase boundaries, SSOT, algorithmic DRY, naming). MUST run AFTER /tpr-review is clean.
  • /improve-tooling retrospective completed — MANDATORY at section close, after both reviews are clean. Reflect on the section’s debugging journey (which diagnostics/ scripts you ran, which command sequences you repeated, where you added ad-hoc dbg!/tracing calls, where output was hard to interpret) and identify any tool/log/diagnostic improvement that would have made this section materially easier OR that would help the next section touching this area. Implement every accepted improvement NOW (zero deferral) and commit each via SEPARATE /commit-push. The retrospective is mandatory even when nothing felt painful — that is exactly when blind spots accumulate. See .claude/skills/improve-tooling/SKILL.md “Retrospective Mode” for the full protocol.

Exit Criteria: Binding and type syntax proposals implemented

  • Subsection close-out (15D.7) — MANDATORY before starting the next subsection. Run /improve-tooling retrospectively on THIS subsection’s debugging journey (per .claude/skills/improve-tooling/SKILL.md “Per-Subsection Workflow”): which diagnostics/ scripts you ran, where you added dbg!/tracing calls, where output was hard to interpret, where test failures gave unhelpful messages, where you ran the same command sequence repeatedly. Forward-look: what tool/log/diagnostic would shorten the next regression in this code path by 10 minutes? Implement improvements NOW (zero deferral) and commit each via SEPARATE /commit-push using a valid conventional-commit type (build(diagnostics): ... — surfaced by section-15D.7 retrospectivebuild/test/chore/ci/docs are valid; tools(...) is rejected by the lefthook commit-msg hook). Mandatory even when nothing felt painful. If genuinely no gaps, document briefly: “Retrospective 15D.7: no tooling gaps”. Update this subsection’s status in section frontmatter to complete.
  • /sync-claude section-close doc sync — verify Claude artifacts across all section commits. Map changed crates to rules files, check CLAUDE.md, canon.md. Fix drift NOW.
  • Repo hygiene check — run diagnostics/repo-hygiene.sh --check and clean any detected temp files.