From b6f9e8309dbcbfb8b233cc6521be9daf559d2c86 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Mon, 23 Mar 2026 01:25:06 +0000 Subject: [PATCH] fix: destructure --- --- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs @@ -142,7 +142,7 @@ Ok(field) } else { let (fctx, expr) = default.as_ref().expect("shape is checked"); - Ok(evaluate(fctx.clone().unwrap(), expr)?) + Ok(crate::evaluate(fctx.clone().unwrap(), expr)?) } }) }; --- 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)?; --- 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 --- 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 { - 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 { + 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 { + 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 { + 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 { @@ -383,6 +490,15 @@ } fn bind(p: &mut Parser<'_>) -> R { + #[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)?; -- gitstuff