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:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

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

View File

@ -4,4 +4,3 @@
pub struct UsingPolicy {
pub search_paths: Vec<String>, // from [using.paths]
}

View File

@ -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")

View File

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

View File

@ -39,4 +39,3 @@ pub struct UsingPackage {
pub main: Option<String>,
pub bid: Option<String>,
}

View File

@ -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> {
// Stage3 + 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)
}
}