difftreelog
feat default params in function description
in: master
5 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -10,7 +10,12 @@
use jrsonnet_types::ValType;
use thiserror::Error;
-use crate::{function::CallLocation, stdlib::format::FormatError, typed::TypeLocError, ObjValue};
+use crate::{
+ function::{builtin::ParamDefault, CallLocation},
+ stdlib::format::FormatError,
+ typed::TypeLocError,
+ ObjValue,
+};
pub(crate) fn format_found(list: &[IStr], what: &str) -> String {
if list.is_empty() {
@@ -43,7 +48,7 @@
if sig.is_empty() {
out.push_str("/*no arguments*/");
} else {
- for (i, (name, has_default)) in sig.iter().enumerate() {
+ for (i, (name, default)) in sig.iter().enumerate() {
if i != 0 {
out.push_str(", ");
}
@@ -52,8 +57,13 @@
} else {
out.push_str("<unnamed>");
}
- if *has_default {
- out.push_str(" = <default>");
+ match default {
+ ParamDefault::None => {}
+ ParamDefault::Exists => out.push_str(" = <default>"),
+ ParamDefault::Literal(lit) => {
+ out.push_str(" = ");
+ out.push_str(lit);
+ }
}
}
}
@@ -88,7 +98,7 @@
heap.into_iter().map(|v| v.1).collect()
}
-type FunctionSignature = Vec<(Option<IStr>, bool)>;
+type FunctionSignature = Vec<(Option<IStr>, ParamDefault)>;
/// Possible errors
#[allow(missing_docs)]
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -33,22 +33,40 @@
}
}
+#[derive(Clone, Copy, Debug, Trace)]
+pub enum ParamDefault {
+ None,
+ Exists,
+ Literal(&'static str),
+}
+impl ParamDefault {
+ pub const fn exists(is_exists: bool) -> Self {
+ if is_exists {
+ Self::Exists
+ } else {
+ Self::None
+ }
+ }
+}
+
#[derive(Clone, Trace)]
pub struct BuiltinParam {
name: ParamName,
- has_default: bool,
+ default: ParamDefault,
}
impl BuiltinParam {
- pub const fn new(name: ParamName, has_default: bool) -> Self {
- Self { name, has_default }
+ pub const fn new(name: ParamName, default: ParamDefault) -> Self {
+ Self { name, default }
}
/// Parameter name for named call parsing
pub fn name(&self) -> &ParamName {
&self.name
}
- /// Is implementation allowed to return empty value
+ pub fn default(&self) -> ParamDefault {
+ self.default
+ }
pub fn has_default(&self) -> bool {
- self.has_default
+ !matches!(self.default, ParamDefault::None)
}
}
@@ -87,7 +105,7 @@
.into_iter()
.map(|n| BuiltinParam {
name: ParamName::new_dynamic(n),
- has_default: false,
+ default: ParamDefault::Exists,
})
.collect(),
handler: tb!(handler),
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -8,7 +8,7 @@
use self::{
arglike::OptionalContext,
- builtin::{Builtin, BuiltinParam, ParamName, StaticBuiltin},
+ builtin::{Builtin, BuiltinParam, ParamDefault, ParamName, StaticBuiltin},
native::NativeDesc,
parse::{parse_default_function_call, parse_function_call},
};
@@ -142,7 +142,7 @@
.as_ref()
.map(IStr::to_string)
.map_or(ParamName::ANONYMOUS, ParamName::new_dynamic),
- p.1.is_some(),
+ ParamDefault::exists(p.1.is_some()),
)
})
.collect(),
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth1use std::mem::replace;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{LocExpr, ParamsDesc};67use super::{arglike::ArgsLike, builtin::BuiltinParam};8use crate::{9 bail,10 destructure::destruct,11 error::{ErrorKind::*, Result},12 evaluate_named,13 function::builtin::ParamDefault,14 gc::GcHashMap,15 val::ThunkValue,16 Context, Pending, 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>) -> Result<Val> {29 evaluate_named(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 ctx: Context,43 body_ctx: Context,44 params: &ParamsDesc,45 args: &dyn ArgsLike,46 tailstrict: bool,47) -> Result<Context> {48 let mut passed_args =49 GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());50 if args.unnamed_len() > params.len() {51 bail!(TooManyArgsFunctionHas(52 params.len(),53 params54 .iter()55 .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))56 .collect()57 ))58 }5960 let mut filled_named = 0;61 let mut filled_positionals = 0;6263 args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {64 let name = params[id].0.clone();65 destruct(66 &name,67 arg,68 Pending::new_filled(ctx.clone()),69 &mut passed_args,70 )?;71 filled_positionals += 1;72 Ok(())73 })?;7475 args.named_iter(ctx, tailstrict, &mut |name, value| {76 // FIXME: O(n) for arg existence check77 if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {78 bail!(UnknownFunctionParameter((name as &str).to_owned()));79 }80 if passed_args.insert(name.clone(), value).is_some() {81 bail!(BindingParameterASecondTime(name.clone()));82 }83 filled_named += 1;84 Ok(())85 })?;8687 if filled_named + filled_positionals < params.len() {88 // Some args are unset, but maybe we have defaults for them89 // Default values should be created in newly created context90 let fctx = Context::new_future();91 let mut defaults = GcHashMap::with_capacity(92 params.iter().map(|p| p.0.capacity_hint()).sum::<usize>()93 - filled_named - filled_positionals,94 );9596 for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {97 if let Some(name) = param.0.name() {98 if passed_args.contains_key(&name) {99 continue;100 }101 } else if idx < filled_positionals {102 continue;103 }104105 destruct(106 ¶m.0,107 Thunk::new(EvaluateNamedThunk {108 ctx: fctx.clone(),109 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),110 value: param.1.clone().expect("default exists"),111 }),112 fctx.clone(),113 &mut defaults,114 )?;115 if param.0.name().is_some() {116 filled_named += 1;117 } else {118 filled_positionals += 1;119 }120 }121122 // Some args still weren't filled123 if filled_named + filled_positionals != params.len() {124 for param in params.iter().skip(args.unnamed_len()) {125 let mut found = false;126 args.named_names(&mut |name| {127 if Some(name) == param.0.name().as_ref() {128 found = true;129 }130 });131 if !found {132 bail!(FunctionParameterNotBoundInCall(133 param.0.clone().name(),134 params135 .iter()136 .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))137 .collect()138 ));139 }140 }141 unreachable!();142 }143144 Ok(body_ctx145 .extend(passed_args, None, None, None)146 .extend(defaults, None, None, None)147 .into_future(fctx))148 } else {149 let body_ctx = body_ctx.extend(passed_args, None, None, None);150 Ok(body_ctx)151 }152}153154/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead155///156/// ## Parameters157/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)158/// * `params`: function parameters' definition159/// * `args`: passed function arguments160/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily161pub fn parse_builtin_call(162 ctx: Context,163 params: &[BuiltinParam],164 args: &dyn ArgsLike,165 tailstrict: bool,166) -> Result<Vec<Option<Thunk<Val>>>> {167 let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];168 if args.unnamed_len() > params.len() {169 bail!(TooManyArgsFunctionHas(170 params.len(),171 params172 .iter()173 .map(|p| (p.name().as_str().map(IStr::from), p.default()))174 .collect()175 ))176 }177178 let mut filled_args = 0;179180 args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| {181 passed_args[id] = Some(arg);182 filled_args += 1;183 Ok(())184 })?;185186 args.named_iter(ctx, tailstrict, &mut |name, arg| {187 // FIXME: O(n) for arg existence check188 let id = params189 .iter()190 .position(|p| p.name() == name)191 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;192 if replace(&mut passed_args[id], Some(arg)).is_some() {193 bail!(BindingParameterASecondTime(name.clone()));194 }195 filled_args += 1;196 Ok(())197 })?;198199 if filled_args < params.len() {200 for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default()) {201 if passed_args[id].is_some() {202 continue;203 }204 filled_args += 1;205 }206207 // Some args still wasn't filled208 if filled_args != params.len() {209 for param in params.iter().skip(args.unnamed_len()) {210 let mut found = false;211 args.named_names(&mut |name| {212 if param.name() == name {213 found = true;214 }215 });216 if !found {217 bail!(FunctionParameterNotBoundInCall(218 param.name().as_str().map(IStr::from),219 params220 .iter()221 .map(|p| (p.name().as_str().map(IStr::from), p.default()))222 .collect()223 ));224 }225 }226 unreachable!();227 }228 }229 Ok(passed_args)230}231232/// Creates Context, which has all argument default values applied233/// and with unbound values causing error to be returned234pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {235 #[derive(Trace)]236 struct DependsOnUnbound(IStr, ParamsDesc);237 impl ThunkValue for DependsOnUnbound {238 type Output = Val;239 fn get(self: Box<Self>) -> Result<Val> {240 Err(FunctionParameterNotBoundInCall(241 Some(self.0.clone()),242 self.1243 .iter()244 .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some())))245 .collect(),246 )247 .into())248 }249 }250251 let fctx = Context::new_future();252253 let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum());254255 for param in params.iter() {256 if let Some(v) = ¶m.1 {257 destruct(258 ¶m.0.clone(),259 Thunk::new(EvaluateNamedThunk {260 ctx: fctx.clone(),261 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),262 value: v.clone(),263 }),264 fctx.clone(),265 &mut bindings,266 )?;267 } else {268 destruct(269 ¶m.0,270 Thunk::new(DependsOnUnbound(271 param.0.name().unwrap_or_else(|| "<destruct>".into()),272 params.clone(),273 )),274 fctx.clone(),275 &mut bindings,276 )?;277 }278 }279280 Ok(body_ctx281 .extend(bindings, None, None, None)282 .into_future(fctx))283}crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -253,10 +253,14 @@
let name = name
.as_ref()
.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});
- let is_optional = optionality.is_optional();
+ let default = match optionality {
+ Optionality::Required => quote!(ParamDefault::None),
+ Optionality::Optional => quote!(ParamDefault::Exists),
+ Optionality::Default(e) => quote!(ParamDefault::Literal(stringify!(#e))),
+ };
Some(quote! {
#(#cfg_attrs)*
- BuiltinParam::new(#name, #is_optional),
+ BuiltinParam::new(#name, #default),
})
}
ArgInfo::Lazy { is_option, name } => {
@@ -264,7 +268,7 @@
.as_ref()
.map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});
Some(quote! {
- BuiltinParam::new(#name, #is_option),
+ BuiltinParam::new(#name, ParamDefault::exists(#is_option)),
})
}
ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,
@@ -375,7 +379,7 @@
const _: () = {
use ::jrsonnet_evaluator::{
State, Val,
- function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName}, CallLocation, ArgsLike, parse::parse_builtin_call},
+ function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call},
Result, Context, typed::Typed,
parser::ExprLocation,
};