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

difftreelog

feat forObj evaluation

pvwvukvwYaroslav Bolyukin2026-05-06parent: #30703de.patch.diff
in: master

4 files changed

modifiedcrates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth
410 /// Is `over` does not depend on any variable introduced by an earlier for-spec in this comprehension chain410 /// Is `over` does not depend on any variable introduced by an earlier for-spec in this comprehension chain
411 loop_invariant: bool,411 loop_invariant: bool,
412 },412 },
413 #[cfg(feature = "exp-object-iteration")]
414 ForObj {
415 frame_shape: ClosureShape,
416 key: LocalSlot,
417 visibility: jrsonnet_ir::Visibility,
418 value: LDestruct,
419 over: LExpr,
420 loop_invariant: bool,
421 },
413}422}
414423
415struct FrameAlloc<'s> {424struct FrameAlloc<'s> {
1863 (r, rest)1872 (r, rest)
1864 }1873 }
1865 #[cfg(feature = "exp-object-iteration")]1874 #[cfg(feature = "exp-object-iteration")]
1866 CompSpec::ForObjSpec(_) => todo!(),1875 CompSpec::ForObjSpec(data) => {
1876 let mut over_taint = AnalysisResult::default();
1877 let over_l = analyze(&data.over, stack, &mut over_taint);
1878 let loop_invariant = over_taint.local_dependent_depth > outer_depth;
1879 taint.taint_by(over_taint);
1880
1881 let mut alloc = FrameAlloc::new(stack);
1882 let closure = alloc.push_locals_closure();
1883 let Some((_, key_slot)) = alloc.define_local(data.key.clone(), None) else {
1884 stack.pop_closure(closure);
1885 return go(idx + 1, specs, outer_depth, stack, taint, inside);
1886 };
1887 let Some(l_value) = alloc.alloc_destruct(&data.value) else {
1888 stack.pop_closure(closure);
1889 return go(idx + 1, specs, outer_depth, stack, taint, inside);
1890 };
1891 let mut pending = alloc.finish();
1892
1893 let var_analysis = AnalysisResult::default();
1894 pending.record_spec_init(&LDestruct::Full(key_slot), var_analysis);
1895 pending.record_spec_init(&l_value, var_analysis);
1896
1897 let body_frame = pending.finish();
1898 let (r, mut rest) =
1899 go(idx + 1, specs, outer_depth, body_frame.stack, taint, inside);
1900 body_frame.finish();
1901 let frame_shape = stack.pop_closure(closure);
1902
1903 rest.insert(
1904 0,
1905 LCompSpec::ForObj {
1906 frame_shape,
1907 key: key_slot,
1908 visibility: data.visibility,
1909 value: l_value,
1910 over: over_l,
1911 loop_invariant,
1912 },
1913 );
1914 (r, rest)
1915 }
1867 }1916 }
1868 }1917 }
1869 let outer_depth = stack.depth;1918 let outer_depth = stack.depth;
1970 use super::*;2019 use super::*;
19712020
1972 #[test]2021 #[test]
2022 #[cfg(not(feature = "exp-null-coaelse"))]
1973 fn snapshots() {2023 fn snapshots() {
1974 glob!("analysis_tests/*.jsonnet", |path| {2024 glob!("analysis_tests/*.jsonnet", |path| {
1975 let code = fs::read_to_string(path).expect("read test file");2025 let code = fs::read_to_string(path).expect("read test file");
modifiedcrates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
@@ -6,6 +6,9 @@
 	destructure::{destruct, evaluate_locals_unbound, fill_letrec_binds},
 	evaluate_field_member_static, evaluate_field_member_unbound,
 };
+#[cfg(feature = "exp-object-iteration")]
+use jrsonnet_interner::IStr;
+
 use crate::{
 	Context, ObjValue, ObjValueBuilder, Result, Thunk, Val,
 	analyze::{
@@ -18,6 +21,12 @@
 	evaluate::{evaluate, evaluate_trivial},
 };
 
+enum CachedOver {
+	Arr(ArrValue),
+	#[cfg(feature = "exp-object-iteration")]
+	Obj(ObjValue),
+}
+
 trait CompCollector {
 	fn reserve(&mut self, _guaranteed: usize) {}
 	fn collect(&mut self, ctx: Context) -> Result<()>;
@@ -215,7 +224,7 @@
 	Ok(Val::arr(items))
 }
 
-fn cache_overs(ctx: &Context, specs: &[LCompSpec]) -> Result<Vec<Option<ArrValue>>> {
+fn cache_overs(ctx: &Context, specs: &[LCompSpec]) -> Result<Vec<Option<CachedOver>>> {
 	specs
 		.iter()
 		.map(|spec| {
@@ -229,7 +238,23 @@
 					let Val::Arr(arr) = val else {
 						bail!(InComprehensionCanOnlyIterateOverArray)
 					};
-					Some(arr)
+					Some(CachedOver::Arr(arr))
+				}
+				#[cfg(feature = "exp-object-iteration")]
+				LCompSpec::ForObj {
+					over,
+					loop_invariant: true,
+					..
+				} => {
+					let val = evaluate(ctx.clone(), over)?;
+					let Val::Obj(obj) = val else {
+						bail!(TypeMismatch(
+							"object iteration over",
+							vec![jrsonnet_types::ValType::Obj],
+							val.value_type(),
+						))
+					};
+					Some(CachedOver::Obj(obj))
 				}
 				_ => None,
 			})
@@ -240,7 +265,7 @@
 fn evaluate_compspecs_eager(
 	ctx: Context,
 	specs: &[LCompSpec],
-	cached_overs: &[Option<ArrValue>],
+	cached_overs: &[Option<CachedOver>],
 	idx: usize,
 	guaranteed_reserve: usize,
 	collector: &mut dyn CompCollector,
@@ -269,7 +294,7 @@
 			over,
 			..
 		} => {
-			let arr = if let Some(cached) = &cached_overs[idx] {
+			let arr = if let Some(CachedOver::Arr(cached)) = &cached_overs[idx] {
 				cached.clone()
 			} else {
 				let arr_val = evaluate(ctx.clone(), over)?;
@@ -304,6 +329,10 @@
 				_ => unreachable!("eager compspecs are not possible with non-full patterns"),
 			}
 		}
+		#[cfg(feature = "exp-object-iteration")]
+		LCompSpec::ForObj { .. } => {
+			unreachable!("eager compspecs filter rejects ForObj");
+		}
 	}
 	Ok(())
 }
@@ -311,7 +340,7 @@
 fn evaluate_compspecs(
 	ctx: Context,
 	specs: &[LCompSpec],
-	cached_overs: &[Option<ArrValue>],
+	cached_overs: &[Option<CachedOver>],
 	idx: usize,
 	guaranteed_reserve: usize,
 	collector: &mut dyn CompCollector,
@@ -340,7 +369,7 @@
 			over,
 			..
 		} => {
-			let arr = if let Some(cached) = &cached_overs[idx] {
+			let arr = if let Some(CachedOver::Arr(cached)) = &cached_overs[idx] {
 				cached.clone()
 			} else {
 				let arr_val = evaluate(ctx.clone(), over)?;
@@ -365,6 +394,61 @@
 				)?;
 			}
 		}
+		#[cfg(feature = "exp-object-iteration")]
+		LCompSpec::ForObj {
+			frame_shape,
+			key,
+			visibility,
+			value,
+			over,
+			..
+		} => {
+			use jrsonnet_ir::Visibility;
+			let obj = if let Some(CachedOver::Obj(cached)) = &cached_overs[idx] {
+				cached.clone()
+			} else {
+				let val = evaluate(ctx.clone(), over)?;
+				let Val::Obj(obj) = val else {
+					bail!(TypeMismatch(
+						"object iteration over",
+						vec![ValType::Obj],
+						val.value_type(),
+					))
+				};
+				obj
+			};
+			let fields = obj.fields_with_visibility(
+				#[cfg(feature = "exp-preserve-order")]
+				false,
+			);
+			let pairs: Vec<(IStr, Visibility)> = fields
+				.into_iter()
+				.filter(|(_, v)| match visibility {
+					Visibility::Normal => v.is_visible(),
+					Visibility::Hidden => !v.is_visible(),
+					Visibility::Unhide => true,
+				})
+				.collect();
+			let inner_reserve = guaranteed_reserve.max(1) * pairs.len();
+			for (i, (field_name, _)) in pairs.into_iter().enumerate() {
+				let key_val = Val::string(field_name.clone());
+				let value_thunk = obj
+					.get_lazy(field_name.clone())
+					.expect("field exists, just enumerated");
+				let inner_ctx = ctx.pack_captures_sup_this(frame_shape).enter(|fill, ctx| {
+					fill.set(*key, Thunk::evaluated(key_val));
+					destruct(value, fill, value_thunk, &ctx);
+				});
+				evaluate_compspecs(
+					inner_ctx,
+					specs,
+					cached_overs,
+					idx + 1,
+					if i == 0 { inner_reserve } else { 0 },
+					collector,
+				)?;
+			}
+		}
 	}
 	Ok(())
 }
modifiedcrates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -909,6 +909,56 @@
 
 		out
 	}
+	pub fn fields_with_visibility(
+		&self,
+		#[cfg(feature = "exp-preserve-order")] preserve_order: bool,
+	) -> Vec<(IStr, Visibility)> {
+		#[cfg(feature = "exp-preserve-order")]
+		if preserve_order {
+			let (mut fields, mut keys): (Vec<_>, Vec<_>) = self
+				.fields_visibility()
+				.into_iter()
+				.enumerate()
+				.map(|(idx, (k, d))| {
+					(
+						(
+							k,
+							d.exists_visible.expect("non-existing fields filtered out"),
+						),
+						(d.sort_key(), idx),
+					)
+				})
+				.unzip();
+			keys.sort_unstable_by_key(|v| v.0);
+			for i in 0..fields.len() {
+				let x = fields[i].clone();
+				let mut j = i;
+				loop {
+					let k = keys[j].1;
+					keys[j].1 = j;
+					if k == i {
+						break;
+					}
+					fields[j] = fields[k].clone();
+					j = k;
+				}
+				fields[j] = x;
+			}
+			return fields;
+		}
+		let mut fields: Vec<_> = self
+			.fields_visibility()
+			.into_iter()
+			.map(|(k, d)| {
+				(
+					k,
+					d.exists_visible.expect("non-existing fields filtered out"),
+				)
+			})
+			.collect();
+		fields.sort_unstable_by(|a, b| a.0.cmp(&b.0));
+		fields
+	}
 	pub fn fields_ex(
 		&self,
 		include_hidden: bool,
modifiedcrates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-peg-parser/src/lib.rs
+++ b/crates/jrsonnet-peg-parser/src/lib.rs
@@ -441,6 +441,7 @@
 	use crate::{ParserSettings, parse};
 
 	#[test]
+	#[cfg(not(feature = "exp-null-coaelse"))]
 	fn snapshots() {
 		glob!("tests/*.jsonnet", |path| {
 			let input = fs::read_to_string(path).expect("read test file");