MIR: lexical scoping + builder vars modules
This commit is contained in:
@ -590,8 +590,8 @@ pub enum ASTNode {
|
||||
span: Span,
|
||||
},
|
||||
|
||||
/// ScopeBox(オプション): 診断/マクロ可視性のためのno-opスコープ。
|
||||
/// 正規化で注入され、MIRビルダがブロックとして処理(意味不変)。
|
||||
/// ScopeBox(オプション): 正規化で注入される明示的なレキシカルスコープ境界。
|
||||
/// MIR ビルダは `{ ... }` と同様にブロックとして処理する(local のシャドウイング/寿命を分離)。
|
||||
ScopeBox { body: Vec<ASTNode>, span: Span },
|
||||
|
||||
/// Outbox変数宣言: outbox x, y, z (static関数内専用)
|
||||
|
||||
@ -92,6 +92,12 @@ pub struct MirBuilder {
|
||||
/// Phase 25.1: HashMap → BTreeMap(PHI生成の決定性確保)
|
||||
pub(super) variable_map: BTreeMap<String, ValueId>,
|
||||
|
||||
/// Lexical scope stack for block-scoped `local` declarations.
|
||||
///
|
||||
/// This tracks per-block shadowing so `local x` inside `{...}` restores the
|
||||
/// outer binding when the block ends.
|
||||
lexical_scope_stack: Vec<vars::lexical_scope::LexicalScopeFrame>,
|
||||
|
||||
/// Pending phi functions to be inserted
|
||||
#[allow(dead_code)]
|
||||
pub(super) pending_phis: Vec<(BasicBlockId, ValueId, String)>,
|
||||
@ -266,6 +272,7 @@ impl MirBuilder {
|
||||
block_gen: BasicBlockIdGenerator::new(),
|
||||
compilation_context: None, // 箱理論: デフォルトは従来モード
|
||||
variable_map: BTreeMap::new(), // Phase 25.1: 決定性確保
|
||||
lexical_scope_stack: Vec::new(),
|
||||
pending_phis: Vec::new(),
|
||||
value_origin_newbox: BTreeMap::new(), // Phase 25.1: 決定性確保
|
||||
user_defined_boxes: HashSet::new(),
|
||||
@ -546,37 +553,45 @@ impl MirBuilder {
|
||||
if let Some(&value_id) = self.variable_map.get(&name) {
|
||||
Ok(value_id)
|
||||
} else {
|
||||
// Enhance diagnostics using Using simple registry (Phase 1)
|
||||
let mut msg = format!("Undefined variable: {}", name);
|
||||
|
||||
// Stage-3 keyword diagnostic (local/flow/try/catch/throw)
|
||||
if name == "local" && !crate::config::env::parser_stage3_enabled() {
|
||||
msg.push_str("\nHint: 'local' is a Stage-3 keyword. Prefer NYASH_FEATURES=stage3 (legacy: NYASH_PARSER_STAGE3=1 / HAKO_PARSER_STAGE3=1 for Stage-B).");
|
||||
msg.push_str("\nFor AotPrep verification, use tools/hakorune_emit_mir.sh which sets these automatically.");
|
||||
} else if (name == "flow" || name == "try" || name == "catch" || name == "throw")
|
||||
&& !crate::config::env::parser_stage3_enabled()
|
||||
{
|
||||
msg.push_str(&format!("\nHint: '{}' is a Stage-3 keyword. Prefer NYASH_FEATURES=stage3 (legacy: NYASH_PARSER_STAGE3=1 / HAKO_PARSER_STAGE3=1 for Stage-B).", name));
|
||||
}
|
||||
|
||||
let suggest = crate::using::simple_registry::suggest_using_for_symbol(&name);
|
||||
if !suggest.is_empty() {
|
||||
msg.push_str("\nHint: symbol appears in using module(s): ");
|
||||
msg.push_str(&suggest.join(", "));
|
||||
msg.push_str(
|
||||
"\nConsider adding 'using <module> [as Alias]' or check nyash.toml [using].",
|
||||
);
|
||||
}
|
||||
Err(msg)
|
||||
Err(self.undefined_variable_message(&name))
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn undefined_variable_message(&self, name: &str) -> String {
|
||||
// Enhance diagnostics using Using simple registry (Phase 1)
|
||||
let mut msg = format!("Undefined variable: {}", name);
|
||||
|
||||
// Stage-3 keyword diagnostic (local/flow/try/catch/throw)
|
||||
if name == "local" && !crate::config::env::parser_stage3_enabled() {
|
||||
msg.push_str("\nHint: 'local' is a Stage-3 keyword. Prefer NYASH_FEATURES=stage3 (legacy: NYASH_PARSER_STAGE3=1 / HAKO_PARSER_STAGE3=1 for Stage-B).");
|
||||
msg.push_str("\nFor AotPrep verification, use tools/hakorune_emit_mir.sh which sets these automatically.");
|
||||
} else if (name == "flow" || name == "try" || name == "catch" || name == "throw")
|
||||
&& !crate::config::env::parser_stage3_enabled()
|
||||
{
|
||||
msg.push_str(&format!("\nHint: '{}' is a Stage-3 keyword. Prefer NYASH_FEATURES=stage3 (legacy: NYASH_PARSER_STAGE3=1 / HAKO_PARSER_STAGE3=1 for Stage-B).", name));
|
||||
}
|
||||
|
||||
let suggest = crate::using::simple_registry::suggest_using_for_symbol(name);
|
||||
if !suggest.is_empty() {
|
||||
msg.push_str("\nHint: symbol appears in using module(s): ");
|
||||
msg.push_str(&suggest.join(", "));
|
||||
msg.push_str("\nConsider adding 'using <module> [as Alias]' or check nyash.toml [using].");
|
||||
}
|
||||
|
||||
msg
|
||||
}
|
||||
|
||||
/// Build assignment
|
||||
pub(super) fn build_assignment(
|
||||
&mut self,
|
||||
var_name: String,
|
||||
value: ASTNode,
|
||||
) -> Result<ValueId, String> {
|
||||
// SSOT (LANGUAGE_REFERENCE_2025 / syntax-cheatsheet):
|
||||
// - Assignment to an undeclared name is an error.
|
||||
// - Use `local name = ...` (or `local name; name = ...`) to declare.
|
||||
vars::assignment_resolver::AssignmentResolverBox::ensure_declared(self, &var_name)?;
|
||||
|
||||
let value_id = self.build_expression(value)?;
|
||||
|
||||
// Step 5-5-E: FIX variable map corruption bug
|
||||
|
||||
@ -21,6 +21,7 @@ impl super::MirBuilder {
|
||||
// Sequentially lower statements and return last value (or Void)
|
||||
self.cf_block(statements)
|
||||
}
|
||||
ASTNode::ScopeBox { body, .. } => self.cf_block(body),
|
||||
ASTNode::Print { expression, .. } => self.build_print_statement(*expression),
|
||||
ASTNode::If {
|
||||
condition,
|
||||
|
||||
@ -170,6 +170,8 @@ impl super::MirBuilder {
|
||||
// Scope hint for bare block (Program)
|
||||
let scope_id = self.current_block.map(|bb| bb.as_u32()).unwrap_or(0);
|
||||
self.hint_scope_enter(scope_id);
|
||||
let _lex_scope = super::vars::lexical_scope::LexicalScopeGuard::new(self);
|
||||
|
||||
let mut last_value = None;
|
||||
let total = statements.len();
|
||||
eprintln!("[DEBUG/build_block] Processing {} statements", total);
|
||||
@ -336,7 +338,7 @@ impl super::MirBuilder {
|
||||
var_name, var_id
|
||||
);
|
||||
}
|
||||
self.variable_map.insert(var_name.clone(), var_id);
|
||||
self.declare_local_in_current_scope(var_name, var_id)?;
|
||||
// SlotRegistry にもローカル変数スロットを登録しておくよ(観測専用)
|
||||
if let Some(reg) = self.current_slot_registry.as_mut() {
|
||||
let ty = self.value_types.get(&var_id).cloned();
|
||||
|
||||
26
src/mir/builder/vars/README.md
Normal file
26
src/mir/builder/vars/README.md
Normal file
@ -0,0 +1,26 @@
|
||||
# `mir::builder::vars`(変数/スコープ系の小箱)
|
||||
|
||||
このディレクトリは「MIR ビルダ内の変数・スコープ」に関する小さな責務を分離するための層だよ。
|
||||
|
||||
## 責務(この層がやる)
|
||||
|
||||
- **レキシカルスコープ**: `{...}` / `ScopeBox` の境界で `local` のシャドウイングを復元する。
|
||||
- **AST 走査ユーティリティ**: free vars 収集など、純粋な走査処理。
|
||||
- **代入の宣言ポリシー**: 未宣言名への代入を Fail-Fast にする(`AssignmentResolverBox`)。
|
||||
|
||||
## 非責務(この層がやらない)
|
||||
|
||||
- JoinIR lowering 側の名前解決(`join_ir/lowering/*` の `ScopeManager` が担当)。
|
||||
- ループパターン/PHI/境界生成(`control_flow/joinir/*` が担当)。
|
||||
- 言語仕様の追加(この層は既存仕様の実装に限定)。
|
||||
|
||||
## スコープ/名前解決の境界(SSOT)
|
||||
|
||||
同じ「名前」を扱っていても、層ごとに “解いている問題” が違うので混ぜない。
|
||||
|
||||
- **MIR(この層)**: `variable_map` + `LexicalScopeGuard` で「束縛の寿命・シャドウイング」を管理する(SSA 変換のため)。
|
||||
- **JoinIR lowering**: `src/mir/join_ir/lowering/scope_manager.rs` は JoinIR 内の `name → ValueId` を解決する。
|
||||
- `ExprLowerer` は **ScopeManager 経由のみ** で名前解決する(env を直参照しない)。
|
||||
- **解析箱**: `LoopConditionScopeBox` / `LoopBodyLocalEnv` は「禁止/許可」「スケジュール」などの補助情報で、束縛そのものではない。
|
||||
|
||||
この境界を跨ぐ “便利メソッド” を作るのは原則禁止(責務混線の温床)。
|
||||
29
src/mir/builder/vars/assignment_resolver.rs
Normal file
29
src/mir/builder/vars/assignment_resolver.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use super::super::MirBuilder;
|
||||
|
||||
/// AssignmentResolverBox
|
||||
///
|
||||
/// Responsibility:
|
||||
/// - Enforce "explicit local declaration" policy for assignments.
|
||||
/// - Produce consistent diagnostics shared with variable access errors.
|
||||
pub(in crate::mir::builder) struct AssignmentResolverBox;
|
||||
|
||||
impl AssignmentResolverBox {
|
||||
pub(in crate::mir::builder) fn ensure_declared(
|
||||
builder: &MirBuilder,
|
||||
var_name: &str,
|
||||
) -> Result<(), String> {
|
||||
// Compiler-generated temporaries are not part of the user variable namespace.
|
||||
if var_name.starts_with("__pin$") {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if builder.variable_map.contains_key(var_name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut msg = builder.undefined_variable_message(var_name);
|
||||
msg.push_str("\nHint: Nyash requires explicit local declaration. Use `local <name>` before assignment.");
|
||||
Err(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ use std::collections::HashSet;
|
||||
/// Collect free variables used in `node` into `used`, excluding names present in `locals`.
|
||||
/// `locals` is updated as new local declarations are encountered.
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn collect_free_vars(
|
||||
pub(in crate::mir::builder) fn collect_free_vars(
|
||||
node: &ASTNode,
|
||||
used: &mut HashSet<String>,
|
||||
locals: &mut HashSet<String>,
|
||||
75
src/mir/builder/vars/lexical_scope.rs
Normal file
75
src/mir/builder/vars/lexical_scope.rs
Normal file
@ -0,0 +1,75 @@
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(in crate::mir::builder) struct LexicalScopeFrame {
|
||||
declared: BTreeSet<String>,
|
||||
restore: BTreeMap<String, Option<ValueId>>,
|
||||
}
|
||||
|
||||
impl LexicalScopeFrame {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) struct LexicalScopeGuard {
|
||||
builder: *mut super::super::MirBuilder,
|
||||
}
|
||||
|
||||
impl LexicalScopeGuard {
|
||||
pub(in crate::mir::builder) fn new(builder: &mut super::super::MirBuilder) -> Self {
|
||||
builder.push_lexical_scope();
|
||||
Self { builder }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LexicalScopeGuard {
|
||||
fn drop(&mut self) {
|
||||
// Safety: LexicalScopeGuard is created from a unique `&mut MirBuilder` and its lifetime
|
||||
// is bounded by the surrounding lexical scope. Drop runs at most once.
|
||||
unsafe { &mut *self.builder }.pop_lexical_scope();
|
||||
}
|
||||
}
|
||||
|
||||
impl super::super::MirBuilder {
|
||||
pub(in crate::mir::builder) fn push_lexical_scope(&mut self) {
|
||||
self.lexical_scope_stack.push(LexicalScopeFrame::new());
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn pop_lexical_scope(&mut self) {
|
||||
let frame = self
|
||||
.lexical_scope_stack
|
||||
.pop()
|
||||
.expect("COMPILER BUG: pop_lexical_scope without push_lexical_scope");
|
||||
|
||||
for (name, previous) in frame.restore {
|
||||
match previous {
|
||||
Some(prev_id) => {
|
||||
self.variable_map.insert(name, prev_id);
|
||||
}
|
||||
None => {
|
||||
self.variable_map.remove(&name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn declare_local_in_current_scope(
|
||||
&mut self,
|
||||
name: &str,
|
||||
value: ValueId,
|
||||
) -> Result<(), String> {
|
||||
let Some(frame) = self.lexical_scope_stack.last_mut() else {
|
||||
return Err("COMPILER BUG: local declaration outside lexical scope".to_string());
|
||||
};
|
||||
|
||||
if frame.declared.insert(name.to_string()) {
|
||||
let previous = self.variable_map.get(name).copied();
|
||||
frame.restore.insert(name.to_string(), previous);
|
||||
}
|
||||
|
||||
self.variable_map.insert(name.to_string(), value);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
3
src/mir/builder/vars/mod.rs
Normal file
3
src/mir/builder/vars/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub(super) mod assignment_resolver;
|
||||
pub(super) mod free_vars;
|
||||
pub(super) mod lexical_scope;
|
||||
@ -7,4 +7,13 @@
|
||||
- ConditionEnv は「条件で参照する JoinIR ValueId だけ」を持つ。body-local を直接入れず、必要なら昇格+ScopeManager に解決を任せる。
|
||||
- Fail-Fast 原則: Unsupported/NotFound は明示エラーにして、by-name ヒューリスティックや静かなフォールバックは禁止。
|
||||
|
||||
## 名前解決の境界(SSOT)
|
||||
|
||||
このディレクトリの `ScopeManager` は「JoinIR lowering の中で」名前を `ValueId` に解決するための箱だよ。
|
||||
同じ “名前” でも、MIR 側の束縛寿命とは問題が違うので混ぜない。
|
||||
|
||||
- **MIR(SSA/束縛寿命)**: `src/mir/builder/vars/*` が `{...}` のレキシカルスコープと `local` のシャドウイングを管理する。
|
||||
- **JoinIR lowering(この層)**: `ScopeManager` が `ConditionEnv/LoopBodyLocalEnv/CapturedEnv/CarrierInfo` を束ねて解決順序を固定する。
|
||||
- **解析箱**: `LoopConditionScopeBox` は「条件が参照して良いスコープか」を判定する箱で、名前解決そのものはしない。
|
||||
|
||||
詳しい境界ルールは `docs/development/current/main/phase238-exprlowerer-scope-boundaries.md` を参照してね。***
|
||||
|
||||
Reference in New Issue
Block a user