--- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -32,6 +32,31 @@ out } +fn format_signature(sig: &FunctionSignature) -> String { + let mut out = String::new(); + out.push_str("\nFunction has the following signature: "); + out.push('('); + if sig.is_empty() { + out.push_str("/*no arguments*/"); + } else { + for (i, (name, has_default)) in sig.iter().enumerate() { + if i != 0 { + out.push_str(", "); + } + if let Some(name) = name { + out.push_str(name); + } else { + out.push_str(""); + } + if *has_default { + out.push_str(" = "); + } + } + } + out.push(')'); + out +} + const fn format_empty_str(str: &str) -> &str { if str.is_empty() { "\"\" (empty string)" @@ -40,6 +65,8 @@ } } +type FunctionSignature = Vec<(Option, bool)>; + #[derive(Error, Debug, Clone, Trace)] pub enum Error { #[error("intrinsic not found: {0}")] @@ -84,10 +111,10 @@ UnknownFunctionParameter(String), #[error("argument {0} is already bound")] BindingParameterASecondTime(IStr), - #[error("too many args, function has {0}")] - TooManyArgsFunctionHas(usize), - #[error("function argument is not passed: {0}")] - FunctionParameterNotBoundInCall(IStr), + #[error("too many args, function has {0}{}", format_signature(.1))] + TooManyArgsFunctionHas(usize, FunctionSignature), + #[error("function argument is not passed: {0}{}", format_signature(.1))] + FunctionParameterNotBoundInCall(IStr, FunctionSignature), #[error("external variable is not defined: {0}")] UndefinedExternalVariable(IStr), --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -48,7 +48,10 @@ ) -> Result { let mut passed_args = GcHashMap::with_capacity(params.len()); if args.unnamed_len() > params.len() { - throw!(TooManyArgsFunctionHas(params.len())) + throw!(TooManyArgsFunctionHas( + params.len(), + params.iter().map(|p| (p.0.name(), p.1.is_some())).collect() + )) } let mut filled_named = 0; @@ -126,7 +129,8 @@ .0 .clone() .name() - .unwrap_or_else(|| "".into()) + .unwrap_or_else(|| "".into()), + params.iter().map(|p| (p.0.name(), p.1.is_some())).collect() )); } } @@ -159,7 +163,13 @@ ) -> Result>> { let mut passed_args = GcHashMap::with_capacity(params.len()); if args.unnamed_len() > params.len() { - throw!(TooManyArgsFunctionHas(params.len())) + throw!(TooManyArgsFunctionHas( + params.len(), + params + .iter() + .map(|p| (Some(p.name.as_ref().into()), p.has_default)) + .collect() + )) } let mut filled_args = 0; @@ -202,7 +212,13 @@ } }); if !found { - throw!(FunctionParameterNotBoundInCall(param.name.clone().into())); + throw!(FunctionParameterNotBoundInCall( + param.name.clone().into(), + params + .iter() + .map(|p| (Some(p.name.as_ref().into()), p.has_default)) + .collect() + )); } } unreachable!(); @@ -215,11 +231,15 @@ /// and with unbound values causing error to be returned pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result { #[derive(Trace)] - struct DependsOnUnbound(IStr); + struct DependsOnUnbound(IStr, ParamsDesc); impl ThunkValue for DependsOnUnbound { type Output = Val; fn get(self: Box, _: State) -> Result { - Err(FunctionParameterNotBoundInCall(self.0.clone()).into()) + Err(FunctionParameterNotBoundInCall( + self.0.clone(), + self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(), + ) + .into()) } } @@ -243,7 +263,8 @@ destruct( ¶m.0, Thunk::new(tb!(DependsOnUnbound( - param.0.name().unwrap_or_else(|| "".into()) + param.0.name().unwrap_or_else(|| "".into()), + params.clone() ))), fctx.clone(), &mut bindings,