Phase 12 TypeBox統合ABI設計完了: C ABI + Nyash ABI革命的統合

主な成果:
- TypeBox(型情報をBoxとして扱う)による統合ABI設計
- C ABI + Nyash ABIの完全統合仕様書作成
- 3大AI専門家(Gemini/Codex/ChatGPT5)による検証済み
- ChatGPT5の10個の安全性改善提案を反映
- README.mdのドキュメント更新(全起点から到達可能)

MapBox拡張:
- string型キーサポート(従来のi64に加えて)
- remove/clear/getOr/keysStr/valuesStr/toJson実装
- keys()/values()のランタイムシムサポート(TypeBox待ち)

その他の改善:
- Phase 11.9(文法統一化)ドキュメント追加
- Phase 16(FoldLang)ドキュメント追加
- 非同期タイムアウトテスト追加
- 各種ビルド警告(未使用import等)は次のリファクタリングで対応予定

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Moe Charm
2025-09-02 09:26:09 +09:00
parent da96bcb906
commit de99b40bee
40 changed files with 4017 additions and 1014 deletions

View File

@ -1225,15 +1225,34 @@ impl MirBuilder {
self.current_static_box = Some(box_name.clone());
// Look for the main() method
let out = if let Some(main_method) = methods.get("main") {
if let ASTNode::FunctionDeclaration { body, .. } = main_method {
if let ASTNode::FunctionDeclaration { params, body, .. } = main_method {
// Convert the method body to a Program AST node and lower it
let program_ast = ASTNode::Program {
statements: body.clone(),
span: crate::ast::Span::unknown(),
};
// Bind default parameters if present (e.g., args=[])
// Save current var map; inject defaults; restore after lowering
let saved_var_map = std::mem::take(&mut self.variable_map);
// Prepare defaults for known patterns
for p in params.iter() {
let pid = self.value_gen.next();
// Heuristic: for parameter named "args", create new ArrayBox(); else use Void
if p == "args" {
// new ArrayBox() -> pid
// Emit NewBox for ArrayBox with no args
self.emit_instruction(MirInstruction::NewBox { dst: pid, box_type: "ArrayBox".to_string(), args: vec![] })?;
} else {
self.emit_instruction(MirInstruction::Const { dst: pid, value: ConstValue::Void })?;
}
self.variable_map.insert(p.clone(), pid);
}
// Use existing Program lowering logic
self.build_expression(program_ast)
let lowered = self.build_expression(program_ast);
// Restore variable map
self.variable_map = saved_var_map;
lowered
} else {
Err("main method in static box is not a FunctionDeclaration".to_string())
}
@ -1526,6 +1545,65 @@ impl MirBuilder {
return Ok(dst);
}
}
// ExternCall: env.X.* pattern via field access (e.g., env.future.delay)
if let ASTNode::FieldAccess { object: env_obj, field: env_field, .. } = object.clone() {
if let ASTNode::Variable { name: env_name, .. } = *env_obj {
if env_name == "env" {
// Build args first (extern uses evaluated args only)
let mut arg_values = Vec::new();
for arg in &arguments { arg_values.push(self.build_expression(arg.clone())?); }
match (env_field.as_str(), method.as_str()) {
("future", "delay") => {
let result_id = self.value_gen.next();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(result_id),
iface_name: "env.future".to_string(),
method_name: "delay".to_string(),
args: arg_values,
effects: EffectMask::READ.add(Effect::Io),
})?;
return Ok(result_id);
}
("task", "currentToken") => {
let result_id = self.value_gen.next();
self.emit_instruction(MirInstruction::ExternCall {
dst: Some(result_id),
iface_name: "env.task".to_string(),
method_name: "currentToken".to_string(),
args: arg_values,
effects: EffectMask::READ,
})?;
return Ok(result_id);
}
("task", "cancelCurrent") => {
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.task".to_string(),
method_name: "cancelCurrent".to_string(),
args: arg_values,
effects: EffectMask::IO,
})?;
let void_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_id, value: ConstValue::Void })?;
return Ok(void_id);
}
("console", "log") => {
self.emit_instruction(MirInstruction::ExternCall {
dst: None,
iface_name: "env.console".to_string(),
method_name: "log".to_string(),
args: arg_values,
effects: EffectMask::IO,
})?;
let void_id = self.value_gen.next();
self.emit_instruction(MirInstruction::Const { dst: void_id, value: ConstValue::Void })?;
return Ok(void_id);
}
_ => { /* fallthrough to normal method lowering */ }
}
}
}
}
// ExternCall判定はobjectの変数解決より先に行う未定義変数で落とさない
if let ASTNode::Variable { name: object_name, .. } = object.clone() {
// Build argument expressions first (externはobject自体を使わない)

View File

@ -583,13 +583,14 @@ impl MirBuilder {
pub(super) fn build_await_expression(&mut self, expression: ASTNode) -> Result<ValueId, String> {
// Evaluate the expression (should be a Future)
let future_value = self.build_expression(expression)?;
// Insert checkpoint before await (safepoint)
self.emit_instruction(MirInstruction::Safepoint)?;
// Create result value for the await
let result_id = self.value_gen.next();
// Emit await instruction
self.emit_instruction(MirInstruction::Await { dst: result_id, future: future_value })?;
// Insert checkpoint after await (safepoint)
self.emit_instruction(MirInstruction::Safepoint)?;
Ok(result_id)
}
}

View File

@ -258,6 +258,39 @@ mod tests {
let dump = MirPrinter::new().print_module(&result.module);
assert!(dump.contains("await"), "Expected await in MIR dump. Got:\n{}", dump);
}
#[test]
fn test_await_has_checkpoints() {
use crate::ast::{LiteralValue, Span};
// Build: await 1
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile");
// Verifier should pass (await flanked by safepoints)
assert!(result.verification_result.is_ok(), "Verifier failed for await checkpoints: {:?}", result.verification_result);
let dump = compiler.dump_mir(&result.module);
// Expect at least two safepoints in the function (before/after await)
let sp_count = dump.matches("safepoint").count();
assert!(sp_count >= 2, "Expected >=2 safepoints around await, got {}. Dump:\n{}", sp_count, dump);
}
#[test]
fn test_rewritten_await_still_checkpoints() {
use crate::ast::{LiteralValue, Span};
// Enable rewrite so Await → ExternCall(env.future.await)
std::env::set_var("NYASH_REWRITE_FUTURE", "1");
let ast = ASTNode::AwaitExpression { expression: Box::new(ASTNode::Literal { value: LiteralValue::Integer(1), span: Span::unknown() }), span: Span::unknown() };
let mut compiler = MirCompiler::new();
let result = compiler.compile(ast).expect("compile");
// Verifier should still pass (checkpoint verification includes ExternCall await)
assert!(result.verification_result.is_ok(), "Verifier failed for rewritten await checkpoints: {:?}", result.verification_result);
let dump = compiler.dump_mir(&result.module);
assert!(dump.contains("env.future.await"), "Expected rewritten await extern call. Dump:\n{}", dump);
let sp_count = dump.matches("safepoint").count();
assert!(sp_count >= 2, "Expected >=2 safepoints around rewritten await, got {}. Dump:\n{}", sp_count, dump);
// Cleanup env
std::env::remove_var("NYASH_REWRITE_FUTURE");
}
#[test]
fn test_throw_compilation() {

View File

@ -256,7 +256,8 @@ impl MirVerifier {
if errors.is_empty() { Ok(()) } else { Err(errors) }
}
/// Ensure that each Await instruction is immediately preceded and followed by a checkpoint
/// Ensure that each Await instruction (or ExternCall(env.future.await)) is immediately
/// preceded and followed by a checkpoint.
/// A checkpoint is either MirInstruction::Safepoint or ExternCall("env.runtime", "checkpoint").
fn verify_await_checkpoints(&self, function: &MirFunction) -> Result<(), Vec<VerificationError>> {
use super::MirInstruction as I;
@ -269,7 +270,12 @@ impl MirVerifier {
for (bid, block) in &function.blocks {
let instrs = &block.instructions;
for (idx, inst) in instrs.iter().enumerate() {
if let I::Await { .. } = inst {
let is_await_like = match inst {
I::Await { .. } => true,
I::ExternCall { iface_name, method_name, .. } => iface_name == "env.future" && method_name == "await",
_ => false,
};
if is_await_like {
// Check immediate previous
if idx == 0 || !is_cp(&instrs[idx - 1]) {
errors.push(VerificationError::MissingCheckpointAroundAwait { block: *bid, instruction_index: idx, position: "before" });