MIR: lexical scoping + builder vars modules
This commit is contained in:
@ -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;
|
||||
Reference in New Issue
Block a user