git.delta.rocks / jrsonnet / refs/commits / 644f9f3b456d

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/destructure.rs6.5 KiBsourcehistory
1use std::rc::Rc;23use jrsonnet_gcmodule::Trace;45use crate::{6	Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,7	analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},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	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::{ObjValueBuilder, bail};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 } => {182			destruct_array(start, rest.as_ref(), end, value, fctx, builder)183		}184		#[cfg(feature = "exp-destruct")]185		LDestruct::Object { fields, rest } => {186			destruct_object(fields, rest.as_ref(), value, fctx, builder)187		}188	}189}190191/// Bind one [`LBind`] as a lazy thunk that evaluates in the given192/// future context. Mirrors the old `evaluate_dest` — one entry per193/// binding in a `local … ;` frame.194pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {195	let value = bind.value.clone();196	let fctx_clone = fctx.clone();197	let thunk = Thunk!(move || {198		let ctx = fctx_clone.unwrap();199		evaluate(ctx, &value)200	});201	destruct(&bind.destruct, thunk, fctx, builder);202}203204/// Bind each LBind's value as a lazy thunk. Mutually recursive locals205/// resolve lazily through the shared Pending<Context>.206pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {207	if binds.is_empty() {208		return parent;209	}210	let fctx = Context::new_future();211	let mut builder =212		ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());213	for bind in binds {214		evaluate_dest(bind, fctx.clone(), &mut builder);215	}216	builder.build().into_future(fctx)217}218219pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}220impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}221222pub fn evaluate_locals_unbound(223	fctx: Context,224	locals: Rc<Vec<LBind>>,225	this_id: Option<LocalId>,226) -> impl CloneableUnbound<Context> {227	#[derive(Trace, Clone)]228	struct UnboundLocals {229		fctx: Context,230		locals: Rc<Vec<LBind>>,231		this_id: Option<LocalId>,232	}233	impl Unbound for UnboundLocals {234		type Bound = Context;235236		fn bind(&self, sup_this: SupThis) -> Result<Context> {237			let parent = self.fctx.clone();238239			let fctx = Context::new_future();240			let mut builder = ContextBuilder::extend(241				parent,242				self.locals.iter().map(|b| b.destruct.ids().len()).sum(),243			);244			for b in self.locals.iter() {245				evaluate_dest(b, fctx.clone(), &mut builder);246			}247			if let Some(this_id) = self.this_id {248				builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));249			}250			let ctx = builder.build_sup_this(sup_this).into_future(fctx);251			Ok(ctx)252		}253	}254255	UnboundLocals {256		fctx,257		locals,258		this_id,259	}260}