git.delta.rocks / jrsonnet / refs/commits / 150f28648ad3

difftreelog

refactor always use prepared calls

wulxrvxzYaroslav Bolyukin2026-04-04parent: #0f5b614.patch.diff
in: master

17 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -19,7 +19,7 @@
 	destructure::evaluate_dest,
 	error::{ErrorKind::*, suggest_object_fields},
 	evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op},
-	function::{CallLocation, FuncDesc, FuncVal},
+	function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal},
 	gc::WithCapacityExt as _,
 	in_frame,
 	typed::{FromUntyped, IntoUntyped as _, Typed},
@@ -430,11 +430,25 @@
 	let value = evaluate(ctx.clone(), value)?;
 	Ok(match value {
 		Val::Func(f) => {
-			let body = || f.evaluate(ctx, loc, args, tailstrict);
+			let name = f.name();
+			let prepare = PreparedFuncVal::new(f, args.unnamed.len(), &args.names)?;
+			let unnamed = args
+				.unnamed
+				.iter()
+				.cloned()
+				.map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))
+				.collect::<Result<Vec<_>>>()?;
+			let named = args
+				.values
+				.iter()
+				.cloned()
+				.map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))
+				.collect::<Result<Vec<_>>>()?;
+			let body = || prepare.call(loc, &unnamed, &named);
 			if tailstrict {
 				body()?
 			} else {
-				in_frame(loc, || format!("function <{}> call", f.name()), body)?
+				in_frame(loc, || format!("function <{name}> call"), body)?
 			}
 		}
 		v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),
@@ -479,6 +493,13 @@
 	})
 }
 
+pub fn evaluate_thunk(ctx: Context, expr: Rc<Expr>, tailstrict: bool) -> Result<Thunk<Val>> {
+	Ok(if tailstrict {
+		Thunk::evaluated(evaluate(ctx, &expr)?)
+	} else {
+		Thunk!(move || { evaluate(ctx, &expr) })
+	})
+}
 #[allow(clippy::too_many_lines)]
 pub fn evaluate(ctx: Context, expr: &Expr) -> Result<Val> {
 	use Expr::*;
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -3,12 +3,12 @@
 use educe::Educe;
 use jrsonnet_gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
-use jrsonnet_ir::{ArgsDesc, Destruct, Expr, ExprParams, Span};
+use jrsonnet_ir::{Destruct, Expr, ExprParams, Span};
 pub use jrsonnet_macros::builtin;
 
 use self::{
 	builtin::Builtin,
-	parse::{parse_builtin_call, parse_default_function_call, parse_function_call},
+	parse::parse_default_function_call,
 	prepared::{PreparedCall, parse_prepared_builtin_call, parse_prepared_function_call},
 };
 use crate::{
@@ -22,7 +22,7 @@
 
 pub use jrsonnet_ir::function::*;
 pub use native::NativeFn;
-pub use prepared::PreparedFuncVal;
+pub(crate) use prepared::PreparedFuncVal;
 
 /// Function callsite location.
 /// Either from other jsonnet code, specified by expression location, or from native (without location).
@@ -77,16 +77,6 @@
 		parse_default_function_call(self.ctx.clone(), &self.params)
 	}
 
-	/// Create context, with which body code will run
-	pub(crate) fn call_body_context(
-		&self,
-		call_ctx: Context,
-		args: &ArgsDesc,
-		tailstrict: bool,
-	) -> Result<Context> {
-		parse_function_call(call_ctx, self.ctx.clone(), &self.params, args, tailstrict)
-	}
-
 	pub fn evaluate_trivial(&self) -> Option<Val> {
 		evaluate_trivial(&self.body)
 	}
@@ -137,27 +127,6 @@
 		match self {
 			Self::Normal(normal) => normal.name.clone(),
 			Self::Builtin(builtin) => builtin.name().into(),
-		}
-	}
-	/// Call function using arguments evaluated in specified `call_ctx` [`Context`].
-	///
-	/// If `tailstrict` is specified - then arguments will be evaluated before being passed to function body.
-	pub fn evaluate(
-		&self,
-		call_ctx: Context,
-		loc: CallLocation<'_>,
-		args: &ArgsDesc,
-		tailstrict: bool,
-	) -> Result<Val> {
-		match self {
-			Self::Normal(func) => {
-				let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?;
-				evaluate(body_ctx, &func.body)
-			}
-			Self::Builtin(b) => {
-				let args = parse_builtin_call(call_ctx, b.params(), args, tailstrict)?;
-				b.call(loc, &args)
-			}
 		}
 	}
 
