git.delta.rocks / jrsonnet / refs/commits / d6e3ee65093c

difftreelog

fix align object iteration with context refactor

wnuqoyquYaroslav Bolyukin2026-04-25parent: #e9c741d.patch.diff
in: master

2 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
after · crates/jrsonnet-evaluator/src/evaluate/destructure.rs
1use jrsonnet_ir::{BindSpec, Destruct};23use crate::{4	Context, ContextBuilder, Pending, Thunk, Val, error::Result, evaluate_method,5	evaluate_named_param,6};78#[allow(clippy::too_many_lines)]9#[allow(unused_variables)]10pub fn destruct(11	d: &Destruct,12	parent: Thunk<Val>,13	fctx: Pending<Context>,14	new_bindings: &mut ContextBuilder,15) -> Result<()> {16	match d {17		Destruct::Full(v) => {18			new_bindings.try_bind(v.clone(), parent)?;19		}20		#[cfg(feature = "exp-destruct")]21		Destruct::Skip => {}22		#[cfg(feature = "exp-destruct")]23		Destruct::Array { start, rest, end } => {24			use jrsonnet_ir::DestructRest;2526			use crate::bail;2728			let min_len = start.len() + end.len();29			let has_rest = rest.is_some();30			let full = Thunk!(move || {31				let v = parent.evaluate()?;32				let Val::Arr(arr) = v else {33					bail!("expected array");34				};35				if !has_rest {36					if arr.len() != min_len {37						bail!("expected {} elements, got {}", min_len, arr.len())38					}39				} else if arr.len() < min_len {40					bail!(41						"expected at least {} elements, but array was only {}",42						min_len,43						arr.len()44					)45				}46				Ok(arr)47			});4849			{50				for (i, d) in start.iter().enumerate() {51					let full = full.clone();52					destruct(53						d,54						Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),55						fctx.clone(),56						new_bindings,57					)?;58				}59			}6061			match rest {62				Some(DestructRest::Keep(v)) => {63					let start = start.len();64					let end = end.len();65					let full = full.clone();66					destruct(67						&Destruct::Full(v.clone()),68						Thunk!(move || {69							let full = full.evaluate()?;70							let to = full.len() - end;71							Ok(Val::Arr(full.slice(72								Some(start as i32),73								Some(to as i32),74								None,75							)))76						}),77						fctx.clone(),78						new_bindings,79					)?;80				}81				Some(DestructRest::Drop) | None => {}82			}8384			{85				for (i, d) in end.iter().enumerate() {86					let full = full.clone();87					let end = end.len();88					destruct(89						d,90						Thunk!(move || {91							let full = full.evaluate()?;92							Ok(full.get(full.len() - end + i)?.expect("length is checked"))93						}),94						fctx.clone(),95						new_bindings,96					)?;97				}98			}99		}100		#[cfg(feature = "exp-destruct")]101		Destruct::Object { fields, rest } => {102			use jrsonnet_ir::DestructRest;103			use rustc_hash::FxHashSet;104105			use crate::{ObjValueBuilder, bail};106107			let captured_fields: FxHashSet<_> = fields.iter().map(|f| f.0.clone()).collect();108			let field_names: Vec<_> = fields109				.iter()110				.map(|f| (f.0.clone(), f.2.is_some()))111				.collect();112			let has_rest = rest.is_some();113			let full = Thunk!(move || {114				let v = parent.evaluate()?;115				let Val::Obj(obj) = v else {116					bail!("expected object");117				};118				for (field, has_default) in &field_names {119					if !has_default && !obj.has_field_ex(field.clone(), true) {120						bail!("missing field: {field}");121					}122				}123				if !has_rest {124					let len = obj.len();125					if len > field_names.len() {126						bail!("too many fields, and rest not found");127					}128				}129				Ok(obj)130			});131132			match rest {133				Some(DestructRest::Keep(v)) => {134					let full = full.clone();135					destruct(136						&Destruct::Full(v.clone()),137						Thunk!(move || {138							let full = full.evaluate()?;139							let mut builder = ObjValueBuilder::new();140							builder.extend_with_core(full.as_standalone());141							builder.with_fields_omitted(captured_fields);142							Ok(Val::Obj(builder.build()))143						}),144						fctx.clone(),145						new_bindings,146					)?;147				}148				Some(DestructRest::Drop) | None => {}149			}150151			for (field, d, default) in fields {152				let default = default.clone().map(|e| (fctx.clone(), e));153				let value = {154					let field = field.clone();155					let full = full.clone();156					Thunk!(move || {157						let full = full.evaluate()?;158						if let Some(field) = full.get(field)? {159							Ok(field)160						} else {161							let (fctx, expr) = default.as_ref().expect("shape is checked");162							Ok(crate::evaluate(fctx.clone().unwrap(), expr)?)163						}164					})165				};166167				if let Some(d) = d {168					destruct(d, value, fctx.clone(), new_bindings)?;169				} else {170					destruct(171						&Destruct::Full(field.clone()),172						value,173						fctx.clone(),174						new_bindings,175					)?;176				}177			}178		}179	}180	Ok(())181}182183pub fn evaluate_dest(184	d: &BindSpec,185	fctx: Pending<Context>,186	new_bindings: &mut ContextBuilder,187) -> Result<()> {188	match d {189		BindSpec::Field { into, value } => {190			let name = into.name();191			let value = value.clone();192			let data = {193				let fctx = fctx.clone();194				Thunk!(move || evaluate_named_param(fctx.unwrap(), &value, name))195			};196			destruct(into, data, fctx, new_bindings)?;197		}198		BindSpec::Function {199			name,200			params,201			value,202		} => {203			let params = params.clone();204			let name = name.clone();205			let value = value.clone();206			new_bindings.try_bind(207				name.clone(),208				Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value))),209			)?;210		}211	}212	Ok(())213}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -145,28 +145,36 @@
 						)?;
 					}
 				}
-				#[cfg(feature = "exp-object-iteration")]
-				Val::Obj(obj) => {
+				Val::Obj(obj) if cfg!(feature = "exp-object-iteration") => {
 					let fields = obj.fields(
 						// TODO: Should there be ability to preserve iteration order?
 						#[cfg(feature = "exp-preserve-order")]
 						false,
 					);
 					guaranteed_reserve = guaranteed_reserve.max(1) * fields.len();
-					for field in fields {
+					for (i, field) in fields.into_iter().enumerate() {
 						let fctx = Pending::new();
-						let mut new_bindings = FxHashMap::with_capacity(into.binds_len());
+						let mut ctx = ContextBuilder::extend_fast(ctx.clone());
 						let obj = obj.clone();
 						let value = Thunk::evaluated(Val::arr(vec![
 							Thunk::evaluated(Val::string(field.clone())),
-							obj.get_lazy(field).transpose().expect(
+							obj.get_lazy(field).expect(
 								"field exists, as field name was obtained from object.fields()",
 							),
 						]));
-						destruct(into, value, fctx.clone(), &mut new_bindings)?;
-						let ctx = ctx.clone().extend_bindings(new_bindings).into_future(fctx);
+						destruct(into, value, fctx.clone(), &mut ctx)?;
+						let ctx = ctx.build().into_future(fctx);
 
-						evaluate_comp(ctx, &specs[1..], callback)?;
+						evaluate_comp(
+							ctx,
+							&specs[1..],
+							if i == 0 || !specs.is_empty() {
+								guaranteed_reserve
+							} else {
+								0
+							},
+							callback,
+						)?;
 					}
 				}
 				_ => bail!(InComprehensionCanOnlyIterateOverArray),