difftreelog
fix destructure
in: master
4 files changed
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth1use std::{collections::HashMap, hash::BuildHasher};23use jrsonnet_interner::IStr;4use jrsonnet_ir::{BindSpec, Destruct};56use crate::{7 bail,8 error::{ErrorKind::*, Result},9 evaluate_method, evaluate_named_param, Context, Pending, Thunk, Val,10};1112#[cfg(feature = "exp-preserve-order")]13use crate::evaluate;1415#[allow(clippy::too_many_lines)]16#[allow(unused_variables)]17pub fn destruct<H: BuildHasher>(18 d: &Destruct,19 parent: Thunk<Val>,20 fctx: Pending<Context>,21 new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,22) -> Result<()> {23 match d {24 Destruct::Full(v) => {25 let old = new_bindings.insert(v.clone(), parent);26 if old.is_some() {27 bail!(DuplicateLocalVar(v.clone()))28 }29 }30 #[cfg(feature = "exp-destruct")]31 Destruct::Skip => {}32 #[cfg(feature = "exp-destruct")]33 Destruct::Array { start, rest, end } => {34 use jrsonnet_ir::DestructRest;3536 let min_len = start.len() + end.len();37 let has_rest = rest.is_some();38 let full = Thunk!(move || {39 let v = parent.evaluate()?;40 let Val::Arr(arr) = v else {41 bail!("expected array");42 };43 if !has_rest {44 if arr.len() != min_len {45 bail!("expected {} elements, got {}", min_len, arr.len())46 }47 } else if arr.len() < min_len {48 bail!(49 "expected at least {} elements, but array was only {}",50 min_len,51 arr.len()52 )53 }54 Ok(arr)55 });5657 {58 for (i, d) in start.iter().enumerate() {59 let full = full.clone();60 destruct(61 d,62 Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),63 fctx.clone(),64 new_bindings,65 )?;66 }67 }6869 match rest {70 Some(DestructRest::Keep(v)) => {71 let start = start.len();72 let end = end.len();73 let full = full.clone();74 destruct(75 &Destruct::Full(v.clone()),76 Thunk!(move || {77 let full = full.evaluate()?;78 let to = full.len() - end;79 Ok(Val::Arr(full.slice(80 Some(start as i32),81 Some(to as i32),82 None,83 )))84 }),85 fctx.clone(),86 new_bindings,87 )?;88 }89 Some(DestructRest::Drop) | None => {}90 }9192 {93 for (i, d) in end.iter().enumerate() {94 let full = full.clone();95 let end = end.len();96 destruct(97 d,98 Thunk!(move || {99 let full = full.evaluate()?;100 Ok(full.get(full.len() - end + i)?.expect("length is checked"))101 }),102 fctx.clone(),103 new_bindings,104 )?;105 }106 }107 }108 #[cfg(feature = "exp-destruct")]109 Destruct::Object { fields, rest } => {110 let field_names: Vec<_> = fields111 .iter()112 .map(|f| (f.0.clone(), f.2.is_some()))113 .collect();114 let has_rest = rest.is_some();115 let full = Thunk!(move || {116 let v = parent.evaluate()?;117 let Val::Obj(obj) = v else {118 bail!("expected object");119 };120 for (field, has_default) in &field_names {121 if !has_default && !obj.has_field_ex(field.clone(), true) {122 bail!("missing field: {field}");123 }124 }125 if !has_rest {126 let len = obj.len();127 if len > field_names.len() {128 bail!("too many fields, and rest not found");129 }130 }131 Ok(obj)132 });133134 for (field, d, default) in fields {135 let default = default.clone().map(|e| (fctx.clone(), e));136 let value = {137 let field = field.clone();138 let full = full.clone();139 Thunk!(move || {140 let full = full.evaluate()?;141 if let Some(field) = full.get(field)? {142 Ok(field)143 } else {144 let (fctx, expr) = default.as_ref().expect("shape is checked");145 Ok(evaluate(fctx.clone().unwrap(), expr)?)146 }147 })148 };149150 if let Some(d) = d {151 destruct(d, value, fctx.clone(), new_bindings)?;152 } else {153 destruct(154 &Destruct::Full(field.clone()),155 value,156 fctx.clone(),157 new_bindings,158 )?;159 }160 }161 }162 }163 Ok(())164}165166pub fn evaluate_dest<H: BuildHasher>(167 d: &BindSpec,168 fctx: Pending<Context>,169 new_bindings: &mut HashMap<IStr, Thunk<Val>, H>,170) -> Result<()> {171 match d {172 BindSpec::Field { into, value } => {173 let name = into.name();174 let value = value.clone();175 let data = {176 let fctx = fctx.clone();177 Thunk!(move || evaluate_named_param(fctx.unwrap(), &value, name))178 };179 destruct(into, data, fctx, new_bindings)?;180 }181 BindSpec::Function {182 name,183 params,184 value,185 } => {186 let params = params.clone();187 let name = name.clone();188 let value = value.clone();189 let old = new_bindings.insert(name.clone(), {190 let name = name.clone();191 Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value)))192 });193 if old.is_some() {194 bail!(DuplicateLocalVar(name))195 }196 }197 }198 Ok(())199}crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -142,7 +142,7 @@
false,
) {
let fctx = Pending::new();
- let mut new_bindings = FxHashMap::with_capacity(var.binds_len());
+ let mut new_bindings = FxHashMap::with_capacity(into.binds_len());
let obj = obj.clone();
let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![
Thunk::evaluated(Val::string(field.clone())),
@@ -150,7 +150,7 @@
"field exists, as field name was obtained from object.fields()",
)),
])));
- destruct(var, value, fctx.clone(), &mut new_bindings)?;
+ destruct(into, value, fctx.clone(), &mut new_bindings)?;
let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
evaluate_comp(ctx, &specs[1..], callback)?;
crates/jrsonnet-ir-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/Cargo.toml
+++ b/crates/jrsonnet-ir-parser/Cargo.toml
@@ -8,6 +8,7 @@
[features]
exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"]
+exp-destruct = ["jrsonnet-ir/exp-destruct"]
[dependencies]
insta.workspace = true
crates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -3,9 +3,9 @@
use jrsonnet_gcmodule::Acyclic;
use jrsonnet_ir::{
unescape, ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BinaryOpType, BindSpec, CompSpec,
- Destruct, Expr, ExprParam, ExprParams, FieldMember, FieldName, ForSpecData, IStr, IfElse,
- IfSpecData, ImportKind, IndexPart, LiteralType, Member, ObjBody, ObjComp, ObjMembers, Slice,
- SliceDesc, Source, Span, Spanned, UnaryOpType, Visibility,
+ Destruct, DestructRest, Expr, ExprParam, ExprParams, FieldMember, FieldName, ForSpecData, IStr,
+ IfElse, IfSpecData, ImportKind, IndexPart, LiteralType, Member, ObjBody, ObjComp, ObjMembers,
+ Slice, SliceDesc, Source, Span, Spanned, UnaryOpType, Visibility,
};
use jrsonnet_lexer::{collect_lexed_str_block, Lexeme, Lexer, SyntaxKind, T};
@@ -316,7 +316,114 @@
}
fn destruct(p: &mut Parser<'_>) -> R<Destruct> {
- Ok(Destruct::Full(p.expect_ident()?))
+ if p.at_ident() {
+ return Ok(Destruct::Full(p.expect_ident()?));
+ }
+ #[cfg(not(feature = "exp-destruct"))]
+ return Err(p.error(format!(
+ "expected identifier, got {}",
+ p.current_desc()
+ )));
+ #[cfg(feature = "exp-destruct")]
+ {
+ if p.try_eat(T![?]) {
+ return Ok(Destruct::Skip);
+ }
+ if p.at(T!['[']) {
+ return destruct_array(p);
+ }
+ if p.at(T!['{']) {
+ return destruct_object(p);
+ }
+ Err(p.error(format!(
+ "expected destructure pattern, got {}",
+ p.current_desc()
+ )))
+ }
+}
+
+#[cfg(feature = "exp-destruct")]
+fn destruct_rest(p: &mut Parser<'_>) -> R<DestructRest> {
+ p.eat(T![...])?;
+ if p.at_ident() {
+ Ok(DestructRest::Keep(p.expect_ident()?))
+ } else {
+ Ok(DestructRest::Drop)
+ }
+}
+
+#[cfg(feature = "exp-destruct")]
+fn destruct_array(p: &mut Parser<'_>) -> R<Destruct> {
+ p.eat(T!['['])?;
+ let mut start = Vec::new();
+ let mut rest = None;
+ let mut end = Vec::new();
+ if !p.at(T![']']) {
+ loop {
+ if p.at(T![...]) {
+ rest = Some(destruct_rest(p)?);
+ if p.try_eat(T![,]) {
+ if !p.at(T![']']) {
+ loop {
+ end.push(destruct(p)?);
+ if !p.try_eat(T![,]) {
+ break;
+ }
+ if p.at(T![']']) {
+ break;
+ }
+ }
+ }
+ }
+ break;
+ }
+ start.push(destruct(p)?);
+ if !p.try_eat(T![,]) {
+ break;
+ }
+ if p.at(T![']']) {
+ break;
+ }
+ }
+ }
+ p.eat(T![']'])?;
+ Ok(Destruct::Array { start, rest, end })
+}
+
+#[cfg(feature = "exp-destruct")]
+fn destruct_object(p: &mut Parser<'_>) -> R<Destruct> {
+ p.eat(T!['{'])?;
+ let mut fields = Vec::new();
+ let mut rest = None;
+ if !p.at(T!['}']) {
+ loop {
+ if p.at(T![...]) {
+ rest = Some(destruct_rest(p)?);
+ p.try_eat(T![,]);
+ break;
+ }
+ let name = p.expect_ident()?;
+ let into = if p.try_eat(T![:]) {
+ Some(destruct(p)?)
+ } else {
+ None
+ };
+ let default = if p.try_eat(T![=]) {
+ Some(Rc::new(spanned(p, expr)?))
+ } else {
+ None
+ };
+ fields.push((name, into, default));
+ if !p.try_eat(T![,]) {
+ break;
+ }
+ if p.at(T!['}']) {
+ break;
+ }
+ }
+ }
+ p.eat(T!['}'])?;
+ Ok(Destruct::Object { fields, rest })
}
fn params(p: &mut Parser<'_>) -> R<ExprParams> {
@@ -383,6 +490,15 @@
}
fn bind(p: &mut Parser<'_>) -> R<BindSpec> {
+ #[cfg(feature = "exp-destruct")]
+ {
+ if !p.at_ident() {
+ let d = destruct(p)?;
+ p.eat(T![=])?;
+ let value = Rc::new(expr(p)?);
+ return Ok(BindSpec::Field { into: d, value });
+ }
+ }
let name = p.expect_ident()?;
if p.try_eat(T!['(']) {
let ps = params(p)?;