A1.4: Add sugar syntax `public weak parent` ≡ `public { weak parent }`
A1.5: Fix parser hang on unsupported `param: Type` syntax
Key changes:
- A1.4: Extend visibility parser to handle weak modifier (fields.rs)
- A1.5: Shared helper `parse_param_name_list()` with progress-zero detection
- A1.5: Fix 6 vulnerable parameter parsing loops (methods, constructors, functions)
- Tests: Sugar syntax (OK/NG), parser hang (timeout-based)
- Docs: lifecycle.md, EBNF.md, phase-285a1-boxification.md
Additional changes:
- weak() builtin implementation (handlers/weak.rs)
- Leak tracking improvements (leak_tracker.rs)
- Documentation updates (lifecycle, types, memory-finalization, etc.)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
3.4 KiB
Variables and Scope (Local/Block Semantics)
Status: Stable (Stage‑3 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).
For the lifecycle/finalization SSOT, see: docs/reference/language/lifecycle.md.
Local Variables
- Syntax:
local name = expr - Scope: Block‑scoped. 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. Writingname = ...withoutlocalupdates 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 (
}); object lifetime/finalization is defined separately indocs/reference/language/lifecycle.md.
Notes:
- Stage‑3 gate: Parsing
localrequires Stage‑3 to be enabled (NYASH_PARSER_STAGE3=1or equivalent runner profile).
Assignment Resolution (Enclosing Scope Update)
Assignment to an identifier resolves as follows:
- If a
localdeclaration with the same name exists in the current block, update that binding. - Otherwise, search outward through enclosing blocks and update the first found binding.
- If no binding exists in any enclosing scope, it is an error (undeclared variable). Declare it with
local.
This matches intuitive block‑scoped semantics (Lua‑like), 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.
- Weak references: Use
weak(x)(and fields that storeWeakRef) to hold a non‑owning reference. Weak refs do not keep the object alive; they can be upgraded at use sites (see SSOT:docs/reference/language/lifecycle.md). - 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` (strong‑count ‑1), but `a` still keeps the object alive
Shadowing vs. Updating
- Shadowing:
local x = ...inside a block hides an outerxfor the remainder of the inner block. The outerxremains unchanged. - Updating:
x = ...withoutlocalupdates the nearest enclosingxbinding.
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 aslocal, but re‑assignment is a compile error. This does not affect reference ownership (still strong by default).
Cross‑VM 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.