git.delta.rocks / jrsonnet / refs/commits / 166fbe9afc2a

difftreelog

fix exp-destruct

wkqlwykpYaroslav Bolyukin2026-04-25parent: #a3646b3.patch.diff
in: master

7 files changed

modifiedcrates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -425,7 +425,7 @@
 	/// h = 1 => referenced += [], closures += 0, destructs += 1
 	/// And the result is
 	///
-	/// ```
+	/// ```rust,ignore
 	/// Closures {
 	///     referenced: vec![d, e, f, a, b, c, h]
 	///     spec_shapes: vec![(3, 3), (4, 3), (0, 1)],
modifiedcrates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth
after · crates/jrsonnet-evaluator/src/evaluate/compspec.rs
1use std::rc::Rc;23use jrsonnet_types::ValType;45use super::{6	destructure::{self, evaluate_locals, evaluate_locals_unbound},7	evaluate_field_member_static, evaluate_field_member_unbound,8};9use crate::{10	Context, ContextBuilder, ObjValue, ObjValueBuilder, Pending, Result, Thunk, Val,11	analyze::{LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp, LocalId},12	arr::ArrValue,13	bail,14	error::ErrorKind::*,15	evaluate::evaluate,16};1718trait CompCollector {19	fn reserve(&mut self, _guaranteed: usize) {}20	fn collect(&mut self, ctx: Context) -> Result<()>;21}2223struct EagerArrCollector<'a> {24	out: &'a mut Vec<Val>,25	value: &'a LExpr,26}27impl CompCollector for EagerArrCollector<'_> {28	fn reserve(&mut self, size_hint: usize) {29		self.out.reserve(size_hint);30	}31	fn collect(&mut self, ctx: Context) -> Result<()> {32		self.out.push(evaluate(ctx, self.value)?);33		Ok(())34	}35}3637struct LazyArrCollector<'a> {38	out: &'a mut Vec<Thunk<Val>>,39	value: &'a Rc<LExpr>,40}41impl CompCollector for LazyArrCollector<'_> {42	fn reserve(&mut self, size_hint: usize) {43		self.out.reserve(size_hint);44	}45	fn collect(&mut self, ctx: Context) -> Result<()> {46		let value_expr = self.value.clone();47		self.out.push(Thunk!(move || evaluate(ctx, &value_expr)));48		Ok(())49	}50}5152struct ObjCompCollectorStatic<'a> {53	builder: &'a mut ObjValueBuilder,54	locals: &'a [LBind],55	field: &'a LFieldMember,56}57impl CompCollector for ObjCompCollectorStatic<'_> {58	fn reserve(&mut self, guaranteed: usize) {59		self.builder.reserve_fields(guaranteed);60	}61	fn collect(&mut self, inner_ctx: Context) -> Result<()> {62		let value_ctx = evaluate_locals(inner_ctx.clone(), self.locals);63		evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)64	}65}6667struct ObjCompCollectorUnbound<'a> {68	builder: &'a mut ObjValueBuilder,69	locals: Rc<Vec<LBind>>,70	this_id: Option<LocalId>,71	field: &'a LFieldMember,72}73impl CompCollector for ObjCompCollectorUnbound<'_> {74	fn reserve(&mut self, guaranteed: usize) {75		self.builder.reserve_fields(guaranteed);76	}77	fn collect(&mut self, inner_ctx: Context) -> Result<()> {78		let uctx = evaluate_locals_unbound(inner_ctx.clone(), self.locals.clone(), self.this_id);79		evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)80	}81}8283pub fn evaluate_obj_comp(84	super_obj: Option<ObjValue>,85	ctx: Context,86	comp: &LObjComp,87) -> Result<Val> {88	let mut builder = ObjValueBuilder::new();89	if let Some(super_obj) = super_obj {90		builder.with_super(super_obj);91	}9293	let cached_overs = cache_overs(&ctx, &comp.compspecs)?;94	if comp.this.is_some() || comp.uses_super {95		evaluate_compspecs(96			ctx,97			&comp.compspecs,98			&cached_overs,99			0,100			0,101			&mut ObjCompCollectorUnbound {102				builder: &mut builder,103				locals: comp.locals.clone(),104				this_id: comp.this,105				field: &comp.field,106			},107		)?;108	} else {109		evaluate_compspecs(110			ctx,111			&comp.compspecs,112			&cached_overs,113			0,114			0,115			&mut ObjCompCollectorStatic {116				builder: &mut builder,117				locals: &comp.locals,118				field: &comp.field,119			},120		)?;121	}122123	Ok(Val::Obj(builder.build()))124}125126pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {127	let cached_overs = cache_overs(&ctx, &comp.compspecs)?;128129	// In eager evaluation, Context is not captured, thus updates in CoW fashion will likely to success130	'eager: {131		let mut out = Vec::new();132133		if evaluate_compspecs_eager(134			ctx.clone(),135			&comp.compspecs,136			&cached_overs,137			0,138			0,139			&mut EagerArrCollector {140				out: &mut out,141				value: &comp.value,142			},143		)144		.is_err()145		{146			break 'eager;147		}148		return Ok(Val::arr(out));149	}150151	let mut items: Vec<Thunk<Val>> = Vec::new();152	evaluate_compspecs(153		ctx,154		&comp.compspecs,155		&cached_overs,156		0,157		0,158		&mut LazyArrCollector {159			out: &mut items,160			value: &comp.value,161		},162	)?;163	Ok(Val::arr(items))164}165166fn cache_overs(ctx: &Context, specs: &[LCompSpec]) -> Result<Vec<Option<ArrValue>>> {167	specs168		.iter()169		.map(|spec| {170			Ok(match spec {171				LCompSpec::For {172					over,173					loop_invariant: true,174					..175				} => {176					let val = evaluate(ctx.clone(), over)?;177					let Val::Arr(arr) = val else {178						bail!(InComprehensionCanOnlyIterateOverArray)179					};180					Some(arr)181				}182				_ => None,183			})184		})185		.collect::<Result<_>>()186}187188fn evaluate_compspecs_eager(189	ctx: Context,190	specs: &[LCompSpec],191	cached_overs: &[Option<ArrValue>],192	idx: usize,193	guaranteed_reserve: usize,194	collector: &mut dyn CompCollector,195) -> Result<()> {196	if idx >= specs.len() {197		collector.reserve(guaranteed_reserve);198		return collector.collect(ctx);199	}200	match &specs[idx] {201		LCompSpec::If(cond) => {202			let val = evaluate(ctx.clone(), cond)?;203			let Val::Bool(b) = val else {204				bail!(TypeMismatch(205					"if spec condition",206					vec![ValType::Bool],207					val.value_type()208				))209			};210			if b {211				evaluate_compspecs_eager(ctx, specs, cached_overs, idx + 1, 0, collector)?;212			}213		}214		LCompSpec::For { destruct, over, .. } => {215			let arr = if let Some(cached) = &cached_overs[idx] {216				cached.clone()217			} else {218				let arr_val = evaluate(ctx.clone(), over)?;219				let Val::Arr(arr) = arr_val else {220					bail!(InComprehensionCanOnlyIterateOverArray)221				};222				arr223			};224			let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;225			match destruct {226				LDestruct::Full(id) => {227					let id = *id;228					let mut inner_ctx = ContextBuilder::extend(ctx, 1).build();229					for (i, item) in arr.iter().enumerate() {230						// TODO: reuse one ContextBuilder for full evaluate_compspecs pipeline231						inner_ctx.cow_fill_binding(id, Thunk::evaluated(item?));232						evaluate_compspecs_eager(233							inner_ctx.clone(),234							specs,235							cached_overs,236							idx + 1,237							if i == 0 { inner_reserve } else { 0 },238							collector,239						)?;240					}241				}242				// TODO: Should not be eager? CoW won't work here243				#[cfg(feature = "exp-destruct")]244				_ => {245					for (i, item) in arr.iter().enumerate() {246						let item_val = item?;247						let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);248						let fctx = Pending::new();249						destructure::destruct(250							destruct,251							Thunk::evaluated(item_val),252							fctx.clone(),253							&mut inner_builder,254						);255						let inner_ctx = inner_builder.build().into_future(fctx);256						evaluate_compspecs_eager(257							inner_ctx,258							specs,259							cached_overs,260							idx + 1,261							if i == 0 { inner_reserve } else { 0 },262							collector,263						)?;264					}265				}266			}267		}268	}269	Ok(())270}271272fn evaluate_compspecs(273	ctx: Context,274	specs: &[LCompSpec],275	cached_overs: &[Option<ArrValue>],276	idx: usize,277	guaranteed_reserve: usize,278	collector: &mut dyn CompCollector,279) -> Result<()> {280	if idx >= specs.len() {281		collector.reserve(guaranteed_reserve);282		return collector.collect(ctx);283	}284	match &specs[idx] {285		LCompSpec::If(cond) => {286			let val = evaluate(ctx.clone(), cond)?;287			let Val::Bool(b) = val else {288				bail!(TypeMismatch(289					"if spec condition",290					vec![ValType::Bool],291					val.value_type()292				))293			};294			if b {295				evaluate_compspecs(ctx, specs, cached_overs, idx + 1, 0, collector)?;296			}297		}298		LCompSpec::For { destruct, over, .. } => {299			let arr = if let Some(cached) = &cached_overs[idx] {300				cached.clone()301			} else {302				let arr_val = evaluate(ctx.clone(), over)?;303				let Val::Arr(arr) = arr_val else {304					bail!(InComprehensionCanOnlyIterateOverArray)305				};306				arr307			};308			let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;309			for (i, item) in arr.iter().enumerate() {310				let item_val = item?;311				let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);312				let fctx = Pending::new();313				destructure::destruct(314					destruct,315					Thunk::evaluated(item_val),316					fctx.clone(),317					&mut inner_builder,318				);319				let inner_ctx = inner_builder.build().into_future(fctx);320				evaluate_compspecs(321					inner_ctx,322					specs,323					cached_overs,324					idx + 1,325					if i == 0 { inner_reserve } else { 0 },326					collector,327				)?;328			}329		}330	}331	Ok(())332}
modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -12,7 +12,7 @@
 #[allow(dead_code, reason = "not dead in exp-destruct")]
 fn destruct_array(
 	start: &[LDestruct],
-	rest: Option<LDestructRest>,
+	rest: Option<&LDestructRest>,
 	end: &[LDestruct],
 
 	value: Thunk<Val>,
@@ -56,7 +56,7 @@
 	if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {
 		let full = full.clone();
 		builder.bind(
-			id,
+			*id,
 			Thunk!(move || {
 				let full = full.evaluate()?;
 				let to = full.len() - end_len;
@@ -88,7 +88,7 @@
 #[allow(dead_code, reason = "not dead in exp-destruct")]
 fn destruct_object(
 	fields: &[LDestructField],
-	rest: Option<LDestructRest>,
+	rest: Option<&LDestructRest>,
 
 	value: Thunk<Val>,
 	fctx: Pending<Context>,
@@ -127,7 +127,7 @@
 	if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {
 		let full = full.clone();
 		builder.bind(
-			id,
+			*id,
 			Thunk!(move || {
 				let full = full.evaluate()?;
 				let mut out = ObjValueBuilder::new();
@@ -178,9 +178,13 @@
 		#[cfg(feature = "exp-destruct")]
 		LDestruct::Skip => {}
 		#[cfg(feature = "exp-destruct")]
-		LDestruct::Array { start, rest, end } => destruct_array(start, rest, end, value, fctx, builder),
+		LDestruct::Array { start, rest, end } => {
+			destruct_array(start, rest.as_ref(), end, value, fctx, builder)
+		}
 		#[cfg(feature = "exp-destruct")]
-		LDestruct::Object { fields, rest } => destruct_object(fields, rest, value, fctx, builder),
+		LDestruct::Object { fields, rest } => {
+			destruct_object(fields, rest.as_ref(), value, fctx, builder)
+		}
 	}
 }
 
modifiedcrates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snap
+++ b/crates/jrsonnet-evaluator/src/snapshots/jrsonnet_evaluator__analyze__tests__snapshots@redeclared_local.jsonnet.snap
@@ -1,7 +1,7 @@
 ---
 source: crates/jrsonnet-evaluator/src/analyze.rs
 expression: rendered
-input_file: crates/jrsonnet-evaluator/src/analyze_tests/redeclared_local.jsonnet
+input_file: crates/jrsonnet-evaluator/src/analysis_tests/redeclared_local.jsonnet
 ---
 --- source ---
 local x = 1, x = 2; x
@@ -10,7 +10,7 @@
 local_dependent_depth: 0
 errored: true
 --- diagnostics ---
-   ·              ╭── variable redeclared: x
+   ·              ╭── local is already defined in the current frame: x
 1  │ local x = 1, x = 2; x 
 2  │  
 --- lir ---
modifiedcrates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -381,7 +381,7 @@
 				None
 			};
 			let default = if p.try_eat(T![=]) {
-				Some(Rc::new(spanned(p, expr)?))
+				Some(spanned(p, expr)?)
 			} else {
 				None
 			};
@@ -466,8 +466,10 @@
 		if !p.at(SyntaxKind::IDENT) {
 			let d = destruct(p)?;
 			p.eat(T![=])?;
-			let value = Rc::new(expr(p)?);
-			return Ok(BindSpec::Field { into: d, value });
+			return Ok(BindSpec::Field {
+				into: d,
+				value: expr(p)?,
+			});
 		}
 	}
 	let name_spanned = spanned(p, ident)?;
modifiedcrates/jrsonnet-ir/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-ir/src/expr.rs
+++ b/crates/jrsonnet-ir/src/expr.rs
@@ -211,7 +211,7 @@
 	}
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Acyclic)]
+#[derive(Debug, PartialEq, Eq, Acyclic)]
 pub enum DestructRest {
 	/// ...rest
 	Keep(IStr),
@@ -219,7 +219,7 @@
 	Drop,
 }
 
-#[derive(Debug, Clone, PartialEq, Acyclic)]
+#[derive(Debug, PartialEq, Acyclic)]
 pub enum Destruct {
 	Full(Spanned<IStr>),
 	#[cfg(feature = "exp-destruct")]
@@ -233,7 +233,7 @@
 	#[cfg(feature = "exp-destruct")]
 	Object {
 		#[allow(clippy::type_complexity)]
-		fields: Vec<(IStr, Option<Destruct>, Option<Rc<Spanned<Expr>>>)>,
+		fields: Vec<(IStr, Option<Destruct>, Option<Spanned<Expr>>)>,
 		rest: Option<DestructRest>,
 	},
 }
modifiedcrates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-peg-parser/src/lib.rs
+++ b/crates/jrsonnet-peg-parser/src/lib.rs
@@ -1,5 +1,3 @@
-use std::rc::Rc;
-
 use jrsonnet_gcmodule::Acyclic;
 use jrsonnet_ir::{
 	ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BindSpec, CompSpec, Destruct, DestructRest, Expr,
@@ -110,7 +108,7 @@
 			}
 		pub rule destruct_object(s: &ParserSettings) -> Destruct
 			= "{" _
-				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:spanned(<expr(s)>, s) {v})? {(name, into, default.map(Rc::new))})**comma()
+				fields:(name:id() into:(_ ":" _ into:destruct(s) {into})? default:(_ "=" _ v:spanned(<expr(s)>, s) {v})? {(name, into, default)})**comma()
 				rest:(
 					comma() rest:destruct_rest()? {rest}
 					/ comma()? {None}