git.delta.rocks / jrsonnet / refs/commits / 88a0ba11fe45

difftreelog

feat field destructuring

Yaroslav Bolyukin2022-04-24parent: #c137fa7.patch.diff
in: master

22 files changed

modifiedbindings/jsonnet/src/native.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -9,7 +9,7 @@
 use jrsonnet_evaluator::{
 	error::{Error, LocError},
 	function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler},
-	gc::TraceBox,
+	tb,
 	typed::Typed,
 	IStr, State, Val,
 };
@@ -78,9 +78,9 @@
 	vm.add_native(
 		name,
 		#[allow(deprecated)]
-		Cc::new(TraceBox(Box::new(NativeCallback::new(
+		Cc::new(tb!(NativeCallback::new(
 			params,
-			TraceBox(Box::new(JsonnetNativeCallbackHandler { ctx, cb })),
-		)))),
+			tb!(JsonnetNativeCallbackHandler { ctx, cb }),
+		))),
 	)
 }
modifiedbindings/jsonnet/src/val_modify.rsdiffbeforeafterboth
--- a/bindings/jsonnet/src/val_modify.rs
+++ b/bindings/jsonnet/src/val_modify.rs
@@ -5,7 +5,7 @@
 use std::{ffi::CStr, os::raw::c_char};
 
 use gcmodule::Cc;
-use jrsonnet_evaluator::{val::ArrValue, LazyVal, State, Val};
+use jrsonnet_evaluator::{val::ArrValue, State, Thunk, Val};
 
 /// # Safety
 ///
@@ -18,7 +18,8 @@
 			for item in old.iter_lazy() {
 				new.push(item);
 			}
-			new.push(LazyVal::new_resolved(val.clone()));
+
+			new.push(Thunk::evaluated(val.clone()));
 			*arr = Val::Arr(ArrValue::Lazy(Cc::new(new)));
 		}
 		_ => panic!("should receive array"),
modifiedcmds/jrsonnet/Cargo.tomldiffbeforeafterboth
--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -16,6 +16,8 @@
     "jrsonnet-evaluator/exp-serde-preserve-order",
     "jrsonnet-cli/exp-preserve-order",
 ]
+# Destructuring of locals
+exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
 
 [dependencies]
 jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" }
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -18,6 +18,8 @@
 # Allows to preserve field order in objects
 exp-preserve-order = []
 exp-serde-preserve-order = ["serde_json/preserve_order"]
+# Implements field destructuring
+exp-destruct = []
 
 [dependencies]
 jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -4,12 +4,12 @@
 use jrsonnet_interner::IStr;
 
 use crate::{
-	cc_ptr_eq, error::Error::*, gc::GcHashMap, map::LayeredHashMap, FutureWrapper, LazyBinding,
-	LazyVal, ObjValue, Result, State, Val,
+	cc_ptr_eq, error::Error::*, gc::GcHashMap, map::LayeredHashMap, LazyBinding, ObjValue, Pending,
+	Result, State, Thunk, Val,
 };
 
 #[derive(Clone, Trace)]
