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>
234 lines
7.9 KiB
Rust
234 lines
7.9 KiB
Rust
//! 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
|
||
}
|
||
}
|