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

difftreelog

feat destructure in comprehensions

Yaroslav Bolyukin2022-11-09parent: #c3b4574.patch.diff
in: master

3 files changed

modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/evaluate/mod.rs
1use std::{cmp::Ordering, rc::Rc};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6	ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, FieldName, ForSpecData,7	IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,8};9use jrsonnet_types::ValType;1011use crate::{12	destructure::evaluate_dest,13	error::Error::*,14	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},15	function::{CallLocation, FuncDesc, FuncVal},16	tb, throw,17	typed::Typed,18	val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue},19	Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,20	Unbound, Val,21};22pub mod destructure;23pub mod operator;2425pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {26	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {27		name,28		ctx,29		params,30		body,31	})))32}3334pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {35	Ok(match field_name {36		FieldName::Fixed(n) => Some(n.clone()),37		FieldName::Dyn(expr) => State::push(38			CallLocation::new(&expr.1),39			|| "evaluating field name".to_string(),40			|| {41				let value = evaluate(ctx, expr)?;42				if matches!(value, Val::Null) {43					Ok(None)44				} else {45					Ok(Some(IStr::from_untyped(value)?))46				}47			},48		)?,49	})50}5152pub fn evaluate_comp(53	ctx: Context,54	specs: &[CompSpec],55	callback: &mut impl FnMut(Context) -> Result<()>,56) -> Result<()> {57	match specs.get(0) {58		None => callback(ctx)?,59		Some(CompSpec::IfSpec(IfSpecData(cond))) => {60			if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {61				evaluate_comp(ctx, &specs[1..], callback)?;62			}63		}64		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {65			Val::Arr(list) => {66				for item in list.iter() {67					evaluate_comp(68						ctx.clone().with_var(var.clone(), item?.clone()),69						&specs[1..],70						callback,71					)?;72				}73			}74			_ => throw!(InComprehensionCanOnlyIterateOverArray),75		},76	}77	Ok(())78}7980trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}8182fn evaluate_object_locals(83	fctx: Pending<Context>,84	locals: Rc<Vec<BindSpec>>,85) -> impl CloneableUnbound<Context> {86	#[derive(Trace, Clone)]87	struct UnboundLocals {88		fctx: Pending<Context>,89		locals: Rc<Vec<BindSpec>>,90	}91	impl CloneableUnbound<Context> for UnboundLocals {}92	impl Unbound for UnboundLocals {93		type Bound = Context;9495		fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {96			let fctx = Context::new_future();97			let mut new_bindings = GcHashMap::new();98			for b in self.locals.iter() {99				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;100			}101102			let ctx = self.fctx.unwrap();103			let new_dollar = ctx.dollar().clone().or_else(|| this.clone());104105			let ctx = ctx106				.extend(new_bindings, new_dollar, sup, this)107				.into_future(fctx);108109			Ok(ctx)110		}111	}112113	UnboundLocals { fctx, locals }114}115116pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(117	builder: &mut ObjValueBuilder,118	ctx: Context,119	uctx: B,120	member: &FieldMember,121) -> Result<()> {122	match member {123		FieldMember {124			name,125			plus,126			params: None,127			visibility,128			value,129		} => {130			#[derive(Trace)]131			struct UnboundValue<B: Trace> {132				uctx: B,133				value: LocExpr,134				name: IStr,135			}136			impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {137				type Bound = Val;138				fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {139					Ok(evaluate_named(140						self.uctx.bind(sup, this)?,141						&self.value,142						self.name.clone(),143					)?)144				}145			}146147			let name = evaluate_field_name(ctx.clone(), name)?;148			let Some(name) = name else {149				return Ok(());150			};151152			builder153				.member(name.clone())154				.with_add(*plus)155				.with_visibility(*visibility)156				.with_location(value.1.clone())157				.bindable(tb!(UnboundValue {158					uctx: uctx.clone(),159					value: value.clone(),160					name: name.clone()161				}))?;162		}163		FieldMember {164			name,165			params: Some(params),166			value,167			..168		} => {169			#[derive(Trace)]170			struct UnboundMethod<B: Trace> {171				uctx: B,172				value: LocExpr,173				params: ParamsDesc,174				name: IStr,175			}176			impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {177				type Bound = Val;178				fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {179					Ok(evaluate_method(180						self.uctx.bind(sup, this)?,181						self.name.clone(),182						self.params.clone(),183						self.value.clone(),184					))185				}186			}187188			let Some(name) = evaluate_field_name(ctx.clone(), name)? else {189				return Ok(());190			};191192			builder193				.member(name.clone())194				.hide()195				.with_location(value.1.clone())196				.bindable(tb!(UnboundMethod {197					uctx: uctx.clone(),198					value: value.clone(),199					params: params.clone(),200					name: name.clone()201				}))?;202		}203	}204	Ok(())205}206207#[allow(clippy::too_many_lines)]208pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {209	let mut builder = ObjValueBuilder::new();210	let locals = Rc::new(211		members212			.iter()213			.filter_map(|m| match m {214				Member::BindStmt(bind) => Some(bind.clone()),215				_ => None,216			})217			.collect::<Vec<_>>(),218	);219220	let fctx = Context::new_future();221222	// We have single context for all fields, so we can cache binds223	let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));224225	for member in members.iter() {226		match member {227			Member::Field(field) => {228				evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?229			}230			Member::BindStmt(_) => {231				// Already handled232			}233			Member::AssertStmt(stmt) => {234				#[derive(Trace)]235				struct ObjectAssert<B: Trace> {236					uctx: B,237					assert: AssertStmt,238				}239				impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {240					fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {241						let ctx = self.uctx.bind(sup, this)?;242						evaluate_assert(ctx, &self.assert)243					}244				}245				builder.assert(tb!(ObjectAssert {246					uctx: uctx.clone(),247					assert: stmt.clone(),248				}));249			}250		}251	}252	let this = builder.build();253	fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));254	Ok(this)255}256257pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {258	Ok(match object {259		ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,260		ObjBody::ObjComp(obj) => {261			let mut builder = ObjValueBuilder::new();262			let locals = Rc::new(263				obj.pre_locals264					.iter()265					.chain(obj.post_locals.iter())266					.cloned()267					.collect::<Vec<_>>(),268			);269			let mut ctxs = vec![];270			evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {271				let fctx = Context::new_future();272				ctxs.push((ctx.clone(), fctx.clone()));273				let uctx = evaluate_object_locals(fctx, locals.clone());274275				evaluate_field_member(&mut builder, ctx, uctx, &obj.field)276			})?;277278			let this = builder.build();279			for (ctx, fctx) in ctxs {280				let _ctx = ctx281					.extend(GcHashMap::new(), None, None, Some(this.clone()))282					.into_future(fctx);283			}284			this285		}286	})287}288289pub fn evaluate_apply(290	ctx: Context,291	value: &LocExpr,292	args: &ArgsDesc,293	loc: CallLocation<'_>,294	tailstrict: bool,295) -> Result<Val> {296	let value = evaluate(ctx.clone(), value)?;297	Ok(match value {298		Val::Func(f) => {299			let body = || f.evaluate(ctx, loc, args, tailstrict);300			if tailstrict {301				body()?302			} else {303				State::push(loc, || format!("function <{}> call", f.name()), body)?304			}305		}306		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),307	})308}309310pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {311	let value = &assertion.0;312	let msg = &assertion.1;313	let assertion_result = State::push(314		CallLocation::new(&value.1),315		|| "assertion condition".to_owned(),316		|| bool::from_untyped(evaluate(ctx.clone(), value)?),317	)?;318	if !assertion_result {319		State::push(320			CallLocation::new(&value.1),321			|| "assertion failure".to_owned(),322			|| {323				if let Some(msg) = msg {324					throw!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));325				}326				throw!(AssertionFailed(Val::Null.to_string()?));327			},328		)?;329	}330	Ok(())331}332333pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {334	use Expr::*;335	let LocExpr(raw_expr, _loc) = expr;336	Ok(match &**raw_expr {337		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),338		_ => evaluate(ctx, expr)?,339	})340}341342#[allow(clippy::too_many_lines)]343pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {344	use Expr::*;345	let LocExpr(expr, loc) = expr;346	// let bp = with_state(|s| s.0.stop_at.borrow().clone());347	Ok(match &**expr {348		Literal(LiteralType::This) => {349			Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)350		}351		Literal(LiteralType::Super) => Val::Obj(352			ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(353				ctx.this()354					.clone()355					.expect("if super exists - then this should to"),356			),357		),358		Literal(LiteralType::Dollar) => {359			Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)360		}361		Literal(LiteralType::True) => Val::Bool(true),362		Literal(LiteralType::False) => Val::Bool(false),363		Literal(LiteralType::Null) => Val::Null,364		Parened(e) => evaluate(ctx, e)?,365		Str(v) => Val::Str(v.clone()),366		Num(v) => Val::new_checked_num(*v)?,367		BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,368		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,369		Var(name) => State::push(370			CallLocation::new(loc),371			|| format!("variable <{name}> access"),372			|| ctx.binding(name.clone())?.evaluate(),373		)?,374		Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {375			(Val::Obj(v), Val::Str(key)) => State::push(376				CallLocation::new(loc),377				|| format!("field <{key}> access"),378				|| match v.get(key.clone()) {379					Ok(Some(v)) => Ok(v),380					#[cfg(not(feature = "friendly-errors"))]381					Ok(None) => throw!(NoSuchField(key.clone(), vec![])),382					#[cfg(feature = "friendly-errors")]383					Ok(None) => {384						let mut heap = Vec::new();385						for field in v.fields_ex(386							true,387							#[cfg(feature = "exp-preserve-order")]388							false,389						) {390							let conf = strsim::jaro_winkler(&field as &str, &key as &str);391							if conf < 0.8 {392								continue;393							}394							heap.push((conf, field));395						}396						heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));397398						throw!(NoSuchField(399							key.clone(),400							heap.into_iter().map(|(_, v)| v).collect()401						))402					}403					Err(e) => Err(e),404				},405			)?,406			(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(407				ValType::Obj,408				ValType::Str,409				n.value_type(),410			)),411412			(Val::Arr(v), Val::Num(n)) => {413				if n.fract() > f64::EPSILON {414					throw!(FractionalIndex)415				}416				v.get(n as usize)?417					.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?418			}419			(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),420			(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(421				ValType::Arr,422				ValType::Num,423				n.value_type(),424			)),425426			(Val::Str(s), Val::Num(n)) => Val::Str({427				let v: IStr = s428					.chars()429					.skip(n as usize)430					.take(1)431					.collect::<String>()432					.into();433				if v.is_empty() {434					let size = s.chars().count();435					throw!(StringBoundsError(n as usize, size))436				}437				v438			}),439			(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(440				ValType::Str,441				ValType::Num,442				n.value_type(),443			)),444445			(v, _) => throw!(CantIndexInto(v.value_type())),446		},447		LocalExpr(bindings, returned) => {448			let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =449				GcHashMap::with_capacity(bindings.len());450			let fctx = Context::new_future();451			for b in bindings {452				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;453			}454			let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);455			evaluate(ctx, &returned.clone())?456		}457		Arr(items) => {458			let mut out = Vec::with_capacity(items.len());459			for item in items {460				// TODO: Implement ArrValue::Lazy with same context for every element?461				#[derive(Trace)]462				struct ArrayElement {463					ctx: Context,464					item: LocExpr,465				}466				impl ThunkValue for ArrayElement {467					type Output = Val;468					fn get(self: Box<Self>) -> Result<Val> {469						evaluate(self.ctx, &self.item)470					}471				}472				out.push(Thunk::new(tb!(ArrayElement {473					ctx: ctx.clone(),474					item: item.clone(),475				})));476			}477			Val::Arr(out.into())478		}479		ArrComp(expr, comp_specs) => {480			let mut out = Vec::new();481			evaluate_comp(ctx, comp_specs, &mut |ctx| {482				out.push(evaluate(ctx, expr)?);483				Ok(())484			})?;485			Val::Arr(ArrValue::Eager(Cc::new(out)))486		}487		Obj(body) => Val::Obj(evaluate_object(ctx, body)?),488		ObjExtend(a, b) => evaluate_add_op(489			&evaluate(ctx.clone(), a)?,490			&Val::Obj(evaluate_object(ctx, b)?),491		)?,492		Apply(value, args, tailstrict) => {493			evaluate_apply(ctx, value, args, CallLocation::new(loc), *tailstrict)?494		}495		Function(params, body) => {496			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())497		}498		AssertExpr(assert, returned) => {499			evaluate_assert(ctx.clone(), assert)?;500			evaluate(ctx, returned)?501		}502		ErrorStmt(e) => State::push(503			CallLocation::new(loc),504			|| "error statement".to_owned(),505			|| throw!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),506		)?,507		IfElse {508			cond,509			cond_then,510			cond_else,511		} => {512			if State::push(513				CallLocation::new(loc),514				|| "if condition".to_owned(),515				|| bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),516			)? {517				evaluate(ctx, cond_then)?518			} else {519				match cond_else {520					Some(v) => evaluate(ctx, v)?,521					None => Val::Null,522				}523			}524		}525		Slice(value, desc) => {526			fn parse_idx<T: Typed>(527				loc: CallLocation<'_>,528				ctx: &Context,529				expr: &Option<LocExpr>,530				desc: &'static str,531			) -> Result<Option<T>> {532				if let Some(value) = expr {533					Ok(Some(State::push(534						loc,535						|| format!("slice {desc}"),536						|| T::from_untyped(evaluate(ctx.clone(), value)?),537					)?))538				} else {539					Ok(None)540				}541			}542543			let indexable = evaluate(ctx.clone(), value)?;544			let loc = CallLocation::new(loc);545546			let start = parse_idx(loc, &ctx, &desc.start, "start")?;547			let end = parse_idx(loc, &ctx, &desc.end, "end")?;548			let step = parse_idx(loc, &ctx, &desc.step, "step")?;549550			IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?551		}552		i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {553			let tmp = loc.clone().0;554			let s = ctx.state();555			let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;556			match i {557				Import(_) => State::push(558					CallLocation::new(loc),559					|| format!("import {:?}", path.clone()),560					|| s.import_resolved(resolved_path),561				)?,562				ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),563				ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),564				_ => unreachable!(),565			}566		}567	})568}
after · crates/jrsonnet-evaluator/src/evaluate/mod.rs
1use std::{cmp::Ordering, rc::Rc};23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_parser::{6	ArgsDesc, AssertStmt, BindSpec, CompSpec, Expr, FieldMember, FieldName, ForSpecData,7	IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc,8};9use jrsonnet_types::ValType;1011use self::destructure::destruct;12use crate::{13	destructure::evaluate_dest,14	error::Error::*,15	evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},16	function::{CallLocation, FuncDesc, FuncVal},17	tb, throw,18	typed::Typed,19	val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue},20	Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,21	Unbound, Val,22};23pub mod destructure;24pub mod operator;2526pub fn evaluate_method(ctx: Context, name: IStr, params: ParamsDesc, body: LocExpr) -> Val {27	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {28		name,29		ctx,30		params,31		body,32	})))33}3435pub fn evaluate_field_name(ctx: Context, field_name: &FieldName) -> Result<Option<IStr>> {36	Ok(match field_name {37		FieldName::Fixed(n) => Some(n.clone()),38		FieldName::Dyn(expr) => State::push(39			CallLocation::new(&expr.1),40			|| "evaluating field name".to_string(),41			|| {42				let value = evaluate(ctx, expr)?;43				if matches!(value, Val::Null) {44					Ok(None)45				} else {46					Ok(Some(IStr::from_untyped(value)?))47				}48			},49		)?,50	})51}5253pub fn evaluate_comp(54	ctx: Context,55	specs: &[CompSpec],56	callback: &mut impl FnMut(Context) -> Result<()>,57) -> Result<()> {58	match specs.get(0) {59		None => callback(ctx)?,60		Some(CompSpec::IfSpec(IfSpecData(cond))) => {61			if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {62				evaluate_comp(ctx, &specs[1..], callback)?;63			}64		}65		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => match evaluate(ctx.clone(), expr)? {66			Val::Arr(list) => {67				for item in list.iter_lazy() {68					let fctx = Pending::new();69					let mut new_bindings = GcHashMap::new();70					destruct(var, item, fctx.clone(), &mut new_bindings)?;71					let ctx = ctx72						.clone()73						.extend(new_bindings, None, None, None)74						.into_future(fctx);7576					evaluate_comp(ctx, &specs[1..], callback)?;77				}78			}79			_ => throw!(InComprehensionCanOnlyIterateOverArray),80		},81	}82	Ok(())83}8485trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}86impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}8788fn evaluate_object_locals(89	fctx: Pending<Context>,90	locals: Rc<Vec<BindSpec>>,91) -> impl CloneableUnbound<Context> {92	#[derive(Trace, Clone)]93	struct UnboundLocals {94		fctx: Pending<Context>,95		locals: Rc<Vec<BindSpec>>,96	}97	impl Unbound for UnboundLocals {98		type Bound = Context;99100		fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Context> {101			let fctx = Context::new_future();102			let mut new_bindings = GcHashMap::new();103			for b in self.locals.iter() {104				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;105			}106107			let ctx = self.fctx.unwrap();108			let new_dollar = ctx.dollar().clone().or_else(|| this.clone());109110			let ctx = ctx111				.extend(new_bindings, new_dollar, sup, this)112				.into_future(fctx);113114			Ok(ctx)115		}116	}117118	UnboundLocals { fctx, locals }119}120121pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(122	builder: &mut ObjValueBuilder,123	ctx: Context,124	uctx: B,125	field: &FieldMember,126) -> Result<()> {127	let name = evaluate_field_name(ctx.clone(), &field.name)?;128	let Some(name) = name else {129		return Ok(());130	};131132	match field {133		FieldMember {134			plus,135			params: None,136			visibility,137			value,138			..139		} => {140			#[derive(Trace)]141			struct UnboundValue<B: Trace> {142				uctx: B,143				value: LocExpr,144				name: IStr,145			}146			impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {147				type Bound = Val;148				fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {149					Ok(evaluate_named(150						self.uctx.bind(sup, this)?,151						&self.value,152						self.name.clone(),153					)?)154				}155			}156157			builder158				.member(name.clone())159				.with_add(*plus)160				.with_visibility(*visibility)161				.with_location(value.1.clone())162				.bindable(tb!(UnboundValue {163					uctx: uctx.clone(),164					value: value.clone(),165					name: name.clone()166				}))?;167		}168		FieldMember {169			params: Some(params),170			value,171			..172		} => {173			#[derive(Trace)]174			struct UnboundMethod<B: Trace> {175				uctx: B,176				value: LocExpr,177				params: ParamsDesc,178				name: IStr,179			}180			impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {181				type Bound = Val;182				fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Val> {183					Ok(evaluate_method(184						self.uctx.bind(sup, this)?,185						self.name.clone(),186						self.params.clone(),187						self.value.clone(),188					))189				}190			}191192			builder193				.member(name.clone())194				.hide()195				.with_location(value.1.clone())196				.bindable(tb!(UnboundMethod {197					uctx: uctx.clone(),198					value: value.clone(),199					params: params.clone(),200					name: name.clone()201				}))?;202		}203	}204	Ok(())205}206207#[allow(clippy::too_many_lines)]208pub fn evaluate_member_list_object(ctx: Context, members: &[Member]) -> Result<ObjValue> {209	let mut builder = ObjValueBuilder::new();210	let locals = Rc::new(211		members212			.iter()213			.filter_map(|m| match m {214				Member::BindStmt(bind) => Some(bind.clone()),215				_ => None,216			})217			.collect::<Vec<_>>(),218	);219220	let fctx = Context::new_future();221222	// We have single context for all fields, so we can cache binds223	let uctx = CachedUnbound::new(evaluate_object_locals(fctx.clone(), locals));224225	for member in members.iter() {226		match member {227			Member::Field(field) => {228				evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), &field)?229			}230			Member::AssertStmt(stmt) => {231				#[derive(Trace)]232				struct ObjectAssert<B: Trace> {233					uctx: B,234					assert: AssertStmt,235				}236				impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {237					fn run(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<()> {238						let ctx = self.uctx.bind(sup, this)?;239						evaluate_assert(ctx, &self.assert)240					}241				}242				builder.assert(tb!(ObjectAssert {243					uctx: uctx.clone(),244					assert: stmt.clone(),245				}));246			}247			Member::BindStmt(_) => {248				// Already handled249			}250		}251	}252	let this = builder.build();253	fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone())));254	Ok(this)255}256257pub fn evaluate_object(ctx: Context, object: &ObjBody) -> Result<ObjValue> {258	Ok(match object {259		ObjBody::MemberList(members) => evaluate_member_list_object(ctx, members)?,260		ObjBody::ObjComp(obj) => {261			let mut builder = ObjValueBuilder::new();262			let locals = Rc::new(263				obj.pre_locals264					.iter()265					.chain(obj.post_locals.iter())266					.cloned()267					.collect::<Vec<_>>(),268			);269			let mut ctxs = vec![];270			evaluate_comp(ctx, &obj.compspecs, &mut |ctx| {271				let fctx = Context::new_future();272				ctxs.push((ctx.clone(), fctx.clone()));273				let uctx = evaluate_object_locals(fctx, locals.clone());274275				evaluate_field_member(&mut builder, ctx, uctx, &obj.field)276			})?;277278			let this = builder.build();279			for (ctx, fctx) in ctxs {280				let _ctx = ctx281					.extend(GcHashMap::new(), None, None, Some(this.clone()))282					.into_future(fctx);283			}284			this285		}286	})287}288289pub fn evaluate_apply(290	ctx: Context,291	value: &LocExpr,292	args: &ArgsDesc,293	loc: CallLocation<'_>,294	tailstrict: bool,295) -> Result<Val> {296	let value = evaluate(ctx.clone(), value)?;297	Ok(match value {298		Val::Func(f) => {299			let body = || f.evaluate(ctx, loc, args, tailstrict);300			if tailstrict {301				body()?302			} else {303				State::push(loc, || format!("function <{}> call", f.name()), body)?304			}305		}306		v => throw!(OnlyFunctionsCanBeCalledGot(v.value_type())),307	})308}309310pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {311	let value = &assertion.0;312	let msg = &assertion.1;313	let assertion_result = State::push(314		CallLocation::new(&value.1),315		|| "assertion condition".to_owned(),316		|| bool::from_untyped(evaluate(ctx.clone(), value)?),317	)?;318	if !assertion_result {319		State::push(320			CallLocation::new(&value.1),321			|| "assertion failure".to_owned(),322			|| {323				if let Some(msg) = msg {324					throw!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));325				}326				throw!(AssertionFailed(Val::Null.to_string()?));327			},328		)?;329	}330	Ok(())331}332333pub fn evaluate_named(ctx: Context, expr: &LocExpr, name: IStr) -> Result<Val> {334	use Expr::*;335	let LocExpr(raw_expr, _loc) = expr;336	Ok(match &**raw_expr {337		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),338		_ => evaluate(ctx, expr)?,339	})340}341342#[allow(clippy::too_many_lines)]343pub fn evaluate(ctx: Context, expr: &LocExpr) -> Result<Val> {344	use Expr::*;345	let LocExpr(expr, loc) = expr;346	// let bp = with_state(|s| s.0.stop_at.borrow().clone());347	Ok(match &**expr {348		Literal(LiteralType::This) => {349			Val::Obj(ctx.this().clone().ok_or(CantUseSelfOutsideOfObject)?)350		}351		Literal(LiteralType::Super) => Val::Obj(352			ctx.super_obj().clone().ok_or(NoSuperFound)?.with_this(353				ctx.this()354					.clone()355					.expect("if super exists - then this should to"),356			),357		),358		Literal(LiteralType::Dollar) => {359			Val::Obj(ctx.dollar().clone().ok_or(NoTopLevelObjectFound)?)360		}361		Literal(LiteralType::True) => Val::Bool(true),362		Literal(LiteralType::False) => Val::Bool(false),363		Literal(LiteralType::Null) => Val::Null,364		Parened(e) => evaluate(ctx, e)?,365		Str(v) => Val::Str(v.clone()),366		Num(v) => Val::new_checked_num(*v)?,367		BinaryOp(v1, o, v2) => evaluate_binary_op_special(ctx, v1, *o, v2)?,368		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,369		Var(name) => State::push(370			CallLocation::new(loc),371			|| format!("variable <{name}> access"),372			|| ctx.binding(name.clone())?.evaluate(),373		)?,374		Index(value, index) => match (evaluate(ctx.clone(), value)?, evaluate(ctx, index)?) {375			(Val::Obj(v), Val::Str(key)) => State::push(376				CallLocation::new(loc),377				|| format!("field <{key}> access"),378				|| match v.get(key.clone()) {379					Ok(Some(v)) => Ok(v),380					#[cfg(not(feature = "friendly-errors"))]381					Ok(None) => throw!(NoSuchField(key.clone(), vec![])),382					#[cfg(feature = "friendly-errors")]383					Ok(None) => {384						let mut heap = Vec::new();385						for field in v.fields_ex(386							true,387							#[cfg(feature = "exp-preserve-order")]388							false,389						) {390							let conf = strsim::jaro_winkler(&field as &str, &key as &str);391							if conf < 0.8 {392								continue;393							}394							heap.push((conf, field));395						}396						heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));397398						throw!(NoSuchField(399							key.clone(),400							heap.into_iter().map(|(_, v)| v).collect()401						))402					}403					Err(e) => Err(e),404				},405			)?,406			(Val::Obj(_), n) => throw!(ValueIndexMustBeTypeGot(407				ValType::Obj,408				ValType::Str,409				n.value_type(),410			)),411412			(Val::Arr(v), Val::Num(n)) => {413				if n.fract() > f64::EPSILON {414					throw!(FractionalIndex)415				}416				v.get(n as usize)?417					.ok_or_else(|| ArrayBoundsError(n as usize, v.len()))?418			}419			(Val::Arr(_), Val::Str(n)) => throw!(AttemptedIndexAnArrayWithString(n)),420			(Val::Arr(_), n) => throw!(ValueIndexMustBeTypeGot(421				ValType::Arr,422				ValType::Num,423				n.value_type(),424			)),425426			(Val::Str(s), Val::Num(n)) => Val::Str({427				let v: IStr = s428					.chars()429					.skip(n as usize)430					.take(1)431					.collect::<String>()432					.into();433				if v.is_empty() {434					let size = s.chars().count();435					throw!(StringBoundsError(n as usize, size))436				}437				v438			}),439			(Val::Str(_), n) => throw!(ValueIndexMustBeTypeGot(440				ValType::Str,441				ValType::Num,442				n.value_type(),443			)),444445			(v, _) => throw!(CantIndexInto(v.value_type())),446		},447		LocalExpr(bindings, returned) => {448			let mut new_bindings: GcHashMap<IStr, Thunk<Val>> =449				GcHashMap::with_capacity(bindings.len());450			let fctx = Context::new_future();451			for b in bindings {452				evaluate_dest(b, fctx.clone(), &mut new_bindings)?;453			}454			let ctx = ctx.extend(new_bindings, None, None, None).into_future(fctx);455			evaluate(ctx, &returned.clone())?456		}457		Arr(items) => {458			let mut out = Vec::with_capacity(items.len());459			for item in items {460				// TODO: Implement ArrValue::Lazy with same context for every element?461				#[derive(Trace)]462				struct ArrayElement {463					ctx: Context,464					item: LocExpr,465				}466				impl ThunkValue for ArrayElement {467					type Output = Val;468					fn get(self: Box<Self>) -> Result<Val> {469						evaluate(self.ctx, &self.item)470					}471				}472				out.push(Thunk::new(tb!(ArrayElement {473					ctx: ctx.clone(),474					item: item.clone(),475				})));476			}477			Val::Arr(out.into())478		}479		ArrComp(expr, comp_specs) => {480			let mut out = Vec::new();481			evaluate_comp(ctx, comp_specs, &mut |ctx| {482				out.push(evaluate(ctx, expr)?);483				Ok(())484			})?;485			Val::Arr(ArrValue::Eager(Cc::new(out)))486		}487		Obj(body) => Val::Obj(evaluate_object(ctx, body)?),488		ObjExtend(a, b) => evaluate_add_op(489			&evaluate(ctx.clone(), a)?,490			&Val::Obj(evaluate_object(ctx, b)?),491		)?,492		Apply(value, args, tailstrict) => {493			evaluate_apply(ctx, value, args, CallLocation::new(loc), *tailstrict)?494		}495		Function(params, body) => {496			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())497		}498		AssertExpr(assert, returned) => {499			evaluate_assert(ctx.clone(), assert)?;500			evaluate(ctx, returned)?501		}502		ErrorStmt(e) => State::push(503			CallLocation::new(loc),504			|| "error statement".to_owned(),505			|| throw!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),506		)?,507		IfElse {508			cond,509			cond_then,510			cond_else,511		} => {512			if State::push(513				CallLocation::new(loc),514				|| "if condition".to_owned(),515				|| bool::from_untyped(evaluate(ctx.clone(), &cond.0)?),516			)? {517				evaluate(ctx, cond_then)?518			} else {519				match cond_else {520					Some(v) => evaluate(ctx, v)?,521					None => Val::Null,522				}523			}524		}525		Slice(value, desc) => {526			fn parse_idx<T: Typed>(527				loc: CallLocation<'_>,528				ctx: &Context,529				expr: &Option<LocExpr>,530				desc: &'static str,531			) -> Result<Option<T>> {532				if let Some(value) = expr {533					Ok(Some(State::push(534						loc,535						|| format!("slice {desc}"),536						|| T::from_untyped(evaluate(ctx.clone(), value)?),537					)?))538				} else {539					Ok(None)540				}541			}542543			let indexable = evaluate(ctx.clone(), value)?;544			let loc = CallLocation::new(loc);545546			let start = parse_idx(loc, &ctx, &desc.start, "start")?;547			let end = parse_idx(loc, &ctx, &desc.end, "end")?;548			let step = parse_idx(loc, &ctx, &desc.step, "step")?;549550			IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?551		}552		i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {553			let tmp = loc.clone().0;554			let s = ctx.state();555			let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;556			match i {557				Import(_) => State::push(558					CallLocation::new(loc),559					|| format!("import {:?}", path.clone()),560					|| s.import_resolved(resolved_path),561				)?,562				ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),563				ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),564				_ => unreachable!(),565			}566		}567	})568}
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -253,7 +253,7 @@
 #[cfg_attr(feature = "structdump", derive(Codegen))]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 #[derive(Debug, PartialEq, Trace)]
-pub struct ForSpecData(pub IStr, pub LocExpr);
+pub struct ForSpecData(pub Destruct, pub LocExpr);
 
 #[cfg_attr(feature = "structdump", derive(Codegen))]
 #[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
@@ -208,7 +208,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, cond)}
+			= keyword("for") _ id:destruct(s) _ 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
@@ -620,7 +620,7 @@
 						16
 					),
 					vec![CompSpec::ForSpec(ForSpecData(
-						"x".into(),
+						Destruct::Full("x".into()),
 						el!(Var("arr".into()), 26, 29)
 					))]
 				),