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>
This commit is contained in:
@ -12,9 +12,9 @@
|
||||
and plugin metadata fusion (nyash_box.toml / embedded BID).\
|
||||
*/
|
||||
|
||||
pub mod resolver;
|
||||
pub mod spec;
|
||||
pub mod policy;
|
||||
pub mod errors;
|
||||
pub mod policy;
|
||||
pub mod resolver;
|
||||
pub mod simple_registry;
|
||||
pub mod spec;
|
||||
pub mod ssot_bridge;
|
||||
|
||||
@ -4,4 +4,3 @@
|
||||
pub struct UsingPolicy {
|
||||
pub search_paths: Vec<String>, // from [using.paths]
|
||||
}
|
||||
|
||||
|
||||
@ -25,8 +25,8 @@ pub fn populate_from_toml(
|
||||
for name in candidates.iter() {
|
||||
let p = std::path::Path::new(name);
|
||||
if p.exists() {
|
||||
let txt = std::fs::read_to_string(p)
|
||||
.map_err(|e| UsingError::ReadToml(e.to_string()))?;
|
||||
let txt =
|
||||
std::fs::read_to_string(p).map_err(|e| UsingError::ReadToml(e.to_string()))?;
|
||||
found = Some((txt, p.to_path_buf()));
|
||||
break;
|
||||
}
|
||||
@ -48,8 +48,8 @@ pub fn populate_from_toml(
|
||||
// 3) Fallback: empty content and path
|
||||
Ok(found.unwrap_or((String::new(), std::path::PathBuf::from(""))))
|
||||
}?;
|
||||
let doc = toml::from_str::<toml::Value>(&text)
|
||||
.map_err(|e| UsingError::ParseToml(e.to_string()))?;
|
||||
let doc =
|
||||
toml::from_str::<toml::Value>(&text).map_err(|e| UsingError::ParseToml(e.to_string()))?;
|
||||
let toml_dir = toml_path
|
||||
.parent()
|
||||
.map(|p| p.to_path_buf())
|
||||
@ -59,7 +59,11 @@ pub fn populate_from_toml(
|
||||
if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) {
|
||||
fn visit(prefix: &str, tbl: &toml::value::Table, out: &mut Vec<(String, String)>) {
|
||||
for (k, v) in tbl.iter() {
|
||||
let name = if prefix.is_empty() { k.to_string() } else { format!("{}.{}", prefix, k) };
|
||||
let name = if prefix.is_empty() {
|
||||
k.to_string()
|
||||
} else {
|
||||
format!("{}.{}", prefix, k)
|
||||
};
|
||||
if let Some(s) = v.as_str() {
|
||||
out.push((name, s.to_string()));
|
||||
} else if let Some(t) = v.as_table() {
|
||||
@ -97,15 +101,35 @@ pub fn populate_from_toml(
|
||||
}
|
||||
// named packages: any subtable not paths/aliases is a package
|
||||
for (k, v) in using_tbl.iter() {
|
||||
if k == "paths" || k == "aliases" { continue; }
|
||||
if k == "paths" || k == "aliases" {
|
||||
continue;
|
||||
}
|
||||
if let Some(tbl) = v.as_table() {
|
||||
let kind = tbl.get("kind").and_then(|x| x.as_str()).map(PackageKind::from_str).unwrap_or(PackageKind::Package);
|
||||
let kind = tbl
|
||||
.get("kind")
|
||||
.and_then(|x| x.as_str())
|
||||
.map(PackageKind::from_str)
|
||||
.unwrap_or(PackageKind::Package);
|
||||
// path is required
|
||||
if let Some(path_s) = tbl.get("path").and_then(|x| x.as_str()) {
|
||||
let path = path_s.to_string();
|
||||
let main = tbl.get("main").and_then(|x| x.as_str()).map(|s| s.to_string());
|
||||
let bid = tbl.get("bid").and_then(|x| x.as_str()).map(|s| s.to_string());
|
||||
packages.insert(k.to_string(), UsingPackage { kind, path, main, bid });
|
||||
let main = tbl
|
||||
.get("main")
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|s| s.to_string());
|
||||
let bid = tbl
|
||||
.get("bid")
|
||||
.and_then(|x| x.as_str())
|
||||
.map(|s| s.to_string());
|
||||
packages.insert(
|
||||
k.to_string(),
|
||||
UsingPackage {
|
||||
kind,
|
||||
path,
|
||||
main,
|
||||
bid,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,7 +163,9 @@ pub fn resolve_using_target_common(
|
||||
) -> Result<String, String> {
|
||||
// 1) modules mapping
|
||||
if let Some((_, p)) = modules.iter().find(|(n, _)| n == tgt) {
|
||||
if verbose { eprintln!("[using/resolve] modules '{}' -> '{}'", tgt, p); }
|
||||
if verbose {
|
||||
eprintln!("[using/resolve] modules '{}' -> '{}'", tgt, p);
|
||||
}
|
||||
return Ok(p.clone());
|
||||
}
|
||||
// 2) named packages
|
||||
@ -147,28 +173,43 @@ pub fn resolve_using_target_common(
|
||||
match pkg.kind {
|
||||
PackageKind::Dylib => {
|
||||
let out = format!("dylib:{}", pkg.path);
|
||||
if verbose { eprintln!("[using/resolve] dylib '{}' -> '{}'", tgt, out); }
|
||||
if verbose {
|
||||
eprintln!("[using/resolve] dylib '{}' -> '{}'", tgt, out);
|
||||
}
|
||||
return Ok(out);
|
||||
}
|
||||
PackageKind::Package => {
|
||||
let base = std::path::Path::new(&pkg.path);
|
||||
let out = if let Some(m) = &pkg.main {
|
||||
if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
|
||||
if matches!(
|
||||
base.extension().and_then(|s| s.to_str()),
|
||||
Some("nyash") | Some("hako")
|
||||
) {
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
base.join(m).to_string_lossy().to_string()
|
||||
}
|
||||
} else {
|
||||
if matches!(base.extension().and_then(|s| s.to_str()), Some("nyash") | Some("hako")) {
|
||||
if matches!(
|
||||
base.extension().and_then(|s| s.to_str()),
|
||||
Some("nyash") | Some("hako")
|
||||
) {
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
let leaf = base.file_name().and_then(|s| s.to_str()).unwrap_or(tgt);
|
||||
let hako = base.join(format!("{}.hako", leaf));
|
||||
if hako.exists() { hako.to_string_lossy().to_string() }
|
||||
else { base.join(format!("{}.hako", leaf)).to_string_lossy().to_string() }
|
||||
if hako.exists() {
|
||||
hako.to_string_lossy().to_string()
|
||||
} else {
|
||||
base.join(format!("{}.hako", leaf))
|
||||
.to_string_lossy()
|
||||
.to_string()
|
||||
}
|
||||
}
|
||||
};
|
||||
if verbose { eprintln!("[using/resolve] package '{}' -> '{}'", tgt, out); }
|
||||
if verbose {
|
||||
eprintln!("[using/resolve] package '{}' -> '{}'", tgt, out);
|
||||
}
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
||||
@ -179,26 +220,41 @@ pub fn resolve_using_target_common(
|
||||
let mut cand: Vec<String> = Vec::new();
|
||||
if let Some(dir) = context_dir {
|
||||
let c1 = dir.join(&rel_hako);
|
||||
if c1.exists() { cand.push(c1.to_string_lossy().to_string()); }
|
||||
if c1.exists() {
|
||||
cand.push(c1.to_string_lossy().to_string());
|
||||
}
|
||||
let c2 = dir.join(&rel_ny);
|
||||
if c2.exists() { cand.push(c2.to_string_lossy().to_string()); }
|
||||
if c2.exists() {
|
||||
cand.push(c2.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
for base in using_paths {
|
||||
let p = std::path::Path::new(base);
|
||||
let c1 = p.join(&rel_hako);
|
||||
if c1.exists() { cand.push(c1.to_string_lossy().to_string()); }
|
||||
if c1.exists() {
|
||||
cand.push(c1.to_string_lossy().to_string());
|
||||
}
|
||||
let c2 = p.join(&rel_ny);
|
||||
if c2.exists() { cand.push(c2.to_string_lossy().to_string()); }
|
||||
if c2.exists() {
|
||||
cand.push(c2.to_string_lossy().to_string());
|
||||
}
|
||||
}
|
||||
if cand.is_empty() {
|
||||
if verbose { eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt); }
|
||||
return Err(format!("using: unresolved '{}': searched relative and using.paths", tgt));
|
||||
if verbose {
|
||||
eprintln!("[using] unresolved '{}' (searched: rel+paths)", tgt);
|
||||
}
|
||||
return Err(format!(
|
||||
"using: unresolved '{}': searched relative and using.paths",
|
||||
tgt
|
||||
));
|
||||
}
|
||||
if cand.len() > 1 && strict {
|
||||
return Err(format!("ambiguous using '{}': {}", tgt, cand.join(", ")));
|
||||
}
|
||||
let out = cand.remove(0);
|
||||
if verbose { eprintln!("[using/resolve] '{}' -> '{}'", tgt, out); }
|
||||
if verbose {
|
||||
eprintln!("[using/resolve] '{}' -> '{}'", tgt, out);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
@ -211,12 +267,20 @@ fn load_workspace_modules(
|
||||
let members = workspace_tbl
|
||||
.get("members")
|
||||
.and_then(|v| v.as_array())
|
||||
.ok_or_else(|| UsingError::ParseWorkspaceModule("modules.workspace".into(), "expected members array".into()))?;
|
||||
.ok_or_else(|| {
|
||||
UsingError::ParseWorkspaceModule(
|
||||
"modules.workspace".into(),
|
||||
"expected members array".into(),
|
||||
)
|
||||
})?;
|
||||
|
||||
for entry in members {
|
||||
let raw_path = entry
|
||||
.as_str()
|
||||
.ok_or_else(|| UsingError::ParseWorkspaceModule("modules.workspace".into(), "members must be string paths".into()))?;
|
||||
let raw_path = entry.as_str().ok_or_else(|| {
|
||||
UsingError::ParseWorkspaceModule(
|
||||
"modules.workspace".into(),
|
||||
"members must be string paths".into(),
|
||||
)
|
||||
})?;
|
||||
let module_path = if std::path::Path::new(raw_path).is_absolute() {
|
||||
std::path::PathBuf::from(raw_path)
|
||||
} else {
|
||||
@ -227,10 +291,16 @@ fn load_workspace_modules(
|
||||
.map(|p| p.to_path_buf())
|
||||
.unwrap_or_else(|| nyash_dir.to_path_buf());
|
||||
let module_text = std::fs::read_to_string(&module_path).map_err(|e| {
|
||||
UsingError::ReadWorkspaceModule(module_path.to_string_lossy().to_string(), e.to_string())
|
||||
UsingError::ReadWorkspaceModule(
|
||||
module_path.to_string_lossy().to_string(),
|
||||
e.to_string(),
|
||||
)
|
||||
})?;
|
||||
let module_doc = toml::from_str::<toml::Value>(&module_text).map_err(|e| {
|
||||
UsingError::ParseWorkspaceModule(module_path.to_string_lossy().to_string(), e.to_string())
|
||||
UsingError::ParseWorkspaceModule(
|
||||
module_path.to_string_lossy().to_string(),
|
||||
e.to_string(),
|
||||
)
|
||||
})?;
|
||||
let module_name = module_doc
|
||||
.get("module")
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
//! Simple ModuleRegistry for Phase 1 diagnostics
|
||||
//! Collects published symbols (top-level `static box Name`) from using targets.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Mutex;
|
||||
|
||||
static CACHE: Lazy<Mutex<HashMap<String, HashSet<String>>>> =
|
||||
@ -14,7 +14,9 @@ pub fn suggest_using_for_symbol(symbol: &str) -> Vec<String> {
|
||||
let mut results: Vec<String> = Vec::new();
|
||||
let snap = crate::runtime::modules_registry::snapshot_names_and_strings();
|
||||
let wanted = symbol.trim();
|
||||
if wanted.is_empty() { return results; }
|
||||
if wanted.is_empty() {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (name, path_token) in snap {
|
||||
// Skip builtin/dylib marker tokens
|
||||
@ -31,7 +33,9 @@ pub fn suggest_using_for_symbol(symbol: &str) -> Vec<String> {
|
||||
if let Some(p) = resolve_path(&path_token) {
|
||||
if let Ok(content) = std::fs::read_to_string(&p) {
|
||||
let syms = scan_static_boxes(&content);
|
||||
for s in syms { set.insert(s); }
|
||||
for s in syms {
|
||||
set.insert(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -47,9 +51,15 @@ pub fn suggest_using_for_symbol(symbol: &str) -> Vec<String> {
|
||||
fn resolve_path(token: &str) -> Option<std::path::PathBuf> {
|
||||
let mut p = std::path::PathBuf::from(token);
|
||||
if p.is_relative() {
|
||||
if let Ok(abs) = std::fs::canonicalize(&p) { p = abs; }
|
||||
if let Ok(abs) = std::fs::canonicalize(&p) {
|
||||
p = abs;
|
||||
}
|
||||
}
|
||||
if p.exists() {
|
||||
Some(p)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
if p.exists() { Some(p) } else { None }
|
||||
}
|
||||
|
||||
fn scan_static_boxes(content: &str) -> Vec<String> {
|
||||
@ -58,13 +68,21 @@ fn scan_static_boxes(content: &str) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
for line in content.lines() {
|
||||
let t = line.trim_start();
|
||||
if t.starts_with("//") { continue; }
|
||||
if t.starts_with("//") {
|
||||
continue;
|
||||
}
|
||||
if let Some(rest) = t.strip_prefix("static box ") {
|
||||
let mut name = String::new();
|
||||
for ch in rest.chars() {
|
||||
if ch.is_ascii_alphanumeric() || ch == '_' { name.push(ch); } else { break; }
|
||||
if ch.is_ascii_alphanumeric() || ch == '_' {
|
||||
name.push(ch);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !name.is_empty() {
|
||||
out.push(name);
|
||||
}
|
||||
if !name.is_empty() { out.push(name); }
|
||||
}
|
||||
}
|
||||
out
|
||||
|
||||
@ -39,4 +39,3 @@ pub struct UsingPackage {
|
||||
pub main: Option<String>,
|
||||
pub bid: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
@ -21,10 +21,14 @@ pub struct SsotCtx {
|
||||
/// - Only consults `modules` map (exact match).
|
||||
/// - Does not access filesystem nor invoke Hako VM.
|
||||
pub fn call_using_resolve_ssot(name: &str, ctx: &SsotCtx) -> Option<String> {
|
||||
if name.is_empty() { return None; }
|
||||
if name.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Optional: delegate to Hako resolver when explicitly requested.
|
||||
if std::env::var("HAKO_USING_SSOT_HAKO").ok().as_deref() == Some("1") {
|
||||
if let Some(hit) = call_hako_box(name, ctx) { return Some(hit); }
|
||||
if let Some(hit) = call_hako_box(name, ctx) {
|
||||
return Some(hit);
|
||||
}
|
||||
}
|
||||
// MVP: modules-only
|
||||
ctx.modules.get(name).cloned()
|
||||
@ -72,7 +76,8 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option<String> {
|
||||
let mut tf = match tempfile::Builder::new()
|
||||
.prefix("ny_ssot_")
|
||||
.suffix(".hako")
|
||||
.tempfile() {
|
||||
.tempfile()
|
||||
{
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
if crate::config::env::fail_fast() {
|
||||
@ -85,8 +90,12 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option<String> {
|
||||
let _ = write!(tf, "{}", code);
|
||||
let path = tf.path().to_path_buf();
|
||||
// Resolve nyash binary; Fail-Fast aware fallback
|
||||
let bin = if let Ok(b) = std::env::var("NYASH_BIN") { b } else {
|
||||
if let Ok(p) = std::env::current_exe() { p.to_string_lossy().to_string() } else {
|
||||
let bin = if let Ok(b) = std::env::var("NYASH_BIN") {
|
||||
b
|
||||
} else {
|
||||
if let Ok(p) = std::env::current_exe() {
|
||||
p.to_string_lossy().to_string()
|
||||
} else {
|
||||
if crate::config::env::fail_fast() {
|
||||
eprintln!("[failfast/ssot/nyash-bin] unable to resolve NYASH_BIN/current_exe");
|
||||
panic!("Fail-Fast: cannot resolve nyash binary for SSOT child");
|
||||
@ -97,7 +106,9 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option<String> {
|
||||
|
||||
// Stage‑3 + tolerance (matches smokes wrappers)
|
||||
let mut cmd = Command::new(bin);
|
||||
cmd.arg("--backend").arg("vm").arg(&path)
|
||||
cmd.arg("--backend")
|
||||
.arg("vm")
|
||||
.arg(&path)
|
||||
// Parser/entry tolerances (same as smokes "safe" mode)
|
||||
.env("NYASH_PARSER_STAGE3", "1")
|
||||
.env("HAKO_PARSER_STAGE3", "1")
|
||||
@ -136,5 +147,7 @@ fn call_hako_box(name: &str, ctx: &SsotCtx) -> Option<String> {
|
||||
panic!("Fail-Fast: SSOT child produced empty output");
|
||||
}
|
||||
None
|
||||
} else { Some(s) }
|
||||
} else {
|
||||
Some(s)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user