-pub struct ContextCreator(pub Context, pub FutureWrapper<GcHashMap<IStr, LazyBinding>>);
+pub struct ContextCreator(pub Context, pub Pending<GcHashMap<IStr, LazyBinding>>);
 impl ContextCreator {
 	pub fn create(
 		&self,
@@ -43,8 +43,8 @@
 #[derive(Debug, Clone, Trace)]
 pub struct Context(Cc<ContextInternals>);
 impl Context {
-	pub fn new_future() -> FutureWrapper<Self> {
-		FutureWrapper::new()
+	pub fn new_future() -> Pending<Self> {
+		Pending::new()
 	}
 
 	pub fn dollar(&self) -> &Option<ObjValue> {
@@ -68,7 +68,7 @@
 		}))
 	}
 
-	pub fn binding(&self, name: IStr) -> Result<LazyVal> {
+	pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {
 		Ok(self
 			.0
 			.bindings
@@ -80,7 +80,7 @@
 		self.0.bindings.contains_key(&name)
 	}
 	#[must_use]
-	pub fn into_future(self, ctx: FutureWrapper<Self>) -> Self {
+	pub fn into_future(self, ctx: Pending<Self>) -> Self {
 		{
 			ctx.0.borrow_mut().replace(self);
 		}
@@ -90,7 +90,7 @@
 	#[must_use]
 	pub fn with_var(self, name: IStr, value: Val) -> Self {
 		let mut new_bindings = GcHashMap::with_capacity(1);
-		new_bindings.insert(name, LazyVal::new_resolved(value));
+		new_bindings.insert(name, Thunk::evaluated(value));
 		self.extend(new_bindings, None, None, None)
 	}
 
@@ -102,7 +102,7 @@
 	#[must_use]
 	pub fn extend(
 		self,
-		new_bindings: GcHashMap<IStr, LazyVal>,
+		new_bindings: GcHashMap<IStr, Thunk<Val>>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
@@ -124,7 +124,7 @@
 		}))
 	}
 	#[must_use]
-	pub fn extend_bound(self, new_bindings: GcHashMap<IStr, LazyVal>) -> Self {
+	pub fn extend_bound(self, new_bindings: GcHashMap<IStr, Thunk<Val>>) -> Self {
 		let new_this = self.0.this.clone();
 		let new_super_obj = self.0.super_obj.clone();
 		self.extend(new_bindings, None, new_this, new_super_obj)
modifiedcrates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -3,8 +3,8 @@
 use gcmodule::{Cc, Trace};
 
 #[derive(Clone, Trace)]
-pub struct FutureWrapper<V: Trace + 'static>(pub Cc<RefCell<Option<V>>>);
-impl<T: Trace + 'static> FutureWrapper<T> {
+pub struct Pending<V: Trace + 'static>(pub Cc<RefCell<Option<V>>>);
+impl<T: Trace + 'static> Pending<T> {
 	pub fn new() -> Self {
 		Self(Cc::new(RefCell::new(None)))
 	}
@@ -15,7 +15,7 @@
 		self.0.borrow_mut().replace(value);
 	}
 }
-impl<T: Clone + Trace + 'static> FutureWrapper<T> {
+impl<T: Clone + Trace + 'static> Pending<T> {
 	/// # Panics
 	/// If wrapper is not yet filled
 	pub fn unwrap(&self) -> T {
@@ -23,7 +23,7 @@
 	}
 }
 
-impl<T: Trace + 'static> Default for FutureWrapper<T> {
+impl<T: Trace + 'static> Default for Pending<T> {
 	fn default() -> Self {
 		Self::new()
 	}
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -45,6 +45,9 @@
 
 	#[error("variable is not defined: {0}")]
 	VariableIsNotDefined(IStr),
+	#[error("duplicate local var: {0}")]
+	DuplicateLocalVar(IStr),
+
 	#[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{}", e)).collect::<Vec<_>>().join(", "))]
 	TypeMismatch(&'static str, Vec<ValType>, ValType),
 	#[error("no such field: {0}")]
addedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -0,0 +1,294 @@
+use gcmodule::Trace;
+use jrsonnet_interner::IStr;
+use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc};
+
+use crate::{
+	error::{Error::*, Result},
+	evaluate, evaluate_method,
+	gc::GcHashMap,
+	tb, throw,
+	val::ThunkValue,
+	Context, Pending, State, Thunk, Val,
+};
+
+fn destruct(
+	d: &Destruct,
+	parent: Thunk<Val>,
+	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,
+) -> Result<()> {
+	Ok(match d {
+		Destruct::Full(v) => {
+			let old = new_bindings.insert(v.clone(), parent);
+			if old.is_some() {
+				throw!(DuplicateLocalVar(v.clone()))
+			}
+		}
+		#[cfg(feature = "exp-destruct")]
+		Destruct::Skip => {}
+		#[cfg(feature = "exp-destruct")]
+		Destruct::Array { start, rest, end } => {
+			use jrsonnet_parser::DestructRest;
+
+			use crate::{throw_runtime, val::ArrValue};
+
+			#[derive(Trace)]
+			struct DataThunk {
+				parent: Thunk<Val>,
+				min_len: usize,
+				has_rest: bool,
+			}
+			impl ThunkValue for DataThunk {
+				type Output = ArrValue;
+
+				fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
+					let v = self.parent.evaluate(s)?;
+					let arr = match v {
+						Val::Arr(a) => a,
+						_ => throw_runtime!("expected array"),
+					};
+					if !self.has_rest {
+						if arr.len() != self.min_len {
+							throw_runtime!("expected {} elements, got {}", self.min_len, arr.len())
+						}
+					} else if arr.len() < self.min_len {
+						throw_runtime!(
+							"expected at least {} elements, but array was only {}",
+							self.min_len,
+							arr.len()
+						)
+					}
+					Ok(arr)
+				}
+			}
+
+			let full = Thunk::new(tb!(DataThunk {
+				min_len: start.len() + end.len(),
+				has_rest: rest.is_some(),
+				parent,
+			}));
+
+			{
+				#[derive(Trace)]
+				struct BaseThunk {
+					full: Thunk<ArrValue>,
+					index: usize,
+				}
+				impl ThunkValue for BaseThunk {
+					type Output = Val;
+
+					fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
+						let full = self.full.evaluate(s.clone())?;
+						Ok(full.get(s, self.index)?.expect("length is checked"))
+					}
+				}
+				for (i, d) in start.iter().enumerate() {
+					destruct(
+						d,
+						Thunk::new(tb!(BaseThunk {
+							full: full.clone(),
+							index: i,
+						})),
+						new_bindings,
+					)?;
+				}
+			}
+
+			match rest {
+				Some(DestructRest::Keep(v)) => {
+					#[derive(Trace)]
+					struct RestThunk {
+						full: Thunk<ArrValue>,
+						start: usize,
+						end: usize,
+					}
+					impl ThunkValue for RestThunk {
+						type Output = Val;
+
+						fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
+							let full = self.full.evaluate(s)?;
+							let to = full.len() - self.end;
+							Ok(Val::Arr(full.slice(Some(self.start), Some(to), None)))
+						}
+					}
+
+					destruct(
+						&Destruct::Full(v.clone()),
+						Thunk::new(tb!(RestThunk {
+							full: full.clone(),
+							start: start.len(),
+							end: end.len(),
+						})),
+						new_bindings,
+					)?;
+				}
+				Some(DestructRest::Drop) => {}
+				None => {}
+			}
+
+			{
+				#[derive(Trace)]
+				struct EndThunk {
+					full: Thunk<ArrValue>,
+					index: usize,
+					end: usize,
+				}
+				impl ThunkValue for EndThunk {
+					type Output = Val;
+
+					fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
+						let full = self.full.evaluate(s.clone())?;
+						Ok(full
+							.get(s, full.len() - self.end + self.index)?
+							.expect("length is checked"))
+					}
+				}
+				for (i, d) in end.iter().enumerate() {
+					destruct(
+						d,
+						Thunk::new(tb!(EndThunk {
+							full: full.clone(),
+							index: i,
+							end: end.len(),
+						})),
+						new_bindings,
+					)?;
+				}
+			}
+		}
+		#[cfg(feature = "exp-destruct")]
+		Destruct::Object { fields, rest } => {
+			use jrsonnet_parser::DestructRest;
+
+			use crate::{obj::ObjValue, throw_runtime};
+
+			#[derive(Trace)]
+			struct DataThunk {
+				parent: Thunk<Val>,
+				field_names: Vec<IStr>,
+				has_rest: bool,
+			}
+			impl ThunkValue for DataThunk {
+				type Output = ObjValue;
+
+				fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
+					let v = self.parent.evaluate(s)?;
+					let obj = match v {
+						Val::Obj(o) => o,
+						_ => throw_runtime!("expected object"),
+					};
+					for field in &self.field_names {
+						if !obj.has_field_ex(field.clone(), true) {
+							throw_runtime!("missing field: {}", field);
+						}
+					}
+					if !self.has_rest {
+						let len = obj.len();
+						if len != self.field_names.len() {
+							throw_runtime!("too many fields, and rest not found");
+						}
+					}
+					Ok(obj)
+				}
+			}
+			let field_names: Vec<_> = fields.iter().map(|f| f.0.clone()).collect();
+			let full = Thunk::new(tb!(DataThunk {
+				parent,
+				field_names: field_names.clone(),
+				has_rest: rest.is_some()
+			}));
+
+			for (field, d) in fields {
+				#[derive(Trace)]
+				struct FieldThunk {
+					full: Thunk<ObjValue>,
+					field: IStr,
+				}
+				impl ThunkValue for FieldThunk {
+					type Output = Val;
+
+					fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
+						let full = self.full.evaluate(s.clone())?;
+						let field = full.get(s, self.field)?.expect("shape is checked");
+						Ok(field)
+					}
+				}
+				let value = Thunk::new(tb!(FieldThunk {
+					full: full.clone(),
+					field: field.clone()
+				}));
+				if let Some(d) = d {
+					destruct(d, value, new_bindings)?;
+				} else {
+					destruct(&Destruct::Full(field.clone()), value, new_bindings)?;
+				}
+			}
+		}
+	})
+}
+
+pub fn evaluate_dest(
+	d: &BindSpec,
+	fctx: Pending<Context>,
+	new_bindings: &mut GcHashMap<IStr, Thunk<Val>>,
+) -> Result<()> {
+	match d {
+		BindSpec::Field { into, value } => {
+			#[derive(Trace)]
+			struct EvaluateThunkValue {
+				fctx: Pending<Context>,
+				expr: LocExpr,
+			}
+			impl ThunkValue for EvaluateThunkValue {
+				type Output = Val;
+				fn get(self: Box<Self>, s: State) -> Result<Self::Output> {
+					evaluate(s, self.fctx.unwrap(), &self.expr)
+				}
+			}
+			// TODO: Generate some name, as destructure spec may be used with plain functions
+			let data = Thunk::new(tb!(EvaluateThunkValue {
+				fctx,
+				expr: value.clone(),
+			}));
+			destruct(into, data, new_bindings)?;
+		}
+		BindSpec::Function {
+			name,
+			params,
+			value,
+		} => {
+			#[derive(Trace)]
+			struct MethodThunk {
+				fctx: Pending<Context>,
+				name: IStr,
+				params: ParamsDesc,
+				value: LocExpr,
+			}
+			impl ThunkValue for MethodThunk {
+				type Output = Val;
+
+				fn get(self: Box<Self>, _s: State) -> Result<Self::Output> {
+					Ok(evaluate_method(
+						self.fctx.unwrap(),
+						self.name,
+						self.params,
+						self.value,
+					))
+				}
+			}
+
+			let old = new_bindings.insert(
+				name.clone(),
+				Thunk::new(tb!(MethodThunk {
+					fctx,
+					name: name.clone(),
+					params: params.clone(),
+					value: value.clone()
+				})),
+			);
+			if old.is_some() {
+				throw!(DuplicateLocalVar(name.clone()))
+			}
+		}
+	}
+	Ok(())
+}
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/evaluate/mod.rs
1use gcmodule::{Cc, Trace};2use jrsonnet_interner::IStr;3use jrsonnet_parser::{4	ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, ForSpecData, IfSpecData,5	LiteralType, LocExpr, Member, ObjBody, ParamsDesc,6};7use jrsonnet_types::ValType;89use crate::{10	error::Error::*,11	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},12	function::{CallLocation, FuncDesc, FuncVal},13	gc::TraceBox,14	stdlib::{std_slice, BUILTINS},15	throw,16	typed::Typed,17	val::{ArrValue, LazyValValue},18	Bindable, Context, ContextCreator, FutureWrapper, GcHashMap, LazyBinding, LazyVal, ObjValue,19	ObjValueBuilder, ObjectAssertion, Result, State, Val,20};21pub mod operator;2223pub fn evaluate_binding_in_future(b: &BindSpec, fctx: FutureWrapper<Context>) -> LazyVal {24	let b = b.clone();25	if let Some(params) = &b.params {26		#[derive(Trace)]27		struct LazyMethodBinding {28			fctx: FutureWrapper<Context>,29			name: IStr,30			params: ParamsDesc,31			value: LocExpr,32		}33		impl LazyValValue for LazyMethodBinding {34			fn get(self: Box<Self>, _: State) -> Result<Val> {35				Ok(evaluate_method(36					self.fctx.unwrap(),37					self.name,38					self.params,39					self.value,40				))41			}42		}4344		let params = params.clone();4546		LazyVal::new(TraceBox(Box::new(LazyMethodBinding {47			fctx,48			name: b.name.clone(),49			params,50			value: b.value.clone(),51		})))52	} else {53		#[derive(Trace)]54		struct LazyNamedBinding {55			fctx: FutureWrapper<Context>,56			name: IStr,57			value: LocExpr,58		}59		impl LazyValValue for LazyNamedBinding {60			fn get(self: Box<Self>, s: State) -> Result<Val> {61				evaluate_named(s, self.fctx.unwrap(), &self.value, self.name)62			}63		}64		LazyVal::new(TraceBox(Box::new(LazyNamedBinding {65			fctx,66			name: b.name.clone(),67			value: b.value,68		})))69	}70}7172#[allow(clippy::too_many_lines)]73pub fn evaluate_binding(b: &BindSpec, cctx: ContextCreator) -> (IStr, LazyBinding) {74	let b = b.clone();75	if let Some(params) = &b.params {76		#[derive(Trace)]77		struct BindableMethodLazyVal {78			this: Option<ObjValue>,79			super_obj: Option<ObjValue>,8081			cctx: ContextCreator,82			name: IStr,83			params: ParamsDesc,84			value: LocExpr,85		}86		impl LazyValValue for BindableMethodLazyVal {87			fn get(self: Box<Self>, s: State) -> Result<Val> {88				Ok(evaluate_method(89					self.cctx.create(s, self.this, self.super_obj)?,90					self.name,91					self.params,92					self.value,93				))94			}95		}9697		#[derive(Trace)]98		struct BindableMethod {99			cctx: ContextCreator,100			name: IStr,101			params: ParamsDesc,102			value: LocExpr,103		}104		impl Bindable for BindableMethod {105			fn bind(106				&self,107				_: State,108				this: Option<ObjValue>,109				super_obj: Option<ObjValue>,110			) -> Result<LazyVal> {111				Ok(LazyVal::new(TraceBox(Box::new(BindableMethodLazyVal {112					this,113					super_obj,114115					cctx: self.cctx.clone(),116					name: self.name.clone(),117					params: self.params.clone(),118					value: self.value.clone(),119				}))))120			}121		}122123		let params = params.clone();124125		(126			b.name.clone(),127			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableMethod {128				cctx,129				name: b.name.clone(),130				params,131				value: b.value.clone(),132			})))),133		)134	} else {135		#[derive(Trace)]136		struct BindableNamedLazyVal {137			this: Option<ObjValue>,138			super_obj: Option<ObjValue>,139140			cctx: ContextCreator,141			name: IStr,142			value: LocExpr,143		}144		impl LazyValValue for BindableNamedLazyVal {145			fn get(self: Box<Self>, s: State) -> Result<Val> {146				evaluate_named(147					s.clone(),148					self.cctx.create(s, self.this, self.super_obj)?,149					&self.value,150					self.name,151				)152			}153		}154155		#[derive(Trace)]156		struct BindableNamed {157			cctx: ContextCreator,158			name: IStr,159			value: LocExpr,160		}161		impl Bindable for BindableNamed {162			fn bind(163				&self,164				_: State,165				this: Option<ObjValue>,166				super_obj: Option<ObjValue>,167			) -> Result<LazyVal> {168				Ok(LazyVal::new(TraceBox(Box::new(BindableNamedLazyVal {169					this,170					super_obj,171172					cctx: self.cctx.clone(),173					name: self.name.clone(),174					value: self.value.clone(),175				}))))176			}177		}178179		(180			b.name.clone(),181			LazyBinding::Bindable(Cc::new(TraceBox(Box::new(BindableNamed {182				cctx,183				name: b.name.clone(),184				value: b.value.clone(),185			})))),186		)187	}188}189190pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {191	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {192		name,193		ctx,194		params,195		body,196	})))197}198199pub fn evaluate_field_name(200	s: State,201	ctx: Context,202	field_name: &jrsonnet_parser::FieldName,203) -> Result<Option<IStr>> {204	Ok(match field_name {205		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),206		jrsonnet_parser::FieldName::Dyn(expr) => s.push(207			CallLocation::new(&expr.1),208			|| "evaluating field name".to_string(),209			|| {210				let value = evaluate(s.clone(), ctx, expr)?;211				if matches!(value, Val::Null) {212					Ok(None)213				} else {214					Ok(Some(IStr::from_untyped(value, s.clone())?))215				}216			},217		)?,218	})219}220221pub fn evaluate_comp(222	s: State,223	ctx: Context,224	specs: &[CompSpec],225	callback: &mut impl FnMut(Context) -> Result<()>,226) -> Result<()> {227	match specs.get(0) {228		None => callback(ctx)?,229		Some(CompSpec::IfSpec(IfSpecData(cond))) => {230			if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {231				evaluate_comp(s, ctx, &specs[1..], callback)?;232			}233		}234		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {235			match evaluate(s.clone(), ctx.clone(), expr)? {236				Val::Arr(list) => {237					for item in list.iter(s.clone()) {238						evaluate_comp(239							s.clone(),240							ctx.clone().with_var(var.clone(), item?.clone()),241							&specs[1..],242							callback,243						)?;244					}245				}246				_ => throw!(InComprehensionCanOnlyIterateOverArray),247			}248		}249	}250	Ok(())251}252253#[allow(clippy::too_many_lines)]254pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {255	let new_bindings = FutureWrapper::new();256	let future_this = FutureWrapper::new();257	let cctx = ContextCreator(ctx.clone(), new_bindings.clone());258	{259		let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());260		for (n, b) in members261			.iter()262			.filter_map(|m| match m {263				Member::BindStmt(b) => Some(b.clone()),264				_ => None,265			})266			.map(|b| evaluate_binding(&b, cctx.clone()))267		{268			bindings.insert(n, b);269		}270		new_bindings.fill(bindings);271	}272273	let mut builder = ObjValueBuilder::new();274	for member in members.iter() {275		match member {276			Member::Field(FieldMember {277				name,278				plus,279				params: None,280				visibility,281				value,282			}) => {283				#[derive(Trace)]284				struct ObjMemberBinding {285					cctx: ContextCreator,286					value: LocExpr,287					name: IStr,288				}289				impl Bindable for ObjMemberBinding {290					fn bind(291						&self,292						s: State,293						this: Option<ObjValue>,294						super_obj: Option<ObjValue>,295					) -> Result<LazyVal> {296						Ok(LazyVal::new_resolved(evaluate_named(297							s.clone(),298							self.cctx.create(s, this, super_obj)?,299							&self.value,300							self.name.clone(),301						)?))302					}303				}304305				let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;306				let name = if let Some(name) = name {307					name308				} else {309					continue;310				};311312				builder313					.member(name.clone())314					.with_add(*plus)315					.with_visibility(*visibility)316					.with_location(value.1.clone())317					.bindable(318						s.clone(),319						TraceBox(Box::new(ObjMemberBinding {320							cctx: cctx.clone(),321							value: value.clone(),322							name,323						})),324					)?;325			}326			Member::Field(FieldMember {327				name,328				params: Some(params),329				value,330				..331			}) => {332				#[derive(Trace)]333				struct ObjMemberBinding {334					cctx: ContextCreator,335					value: LocExpr,336					params: ParamsDesc,337					name: IStr,338				}339				impl Bindable for ObjMemberBinding {340					fn bind(341						&self,342						s: State,343						this: Option<ObjValue>,344						super_obj: Option<ObjValue>,345					) -> Result<LazyVal> {346						Ok(LazyVal::new_resolved(evaluate_method(347							self.cctx.create(s, this, super_obj)?,348							self.name.clone(),349							self.params.clone(),350							self.value.clone(),351						)))352					}353				}354355				let name = if let Some(name) = evaluate_field_name(s.clone(), ctx.clone(), name)? {356					name357				} else {358					continue;359				};360361				builder362					.member(name.clone())363					.hide()364					.with_location(value.1.clone())365					.bindable(366						s.clone(),367						TraceBox(Box::new(ObjMemberBinding {368							cctx: cctx.clone(),369							value: value.clone(),370							params: params.clone(),371							name,372						})),373					)?;374			}375			Member::BindStmt(_) => {}376			Member::AssertStmt(stmt) => {377				#[derive(Trace)]378				struct ObjectAssert {379					cctx: ContextCreator,380					assert: AssertStmt,381				}382				impl ObjectAssertion for ObjectAssert {383					fn run(384						&self,385						s: State,386						this: Option<ObjValue>,387						super_obj: Option<ObjValue>,388					) -> Result<()> {389						let ctx = self.cctx.create(s.clone(), this, super_obj)?;390						evaluate_assert(s, ctx, &self.assert)391					}392				}393				builder.assert(TraceBox(Box::new(ObjectAssert {394					cctx: cctx.clone(),395					assert: stmt.clone(),396				})));397			}398		}399	}400	let this = builder.build();401	future_this.fill(this.clone());402	Ok(this)403}404405pub fn evaluate_object(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {406	Ok(match object {407		ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,408		ObjBody::ObjComp(obj) => {409			let future_this = FutureWrapper::new();410			let mut builder = ObjValueBuilder::new();411			evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {412				let new_bindings = FutureWrapper::new();413				let cctx = ContextCreator(ctx.clone(), new_bindings.clone());414				let mut bindings: GcHashMap<IStr, LazyBinding> =415					GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());416				for (n, b) in obj417					.pre_locals418					.iter()419					.chain(obj.post_locals.iter())420					.map(|b| evaluate_binding(b, cctx.clone()))421				{422					bindings.insert(n, b);423				}424				new_bindings.fill(bindings.clone());425				let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;426				let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;427428				match key {429					Val::Null => {}430					Val::Str(n) => {431						#[derive(Trace)]432						struct ObjCompBinding {433							ctx: Context,434							value: LocExpr,435						}436						impl Bindable for ObjCompBinding {437							fn bind(438								&self,439								s: State,440								this: Option<ObjValue>,441								_super_obj: Option<ObjValue>,442							) -> Result<LazyVal> {443								Ok(LazyVal::new_resolved(evaluate(444									s,445									self.ctx.clone().extend(GcHashMap::new(), None, this, None),446									&self.value,447								)?))448							}449						}450						builder451							.member(n)452							.with_location(obj.value.1.clone())453							.with_add(obj.plus)454							.bindable(455								s.clone(),456								TraceBox(Box::new(ObjCompBinding {457									ctx,458									value: obj.value.clone(),459								})),460							)?;461					}462					v => throw!(FieldMustBeStringGot(v.value_type())),463				}464465				Ok(())466			})?;467468			let this = builder.build();469			future_this.fill(this.clone());470			this471		}472	})473}474475pub fn evaluate_apply(476	s: State,477	ctx: Context,478	value: &LocExpr,479	args: &ArgsDesc,480	loc: CallLocation,481	tailstrict: bool,482) -> Result<Val> {483	let value = evaluate(s.clone(), ctx.clone(), value)?;484	Ok(match value {485		Val::Func(f) => {486			let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);487			if tailstrict {488				body()?489			} else {490				s.push(loc, || format!("function <{}> call", f.name()), body)?491			}492		}493		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),494	})495}496497pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {498	let value = &assertion.0;499	let msg = &assertion.1;500	let assertion_result = s.push(501		CallLocation::new(&value.1),502		|| "assertion condition".to_owned(),503		|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),504	)?;505	if !assertion_result {506		s.push(507			CallLocation::new(&value.1),508			|| "assertion failure".to_owned(),509			|| {510				if let Some(msg) = msg {511					throw!(AssertionFailed(512						evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?513					));514				}515				throw!(AssertionFailed(Val::Null.to_string(s.clone())?));516			},517		)?;518	}519	Ok(())520}521522pub fn evaluate_named(s: State, ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {523	use Expr::*;524	let LocExpr(raw_expr, _loc) = expr;525	Ok(match &**raw_expr {526		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),527		_ => evaluate(s, ctx, expr)?,528	})529}530531#[allow(clippy::too_many_lines)]532pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {533	use Expr::*;534	let LocExpr(expr, loc) = expr;535	// let bp = with_state(|s| s.0.stop_at.borrow().clone());536	Ok(match &**expr {537		Literal(LiteralType::This) => {538			Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)539		}540		Literal(LiteralType::Super) => Val::Obj(541			ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(542				ctx.this()543					.clone()544					.expect("if super exists - then this should to"),545			),546		),547		Literal(LiteralType::Dollar) => {548			Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)549		}550		Literal(LiteralType::True) => Val::Bool(true),551		Literal(LiteralType::False) => Val::Bool(false),552		Literal(LiteralType::Null) => Val::Null,553		Parened(e) => evaluate(s, ctx, e)?,554		Str(v) => Val::Str(v.clone()),555		Num(v) => Val::new_checked_num(*v)?,556		BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,557		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,558		Var(name) => s.push(559			CallLocation::new(loc),560			|| format!("variable <{}> access", name),561			|| ctx.binding(name.clone())?.evaluate(s.clone()),562		)?,563		Index(value, index) => {564			match (565				evaluate(s.clone(), ctx.clone(), value)?,566				evaluate(s.clone(), ctx, index)?,567			) {568				(Val::Obj(v), Val::Str(key)) => s.push(569					CallLocation::new(loc),570					|| format!("field <{}> access", key),571					|| match v.get(s.clone(), key.clone()) {572						Ok(Some(v)) => Ok(v),573						Ok(None) => throw!(NoSuchField(key.clone())),574						Err(e) if matches!(e.error(), MagicThisFileUsed) => {575							Ok(Val::Str(loc.0.to_string_lossy().into()))576						}577						Err(e) => Err(e),578					},579				)?,580				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(581					ValType::Obj,582					ValType::Str,583					n.value_type(),584				)),585586				(Val::Arr(v), Val::Num(n)) => {587					if n.fract() > f64::EPSILON {588						throw!(FractionalIndex)589					}590					v.get(s, n as usize)?591						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?592				}593				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),594				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(595					ValType::Arr,596					ValType::Num,597					n.value_type(),598				)),599600				(Val::Str(s), Val::Num(n)) => Val::Str({601					let v: IStr = s602						.chars()603						.skip(n as usize)604						.take(1)605						.collect::<String>()606						.into();607					if v.is_empty() {608						let size = s.chars().count();609						throw!(StringBoundsError(n as usize, size))610					}611					v612				}),613				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(614					ValType::Str,615					ValType::Num,616					n.value_type(),617				)),618619				(v, _) => throw!(CantIndexInto(v.value_type())),620			}621		}622		LocalExpr(bindings, returned) => {623			let mut new_bindings: GcHashMap<IStr, LazyVal> =624				GcHashMap::with_capacity(bindings.len());625			let fctx = Context::new_future();626			for b in bindings {627				new_bindings.insert(b.name.clone(), evaluate_binding_in_future(b, fctx.clone()));628			}629			let ctx = ctx.extend_bound(new_bindings).into_future(fctx);630			evaluate(s, ctx, &returned.clone())?631		}632		Arr(items) => {633			let mut out = Vec::with_capacity(items.len());634			for item in items {635				// TODO: Implement ArrValue::Lazy with same context for every element?636				#[derive(Trace)]637				struct ArrayElement {638					ctx: Context,639					item: LocExpr,640				}641				impl LazyValValue for ArrayElement {642					fn get(self: Box<Self>, s: State) -> Result<Val> {643						evaluate(s, self.ctx, &self.item)644					}645				}646				out.push(LazyVal::new(TraceBox(Box::new(ArrayElement {647					ctx: ctx.clone(),648					item: item.clone(),649				}))));650			}651			Val::Arr(out.into())652		}653		ArrComp(expr, comp_specs) => {654			let mut out = Vec::new();655			evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {656				out.push(evaluate(s.clone(), ctx, expr)?);657				Ok(())658			})?;659			Val::Arr(ArrValue::Eager(Cc::new(out)))660		}661		Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),662		ObjExtend(a, b) => evaluate_add_op(663			s.clone(),664			&evaluate(s.clone(), ctx.clone(), a)?,665			&Val::Obj(evaluate_object(s, ctx, b)?),666		)?,667		Apply(value, args, tailstrict) => {668			evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?669		}670		Function(params, body) => {671			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())672		}673		Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(674			BUILTINS675				.with(|b| b.get(name).copied())676				.ok_or_else(|| IntrinsicNotFound(name.clone()))?,677		)),678		IntrinsicThisFile => return Err(MagicThisFileUsed.into()),679		IntrinsicId => Val::Func(FuncVal::identity()),680		AssertExpr(assert, returned) => {681			evaluate_assert(s.clone(), ctx.clone(), assert)?;682			evaluate(s, ctx, returned)?683		}684		ErrorStmt(e) => s.push(685			CallLocation::new(loc),686			|| "error statement".to_owned(),687			|| {688				throw!(RuntimeError(689					evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,690				))691			},692		)?,693		IfElse {694			cond,695			cond_then,696			cond_else,697		} => {698			if s.push(699				CallLocation::new(loc),700				|| "if condition".to_owned(),701				|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),702			)? {703				evaluate(s, ctx, cond_then)?704			} else {705				match cond_else {706					Some(v) => evaluate(s, ctx, v)?,707					None => Val::Null,708				}709			}710		}711		Slice(value, desc) => {712			fn parse_idx<T: Typed>(713				loc: CallLocation,714				s: State,715				ctx: &Context,716				expr: &Option<LocExpr>,717				desc: &'static str,718			) -> Result<Option<T>> {719				if let Some(value) = expr {720					Ok(Some(s.push(721						loc,722						|| format!("slice {}", desc),723						|| T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),724					)?))725				} else {726					Ok(None)727				}728			}729730			let indexable = evaluate(s.clone(), ctx.clone(), value)?;731			let loc = CallLocation::new(loc);732733			let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;734			let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;735			let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;736737			std_slice(indexable.into_indexable()?, start, end, step)?738		}739		Import(path) => {740			let tmp = loc.clone().0;741			let mut import_location = tmp.to_path_buf();742			import_location.pop();743			s.push(744				CallLocation::new(loc),745				|| format!("import {:?}", path),746				|| s.import_file(&import_location, path),747			)?748		}749		ImportStr(path) => {750			let tmp = loc.clone().0;751			let mut import_location = tmp.to_path_buf();752			import_location.pop();753			Val::Str(s.import_file_str(&import_location, path)?)754		}755		ImportBin(path) => {756			let tmp = loc.clone().0;757			let mut import_location = tmp.to_path_buf();758			import_location.pop();759			let bytes = s.import_file_bin(&import_location, path)?;760			Val::Arr(ArrValue::Bytes(bytes))761		}762	})763}
after · crates/jrsonnet-evaluator/src/evaluate/mod.rs
1use gcmodule::{Cc, Trace};2use jrsonnet_interner::IStr;3use jrsonnet_parser::{4	ArgsDesc, AssertStmt, BindSpec, CompSpec, Destruct, Expr, FieldMember, ForSpecData, IfSpecData,5	LiteralType, LocExpr, Member, ObjBody, ParamsDesc,6};7use jrsonnet_types::ValType;89use crate::{10	destructure::evaluate_dest,11	error::Error::*,12	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},13	function::{CallLocation, FuncDesc, FuncVal},14	stdlib::{std_slice, BUILTINS},15	tb, throw,16	typed::Typed,17	val::{ArrValue, Thunk, ThunkValue},18	Bindable, Context, ContextCreator, GcHashMap, LazyBinding, ObjValue, ObjValueBuilder,19	ObjectAssertion, Pending, Result, State, Val,20};21pub mod destructure;22pub mod operator;2324#[allow(clippy::too_many_lines)]25pub fn evaluate_binding(b: BindSpec, cctx: ContextCreator) -> Result<(IStr, LazyBinding)> {26	match b {27		BindSpec::Field {28			into: Destruct::Full(name),29			value,30		} => {31			#[derive(Trace)]32			struct BindableNamedThunk {33				this: Option<ObjValue>,34				super_obj: Option<ObjValue>,3536				cctx: ContextCreator,37				name: IStr,38				value: LocExpr,39			}40			impl ThunkValue for BindableNamedThunk {41				type Output = Val;42				fn get(self: Box<Self>, s: State) -> Result<Val> {43					evaluate_named(44						s.clone(),45						self.cctx.create(s, self.this, self.super_obj)?,46						&self.value,47						self.name,48					)49				}50			}5152			#[derive(Trace)]53			struct BindableNamed {54				cctx: ContextCreator,55				name: IStr,56				value: LocExpr,57			}58			impl Bindable for BindableNamed {59				fn bind(60					&self,61					_: State,62					this: Option<ObjValue>,63					super_obj: Option<ObjValue>,64				) -> Result<Thunk<Val>> {65					Ok(Thunk::new(tb!(BindableNamedThunk {66						this,67						super_obj,6869						cctx: self.cctx.clone(),70						name: self.name.clone(),71						value: self.value.clone(),72					})))73				}74			}7576			Ok((77				name.clone(),78				LazyBinding::Bindable(Cc::new(tb!(BindableNamed {79					cctx,80					name: name.clone(),81					value: value.clone(),82				}))),83			))84		}85		#[cfg(feature = "exp-destruct")]86		BindSpec::Field { into: _, .. } => {87			use crate::throw_runtime;88			throw_runtime!("destructuring is not yet supported here")89		}90		BindSpec::Function {91			name,92			params,93			value,94		} => {95			#[derive(Trace)]96			struct BindableMethodThunk {97				this: Option<ObjValue>,98				super_obj: Option<ObjValue>,99100				cctx: ContextCreator,101				name: IStr,102				params: ParamsDesc,103				value: LocExpr,104			}105			impl ThunkValue for BindableMethodThunk {106				type Output = Val;107				fn get(self: Box<Self>, s: State) -> Result<Val> {108					Ok(evaluate_method(109						self.cctx.create(s, self.this, self.super_obj)?,110						self.name,111						self.params,112						self.value,113					))114				}115			}116117			#[derive(Trace)]118			struct BindableMethod {119				cctx: ContextCreator,120				name: IStr,121				params: ParamsDesc,122				value: LocExpr,123			}124			impl Bindable for BindableMethod {125				fn bind(126					&self,127					_: State,128					this: Option<ObjValue>,129					super_obj: Option<ObjValue>,130				) -> Result<Thunk<Val>> {131					Ok(Thunk::<Val>::new(tb!(BindableMethodThunk {132						this,133						super_obj,134135						cctx: self.cctx.clone(),136						name: self.name.clone(),137						params: self.params.clone(),138						value: self.value.clone(),139					})))140				}141			}142143			let params = params.clone();144145			Ok((146				name.clone(),147				LazyBinding::Bindable(Cc::new(tb!(BindableMethod {148					cctx,149					name: name.clone(),150					params,151					value,152				}))),153			))154		}155	}156}157158pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {159	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {160		name,161		ctx,162		params,163		body,164	})))165}166167pub fn evaluate_field_name(168	s: State,169	ctx: Context,170	field_name: &jrsonnet_parser::FieldName,171) -> Result<Option<IStr>> {172	Ok(match field_name {173		jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()),174		jrsonnet_parser::FieldName::Dyn(expr) => s.push(175			CallLocation::new(&expr.1),176			|| "evaluating field name".to_string(),177			|| {178				let value = evaluate(s.clone(), ctx, expr)?;179				if matches!(value, Val::Null) {180					Ok(None)181				} else {182					Ok(Some(IStr::from_untyped(value, s.clone())?))183				}184			},185		)?,186	})187}188189pub fn evaluate_comp(190	s: State,191	ctx: Context,192	specs: &[CompSpec],193	callback: &mut impl FnMut(Context) -> Result<()>,194) -> Result<()> {195	match specs.get(0) {196		None => callback(ctx)?,197		Some(CompSpec::IfSpec(IfSpecData(cond))) => {198			if bool::from_untyped(evaluate(s.clone(), ctx.clone(), cond)?, s.clone())? {199				evaluate_comp(s, ctx, &specs[1..], callback)?;200			}201		}202		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {203			match evaluate(s.clone(), ctx.clone(), expr)? {204				Val::Arr(list) => {205					for item in list.iter(s.clone()) {206						evaluate_comp(207							s.clone(),208							ctx.clone().with_var(var.clone(), item?.clone()),209							&specs[1..],210							callback,211						)?;212					}213				}214				_ => throw!(InComprehensionCanOnlyIterateOverArray),215			}216		}217	}218	Ok(())219}220221#[allow(clippy::too_many_lines)]222pub fn evaluate_member_list_object(s: State, ctx: Context, members: &[Member]) -> Result<ObjValue> {223	let new_bindings = Pending::new();224	let future_this = Pending::new();225	let cctx = ContextCreator(ctx.clone(), new_bindings.clone());226	{227		let mut bindings: GcHashMap<IStr, LazyBinding> = GcHashMap::with_capacity(members.len());228		for r in members229			.iter()230			.filter_map(|m| match m {231				Member::BindStmt(b) => Some(b.clone()),232				_ => None,233			})234			.map(|b| evaluate_binding(b.clone(), cctx.clone()))235		{236			let (n, b) = r?;237			bindings.insert(n, b);238		}239		new_bindings.fill(bindings);240	}241242	let mut builder = ObjValueBuilder::new();243	for member in members.iter() {244		match member {245			Member::Field(FieldMember {246				name,247				plus,248				params: None,249				visibility,250				value,251			}) => {252				#[derive(Trace)]253				struct ObjMemberBinding {254					cctx: ContextCreator,255					value: LocExpr,256					name: IStr,257				}258				impl Bindable for ObjMemberBinding {259					fn bind(260						&self,261						s: State,262						this: Option<ObjValue>,263						super_obj: Option<ObjValue>,264					) -> Result<Thunk<Val>> {265						Ok(Thunk::evaluated(evaluate_named(266							s.clone(),267							self.cctx.create(s, this, super_obj)?,268							&self.value,269							self.name.clone(),270						)?))271					}272				}273274				let name = evaluate_field_name(s.clone(), ctx.clone(), name)?;275				let name = if let Some(name) = name {276					name277				} else {278					continue;279				};280281				builder282					.member(name.clone())283					.with_add(*plus)284					.with_visibility(*visibility)285					.with_location(value.1.clone())286					.bindable(287						s.clone(),288						tb!(ObjMemberBinding {289							cctx: cctx.clone(),290							value: value.clone(),291							name,292						}),293					)?;294			}295			Member::Field(FieldMember {296				name,297				params: Some(params),298				value,299				..300			}) => {301				#[derive(Trace)]302				struct ObjMemberBinding {303					cctx: ContextCreator,304					value: LocExpr,305					params: ParamsDesc,306					name: IStr,307				}308				impl Bindable for ObjMemberBinding {309					fn bind(310						&self,311						s: State,312						this: Option<ObjValue>,313						super_obj: Option<ObjValue>,314					) -> Result<Thunk<Val>> {315						Ok(Thunk::evaluated(evaluate_method(316							self.cctx.create(s, this, super_obj)?,317							self.name.clone(),318							self.params.clone(),319							self.value.clone(),320						)))321					}322				}323324				let name = if let Some(name) = evaluate_field_name(s.clone(), ctx.clone(), name)? {325					name326				} else {327					continue;328				};329330				builder331					.member(name.clone())332					.hide()333					.with_location(value.1.clone())334					.bindable(335						s.clone(),336						tb!(ObjMemberBinding {337							cctx: cctx.clone(),338							value: value.clone(),339							params: params.clone(),340							name,341						}),342					)?;343			}344			Member::BindStmt(_) => {}345			Member::AssertStmt(stmt) => {346				#[derive(Trace)]347				struct ObjectAssert {348					cctx: ContextCreator,349					assert: AssertStmt,350				}351				impl ObjectAssertion for ObjectAssert {352					fn run(353						&self,354						s: State,355						this: Option<ObjValue>,356						super_obj: Option<ObjValue>,357					) -> Result<()> {358						let ctx = self.cctx.create(s.clone(), this, super_obj)?;359						evaluate_assert(s, ctx, &self.assert)360					}361				}362				builder.assert(tb!(ObjectAssert {363					cctx: cctx.clone(),364					assert: stmt.clone(),365				}));366			}367		}368	}369	let this = builder.build();370	future_this.fill(this.clone());371	Ok(this)372}373374pub fn evaluate_object(s: State, ctx: Context, object: &ObjBody) -> Result<ObjValue> {375	Ok(match object {376		ObjBody::MemberList(members) => evaluate_member_list_object(s, ctx, members)?,377		ObjBody::ObjComp(obj) => {378			let future_this = Pending::new();379			let mut builder = ObjValueBuilder::new();380			evaluate_comp(s.clone(), ctx, &obj.compspecs, &mut |ctx| {381				let new_bindings = Pending::new();382				let cctx = ContextCreator(ctx.clone(), new_bindings.clone());383				let mut bindings: GcHashMap<IStr, LazyBinding> =384					GcHashMap::with_capacity(obj.pre_locals.len() + obj.post_locals.len());385				for r in obj386					.pre_locals387					.iter()388					.chain(obj.post_locals.iter())389					.map(|b| evaluate_binding(b.clone(), cctx.clone()))390				{391					let (n, b) = r?;392					bindings.insert(n, b);393				}394				new_bindings.fill(bindings.clone());395				let ctx = ctx.extend_unbound(s.clone(), bindings, None, None, None)?;396				let key = evaluate(s.clone(), ctx.clone(), &obj.key)?;397398				match key {399					Val::Null => {}400					Val::Str(n) => {401						#[derive(Trace)]402						struct ObjCompBinding {403							ctx: Context,404							value: LocExpr,405						}406						impl Bindable for ObjCompBinding {407							fn bind(408								&self,409								s: State,410								this: Option<ObjValue>,411								_super_obj: Option<ObjValue>,412							) -> Result<Thunk<Val>> {413								Ok(Thunk::evaluated(evaluate(414									s,415									self.ctx.clone().extend(GcHashMap::new(), None, this, None),416									&self.value,417								)?))418							}419						}420						builder421							.member(n)422							.with_location(obj.value.1.clone())423							.with_add(obj.plus)424							.bindable(425								s.clone(),426								tb!(ObjCompBinding {427									ctx,428									value: obj.value.clone(),429								}),430							)?;431					}432					v => throw!(FieldMustBeStringGot(v.value_type())),433				}434435				Ok(())436			})?;437438			let this = builder.build();439			future_this.fill(this.clone());440			this441		}442	})443}444445pub fn evaluate_apply(446	s: State,447	ctx: Context,448	value: &LocExpr,449	args: &ArgsDesc,450	loc: CallLocation,451	tailstrict: bool,452) -> Result<Val> {453	let value = evaluate(s.clone(), ctx.clone(), value)?;454	Ok(match value {455		Val::Func(f) => {456			let body = || f.evaluate(s.clone(), ctx, loc, args, tailstrict);457			if tailstrict {458				body()?459			} else {460				s.push(loc, || format!("function <{}> call", f.name()), body)?461			}462		}463		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),464	})465}466467pub fn evaluate_assert(s: State, ctx: Context, assertion: &AssertStmt) -> Result<()> {468	let value = &assertion.0;469	let msg = &assertion.1;470	let assertion_result = s.push(471		CallLocation::new(&value.1),472		|| "assertion condition".to_owned(),473		|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),474	)?;475	if !assertion_result {476		s.push(477			CallLocation::new(&value.1),478			|| "assertion failure".to_owned(),479			|| {480				if let Some(msg) = msg {481					throw!(AssertionFailed(482						evaluate(s.clone(), ctx, msg)?.to_string(s.clone())?483					));484				}485				throw!(AssertionFailed(Val::Null.to_string(s.clone())?));486			},487		)?;488	}489	Ok(())490}491492pub fn evaluate_named(s: State, ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {493	use Expr::*;494	let LocExpr(raw_expr, _loc) = expr;495	Ok(match &**raw_expr {496		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),497		_ => evaluate(s, ctx, expr)?,498	})499}500501#[allow(clippy::too_many_lines)]502pub fn evaluate(s: State, ctx: Context, expr: &LocExpr) -> Result<Val> {503	use Expr::*;504	let LocExpr(expr, loc) = expr;505	// let bp = with_state(|s| s.0.stop_at.borrow().clone());506	Ok(match &**expr {507		Literal(LiteralType::This) => {508			Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)509		}510		Literal(LiteralType::Super) => Val::Obj(511			ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(512				ctx.this()513					.clone()514					.expect("if super exists - then this should to"),515			),516		),517		Literal(LiteralType::Dollar) => {518			Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)519		}520		Literal(LiteralType::True) => Val::Bool(true),521		Literal(LiteralType::False) => Val::Bool(false),522		Literal(LiteralType::Null) => Val::Null,523		Parened(e) => evaluate(s, ctx, e)?,524		Str(v) => Val::Str(v.clone()),525		Num(v) => Val::new_checked_num(*v)?,526		BinaryOp(v1, o, v2) => evaluate_binary_op_special(s, ctx, v1, *o, v2)?,527		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,528		Var(name) => s.push(529			CallLocation::new(loc),530			|| format!("variable <{}> access", name),531			|| ctx.binding(name.clone())?.evaluate(s.clone()),532		)?,533		Index(value, index) => {534			match (535				evaluate(s.clone(), ctx.clone(), value)?,536				evaluate(s.clone(), ctx, index)?,537			) {538				(Val::Obj(v), Val::Str(key)) => s.push(539					CallLocation::new(loc),540					|| format!("field <{}> access", key),541					|| match v.get(s.clone(), key.clone()) {542						Ok(Some(v)) => Ok(v),543						Ok(None) => throw!(NoSuchField(key.clone())),544						Err(e) if matches!(e.error(), MagicThisFileUsed) => {545							Ok(Val::Str(loc.0.to_string_lossy().into()))546						}547						Err(e) => Err(e),548					},549				)?,550				(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(551					ValType::Obj,552					ValType::Str,553					n.value_type(),554				)),555556				(Val::Arr(v), Val::Num(n)) => {557					if n.fract() > f64::EPSILON {558						throw!(FractionalIndex)559					}560					v.get(s, n as usize)?561						.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?562				}563				(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),564				(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(565					ValType::Arr,566					ValType::Num,567					n.value_type(),568				)),569570				(Val::Str(s), Val::Num(n)) => Val::Str({571					let v: IStr = s572						.chars()573						.skip(n as usize)574						.take(1)575						.collect::<String>()576						.into();577					if v.is_empty() {578						let size = s.chars().count();579						throw!(StringBoundsError(n as usize, size))580					}581					v582				}),583				(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(584					ValType::Str,585					ValType::Num,586					n.value_type(),587				)),588589				(v, _) => throw!(CantIndexInto(v.value_type())),590			}591		}592		LocalExpr(bindings, returned) => {593			let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =594				GcHashMap::with_capacity(bindings.len());595			let fctx = Context::new_future();596			for b in bindings {597				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;598			}599			let ctx = ctx.extend_bound(new_bindings).into_future(fctx);600			evaluate(s, ctx, &returned.clone())?601		}602		Arr(items) => {603			let mut out = Vec::with_capacity(items.len());604			for item in items {605				// TODO: Implement ArrValue::Lazy with same context for every element?606				#[derive(Trace)]607				struct ArrayElement {608					ctx: Context,609					item: LocExpr,610				}611				impl ThunkValue for ArrayElement {612					type Output = Val;613					fn get(self: Box<Self>, s: State) -> Result<Val> {614						evaluate(s, self.ctx, &self.item)615					}616				}617				out.push(Thunk::new(tb!(ArrayElement {618					ctx: ctx.clone(),619					item: item.clone(),620				})));621			}622			Val::Arr(out.into())623		}624		ArrComp(expr, comp_specs) => {625			let mut out = Vec::new();626			evaluate_comp(s.clone(), ctx, comp_specs, &mut |ctx| {627				out.push(evaluate(s.clone(), ctx, expr)?);628				Ok(())629			})?;630			Val::Arr(ArrValue::Eager(Cc::new(out)))631		}632		Obj(body) => Val::Obj(evaluate_object(s, ctx, body)?),633		ObjExtend(a, b) => evaluate_add_op(634			s.clone(),635			&evaluate(s.clone(), ctx.clone(), a)?,636			&Val::Obj(evaluate_object(s, ctx, b)?),637		)?,638		Apply(value, args, tailstrict) => {639			evaluate_apply(s, ctx, value, args, CallLocation::new(loc), *tailstrict)?640		}641		Function(params, body) => {642			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())643		}644		Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(645			BUILTINS646				.with(|b| b.get(name).copied())647				.ok_or_else(|| IntrinsicNotFound(name.clone()))?,648		)),649		IntrinsicThisFile => return Err(MagicThisFileUsed.into()),650		IntrinsicId => Val::Func(FuncVal::identity()),651		AssertExpr(assert, returned) => {652			evaluate_assert(s.clone(), ctx.clone(), assert)?;653			evaluate(s, ctx, returned)?654		}655		ErrorStmt(e) => s.push(656			CallLocation::new(loc),657			|| "error statement".to_owned(),658			|| {659				throw!(RuntimeError(660					evaluate(s.clone(), ctx, e)?.to_string(s.clone())?,661				))662			},663		)?,664		IfElse {665			cond,666			cond_then,667			cond_else,668		} => {669			if s.push(670				CallLocation::new(loc),671				|| "if condition".to_owned(),672				|| bool::from_untyped(evaluate(s.clone(), ctx.clone(), &cond.0)?, s.clone()),673			)? {674				evaluate(s, ctx, cond_then)?675			} else {676				match cond_else {677					Some(v) => evaluate(s, ctx, v)?,678					None => Val::Null,679				}680			}681		}682		Slice(value, desc) => {683			fn parse_idx<T: Typed>(684				loc: CallLocation,685				s: State,686				ctx: &Context,687				expr: &Option<LocExpr>,688				desc: &'static str,689			) -> Result<Option<T>> {690				if let Some(value) = expr {691					Ok(Some(s.push(692						loc,693						|| format!("slice {}", desc),694						|| T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),695					)?))696				} else {697					Ok(None)698				}699			}700701			let indexable = evaluate(s.clone(), ctx.clone(), value)?;702			let loc = CallLocation::new(loc);703704			let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;705			let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;706			let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;707708			std_slice(indexable.into_indexable()?, start, end, step)?709		}710		Import(path) => {711			let tmp = loc.clone().0;712			let mut import_location = tmp.to_path_buf();713			import_location.pop();714			s.push(715				CallLocation::new(loc),716				|| format!("import {:?}", path),717				|| s.import_file(&import_location, path),718			)?719		}720		ImportStr(path) => {721			let tmp = loc.clone().0;722			let mut import_location = tmp.to_path_buf();723			import_location.pop();724			Val::Str(s.import_file_str(&import_location, path)?)725		}726		ImportBin(path) => {727			let tmp = loc.clone().0;728			let mut import_location = tmp.to_path_buf();729			import_location.pop();730			let bytes = s.import_file_bin(&import_location, path)?;731			Val::Arr(ArrValue::Bytes(bytes))732		}733	})734}
modifiedcrates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -5,34 +5,34 @@
 use jrsonnet_parser::{ArgsDesc, LocExpr};
 
 use crate::{
-	error::Result, evaluate, gc::TraceBox, typed::Typed, val::LazyValValue, Context, LazyVal,
-	State, Val,
+	error::Result, evaluate, tb, typed::Typed, val::ThunkValue, Context, State, Thunk, Val,
 };
 
 #[derive(Trace)]
-struct EvaluateLazyVal {
+struct EvaluateThunk {
 	ctx: Context,
 	expr: LocExpr,
 }
-impl LazyValValue for EvaluateLazyVal {
+impl ThunkValue for EvaluateThunk {
+	type Output = Val;
 	fn get(self: Box<Self>, s: State) -> Result<Val> {
 		evaluate(s, self.ctx, &self.expr)
 	}
 }
 
 pub trait ArgLike {
-	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
+	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>>;
 }
 
 impl ArgLike for &LocExpr {
-	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
+	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {
 		Ok(if tailstrict {
-			LazyVal::new_resolved(evaluate(s, ctx, self)?)
+			Thunk::evaluated(evaluate(s, ctx, self)?)
 		} else {
-			LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+			Thunk::new(tb!(EvaluateThunk {
 				ctx,
 				expr: (*self).clone(),
-			})))
+			}))
 		})
 	}
 }
