Files
hakorune/src/runner/modes/common_util/resolve/using_resolution.rs
nyash-codex f9d100ce01 chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更
Phase 25.1 完了成果:
-  LoopForm v2 テスト・ドキュメント・コメント完備
  - 4ケース(A/B/C/D)完全テストカバレッジ
  - 最小再現ケース作成(SSAバグ調査用)
  - SSOT文書作成(loopform_ssot.md)
  - 全ソースに [LoopForm] コメントタグ追加

-  Stage-1 CLI デバッグ環境構築
  - stage1_cli.hako 実装
  - stage1_bridge.rs ブリッジ実装
  - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh)
  - アーキテクチャ改善提案文書

-  環境変数削減計画策定
  - 25変数の完全調査・分類
  - 6段階削減ロードマップ(25→5、80%削減)
  - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG)

Phase 26-D からの累積変更:
- PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等)
- MIRビルダーリファクタリング
- 型伝播・最適化パス改善
- その他約300ファイルの累積変更

🎯 技術的成果:
- SSAバグ根本原因特定(条件分岐内loop変数変更)
- Region+next_iパターン適用完了(UsingCollectorBox等)
- LoopFormパターン文書化・テスト化完了
- セルフホスティング基盤強化

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <noreply@openai.com>
Co-Authored-By: Task Assistant <task@anthropic.com>
2025-11-21 06:25:17 +09:00

