MIR: lexical scoping + builder vars modules

This commit is contained in:
nyash-codex
2025-12-13 01:30:04 +09:00
parent 5c75506dcc
commit 1fae4f1648
16 changed files with 388 additions and 29 deletions

View File

@ -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,

View File

@ -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();

View 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` は「禁止/許可」「スケジュール」などの補助情報で、束縛そのものではない。
この境界を跨ぐ “便利メソッド” を作るのは原則禁止(責務混線の温床)。

View 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)
}
}

View File

@ -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>,

View 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(())
}
}

View File

@ -0,0 +1,3 @@
pub(super) mod assignment_resolver;
pub(super) mod free_vars;
pub(super) mod lexical_scope;