fix(env): improve Environment::set scope resolution (partial)
Fixed:
- Environment::set now properly searches ancestor chain before creating new binding
- Added exists_in_chain_locked() helper for explicit existence checking
- Simple {} blocks now correctly update outer scope variables
Verified Working:
- local x = 10; { x = 42 }; print(x) → prints 42 ✅
Still Broken:
- else blocks don't update outer scope variables
- local x = 10; if flag { x = 99 } else { x = 42 }; print(x) → prints 10 ❌
Root Cause Identified:
- Issue is in MIR Builder (compile-time), not Environment (runtime)
- src/mir/builder/if_form.rs:108 resets variable_map before else block
- PHI generation at merge doesn't use else_var_map_end correctly
- MIR shows: phi [%32, bb1], [%1, bb2] where %1 is original value, not else value
Next: Fix else block variable merging in if_form.rs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -102,6 +102,13 @@ static box Main {
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[DEBUG] Source length: " + s.length())
|
||||
print("[DEBUG] k0 (main position) = " + k0)
|
||||
}
|
||||
}
|
||||
if k0 >= 0 {
|
||||
// find '(' after k0 (skip inside strings)
|
||||
local k1 = -1
|
||||
@ -124,6 +131,12 @@ static box Main {
|
||||
j = j + 1
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[DEBUG] k1 ('(' position) = " + k1)
|
||||
}
|
||||
}
|
||||
if k1 >= 0 {
|
||||
// find ')' after k1 (skip inside strings)
|
||||
local k2 = -1
|
||||
@ -146,6 +159,12 @@ static box Main {
|
||||
j = j + 1
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[DEBUG] k2 (')' position) = " + k2)
|
||||
}
|
||||
}
|
||||
if k2 >= 0 {
|
||||
// Find opening '{' following ')' (skip inside strings)
|
||||
local k3 = -1
|
||||
@ -168,6 +187,12 @@ static box Main {
|
||||
j = j + 1
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[DEBUG] k3 ('{' position) = " + k3)
|
||||
}
|
||||
}
|
||||
if k3 >= 0 {
|
||||
// Balanced scan for matching '}'
|
||||
local depth = 0
|
||||
@ -175,6 +200,8 @@ static box Main {
|
||||
local n = s.length()
|
||||
local in_str = 0
|
||||
local esc = 0
|
||||
local start_pos = -1
|
||||
local end_pos = -1
|
||||
loop(i < n) {
|
||||
local ch = s.substring(i, i + 1)
|
||||
if in_str == 1 {
|
||||
@ -194,16 +221,85 @@ static box Main {
|
||||
}
|
||||
i = i + 1
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[DEBUG] After balanced scan: depth=" + depth + ", i=" + i)
|
||||
}
|
||||
}
|
||||
if depth == 0 {
|
||||
// inside of '{'..'}'
|
||||
body_src = s.substring(k3 + 1, i - 1)
|
||||
start_pos = k3 + 1
|
||||
end_pos = i - 1
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[DEBUG] Will extract substring(" + start_pos + ", " + end_pos + ")")
|
||||
}
|
||||
}
|
||||
}
|
||||
// Extract outside the nested blocks to avoid potential scoping issues
|
||||
if start_pos >= 0 && end_pos >= start_pos {
|
||||
body_src = s.substring(start_pos, end_pos)
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
if body_src == null {
|
||||
print("[DEBUG] After extraction: body_src is NULL!")
|
||||
} else {
|
||||
print("[DEBUG] After extraction: body_src length = " + body_src.length())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
if body_src == null { print("[DEBUG] body_src is NULL after line 254") } else { print("[DEBUG] body_src OK after line 254, len=" + body_src.length()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
if body_src == null { print("[DEBUG] body_src is NULL after line 256") } else { print("[DEBUG] body_src OK after line 256, len=" + body_src.length()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
if body_src == null { print("[DEBUG] body_src is NULL after line 268") } else { print("[DEBUG] body_src OK after line 268, len=" + body_src.length()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
if body_src == null { print("[DEBUG] body_src is NULL after line 269") } else { print("[DEBUG] body_src OK after line 269, len=" + body_src.length()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
if body_src == null { print("[DEBUG] body_src is NULL after line 270") } else { print("[DEBUG] body_src OK after line 270, len=" + body_src.length()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fallback: if extraction failed or produced empty, use full src
|
||||
{
|
||||
local dbg = env.get("HAKO_STAGEB_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[DEBUG] Body extraction:")
|
||||
if body_src == null {
|
||||
print("[DEBUG] body_src = null")
|
||||
} else {
|
||||
print("[DEBUG] body_src length = " + ("" + body_src).length())
|
||||
print("[DEBUG] body_src = [" + body_src + "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
if body_src == null || ("" + body_src).length() == 0 { body_src = src }
|
||||
}
|
||||
|
||||
|
||||
@ -109,20 +109,32 @@ impl Environment {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// 親スコープで再帰的に検索・設定
|
||||
// 祖先いずれかに既存があれば「既存を更新」、無ければ「現在スコープに新規定義」
|
||||
if let Some(parent) = &self.parent {
|
||||
match parent.lock().unwrap().set(&name, value.clone_or_share()) {
|
||||
Ok(()) => return Ok(()),
|
||||
Err(EnvironmentError::UndefinedVariable { .. }) => {
|
||||
// 親にもない場合は現在のスコープに新規定義
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
if Self::exists_in_chain_locked(&parent.lock().unwrap(), &name) {
|
||||
// 祖先のどこかに存在 → そこで更新
|
||||
parent.lock().unwrap().set(&name, value)
|
||||
} else {
|
||||
// どこにも存在しない → 現在のスコープで定義
|
||||
self.bindings.lock().unwrap().insert(name, value);
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
// 親がない(グローバル)→ 現在で定義
|
||||
self.bindings.lock().unwrap().insert(name, value);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// 新規定義として現在のスコープに追加
|
||||
self.bindings.lock().unwrap().insert(name, value);
|
||||
Ok(())
|
||||
/// 祖先(自分含む)に名前が存在するか(ロック下で使用)
|
||||
fn exists_in_chain_locked(&self, name: &str) -> bool {
|
||||
if self.bindings.lock().unwrap().contains_key(name) {
|
||||
return true;
|
||||
}
|
||||
if let Some(parent) = &self.parent {
|
||||
return parent.lock().unwrap().exists_in_chain_locked(name);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 変数が存在するかチェック
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/../../../../../.." && pwd)"
|
||||
source "$ROOT_DIR/tools/smokes/v2/lib/test_runner.sh" || true
|
||||
source "$ROOT_DIR/tools/smokes/v2/lib/result_checker.sh" || true
|
||||
|
||||
require_env || { echo "[SKIP] env not ready"; exit 0; }
|
||||
|
||||
test_vm_llvm_scope_assign() {
|
||||
local code='static box Main { method main(args) { local x = 0 { if (1==1) { x = 42 } } return x } }'
|
||||
# Stage-3 parse is required for `local`
|
||||
NYASH_PARSER_STAGE3=1 check_parity -c "$code" "vm_llvm_scope_assign"
|
||||
}
|
||||
|
||||
run_test "vm_llvm_scope_assign" test_vm_llvm_scope_assign
|
||||
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT_DIR="$(cd "$SCRIPT_DIR/../../../../../../.." && pwd)"
|
||||
source "$ROOT_DIR/tools/smokes/v2/lib/test_runner.sh" || true
|
||||
source "$ROOT_DIR/tools/smokes/v2/lib/result_checker.sh" || true
|
||||
|
||||
require_env || { echo "[SKIP] env not ready"; exit 0; }
|
||||
|
||||
test_scope_assign_vm() {
|
||||
local code='static box Main { method main(args) { local x = 0 { if (1==1) { x = 42 } } return x } }'
|
||||
local tmp
|
||||
tmp=$(mktemp --suffix .hako)
|
||||
printf '%s' "$code" > "$tmp"
|
||||
# Quick: VM should reflect nested assignment
|
||||
local rc
|
||||
NYASH_PARSER_STAGE3=1 "$NYASH_BIN" --backend vm "$tmp" >/dev/null 2>&1
|
||||
rc=$?
|
||||
if [[ "$rc" -ne 42 ]]; then
|
||||
echo "[FAIL] scope_assign_vm: vm rc=$rc (expected 42)"
|
||||
return 1
|
||||
fi
|
||||
echo "[PASS] scope_assign_vm"
|
||||
rm -f "$tmp" 2>/dev/null || true
|
||||
return 0
|
||||
}
|
||||
|
||||
run_test "scope_assign_vm" test_scope_assign_vm
|
||||
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../../../../../.." && pwd)"
|
||||
BIN="$ROOT/target/release/hakorune"
|
||||
|
||||
if [[ ! -x "$BIN" ]]; then
|
||||
echo "[SKIP] hakorune not built"; exit 0
|
||||
fi
|
||||
|
||||
# Source with nested assignment; Stage-B should extract body or at least output Program JSON
|
||||
SRC='static box Main { method main(args) { local x = 0 { if (1==1) { x = 42 } } return x } }'
|
||||
|
||||
out=$(NYASH_ENABLE_USING=1 HAKO_ENABLE_USING=1 "$BIN" --backend vm "$ROOT/lang/src/compiler/entry/compiler_stageb.hako" -- --source "$SRC" 2>/dev/null || true)
|
||||
|
||||
echo "$out" | grep -q '"kind":"Program"' || { echo "[FAIL] stageb_scope_extract_canary: no Program JSON"; exit 1; }
|
||||
|
||||
# Heuristic check: JSON contains at least a numeric literal 42 somewhere
|
||||
echo "$out" | grep -q '42' || { echo "[FAIL] stageb_scope_extract_canary: literal 42 not found"; exit 1; }
|
||||
|
||||
echo "[PASS] stageb_scope_extract_canary"
|
||||
|
||||
Reference in New Issue
Block a user