@@ -41,9 +41,9 @@
 where
 	T: Typed + Clone,
 {
-	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<LazyVal> {
+	fn evaluate_arg(&self, s: State, _ctx: Context, _tailstrict: bool) -> Result<Thunk<Val>> {
 		let val = T::into_untyped(self.clone(), s)?;
-		Ok(LazyVal::new_resolved(val))
+		Ok(Thunk::evaluated(val))
 	}
 }
 
@@ -53,18 +53,18 @@
 	Val(Val),
 }
 impl ArgLike for TlaArg {
-	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal> {
+	fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {
 		match self {
-			TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))),
+			TlaArg::String(s) => Ok(Thunk::evaluated(Val::Str(s.clone()))),
 			TlaArg::Code(code) => Ok(if tailstrict {
-				LazyVal::new_resolved(evaluate(s, ctx, code)?)
+				Thunk::evaluated(evaluate(s, ctx, code)?)
 			} else {
-				LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+				Thunk::new(tb!(EvaluateThunk {
 					ctx,
 					expr: code.clone(),
-				})))
+				}))
 			}),
-			TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())),
+			TlaArg::Val(val) => Ok(Thunk::evaluated(val.clone())),
 		}
 	}
 }
@@ -83,14 +83,14 @@
 		s: State,
 		ctx: Context,
 		tailstrict: bool,
