difftreelog
feat object destructuring defaults
in: master
5 files changed
crates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -8,6 +8,9 @@
pub fn new() -> Self {
Self(Cc::new(RefCell::new(None)))
}
+ pub fn new_filled(v: T) -> Self {
+ Self(Cc::new(RefCell::new(Some(v))))
+ }
/// # Panics
/// If wrapper is filled already
pub fn fill(self, value: T) {
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -12,9 +12,11 @@
};
#[allow(clippy::too_many_lines)]
+#[allow(unused_variables)]
pub fn destruct(
d: &Destruct,
parent: Thunk<Val>,
+ fctx: Pending<Context>,
new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,
) -> Result<()> {
match d {
@@ -89,6 +91,7 @@
full: full.clone(),
index: i,
})),
+ fctx.clone(),
new_bindings,
)?;
}
@@ -119,6 +122,7 @@
start: start.len(),
end: end.len(),
})),
+ fctx.clone(),
new_bindings,
)?;
}
@@ -151,6 +155,7 @@
index: i,
end: end.len(),
})),
+ fctx.clone(),
new_bindings,
)?;
}
@@ -189,36 +194,51 @@
Ok(obj)
}
}
- let field_names: Vec<_> = fields.iter().map(|f| f.0.clone()).collect();
+ let field_names: Vec<_> = fields
+ .iter()
+ .filter(|f| f.2.is_none())
+ .map(|f| f.0.clone())
+ .collect();
let full = Thunk::new(tb!(DataThunk {
parent,
field_names: field_names.clone(),
has_rest: rest.is_some()
}));
- for (field, d) in fields {
+ for (field, d, default) in fields {
#[derive(Trace)]
struct FieldThunk {
full: Thunk<ObjValue>,
field: IStr,
+ default: Option<(Pending<Context>, LocExpr)>,
}
impl ThunkValue for FieldThunk {
type Output = Val;
fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
let full = self.full.evaluate(s.clone())?;
- let field = full.get(s, self.field)?.expect("shape is checked");
- Ok(field)
+ if let Some(field) = full.get(s.clone(), self.field)? {
+ Ok(field)
+ } else {
+ let (fctx, expr) = self.default.as_ref().expect("shape is checked");
+ Ok(evaluate(s, fctx.clone().unwrap(), &expr)?)
+ }
}
}
let value = Thunk::new(tb!(FieldThunk {
full: full.clone(),
- field: field.clone()
+ field: field.clone(),
+ default: default.clone().map(|e| (fctx.clone(), e)),
}));
if let Some(d) = d {
- destruct(d, value, new_bindings)?;
+ destruct(d, value, fctx.clone(), new_bindings)?;
} else {
- destruct(&Destruct::Full(field.clone()), value, new_bindings)?;
+ destruct(
+ &Destruct::Full(field.clone()),
+ value,
+ fctx.clone(),
+ new_bindings,
+ )?;
}
}
}
@@ -251,10 +271,10 @@
}
let data = Thunk::new(tb!(EvaluateThunkValue {
name: into.name(),
- fctx,
+ fctx: fctx.clone(),
expr: value.clone(),
}));
- destruct(into, data, new_bindings)?;
+ destruct(into, data, fctx, new_bindings)?;
}
BindSpec::Function {
name,
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth1use 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(params.len()))52 }5354 let mut filled_named = 0;55 let mut filled_positionals = 0;5657 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {58 let name = params[id].0.clone();59 destruct(&name, arg, &mut passed_args)?;60 filled_positionals += 1;61 Ok(())62 })?;6364 args.named_iter(s, ctx, tailstrict, &mut |name, value| {65 // FIXME: O(n) for arg existence check66 if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {67 throw!(UnknownFunctionParameter((name as &str).to_owned()));68 }69 if passed_args.insert(name.clone(), value).is_some() {70 throw!(BindingParameterASecondTime(name.clone()));71 }72 filled_named += 1;73 Ok(())74 })?;7576 if filled_named + filled_positionals < params.len() {77 // Some args are unset, but maybe we have defaults for them78 // Default values should be created in newly created context79 let fctx = Context::new_future();80 let mut defaults =81 GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8283 for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {84 if let Some(name) = param.0.name() {85 if passed_args.contains_key(&name) {86 continue;87 }88 } else if idx < filled_positionals {89 continue;90 }9192 destruct(93 ¶m.0,94 Thunk::new(tb!(EvaluateNamedThunk {95 ctx: fctx.clone(),96 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),97 value: param.1.clone().expect("default exists"),98 })),99 &mut defaults,100 )?;101 if param.0.name().is_some() {102 filled_named += 1;103 } else {104 filled_positionals += 1;105 }106 }107108 // Some args still weren't filled109 if filled_named + filled_positionals != params.len() {110 for param in params.iter().skip(args.unnamed_len()) {111 let mut found = false;112 args.named_names(&mut |name| {113 if Some(name) == param.0.name().as_ref() {114 found = true;115 }116 });117 if !found {118 throw!(FunctionParameterNotBoundInCall(119 param120 .0121 .clone()122 .name()123 .unwrap_or_else(|| "<destruct>".into())124 ));125 }126 }127 unreachable!();128 }129130 Ok(body_ctx131 .extend(passed_args, None, None, None)132 .extend(defaults, None, None, None)133 .into_future(fctx))134 } else {135 let body_ctx = body_ctx.extend(passed_args, None, None, None);136 Ok(body_ctx)137 }138}139140/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead141///142/// ## Parameters143/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)144/// * `params`: function parameters' definition145/// * `args`: passed function arguments146/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily147pub fn parse_builtin_call(148 s: State,149 ctx: Context,150 params: &[BuiltinParam],151 args: &dyn ArgsLike,152 tailstrict: bool,153) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {154 let mut passed_args = GcHashMap::with_capacity(params.len());155 if args.unnamed_len() > params.len() {156 throw!(TooManyArgsFunctionHas(params.len()))157 }158159 let mut filled_args = 0;160161 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {162 let name = params[id].name.clone();163 passed_args.insert(name, arg);164 filled_args += 1;165 Ok(())166 })?;167168 args.named_iter(s, ctx, tailstrict, &mut |name, arg| {169 // FIXME: O(n) for arg existence check170 let p = params171 .iter()172 .find(|p| p.name == name as &str)173 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;174 if passed_args.insert(p.name.clone(), arg).is_some() {175 throw!(BindingParameterASecondTime(name.clone()));176 }177 filled_args += 1;178 Ok(())179 })?;180181 if filled_args < params.len() {182 for param in params.iter().filter(|p| p.has_default) {183 if passed_args.contains_key(¶m.name) {184 continue;185 }186 filled_args += 1;187 }188189 // Some args still wasn't filled190 if filled_args != params.len() {191 for param in params.iter().skip(args.unnamed_len()) {192 let mut found = false;193 args.named_names(&mut |name| {194 if name as &str == ¶m.name as &str {195 found = true;196 }197 });198 if !found {199 throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));200 }201 }202 unreachable!();203 }204 }205 Ok(passed_args)206}207208/// Creates Context, which has all argument default values applied209/// and with unbound values causing error to be returned210pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {211 #[derive(Trace)]212 struct DependsOnUnbound(IStr);213 impl ThunkValue for DependsOnUnbound {214 type Output = Val;215 fn get(self: Box<Self>, _: State) -> Result<Val> {216 Err(FunctionParameterNotBoundInCall(self.0.clone()).into())217 }218 }219220 let fctx = Context::new_future();221222 let mut bindings = GcHashMap::new();223224 for param in params.iter() {225 if let Some(v) = ¶m.1 {226 destruct(227 ¶m.0.clone(),228 Thunk::new(tb!(EvaluateNamedThunk {229 ctx: fctx.clone(),230 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),231 value: v.clone(),232 })),233 &mut bindings,234 )?;235 } else {236 destruct(237 ¶m.0,238 Thunk::new(tb!(DependsOnUnbound(239 param.0.name().unwrap_or_else(|| "<destruct>".into())240 ))),241 &mut bindings,242 )?;243 }244 }245246 Ok(body_ctx247 .extend(bindings, None, None, None)248 .into_future(fctx))249}1use 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(params.len()))52 }5354 let mut filled_named = 0;55 let mut filled_positionals = 0;5657 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {58 let name = params[id].0.clone();59 destruct(60 &name,61 arg,62 Pending::new_filled(ctx.clone()),63 &mut passed_args,64 )?;65 filled_positionals += 1;66 Ok(())67 })?;6869 args.named_iter(s, ctx, tailstrict, &mut |name, value| {70 // FIXME: O(n) for arg existence check71 if !params.iter().any(|p| p.0.name().as_ref() == Some(name)) {72 throw!(UnknownFunctionParameter((name as &str).to_owned()));73 }74 if passed_args.insert(name.clone(), value).is_some() {75 throw!(BindingParameterASecondTime(name.clone()));76 }77 filled_named += 1;78 Ok(())79 })?;8081 if filled_named + filled_positionals < params.len() {82 // Some args are unset, but maybe we have defaults for them83 // Default values should be created in newly created context84 let fctx = Context::new_future();85 let mut defaults =86 GcHashMap::with_capacity(params.len() - filled_named - filled_positionals);8788 for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) {89 if let Some(name) = param.0.name() {90 if passed_args.contains_key(&name) {91 continue;92 }93 } else if idx < filled_positionals {94 continue;95 }9697 destruct(98 ¶m.0,99 Thunk::new(tb!(EvaluateNamedThunk {100 ctx: fctx.clone(),101 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),102 value: param.1.clone().expect("default exists"),103 })),104 fctx.clone(),105 &mut defaults,106 )?;107 if param.0.name().is_some() {108 filled_named += 1;109 } else {110 filled_positionals += 1;111 }112 }113114 // Some args still weren't filled115 if filled_named + filled_positionals != params.len() {116 for param in params.iter().skip(args.unnamed_len()) {117 let mut found = false;118 args.named_names(&mut |name| {119 if Some(name) == param.0.name().as_ref() {120 found = true;121 }122 });123 if !found {124 throw!(FunctionParameterNotBoundInCall(125 param126 .0127 .clone()128 .name()129 .unwrap_or_else(|| "<destruct>".into())130 ));131 }132 }133 unreachable!();134 }135136 Ok(body_ctx137 .extend(passed_args, None, None, None)138 .extend(defaults, None, None, None)139 .into_future(fctx))140 } else {141 let body_ctx = body_ctx.extend(passed_args, None, None, None);142 Ok(body_ctx)143 }144}145146/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead147///148/// ## Parameters149/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)150/// * `params`: function parameters' definition151/// * `args`: passed function arguments152/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily153pub fn parse_builtin_call(154 s: State,155 ctx: Context,156 params: &[BuiltinParam],157 args: &dyn ArgsLike,158 tailstrict: bool,159) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {160 let mut passed_args = GcHashMap::with_capacity(params.len());161 if args.unnamed_len() > params.len() {162 throw!(TooManyArgsFunctionHas(params.len()))163 }164165 let mut filled_args = 0;166167 args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {168 let name = params[id].name.clone();169 passed_args.insert(name, arg);170 filled_args += 1;171 Ok(())172 })?;173174 args.named_iter(s, ctx, tailstrict, &mut |name, arg| {175 // FIXME: O(n) for arg existence check176 let p = params177 .iter()178 .find(|p| p.name == name as &str)179 .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;180 if passed_args.insert(p.name.clone(), arg).is_some() {181 throw!(BindingParameterASecondTime(name.clone()));182 }183 filled_args += 1;184 Ok(())185 })?;186187 if filled_args < params.len() {188 for param in params.iter().filter(|p| p.has_default) {189 if passed_args.contains_key(¶m.name) {190 continue;191 }192 filled_args += 1;193 }194195 // Some args still wasn't filled196 if filled_args != params.len() {197 for param in params.iter().skip(args.unnamed_len()) {198 let mut found = false;199 args.named_names(&mut |name| {200 if name as &str == ¶m.name as &str {201 found = true;202 }203 });204 if !found {205 throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));206 }207 }208 unreachable!();209 }210 }211 Ok(passed_args)212}213214/// Creates Context, which has all argument default values applied215/// and with unbound values causing error to be returned216pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {217 #[derive(Trace)]218 struct DependsOnUnbound(IStr);219 impl ThunkValue for DependsOnUnbound {220 type Output = Val;221 fn get(self: Box<Self>, _: State) -> Result<Val> {222 Err(FunctionParameterNotBoundInCall(self.0.clone()).into())223 }224 }225226 let fctx = Context::new_future();227228 let mut bindings = GcHashMap::new();229230 for param in params.iter() {231 if let Some(v) = ¶m.1 {232 destruct(233 ¶m.0.clone(),234 Thunk::new(tb!(EvaluateNamedThunk {235 ctx: fctx.clone(),236 name: param.0.name().unwrap_or_else(|| "<destruct>".into()),237 value: v.clone(),238 })),239 fctx.clone(),240 &mut bindings,241 )?;242 } else {243 destruct(244 ¶m.0,245 Thunk::new(tb!(DependsOnUnbound(246 param.0.name().unwrap_or_else(|| "<destruct>".into())247 ))),248 fctx.clone(),249 &mut bindings,250 )?;251 }252 }253254 Ok(body_ctx255 .extend(bindings, None, None, None)256 .into_future(fctx))257}crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -188,7 +188,7 @@
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(Debug, Clone, PartialEq, Eq, Trace)]
+#[derive(Debug, Clone, PartialEq, Trace)]
pub enum Destruct {
Full(IStr),
#[cfg(feature = "exp-destruct")]
@@ -201,7 +201,7 @@
},
#[cfg(feature = "exp-destruct")]
Object {
- fields: Vec<(IStr, Option<Destruct>)>,
+ fields: Vec<(IStr, Option<Destruct>, Option<LocExpr>)>,
rest: Option<DestructRest>,
},
}
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -1,4 +1,4 @@
-#![allow(clippy::redundant_closure_call)]
+#![allow(clippy::redundant_closure_call, clippy::derive_partial_eq_without_eq)]
use std::rc::Rc;
@@ -109,7 +109,7 @@
}
pub rule destruct_object(s: &ParserSettings) -> expr::Destruct
= "{" _
- fields:(name:id() _ into:(":" _ into:destruct(s) {into})? {(name, into)})**comma()
+ fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:expr(s) {v})? {(name, into, default)})**comma()
rest:(
comma() rest:destruct_rest()? {rest}
/ comma()? {None}