difftreelog
feat allow unnamed builtin arguments
in: master
5 files changed
bindings/jsonnet/src/native.rsdiffbeforeafterboth--- 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);
}
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- 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("<unnamed>"), format_signature(.1))]
+ FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),
#[error("external variable is not defined: {0}")]
UndefinedExternalVariable(IStr),
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- 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<BuiltinParamName>,
+ /// 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<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
- Self { params, handler }
+ pub fn new(
+ params: Vec<Cow<'static, str>>,
+ handler: TraceBox<dyn NativeCallbackHandler>,
+ ) -> 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<Val> {
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::<Result<Vec<Val>>>()?;
+ self.handler.call(s, &args)
}
}
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth1use jrsonnet_gcmodule::Trace;2use jrsonnet_interner::IStr;3use jrsonnet_parser::{LocExpr, ParamsDesc};45use super::{6 arglike::ArgsLike,7 builtin::{BuiltinParam, BuiltinParamName},8};9use crate::{10 destructure::destruct,11 error::{Error::*, Result},12 evaluate_named,13 gc::GcHashMap,14 tb, throw,15 val::ThunkValue,16 Context, Pending, State, Thunk, Val,17};1819#[derive(Trace)]20struct EvaluateNamedThunk {21 ctx: Pending<Context>,22 name: IStr,23 value: LocExpr,24}2526impl ThunkValue for EvaluateNamedThunk {27 type Output = Val;28 fn get(self: Box<Self>, s: State) -> Result<Val> {29 evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)30 }31}3233/// Creates correct [context](Context) for function body evaluation returning error on invalid call.34///35/// ## Parameters36/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)37/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)38/// * `params`: function parameters' definition39/// * `args`: passed function arguments40/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily41pub fn parse_function_call(42 s: State,43 ctx: Context,44 body_ctx: Context,45 params: &ParamsDesc,46 args: &dyn ArgsLike,47 tailstrict: bool,48) -> Result<Context> {49 let mut passed_args = GcHashMap::with_capacity(params.len());50 if args.unnamed_len() > params.len() {51 throw!(TooManyArgsFunctionHas(52 params.len(),53 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()54 ))55 }5657 let mut filled_named = 0;58 let mut filled_positionals = 0;5960 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {61 let name = params[id].0.clone();62 destruct(63 &name,64 arg,65 Pending::new_filled(ctx.clone()),66 &mut passed_args,67 )?;68 filled_positionals += 1;69 Ok(())70 })?;7172 args.named_iter(s, ctx, tailstrict, &mut |name, value| {73 // FIXME: O(n) for arg existence check74 if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {75 throw!(UnknownFunctionParameter((name as &str).to_owned()));76 }77 if passed_args.insert(name.clone(), value).is_some() {78 throw!(BindingParameterASecondTime(name.clone()));79 }80 filled_named += 1;81 Ok(())82 })?;8384 if filled_named + filled_positionals < params.len() {85 // Some args are unset, but maybe we have defaults for them86 // Default values should be created in newly created context87 let fctx = Context::new_future();88 let mut defaults =89 GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);9091 for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {92 if let Some(name) = param.0.name() {93 if passed_args.contains_key(&name) {94 continue;95 }96 } else if idx < filled_positionals {97 continue;98 }99100 destruct(101 ¶m.0,102 Thunk::new(tb!(EvaluateNamedThunk {103 ctx: fctx.clone(),104 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),105 value: param.1.clone().expect("default exists"),106 })),107 fctx.clone(),108 &mut defaults,109 )?;110 if param.0.name().is_some() {111 filled_named += 1;112 } else {113 filled_positionals += 1;114 }115 }116117 // Some args still weren't filled118 if filled_named + filled_positionals != params.len() {119 for param in params.iter().skip(args.unnamed_len()) {120 let mut found = false;121 args.named_names(&mut |name| {122 if Some(name) == param.0.name().as_ref() {123 found = true;124 }125 });126 if !found {127 throw!(FunctionParameterNotBoundInCall(128 param129 .0130 .clone()131 .name()132 .unwrap_or_else(|| "<destruct>".into()),133 params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()134 ));135 }136 }137 unreachable!();138 }139140 Ok(body_ctx141 .extend(passed_args, None, None, None)142 .extend(defaults, None, None, None)143 .into_future(fctx))144 } else {145 let body_ctx = body_ctx.extend(passed_args, None, None, None);146 Ok(body_ctx)147 }148}149150/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead151///152/// ## Parameters153/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)154/// * `params`: function parameters' definition155/// * `args`: passed function arguments156/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily157pub fn parse_builtin_call(158 s: State,159 ctx: Context,160 params: &[BuiltinParam],161 args: &dyn ArgsLike,162 tailstrict: bool,163) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {164 let mut passed_args = GcHashMap::with_capacity(params.len());165 if args.unnamed_len() > params.len() {166 throw!(TooManyArgsFunctionHas(167 params.len(),168 params169 .iter()170 .map(|p| (Some(p.name.as_ref().into()), p.has_default))171 .collect()172 ))173 }174175 let mut filled_args = 0;176177 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {178 let name = params[id].name.clone();179 passed_args.insert(name, arg);180 filled_args += 1;181 Ok(())182 })?;183184 args.named_iter(s, ctx, tailstrict, &mut |name, arg| {185 // FIXME: O(n) for arg existence check186 let p = params187 .iter()188 .find(|p| p.name == name as &str)189 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;190 if passed_args.insert(p.name.clone(), arg).is_some() {191 throw!(BindingParameterASecondTime(name.clone()));192 }193 filled_args += 1;194 Ok(())195 })?;196197 if filled_args < params.len() {198 for param in params.iter().filter(|p| p.has_default) {199 if passed_args.contains_key(¶m.name) {200 continue;201 }202 filled_args += 1;203 }204205 // Some args still wasn't filled206 if filled_args != params.len() {207 for param in params.iter().skip(args.unnamed_len()) {208 let mut found = false;209 args.named_names(&mut |name| {210 if name as &str == ¶m.name as &str {211 found = true;212 }213 });214 if !found {215 throw!(FunctionParameterNotBoundInCall(216 param.name.clone().into(),217 params218 .iter()219 .map(|p| (Some(p.name.as_ref().into()), p.has_default))220 .collect()221 ));222 }223 }224 unreachable!();225 }226 }227 Ok(passed_args)228}229230/// Creates Context, which has all argument default values applied231/// and with unbound values causing error to be returned232pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {233 #[derive(Trace)]234 struct DependsOnUnbound(IStr, ParamsDesc);235 impl ThunkValue for DependsOnUnbound {236 type Output = Val;237 fn get(self: Box<Self>, _: State) -> Result<Val> {238 Err(FunctionParameterNotBoundInCall(239 self.0.clone(),240 self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),241 )242 .into())243 }244 }245246 let fctx = Context::new_future();247248 let mut bindings = GcHashMap::new();249250 for param in params.iter() {251 if let Some(v) = ¶m.1 {252 destruct(253 ¶m.0.clone(),254 Thunk::new(tb!(EvaluateNamedThunk {255 ctx: fctx.clone(),256 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),257 value: v.clone(),258 })),259 fctx.clone(),260 &mut bindings,261 )?;262 } else {263 destruct(264 ¶m.0,265 Thunk::new(tb!(DependsOnUnbound(266 param.0.name().unwrap_or_else(|| "<destruct>".into()),267 params.clone()268 ))),269 fctx.clone(),270 &mut bindings,271 )?;272 }273 }274275 Ok(body_ctx276 .extend(bindings, None, None, None)277 .into_future(fctx))278}crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -122,13 +122,13 @@
Normal {
ty: Box<Type>,
is_option: bool,
- name: String,
+ name: Option<String>,
cfg_attrs: Vec<Attribute>,
// ident: Ident,
},
Lazy {
is_option: bool,
- name: String,
+ name: Option<String>,
},
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("<unnamed>");
+ 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;