Files
hakorune/docs/reference/language/variables-and-scope.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

3.3 KiB
Raw Blame History

Variables and Scope (Local/Block Semantics)

Status: Stable (Stage3 surface for local), default strong references.

This document defines the variable model used by Hakorune/Nyash and clarifies how locals interact with blocks, memory, and references across VMs (Rust VM, Hakorune VM, LLVM harness).

Local Variables

  • Syntax: local name = expr
  • Scope: Blockscoped. The variable is visible from its declaration to the end of the lexical block.
  • Redeclaration: Writing local name = ... inside a nested block creates a new shadowing binding. Writing name = ... without local updates the nearest existing binding in an enclosing scope.
  • Mutability: Locals are mutable unless future keywords specify otherwise (e.g., const).
  • Lifetime: The variable binding is dropped at block end; any referenced objects live as long as at least one strong reference exists elsewhere.

Notes:

  • Stage3 gate: Parsing local requires Stage3 to be enabled (NYASH_PARSER_STAGE3=1 or equivalent runner profile).

Assignment Resolution (Enclosing Scope Update)

Assignment to an identifier resolves as follows:

  1. If a local declaration with the same name exists in the current block, update that binding.
  2. Otherwise, search outward through enclosing blocks and update the first found binding.
  3. If no binding exists in any enclosing scope, create a new binding in the current scope.

This matches intuitive blockscoped semantics (Lualike), and differs from Python where inner blocks do not create a new scope (function scope), and assignment would create a local unless nonlocal/global is used.

Reference Semantics (Strong/Weak)

  • Default: Locals hold strong references to boxes/collections. Implementation uses reference counting (strong = ownership) with internal synchronization.
  • Weak references: Use WeakBox to hold a nonowning (weak) reference. Weak refs do not keep the object alive; they can be upgraded to strong at use sites. Intended for backpointers and cachelike links to avoid cycles.
  • Typical guidance:
    • Locals and return values: strong references.
    • Object fields that create cycles (child→parent): weak references.

Example (nested block retains object via outer local):

local a = null
{
  local b = new Box(a)
  a = b  // outer binding updated; a and b point to the same object
}
// leaving the block drops `b` (strongcount 1), but `a` still keeps the object alive

Shadowing vs. Updating

  • Shadowing: local x = ... inside a block hides an outer x for the remainder of the inner block. The outer x remains unchanged.
  • Updating: x = ... without local updates the nearest enclosing x binding.

Prefer clarity: avoid accidental shadowing. If you intentionally shadow, consider naming or comments to clarify intent.

Const/Immutability (Future)

  • A separate keyword (e.g., const) can introduce an immutable local. Semantics: same scoping as local, but reassignment is a compile error. This does not affect reference ownership (still strong by default).

CrossVM Consistency

The above semantics are enforced consistently across:

  • Rust VM (MIR interpreter): scope updates propagate to enclosing locals.
  • Hakorune VM/runner: same resolution rules.
  • LLVM harness/EXE: parity tests validate identical exit codes/behavior.

See also: quick/integration smokes scope_assign_vm.sh, vm_llvm_scope_assign.sh.