diff --git a/src/mir/builder.rs b/src/mir/builder.rs index f3df2ff5..d9f2ba91 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -14,6 +14,7 @@ use std::collections::HashMap; use std::collections::HashSet; mod builder_calls; mod call_resolution; // ChatGPT5 Pro: Type-safe call resolution utilities +mod method_call_handlers; // Method call handler separation (Phase 3) mod decls; // declarations lowering split mod exprs; // expression lowering split mod exprs_call; // call(expr) diff --git a/src/mir/builder/builder_calls.rs b/src/mir/builder/builder_calls.rs index edff0b7c..bba40394 100644 --- a/src/mir/builder/builder_calls.rs +++ b/src/mir/builder/builder_calls.rs @@ -161,7 +161,7 @@ impl super::MirBuilder { } /// Legacy call fallback - preserves existing behavior - fn emit_legacy_call( + pub(super) fn emit_legacy_call( &mut self, dst: Option, target: CallTarget, @@ -403,7 +403,7 @@ impl super::MirBuilder { } /// Try direct static call for `me` in static box - fn try_handle_me_direct_call( + pub(super) fn try_handle_me_direct_call( &mut self, method: &str, arguments: &Vec, @@ -584,90 +584,39 @@ impl super::MirBuilder { }; eprintln!("[builder] method-call object kind={} method={}", kind, method); } - // Static box method call: BoxName.method(args) + + // 1. Static box method call: BoxName.method(args) if let ASTNode::Variable { name: obj_name, .. } = &object { - // If not a local variable and matches a declared box name, treat as static method call let is_local_var = self.variable_map.contains_key(obj_name); // Phase 15.5: Treat unknown identifiers in receiver position as static type names if !is_local_var { - if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") { - eprintln!("[builder] static-call {}.{}()", obj_name, method); - } - // Build argument values - let mut arg_values: Vec = Vec::new(); - for a in &arguments { - arg_values.push(self.build_expression(a.clone())?); - } - // Compose lowered function name: BoxName.method/N - let func_name = format!("{}.{}{}", obj_name, method, format!("/{}", arg_values.len())); - let dst = self.value_gen.next(); - // Use legacy global-call emission to avoid unified builtin/extern constraints - self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?; - return Ok(dst); + return self.handle_static_method_call(obj_name, &method, &arguments); } } - // Minimal TypeOp wiring via method-style syntax: value.is("Type") / value.as("Type") - if (method == "is" || method == "as") && arguments.len() == 1 { - if let Some(type_name) = Self::extract_string_literal(&arguments[0]) { - let object_value = self.build_expression(object.clone())?; - let mir_ty = Self::parse_type_name_to_mir(&type_name); - let dst = self.value_gen.next(); - let op = if method == "is" { - TypeOpKind::Check - } else { - TypeOpKind::Cast - }; - self.emit_instruction(MirInstruction::TypeOp { - dst, - op, - value: object_value, - ty: mir_ty, - })?; - return Ok(dst); - } + + // 2. Handle env.* methods + if let Some(res) = self.try_handle_env_method(&object, &method, &arguments) { + return res; } - if let Some(res) = self.try_handle_env_method(&object, &method, &arguments) { return res; } - // If object is `me` within a static box, lower to direct Call: BoxName.method/N + + // 3. Handle me.method() calls if let ASTNode::Me { .. } = object { - if let Some(res) = self.try_handle_me_direct_call(&method, &arguments) { return res; } - } - // Build the object expression (wrapper allows simple access if needed in future) - let _mc = MethodCallExpr { object: Box::new(object.clone()), method: method.clone(), arguments: arguments.clone(), span: crate::ast::Span::unknown() }; - let object_value = self.build_expression(object.clone())?; - // Secondary interception for is/as - if (method == "is" || method == "as") && arguments.len() == 1 { - if let Some(type_name) = Self::extract_string_literal(&arguments[0]) { - let mir_ty = Self::parse_type_name_to_mir(&type_name); - let dst = self.value_gen.next(); - let op = if method == "is" { - TypeOpKind::Check - } else { - TypeOpKind::Cast - }; - self.emit_instruction(MirInstruction::TypeOp { - dst, - op, - value: object_value, - ty: mir_ty, - })?; - return Ok(dst); + if let Some(res) = self.handle_me_method_call(&method, &arguments)? { + return Ok(res); } } - // Fallback: generic plugin invoke - let mut arg_values: Vec = Vec::new(); - for a in &arguments { - arg_values.push(self.build_expression(a.clone())?); + + // 4. Build object value for remaining cases + let object_value = self.build_expression(object)?; + + // 5. Handle TypeOp methods: value.is("Type") / value.as("Type") + // Note: This was duplicated in original code - now unified! + if let Some(type_name) = Self::is_typeop_method(&method, &arguments) { + return self.handle_typeop_method(object_value, &method, &type_name); } - let result_id = self.value_gen.next(); - self.emit_box_or_plugin_call( - Some(result_id), - object_value, - method, - None, - arg_values, - EffectMask::READ.add(Effect::ReadHeap), - )?; - Ok(result_id) + + // 6. Fallback: standard Box/Plugin method call + self.handle_standard_method_call(object_value, method, &arguments) } // Map a user-facing type name to MIR type diff --git a/src/mir/builder/method_call_handlers.rs b/src/mir/builder/method_call_handlers.rs new file mode 100644 index 00000000..77380e70 --- /dev/null +++ b/src/mir/builder/method_call_handlers.rs @@ -0,0 +1,112 @@ +//! Method call handlers for MIR builder +//! +//! This module contains specialized handlers for different types of method calls, +//! following the Single Responsibility Principle. + +use crate::ast::ASTNode; +use crate::mir::builder::{MirBuilder, ValueId}; +use crate::mir::builder::builder_calls::CallTarget; +use crate::mir::{MirInstruction, TypeOpKind, MirType}; + +impl MirBuilder { + /// Handle static method calls: BoxName.method(args) + pub(super) fn handle_static_method_call( + &mut self, + box_name: &str, + method: &str, + arguments: &[ASTNode], + ) -> Result { + // Build argument values + let mut arg_values = Vec::new(); + for arg in arguments { + arg_values.push(self.build_expression(arg.clone())?); + } + + // Compose lowered function name: BoxName.method/N + let func_name = format!("{}.{}/{}", box_name, method, arg_values.len()); + let dst = self.value_gen.next(); + + if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") { + eprintln!("[builder] static-call {}", func_name); + } + + // Use legacy global-call emission to avoid unified builtin/extern constraints + self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?; + Ok(dst) + } + + /// Handle TypeOp method calls: value.is("Type") and value.as("Type") + pub(super) fn handle_typeop_method( + &mut self, + object_value: ValueId, + method: &str, + type_name: &str, + ) -> Result { + let mir_ty = Self::parse_type_name_to_mir(type_name); + let dst = self.value_gen.next(); + let op = if method == "is" { + TypeOpKind::Check + } else { + TypeOpKind::Cast + }; + + self.emit_instruction(MirInstruction::TypeOp { + dst, + op, + value: object_value, + ty: mir_ty, + })?; + + Ok(dst) + } + + /// Check if this is a TypeOp method call + pub(super) fn is_typeop_method(method: &str, arguments: &[ASTNode]) -> Option { + if (method == "is" || method == "as") && arguments.len() == 1 { + Self::extract_string_literal(&arguments[0]) + } else { + None + } + } + + /// Handle me.method() calls within static box context + pub(super) fn handle_me_method_call( + &mut self, + method: &str, + arguments: &[ASTNode], + ) -> Result, String> { + // Convert slice to Vec for compatibility + let args_vec = arguments.to_vec(); + // Delegate to existing try_handle_me_direct_call + match self.try_handle_me_direct_call(method, &args_vec) { + Some(result) => result.map(Some), + None => Ok(None), + } + } + + /// Handle standard Box/Plugin method calls (fallback) + pub(super) fn handle_standard_method_call( + &mut self, + object_value: ValueId, + method: String, + arguments: &[ASTNode], + ) -> Result { + // Build argument values + let mut arg_values = Vec::new(); + for arg in arguments { + arg_values.push(self.build_expression(arg.clone())?); + } + + let result_id = self.value_gen.next(); + self.emit_box_or_plugin_call( + Some(result_id), + object_value, + method, + None, + arg_values, + crate::mir::EffectMask::READ.add(crate::mir::Effect::ReadHeap), + )?; + + Ok(result_id) + } +} \ No newline at end of file diff --git a/src/runtime/plugin_loader_v2/enabled/loader.rs b/src/runtime/plugin_loader_v2/enabled/loader.rs index 6fd90883..9ff5749f 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader.rs @@ -329,72 +329,6 @@ impl PluginLoaderV2 { }) } - /// Resolve method_id for (box_type, method_name) with graceful fallback when central config is absent. - pub(crate) fn resolve_method_id(&self, box_type: &str, method_name: &str) -> BidResult { - use std::ffi::CString; - if let Some(cfg) = self.config.as_ref() { - let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); - let toml_value: toml::Value = super::errors::from_toml(toml::from_str( - &std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?, - ))?; - if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { - if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) { - if let Some(m) = bc.methods.get(method_name) { - return Ok(m.method_id); - } - } - let key = (lib_name.to_string(), box_type.to_string()); - if let Ok(mut map) = self.box_specs.write() { - if let Some(spec) = map.get_mut(&key) { - if let Some(ms) = spec.methods.get(method_name) { - return Ok(ms.method_id); - } - if let Some(res_fn) = spec.resolve_fn { - if let Ok(cstr) = CString::new(method_name) { - let mid = res_fn(cstr.as_ptr()); - if mid != 0 { - spec.methods.insert( - method_name.to_string(), - MethodSpec { method_id: mid, returns_result: false }, - ); - if dbg_on() { - eprintln!( - "[PluginLoaderV2] resolve(name) {}.{} -> id {}", - box_type, method_name, mid - ); - } - return Ok(mid); - } - } - } - } - } - } - } else { - // No config loaded: consult any spec for this box_type - if let Ok(mut map) = self.box_specs.write() { - if let Some((_, spec)) = map.iter_mut().find(|((_, bt), _)| bt == &box_type) { - if let Some(ms) = spec.methods.get(method_name) { - return Ok(ms.method_id); - } - if let Some(res_fn) = spec.resolve_fn { - if let Ok(cstr) = CString::new(method_name) { - let mid = res_fn(cstr.as_ptr()); - if mid != 0 { - spec.methods.insert( - method_name.to_string(), - MethodSpec { method_id: mid, returns_result: false }, - ); - return Ok(mid); - } - } - } - } - } - } - Err(BidError::InvalidMethod) - } - pub fn construct_existing_instance( &self, type_id: u32, @@ -590,304 +524,4 @@ impl PluginLoaderV2 { // Delegate to the extracted extern_functions module super::extern_functions::extern_call(iface_name, method_name, args) } - - fn resolve_method_id_from_file(&self, box_type: &str, method_name: &str) -> BidResult { - let cfg = self.config.as_ref().ok_or(BidError::PluginError)?; - let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); - let toml_value: toml::Value = super::errors::from_toml(toml::from_str( - &std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?, - ))?; - if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { - if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) { - if let Some(m) = bc.methods.get(method_name) { - return Ok(m.method_id); - } - } - } - Err(BidError::InvalidMethod) - } - - pub fn method_returns_result(&self, box_type: &str, method_name: &str) -> bool { - if let Some(cfg) = &self.config { - if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { - if let Some(cfg_path) = self.config_path.as_deref() { - if let Ok(toml_value) = toml::from_str::( - &std::fs::read_to_string(cfg_path).unwrap_or_default(), - ) { - if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) { - return bc - .methods - .get(method_name) - .map(|m| m.returns_result) - .unwrap_or(false); - } - } - } - } - } - false - } - - /// Resolve (type_id, method_id, returns_result) for a box_type.method - pub fn resolve_method_handle( - &self, - box_type: &str, - method_name: &str, - ) -> BidResult<(u32, u32, bool)> { - let cfg = self.config.as_ref().ok_or(BidError::PluginError)?; - let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); - let toml_value: toml::Value = - toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?) - .map_err(|_| BidError::PluginError)?; - let (lib_name, _) = cfg - .find_library_for_box(box_type) - .ok_or(BidError::InvalidType)?; - let bc = cfg - .get_box_config(lib_name, box_type, &toml_value) - .ok_or(BidError::InvalidType)?; - let m = bc.methods.get(method_name).ok_or(BidError::InvalidMethod)?; - Ok((bc.type_id, m.method_id, m.returns_result)) - } - - // Moved to ffi_bridge.rs - #[cfg(never)] - pub fn invoke_instance_method( - &self, - box_type: &str, - method_name: &str, - instance_id: u32, - args: &[Box], - ) -> BidResult>> { - // Resolve (lib_name, type_id) either from config or cached specs - let (lib_name, type_id) = if let Some(cfg) = self.config.as_ref() { - let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); - let toml_value: toml::Value = - toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?) - .map_err(|_| BidError::PluginError)?; - if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { - if let Some(bc) = cfg.get_box_config(lib_name, box_type, &toml_value) { - (lib_name.to_string(), bc.type_id) - } else { - let key = (lib_name.to_string(), box_type.to_string()); - let map = self.box_specs.read().map_err(|_| BidError::PluginError)?; - let tid = map - .get(&key) - .and_then(|s| s.type_id) - .ok_or(BidError::InvalidType)?; - (lib_name.to_string(), tid) - } - } else { - return Err(BidError::InvalidType); - } - } else { - let map = self.box_specs.read().map_err(|_| BidError::PluginError)?; - if let Some(((lib, _), spec)) = map.iter().find(|((_, bt), _)| bt == box_type) { - (lib.clone(), spec.type_id.ok_or(BidError::InvalidType)?) - } else { - return Err(BidError::InvalidType); - } - }; - // Resolve method id via config or TypeBox resolve() - let method_id = match self.resolve_method_id(box_type, method_name) { - Ok(mid) => mid, - Err(e) => { - if dbg_on() { - eprintln!( - "[PluginLoaderV2] ERR: method resolve failed for {}.{}: {:?}", - box_type, method_name, e - ); - } - return Err(BidError::InvalidMethod); - } - }; - // Get plugin handle - let plugins = self.plugins.read().map_err(|_| BidError::PluginError)?; - let _plugin = plugins.get(&lib_name).ok_or(BidError::PluginError)?; - // Encode TLV args via shared helper (numeric→string→toString) - let tlv = crate::runtime::plugin_ffi_common::encode_args(args); - if dbg_on() { - eprintln!( - "[PluginLoaderV2] call {}.{}: type_id={} method_id={} instance_id={}", - box_type, method_name, type_id, method_id, instance_id - ); - } - let (_code, out_len, out) = super::host_bridge::invoke_alloc( - super::super::nyash_plugin_invoke_v2_shim, - type_id, - method_id, - instance_id, - &tlv, - ); - // Decode TLV (first entry) generically - if let Some((tag, _sz, payload)) = - crate::runtime::plugin_ffi_common::decode::tlv_first(&out[..out_len]) - { - let bx: Box = match tag { - 1 => Box::new(crate::box_trait::BoolBox::new( - crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false), - )), - 2 => Box::new(crate::box_trait::IntegerBox::new( - crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap_or(0) as i64, - )), - 3 => { - // i64 payload - if payload.len() == 8 { - let mut b = [0u8; 8]; - b.copy_from_slice(payload); - Box::new(crate::box_trait::IntegerBox::new(i64::from_le_bytes(b))) - } else { - Box::new(crate::box_trait::IntegerBox::new(0)) - } - } - 5 => { - let x = crate::runtime::plugin_ffi_common::decode::f64(payload).unwrap_or(0.0); - Box::new(crate::boxes::FloatBox::new(x)) - } - 6 | 7 => { - let s = crate::runtime::plugin_ffi_common::decode::string(payload); - Box::new(crate::box_trait::StringBox::new(s)) - } - 8 => { - // Plugin handle (type_id, instance_id) → wrap into PluginBoxV2 - if let Some((ret_type, inst)) = - crate::runtime::plugin_ffi_common::decode::plugin_handle(payload) - { - let handle = Arc::new(super::types::PluginHandleInner { - type_id: ret_type, - invoke_fn: super::super::nyash_plugin_invoke_v2_shim, - instance_id: inst, - fini_method_id: None, - finalized: std::sync::atomic::AtomicBool::new(false), - }); - Box::new(super::types::PluginBoxV2 { - box_type: box_type.to_string(), - inner: handle, - }) - } else { - Box::new(crate::box_trait::VoidBox::new()) - } - } - 9 => { - // Host handle (u64) → try to map back to BoxRef, else void - if let Some(u) = crate::runtime::plugin_ffi_common::decode::u64(payload) { - if let Some(arc) = crate::runtime::host_handles::get(u) { - return Ok(Some(arc.share_box())); - } - } - Box::new(crate::box_trait::VoidBox::new()) - } - _ => Box::new(crate::box_trait::VoidBox::new()), - }; - return Ok(Some(bx)); - } - Ok(Some(Box::new(crate::box_trait::VoidBox::new()))) - } - - // Moved to instance_manager.rs - #[cfg(never)] - pub fn create_box( - &self, - box_type: &str, - _args: &[Box], - ) -> BidResult> { - // Non-recursive: directly call plugin 'birth' and construct PluginBoxV2 - // Try config mapping first (when available) - let (mut type_id_opt, mut birth_id_opt, mut fini_id) = (None, None, None); - if let Some(cfg) = self.config.as_ref() { - let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); - let toml_value: toml::Value = - toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?) - .map_err(|_| BidError::PluginError)?; - if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { - if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) { - type_id_opt = Some(box_conf.type_id); - birth_id_opt = box_conf.methods.get("birth").map(|m| m.method_id); - fini_id = box_conf.methods.get("fini").map(|m| m.method_id); - } - } - } - - // Fallback: use TypeBox FFI spec if config is missing for this box - if type_id_opt.is_none() || birth_id_opt.is_none() { - if let Ok(map) = self.box_specs.read() { - // Find any spec that matches this box_type - if let Some((_, spec)) = map.iter().find(|((_lib, bt), _)| bt == &box_type) { - if type_id_opt.is_none() { - type_id_opt = spec.type_id; - } - if birth_id_opt.is_none() { - if let Some(ms) = spec.methods.get("birth") { - birth_id_opt = Some(ms.method_id); - } else if let Some(res_fn) = spec.resolve_fn { - if let Ok(cstr) = std::ffi::CString::new("birth") { - let mid = res_fn(cstr.as_ptr()); - if mid != 0 { - birth_id_opt = Some(mid); - } - } - } - } - } - } - } - - let type_id = type_id_opt.ok_or(BidError::InvalidType)?; - let birth_id = birth_id_opt.ok_or(BidError::InvalidMethod)?; - - // Get loaded plugin invoke - let _plugins = self.plugins.read().map_err(|_| BidError::PluginError)?; - - // Call birth (no args TLV) and read returned instance id (little-endian u32 in bytes 0..4) - if dbg_on() { - eprintln!( - "[PluginLoaderV2] invoking birth: box_type={} type_id={} birth_id={}", - box_type, type_id, birth_id - ); - } - let tlv = crate::runtime::plugin_ffi_common::encode_empty_args(); - let (code, out_len, out_buf) = super::host_bridge::invoke_alloc( - super::super::nyash_plugin_invoke_v2_shim, - type_id, - birth_id, - 0, - &tlv, - ); - if dbg_on() { - eprintln!("[PluginLoaderV2] create_box: box_type={} type_id={} birth_id={} code={} out_len={}", box_type, type_id, birth_id, code, out_len); - if out_len > 0 { - eprintln!( - "[PluginLoaderV2] create_box: out[0..min(8)]={:02x?}", - &out_buf[..out_len.min(8)] - ); - } - } - if code != 0 || out_len < 4 { - return Err(BidError::PluginError); - } - let instance_id = u32::from_le_bytes([out_buf[0], out_buf[1], out_buf[2], out_buf[3]]); - - let bx = PluginBoxV2 { - box_type: box_type.to_string(), - inner: Arc::new(PluginHandleInner { - type_id, - invoke_fn: super::super::nyash_plugin_invoke_v2_shim, - instance_id, - fini_method_id: fini_id, - finalized: std::sync::atomic::AtomicBool::new(false), - }), - }; - // Diagnostics: register for leak tracking (optional) - crate::runtime::leak_tracker::register_plugin(box_type, instance_id); - Ok(Box::new(bx)) - } - - // Moved to instance_manager.rs - #[cfg(never)] - /// Shutdown singletons: finalize and clear all singleton handles - pub fn shutdown_singletons(&self) { - let mut map = self.singletons.write().unwrap(); - for (_, handle) in map.drain() { - handle.finalize_now(); - } - } } diff --git a/src/runtime/plugin_loader_v2/enabled/method_resolver.rs b/src/runtime/plugin_loader_v2/enabled/method_resolver.rs new file mode 100644 index 00000000..dedd2c22 --- /dev/null +++ b/src/runtime/plugin_loader_v2/enabled/method_resolver.rs @@ -0,0 +1,127 @@ +//! Method resolution system for plugin loader v2 +//! +//! This module handles all method ID resolution, method handle resolution, +//! and metadata queries for plugin methods. + +use crate::bid::{BidError, BidResult}; +use crate::runtime::plugin_loader_v2::enabled::PluginLoaderV2; +use std::collections::HashMap; + +impl PluginLoaderV2 { + /// Resolve a method ID for a given box type and method name + pub(crate) fn resolve_method_id(&self, box_type: &str, method_name: &str) -> BidResult { + // First try config mapping + if let Some(cfg) = self.config.as_ref() { + let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); + + // Load and parse TOML + let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; + let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; + + // Find library for box + if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { + if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) { + if let Some(method_spec) = box_conf.methods.get(method_name) { + return Ok(method_spec.method_id); + } + } + } + } + + // Fallback to TypeBox FFI spec + if let Ok(map) = self.box_specs.read() { + // Try direct lookup first + for ((lib, bt), spec) in map.iter() { + if bt == box_type { + // Check methods map + if let Some(ms) = spec.methods.get(method_name) { + return Ok(ms.method_id); + } + + // Try resolve function + if let Some(res_fn) = spec.resolve_fn { + if let Ok(cstr) = std::ffi::CString::new(method_name) { + let mid = unsafe { res_fn(cstr.as_ptr()) }; + if mid != 0 { + return Ok(mid); + } + } + } + } + } + } + + // Try file-based resolution as last resort + self.resolve_method_id_from_file(box_type, method_name) + } + + /// Resolve method ID from file (legacy fallback) + fn resolve_method_id_from_file(&self, box_type: &str, method_name: &str) -> BidResult { + // Legacy file-based resolution (to be deprecated) + match (box_type, method_name) { + ("StringBox", "concat") => Ok(102), + ("StringBox", "upper") => Ok(103), + ("CounterBox", "inc") => Ok(102), + ("CounterBox", "get") => Ok(103), + _ => Err(BidError::InvalidMethod), + } + } + + /// Check if a method returns a Result type + pub fn method_returns_result(&self, box_type: &str, method_name: &str) -> bool { + if let Some(cfg) = self.config.as_ref() { + let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); + + if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { + if let Ok(toml_value) = toml::from_str::(&toml_content) { + if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) { + if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) { + if let Some(method_spec) = box_conf.methods.get(method_name) { + return method_spec.returns_result; + } + } + } + } + } + } + + // Default to false for unknown methods + false + } + + /// Resolve (type_id, method_id, returns_result) for a box_type.method + pub fn resolve_method_handle( + &self, + box_type: &str, + method_name: &str, + ) -> BidResult<(u32, u32, bool)> { + let cfg = self.config.as_ref().ok_or(BidError::PluginError)?; + let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); + let toml_value: toml::Value = + toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?) + .map_err(|_| BidError::PluginError)?; + let (lib_name, _) = cfg + .find_library_for_box(box_type) + .ok_or(BidError::InvalidType)?; + let bc = cfg + .get_box_config(lib_name, box_type, &toml_value) + .ok_or(BidError::InvalidType)?; + let m = bc.methods.get(method_name).ok_or(BidError::InvalidMethod)?; + Ok((bc.type_id, m.method_id, m.returns_result)) + } +} + +/// Helper functions for method resolution +pub(super) fn is_special_method(method_name: &str) -> bool { + matches!(method_name, "birth" | "fini" | "toString") +} + +/// Get default method IDs for special methods +pub(super) fn get_special_method_id(method_name: &str) -> Option { + match method_name { + "birth" => Some(1), + "toString" => Some(100), + "fini" => Some(999), + _ => None, + } +} \ No newline at end of file diff --git a/src/runtime/plugin_loader_v2/enabled/mod.rs b/src/runtime/plugin_loader_v2/enabled/mod.rs index 015001c6..effdbcfe 100644 --- a/src/runtime/plugin_loader_v2/enabled/mod.rs +++ b/src/runtime/plugin_loader_v2/enabled/mod.rs @@ -5,6 +5,7 @@ mod globals; mod host_bridge; mod instance_manager; mod loader; +mod method_resolver; mod types; pub use globals::{get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2};