modifiedcrates/jrsonnet-evaluator/src/function/native.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/native.rs
+++ b/crates/jrsonnet-evaluator/src/function/native.rs
@@ -39,12 +39,19 @@
 		impl<$($gen,)* O> FromUntyped for NativeFn<($($gen,)* O,)> {
 			fn from_untyped(untyped: Val) -> Result<Self> {
 				let func = FuncVal::from_untyped(untyped)?;
+				Self::try_from(func)
+			}
+		}
+		impl<$($gen,)* O> TryFrom<FuncVal> for NativeFn<($($gen,)* O,)> {
+			type Error = crate::Error;
+			fn try_from(v: FuncVal) -> Result<Self> {
 				Ok(Self(
-					PreparedFuncVal::new(func, $i, &[])?,
+					PreparedFuncVal::new(v, $i, &[])?,
 					PhantomData,
 				))
 			}
 		}
+
 	};
 	($i:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => {
 		impl_native_desc!($i; $($cur)*);
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,215 +1,13 @@
-use std::rc::Rc;
-
-use jrsonnet_ir::{
-	ArgsDesc, Expr, ExprParams,
-	function::{FunctionSignature, ParamName},
-};
+use jrsonnet_ir::ExprParams;
 use rustc_hash::FxHashMap;
 
 use crate::{
-	Context, Pending, Thunk, Val, bail,
+	Context, Thunk,
 	destructure::destruct,
 	error::{ErrorKind::*, Result},
-	evaluate, evaluate_named_param,
+	evaluate_named_param,
 	gc::WithCapacityExt as _,
 };
-
-fn eval_arg(ctx: Context, arg: &Rc<Expr>, tailstrict: bool) -> Result<Thunk<Val>> {
-	if tailstrict {
-		Ok(Thunk::evaluated(evaluate(ctx, arg)?))
-	} else {
-		let arg = arg.clone();
-		Ok(Thunk!(move || evaluate(ctx, &arg)))
-	}
-}
-
-/// Creates correct [context](Context) for function body evaluation returning error on invalid call.
-///
-/// ## Parameters
-/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
-/// * `body_ctx`: used for default parameter values' execution and for body execution (if set)
-/// * `params`: function parameters' definition
-/// * `args`: passed function arguments
-/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
-pub(crate) fn parse_function_call(
-	ctx: Context,
-	body_ctx: Context,
-	params: &ExprParams,
-	args: &ArgsDesc,
-	tailstrict: bool,
-) -> Result<Context> {
-	let mut passed_args = FxHashMap::with_capacity(params.binds_len());
-	if args.unnamed.len() > params.signature.len() {
-		bail!(TooManyArgsFunctionHas(
-			params.signature.len(),
-			params.signature.clone(),
-		))
-	}
-
-	let mut filled_named = 0;
-	let mut filled_positionals = 0;
-
-	for (id, arg) in args.unnamed.iter().enumerate() {
-		destruct(
-			&params.exprs[id].destruct,
-			eval_arg(ctx.clone(), arg, tailstrict)?,
-			Pending::new_filled(ctx.clone()),
-			&mut passed_args,
-		)?;
-		filled_positionals += 1;
-	}
-
-	for (name, value) in &args.named {
-		// FIXME: O(n) for arg existence check
-		if !params.exprs.iter().any(|p| &p.destruct.name() == name) {
-			bail!(UnknownFunctionParameter(name.clone()));
-		}
-		if passed_args
-			.insert(name.clone(), eval_arg(ctx.clone(), value, tailstrict)?)
-			.is_some()
-		{
-			bail!(BindingParameterASecondTime(name.clone()));
-		}
-		filled_named += 1;
-	}
-
-	if filled_named + filled_positionals < params.len() {
-		// Some args are unset, but maybe we have defaults for them
-		// Default values should be created in newly created context
-		let fctx = Context::new_future();
-		let mut defaults =
-			FxHashMap::with_capacity(params.binds_len() - filled_named - filled_positionals);
-
-		for (idx, into, default) in params
-			.exprs
-			.iter()
-			.enumerate()
-			.filter_map(|(i, p)| Some((i, &p.destruct, p.default.as_ref()?)))
-		{
-			if let ParamName::Named(name) = into.name() {
-				if passed_args.contains_key(&name) {
-					continue;
-				}
-			} else if idx < filled_positionals {
-				continue;
-			}
-
-			destruct(
-				into,
-				{
-					let ctx = fctx.clone();
-					let name = into.name();
-					let value = default.clone();
-					Thunk!(move || evaluate_named_param(ctx.unwrap(), &value, name))
-				},
-				fctx.clone(),
-				&mut defaults,
-			)?;
-			if into.name().is_named() {
-				filled_named += 1;
-			} else {
-				filled_positionals += 1;
-			}
-		}
-
-		// Some args still weren't filled
-		if filled_named + filled_positionals != params.len() {
-			for param in params.exprs.iter().skip(args.unnamed.len()) {
-				let mut found = false;
-				for (name, _) in &args.named {
-					if &param.destruct.name() == name {
-						found = true;
-					}
-				}
-				if !found {
-					bail!(FunctionParameterNotBoundInCall(
-						param.destruct.name(),
-						params.signature.clone()
-					));
-				}
-			}
-			unreachable!();
-		}
-
-		Ok(body_ctx
-			.extend_bindings(passed_args)
-			.extend_bindings(defaults)
-			.into_future(fctx))
-	} else {
-		let body_ctx = body_ctx.extend_bindings(passed_args);
-		Ok(body_ctx)
-	}
-}
-
-/// You shouldn't probally use this function, use `jrsonnet_macros::builtin` instead
-///
-/// ## Parameters
-/// * `ctx`: used for passed argument expressions' execution and for body execution (if `body_ctx` is not set)
-/// * `params`: function parameters' definition
-/// * `args`: passed function arguments
-/// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily
-pub fn parse_builtin_call(
-	ctx: Context,
-	params: FunctionSignature,
-	args: &ArgsDesc,
-	tailstrict: bool,
-) -> Result<Vec<Option<Thunk<Val>>>> {
-	let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];
-	if args.unnamed.len() > params.len() {
-		bail!(TooManyArgsFunctionHas(params.len(), params,))
-	}
-
-	let mut filled_args = 0;
-
-	for (id, arg) in args.unnamed.iter().enumerate() {
-		passed_args[id] = Some(eval_arg(ctx.clone(), arg, tailstrict)?);
-		filled_args += 1;
-	}
-
-	for (name, arg) in &args.named {
-		// FIXME: O(n) for arg existence check
-		let id = params
-			.iter()
-			.position(|p| p.name() == name)
-			.ok_or_else(|| UnknownFunctionParameter(name.clone()))?;
-		if passed_args[id]
-			.replace(eval_arg(ctx.clone(), arg, tailstrict)?)
-			.is_some()
-		{
-			bail!(BindingParameterASecondTime(name.clone()));
-		}
-		filled_args += 1;
-	}
-
-	if filled_args < params.len() {
-		for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default()) {
-			if passed_args[id].is_some() {
-				continue;
-			}
-			filled_args += 1;
-		}
-
-		// Some args still wasn't filled
-		if filled_args != params.len() {
-			for param in params.iter().skip(args.unnamed.len()) {
-				let mut found = false;
-				for (name, _) in &args.named {
-					if param.name() == name {
-						found = true;
-					}
-				}
-				if !found {
-					bail!(FunctionParameterNotBoundInCall(
-						param.name().clone(),
-						params,
-					));
-				}
-			}
-			unreachable!();
-		}
-	}
-	Ok(passed_args)
-}
 
 /// Creates Context, which has all argument default values applied
 /// and with unbound values causing error to be returned
