--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs @@ -12,7 +12,7 @@ }; #[allow(clippy::too_many_lines)] -fn destruct( +pub fn destruct( d: &Destruct, parent: Thunk, new_bindings: &mut GcHashMap>, --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -59,7 +59,7 @@ } impl FuncDesc { /// Create body context, but fill arguments without defaults with lazy error - pub fn default_body_context(&self) -> Context { + pub fn default_body_context(&self) -> Result { parse_default_function_call(self.ctx.clone(), &self.params) } --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -7,6 +7,7 @@ builtin::{BuiltinParam, BuiltinParamName}, }; use crate::{ + destructure::destruct, error::{Error::*, Result}, evaluate_named, gc::GcHashMap, @@ -50,60 +51,77 @@ throw!(TooManyArgsFunctionHas(params.len())) } - let mut filled_args = 0; + let mut filled_named = 0; + let mut filled_positionals = 0; args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| { let name = params[id].0.clone(); - passed_args.insert(name, arg); - filled_args += 1; + destruct(&name, arg, &mut passed_args)?; + filled_positionals += 1; Ok(()) })?; args.named_iter(s, ctx, tailstrict, &mut |name, value| { // FIXME: O(n) for arg existence check - if !params.iter().any(|p| &p.0 == name) { + if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) { throw!(UnknownFunctionParameter((name as &str).to_owned())); } if passed_args.insert(name.clone(), value).is_some() { throw!(BindingParameterASecondTime(name.clone())); } - filled_args += 1; + filled_named += 1; Ok(()) })?; - if filled_args < params.len() { + if filled_named + filled_positionals < params.len() { // Some args are unset, but maybe we have defaults for them // Default values should be created in newly created context let fctx = Context::new_future(); - let mut defaults = GcHashMap::with_capacity(params.len() - filled_args); + let mut defaults = + GcHashMap::with_capacity(params.len() - filled_named - filled_positionals); - for param in params.iter().filter(|p| p.1.is_some()) { - if passed_args.contains_key(¶m.0.clone()) { + for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) { + if let Some(name) = param.0.name() { + if passed_args.contains_key(&name) { + continue; + } + } else if idx < filled_positionals { continue; } - defaults.insert( - param.0.clone(), + destruct( + ¶m.0, Thunk::new(tb!(EvaluateNamedThunk { ctx: fctx.clone(), - name: param.0.clone(), + name: param.0.name().unwrap_or_else(|| "".into()), value: param.1.clone().expect("default exists"), })), - ); - filled_args += 1; + &mut defaults, + )?; + if param.0.name().is_some() { + filled_named += 1; + } else { + filled_positionals += 1; + } } - // Some args still wasn't filled - if filled_args != params.len() { + // Some args still weren't filled + if filled_named + filled_positionals != params.len() { for param in params.iter().skip(args.unnamed_len()) { let mut found = false; args.named_names(&mut |name| { - if name == ¶m.0 { + if Some(name) == param.0.name().as_ref() { found = true; } }); if !found { - throw!(FunctionParameterNotBoundInCall(param.0.clone())); + throw!(FunctionParameterNotBoundInCall( + param + .0 + .clone() + .name() + .unwrap_or_else(|| "".into()) + )); } } unreachable!(); @@ -189,7 +207,7 @@ /// Creates Context, which has all argument default values applied /// and with unbound values causing error to be returned -pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context { +pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result { #[derive(Trace)] struct DependsOnUnbound(IStr); impl ThunkValue for DependsOnUnbound { @@ -205,23 +223,27 @@ for param in params.iter() { if let Some(v) = ¶m.1 { - bindings.insert( - param.0.clone(), + destruct( + ¶m.0.clone(), Thunk::new(tb!(EvaluateNamedThunk { ctx: fctx.clone(), - name: param.0.clone(), + name: param.0.name().unwrap_or_else(|| "".into()), value: v.clone(), })), - ); + &mut bindings, + )?; } else { - bindings.insert( - param.0.clone(), - Thunk::new(tb!(DependsOnUnbound(param.0.clone()))), - ); + destruct( + ¶m.0, + Thunk::new(tb!(DependsOnUnbound( + param.0.name().unwrap_or_else(|| "".into()) + ))), + &mut bindings, + )?; } } - body_ctx + Ok(body_ctx .extend(bindings, None, None, None) - .into_future(fctx) + .into_future(fctx)) } --- a/crates/jrsonnet-parser/src/expr.rs +++ b/crates/jrsonnet-parser/src/expr.rs @@ -152,7 +152,7 @@ /// name, default value #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, PartialEq, Trace)] -pub struct Param(pub IStr, pub Option); +pub struct Param(pub Destruct, pub Option); /// Defined function parameters #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] @@ -206,6 +206,7 @@ }, } impl Destruct { + /// Name of destructure, used for function parameter names pub fn name(&self) -> Option { match self { Self::Full(name) => Some(name.clone()), --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -59,7 +59,7 @@ rule keyword(id: &'static str) -> () = ##parse_string_literal(id) end_of_ident() - pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) } + pub rule param(s: &ParserSettings) -> expr::Param = name:destruct(s) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) } pub rule params(s: &ParserSettings) -> expr::ParamsDesc = params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) } / { expr::ParamsDesc(Rc::new(Vec::new())) }