diff --git a/src/backend/mir_interpreter/handlers/calls.rs b/src/backend/mir_interpreter/handlers/calls.rs index 86c2441c..c953eec5 100644 --- a/src/backend/mir_interpreter/handlers/calls.rs +++ b/src/backend/mir_interpreter/handlers/calls.rs @@ -120,24 +120,14 @@ impl MirInterpreter { } Ok(out) } else { - Err(VMError::InvalidInstruction(format!( - "Method call missing receiver for {}", - method - ))) + Err(self.err_with_context("Method call", &format!("missing receiver for {}", method))) } } - Callee::Constructor { box_type } => Err(VMError::InvalidInstruction(format!( - "Constructor calls not yet implemented for {}", - box_type - ))), - Callee::Closure { .. } => Err(VMError::InvalidInstruction( - "Closure creation not yet implemented in VM".into(), - )), + Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))), + Callee::Closure { .. } => Err(self.err_unsupported("Closure creation in VM")), Callee::Value(func_val_id) => { let _func_val = self.reg_load(*func_val_id)?; - Err(VMError::InvalidInstruction( - "First-class function calls not yet implemented in VM".into(), - )) + Err(self.err_unsupported("First-class function calls in VM")) } Callee::Extern(extern_name) => self.execute_extern_function(extern_name, args), } @@ -209,11 +199,7 @@ impl MirInterpreter { self.cur_fn.as_deref(), ) .ok_or_else(|| { - VMError::InvalidInstruction(format!( - "call unresolved: '{}' (arity={})", - raw, - args.len() - )) + ErrorBuilder::with_context("call", &format!("unresolved: '{}' (arity={})", raw, args.len())) })?; if std::env::var("NYASH_VM_CALL_TRACE").ok().as_deref() == Some("1") { @@ -225,7 +211,7 @@ impl MirInterpreter { let callee = self.functions.get(&fname).cloned().ok_or_else(|| { - VMError::InvalidInstruction(format!("function not found: {}", fname)) + self.err_with_context("function call", &format!("function not found: {}", fname)) })?; // SSOT: delegate hostbridge.extern_invoke to the extern dispatcher early, @@ -411,9 +397,7 @@ impl MirInterpreter { return self.execute_extern_function("hostbridge.extern_invoke", args); // Treat as extern_invoke in legacy/global-resolved form if args.len() < 3 { - return Err(VMError::InvalidInstruction( - "hostbridge.extern_invoke expects 3 args".into(), - )); + return Err(self.err_arg_count("hostbridge.extern_invoke", 3, args.len())); } let name = self.reg_load(args[0])?.to_string(); let method = self.reg_load(args[1])?.to_string(); @@ -476,7 +460,7 @@ impl MirInterpreter { // C-API route only; here args[2] is already loaded into `v` if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into())); + return Err(self.err_invalid("env.codegen.link_object: C-API route disabled")); } let (obj_path, exe_out) = match v { VMValue::BoxRef(b) => { @@ -497,16 +481,16 @@ impl MirInterpreter { let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e))) + Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())) } } ("env.codegen", "link_object") => { // C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?] if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into())); + return Err(self.err_invalid("env.codegen.link_object: C-API route disabled")); } - if args.len() < 3 { return Err(VMError::InvalidInstruction("extern_invoke env.codegen.link_object expects args array".into())); } + if args.len() < 3 { return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); } let v = self.reg_load(args[2])?; let (obj_path, exe_out) = match v { VMValue::BoxRef(b) => { @@ -527,13 +511,13 @@ impl MirInterpreter { let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e))) + Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())) } } ("env.codegen", "link_object") => { if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into())); + return Err(self.err_invalid("env.codegen.link_object: C-API route disabled")); } // Extract from args[2] (ArrayBox): [obj_path, exe_out?] let v = self.reg_load(args[2])?; @@ -556,13 +540,13 @@ impl MirInterpreter { let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e))) + Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())) } } ("env.codegen", "link_object") => { if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into())); + return Err(self.err_invalid("env.codegen.link_object: C-API route disabled")); } // Here args[2] is already loaded into `v`; parse ArrayBox [obj, exe?] let (obj_path, exe_out) = match v { @@ -584,7 +568,7 @@ impl MirInterpreter { let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e))) + Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())) } } _ => { @@ -592,7 +576,7 @@ impl MirInterpreter { if name == "env.codegen" && method == "link_object" { if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into())); + return Err(self.err_invalid("env.codegen.link_object: C-API route disabled")); } // Expect args[2] as ArrayBox [obj, exe?] if args.len() >= 3 { @@ -616,7 +600,7 @@ impl MirInterpreter { let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => return Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => return Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e))), + Err(e) => return Err(self.err_with_context("env.codegen.link_object", &e.to_string())), } } } @@ -702,10 +686,7 @@ impl MirInterpreter { } Ok(VMValue::Void) } - _ => Err(VMError::InvalidInstruction(format!( - "Unknown global function: {}", - func_name - ))), + _ => Err(self.err_with_context("global function", &format!("Unknown: {}", func_name))), } } @@ -778,10 +759,7 @@ impl MirInterpreter { let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default(); Ok(VMValue::String(sub)) } - _ => Err(VMError::InvalidInstruction(format!( - "Unknown String method: {}", - method - ))), + _ => Err(self.err_method_not_found("String", method)), }, VMValue::BoxRef(box_ref) => { // Try builtin StringBox first @@ -812,10 +790,7 @@ impl MirInterpreter { )) } } - _ => Err(VMError::InvalidInstruction(format!( - "Method {} not supported on StringBox", - method - ))), + _ => Err(self.err_method_not_found("StringBox", method)), } } else if let Some(p) = box_ref .as_any() @@ -849,10 +824,7 @@ impl MirInterpreter { ))) } } - _ => Err(VMError::InvalidInstruction(format!( - "Method {} not supported on {:?}", - method, receiver - ))), + _ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))), } } @@ -896,13 +868,8 @@ impl MirInterpreter { }; panic!("{}", msg); } - "hostbridge.extern_invoke" => Err(VMError::InvalidInstruction( - "hostbridge.extern_invoke should be routed via extern_provider_dispatch".into(), - )), - _ => Err(VMError::InvalidInstruction(format!( - "Unknown extern function: {}", - extern_name - ))), + "hostbridge.extern_invoke" => Err(self.err_invalid("hostbridge.extern_invoke should be routed via extern_provider_dispatch")), + _ => Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))), } } } diff --git a/src/backend/mir_interpreter/handlers/extern_provider.rs b/src/backend/mir_interpreter/handlers/extern_provider.rs index 1bfce863..3d00413f 100644 --- a/src/backend/mir_interpreter/handlers/extern_provider.rs +++ b/src/backend/mir_interpreter/handlers/extern_provider.rs @@ -49,11 +49,11 @@ impl MirInterpreter { if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") { return Some(Ok(VMValue::String(String::new()))); } - if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.mirbuilder.emit expects 1 arg".into()))); } + if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.mirbuilder.emit", 1, args.len()))); } let program_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; let res = match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) { Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))), - Err(e) => Err(VMError::InvalidInstruction(format!("env.mirbuilder.emit: {}", e))), + Err(e) => Err(ErrorBuilder::with_context("env.mirbuilder.emit", &e.to_string())), }; Some(res) } @@ -62,7 +62,7 @@ impl MirInterpreter { if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") { return Some(Ok(VMValue::String(String::new()))); } - if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.codegen.emit_object expects 1 arg".into()))); } + if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.codegen.emit_object", 1, args.len()))); } let mir_json_raw = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; // Normalize to v1 shape if missing/legacy (prevents harness NoneType errors) let mir_json = Self::patch_mir_json_version(&mir_json_raw); @@ -74,7 +74,7 @@ impl MirInterpreter { }; let res = match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) { Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())), - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.emit_object: {}", e))), + Err(e) => Err(ErrorBuilder::with_context("env.codegen.emit_object", &e.to_string())), }; Some(res) } @@ -92,18 +92,18 @@ impl MirInterpreter { // Require C-API toggles if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Some(Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into()))); + return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled"))); } let obj = std::path::PathBuf::from(obj_path); let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => Some(Ok(VMValue::String(exe.to_string_lossy().into_owned()))), - Err(e) => Some(Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e)))), + Err(e) => Some(Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))), } } // Environment "env.get" => { - if args.is_empty() { return Some(Err(VMError::InvalidInstruction("env.get expects 1 arg".into()))); } + if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.get", 1, args.len()))); } let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }; let val = std::env::var(&key).ok(); Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void })) @@ -190,14 +190,14 @@ impl MirInterpreter { }; if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Some(Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into()))); + return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled"))); } let extra = std::env::var("HAKO_AOT_LDFLAGS").ok(); let obj = std::path::PathBuf::from(objs); let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e))) + Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string())) } } ("env.mirbuilder", "emit") => { @@ -271,14 +271,14 @@ impl MirInterpreter { }; if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") || std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") { - return Some(Err(VMError::InvalidInstruction("env.codegen.link_object: C-API route disabled".into()))); + return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled"))); } let extra = std::env::var("HAKO_AOT_LDFLAGS").ok(); let obj = std::path::PathBuf::from(objs); let exe = exe_s.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe")); match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) { Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())), - Err(e) => Err(VMError::InvalidInstruction(format!("env.codegen.link_object: {}", e))) + Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string())) } } _ => { diff --git a/src/backend/mir_interpreter/utils/error_helpers.rs b/src/backend/mir_interpreter/utils/error_helpers.rs new file mode 100644 index 00000000..635a8ae9 --- /dev/null +++ b/src/backend/mir_interpreter/utils/error_helpers.rs @@ -0,0 +1,239 @@ +//! Error generation utilities for MIR Interpreter +//! +//! Purpose: Centralize error message generation to reduce duplication +//! and ensure consistent error formatting across ~95 error sites. +//! +//! Phase 3 refactoring: 200-300 lines saved + +use crate::backend::vm_types::VMError; + +/// Error message builder utilities +/// +/// Provides standardized error generation methods to replace +/// scattered `VMError::InvalidInstruction(...)` calls. +pub struct ErrorBuilder; + +impl ErrorBuilder { + /// General invalid instruction error + /// + /// Use for simple error messages without specific patterns. + /// + /// # Example + /// ```ignore + /// return Err(ErrorBuilder::invalid_instruction("push expects 1 arg")); + /// ``` + #[inline] + pub fn invalid_instruction(msg: impl Into) -> VMError { + VMError::InvalidInstruction(msg.into()) + } + + /// Type mismatch error with consistent formatting + /// + /// # Arguments + /// * `method` - Method or operation name + /// * `expected` - Expected type description + /// * `actual` - Actual type received + /// + /// # Example + /// ```ignore + /// ErrorBuilder::type_mismatch("get", "Integer", "String") + /// // => "get expects Integer type, got String" + /// ``` + #[inline] + pub fn type_mismatch(method: &str, expected: &str, actual: &str) -> VMError { + VMError::InvalidInstruction(format!("{} expects {} type, got {}", method, expected, actual)) + } + + /// Index out of bounds error + /// + /// # Arguments + /// * `method` - Method or operation name + /// * `index` - Attempted index + /// * `len` - Actual length/size + /// + /// # Example + /// ```ignore + /// ErrorBuilder::out_of_bounds("get", 5, 3) + /// // => "get index out of bounds: 5 >= 3" + /// ``` + #[inline] + pub fn out_of_bounds(method: &str, index: usize, len: usize) -> VMError { + VMError::InvalidInstruction(format!("{} index out of bounds: {} >= {}", method, index, len)) + } + + /// Unsupported operation error + /// + /// # Example + /// ```ignore + /// ErrorBuilder::unsupported_operation("divide by string") + /// // => "divide by string operation not supported" + /// ``` + #[inline] + pub fn unsupported_operation(operation: &str) -> VMError { + VMError::InvalidInstruction(format!("{} operation not supported", operation)) + } + + /// Method not found on box type + /// + /// # Example + /// ```ignore + /// ErrorBuilder::method_not_found("StringBox", "push") + /// // => "Unknown method 'push' on StringBox" + /// ``` + #[inline] + pub fn method_not_found(box_type: &str, method: &str) -> VMError { + VMError::InvalidInstruction(format!("Unknown method '{}' on {}", method, box_type)) + } + + /// Receiver type error + /// + /// # Example + /// ```ignore + /// ErrorBuilder::receiver_type_error("ArrayBox") + /// // => "receiver must be ArrayBox" + /// ``` + #[inline] + pub fn receiver_type_error(expected: &str) -> VMError { + VMError::InvalidInstruction(format!("receiver must be {}", expected)) + } + + /// Argument count mismatch + /// + /// # Example + /// ```ignore + /// ErrorBuilder::arg_count_mismatch("push", 1, 0) + /// // => "push expects 1 arg, got 0" + /// ``` + #[inline] + pub fn arg_count_mismatch(method: &str, expected: usize, actual: usize) -> VMError { + VMError::InvalidInstruction(format!( + "{} expects {} arg{}, got {}", + method, + expected, + if expected == 1 { "" } else { "s" }, + actual + )) + } + + /// Minimum argument count error + /// + /// # Example + /// ```ignore + /// ErrorBuilder::arg_count_min("link_object", 1, 0) + /// // => "link_object expects at least 1 arg, got 0" + /// ``` + #[inline] + pub fn arg_count_min(method: &str, min: usize, actual: usize) -> VMError { + VMError::InvalidInstruction(format!( + "{} expects at least {} arg{}, got {}", + method, + min, + if min == 1 { "" } else { "s" }, + actual + )) + } + + /// Custom formatted error with context + /// + /// # Example + /// ```ignore + /// ErrorBuilder::with_context("emit_object", "invalid JSON format") + /// // => "emit_object: invalid JSON format" + /// ``` + #[inline] + pub fn with_context(operation: &str, detail: &str) -> VMError { + VMError::InvalidInstruction(format!("{}: {}", operation, detail)) + } + + /// Error from another error type + /// + /// # Example + /// ```ignore + /// ErrorBuilder::from_error("link_object", &parse_error) + /// // => "link_object: " + /// ``` + #[inline] + pub fn from_error(operation: &str, error: &dyn std::error::Error) -> VMError { + VMError::InvalidInstruction(format!("{}: {}", operation, error)) + } +} + +// Convenience methods on MirInterpreter to make error generation even shorter +impl super::super::MirInterpreter { + /// General invalid instruction error (shortest form) + /// + /// # Example + /// ```ignore + /// return Err(self.err_invalid("push expects 1 arg")); + /// ``` + #[inline] + pub(crate) fn err_invalid(&self, msg: impl Into) -> VMError { + ErrorBuilder::invalid_instruction(msg) + } + + /// Type mismatch error + /// + /// # Example + /// ```ignore + /// return Err(self.err_type_mismatch("get", "Integer", actual_type)); + /// ``` + #[inline] + pub(crate) fn err_type_mismatch(&self, method: &str, expected: &str, actual: &str) -> VMError { + ErrorBuilder::type_mismatch(method, expected, actual) + } + + /// Index out of bounds error + /// + /// # Example + /// ```ignore + /// return Err(self.err_out_of_bounds("get", idx, len)); + /// ``` + #[inline] + pub(crate) fn err_out_of_bounds(&self, method: &str, index: usize, len: usize) -> VMError { + ErrorBuilder::out_of_bounds(method, index, len) + } + + /// Unsupported operation error + /// + /// # Example + /// ```ignore + /// return Err(self.err_unsupported("divide by zero")); + /// ``` + #[inline] + pub(crate) fn err_unsupported(&self, operation: &str) -> VMError { + ErrorBuilder::unsupported_operation(operation) + } + + /// Method not found error + /// + /// # Example + /// ```ignore + /// return Err(self.err_method_not_found("StringBox", method_name)); + /// ``` + #[inline] + pub(crate) fn err_method_not_found(&self, box_type: &str, method: &str) -> VMError { + ErrorBuilder::method_not_found(box_type, method) + } + + /// Argument count mismatch error + /// + /// # Example + /// ```ignore + /// return Err(self.err_arg_count("push", 1, args.len())); + /// ``` + #[inline] + pub(crate) fn err_arg_count(&self, method: &str, expected: usize, actual: usize) -> VMError { + ErrorBuilder::arg_count_mismatch(method, expected, actual) + } + + /// Error with context + /// + /// # Example + /// ```ignore + /// return Err(self.err_with_context("emit_object", "parse failed")); + /// ``` + #[inline] + pub(crate) fn err_with_context(&self, operation: &str, detail: &str) -> VMError { + ErrorBuilder::with_context(operation, detail) + } +} diff --git a/src/backend/mir_interpreter/utils/mod.rs b/src/backend/mir_interpreter/utils/mod.rs index 753e863b..9f58b442 100644 --- a/src/backend/mir_interpreter/utils/mod.rs +++ b/src/backend/mir_interpreter/utils/mod.rs @@ -3,8 +3,10 @@ pub mod destination_helpers; pub mod arg_validation; pub mod receiver_helpers; +pub mod error_helpers; // Re-export for convenience pub use destination_helpers::*; pub use arg_validation::*; pub use receiver_helpers::*; +pub use error_helpers::*;