Files
hakorune/docs/reference/invariants.md
nyash-codex 8b44c5009f fix(mir): fix else block scope bug - PHI materialization order
Root Cause:
- Else blocks were not propagating variable assignments to outer scope
- Bug 1 (if_form.rs): PHI materialization happened before variable_map reset,
  causing PHI nodes to be lost
- Bug 2 (phi.rs): Variable merge didn't check if else branch modified variables

Changes:
- src/mir/builder/if_form.rs:93-127
  - Reordered: reset variable_map BEFORE materializing PHI nodes
  - Now matches then-branch pattern (reset → materialize → execute)
  - Applied to both "else" and "no else" branches for consistency
- src/mir/builder/phi.rs:137-154
  - Added else_modified_var check to detect variable modifications
  - Use modified value from else_var_map_end_opt when available
  - Fall back to pre-if value only when truly not modified

Test Results:
 Simple block: { x=42 } → 42
 If block: if 1 { x=42 } → 42
 Else block: if 0 { x=99 } else { x=42 } → 42 (FIXED!)
 Stage-B body extraction: "return 42" correctly extracted (was null)

Impact:
- Else block variable assignments now work correctly
- Stage-B compiler body extraction restored
- Selfhost builder path can now function
- Foundation for Phase 21.x progress

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-13 20:16:20 +09:00

1.8 KiB
Raw Blame History

Nyash Invariants (Spec)

This document lists nonnegotiable invariants the implementation preserves across backends. Theyre used as a contract for testing and design.

Core

  • PHI hygiene
    • No empty PHI nodes are emitted in LLVM IR (temporary safety valve exists behind NYASH_LLVM_SANITIZE_EMPTY_PHI=1).
    • All PHIs appear at the beginning of a basic block.
  • Match/Peek
    • A match scrutinee is evaluated exactly once, bound to an internal gensym.
    • Guard conditions are logically conjoined with the arm predicate; fallthrough reaches the next arm.
  • Exceptions
    • Exceptions follow “scope first” semantics. Postfix catch/cleanup normalize to a single TryCatch block around the immediatelypreceding expression.
  • LoopForm
    • Loop bodies may be normalized where safe to a stable order: nonassign statements then assign statements. No semantic reorder is performed when a nonassign appears after any assign.
    • Carrier analysis emits observation hints only (zero runtime cost).
    • Break/continue lowering is unified via LoopBuilder; nested bare blocks inside loops are handled consistently (Program nodes recurse into loopaware lowering).
  • Scope
  • Enter/Leave scope events are observable through MIR hints; they do not affect program semantics.
  • Blockscoped locals: local x = ... declares a binding limited to the lexical block. Assignment without local updates the nearest enclosing binding; redeclaration with local shadows the outer variable. This is Lualike and differs from Python's block (no) scope.

Observability

  • MIR hints can be traced via NYASH_MIR_HINTS (pipe style): trace|scope|join|loop|phi or jsonl=path|loop.
  • Golden/Smoke tests cover representative invariants.

Backends

  • PyVM and LLVM share semantics; PyVM prioritizes clarity over performance.
  • Cranelift/WASM routes inherit the same invariants when enabled.