234 lines
7.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Using Resolution Box - 綺麗綺麗なusing文解決専門家📦
//!
//! 巨大な `collect_using_and_strip` 関数を箱に分解して、
//! 責務を明確にしてテストしやすくするにゃ!
use crate::runner::NyashRunner;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// 📦 UsingResolutionBox - using文解決の専門家
///
/// using文の解析、パス解決、重複チェックを一手に引き受ける箱にゃ
pub struct UsingResolutionBox<'a> {
runner: &'a NyashRunner,
config: UsingConfig,
ctx_dir: Option<PathBuf>,
#[allow(dead_code)]
filename_canon: Option<PathBuf>,
inside_pkg: bool,
seen_paths: HashMap<String, (String, usize)>, // canon_path -> (alias/label, first_line)
seen_aliases: HashMap<String, (String, usize)>, // alias -> (canon_path, first_line)
}
/// 🎯 UsingTarget - 解析済みusing文の構造体にゃ
#[derive(Debug, Clone)]
pub struct UsingTarget {
pub original: String,
pub target: String,
pub target_unquoted: String,
pub alias: Option<String>,
pub line_no: usize,
pub is_path: bool,
}
/// ⚙️ UsingConfig - using解決の設定
#[derive(Debug)]
pub struct UsingConfig {
pub prod: bool,
pub strict: bool,
pub verbose: bool,
pub allow_file_using: bool,
}
impl<'a> UsingResolutionBox<'a> {
/// 🌟 新しいUsingResolutionBoxを作るにゃ
pub fn new(runner: &'a NyashRunner, filename: &str) -> Result<Self, String> {
let using_ctx = runner.init_using_context();
let config = UsingConfig {
prod: crate::config::env::using_is_prod(),
strict: crate::config::env::env_bool("NYASH_USING_STRICT"),
verbose: crate::config::env::cli_verbose()
|| crate::config::env::env_bool("NYASH_RESOLVE_TRACE"),
allow_file_using: crate::config::env::allow_using_file(),
};
let ctx_dir = Path::new(filename).parent().map(|p| p.to_path_buf());
// ファイルがパッケージ内にあるかチェック
let filename_canon = std::fs::canonicalize(filename).ok();
let mut inside_pkg = false;
if let Some(ref fc) = filename_canon {
for (_name, pkg) in &using_ctx.packages {
let base = Path::new(&pkg.path);
if let Ok(root) = std::fs::canonicalize(base) {
if fc.starts_with(&root) {
inside_pkg = true;
break;
}
}
}
}
Ok(Self {
runner,
config,
ctx_dir,
filename_canon,
inside_pkg,
seen_paths: HashMap::new(),
seen_aliases: HashMap::new(),
})
}
/// 🔍 using文を解析するにゃ
pub fn parse_using_line(&self, line: &str, line_no: usize) -> Option<UsingTarget> {
let t = line.trim_start();
if !t.starts_with("using ") {
return None;
}
crate::cli_v!("[using] stripped line: {}", line);
let rest0 = t.strip_prefix("using ").unwrap().trim();
let rest0 = rest0.split('#').next().unwrap_or(rest0).trim();
let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim();
let (target, alias) = if let Some(pos) = rest0.find(" as ") {
(
rest0[..pos].trim().to_string(),
Some(rest0[pos + 4..].trim().to_string()),
)
} else {
(rest0.to_string(), None)
};
let target_unquoted = target.trim_matches('"').to_string();
let using_ctx = self.runner.init_using_context();
// 既知のエイリアスかモジュールかチェック
let is_known_alias_or_module = using_ctx.aliases.contains_key(&target_unquoted)
|| using_ctx
.pending_modules
.iter()
.any(|(k, _)| k == &target_unquoted)
|| using_ctx.packages.contains_key(&target_unquoted);
let is_path = if is_known_alias_or_module {
false
} else {
crate::runner::modes::common_util::resolve::path_util::is_using_target_path_unquoted(
&target_unquoted,
)
};
Some(UsingTarget {
original: line.to_string(),
target,
target_unquoted,
alias,
line_no,
is_path,
})
}
/// 🚀 パスを解決するにゃ!
pub fn resolve_path(&self, target: &UsingTarget) -> Result<String, String> {
if !target.is_path {
return Err("Not a file path".to_string());
}
// ファイルusingチェック
if (self.config.prod || !self.config.allow_file_using) && !self.inside_pkg {
return Err(format!(
"{}:{}: using: file paths are disallowed in this profile. Add it to nyash.toml [using]/[modules] and reference by name: {}\n suggestions: using \"alias.name\" as Name | dev/test: set NYASH_PREINCLUDE=1 to expand includes ahead of VM\n docs: see docs/reference/using.md",
"filename", // TODO: 実際のファイル名を渡す
target.line_no,
target.target
));
}
let path = target.target.trim_matches('"').to_string();
let mut p = PathBuf::from(&path);
// 相対パス解決
if p.is_relative() {
if let Some(dir) = &self.ctx_dir {
let cand = dir.join(&p);
if cand.exists() {
p = cand;
}
}
// NYASH_ROOTも試す
if p.is_relative() {
if let Ok(root) = std::env::var("NYASH_ROOT") {
let cand = Path::new(&root).join(&p);
if cand.exists() {
p = cand;
}
}
}
}
p.to_str()
.ok_or_else(|| "Invalid path".to_string())
.map(|s| s.to_string())
}
/// 🛡️ 重複チェックするにゃ!
pub fn check_duplicates(
&mut self,
target: &UsingTarget,
resolved_path: &str,
) -> Result<(), String> {
let canon_path =
std::fs::canonicalize(resolved_path).unwrap_or_else(|_| PathBuf::from(resolved_path));
let canon_str = canon_path.to_string_lossy();
// パスの重複チェック
if let Some((prev_alias, prev_line)) = self.seen_paths.get(&canon_str.to_string()) {
return Err(format!(
"{}:{}: using: duplicate target (first imported at {}:{})",
"filename", // TODO: 実際のファイル名を渡す
target.line_no,
prev_alias,
prev_line
));
}
// エイリアスの重複チェック
if let Some(ref alias_name) = target.alias {
if let Some((prev_path, prev_line)) = self.seen_aliases.get(alias_name) {
return Err(format!(
"{}:{}: using: duplicate alias '{}' (first used for {} at {})",
"filename", // TODO: 実際のファイル名を渡す
target.line_no,
alias_name,
prev_path,
prev_line
));
}
}
// 記録
let alias_label = target.alias.as_ref().unwrap_or(&target.target).clone();
self.seen_paths
.insert(canon_str.to_string(), (alias_label.clone(), target.line_no));
if let Some(ref alias_name) = target.alias {
self.seen_aliases.insert(
alias_name.clone(),
(resolved_path.to_string(), target.line_no),
);
}
Ok(())
}
/// 📊 設定を取得するにゃ!
pub fn config(&self) -> &UsingConfig {
&self.config
}
}