-		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
 	) -> Result<()>;
 	fn named_iter(
 		&self,
 		s: State,
 		ctx: Context,
 		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
 	) -> Result<()>;
 	fn named_names(&self, handler: &mut dyn FnMut(&IStr));
 }
@@ -105,18 +105,18 @@
 		s: State,
 		ctx: Context,
 		tailstrict: bool,
-		handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+		handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
 	) -> Result<()> {
 		for (id, arg) in self.unnamed.iter().enumerate() {
 			handler(
 				id,
 				if tailstrict {
-					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
+					Thunk::evaluated(evaluate(s.clone(), ctx.clone(), arg)?)
 				} else {
-					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+					Thunk::new(tb!(EvaluateThunk {
 						ctx: ctx.clone(),
 						expr: arg.clone(),
-					})))
+					}))
 				},
 			)?;
 		}
@@ -128,18 +128,18 @@
 		s: State,
 		ctx: Context,
 		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
 	) -> Result<()> {
 		for (name, arg) in &self.named {
 			handler(
 				name,
 				if tailstrict {
-					LazyVal::new_resolved(evaluate(s.clone(), ctx.clone(), arg)?)
+					Thunk::evaluated(evaluate(s.clone(), ctx.clone(), arg)?)
 				} else {
-					LazyVal::new(TraceBox(Box::new(EvaluateLazyVal {
+					Thunk::new(tb!(EvaluateThunk {
 						ctx: ctx.clone(),
 						expr: arg.clone(),
-					})))
+					}))
 				},
 			)?;
 		}
@@ -164,7 +164,7 @@
 		_s: State,
 		_ctx: Context,
 		_tailstrict: bool,
-		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
 	) -> Result<()> {
 		Ok(())
 	}
