git.delta.rocks / jrsonnet / refs/commits / 3c251da01a10

difftreelog

feat minimal tco

yzrzokypYaroslav Bolyukin2026-05-07parent: #e468723.patch.diff
in: master

3 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/evaluate/destructure.rs
1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6	Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,7	analyze::{8		ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LLocalExpr, LocalSlot,9	},10	bail,11	evaluate::evaluate,12};1314#[allow(dead_code, reason = "not dead in exp-destruct")]15fn destruct_array(16	start: &[LDestruct],17	rest: Option<&LDestructRest>,18	end: &[LDestruct],1920	fill: &LocalsFrame,21	value: Thunk<Val>,22	ctx: &Context,23) {24	let min_len = start.len() + end.len();25	let has_rest = rest.is_some();26	let full = Thunk!(move || {27		let v = value.evaluate()?;28		let Val::Arr(arr) = v else {29			bail!("expected array");30		};31		if !has_rest {32			if arr.len() != min_len {33				bail!("expected {} elements, got {}", min_len, arr.len32())34			}35		} else if arr.len() < min_len {36			bail!(37				"expected at least {} elements, but array was only {}",38				min_len,39				arr.len32()40			)41		}42		Ok(arr)43	});4445	for (i, d) in start.iter().enumerate() {46		let full = full.clone();47		destruct(48			d,49			fill,50			Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),51			ctx,52		);53	}5455	let start_len = start.len();56	let end_len = end.len();5758	if let Some(LDestructRest::Keep(slot)) = rest {59		let full = full.clone();60		fill.set(61			*slot,62			Thunk!(move || {63				let full = full.evaluate()?;64				let to = full.len() - end_len;65				Ok(Val::Arr(full.slice(start_len..to)))66			}),67		);68	}6970	for (i, d) in end.iter().enumerate() {71		let full = full.clone();72		destruct(73			d,74			fill,75			Thunk!(move || {76				let full = full.evaluate()?;77				Ok(full78					.get(full.len() - end_len + i)?79					.expect("length is checked"))80			}),81			ctx,82		);83	}84}8586#[allow(dead_code, reason = "not dead in exp-destruct")]87fn destruct_object(88	fields: &[LDestructField],89	rest: Option<&LDestructRest>,9091	fill: &LocalsFrame,92	value: Thunk<Val>,93	ctx: &Context,94) {95	use jrsonnet_interner::IStr;96	use rustc_hash::FxHashSet;9798	use crate::{ObjValueBuilder, bail};99100	let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();101	let field_names: Vec<(IStr, bool)> = fields102		.iter()103		.map(|f| (f.name.clone(), f.default.is_some()))104		.collect();105	let has_rest = rest.is_some();106	let full = Thunk!(move || {107		let v = value.evaluate()?;108		let Val::Obj(obj) = v else {109			bail!("expected object");110		};111		for (field, has_default) in &field_names {112			if !has_default && !obj.has_field_ex(field.clone(), true) {113				bail!("missing field: {field}");114			}115		}116		if !has_rest {117			let len = obj.len32();118			if len as usize > field_names.len() {119				bail!("too many fields, and rest not found");120			}121		}122		Ok(obj)123	});124125	if let Some(LDestructRest::Keep(slot)) = rest {126		let full = full.clone();127		fill.set(128			*slot,129			Thunk!(move || {130				let full = full.evaluate()?;131				let mut out = ObjValueBuilder::new();132				out.extend_with_core(full.as_standalone());133				out.with_fields_omitted(captured_fields);134				Ok(Val::Obj(out.build()))135			}),136		);137	}138139	for field in fields {140		let field_name = field.name.clone();141		let default_thunk: Option<Thunk<Val>> = field.default.as_ref().map(|(shape, expr)| {142			let expr = expr.clone();143			let env = Context::enter_using(ctx, shape);144			Thunk!(move || evaluate(env, &expr))145		});146147		let field_full = full.clone();148		let value_thunk = Thunk!(move || {149			let obj = field_full.evaluate()?;150			obj.get(field_name)?.map_or_else(151				|| default_thunk.as_ref().expect("shape is checked").evaluate(),152				Ok,153			)154		});155156		if let Some(into) = &field.into {157			destruct(into, fill, value_thunk, ctx);158		} else {159			unreachable!("analyzer lowers object-destruct shorthands into `into`");160		}161	}162}163164#[allow(unused_variables)]165pub fn destruct(d: &LDestruct, fill: &LocalsFrame, value: Thunk<Val>, a_ctx: &Context) {166	match d {167		LDestruct::Full(slot) => fill.set(*slot, value),168		#[cfg(feature = "exp-destruct")]169		LDestruct::Skip => {}170		#[cfg(feature = "exp-destruct")]171		LDestruct::Array { start, rest, end } => {172			destruct_array(start, rest.as_ref(), end, fill, value, a_ctx)173		}174		#[cfg(feature = "exp-destruct")]175		LDestruct::Object { fields, rest } => destruct_object(fields, rest.as_ref(), fill, value, a_ctx),176	}177}178179pub fn fill_letrec_binds(fill: &LocalsFrame, ctx: &Context, binds: &[LBind]) {180	for bind in binds {181		let expr = bind.value.clone();182		let env = Context::enter_using(ctx, &bind.value_shape);183		destruct(184			&bind.destruct,185			fill,186			Thunk!(move || evaluate(env, &expr)),187			ctx,188		);189	}190}191192pub fn evaluate_local_expr(parent: Context, l: &LLocalExpr) -> Result<Val> {193	let ctx = parent194		.pack_captures_sup_this(&l.frame_shape)195		.enter(|fill, ctx| {196			fill_letrec_binds(fill, ctx, &l.binds);197		});198	evaluate(ctx, &l.body)199}200201pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}202impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}203204pub fn evaluate_locals_unbound(205	outer: &Context,206	frame_shape: &ClosureShape,207	this_slot: Option<LocalSlot>,208	locals: Rc<Vec<LBind>>,209) -> impl CloneableUnbound<Context> {210	#[derive(Trace, Clone)]211	struct UnboundLocals {212		captures: PackedContext,213		this_slot: Option<LocalSlot>,214		locals: Rc<Vec<LBind>>,215	}216	impl Unbound for UnboundLocals {217		type Bound = Context;218219		fn bind(&self, sup_this: SupThis) -> Result<Context> {220			Ok(self.captures.clone().enter(sup_this, |fill, ctx| {221				if let Some(slot) = self.this_slot {222					let this_obj = ctx.sup_this().expect("sup_this set above").this().clone();223					fill.set(slot, Thunk::evaluated(Val::Obj(this_obj)));224				}225				fill_letrec_binds(fill, ctx, &self.locals);226			}))227		}228	}229230	UnboundLocals {231		captures: outer.pack_captures(frame_shape),232		this_slot,233		locals,234	}235}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -7,7 +7,7 @@
 
 use self::{
 	compspec::{evaluate_arr_comp, evaluate_obj_comp},
-	destructure::{evaluate_local_expr, evaluate_locals_unbound},
+	destructure::evaluate_locals_unbound,
 	operator::evaluate_binary_op_special,
 };
 use crate::{
@@ -116,129 +116,143 @@
 }
 
 #[allow(clippy::too_many_lines)]
-pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {
-	Ok(match expr {
-		LExpr::Null => Val::Null,
-		LExpr::Bool(b) => Val::Bool(*b),
-		LExpr::Str(s) => Val::string(s.clone()),
-		LExpr::Num(n) => Val::Num(*n),
-		LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,
-		LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),
-		LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),
-		LExpr::UnaryOp(op, value) => {
-			let value = evaluate(ctx, value)?;
-			evaluate_unary_op(*op, &value)?
-		}
-		LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,
-		LExpr::LocalExpr(local_expr) => evaluate_local_expr(ctx, local_expr)?,
-		LExpr::IfElse {
-			cond,
-			cond_then,
-			cond_else,
-		} => {
-			let cond_val = evaluate(ctx.clone(), cond)?;
-			let Val::Bool(b) = cond_val else {
-				bail!(TypeMismatch(
-					"if condition",
-					vec![ValType::Bool],
-					cond_val.value_type()
-				))
-			};
-			if b {
-				evaluate(ctx, cond_then)?
-			} else if let Some(e) = cond_else {
-				evaluate(ctx, e)?
-			} else {
+pub fn evaluate(mut ctx: Context, mut expr: &LExpr) -> Result<Val> {
+	loop {
+		return Ok(match expr {
+			LExpr::Null => Val::Null,
+			LExpr::Bool(b) => Val::Bool(*b),
+			LExpr::Str(s) => Val::string(s.clone()),
+			LExpr::Num(n) => Val::Num(*n),
+			LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,
+			LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),
+			LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),
+			LExpr::UnaryOp(op, value) => {
+				let value = evaluate(ctx, value)?;
+				evaluate_unary_op(*op, &value)?
+			}
+			LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,
+			LExpr::LocalExpr(l) => {
+				ctx = ctx
+					.pack_captures_sup_this(&l.frame_shape)
+					.enter(|fill, ctx| {
+						fill_letrec_binds(fill, ctx, &l.binds);
+					});
+				expr = &l.body;
+				continue;
+			}
+			LExpr::IfElse {
+				cond,
+				cond_then,
+				cond_else,
+			} => {
+				let cond_val = evaluate(ctx.clone(), cond)?;
+				let Val::Bool(b) = cond_val else {
+					bail!(TypeMismatch(
+						"if condition",
+						vec![ValType::Bool],
+						cond_val.value_type()
+					))
+				};
+				if b {
+					expr = cond_then;
+					continue;
+				} else if let Some(e) = cond_else {
+					expr = e;
+					continue;
+				}
 				Val::Null
 			}
-		}
-		LExpr::Error(s, e) => in_frame(
-			CallLocation::new(s),
-			|| "error statement".to_owned(),
-			|| bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
-		)?,
-		LExpr::AssertExpr { assert, rest } => {
-			evaluate_assert(ctx.clone(), assert)?;
-			evaluate(ctx, rest)?
-		}
+			LExpr::Error(s, e) => in_frame(
+				CallLocation::new(s),
+				|| "error statement".to_owned(),
+				|| bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
+			)?,
+			LExpr::AssertExpr { assert, rest } => {
+				evaluate_assert(ctx.clone(), assert)?;
+				expr = rest;
+				continue;
+			}
 
-		LExpr::Function(func) => evaluate_method(
-			ctx,
-			func.name.clone().unwrap_or_else(names::anonymous),
-			func,
-		),
-		LExpr::IdentityFunction => Val::Func(FuncVal::identity()),
-		LExpr::Apply {
-			applicable,
-			args,
-			tailstrict,
-		} => evaluate_apply(
-			ctx,
-			applicable,
-			args,
-			CallLocation::new(&args.span),
-			*tailstrict,
-		)?,
-		LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,
-		LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,
-		LExpr::ObjExtend(lhs, body) => {
-			let lhs_val = evaluate(ctx.clone(), lhs)?;
-			let Val::Obj(lhs_obj) = lhs_val else {
-				bail!(TypeMismatch(
-					"object extend lhs",
-					vec![ValType::Obj],
-					lhs_val.value_type(),
-				))
-			};
-			evaluate_obj_body(Some(lhs_obj), ctx, body)?
-		}
-		LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,
-		LExpr::Slice(slice) => {
-			let val = evaluate(ctx.clone(), &slice.value)?;
-			let indexable = val.into_indexable()?;
-			let start = slice
-				.start
-				.as_ref()
-				.map(|e| evaluate(ctx.clone(), e))
-				.transpose()?
-				.map(|v| -> Result<i32> { i32::from_untyped(v).description("slice start value") })
-				.transpose()?;
-			let end = slice
-				.end
-				.as_ref()
-				.map(|e| evaluate(ctx.clone(), e))
-				.transpose()?
-				.map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })
-				.transpose()?;
-			let step = slice
-				.step
-				.as_ref()
-				.map(|e| evaluate(ctx, e))
-				.transpose()?
-				.map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {
-					BoundedUsize::from_untyped(v).description("slice step value")
+			LExpr::Function(func) => evaluate_method(
+				ctx,
+				func.name.clone().unwrap_or_else(names::anonymous),
+				func,
+			),
+			LExpr::IdentityFunction => Val::Func(FuncVal::identity()),
+			LExpr::Apply {
+				applicable,
+				args,
+				tailstrict,
+			} => evaluate_apply(
+				ctx,
+				applicable,
+				args,
+				CallLocation::new(&args.span),
+				*tailstrict,
+			)?,
+			LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,
+			LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,
+			LExpr::ObjExtend(lhs, body) => {
+				let lhs_val = evaluate(ctx.clone(), lhs)?;
+				let Val::Obj(lhs_obj) = lhs_val else {
+					bail!(TypeMismatch(
+						"object extend lhs",
+						vec![ValType::Obj],
+						lhs_val.value_type(),
+					))
+				};
+				evaluate_obj_body(Some(lhs_obj), ctx, body)?
+			}
+			LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,
+			LExpr::Slice(slice) => {
+				let val = evaluate(ctx.clone(), &slice.value)?;
+				let indexable = val.into_indexable()?;
+				let start = slice
+					.start
+					.as_ref()
+					.map(|e| evaluate(ctx.clone(), e))
+					.transpose()?
+					.map(|v| -> Result<i32> {
+						i32::from_untyped(v).description("slice start value")
+					})
+					.transpose()?;
+				let end = slice
+					.end
+					.as_ref()
+					.map(|e| evaluate(ctx.clone(), e))
+					.transpose()?
+					.map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })
+					.transpose()?;
+				let step = slice
+					.step
+					.as_ref()
+					.map(|e| evaluate(ctx, e))
+					.transpose()?
+					.map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {
+						BoundedUsize::from_untyped(v).description("slice step value")
+					})
+					.transpose()?;
+				Val::from(indexable.slice32(start, end, step)?)
+			}
+			LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),
+			LExpr::Import {
+				kind,
+				kind_span,
+				path,
+			} => with_state(|state| {
+				let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;
+				Ok::<_, Error>(match kind.value {
+					ImportKind::Normal => in_frame(
+						CallLocation::new(&kind.span),
+						|| "import".to_string(),
+						|| state.import_resolved(resolved),
+					)?,
+					ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),
+					ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),
 				})
-				.transpose()?;
-			Val::from(indexable.slice32(start, end, step)?)
-		}
-		LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),
-		LExpr::Import {
-			kind,
-			kind_span,
-			path,
-		} => with_state(|state| {
-			let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;
-			Ok::<_, Error>(match kind.value {
-				ImportKind::Normal => in_frame(
-					CallLocation::new(&kind.span),
-					|| "import".to_string(),
-					|| state.import_resolved(resolved),
-				)?,
-				ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),
-				ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),
-			})
-		})?,
-	})
+			})?,
+		});
+	}
 }
 
 fn evaluate_apply(
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -14,7 +14,8 @@
 	Context, PackedContextSupThis, Result, Thunk, Val,
 	analyze::LFunction,
 	arr::arridx,
-	evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},
+	ensure_sufficient_stack,
+	evaluate::{destructure::destruct, evaluate, evaluate_trivial},
 	function::builtin::BuiltinFunc,
 };
 
@@ -68,7 +69,7 @@
 		self.func.signature.clone()
 	}
 
-	pub fn call(
+	fn call(
 		&self,
 		unnamed: &[Thunk<Val>],
 		named: &[Thunk<Val>],