modifiedcrates/jrsonnet-ir-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/lib.rs
+++ b/crates/jrsonnet-ir-parser/src/lib.rs
@@ -416,10 +416,11 @@
 
 fn args(p: &mut Parser<'_>) -> Result<ArgsDesc> {
 	if p.at(T![')']) {
-		return Ok(ArgsDesc::new(Vec::new(), Vec::new()));
+		return Ok(ArgsDesc::new(Vec::new(), Vec::new(), Vec::new()));
 	}
 	let mut unnamed = Vec::new();
-	let mut named = Vec::new();
+	let mut names = Vec::new();
+	let mut values = Vec::new();
 	let mut named_started = false;
 	loop {
 		let is_named = p.at(SyntaxKind::IDENT) && {
@@ -430,7 +431,9 @@
 			let name: IStr = ident(p)?;
 			p.eat(T![=])?;
 			let value = Rc::new(expr(p)?);
-			named.push((name, value));
+
+			names.push(name);
+			values.push(value);
 			named_started = true;
 		} else {
 			if named_started {
@@ -445,7 +448,7 @@
 			break;
 		}
 	}
-	Ok(ArgsDesc::new(unnamed, named))
+	Ok(ArgsDesc::new(unnamed, names, values))
 }
 
 fn bind(p: &mut Parser<'_>) -> Result<BindSpec> {
modifiedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snapdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snap
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__function_and_call.snap
@@ -66,12 +66,12 @@
                     2.0,
                 ),
             ],
