git.delta.rocks / jrsonnet / refs/commits / 1c69f1ac7855

difftreelog

refactor move state to global

touwqtkwYaroslav Bolyukin2026-02-07parent: #5585b94.patch.diff
in: master

9 files changed

modifiedbindings/jsonnet/src/lib.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -247,7 +247,7 @@
 	match vm
 		.state
 		.import(filename)
-		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))
+		.and_then(|val| apply_tla(&vm.tla_args, val))
 		.and_then(|val| val.manifest(&vm.manifest_format))
 	{
 		Ok(v) => {
@@ -282,7 +282,7 @@
 	match vm
 		.state
 		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())
-		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))
+		.and_then(|val| apply_tla(&vm.tla_args, val))
 		.and_then(|val| val.manifest(&vm.manifest_format))
 	{
 		Ok(v) => {
@@ -340,7 +340,7 @@
 	match vm
 		.state
 		.import(filename)
-		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))
+		.and_then(|val| apply_tla(&vm.tla_args, val))
 		.and_then(|val| val_to_multi(val, &vm.manifest_format))
 	{
 		Ok(v) => {
@@ -369,7 +369,7 @@
 	match vm
 		.state
 		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())
-		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))
+		.and_then(|val| apply_tla(&vm.tla_args, val))
 		.and_then(|val| val_to_multi(val, &vm.manifest_format))
 	{
 		Ok(v) => {
@@ -422,7 +422,7 @@
 	match vm
 		.state
 		.import(filename)
-		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))
+		.and_then(|val| apply_tla(&vm.tla_args, val))
 		.and_then(|val| val_to_stream(val, &vm.manifest_format))
 	{
 		Ok(v) => {
@@ -451,7 +451,7 @@
 	match vm
 		.state
 		.evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())
-		.and_then(|val| apply_tla(vm.state.clone(), &vm.tla_args, val))
+		.and_then(|val| apply_tla(&vm.tla_args, val))
 		.and_then(|val| val_to_stream(val, &vm.manifest_format))
 	{
 		Ok(v) => {
modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -173,6 +173,7 @@
 	let mut s = State::builder();
 	s.import_resolver(import_resolver).context_initializer(std);
 	let s = s.build();
+	let _s = s.enter();
 
 	let input = opts.input.input.ok_or(Error::MissingInputArgument)?;
 	let val = if opts.input.exec {
@@ -192,7 +193,7 @@
 		unused_mut,
 		clippy::redundant_clone,
 	)]
-	let mut val = apply_tla(s.clone(), &tla, val)?;
+	let mut val = apply_tla(&tla, val)?;
 
 	#[cfg(feature = "exp-apply")]
 	for apply in opts.input.exp_apply {
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -6,12 +6,11 @@
 
 use crate::{
 	error::ErrorKind::*, gc::WithCapacityExt as _, map::LayeredHashMap, ObjValue, Pending, Result,
-	State, Thunk, Val,
+	Thunk, Val,
 };
 
 #[derive(Trace)]
 struct ContextInternals {
-	state: Option<State>,
 	dollar: Option<ObjValue>,
 	sup: Option<ObjValue>,
 	this: Option<ObjValue>,
@@ -33,13 +32,6 @@
 		Pending::new()
 	}
 
-	pub fn state(&self) -> &State {
-		self.0
-			.state
-			.as_ref()
-			.expect("used state from dummy context")
-	}
-
 	pub fn dollar(&self) -> Option<&ObjValue> {
 		self.0.dollar.as_ref()
 	}
@@ -112,7 +104,6 @@
 			ctx.bindings.clone().extend(new_bindings)
 		};
 		Self(Cc::new(ContextInternals {
-			state: ctx.state.clone(),
 			dollar,
 			sup,
 			this,
@@ -128,34 +119,22 @@
 }
 
 pub struct ContextBuilder {
-	state: Option<State>,
 	bindings: FxHashMap<IStr, Thunk<Val>>,
 	extend: Option<Context>,
 }
 
 impl ContextBuilder {
-	/// # Panics
-	/// Panics aren't directly caused by this function, but if state from resulting context is used
-	pub fn dangerous_empty_state() -> Self {
-		Self {
-			state: None,
-			bindings: FxHashMap::new(),
-			extend: None,
-		}
+	pub fn new() -> Self {
+		Self::with_capacity(0)
 	}
-	pub fn new(state: State) -> Self {
-		Self::with_capacity(state, 0)
-	}
-	pub fn with_capacity(state: State, capacity: usize) -> Self {
+	pub fn with_capacity(capacity: usize) -> Self {
 		Self {
-			state: Some(state),
 			bindings: FxHashMap::with_capacity(capacity),
 			extend: None,
 		}
 	}
 	pub fn extend(parent: Context) -> Self {
 		Self {
-			state: parent.0.state.clone(),
 			bindings: FxHashMap::new(),
 			extend: Some(parent),
 		}
@@ -173,7 +152,6 @@
 			parent.extend(self.bindings, None, None, None)
 		} else {
 			Context(Cc::new(ContextInternals {
-				state: self.state,
 				bindings: LayeredHashMap::new(self.bindings),
 				dollar: None,
 				sup: None,
modifiedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/function/arglike.rs
1use std::collections::HashMap;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{ArgsDesc, LocExpr, SourceFifo, SourcePath};67use crate::{evaluate, typed::Typed, Context, Result, Thunk, Val};89/// Marker for arguments, which can be evaluated with context set to None10pub trait OptionalContext {}1112pub trait ArgLike {13	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>>;14}1516impl ArgLike for &LocExpr {17	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {18		Ok(if tailstrict {19			Thunk::evaluated(evaluate(ctx, self)?)20		} else {21			let expr = (*self).clone();22			Thunk!(move || evaluate(ctx, &expr))23		})24	}25}2627impl<T> ArgLike for T28where29	T: Typed + Clone,30{31	fn evaluate_arg(&self, _ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {32		if T::provides_lazy() && !tailstrict {33			return Ok(T::into_lazy_untyped(self.clone()));34		}35		let val = T::into_untyped(self.clone())?;36		Ok(Thunk::evaluated(val))37	}38}39impl<T> OptionalContext for T where T: Typed + Clone {}4041#[derive(Clone, Trace)]42pub enum TlaArg {43	String(IStr),44	Val(Val),45	Lazy(Thunk<Val>),46	Import(String),47	ImportStr(String),48	InlineCode(String),49}50impl ArgLike for TlaArg {51	fn evaluate_arg(&self, ctx: Context, _tailstrict: bool) -> Result<Thunk<Val>> {52		match self {53			Self::String(s) => Ok(Thunk::evaluated(Val::string(s.clone()))),54			Self::Val(val) => Ok(Thunk::evaluated(val.clone())),55			Self::Lazy(lazy) => Ok(lazy.clone()),56			Self::Import(p) => {57				let resolved = ctx.state().resolve_from_default(&p.as_str())?;58				Ok(Thunk!(move || ctx.state().import_resolved(resolved)))59			}60			Self::ImportStr(p) => {61				let resolved = ctx.state().resolve_from_default(&p.as_str())?;62				Ok(Thunk!(move || ctx63					.state()64					.import_resolved_str(resolved)65					.map(Val::string)))66			}67			Self::InlineCode(p) => {68				let resolved =69					SourcePath::new(SourceFifo("<inline code>".to_owned(), p.as_bytes().into()));70				Ok(Thunk!(move || ctx.state().import_resolved(resolved)))71			}72		}73	}74}7576pub trait ArgsLike {77	fn unnamed_len(&self) -> usize;78	fn unnamed_iter(79		&self,80		ctx: Context,81		tailstrict: bool,82		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,83	) -> Result<()>;84	fn named_iter(85		&self,86		ctx: Context,87		tailstrict: bool,88		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,89	) -> Result<()>;90	fn named_names(&self, handler: &mut dyn FnMut(&IStr));91	fn is_empty(&self) -> bool;92}9394impl ArgsLike for Vec<Val> {95	fn unnamed_len(&self) -> usize {96		self.len()97	}98	fn unnamed_iter(99		&self,100		_ctx: Context,101		_tailstrict: bool,102		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,103	) -> Result<()> {104		for (idx, el) in self.iter().enumerate() {105			handler(idx, Thunk::evaluated(el.clone()))?;106		}107		Ok(())108	}109	fn named_iter(110		&self,111		_ctx: Context,112		_tailstrict: bool,113		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,114	) -> Result<()> {115		Ok(())116	}117	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}118	fn is_empty(&self) -> bool {119		self.is_empty()120	}121}122123impl ArgsLike for ArgsDesc {124	fn unnamed_len(&self) -> usize {125		self.unnamed.len()126	}127128	fn unnamed_iter(129		&self,130		ctx: Context,131		tailstrict: bool,132		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,133	) -> Result<()> {134		for (id, arg) in self.unnamed.iter().enumerate() {135			handler(136				id,137				if tailstrict {138					Thunk::evaluated(evaluate(ctx.clone(), arg)?)139				} else {140					let ctx = ctx.clone();141					let arg = arg.clone();142143					Thunk!(move || evaluate(ctx, &arg))144				},145			)?;146		}147		Ok(())148	}149150	fn named_iter(151		&self,152		ctx: Context,153		tailstrict: bool,154		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,155	) -> Result<()> {156		for (name, arg) in &self.named {157			handler(158				name,159				if tailstrict {160					Thunk::evaluated(evaluate(ctx.clone(), arg)?)161				} else {162					let ctx = ctx.clone();163					let arg = arg.clone();164165					Thunk!(move || evaluate(ctx, &arg))166				},167			)?;168		}169		Ok(())170	}171172	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {173		for (name, _) in &self.named {174			handler(name);175		}176	}177178	fn is_empty(&self) -> bool {179		self.unnamed.is_empty() && self.named.is_empty()180	}181}182183impl<V: ArgLike, S> ArgsLike for HashMap<IStr, V, S> {184	fn unnamed_len(&self) -> usize {185		0186	}187188	fn unnamed_iter(189		&self,190		_ctx: Context,191		_tailstrict: bool,192		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,193	) -> Result<()> {194		Ok(())195	}196197	fn named_iter(198		&self,199		ctx: Context,200		tailstrict: bool,201		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,202	) -> Result<()> {203		for (name, value) in self {204			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;205		}206		Ok(())207	}208209	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {210		for (name, _) in self {211			handler(name);212		}213	}214215	fn is_empty(&self) -> bool {216		self.is_empty()217	}218}219impl<V, S> OptionalContext for HashMap<IStr, V, S> where V: ArgLike + OptionalContext {}220221macro_rules! impl_args_like {222	($count:expr; $($gen:ident)*) => {223		impl<$($gen: ArgLike,)*> ArgsLike for ($($gen,)*) {224			fn unnamed_len(&self) -> usize {225				$count226			}227			#[allow(non_snake_case, unused_assignments)]228			fn unnamed_iter(229				&self,230				ctx: Context,231				tailstrict: bool,232				handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,233			) -> Result<()> {234				let mut i = 0usize;235				let ($($gen,)*) = self;236				$(237					handler(i, $gen.evaluate_arg(ctx.clone(), tailstrict)?)?;238					i+=1;239				)*240				Ok(())241			}242			fn named_iter(243				&self,244				_ctx: Context,245				_tailstrict: bool,246				_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,247			) -> Result<()> {248				Ok(())249			}250			fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}251252			fn is_empty(&self) -> bool {253				// impl_args_like only implements non-empty tuples.254				false255			}256		}257		impl<$($gen: ArgLike,)*> OptionalContext for ($($gen,)*) where $($gen: OptionalContext),* {}258	};259	($count:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => {260		impl_args_like!($count; $($cur)*);261		impl_args_like!($count + 1usize; $($cur)* $c @ $($rest)*);262	};263	($count:expr; $($cur:ident)* @) => {264		impl_args_like!($count; $($cur)*);265	}266}267impl_args_like! {268	// First argument is already in position, so count starts from 1269	1usize; A @ B C D E F G H I J K L270}271272impl ArgsLike for () {273	fn unnamed_len(&self) -> usize {274		0275	}276277	fn unnamed_iter(278		&self,279		_ctx: Context,280		_tailstrict: bool,281		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,282	) -> Result<()> {283		Ok(())284	}285286	fn named_iter(287		&self,288		_ctx: Context,289		_tailstrict: bool,290		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,291	) -> Result<()> {292		Ok(())293	}294295	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}296	fn is_empty(&self) -> bool {297		true298	}299}300impl OptionalContext for () {}
after · crates/jrsonnet-evaluator/src/function/arglike.rs
1use std::collections::HashMap;23use jrsonnet_gcmodule::Trace;4use jrsonnet_interner::IStr;5use jrsonnet_parser::{ArgsDesc, LocExpr, SourceFifo, SourcePath};67use crate::{evaluate, typed::Typed, with_state, Context, Result, Thunk, Val};89/// Marker for arguments, which can be evaluated with context set to None10pub trait OptionalContext {}1112pub trait ArgLike {13	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>>;14}1516impl ArgLike for &LocExpr {17	fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {18		Ok(if tailstrict {19			Thunk::evaluated(evaluate(ctx, self)?)20		} else {21			let expr = (*self).clone();22			Thunk!(move || evaluate(ctx, &expr))23		})24	}25}2627impl<T> ArgLike for T28where29	T: Typed + Clone,30{31	fn evaluate_arg(&self, _ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {32		if T::provides_lazy() && !tailstrict {33			return Ok(T::into_lazy_untyped(self.clone()));34		}35		let val = T::into_untyped(self.clone())?;36		Ok(Thunk::evaluated(val))37	}38}39impl<T> OptionalContext for T where T: Typed + Clone {}4041#[derive(Clone, Trace)]42pub enum TlaArg {43	String(IStr),44	Val(Val),45	Lazy(Thunk<Val>),46	Import(String),47	ImportStr(String),48	InlineCode(String),49}50impl TlaArg {51	pub fn evaluate_tailstrict(&self) -> Result<Val> {52		match self {53			Self::String(s) => Ok(Val::string(s.clone())),54			Self::Val(val) => Ok(val.clone()),55			Self::Lazy(lazy) => Ok(lazy.evaluate()?),56			Self::Import(p) => with_state(|s| {57				let resolved = s.resolve_from_default(&p.as_str())?;58				s.import_resolved(resolved)59			}),60			Self::ImportStr(p) => with_state(|s| {61				let resolved = s.resolve_from_default(&p.as_str())?;62				s.import_resolved_str(resolved).map(Val::string)63			}),64			Self::InlineCode(p) => with_state(|s| {65				let resolved =66					SourcePath::new(SourceFifo("<inline code>".to_owned(), p.as_bytes().into()));67				s.import_resolved(resolved)68			}),69		}70	}71	pub fn evaluate(&self) -> Result<Thunk<Val>> {72		match self {73			Self::String(s) => Ok(Thunk::evaluated(Val::string(s.clone()))),74			Self::Val(val) => Ok(Thunk::evaluated(val.clone())),75			Self::Lazy(lazy) => Ok(lazy.clone()),76			Self::Import(p) => with_state(|s| {77				let resolved = s.resolve_from_default(&p.as_str())?;78				Ok(Thunk!(move || s.import_resolved(resolved)))79			}),80			Self::ImportStr(p) => with_state(|s| {81				let resolved = s.resolve_from_default(&p.as_str())?;82				Ok(Thunk!(move || s83					.import_resolved_str(resolved)84					.map(Val::string)))85			}),86			Self::InlineCode(p) => with_state(|s| {87				let resolved =88					SourcePath::new(SourceFifo("<inline code>".to_owned(), p.as_bytes().into()));89				Ok(Thunk!(move || s.import_resolved(resolved)))90			}),91		}92	}93}9495// TODO: Is this implementation really required, as there is no Context to use?96// Maybe something a bit stricter is possible to add, especially with precompiled calls?97impl ArgLike for TlaArg {98	fn evaluate_arg(&self, _ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {99		if tailstrict {100			self.evaluate_tailstrict().map(Thunk::evaluated)101		} else {102			self.evaluate()103		}104	}105}106107pub trait ArgsLike {108	fn unnamed_len(&self) -> usize;109	fn unnamed_iter(110		&self,111		ctx: Context,112		tailstrict: bool,113		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,114	) -> Result<()>;115	fn named_iter(116		&self,117		ctx: Context,118		tailstrict: bool,119		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,120	) -> Result<()>;121	fn named_names(&self, handler: &mut dyn FnMut(&IStr));122	fn is_empty(&self) -> bool;123}124125impl ArgsLike for Vec<Val> {126	fn unnamed_len(&self) -> usize {127		self.len()128	}129	fn unnamed_iter(130		&self,131		_ctx: Context,132		_tailstrict: bool,133		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,134	) -> Result<()> {135		for (idx, el) in self.iter().enumerate() {136			handler(idx, Thunk::evaluated(el.clone()))?;137		}138		Ok(())139	}140	fn named_iter(141		&self,142		_ctx: Context,143		_tailstrict: bool,144		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,145	) -> Result<()> {146		Ok(())147	}148	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}149	fn is_empty(&self) -> bool {150		self.is_empty()151	}152}153154impl ArgsLike for ArgsDesc {155	fn unnamed_len(&self) -> usize {156		self.unnamed.len()157	}158159	fn unnamed_iter(160		&self,161		ctx: Context,162		tailstrict: bool,163		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,164	) -> Result<()> {165		for (id, arg) in self.unnamed.iter().enumerate() {166			handler(167				id,168				if tailstrict {169					Thunk::evaluated(evaluate(ctx.clone(), arg)?)170				} else {171					let ctx = ctx.clone();172					let arg = arg.clone();173174					Thunk!(move || evaluate(ctx, &arg))175				},176			)?;177		}178		Ok(())179	}180181	fn named_iter(182		&self,183		ctx: Context,184		tailstrict: bool,185		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,186	) -> Result<()> {187		for (name, arg) in &self.named {188			handler(189				name,190				if tailstrict {191					Thunk::evaluated(evaluate(ctx.clone(), arg)?)192				} else {193					let ctx = ctx.clone();194					let arg = arg.clone();195196					Thunk!(move || evaluate(ctx, &arg))197				},198			)?;199		}200		Ok(())201	}202203	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {204		for (name, _) in &self.named {205			handler(name);206		}207	}208209	fn is_empty(&self) -> bool {210		self.unnamed.is_empty() && self.named.is_empty()211	}212}213214impl<V: ArgLike, S> ArgsLike for HashMap<IStr, V, S> {215	fn unnamed_len(&self) -> usize {216		0217	}218219	fn unnamed_iter(220		&self,221		_ctx: Context,222		_tailstrict: bool,223		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,224	) -> Result<()> {225		Ok(())226	}227228	fn named_iter(229		&self,230		ctx: Context,231		tailstrict: bool,232		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,233	) -> Result<()> {234		for (name, value) in self {235			handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?;236		}237		Ok(())238	}239240	fn named_names(&self, handler: &mut dyn FnMut(&IStr)) {241		for (name, _) in self {242			handler(name);243		}244	}245246	fn is_empty(&self) -> bool {247		self.is_empty()248	}249}250impl<V, S> OptionalContext for HashMap<IStr, V, S> where V: ArgLike + OptionalContext {}251252macro_rules! impl_args_like {253	($count:expr; $($gen:ident)*) => {254		impl<$($gen: ArgLike,)*> ArgsLike for ($($gen,)*) {255			fn unnamed_len(&self) -> usize {256				$count257			}258			#[allow(non_snake_case, unused_assignments)]259			fn unnamed_iter(260				&self,261				ctx: Context,262				tailstrict: bool,263				handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,264			) -> Result<()> {265				let mut i = 0usize;266				let ($($gen,)*) = self;267				$(268					handler(i, $gen.evaluate_arg(ctx.clone(), tailstrict)?)?;269					i+=1;270				)*271				Ok(())272			}273			fn named_iter(274				&self,275				_ctx: Context,276				_tailstrict: bool,277				_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,278			) -> Result<()> {279				Ok(())280			}281			fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}282283			fn is_empty(&self) -> bool {284				// impl_args_like only implements non-empty tuples.285				false286			}287		}288		impl<$($gen: ArgLike,)*> OptionalContext for ($($gen,)*) where $($gen: OptionalContext),* {}289	};290	($count:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => {291		impl_args_like!($count; $($cur)*);292		impl_args_like!($count + 1usize; $($cur)* $c @ $($rest)*);293	};294	($count:expr; $($cur:ident)* @) => {295		impl_args_like!($count; $($cur)*);296	}297}298impl_args_like! {299	// First argument is already in position, so count starts from 1300	1usize; A @ B C D E F G H I J K L301}302303impl ArgsLike for () {304	fn unnamed_len(&self) -> usize {305		0306	}307308	fn unnamed_iter(309		&self,310		_ctx: Context,311		_tailstrict: bool,312		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,313	) -> Result<()> {314		Ok(())315	}316317	fn named_iter(318		&self,319		_ctx: Context,320		_tailstrict: bool,321		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,322	) -> Result<()> {323		Ok(())324	}325326	fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}327	fn is_empty(&self) -> bool {328		true329	}330}331impl OptionalContext for () {}
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -207,7 +207,7 @@
 		tailstrict: bool,
 	) -> Result<Val> {
 		self.evaluate(
-			ContextBuilder::dangerous_empty_state().build(),
+			ContextBuilder::new().build(),
 			CallLocation::native(),
 			args,
 			tailstrict,
modifiedcrates/jrsonnet-evaluator/src/tla.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/tla.rs
+++ b/crates/jrsonnet-evaluator/src/tla.rs
@@ -1,21 +1,24 @@
 use jrsonnet_interner::IStr;
 use jrsonnet_parser::Source;
+use rustc_hash::FxHashMap;
 
 use crate::{
-	function::{ArgsLike, CallLocation},
-	in_description_frame, Result, State, Val,
+	function::{CallLocation, TlaArg},
+	in_description_frame, with_state, Result, Val,
 };
 
-pub fn apply_tla<A: ArgsLike>(s: State, args: &A, val: Val) -> Result<Val> {
+pub fn apply_tla(args: &FxHashMap<IStr, TlaArg>, val: Val) -> Result<Val> {
 	Ok(if let Val::Func(func) = val {
 		in_description_frame(
 			|| "during TLA call".to_owned(),
 			|| {
 				func.evaluate(
-					s.create_default_context(Source::new_virtual(
-						"<top-level-arg>".into(),
-						IStr::empty(),
-					)),
+					with_state(|s| {
+						s.create_default_context(Source::new_virtual(
+							"<top-level-arg>".into(),
+							IStr::empty(),
+						))
+					}),
 					CallLocation::native(),
 					args,
 					false,
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -335,11 +335,6 @@
 	pub path_resolver: PathResolver,
 }
 
-fn extvar_source(name: &str, code: impl Into<IStr>) -> Source {
-	let source_name = format!("<extvar:{name}>");
-	Source::new_virtual(source_name.into(), code.into())
-}
-
 #[derive(Trace, Clone)]
 pub struct ContextInitializer {
 	/// std without applied thisFile overlay
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -3,15 +3,15 @@
 use jrsonnet_evaluator::{
 	bail,
 	error::{ErrorKind::*, Result},
-	function::{builtin, ArgLike, CallLocation, FuncVal},
+	function::{builtin, CallLocation, FuncVal},
 	manifest::JsonFormat,
 	typed::{Either2, Either4},
 	val::{equals, ArrValue},
-	Context, Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,
+	Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,
 };
 use jrsonnet_gcmodule::Cc;
 
-use crate::{extvar_source, Settings};
+use crate::Settings;
 
 #[builtin]
 pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> usize {
@@ -50,16 +50,14 @@
 #[builtin(fields(
 	settings: Cc<RefCell<Settings>>,
 ))]
-pub fn builtin_ext_var(this: &builtin_ext_var, ctx: Context, x: IStr) -> Result<Val> {
-	let ctx = ctx.state().create_default_context(extvar_source(&x, ""));
+pub fn builtin_ext_var(this: &builtin_ext_var, x: IStr) -> Result<Val> {
 	this.settings
 		.borrow()
 		.ext_vars
 		.get(&x)
 		.cloned()
 		.ok_or_else(|| UndefinedExternalVariable(x))?
-		.evaluate_arg(ctx, true)?
-		.evaluate()
+		.evaluate_tailstrict()
 }
 
 #[builtin(fields(
modifiedtests/tests/builtin.rsdiffbeforeafterboth
--- a/tests/tests/builtin.rs
+++ b/tests/tests/builtin.rs
@@ -19,7 +19,7 @@
 fn basic_function() -> Result<()> {
 	let a: a = a {};
 	let v = u32::from_untyped(a.call(
-		ContextBuilder::dangerous_empty_state().build(),
+		ContextBuilder::new().build(),
 		CallLocation::native(),
 		&(),
 	)?)?;