From afca25294a054ddcb98c957ff298d23cc10958c3 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Tue, 11 Oct 2022 18:25:08 +0000 Subject: [PATCH] feat: allow unnamed builtin arguments --- --- a/bindings/jsonnet/src/native.rs +++ b/bindings/jsonnet/src/native.rs @@ -1,11 +1,12 @@ use std::{ + borrow::Cow, ffi::{c_void, CStr}, os::raw::{c_char, c_int}, }; use jrsonnet_evaluator::{ error::{Error, LocError}, - function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler}, + function::builtin::{NativeCallback, NativeCallbackHandler}, tb, typed::Typed, IStr, State, Val, @@ -87,10 +88,7 @@ let param = CStr::from_ptr(*raw_params) .to_str() .expect("param name is not utf-8"); - params.push(BuiltinParam { - name: param.into(), - has_default: false, - }); + params.push(Cow::Owned(param.into())); raw_params = raw_params.offset(1); } --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -113,8 +113,8 @@ BindingParameterASecondTime(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("function argument is not passed: {}{}", .0.as_ref().map(|n| n.as_str()).unwrap_or(""), format_signature(.1))] + FunctionParameterNotBoundInCall(Option, FunctionSignature), #[error("external variable is not defined: {0}")] UndefinedExternalVariable(IStr), --- a/crates/jrsonnet-evaluator/src/function/builtin.rs +++ b/crates/jrsonnet-evaluator/src/function/builtin.rs @@ -9,7 +9,9 @@ #[derive(Clone, Trace)] pub struct BuiltinParam { - pub name: BuiltinParamName, + /// Parameter name for named call parsing + pub name: Option, + /// Is implementation allowed to return empty value pub has_default: bool, } @@ -40,8 +42,20 @@ } impl NativeCallback { #[deprecated = "prefer using builtins directly, use this interface only for bindings"] - pub fn new(params: Vec, handler: TraceBox) -> Self { - Self { params, handler } + pub fn new( + params: Vec>, + handler: TraceBox, + ) -> Self { + Self { + params: params + .into_iter() + .map(|n| BuiltinParam { + name: Some(n), + has_default: false, + }) + .collect(), + handler, + } } } @@ -58,11 +72,12 @@ fn call(&self, s: State, ctx: Context, _loc: CallLocation, args: &dyn ArgsLike) -> Result { let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?; - let mut out_args = Vec::with_capacity(self.params.len()); - for p in &self.params { - out_args.push(args[&p.name].evaluate(s.clone())?); - } - self.handler.call(s, &out_args) + let args = args + .into_iter() + .map(|a| a.expect("legacy natives have no default params")) + .map(|a| a.evaluate(s.clone())) + .collect::>>()?; + self.handler.call(s, &args) } } --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -1,11 +1,10 @@ +use std::mem::replace; + use jrsonnet_gcmodule::Trace; use jrsonnet_interner::IStr; use jrsonnet_parser::{LocExpr, ParamsDesc}; -use super::{ - arglike::ArgsLike, - builtin::{BuiltinParam, BuiltinParamName}, -}; +use super::{arglike::ArgsLike, builtin::BuiltinParam}; use crate::{ destructure::destruct, error::{Error::*, Result}, @@ -125,11 +124,7 @@ }); if !found { throw!(FunctionParameterNotBoundInCall( - param - .0 - .clone() - .name() - .unwrap_or_else(|| "".into()), + param.0.clone().name(), params.iter().map(|p| (p.0.name(), p.1.is_some())).collect() )); } @@ -160,14 +155,14 @@ params: &[BuiltinParam], args: &dyn ArgsLike, tailstrict: bool, -) -> Result>> { - let mut passed_args = GcHashMap::with_capacity(params.len()); +) -> Result>>> { + let mut passed_args: Vec>> = vec![None; params.len()]; if args.unnamed_len() > params.len() { throw!(TooManyArgsFunctionHas( params.len(), params .iter() - .map(|p| (Some(p.name.as_ref().into()), p.has_default)) + .map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default)) .collect() )) } @@ -175,19 +170,23 @@ let mut filled_args = 0; args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| { - let name = params[id].name.clone(); - passed_args.insert(name, arg); + passed_args[id] = Some(arg); filled_args += 1; Ok(()) })?; args.named_iter(s, ctx, tailstrict, &mut |name, arg| { // FIXME: O(n) for arg existence check - let p = params + let id = params .iter() - .find(|p| p.name == name as &str) + .position(|p| { + p.name + .as_ref() + .map(|v| &v as &str == name as &str) + .unwrap_or(false) + }) .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?; - if passed_args.insert(p.name.clone(), arg).is_some() { + if replace(&mut passed_args[id], Some(arg)).is_some() { throw!(BindingParameterASecondTime(name.clone())); } filled_args += 1; @@ -195,8 +194,8 @@ })?; if filled_args < params.len() { - for param in params.iter().filter(|p| p.has_default) { - if passed_args.contains_key(¶m.name) { + for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) { + if passed_args[id].is_some() { continue; } filled_args += 1; @@ -207,16 +206,21 @@ for param in params.iter().skip(args.unnamed_len()) { let mut found = false; args.named_names(&mut |name| { - if name as &str == ¶m.name as &str { + if param + .name + .as_ref() + .map(|v| &v as &str == name as &str) + .unwrap_or(false) + { found = true; } }); if !found { throw!(FunctionParameterNotBoundInCall( - param.name.clone().into(), + param.name.as_ref().map(|v| v.as_ref().into()), params .iter() - .map(|p| (Some(p.name.as_ref().into()), p.has_default)) + .map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default)) .collect() )); } @@ -236,7 +240,7 @@ type Output = Val; fn get(self: Box, _: State) -> Result { Err(FunctionParameterNotBoundInCall( - self.0.clone(), + Some(self.0.clone()), self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(), ) .into()) --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -122,13 +122,13 @@ Normal { ty: Box, is_option: bool, - name: String, + name: Option, cfg_attrs: Vec, // ident: Ident, }, Lazy { is_option: bool, - name: String, + name: Option, }, State, Location, @@ -142,8 +142,8 @@ FnArg::Typed(a) => a, }; let ident = match &arg.pat as &Pat { - Pat::Ident(i) => i.ident.clone(), - _ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")), + Pat::Ident(i) => Some(i.ident.clone()), + _ => None, }; let ty = &arg.ty; if type_is_path(ty, "State").is_some() { @@ -153,7 +153,7 @@ } else if type_is_path(ty, "Thunk").is_some() { return Ok(Self::Lazy { is_option: false, - name: ident.to_string(), + name: ident.map(|v| v.to_string()), }); } @@ -166,7 +166,7 @@ if type_is_path(ty, "Thunk").is_some() { return Ok(Self::Lazy { is_option: true, - name: ident.to_string(), + name: ident.map(|v| v.to_string()), }); } @@ -185,7 +185,7 @@ Ok(Self::Normal { ty, is_option, - name: ident.to_string(), + name: ident.map(|v| v.to_string()), cfg_attrs, }) } @@ -248,69 +248,95 @@ name, cfg_attrs, .. - } => Some(quote! { - #(#cfg_attrs)* - BuiltinParam { - name: std::borrow::Cow::Borrowed(#name), - has_default: #is_option, - }, - }), - ArgInfo::Lazy { is_option, name } => Some(quote! { - BuiltinParam { - name: std::borrow::Cow::Borrowed(#name), - has_default: #is_option, - }, - }), + } => { + let name = name + .as_ref() + .map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))}) + .unwrap_or_else(|| quote! {None}); + Some(quote! { + #(#cfg_attrs)* + BuiltinParam { + name: #name, + has_default: #is_option, + }, + }) + } + ArgInfo::Lazy { is_option, name } => { + let name = name + .as_ref() + .map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))}) + .unwrap_or_else(|| quote! {None}); + Some(quote! { + BuiltinParam { + name: #name, + has_default: #is_option, + }, + }) + } ArgInfo::State => None, ArgInfo::Location => None, ArgInfo::This => None, }); - let pass = args.iter().map(|a| match a { - ArgInfo::Normal { - ty, - is_option, - name, - cfg_attrs, - } => { - let eval = quote! {s.push_description( - || format!("argument <{}> evaluation", #name), - || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()), - )?}; - let value = if *is_option { - quote! {if let Some(value) = parsed.get(#name) { - Some(#eval) + let mut id = 0usize; + let pass = args + .iter() + .map(|a| match a { + ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => { + let cid = id; + id += 1; + (quote! {#cid}, a) + } + ArgInfo::State | ArgInfo::Location | ArgInfo::This => { + (quote! {compile_error!("should not use id")}, a) + } + }) + .map(|(id, a)| match a { + ArgInfo::Normal { + ty, + is_option, + name, + cfg_attrs, + } => { + let name = name.as_ref().map(|v| v.as_str()).unwrap_or(""); + let eval = quote! {s.push_description( + || format!("argument <{}> evaluation", #name), + || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()), + )?}; + let value = if *is_option { + quote! {if let Some(value) = &parsed[#id] { + Some(#eval) + } else { + None + },} } else { - None - },} - } else { - quote! {{ - let value = parsed.get(#name).expect("args shape is checked"); - #eval - },} - }; - quote! { - #(#cfg_attrs)* - #value + quote! {{ + let value = parsed[#id].as_ref().expect("args shape is checked"); + #eval + },} + }; + quote! { + #(#cfg_attrs)* + #value + } } - } - ArgInfo::Lazy { is_option, name } => { - if *is_option { - quote! {if let Some(value) = parsed.get(#name) { - Some(value.clone()) + ArgInfo::Lazy { is_option, .. } => { + if *is_option { + quote! {if let Some(value) = &parsed[#id] { + Some(value.clone()) + } else { + None + }} } else { - None - }} - } else { - quote! { - parsed.get(#name).expect("args shape is correct").clone(), + quote! { + parsed[#id].as_ref().expect("args shape is correct").clone(), + } } } - } - ArgInfo::State => quote! {s.clone(),}, - ArgInfo::Location => quote! {location,}, - ArgInfo::This => quote! {self,}, - }); + ArgInfo::State => quote! {s.clone(),}, + ArgInfo::Location => quote! {location,}, + ArgInfo::This => quote! {self,}, + }); let fields = attr.fields.iter().map(|field| { let name = &field.name; -- gitstuff