-            named: [
-                (
-                    "y",
-                    Num(
-                        3.0,
-                    ),
+            names: [
+                "y",
+            ],
+            values: [
+                Num(
+                    3.0,
                 ),
             ],
         } from virtual:<test>:26-34,
modifiedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snapdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snap
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__index_and_suffix.snap
@@ -23,7 +23,8 @@
                     2.0,
                 ),
             ],
-            named: [],
+            names: [],
+            values: [],
         } from virtual:<test>:8-11,
         false,
     ),
modifiedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snap
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@array_comp.jsonnet.snap
@@ -26,7 +26,8 @@
                             "x" from virtual:<test>:16-17,
                         ),
                     ],
-                    named: [],
+                    names: [],
+                    values: [],
                 } from virtual:<test>:15-18,
                 false,
             ),
modifiedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snap
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@reserved.jsonnet.snap
@@ -24,7 +24,8 @@
                         "null_fields" from virtual:<test>:20-31,
                     ),
                 ],
-                named: [],
+                names: [],
+                values: [],
             } from virtual:<test>:16-32,
             false,
         ),
modifiedcrates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snap
+++ b/crates/jrsonnet-ir-parser/src/snapshots/jrsonnet_ir_parser__tests__peg_snapshots@suffix.jsonnet.snap
@@ -28,7 +28,8 @@
                         2.0,
                     ),
                 ],
-                named: [],
+                names: [],
+                values: [],
             } from virtual:<test>:15-18,
             false,
         ),
@@ -52,7 +53,8 @@
                         2.0,
                     ),
                 ],
-                named: [],
+                names: [],
+                values: [],
             } from virtual:<test>:28-31,
             false,
         ),
