git.delta.rocks / jrsonnet / refs/commits / 112adb2810f2

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/destructure.rs6.4 KiBsourcehistory
1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6	analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},7	bail,8	evaluate::evaluate,9	Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,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	value: Thunk<Val>,19	fctx: Pending<Context>,20	builder: &mut ContextBuilder,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() as usize != min_len {31				bail!("expected {} elements, got {}", min_len, arr.len())32			}33		} else if (arr.len() as usize) < min_len {34			bail!(35				"expected at least {} elements, but array was only {}",36				min_len,37				arr.len()38			)39		}40		Ok(arr)41	});4243	for (i, d) in start.iter().enumerate() {44		let full = full.clone();45		destruct(46			d,47			Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),48			fctx.clone(),49			builder,50		);51	}5253	let start_len = start.len() as u32;54	let end_len = end.len() as u32;5556	if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {57		let full = full.clone();58		builder.bind(59			id,60			Thunk!(move || {61				let full = full.evaluate()?;62				let to = full.len() - end_len;63				Ok(Val::Arr(full.slice(64					Some(start_len as i32),65					Some(to as i32),66					None,67				)))68			}),69		);70	}7172	for (i, d) in end.iter().enumerate() {73		let full = full.clone();74		destruct(75			d,76			Thunk!(move || {77				let full = full.evaluate()?;78				Ok(full79					.get(full.len() - end_len + i as u32)?80					.expect("length is checked"))81			}),82			fctx.clone(),83			builder,84		);85	}86}8788#[allow(dead_code, reason = "not dead in exp-destruct")]89fn destruct_object(90	fields: &[LDestructField],91	rest: Option<LDestructRest>,9293	value: Thunk<Val>,94	fctx: Pending<Context>,95	builder: &mut ContextBuilder,96) {97	use jrsonnet_interner::IStr;98	use rustc_hash::FxHashSet;99100	use crate::{bail, ObjValueBuilder};101102	let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();103	let field_names: Vec<(IStr, bool)> = fields104		.iter()105		.map(|f| (f.name.clone(), f.default.is_some()))106		.collect();107	let has_rest = rest.is_some();108	let full = Thunk!(move || {109		let v = value.evaluate()?;110		let Val::Obj(obj) = v else {111			bail!("expected object");112		};113		for (field, has_default) in &field_names {114			if !has_default && !obj.has_field_ex(field.clone(), true) {115				bail!("missing field: {field}");116			}117		}118		if !has_rest {119			let len = obj.len();120			if len as usize > field_names.len() {121				bail!("too many fields, and rest not found");122			}123		}124		Ok(obj)125	});126127	if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {128		let full = full.clone();129		builder.bind(130			id,131			Thunk!(move || {132				let full = full.evaluate()?;133				let mut out = ObjValueBuilder::new();134				out.extend_with_core(full.as_standalone());135				out.with_fields_omitted(captured_fields);136				Ok(Val::Obj(out.build()))137			}),138		);139	}140141	for field in fields {142		let field_name = field.name.clone();143		let default: Option<(Pending<Context>, Rc<LExpr>)> =144			field.default.as_ref().map(|e| (fctx.clone(), e.clone()));145		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				|| {150					let (fctx, expr) = default.as_ref().expect("shape is checked");151					evaluate(fctx.unwrap(), expr)152				},153				Ok,154			)155		});156157		if let Some(into) = &field.into {158			destruct(into, value_thunk, fctx.clone(), builder);159		} else {160			unreachable!("analyzer lowers object-destruct shorthands into `into`");161		}162	}163}164165/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one166/// binding per [`LocalId`] the pattern introduces.167///168/// `fctx` is needed for object-destruct defaults (feature `exp-destruct`).169#[allow(unused_variables)]170pub fn destruct(171	d: &LDestruct,172	value: Thunk<Val>,173	fctx: Pending<Context>,174	builder: &mut ContextBuilder,175) {176	match d {177		LDestruct::Full(id) => builder.bind(*id, value),178		#[cfg(feature = "exp-destruct")]179		LDestruct::Skip => {}180		#[cfg(feature = "exp-destruct")]181		LDestruct::Array { start, rest, end } => destruct_array(start, rest, end, value, fctx, builder),182		#[cfg(feature = "exp-destruct")]183		LDestruct::Object { fields, rest } => destruct_object(fields, rest, value, fctx, builder),184	}185}186187/// Bind one [`LBind`] as a lazy thunk that evaluates in the given188/// future context. Mirrors the old `evaluate_dest` — one entry per189/// binding in a `local … ;` frame.190pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {191	let value = bind.value.clone();192	let fctx_clone = fctx.clone();193	let thunk = Thunk!(move || {194		let ctx = fctx_clone.unwrap();195		evaluate(ctx, &value)196	});197	destruct(&bind.destruct, thunk, fctx, builder);198}199200/// Bind each LBind's value as a lazy thunk. Mutually recursive locals201/// resolve lazily through the shared Pending<Context>.202pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {203	if binds.is_empty() {204		return parent;205	}206	let fctx = Context::new_future();207	let mut builder =208		ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());209	for bind in binds {210		evaluate_dest(bind, fctx.clone(), &mut builder);211	}212	builder.build().into_future(fctx)213}214215pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}216impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}217218pub fn evaluate_locals_unbound(219	fctx: Context,220	locals: Rc<Vec<LBind>>,221	this_id: Option<LocalId>,222) -> impl CloneableUnbound<Context> {223	#[derive(Trace, Clone)]224	struct UnboundLocals {225		fctx: Context,226		locals: Rc<Vec<LBind>>,227		this_id: Option<LocalId>,228	}229	impl Unbound for UnboundLocals {230		type Bound = Context;231232		fn bind(&self, sup_this: SupThis) -> Result<Context> {233			let parent = self.fctx.clone();234235			let fctx = Context::new_future();236			let mut builder = ContextBuilder::extend(237				parent,238				self.locals.iter().map(|b| b.destruct.ids().len()).sum(),239			);240			for b in self.locals.iter() {241				evaluate_dest(b, fctx.clone(), &mut builder);242			}243			if let Some(this_id) = self.this_id {244				builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));245			}246			let ctx = builder.build_sup_this(sup_this).into_future(fctx);247			Ok(ctx)248		}249	}250251	UnboundLocals {252		fctx,253		locals,254		this_id,255	}256}