@@ -174,7 +174,7 @@
 		s: State,
 		ctx: Context,
 		tailstrict: bool,
-		handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+		handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
 	) -> Result<()> {
 		for (name, value) in self.iter() {
 			handler(
@@ -205,7 +205,7 @@
 				s: State,
 				ctx: Context,
 				tailstrict: bool,
-				handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+				handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
 			) -> Result<()> {
 				let mut i = 0usize;
 				let ($($gen,)*) = self;
@@ -220,7 +220,7 @@
 				_s: State,
 				_ctx: Context,
 				_tailstrict: bool,
-				_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+				_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
 			) -> Result<()> {
 				Ok(())
 			}
@@ -236,7 +236,7 @@
 				_s: State,
 				_ctx: Context,
 				_tailstrict: bool,
-				_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+				_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
 			) -> Result<()> {
 				Ok(())
 			}
@@ -246,7 +246,7 @@
 				s: State,
 				ctx: Context,
 				tailstrict: bool,
-				handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+				handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
 			) -> Result<()> {
 				let ($($gen,)*) = self;
 				$(
@@ -285,7 +285,7 @@
 		_s: State,
 		_ctx: Context,
 		_tailstrict: bool,
-		_handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+		_handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
 	) -> Result<()> {
 		Ok(())
 	}
@@ -295,7 +295,7 @@
 		_s: State,
 		_ctx: Context,
 		_tailstrict: bool,
-		_handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+		_handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
 	) -> Result<()> {
 		Ok(())
 	}
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -9,20 +9,21 @@
 use crate::{
 	error::{Error::*, Result},
 	evaluate_named,
-	gc::{GcHashMap, TraceBox},
-	throw,
-	val::LazyValValue,
-	Context, FutureWrapper, LazyVal, State, Val,
+	gc::GcHashMap,
+	tb, throw,
+	val::ThunkValue,
+	Context, Pending, State, Thunk, Val,
 };
 
 #[derive(Trace)]
