Files
hakorune/src/mir/builder/calls/build.rs
nyash-codex e404746612 refactor(mir): Phase 139-P3-B - RoutingDecision を enum 対応 + レガシー削除
- RoutingDecision の missing_caps を Vec<CapabilityTag> に変更(型安全化)
- error_tags は to_tag() メソッドで自動生成
- 全 callsite を enum variant に修正
- capability_tags モジュール(文字列定数群)を完全削除
- 全テスト PASS(型安全性向上を確認)
- フォーマット適用
2025-12-16 07:02:14 +09:00

597 lines
22 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.

//! 🎯 箱理論: Call構築専用モジュール
//!
//! 責務: ASTからCall構築のみ
//! - build_function_call: 関数呼び出し構築
//! - build_method_call: メソッド呼び出し構築
//! - build_from_expression: from式構築
use super::super::{Effect, EffectMask, MirBuilder, MirInstruction, MirType, ValueId};
use super::special_handlers;
use super::CallTarget;
use crate::ast::{ASTNode, LiteralValue};
use crate::mir::TypeOpKind;
impl MirBuilder {
// Build function call: name(args)
pub fn build_function_call(
&mut self,
name: String,
args: Vec<ASTNode>,
) -> Result<ValueId, String> {
// Dev trace
if std::env::var("NYASH_CLI_VERBOSE").ok().as_deref() == Some("1") {
let cur_fun = self
.scope_ctx
.current_function
.as_ref()
.map(|f| f.signature.name.clone())
.unwrap_or_else(|| "<none>".to_string());
eprintln!(
"[builder] function-call name={} static_ctx={} in_fn={}",
name,
self.comp_ctx.current_static_box.as_deref().unwrap_or(""),
cur_fun
);
}
// 1. TypeOp wiring: isType(value, "Type"), asType(value, "Type")
if let Some(result) = self.try_build_typeop_function(&name, &args)? {
return Ok(result);
}
// 2. Math function handling
let raw_args = args.clone();
if let Some(res) = self.try_handle_math_function(&name, raw_args) {
return res;
}
// 3. Build argument values
let arg_values = self.build_call_args(&args)?;
// 4. Special-case: global str(x) → x.str() normalization
if name == "str" && arg_values.len() == 1 {
return self.build_str_normalization(arg_values[0]);
}
// 5. Determine call route (unified vs legacy)
let use_unified = super::call_unified::is_unified_call_enabled()
&& (super::super::call_resolution::is_builtin_function(&name)
|| super::super::call_resolution::is_extern_function(&name));
if !use_unified {
self.build_legacy_function_call(name, arg_values)
} else {
self.build_unified_function_call(name, arg_values)
}
}
// Build method call: object.method(arguments)
pub fn build_method_call(
&mut self,
object: ASTNode,
method: String,
arguments: Vec<ASTNode>,
) -> Result<ValueId, String> {
// Debug: Check recursion depth
const MAX_METHOD_DEPTH: usize = 100;
self.recursion_depth += 1;
if self.recursion_depth > MAX_METHOD_DEPTH {
eprintln!(
"[FATAL] build_method_call recursion depth exceeded {}",
MAX_METHOD_DEPTH
);
eprintln!("[FATAL] Current depth: {}", self.recursion_depth);
eprintln!("[FATAL] Method: {}", method);
return Err(format!(
"build_method_call recursion depth exceeded: {}",
self.recursion_depth
));
}
let result = self.build_method_call_impl(object, method, arguments);
self.recursion_depth -= 1;
result
}
fn build_method_call_impl(
&mut self,
object: ASTNode,
method: String,
arguments: Vec<ASTNode>,
) -> Result<ValueId, String> {
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
let kind = match &object {
ASTNode::Variable { .. } => "Variable",
ASTNode::FieldAccess { .. } => "FieldAccess",
ASTNode::This { .. } => "This",
ASTNode::Me { .. } => "Me",
_ => "Other",
};
eprintln!(
"[builder] method-call object kind={} method={}",
kind, method
);
}
// 0. Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog へ直接 lowering
if let ASTNode::Variable { name: obj_name, .. } = &object {
if obj_name == "__mir__" {
if let Some(result) = self.try_build_mir_debug_call(&method, &arguments)? {
return Ok(result);
}
}
}
// 1. Static box method call: BoxName.method(args)
if let ASTNode::Variable { name: obj_name, .. } = &object {
if let Some(result) =
self.try_build_static_method_call(obj_name, &method, &arguments)?
{
return Ok(result);
}
}
// 2. Handle env.* methods
if let Some(res) = self.try_handle_env_method(&object, &method, &arguments) {
return res;
}
// 3. Handle me.method() calls
if let ASTNode::Me { .. } = object {
if let Some(result) = self.handle_me_method_call(&method, &arguments)? {
return Ok(result);
}
}
// 4. Build object value
let object_value = self.build_expression(object.clone())?;
// Debug trace for receiver
self.trace_receiver_if_enabled(&object, object_value);
// 5. Handle TypeOp methods: value.is("Type") / value.as("Type")
if let Some(type_name) = special_handlers::is_typeop_method(&method, &arguments) {
return self.handle_typeop_method(object_value, &method, &type_name);
}
// 6. Fallback: standard Box/Plugin method call
self.handle_standard_method_call(object_value, method, &arguments)
}
// Build from expression: from Parent.method(arguments)
pub fn build_from_expression(
&mut self,
parent: String,
method: String,
arguments: Vec<ASTNode>,
) -> Result<ValueId, String> {
let mut arg_values = Vec::new();
for arg in arguments {
arg_values.push(self.build_expression(arg)?);
}
let parent_value = crate::mir::builder::emission::constant::emit_string(self, parent);
let result_id = self.next_value_id();
self.emit_box_or_plugin_call(
Some(result_id),
parent_value,
method,
None,
arg_values,
EffectMask::READ.add(Effect::ReadHeap),
)?;
Ok(result_id)
}
// ========================================
// Private helper methods (small functions)
// ========================================
/// Try build TypeOp function calls (isType, asType)
fn try_build_typeop_function(
&mut self,
name: &str,
args: &[ASTNode],
) -> Result<Option<ValueId>, String> {
if (name == "isType" || name == "asType") && args.len() == 2 {
if let Some(type_name) = special_handlers::extract_string_literal(&args[1]) {
let val = self.build_expression(args[0].clone())?;
let ty = special_handlers::parse_type_name_to_mir(&type_name);
let dst = self.next_value_id();
let op = if name == "isType" {
TypeOpKind::Check
} else {
TypeOpKind::Cast
};
self.emit_instruction(MirInstruction::TypeOp {
dst,
op,
value: val,
ty,
})?;
return Ok(Some(dst));
}
}
Ok(None)
}
/// Try handle math.* function in function-style (sin/cos/abs/min/max)
fn try_handle_math_function(
&mut self,
name: &str,
raw_args: Vec<ASTNode>,
) -> Option<Result<ValueId, String>> {
if !special_handlers::is_math_function(name) {
return None;
}
// Build numeric args directly for math.* to preserve f64 typing
let mut math_args: Vec<ValueId> = Vec::new();
for a in raw_args.into_iter() {
match a {
ASTNode::New {
class, arguments, ..
} if class == "FloatBox" && arguments.len() == 1 => {
match self.build_expression(arguments[0].clone()) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
}
}
ASTNode::New {
class, arguments, ..
} if class == "IntegerBox" && arguments.len() == 1 => {
let iv = match self.build_expression(arguments[0].clone()) {
Ok(v) => v,
Err(e) => return Some(Err(e)),
};
let fv = self.next_value_id();
if let Err(e) = self.emit_instruction(MirInstruction::TypeOp {
dst: fv,
op: TypeOpKind::Cast,
value: iv,
ty: MirType::Float,
}) {
return Some(Err(e));
}
math_args.push(fv);
}
ASTNode::Literal {
value: LiteralValue::Float(_),
..
} => match self.build_expression(a) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
},
other => match self.build_expression(other) {
v @ Ok(_) => math_args.push(v.unwrap()),
err @ Err(_) => return Some(err),
},
}
}
// new MathBox()
let math_recv = self.next_value_id();
if let Err(e) = self.emit_constructor_call(math_recv, "MathBox".to_string(), vec![]) {
return Some(Err(e));
}
self.type_ctx
.value_origin_newbox
.insert(math_recv, "MathBox".to_string());
// birth()
if let Err(e) = self.emit_method_call(None, math_recv, "birth".to_string(), vec![]) {
return Some(Err(e));
}
// call method
let dst = self.next_value_id();
if let Err(e) = self.emit_method_call(Some(dst), math_recv, name.to_string(), math_args) {
return Some(Err(e));
}
Some(Ok(dst))
}
/// Try handle env.* extern methods
fn try_handle_env_method(
&mut self,
object: &ASTNode,
method: &str,
arguments: &Vec<ASTNode>,
) -> Option<Result<ValueId, String>> {
let ASTNode::FieldAccess {
object: env_obj,
field: env_field,
..
} = object
else {
return None;
};
if let ASTNode::Variable { name: env_name, .. } = env_obj.as_ref() {
if env_name != "env" {
return None;
}
// Build arguments once
let mut arg_values = Vec::new();
for arg in arguments {
match self.build_expression(arg.clone()) {
Ok(v) => arg_values.push(v),
Err(e) => return Some(Err(e)),
}
}
let iface = env_field.as_str();
let m = method;
let mut extern_call = |iface_name: &str,
method_name: &str,
effects: EffectMask,
returns: bool|
-> Result<ValueId, String> {
let result_id = self.next_value_id();
self.emit_instruction(MirInstruction::ExternCall {
dst: if returns { Some(result_id) } else { None },
iface_name: iface_name.to_string(),
method_name: method_name.to_string(),
args: arg_values.clone(),
effects,
})?;
if returns {
Ok(result_id)
} else {
let void_id = crate::mir::builder::emission::constant::emit_void(self);
Ok(void_id)
}
};
// Use the new module for env method spec
if let Some((iface_name, method_name, effects, returns)) =
super::extern_calls::get_env_method_spec(iface, m)
{
return Some(extern_call(&iface_name, &method_name, effects, returns));
}
return None;
}
None
}
/// Build call arguments from AST
fn build_call_args(&mut self, args: &[ASTNode]) -> Result<Vec<ValueId>, String> {
let mut arg_values = Vec::new();
for a in args {
arg_values.push(self.build_expression(a.clone())?);
}
Ok(arg_values)
}
/// Build str(x) normalization to x.str()
fn build_str_normalization(&mut self, arg: ValueId) -> Result<ValueId, String> {
let dst = self.next_value_id();
// Use unified method emission; downstream rewrite will functionize as needed
self.emit_method_call(Some(dst), arg, "str".to_string(), vec![])?;
Ok(dst)
}
/// Build legacy function call
fn build_legacy_function_call(
&mut self,
name: String,
arg_values: Vec<ValueId>,
) -> Result<ValueId, String> {
let dst = self.next_value_id();
// === ChatGPT5 Pro Design: Type-safe function call resolution ===
let callee = match self.resolve_call_target(&name) {
Ok(c) => c,
Err(_e) => {
// Fallback: unique static method
if let Some(result) = self.try_static_method_fallback(&name, &arg_values)? {
return Ok(result);
}
// Tail-based fallback (disabled by default)
if let Some(result) = self.try_tail_based_fallback(&name, &arg_values)? {
return Ok(result);
}
return Err(format!(
"Unresolved function: '{}'. {}",
name,
super::super::call_resolution::suggest_resolution(&name)
));
}
};
// Legacy compatibility: Create dummy func value for old systems
let fun_val = crate::mir::builder::name_const::make_name_const_result(self, &name)?;
// Emit new-style Call with type-safe callee
self.emit_instruction(MirInstruction::Call {
dst: Some(dst),
func: fun_val,
callee: Some(callee),
args: arg_values,
effects: EffectMask::READ.add(Effect::ReadHeap),
})?;
Ok(dst)
}
/// Build unified function call
fn build_unified_function_call(
&mut self,
name: String,
arg_values: Vec<ValueId>,
) -> Result<ValueId, String> {
let dst = self.next_value_id();
self.emit_unified_call(Some(dst), CallTarget::Global(name), arg_values)?;
Ok(dst)
}
/// Try static method call: BoxName.method(args)
fn try_build_static_method_call(
&mut self,
obj_name: &str,
method: &str,
arguments: &[ASTNode],
) -> Result<Option<ValueId>, String> {
let is_local_var = self.variable_ctx.variable_map.contains_key(obj_name);
// Debug trace
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[DEBUG] try_build_static_method_call: obj_name={}, method={}",
obj_name, method
);
eprintln!("[DEBUG] is_local_var={}", is_local_var);
if is_local_var {
eprintln!("[DEBUG] variable_map contains '{}' - treating as local variable, will use method call", obj_name);
eprintln!(
"[DEBUG] variable_map keys: {:?}",
self.variable_ctx.variable_map.keys().collect::<Vec<_>>()
);
} else {
eprintln!("[DEBUG] '{}' not in variable_map - treating as static box, will use global call", obj_name);
}
}
// Phase 15.5: Treat unknown identifiers in receiver position as static type names
if !is_local_var {
let result = self.handle_static_method_call(obj_name, method, arguments)?;
return Ok(Some(result));
}
Ok(None)
}
/// Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog への変換
///
/// 構文:
/// __mir__.log("label", v1, v2, ...)
/// __mir__.mark("label")
///
/// - 第一引数は String リテラル想定(それ以外はこのハンドラをスキップして通常の解決に回す)。
/// - 戻り値は Void 定数の ValueId式コンテキストでも型破綻しないようにするため
fn try_build_mir_debug_call(
&mut self,
method: &str,
arguments: &[ASTNode],
) -> Result<Option<ValueId>, String> {
if method != "log" && method != "mark" {
return Ok(None);
}
if arguments.is_empty() {
return Err("__mir__.log/__mir__.mark requires at least a label argument".to_string());
}
// 第一引数は String リテラルのみ対応(それ以外は通常経路にフォールバック)
let label = match &arguments[0] {
ASTNode::Literal {
value: LiteralValue::String(s),
..
} => s.clone(),
_ => {
// ラベルがリテラルでない場合はこのハンドラをスキップし、通常の static box 解決に任せる
return Ok(None);
}
};
// 残りの引数を評価して ValueId を集める
let mut values: Vec<ValueId> = Vec::new();
if method == "log" {
for arg in &arguments[1..] {
values.push(self.build_expression(arg.clone())?);
}
}
// MIR に DebugLog 命令を 1 つ挿入(意味論は NYASH_MIR_DEBUG_LOG=1 のときにだけ効く)
self.emit_instruction(MirInstruction::DebugLog {
message: label,
values,
})?;
// 式コンテキスト用に Void 定数を返す(呼び出し元では通常使われない)
let void_id = crate::mir::builder::emission::constant::emit_void(self);
Ok(Some(void_id))
}
/// Try static method fallback (name+arity)
fn try_static_method_fallback(
&mut self,
name: &str,
arg_values: &[ValueId],
) -> Result<Option<ValueId>, String> {
if let Some(cands) = self.comp_ctx.static_method_index.get(name) {
let mut matches: Vec<(String, usize)> = cands
.iter()
.cloned()
.filter(|(_, ar)| *ar == arg_values.len())
.collect();
if matches.len() == 1 {
let (bx, _arity) = matches.remove(0);
let dst = self.next_value_id();
let func_name = format!("{}.{}{}", bx, name, format!("/{}", arg_values.len()));
// Emit unified global call to the lowered static method function
self.emit_unified_call(
Some(dst),
CallTarget::Global(func_name),
arg_values.to_vec(),
)?;
return Ok(Some(dst));
}
}
Ok(None)
}
/// Try tail-based fallback (disabled by default)
fn try_tail_based_fallback(
&mut self,
name: &str,
arg_values: &[ValueId],
) -> Result<Option<ValueId>, String> {
if std::env::var("NYASH_BUILDER_TAIL_RESOLVE").ok().as_deref() == Some("1") {
if let Some(ref module) = self.current_module {
let tail = format!(".{}{}", name, format!("/{}", arg_values.len()));
let mut cands: Vec<String> = module
.functions
.keys()
.filter(|k| k.ends_with(&tail))
.cloned()
.collect();
if cands.len() == 1 {
let func_name = cands.remove(0);
let dst = self.next_value_id();
self.emit_legacy_call(
Some(dst),
CallTarget::Global(func_name),
arg_values.to_vec(),
)?;
return Ok(Some(dst));
}
}
}
Ok(None)
}
/// Debug trace for receiver (if enabled)
fn trace_receiver_if_enabled(&self, object: &ASTNode, object_value: ValueId) {
if std::env::var("NYASH_DEBUG_PARAM_RECEIVER").ok().as_deref() == Some("1") {
if let ASTNode::Variable { name, .. } = object {
eprintln!(
"[DEBUG/param-recv] build_method_call receiver '{}' → ValueId({})",
name, object_value.0
);
if let Some(origin) = self.type_ctx.value_origin_newbox.get(&object_value) {
eprintln!("[DEBUG/param-recv] origin: {}", origin);
}
if let Some(&mapped_id) = self.variable_ctx.variable_map.get(name) {
eprintln!(
"[DEBUG/param-recv] variable_map['{}'] = ValueId({})",
name, mapped_id.0
);
if mapped_id != object_value {
eprintln!("[DEBUG/param-recv] ⚠️ MISMATCH! build_expression returned different ValueId!");
}
} else {
eprintln!(
"[DEBUG/param-recv] ⚠️ '{}' NOT FOUND in variable_map!",
name
);
}
eprintln!(
"[DEBUG/param-recv] current_block: {:?}",
self.current_block
);
}
}
}
}