modifiedcrates/jrsonnet-ir/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-ir/src/expr.rs
+++ b/crates/jrsonnet-ir/src/expr.rs
@@ -195,11 +195,16 @@
 #[derive(Debug, PartialEq, Acyclic)]
 pub struct ArgsDesc {
 	pub unnamed: Vec<Rc<Expr>>,
-	pub named: Vec<(IStr, Rc<Expr>)>,
+	pub names: Vec<IStr>,
+	pub values: Vec<Rc<Expr>>,
 }
 impl ArgsDesc {
-	pub fn new(unnamed: Vec<Rc<Expr>>, named: Vec<(IStr, Rc<Expr>)>) -> Self {
-		Self { unnamed, named }
+	pub fn new(unnamed: Vec<Rc<Expr>>, names: Vec<IStr>, values: Vec<Rc<Expr>>) -> Self {
+		Self {
+			unnamed,
+			names,
+			values,
+		}
 	}
 }
 
modifiedcrates/jrsonnet-ir/src/visit.rsdiffbeforeafterboth
before · crates/jrsonnet-ir/src/visit.rs
1use jrsonnet_interner::IStr;23use crate::{4	ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BindSpec, CompSpec, Destruct, Expr, ExprParam,5	ExprParams, FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, IndexPart,6	ObjBody, ObjComp, ObjMembers, Slice, SliceDesc,7};89pub trait Visitor: Sized {10	fn visit_expr(&mut self, e: &Expr) {11		visit_expr(self, e)12	}13	fn visit_import(&mut self, _as_expression: bool, _value: IStr) {}14}1516#[cfg(feature = "exp-destruct")]17pub fn visit_destruct_rest<V: Visitor>(_v: &mut V, destruct: &crate::DestructRest) {18	match destruct {19		crate::DestructRest::Keep(_name) => {}20		crate::DestructRest::Drop => {}21	}22}2324#[allow(unused_variables, reason = "used with exp-destruct")]25pub fn visit_destruct<V: Visitor>(v: &mut V, destruct: &Destruct) {26	match destruct {27		Destruct::Full(_istr) => {}28		#[cfg(feature = "exp-destruct")]29		Destruct::Skip => {}30		#[cfg(feature = "exp-destruct")]31		Destruct::Array { start, rest, end } => {32			for s in start {33				visit_destruct(v, s);34			}35			if let Some(rest) = rest {36				visit_destruct_rest(v, rest);37			}38			for s in end {39				visit_destruct(v, s);40			}41		}42		#[cfg(feature = "exp-destruct")]43		Destruct::Object { fields, rest } => {44			for (_name, into, default) in fields {45				if let Some(into) = into {46					visit_destruct(v, into);47				}48				if let Some(default) = default {49					v.visit_expr(default);50				}51				if let Some(rest) = rest {52					visit_destruct_rest(v, rest);53				}54			}55		}56	}57}5859pub fn visit_if_spec<V: Visitor>(v: &mut V, cond: &IfSpecData) {60	let IfSpecData { span: _, cond } = cond;61	v.visit_expr(cond);62}6364pub fn visit_comp_spec<V: Visitor>(v: &mut V, c: &CompSpec) {65	match c {66		CompSpec::IfSpec(cond) => visit_if_spec(v, cond),67		CompSpec::ForSpec(for_spec_data) => {68			let ForSpecData { destruct, over } = for_spec_data;69			visit_destruct(v, destruct);70			v.visit_expr(over);71		}72	}73}74pub fn visit_params<V: Visitor>(v: &mut V, par: &ExprParams) {75	let ExprParams {76		exprs,77		signature: _,78		binds_len: _,79	} = par;80	for par in &**exprs {81		let ExprParam { destruct, default } = &par;82		visit_destruct(v, destruct);83		if let Some(default) = default {84			v.visit_expr(default);85		}86	}87}8889pub fn visit_bind_spec<V: Visitor>(v: &mut V, bind: &BindSpec) {90	match bind {91		BindSpec::Field { into, value } => {92			visit_destruct(v, into);93			v.visit_expr(value);94		}95		BindSpec::Function {96			name: _,97			params,98			value,99		} => {100			visit_params(v, params);101			v.visit_expr(value);102		}103	}104}105106pub fn visit_field_member<V: Visitor>(v: &mut V, mem: &FieldMember) {107	let FieldMember {108		name,109		plus: _,110		params,111		visibility: _,112		value,113	} = mem;114	match &**name {115		FieldName::Fixed(_istr) => {}116		FieldName::Dyn(expr) => v.visit_expr(expr),117	}118	if let Some(params) = params {119		visit_params(v, params);120	}121	v.visit_expr(value);122}123124pub fn visit_obj_body<V: Visitor>(v: &mut V, obj_body: &ObjBody) {125	match obj_body {126		ObjBody::MemberList(obj_members) => {127			let ObjMembers {128				locals,129				asserts,130				fields,131			} = obj_members;132			for local in &**locals {133				visit_bind_spec(v, local);134			}135			for assert in &**asserts {136				visit_assert_stmt(v, assert);137			}138			for field in fields {139				visit_field_member(v, field);140			}141		}142		ObjBody::ObjComp(obj_comp) => {143			let ObjComp {144				locals,145				field,146				compspecs,147			} = obj_comp;148			for local in &**locals {149				visit_bind_spec(v, local);150			}151			visit_field_member(v, field);152			for compspec in compspecs {153				visit_comp_spec(v, compspec);154			}155		}156	}157}158159pub fn visit_assert_stmt<V: Visitor>(v: &mut V, ass: &AssertStmt) {160	let AssertStmt(cond, msg) = ass;161	v.visit_expr(cond);162	if let Some(msg) = msg {163		v.visit_expr(msg);164	}165}166pub fn visit_expr<V: Visitor>(v: &mut V, e: &Expr) {167	match e {168		Expr::Literal(_literal_type) => {}169		Expr::Str(_istr) => {}170		Expr::Num(_num) => {}171		Expr::Var(_spanned) => {}172		Expr::Arr(exprs) => {173			for e in &**exprs {174				v.visit_expr(e);175			}176		}177		Expr::ArrComp(expr, comp_specs) => {178			v.visit_expr(expr);179			for ele in comp_specs {180				visit_comp_spec(v, ele);181			}182		}183		Expr::Obj(obj_body) => visit_obj_body(v, obj_body),184		Expr::ObjExtend(expr, obj_body) => {185			v.visit_expr(expr);186			visit_obj_body(v, obj_body);187		}188		Expr::UnaryOp(_unary_op_type, expr) => {189			v.visit_expr(expr);190		}191		Expr::BinaryOp(binary_op) => {192			let BinaryOp { lhs, op: _, rhs } = &**binary_op;193			v.visit_expr(lhs);194			v.visit_expr(rhs);195		}196		Expr::AssertExpr(assert_expr) => {197			let AssertExpr { assert, rest } = &**assert_expr;198			visit_assert_stmt(v, assert);199			v.visit_expr(rest);200		}201		Expr::LocalExpr(bind_specs, expr) => {202			for local in bind_specs {203				visit_bind_spec(v, local);204			}205			v.visit_expr(expr);206		}207		Expr::Import(kind, expr) => {208			v.visit_expr(expr);209210			if let Expr::Str(expr) = &**expr {211				v.visit_import(matches!(**kind, ImportKind::Normal), expr.clone());212			}213		}214		Expr::ErrorStmt(_span, expr) => {215			v.visit_expr(expr);216		}217		Expr::Apply(expr, spanned, _) => {218			v.visit_expr(expr);219			let ArgsDesc { unnamed, named } = &**spanned;220			for unnamed in unnamed {221				v.visit_expr(unnamed);222			}223			for (_name, named) in named {224				v.visit_expr(named);225			}226		}227		Expr::Index { indexable, parts } => {228			v.visit_expr(indexable);229230			for part in parts {231				let IndexPart {232					span: _,233					value,234					#[cfg(feature = "exp-null-coaelse")]235						null_coaelse: _,236				} = part;237				v.visit_expr(value);238			}239		}240		Expr::Function(expr_params, expr) => {241			visit_params(v, expr_params);242			v.visit_expr(expr);243		}244		Expr::IfElse(if_else) => {245			let IfElse {246				cond,247				cond_then,248				cond_else,249			} = &**if_else;250			visit_if_spec(v, cond);251			v.visit_expr(cond_then);252			if let Some(cond_else) = cond_else {253				v.visit_expr(cond_else);254			}255		}256		Expr::Slice(slice) => {257			let Slice { value, slice } = &**slice;258			v.visit_expr(value);259			let SliceDesc { start, end, step } = slice;260261			if let Some(start) = start {262				v.visit_expr(start);263			}264			if let Some(end) = end {265				v.visit_expr(end);266			}267			if let Some(step) = step {268				v.visit_expr(step);269			}270		}271	}272}
modifiedcrates/jrsonnet-peg-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-peg-parser/src/lib.rs
+++ b/crates/jrsonnet-peg-parser/src/lib.rs
@@ -73,12 +73,14 @@
 			= args:arg(s)**comma() comma()? {?
 				let unnamed_count = args.iter().take_while(|(n, _)| n.is_none()).count();
 				let mut unnamed = Vec::with_capacity(unnamed_count);
-				let mut named = Vec::with_capacity(args.len() - unnamed_count);
+				let mut names = Vec::with_capacity(args.len() - unnamed_count);
+				let mut values = Vec::with_capacity(args.len() - unnamed_count);
 				let mut named_started = false;
 				for (name, value) in args {
 					if let Some(name) = name {
 						named_started = true;
-						named.push((name, value));
+						names.push(name);
+						values.push(value);
 					} else {
 						if named_started {
 							return Err("<named argument>")
@@ -86,7 +88,7 @@
 						unnamed.push(value);
 					}
 				}
-				Ok(ArgsDesc::new(unnamed, named))
+				Ok(ArgsDesc{unnamed, names, values})
 			}
 
 		pub rule destruct_rest() -> DestructRest
modifiedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@array_comp.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@array_comp.jsonnet.snap
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@array_comp.jsonnet.snap
@@ -26,7 +26,8 @@
                             "x" from virtual:<test>:16-17,
                         ),
                     ],