-struct EvaluateNamedLazyVal {
-	ctx: FutureWrapper<Context>,
+struct EvaluateNamedThunk {
+	ctx: Pending<Context>,
 	name: IStr,
 	value: LocExpr,
 }
 
-impl LazyValValue for EvaluateNamedLazyVal {
+impl ThunkValue for EvaluateNamedThunk {
+	type Output = Val;
 	fn get(self: Box<Self>, s: State) -> Result<Val> {
 		evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
 	}
@@ -83,11 +84,11 @@
 
 			defaults.insert(
 				param.0.clone(),
-				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
+				Thunk::new(tb!(EvaluateNamedThunk {
 					ctx: fctx.clone(),
 					name: param.0.clone(),
 					value: param.1.clone().expect("default exists"),
-				}))),
+				})),
 			);
 			filled_args += 1;
 		}
@@ -131,7 +132,7 @@
 	params: &[BuiltinParam],
 	args: &dyn ArgsLike,
 	tailstrict: bool,
-) -> Result<GcHashMap<BuiltinParamName, LazyVal>> {
+) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {
 	let mut passed_args = GcHashMap::with_capacity(params.len());
 	if args.unnamed_len() > params.len() {
 		throw!(TooManyArgsFunctionHas(params.len()))
@@ -191,7 +192,8 @@
 pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Context {
 	#[derive(Trace)]
 	struct DependsOnUnbound(IStr);
-	impl LazyValValue for DependsOnUnbound {
+	impl ThunkValue for DependsOnUnbound {
+		type Output = Val;
 		fn get(self: Box<Self>, _: State) -> Result<Val> {
 			Err(FunctionParameterNotBoundInCall(self.0.clone()).into())
 		}
@@ -205,16 +207,16 @@
 		if let Some(v) = &param.1 {
 			bindings.insert(
 				param.0.clone(),
-				LazyVal::new(TraceBox(Box::new(EvaluateNamedLazyVal {
+				Thunk::new(tb!(EvaluateNamedThunk {
 					ctx: fctx.clone(),
 					name: param.0.clone(),
 					value: v.clone(),
-				}))),
+				})),
 			);
 		} else {
 			bindings.insert(
 				param.0.clone(),
-				LazyVal::new(TraceBox(Box::new(DependsOnUnbound(param.0.clone())))),
+				Thunk::new(tb!(DependsOnUnbound(param.0.clone()))),
 			);
 		}
 	}
modifiedcrates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -10,8 +10,15 @@
 use rustc_hash::{FxHashMap, FxHashSet};
 
 /// Replacement for box, which assumes that the underlying type is [`Trace`]
+/// Used in places, where Cc<dyn Trait> should be used instead, but it can't, because CoerceUnsiced is not stable
 #[derive(Debug, Clone)]
 pub struct TraceBox<T: ?Sized>(pub Box<T>);
+#[macro_export]
+macro_rules! tb {
+	($v:expr) => {
+		$crate::gc::TraceBox(Box::new($v))
+	};
+}
 
 impl<T: ?Sized + Trace> Trace for TraceBox<T> {
 	fn trace(&self, tracer: &mut Tracer) {
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -58,7 +58,7 @@
 use jrsonnet_parser::*;
 pub use obj::*;
 use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat};
-pub use val::{LazyVal, ManifestFormat, Val};
+pub use val::{ManifestFormat, Thunk, Val};
 
 pub trait Bindable: Trace + 'static {
 	fn bind(
@@ -66,13 +66,13 @@
 		s: State,
 		this: Option<ObjValue>,
 		super_obj: Option<ObjValue>,
-	) -> Result<LazyVal>;
+	) -> Result<Thunk<Val>>;
 }
 
 #[derive(Clone, Trace)]
 pub enum LazyBinding {
 	Bindable(Cc<TraceBox<dyn Bindable>>),
-	Bound(LazyVal),
+	Bound(Thunk<Val>),
 }
 
 impl Debug for LazyBinding {
@@ -86,7 +86,7 @@
 		s: State,
 		this: Option<ObjValue>,
 		super_obj: Option<ObjValue>,
-	) -> Result<LazyVal> {
+	) -> Result<Thunk<Val>> {
 		match self {
 			Self::Bindable(v) => v.bind(s, this, super_obj),
 			Self::Bound(v) => Ok(v.clone()),
@@ -343,7 +343,7 @@
 		let globals = &self.settings().globals;
 		let mut new_bindings = GcHashMap::with_capacity(globals.len());
 		for (name, value) in globals.iter() {
-			new_bindings.insert(name.clone(), LazyVal::new_resolved(value.clone()));
+			new_bindings.insert(name.clone(), Thunk::evaluated(value.clone()));
 		}
 		Context::new().extend_bound(new_bindings)
 	}
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -1,27 +1,27 @@
 use gcmodule::{Cc, Trace};
 use jrsonnet_interner::IStr;
 
-use crate::{GcHashMap, LazyVal};
+use crate::{GcHashMap, Thunk, Val};
 
 #[derive(Trace)]
 #[force_tracking]
 pub struct LayeredHashMapInternals {
 	parent: Option<LayeredHashMap>,
-	current: GcHashMap<IStr, LazyVal>,
+	current: GcHashMap<IStr, Thunk<Val>>,
 }
 
 #[derive(Trace)]
 pub struct LayeredHashMap(Cc<LayeredHashMapInternals>);
 
 impl LayeredHashMap {
-	pub fn extend(self, new_layer: GcHashMap<IStr, LazyVal>) -> Self {
+	pub fn extend(self, new_layer: GcHashMap<IStr, Thunk<Val>>) -> Self {
 		Self(Cc::new(LayeredHashMapInternals {
 			parent: Some(self),
 			current: new_layer,
 		}))
 	}
 
-	pub fn get(&self, key: &IStr) -> Option<&LazyVal> {
+	pub fn get(&self, key: &IStr) -> Option<&Thunk<Val>> {
 		(self.0)
 			.current
 			.get(key)
modifiedcrates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -16,7 +16,7 @@
 	function::CallLocation,
 	gc::{GcHashMap, GcHashSet, TraceBox},
 	operator::evaluate_add_op,
-	throw, weak_ptr_eq, weak_raw, Bindable, LazyBinding, LazyVal, Result, State, Val,
+	throw, weak_ptr_eq, weak_raw, Bindable, LazyBinding, Result, State, Thunk, Val,
 };
 
 #[cfg(not(feature = "exp-preserve-order"))]
@@ -581,7 +581,7 @@
 pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder);
 impl<'v> ObjMemberBuilder<ValueBuilder<'v>> {
 	pub fn value(self, s: State, value: Val) -> Result<()> {
-		self.binding(s, LazyBinding::Bound(LazyVal::new_resolved(value)))
+		self.binding(s, LazyBinding::Bound(Thunk::evaluated(value)))
 	}
 	pub fn bindable(self, s: State, bindable: TraceBox<dyn Bindable>) -> Result<()> {
 		self.binding(s, LazyBinding::Bindable(Cc::new(bindable)))
@@ -604,7 +604,7 @@
 pub struct ExtendBuilder<'v>(&'v mut ObjValue);
 impl<'v> ObjMemberBuilder<ExtendBuilder<'v>> {
 	pub fn value(self, value: Val) {
-		self.binding(LazyBinding::Bound(LazyVal::new_resolved(value)));
+		self.binding(LazyBinding::Bound(Thunk::evaluated(value)));
 	}
 	pub fn bindable(self, bindable: TraceBox<dyn Bindable>) {
 		self.binding(LazyBinding::Bindable(Cc::new(bindable)));
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -15,41 +15,45 @@
 	throw, ObjValue, Result, State,
 };
 
-pub trait LazyValValue: Trace {
-	fn get(self: Box<Self>, s: State) -> Result<Val>;
+pub trait ThunkValue: Trace {
+	type Output;
+	fn get(self: Box<Self>, s: State) -> Result<Self::Output>;
 }
 
 #[derive(Trace)]
-enum LazyValInternals {
-	Computed(Val),
+enum ThunkInner<T> {
+	Computed(T),
 	Errored(LocError),
-	Waiting(TraceBox<dyn LazyValValue>),
+	Waiting(TraceBox<dyn ThunkValue<Output = T>>),
 	Pending,
 }
 
 #[allow(clippy::module_name_repetitions)]
 #[derive(Clone, Trace)]
-pub struct LazyVal(Cc<RefCell<LazyValInternals>>);
-impl LazyVal {
-	pub fn new(f: TraceBox<dyn LazyValValue>) -> Self {
-		Self(Cc::new(RefCell::new(LazyValInternals::Waiting(f))))
+pub struct Thunk<T>(Cc<RefCell<ThunkInner<T>>>);
+impl<T> Thunk<T>
+where
+	T: Clone + Trace,
+{
+	pub fn new(f: TraceBox<dyn ThunkValue<Output = T>>) -> Self {
+		Self(Cc::new(RefCell::new(ThunkInner::Waiting(f))))
 	}
-	pub fn new_resolved(val: Val) -> Self {
-		Self(Cc::new(RefCell::new(LazyValInternals::Computed(val))))
+	pub fn evaluated(val: T) -> Self {
+		Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))
 	}
 	pub fn force(&self, s: State) -> Result<()> {
 		self.evaluate(s)?;
 		Ok(())
 	}
-	pub fn evaluate(&self, s: State) -> Result<Val> {
+	pub fn evaluate(&self, s: State) -> Result<T> {
 		match &*self.0.borrow() {
-			LazyValInternals::Computed(v) => return Ok(v.clone()),
-			LazyValInternals::Errored(e) => return Err(e.clone()),
-			LazyValInternals::Pending => return Err(InfiniteRecursionDetected.into()),
-			LazyValInternals::Waiting(..) => (),
+			ThunkInner::Computed(v) => return Ok(v.clone()),
+			ThunkInner::Errored(e) => return Err(e.clone()),
+			ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),
+			ThunkInner::Waiting(..) => (),
 		};
-		let value = if let LazyValInternals::Waiting(value) =
-			std::mem::replace(&mut *self.0.borrow_mut(), LazyValInternals::Pending)
+		let value = if let ThunkInner::Waiting(value) =
+			std::mem::replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)
 		{
 			value
 		} else {
@@ -58,21 +62,21 @@
 		let new_value = match value.0.get(s) {
 			Ok(v) => v,
 			Err(e) => {
-				*self.0.borrow_mut() = LazyValInternals::Errored(e.clone());
+				*self.0.borrow_mut() = ThunkInner::Errored(e.clone());
 				return Err(e);
 			}
 		};
-		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());
+		*self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());
 		Ok(new_value)
 	}
 }
 
-impl Debug for LazyVal {
+impl<T: Debug> Debug for Thunk<T> {
 	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 		write!(f, "Lazy")
 	}
 }
-impl PartialEq for LazyVal {
+impl<T> PartialEq for Thunk<T> {
 	fn eq(&self, other: &Self) -> bool {
 		cc_ptr_eq(&self.0, &other.0)
 	}
@@ -142,7 +146,7 @@
 #[force_tracking]
 pub enum ArrValue {
 	Bytes(#[skip_trace] Rc<[u8]>),
-	Lazy(Cc<Vec<LazyVal>>),
+	Lazy(Cc<Vec<Thunk<Val>>>),
 	Eager(Cc<Vec<Val>>),
 	Extended(Box<(Self, Self)>),
 	Range(i32, i32),
@@ -240,13 +244,13 @@
 		}
 	}
 
-	pub fn get_lazy(&self, index: usize) -> Option<LazyVal> {
+	pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
 		match self {
 			Self::Bytes(i) => i
 				.get(index)
-				.map(|b| LazyVal::new_resolved(Val::Num(f64::from(*b)))),
+				.map(|b| Thunk::evaluated(Val::Num(f64::from(*b)))),
 			Self::Lazy(vec) => vec.get(index).cloned(),
-			Self::Eager(vec) => vec.get(index).cloned().map(LazyVal::new_resolved),
+			Self::Eager(vec) => vec.get(index).cloned().map(Thunk::evaluated),
 			Self::Extended(v) => {
 				let a_len = v.0.len();
 				if a_len > index {
@@ -259,7 +263,7 @@
 				if index >= self.len() {
 					return None;
 				}
-				Some(LazyVal::new_resolved(Val::Num(
+				Some(Thunk::evaluated(Val::Num(
 					((*a as isize) + index as isize) as f64,
 				)))
 			}
@@ -343,11 +347,11 @@
 		})
 	}
 
-	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = LazyVal> + '_ {
+	pub fn iter_lazy(&self) -> impl DoubleEndedIterator<Item = Thunk<Val>> + '_ {
 		(0..self.len()).map(move |idx| match self {
-			Self::Bytes(b) => LazyVal::new_resolved(Val::Num(f64::from(b[idx]))),
+			Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))),
 			Self::Lazy(l) => l[idx].clone(),
-			Self::Eager(e) => LazyVal::new_resolved(e[idx].clone()),
+			Self::Eager(e) => Thunk::evaluated(e[idx].clone()),
 			Self::Slice(..) | Self::Extended(..) | Self::Range(..) | Self::Reversed(..) => {
 				self.get_lazy(idx).expect("idx < len")
 			}
@@ -391,8 +395,8 @@
 	}
 }
 
-impl From<Vec<LazyVal>> for ArrValue {
-	fn from(v: Vec<LazyVal>) -> Self {
+impl From<Vec<Thunk<Val>>> for ArrValue {
+	fn from(v: Vec<Thunk<Val>>) -> Self {
 		Self::Lazy(Cc::new(v))
 	}
 }
modifiedcrates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/builtin.rs
+++ b/crates/jrsonnet-evaluator/tests/builtin.rs
@@ -7,6 +7,7 @@
 	error::Result,
 	function::{builtin, builtin::Builtin, CallLocation, FuncVal},
 	gc::TraceBox,
+	tb,
 	typed::Typed,
 	State, Val,
 };
@@ -70,9 +71,7 @@
 
 #[builtin]
 fn curry_add(a: u32) -> Result<FuncVal> {
-	Ok(FuncVal::Builtin(Cc::new(TraceBox(Box::new(curried_add {
-		a,
-	})))))
+	Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a }))))
 }
 
 #[test]
modifiedcrates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/tests/common.rs
+++ b/crates/jrsonnet-evaluator/tests/common.rs
@@ -1,7 +1,7 @@
 use jrsonnet_evaluator::{
 	error::Result,
 	function::{builtin, FuncVal},
-	throw_runtime, LazyVal, ObjValueBuilder, State, Val,
+	throw_runtime, ObjValueBuilder, State, Thunk, Val,
 };
 
 #[macro_export]
@@ -38,7 +38,7 @@
 }
 
 #[builtin]
-fn assert_throw(s: State, lazy: LazyVal, message: String) -> Result<bool> {
+fn assert_throw(s: State, lazy: Thunk<Val>, message: String) -> Result<bool> {
 	match lazy.evaluate(s) {
 		Ok(_) => {
 			throw_runtime!("expected argument to throw on evaluation, but it returned instead")
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -150,7 +150,7 @@
 			return Ok(Self::State);
 		} else if type_is_path(ty, "CallLocation").is_some() {
 			return Ok(Self::Location);
-		} else if type_is_path(ty, "LazyVal").is_some() {
+		} else if type_is_path(ty, "Thunk").is_some() {
 			return Ok(Self::Lazy {
 				is_option: false,
 				name: ident.to_string(),
@@ -163,7 +163,7 @@
 		}
 
 		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {
-			if type_is_path(ty, "LazyVal").is_some() {
+			if type_is_path(ty, "Thunk").is_some() {
 				return Ok(Self::Lazy {
 					is_option: true,
 					name: ident.to_string(),
modifiedcrates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-parser/Cargo.toml
+++ b/crates/jrsonnet-parser/Cargo.toml
@@ -6,6 +6,9 @@
 license = "MIT"
 edition = "2021"
 
+[features]
+exp-destruct = []
+
 [dependencies]
 jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
 
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -179,10 +179,44 @@
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, Clone, PartialEq, Trace)]
-pub struct BindSpec {
-	pub name: IStr,
-	pub params: Option<ParamsDesc>,
-	pub value: LocExpr,
+pub enum DestructRest {
+	/// ...rest
+	Keep(IStr),
+	/// ...
+	Drop,
+}
+
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[derive(Debug, Clone, PartialEq, Trace)]
+pub enum Destruct {
+	Full(IStr),
+	#[cfg(feature = "exp-destruct")]
+	Skip,
+	#[cfg(feature = "exp-destruct")]
+	Array {
+		start: Vec<Destruct>,
+		rest: Option<DestructRest>,
+		end: Vec<Destruct>,
+	},
+	#[cfg(feature = "exp-destruct")]
+	Object {
+		fields: Vec<(IStr, Option<Destruct>)>,
+		rest: Option<DestructRest>,
+	},
+}
+
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[derive(Debug, Clone, PartialEq, Trace)]
+pub enum BindSpec {
+	Field {
+		into: Destruct,
+		value: LocExpr,
+	},
+	Function {
+		name: IStr,
+		params: ParamsDesc,
+		value: LocExpr,
+	},
 }
 
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
modifiedcrates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -55,18 +55,18 @@
 
 		/// Reserved word followed by any non-alphanumberic
 		rule reserved() = ("assert" / "else" / "error" / "false" / "for" / "function" / "if" / "import" / "importstr" / "importbin" / "in" / "local" / "null" / "tailstrict" / "then" / "self" / "super" / "true") end_of_ident()
-		rule id() = quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")
+		rule id() -> IStr = v:$(quiet!{ !reserved() alpha() (alpha() / digit())*} / expected!("<identifier>")) { v.into() }
 
 		rule keyword(id: &'static str) -> ()
 			= ##parse_string_literal(id) end_of_ident()
 
-		pub rule param(s: &ParserSettings) -> expr::Param = name:$(id()) expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name.into(), expr) }
+		pub rule param(s: &ParserSettings) -> expr::Param = name:id() expr:(_ "=" _ expr:expr(s){expr})? { expr::Param(name, expr) }
 		pub rule params(s: &ParserSettings) -> expr::ParamsDesc
 			= params:param(s) ** comma() comma()? { expr::ParamsDesc(Rc::new(params)) }
 			/ { expr::ParamsDesc(Rc::new(Vec::new())) }
 
 		pub rule arg(s: &ParserSettings) -> (Option<IStr>, LocExpr)
-			= quiet! { name:(s:$(id()) _ "=" _ {s})? expr:expr(s) {(name.map(Into::into), expr)} }
+			= quiet! { name:(s:id() _ "=" _ {s})? expr:expr(s) {(name, expr)} }
 			/ expected!("<argument>")
 
 		pub rule args(s: &ParserSettings) -> expr::ArgsDesc
@@ -89,9 +89,52 @@
 				Ok(expr::ArgsDesc::new(unnamed, named))
 			}
 
+		pub rule destruct_rest() -> expr::DestructRest
+			= "..." into:(_ into:id() {into})? {if let Some(into) = into {
+				expr::DestructRest::Keep(into)
+			} else {expr::DestructRest::Drop}}
+		pub rule destruct_array(s: &ParserSettings) -> expr::Destruct
+			= "[" _ start:destruct(s)**comma() rest:(
+				comma() _ rest:destruct_rest()? end:(
+					comma() end:destruct(s)**comma() (_ comma())? {end}
+					/ comma()? {Vec::new()}
+				) {(rest, end)}
+				/ comma()? {(None, Vec::new())}
+			) _ "]" {?
+				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Array {
+					start,
+					rest: rest.0,
+					end: rest.1,
+				});
+				#[cfg(not(feature = "exp-destruct"))] Err("experimental destructuring was not enabled")
+			}
+		pub rule destruct_object(s: &ParserSettings) -> expr::Destruct
+			= "{" _
+				fields:(name:id() _ into:(":" _ into:destruct(s) {into})? {(name, into)})**comma()
+				rest:(
+					comma() rest:destruct_rest()? {rest}
+					/ comma()? {None}
+				)
+			_ "}" {?
+				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Object {
+					fields,
+					rest,
+				});
+				#[cfg(not(feature = "exp-destruct"))] Err("experimental destructuring was not enabled")
+			}
+		pub rule destruct(s: &ParserSettings) -> expr::Destruct
+			= v:id() {expr::Destruct::Full(v)}
+			/ "?" {?
+				#[cfg(feature = "exp-destruct")] return Ok(expr::Destruct::Skip);
+				#[cfg(not(feature = "exp-destruct"))] Err("experimental destructuring was not enabled")
+			}
+			/ arr:destruct_array(s) {arr}
+			/ obj:destruct_object(s) {obj}
+
 		pub rule bind(s: &ParserSettings) -> expr::BindSpec
-			= name:$(id()) _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: None, value: expr}}
-			/ name:$(id()) _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec{name:name.into(), params: Some(params), value: expr}}
+			= into:destruct(s) _ "=" _ expr:expr(s) {expr::BindSpec::Field{into, value: expr}}
+			/ name:id() _ "(" _ params:params(s) _ ")" _ "=" _ expr:expr(s) {expr::BindSpec::Function{name, params, value: expr}}
+
 		pub rule assertion(s: &ParserSettings) -> expr::AssertStmt
 			= keyword("assert") _ cond:expr(s) msg:(_ ":" _ e:expr(s) {e})? { expr::AssertStmt(cond, msg) }
 
@@ -122,7 +165,7 @@
 			/ string_block() } / expected!("<string>")
 
 		pub rule field_name(s: &ParserSettings) -> expr::FieldName
-			= name:$(id()) {expr::FieldName::Fixed(name.into())}
+			= name:id() {expr::FieldName::Fixed(name.into())}
 			/ name:string() {expr::FieldName::Fixed(name.into())}
 			/ "[" _ expr:expr(s) _ "]" {expr::FieldName::Dyn(expr)}
 		pub rule visibility() -> expr::Visibility
@@ -167,7 +210,7 @@
 		pub rule ifspec(s: &ParserSettings) -> IfSpecData
 			= keyword("if") _ expr:expr(s) {IfSpecData(expr)}
 		pub rule forspec(s: &ParserSettings) -> ForSpecData
-			= keyword("for") _ id:$(id()) _ keyword("in") _ cond:expr(s) {ForSpecData(id.into(), cond)}
+			= keyword("for") _ id:id() _ keyword("in") _ cond:expr(s) {ForSpecData(id, cond)}
 		pub rule compspec(s: &ParserSettings) -> Vec<expr::CompSpec>
 			= s:(i:ifspec(s) { expr::CompSpec::IfSpec(i) } / f:forspec(s) {expr::CompSpec::ForSpec(f)} ) ** _ {s}
 		pub rule local_expr(s: &ParserSettings) -> Expr
@@ -187,9 +230,9 @@
 		pub rule number_expr(s: &ParserSettings) -> Expr
 			= n:number() { expr::Expr::Num(n) }
 		pub rule var_expr(s: &ParserSettings) -> Expr
-			= n:$(id()) { expr::Expr::Var(n.into()) }
+			= n:id() { expr::Expr::Var(n) }
 		pub rule id_loc(s: &ParserSettings) -> LocExpr
-			= a:position!() n:$(id()) b:position!() { LocExpr(Rc::new(expr::Expr::Str(n.into())), ExprLocation(s.file_name.clone(), a,b)) }
+			= a:position!() n:id() b:position!() { LocExpr(Rc::new(expr::Expr::Str(n)), ExprLocation(s.file_name.clone(), a,b)) }
 		pub rule if_then_else_expr(s: &ParserSettings) -> Expr
 			= cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{
 				cond,
@@ -212,7 +255,7 @@
 
 			/ quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}
 			/ quiet!{"$intrinsicId" {Expr::IntrinsicId}}
-			/ quiet!{"$intrinsic(" name:$(id()) ")" {Expr::Intrinsic(name.into())}}
+			/ quiet!{"$intrinsic(" name:id() ")" {Expr::Intrinsic(name)}}
 
 			/ string_expr(s) / number_expr(s)
 			/ array_expr(s)
@@ -693,9 +736,8 @@
 						ObjExtend(
 							el!(Obj(ObjBody::MemberList(vec![])), 0, 2),
 							ObjBody::MemberList(vec![
-								Member::BindStmt(BindSpec {
-									name: "x".into(),
-									params: None,
+								Member::BindStmt(BindSpec::Field {
+									into: Destruct::Full("x".into()),
 									value: el!(Num(1.0), 15, 16)
 								}),
 								Member::Field(FieldMember {