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

29 lines
1.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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.