-                    named: [],
+                    names: [],
+                    values: [],
                 } from virtual:<test>:15-18,
                 false,
             ),
modifiedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@reserved.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@reserved.jsonnet.snap
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@reserved.jsonnet.snap
@@ -24,7 +24,8 @@
                         "null_fields" from virtual:<test>:20-31,
                     ),
                 ],
-                named: [],
+                names: [],
+                values: [],
             } from virtual:<test>:16-32,
             false,
         ),
modifiedcrates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@suffix.jsonnet.snapdiffbeforeafterboth
--- a/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@suffix.jsonnet.snap
+++ b/crates/jrsonnet-peg-parser/src/snapshots/jrsonnet_peg_parser__tests__snapshots@suffix.jsonnet.snap
@@ -28,7 +28,8 @@
                         2.0,
                     ),
                 ],
-                named: [],
+                names: [],
+                values: [],
             } from virtual:<test>:15-18,
             false,
         ),
@@ -52,7 +53,8 @@
                         2.0,
                     ),
                 ],
-                named: [],
+                names: [],
+                values: [],
             } from virtual:<test>:28-31,
             false,
         ),
modifiedcrates/jrsonnet-stdlib/src/keyf.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/keyf.rs
+++ b/crates/jrsonnet-stdlib/src/keyf.rs
@@ -1,14 +1,16 @@
 use jrsonnet_evaluator::{
 	Error, Result, Thunk, Val,
-	function::{CallLocation, FuncVal, PreparedFuncVal},
+	function::{FuncVal, NativeFn},
 	typed::{ComplexValType, FromUntyped, Typed, ValType},
 };
 
+type PreparedKeyF = NativeFn!((Thunk<Val>) -> Val);
+
 #[derive(Default, Clone)]
 pub enum KeyF {
 	#[default]
 	Identity,
-	Prepared(PreparedFuncVal),
+	Prepared(PreparedKeyF),
 	PrepareFailure(Error),
 }
 impl KeyF {
@@ -19,13 +21,13 @@
 		if val.is_identity() {
 			Self::Identity
 		} else {
-			PreparedFuncVal::new(val, 1, &[]).map_or_else(Self::PrepareFailure, Self::Prepared)
+			PreparedKeyF::try_from(val).map_or_else(Self::PrepareFailure, Self::Prepared)
 		}
 	}
 	pub fn eval(&self, val: impl Into<Thunk<Val>>) -> Result<Val> {
 		match self {
 			KeyF::Identity => val.into().evaluate(),
-			KeyF::Prepared(p) => p.call(CallLocation::native(), &[val.into()], &[]),
+			KeyF::Prepared(p) => p.call(val.into()),
 			KeyF::PrepareFailure(e) => Err(e.clone()),
 		}
 	}