実装内容: - AST Rewriter (~430行): 未宣言変数を __repl.get/set に変換 - ExternCall Bridge: VM で __repl.get/set ハンドラー実装 - Rc<RefCell<>> セッション共有: VM と REPL runner 間で永続化 - 式自動表示: pure expression の結果を自動出力 - _ 変数: 最後の表示値を保存(Void は除外) - .reset 実装: セッション変数の完全クリア - Fail-Fast: 未定義変数読み取りで明示的エラー + ヒント 変更ファイル (8ファイル, +592行): - src/runner/repl/ast_rewriter.rs (NEW, +430行) - src/runner/repl/repl_runner.rs (+84/-35行) - src/backend/mir_interpreter/handlers/externals.rs (+54行) - src/mir/builder/calls/build.rs (+41行) - src/backend/mir_interpreter/mod.rs (+12行) - src/runner/repl/repl_session.rs (+11/-9行) - src/runner/repl/mod.rs (+2行) - src/runner/mod.rs (+2/-1行) REPL専用設計(src/mir/builder/calls/build.rs の特別扱い理由): - __repl.get/set は REPL mode 専用の橋渡し機能 - try_build_repl_method_call() で早期検出・ExternCall 変換 - file mode では決して使用されない(VM で "outside REPL mode" エラー) - 将来的にも file mode への影響ゼロを保証 検証済み: - 変数永続化: x = 42; print(x) → 42 ✅ - 式自動表示: 1 + 1 → 2 ✅ - _ 変数: 10 * 2 → 20; _ → 20 ✅ - Fail-Fast: 未定義エラー + ヒント ✅ - 回帰テスト: 154/154 PASS ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
756 lines
28 KiB
Rust
756 lines
28 KiB
Rust
//! 🎯 箱理論: 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
|
||
);
|
||
}
|
||
|
||
// 0. Phase 285W-Syntax-0.1: Reject weak(...) function call syntax
|
||
// SSOT: docs/reference/language/lifecycle.md - weak <expr> is the ONLY valid syntax
|
||
if name == "weak" {
|
||
eprintln!("[Phase285W-0.1] Rejecting weak(...) function call");
|
||
return Err(format!(
|
||
"Invalid syntax: weak(...). Use unary operator: weak <expr>\n\
|
||
Help: Change 'weak(obj)' to 'weak obj' (unary operator, no parentheses)\n\
|
||
SSOT: docs/reference/language/lifecycle.md"
|
||
));
|
||
}
|
||
|
||
// 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> {
|
||
self.trace_method_call_if_enabled(&object, &method);
|
||
|
||
// 0. Dev-only: __mir__.log / __mir__.mark → MirInstruction::DebugLog へ直接 lowering
|
||
if let Some(result) = self.try_build_mir_debug_method_call(&object, &method, &arguments)? {
|
||
return Ok(result);
|
||
}
|
||
|
||
// Phase 288.1: REPL session variable bridge: __repl.get/set → ExternCall
|
||
if let Some(result) = self.try_build_repl_method_call(&object, &method, &arguments)? {
|
||
return Ok(result);
|
||
}
|
||
|
||
// 1. Static box method call: BoxName.method(args)
|
||
if let Some(result) = self.try_build_static_receiver_method_call(&object, &method, &arguments)? {
|
||
return Ok(result);
|
||
}
|
||
|
||
// 2. Handle env.* methods
|
||
if let Some(res) = self.try_handle_env_method(&object, &method, &arguments) {
|
||
return res;
|
||
}
|
||
|
||
// 3. Phase 269 P1.2: ReceiverNormalizeBox - MethodCall 共通入口 SSOT
|
||
if let Some(result) = self.try_normalize_this_me_method_call(&object, &method, &arguments)? {
|
||
return Ok(result);
|
||
}
|
||
|
||
// 4. Build object value
|
||
let object_value = self.build_expression(object.clone())?;
|
||
|
||
// Phase 287 P4: Debug object value after build_expression
|
||
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
|
||
eprintln!("[P287-DEBUG] After build_expression: object_value={:?}", object_value);
|
||
}
|
||
|
||
// 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)
|
||
}
|
||
|
||
fn trace_method_call_if_enabled(&self, object: &ASTNode, method: &str) {
|
||
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() != Some("1") {
|
||
return;
|
||
}
|
||
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);
|
||
}
|
||
|
||
fn try_build_mir_debug_method_call(
|
||
&mut self,
|
||
object: &ASTNode,
|
||
method: &str,
|
||
arguments: &[ASTNode],
|
||
) -> Result<Option<ValueId>, String> {
|
||
let ASTNode::Variable { name: obj_name, .. } = object else {
|
||
return Ok(None);
|
||
};
|
||
if obj_name != "__mir__" {
|
||
return Ok(None);
|
||
}
|
||
self.try_build_mir_debug_call(method, arguments)
|
||
}
|
||
|
||
/// Phase 288.1: REPL session variable bridge
|
||
/// Transform __repl.get/set → ExternCall("__repl", "get/set", args)
|
||
fn try_build_repl_method_call(
|
||
&mut self,
|
||
object: &ASTNode,
|
||
method: &str,
|
||
arguments: &[ASTNode],
|
||
) -> Result<Option<ValueId>, String> {
|
||
let ASTNode::Variable { name: obj_name, .. } = object else {
|
||
return Ok(None);
|
||
};
|
||
if obj_name != "__repl" {
|
||
return Ok(None);
|
||
}
|
||
|
||
// Only handle get/set methods
|
||
if method != "get" && method != "set" {
|
||
return Err(format!("__repl.{} is not supported. Only __repl.get and __repl.set are allowed.", method));
|
||
}
|
||
|
||
// Build argument values
|
||
let arg_values = self.build_call_args(arguments)?;
|
||
|
||
// Emit ExternCall instruction
|
||
let dst = self.next_value_id();
|
||
self.emit_instruction(MirInstruction::ExternCall {
|
||
dst: Some(dst),
|
||
iface_name: "__repl".to_string(),
|
||
method_name: method.to_string(),
|
||
args: arg_values,
|
||
effects: EffectMask::PURE, // get/set are pure from MIR perspective
|
||
})?;
|
||
|
||
Ok(Some(dst))
|
||
}
|
||
|
||
fn try_build_static_receiver_method_call(
|
||
&mut self,
|
||
object: &ASTNode,
|
||
method: &str,
|
||
arguments: &[ASTNode],
|
||
) -> Result<Option<ValueId>, String> {
|
||
let ASTNode::Variable { name: obj_name, .. } = object else {
|
||
return Ok(None);
|
||
};
|
||
|
||
// Phase 287 P4: Fix toString() method resolution bug
|
||
// Guard: If this is a local variable, don't treat as static box name
|
||
let is_local_var = self.variable_ctx.variable_map.contains_key(obj_name);
|
||
|
||
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
|
||
eprintln!("[P287-DEBUG] try_build_static_receiver_method_call: obj_name={}, method={}, is_local_var={}", obj_name, method, is_local_var);
|
||
eprintln!("[P287-DEBUG] variable_map keys: {:?}", self.variable_ctx.variable_map.keys().collect::<Vec<_>>());
|
||
}
|
||
|
||
if is_local_var {
|
||
// This is a variable reference (primitive or box instance), not a static box name
|
||
// Let it flow through to handle_standard_method_call (line 147 in build_method_call_impl)
|
||
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
|
||
eprintln!("[P287-DEBUG] -> Returning None (local var, will use method call)");
|
||
}
|
||
return Ok(None);
|
||
}
|
||
|
||
// Only treat as static box method call if obj_name is NOT a local variable
|
||
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
|
||
eprintln!("[P287-DEBUG] -> Calling try_build_static_method_call (not a local var)");
|
||
}
|
||
self.try_build_static_method_call(obj_name, method, arguments)
|
||
}
|
||
|
||
fn try_normalize_this_me_method_call(
|
||
&mut self,
|
||
object: &ASTNode,
|
||
method: &str,
|
||
arguments: &[ASTNode],
|
||
) -> Result<Option<ValueId>, String> {
|
||
if !matches!(object, ASTNode::This { .. } | ASTNode::Me { .. }) {
|
||
return Ok(None);
|
||
}
|
||
|
||
// Priority 1: static box → compile-time static call normalization
|
||
if let Some(box_name) = self.comp_ctx.current_static_box.clone() {
|
||
if std::env::var("NYASH_TRACE_NORMALIZE").is_ok() {
|
||
eprintln!(
|
||
"[trace:normalize] this.{}() → {}.{}() (static call)",
|
||
method, box_name, method
|
||
);
|
||
}
|
||
// this.method(args) → current_static_box.method/arity(args)
|
||
return Ok(Some(self.handle_static_method_call(
|
||
&box_name, method, arguments,
|
||
)?));
|
||
}
|
||
|
||
// Instance method fallback (requires variable_map["me"])
|
||
self.handle_me_method_call(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") {
|
||
let trace = crate::mir::builder::control_flow::joinir::trace::trace();
|
||
trace.stderr_if(
|
||
&format!(
|
||
"[DEBUG] try_build_static_method_call: obj_name={}, method={}",
|
||
obj_name, method
|
||
),
|
||
true,
|
||
);
|
||
trace.stderr_if(&format!("[DEBUG] is_local_var={}", is_local_var), true);
|
||
if is_local_var {
|
||
trace.stderr_if(
|
||
&format!(
|
||
"[DEBUG] variable_map contains '{}' - treating as local variable, will use method call",
|
||
obj_name
|
||
),
|
||
true,
|
||
);
|
||
trace.stderr_if(
|
||
&format!(
|
||
"[DEBUG] variable_map keys: {:?}",
|
||
self.variable_ctx.variable_map.keys().collect::<Vec<_>>()
|
||
),
|
||
true,
|
||
);
|
||
} else {
|
||
trace.stderr_if(
|
||
&format!(
|
||
"[DEBUG] '{}' not in variable_map - treating as static box, will use global call",
|
||
obj_name
|
||
),
|
||
true,
|
||
);
|
||
}
|
||
}
|
||
|
||
// 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 {
|
||
let trace = crate::mir::builder::control_flow::joinir::trace::trace();
|
||
trace.stderr_if(
|
||
&format!(
|
||
"[DEBUG/param-recv] build_method_call receiver '{}' → ValueId({})",
|
||
name, object_value.0
|
||
),
|
||
true,
|
||
);
|
||
if let Some(origin) = self.type_ctx.value_origin_newbox.get(&object_value) {
|
||
trace.stderr_if(&format!("[DEBUG/param-recv] origin: {}", origin), true);
|
||
}
|
||
if let Some(&mapped_id) = self.variable_ctx.variable_map.get(name) {
|
||
trace.stderr_if(
|
||
&format!(
|
||
"[DEBUG/param-recv] variable_map['{}'] = ValueId({})",
|
||
name, mapped_id.0
|
||
),
|
||
true,
|
||
);
|
||
if mapped_id != object_value {
|
||
trace.stderr_if(
|
||
"[DEBUG/param-recv] ⚠️ MISMATCH! build_expression returned different ValueId!",
|
||
true,
|
||
);
|
||
}
|
||
} else {
|
||
trace.stderr_if(
|
||
&format!(
|
||
"[DEBUG/param-recv] ⚠️ '{}' NOT FOUND in variable_map!",
|
||
name
|
||
),
|
||
true,
|
||
);
|
||
}
|
||
trace.stderr_if(
|
||
&format!("[DEBUG/param-recv] current_block: {:?}", self.current_block),
|
||
true,
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|