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
--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -4,9 +4,7 @@
 
 use crate::{
 	Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,
-	analyze::{
-		ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LLocalExpr, LocalSlot,
-	},
+	analyze::{ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LocalSlot},
 	bail,
 	evaluate::evaluate,
 };
@@ -187,15 +185,6 @@
 			ctx,
 		);
 	}
-}
-
-pub fn evaluate_local_expr(parent: Context, l: &LLocalExpr) -> Result<Val> {
-	let ctx = parent
-		.pack_captures_sup_this(&l.frame_shape)
-		.enter(|fill, ctx| {
-			fill_letrec_binds(fill, ctx, &l.binds);
-		});
-	evaluate(ctx, &l.body)
 }
 
 pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}
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
before · crates/jrsonnet-evaluator/src/function/mod.rs
1use std::{fmt::Debug, rc::Rc};23use educe::Educe;4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;6use jrsonnet_ir::Span;7pub use jrsonnet_macros::builtin;89use self::{10	builtin::Builtin,11	prepared::{PreparedCall, parse_prepared_builtin_call},12};13use crate::{14	Context, PackedContextSupThis, Result, Thunk, Val,15	analyze::LFunction,16	arr::arridx,17	evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},18	function::builtin::BuiltinFunc,19};2021pub mod builtin;22mod native;23pub(crate) mod prepared;2425pub use jrsonnet_ir::function::*;26pub use native::NativeFn;27pub(crate) use prepared::PreparedFuncVal;2829/// Function callsite location.30/// Either from other jsonnet code, specified by expression location, or from native (without location).31#[derive(Clone, Copy)]32pub struct CallLocation<'l>(pub Option<&'l Span>);33impl<'l> CallLocation<'l> {34	/// Construct new location for calls coming from specified jsonnet expression location.35	pub const fn new(loc: &'l Span) -> Self {36		Self(Some(loc))37	}38}39impl CallLocation<'static> {40	/// Construct new location for calls coming from native code.41	pub const fn native() -> Self {42		Self(None)43	}44}4546/// Represents Jsonnet function defined in code.47#[derive(Trace, Educe)]48#[educe(Debug, PartialEq)]49pub struct FuncDesc {50	/// # Example51	///52	/// In expressions like this, deducted to `a`, unspecified otherwise.53	/// ```jsonnet54	/// local a = function() ...55	/// local a() ...56	/// { a: function() ... }57	/// { a() = ... }58	/// ```59	pub name: IStr,60	pub(crate) body_captures: PackedContextSupThis,6162	#[educe(PartialEq(method = Rc::ptr_eq))]63	pub func: Rc<LFunction>,64}6566impl FuncDesc {67	pub fn signature(&self) -> FunctionSignature {68		self.func.signature.clone()69	}7071	pub fn call(72		&self,73		unnamed: &[Thunk<Val>],74		named: &[Thunk<Val>],75		prepared: &PreparedCall,76	) -> Result<Val> {77		let body_ctx = self.body_captures.clone().enter(|fill, ctx| {78			// Place each provided arg-thunk into its destructured slots.79			for (param_idx, thunk) in unnamed.iter().enumerate() {80				destruct(81					&self.func.params[param_idx].destruct,82					fill,83					thunk.clone(),84					ctx,85				);86			}87			for &(param_idx, arg_idx) in prepared.named() {88				destruct(89					&self.func.params[param_idx].destruct,90					fill,91					named[arg_idx].clone(),92					ctx,93				);94			}9596			for &param_idx in prepared.defaults() {97				let param = &self.func.params[param_idx];98				let (shape, expr) = param.default.as_ref().expect("default exists");99				let expr = expr.clone();100				let env = Context::enter_using(ctx, shape);101102				destruct(103					&param.destruct,104					fill,105					Thunk!(move || evaluate(env, &expr)),106					ctx,107				);108			}109		});110111		ensure_sufficient_stack(|| evaluate(body_ctx, &self.func.body))112	}113114	pub fn evaluate_trivial(&self) -> Option<Val> {115		evaluate_trivial(&self.func.body)116	}117}118119/// Represents a Jsonnet function value, including plain functions and user-provided builtins.120#[allow(clippy::module_name_repetitions)]121#[derive(Trace, Clone)]122pub enum FuncVal {123	/// Plain function implemented in jsonnet.124	Normal(Cc<FuncDesc>),125	/// User-provided function.126	Builtin(BuiltinFunc),127}128129impl Debug for FuncVal {130	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {131		match self {132			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),133			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),134		}135	}136}137138#[allow(clippy::unnecessary_wraps)]139#[builtin]140pub const fn builtin_id(x: Thunk<Val>) -> Thunk<Val> {141	x142}143144impl FuncVal {145	pub fn builtin(builtin: impl Builtin) -> Self {146		Self::Builtin(BuiltinFunc::new(builtin))147	}148149	pub fn identity() -> Self {150		Self::builtin(builtin_id {})151	}152153	pub fn params(&self) -> FunctionSignature {154		match self {155			Self::Builtin(i) => i.params(),156			Self::Normal(p) => p.signature(),157		}158	}159	/// Amount of non-default required arguments160	pub fn params_len32(&self) -> u32 {161		arridx(self.params().iter().filter(|p| !p.has_default()).count())162	}163	/// Function name, as defined in code.164	pub fn name(&self) -> IStr {165		match self {166			Self::Normal(normal) => normal.name.clone(),167			Self::Builtin(builtin) => builtin.name().into(),168		}169	}170171	pub(crate) fn evaluate_prepared(172		&self,173		prepared: &PreparedCall,174		loc: CallLocation<'_>,175		unnamed: &[Thunk<Val>],176		named: &[Thunk<Val>],177		_tailstrict: bool,178	) -> Result<Val> {179		match self {180			FuncVal::Normal(func) => func.call(unnamed, named, prepared),181			FuncVal::Builtin(b) => {182				let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named);183				b.call(loc, &args)184			}185		}186	}187188	/// Is this function an identity function.189	///190	/// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too191	pub fn is_identity(&self) -> bool {192		match self {193			Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),194			Self::Normal(_) => false,195		}196	}197198	pub fn evaluate_trivial(&self) -> Option<Val> {199		match self {200			Self::Normal(n) => n.evaluate_trivial(),201			Self::Builtin(_) => None,202		}203	}204}205206impl<T> From<T> for FuncVal207where208	T: Builtin,209{210	fn from(value: T) -> Self {211		Self::builtin(value)212	}213}
after · crates/jrsonnet-evaluator/src/function/mod.rs
1use std::{fmt::Debug, rc::Rc};23use educe::Educe;4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;6use jrsonnet_ir::Span;7pub use jrsonnet_macros::builtin;89use self::{10	builtin::Builtin,11	prepared::{PreparedCall, parse_prepared_builtin_call},12};13use crate::{14	Context, PackedContextSupThis, Result, Thunk, Val,15	analyze::LFunction,16	arr::arridx,17	ensure_sufficient_stack,18	evaluate::{destructure::destruct, evaluate, evaluate_trivial},19	function::builtin::BuiltinFunc,20};2122pub mod builtin;23mod native;24pub(crate) mod prepared;2526pub use jrsonnet_ir::function::*;27pub use native::NativeFn;28pub(crate) use prepared::PreparedFuncVal;2930/// Function callsite location.31/// Either from other jsonnet code, specified by expression location, or from native (without location).32#[derive(Clone, Copy)]33pub struct CallLocation<'l>(pub Option<&'l Span>);34impl<'l> CallLocation<'l> {35	/// Construct new location for calls coming from specified jsonnet expression location.36	pub const fn new(loc: &'l Span) -> Self {37		Self(Some(loc))38	}39}40impl CallLocation<'static> {41	/// Construct new location for calls coming from native code.42	pub const fn native() -> Self {43		Self(None)44	}45}4647/// Represents Jsonnet function defined in code.48#[derive(Trace, Educe)]49#[educe(Debug, PartialEq)]50pub struct FuncDesc {51	/// # Example52	///53	/// In expressions like this, deducted to `a`, unspecified otherwise.54	/// ```jsonnet55	/// local a = function() ...56	/// local a() ...57	/// { a: function() ... }58	/// { a() = ... }59	/// ```60	pub name: IStr,61	pub(crate) body_captures: PackedContextSupThis,6263	#[educe(PartialEq(method = Rc::ptr_eq))]64	pub func: Rc<LFunction>,65}6667impl FuncDesc {68	pub fn signature(&self) -> FunctionSignature {69		self.func.signature.clone()70	}7172	fn call(73		&self,74		unnamed: &[Thunk<Val>],75		named: &[Thunk<Val>],76		prepared: &PreparedCall,77	) -> Result<Val> {78		let body_ctx = self.body_captures.clone().enter(|fill, ctx| {79			// Place each provided arg-thunk into its destructured slots.80			for (param_idx, thunk) in unnamed.iter().enumerate() {81				destruct(82					&self.func.params[param_idx].destruct,83					fill,84					thunk.clone(),85					ctx,86				);87			}88			for &(param_idx, arg_idx) in prepared.named() {89				destruct(90					&self.func.params[param_idx].destruct,91					fill,92					named[arg_idx].clone(),93					ctx,94				);95			}9697			for &param_idx in prepared.defaults() {98				let param = &self.func.params[param_idx];99				let (shape, expr) = param.default.as_ref().expect("default exists");100				let expr = expr.clone();101				let env = Context::enter_using(ctx, shape);102103				destruct(104					&param.destruct,105					fill,106					Thunk!(move || evaluate(env, &expr)),107					ctx,108				);109			}110		});111112		ensure_sufficient_stack(|| evaluate(body_ctx, &self.func.body))113	}114115	pub fn evaluate_trivial(&self) -> Option<Val> {116		evaluate_trivial(&self.func.body)117	}118}119120/// Represents a Jsonnet function value, including plain functions and user-provided builtins.121#[allow(clippy::module_name_repetitions)]122#[derive(Trace, Clone)]123pub enum FuncVal {124	/// Plain function implemented in jsonnet.125	Normal(Cc<FuncDesc>),126	/// User-provided function.127	Builtin(BuiltinFunc),128}129130impl Debug for FuncVal {131	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {132		match self {133			Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),134			Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),135		}136	}137}138139#[allow(clippy::unnecessary_wraps)]140#[builtin]141pub const fn builtin_id(x: Thunk<Val>) -> Thunk<Val> {142	x143}144145impl FuncVal {146	pub fn builtin(builtin: impl Builtin) -> Self {147		Self::Builtin(BuiltinFunc::new(builtin))148	}149150	pub fn identity() -> Self {151		Self::builtin(builtin_id {})152	}153154	pub fn params(&self) -> FunctionSignature {155		match self {156			Self::Builtin(i) => i.params(),157			Self::Normal(p) => p.signature(),158		}159	}160	/// Amount of non-default required arguments161	pub fn params_len32(&self) -> u32 {162		arridx(self.params().iter().filter(|p| !p.has_default()).count())163	}164	/// Function name, as defined in code.165	pub fn name(&self) -> IStr {166		match self {167			Self::Normal(normal) => normal.name.clone(),168			Self::Builtin(builtin) => builtin.name().into(),169		}170	}171172	pub(crate) fn evaluate_prepared(173		&self,174		prepared: &PreparedCall,175		loc: CallLocation<'_>,176		unnamed: &[Thunk<Val>],177		named: &[Thunk<Val>],178		_tailstrict: bool,179	) -> Result<Val> {180		match self {181			FuncVal::Normal(func) => func.call(unnamed, named, prepared),182			FuncVal::Builtin(b) => {183				let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named);184				b.call(loc, &args)185			}186		}187	}188189	/// Is this function an identity function.190	///191	/// This function should only be used for optimization, not for the conditional logic, i.e code should work with syntetic identity function too192	pub fn is_identity(&self) -> bool {193		match self {194			Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),195			Self::Normal(_) => false,196		}197	}198199	pub fn evaluate_trivial(&self) -> Option<Val> {200		match self {201			Self::Normal(n) => n.evaluate_trivial(),202			Self::Builtin(_) => None,203		}204	}205}206207impl<T> From<T> for FuncVal208where209	T: Builtin,210{211	fn from(value: T) -> Self {212		Self::builtin(value)213	}214}