git.delta.rocks / jrsonnet / refs/commits / 7bd5bbd0756a

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/destructure.rs5.3 KiBsourcehistory
1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6	Context, LocalsFrame, PackedContext, Result, SupThis, Thunk, Unbound, Val,7	analyze::{ClosureShape, LBind, LDestruct, LDestructField, LDestructRest, LocalSlot},8	bail,9	evaluate::evaluate,10};1112#[allow(dead_code, reason = "not dead in exp-destruct")]13fn destruct_array(14	start: &[LDestruct],15	rest: Option<&LDestructRest>,16	end: &[LDestruct],1718	fill: &LocalsFrame,19	value: Thunk<Val>,20	ctx: &Context,21) {22	let min_len = start.len() + end.len();23	let has_rest = rest.is_some();24	let full = Thunk!(move || {25		let v = value.evaluate()?;26		let Val::Arr(arr) = v else {27			bail!("expected array");28		};29		if !has_rest {30			if arr.len() != min_len {31				bail!("expected {} elements, got {}", min_len, arr.len32())32			}33		} else if arr.len() < min_len {34			bail!(35				"expected at least {} elements, but array was only {}",36				min_len,37				arr.len32()38			)39		}40		Ok(arr)41	});4243	for (i, d) in start.iter().enumerate() {44		let full = full.clone();45		destruct(46			d,47			fill,48			Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),49			ctx,50		);51	}5253	let start_len = start.len();54	let end_len = end.len();5556	if let Some(LDestructRest::Keep(slot)) = rest {57		let full = full.clone();58		fill.set(59			*slot,60			Thunk!(move || {61				let full = full.evaluate()?;62				let to = full.len() - end_len;63				Ok(Val::Arr(full.slice(start_len..to)))64			}),65		);66	}6768	for (i, d) in end.iter().enumerate() {69		let full = full.clone();70		destruct(71			d,72			fill,73			Thunk!(move || {74				let full = full.evaluate()?;75				Ok(full76					.get(full.len() - end_len + i)?77					.expect("length is checked"))78			}),79			ctx,80		);81	}82}8384#[allow(dead_code, reason = "not dead in exp-destruct")]85fn destruct_object(86	fields: &[LDestructField],87	rest: Option<&LDestructRest>,8889	fill: &LocalsFrame,90	value: Thunk<Val>,91	ctx: &Context,92) {93	use jrsonnet_interner::IStr;94	use rustc_hash::FxHashSet;9596	use crate::{ObjValueBuilder, bail};9798	let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();99	let field_names: Vec<(IStr, bool)> = fields100		.iter()101		.map(|f| (f.name.clone(), f.default.is_some()))102		.collect();103	let has_rest = rest.is_some();104	let full = Thunk!(move || {105		let v = value.evaluate()?;106		let Val::Obj(obj) = v else {107			bail!("expected object");108		};109		for (field, has_default) in &field_names {110			if !has_default && !obj.has_field_ex(field.clone(), true) {111				bail!("missing field: {field}");112			}113		}114		if !has_rest {115			let len = obj.len32();116			if len as usize > field_names.len() {117				bail!("too many fields, and rest not found");118			}119		}120		Ok(obj)121	});122123	if let Some(LDestructRest::Keep(slot)) = rest {124		let full = full.clone();125		fill.set(126			*slot,127			Thunk!(move || {128				let full = full.evaluate()?;129				let mut out = ObjValueBuilder::new();130				out.extend_with_core(full.as_standalone());131				out.with_fields_omitted(captured_fields);132				Ok(Val::Obj(out.build()))133			}),134		);135	}136137	for field in fields {138		let field_name = field.name.clone();139		let default_thunk: Option<Thunk<Val>> = field.default.as_ref().map(|(shape, expr)| {140			let expr = expr.clone();141			let env = Context::enter_using(ctx, shape);142			Thunk!(move || evaluate(env, &expr))143		});144145		let field_full = full.clone();146		let value_thunk = Thunk!(move || {147			let obj = field_full.evaluate()?;148			obj.get(field_name)?.map_or_else(149				|| default_thunk.as_ref().expect("shape is checked").evaluate(),150				Ok,151			)152		});153154		if let Some(into) = &field.into {155			destruct(into, fill, value_thunk, ctx);156		} else {157			unreachable!("analyzer lowers object-destruct shorthands into `into`");158		}159	}160}161162#[allow(unused_variables)]163pub fn destruct(d: &LDestruct, fill: &LocalsFrame, value: Thunk<Val>, a_ctx: &Context) {164	match d {165		LDestruct::Full(slot) => fill.set(*slot, value),166		#[cfg(feature = "exp-destruct")]167		LDestruct::Skip => {}168		#[cfg(feature = "exp-destruct")]169		LDestruct::Array { start, rest, end } => {170			destruct_array(start, rest.as_ref(), end, fill, value, a_ctx)171		}172		#[cfg(feature = "exp-destruct")]173		LDestruct::Object { fields, rest } => destruct_object(fields, rest.as_ref(), fill, value, a_ctx),174	}175}176177pub fn fill_letrec_binds(fill: &LocalsFrame, ctx: &Context, binds: &[LBind]) {178	for bind in binds {179		let expr = bind.value.clone();180		let env = Context::enter_using(ctx, &bind.value_shape);181		destruct(182			&bind.destruct,183			fill,184			Thunk!(move || evaluate(env, &expr)),185			ctx,186		);187	}188}189190pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}191impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}192193pub fn evaluate_locals_unbound(194	outer: &Context,195	frame_shape: &ClosureShape,196	this_slot: Option<LocalSlot>,197	locals: Rc<Vec<LBind>>,198) -> impl CloneableUnbound<Context> {199	#[derive(Trace, Clone)]200	struct UnboundLocals {201		captures: PackedContext,202		this_slot: Option<LocalSlot>,203		locals: Rc<Vec<LBind>>,204	}205	impl Unbound for UnboundLocals {206		type Bound = Context;207208		fn bind(&self, sup_this: SupThis) -> Result<Context> {209			Ok(self.captures.clone().enter(sup_this, |fill, ctx| {210				if let Some(slot) = self.this_slot {211					let this_obj = ctx.sup_this().expect("sup_this set above").this().clone();212					fill.set(slot, Thunk::evaluated(Val::Obj(this_obj)));213				}214				fill_letrec_binds(fill, ctx, &self.locals);215			}))216		}217	}218219	UnboundLocals {220		captures: outer.pack_captures(frame_shape),221		this_slot,222		locals,223	}224}