git.delta.rocks / jrsonnet / refs/commits / de9f0cb0f75a

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/compspec.rs10.8 KiBsourcehistory
1use std::rc::Rc;23#[cfg(feature = "exp-object-iteration")]4use jrsonnet_interner::IStr;5use jrsonnet_types::ValType;67use super::{8	destructure::{destruct, evaluate_locals_unbound, fill_letrec_binds},9	evaluate_field_member_static, evaluate_field_member_unbound,10};11use crate::{12	Context, ObjValue, ObjValueBuilder, Result, Thunk, Val,13	analyze::{14		ClosureShape, LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp,15		LocalSlot,16	},17	arr::ArrValue,18	bail,19	error::ErrorKind::*,20	evaluate::{evaluate, evaluate_trivial},21};2223enum CachedOver {24	Arr(ArrValue),25	#[cfg(feature = "exp-object-iteration")]26	Obj(ObjValue),27}2829trait CompCollector {30	fn reserve(&mut self, _guaranteed: usize) {}31	fn collect(&mut self, ctx: Context) -> Result<()>;32}3334struct EagerArrCollector<'a> {35	out: &'a mut Vec<Val>,36	value_shape: &'a ClosureShape,37	value: &'a LExpr,38}39impl CompCollector for EagerArrCollector<'_> {40	fn reserve(&mut self, size_hint: usize) {41		self.out.reserve(size_hint);42	}43	fn collect(&mut self, ctx: Context) -> Result<()> {44		if let Some(v) = evaluate_trivial(self.value) {45			self.out.push(v);46			return Ok(());47		}48		if let LExpr::Slot(slot) = self.value {49			self.out.push(ctx.slot(*slot).evaluate()?);50			return Ok(());51		}52		let env = Context::enter_using(&ctx, self.value_shape);53		self.out.push(evaluate(env, self.value)?);54		Ok(())55	}56}5758struct LazyArrCollector<'a> {59	out: &'a mut Vec<Thunk<Val>>,60	value_shape: &'a ClosureShape,61	value: &'a Rc<LExpr>,62}63impl CompCollector for LazyArrCollector<'_> {64	fn reserve(&mut self, size_hint: usize) {65		self.out.reserve(size_hint);66	}67	fn collect(&mut self, ctx: Context) -> Result<()> {68		if let Some(v) = evaluate_trivial(self.value) {69			self.out.push(Thunk::evaluated(v));70			return Ok(());71		}72		if let LExpr::Slot(slot) = self.value.as_ref() {73			self.out.push(ctx.slot(*slot));74			return Ok(());75		}76		let env = Context::enter_using(&ctx, self.value_shape);77		let value_expr = self.value.clone();78		self.out.push(Thunk!(move || evaluate(env, &value_expr)));79		Ok(())80	}81}8283struct ObjCompCollectorStatic<'a> {84	builder: &'a mut ObjValueBuilder,85	frame_shape: &'a ClosureShape,86	locals: &'a [LBind],87	field: &'a LFieldMember,88}89impl CompCollector for ObjCompCollectorStatic<'_> {90	fn reserve(&mut self, guaranteed: usize) {91		self.builder.reserve_fields(guaranteed);92	}93	fn collect(&mut self, inner_ctx: Context) -> Result<()> {94		// Build the object's A-frame fresh per iteration: captures from95		// the comp's iter ctx, locals = `this` (slot 0, unfilled in the96		// static path) + member-locals via letrec.97		let value_ctx = inner_ctx98			.pack_captures_sup_this(self.frame_shape)99			.enter(|fill, ctx| {100				fill_letrec_binds(fill, ctx, self.locals);101			});102		evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)103	}104}105106struct ObjCompCollectorUnbound<'a> {107	builder: &'a mut ObjValueBuilder,108	frame_shape: Rc<ClosureShape>,109	locals: Rc<Vec<LBind>>,110	this_slot: Option<LocalSlot>,111	field: &'a LFieldMember,112}113impl CompCollector for ObjCompCollectorUnbound<'_> {114	fn reserve(&mut self, guaranteed: usize) {115		self.builder.reserve_fields(guaranteed);116	}117	fn collect(&mut self, inner_ctx: Context) -> Result<()> {118		let uctx = evaluate_locals_unbound(119			&inner_ctx,120			&self.frame_shape,121			self.this_slot,122			self.locals.clone(),123		);124		evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)125	}126}127128pub fn evaluate_obj_comp(129	super_obj: Option<ObjValue>,130	ctx: Context,131	comp: &LObjComp,132) -> Result<Val> {133	let mut builder = ObjValueBuilder::new();134	if let Some(super_obj) = super_obj {135		builder.with_super(super_obj);136	}137138	let cached_overs = cache_overs(&ctx, &comp.compspecs)?;139	if comp.this.is_some() || comp.uses_super {140		evaluate_compspecs(141			ctx,142			&comp.compspecs,143			&cached_overs,144			0,145			0,146			&mut ObjCompCollectorUnbound {147				builder: &mut builder,148				frame_shape: comp.frame_shape.clone(),149				locals: comp.locals.clone(),150				this_slot: comp.this,151				field: &comp.field,152			},153		)?;154	} else {155		evaluate_compspecs(156			ctx,157			&comp.compspecs,158			&cached_overs,159			0,160			0,161			&mut ObjCompCollectorStatic {162				builder: &mut builder,163				frame_shape: &comp.frame_shape,164				locals: &comp.locals,165				field: &comp.field,166			},167		)?;168	}169170	Ok(Val::Obj(builder.build()))171}172173pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {174	let cached_overs = cache_overs(&ctx, &comp.compspecs)?;175176	// Eager fast-path: when the comp has only `if` and `for { destruct: Full(_) }`177	// specs, allocate one Iter A-frame per for-spec and re-set the slot178	// per iteration as long as the frame's refcount stays at 1.179	'eager: {180		let mut out = Vec::new();181182		if comp.compspecs.iter().all(|c| {183			matches!(184				c,185				LCompSpec::If(_)186					| LCompSpec::For {187						destruct: LDestruct::Full(_),188						..189					}190			)191		}) && evaluate_compspecs_eager(192			ctx.clone(),193			&comp.compspecs,194			&cached_overs,195			0,196			0,197			&mut EagerArrCollector {198				out: &mut out,199				value_shape: &comp.value_shape,200				value: &comp.value,201			},202		)203		.is_err()204		{205			break 'eager;206		}207		return Ok(Val::arr(out));208	}209210	let mut items: Vec<Thunk<Val>> = Vec::new();211	evaluate_compspecs(212		ctx,213		&comp.compspecs,214		&cached_overs,215		0,216		0,217		&mut LazyArrCollector {218			out: &mut items,219			value_shape: &comp.value_shape,220			value: &comp.value,221		},222	)?;223	Ok(Val::arr(items))224}225226fn cache_overs(ctx: &Context, specs: &[LCompSpec]) -> Result<Vec<Option<CachedOver>>> {227	specs228		.iter()229		.map(|spec| {230			Ok(match spec {231				LCompSpec::For {232					over,233					loop_invariant: true,234					..235				} => {236					let val = evaluate(ctx.clone(), over)?;237					let Val::Arr(arr) = val else {238						bail!(InComprehensionCanOnlyIterateOverArray)239					};240					Some(CachedOver::Arr(arr))241				}242				#[cfg(feature = "exp-object-iteration")]243				LCompSpec::ForObj {244					over,245					loop_invariant: true,246					..247				} => {248					let val = evaluate(ctx.clone(), over)?;249					let Val::Obj(obj) = val else {250						bail!(TypeMismatch(251							"object iteration over",252							vec![jrsonnet_types::ValType::Obj],253							val.value_type(),254						))255					};256					Some(CachedOver::Obj(obj))257				}258				_ => None,259			})260		})261		.collect::<Result<_>>()262}263264fn evaluate_compspecs_eager(265	ctx: Context,266	specs: &[LCompSpec],267	cached_overs: &[Option<CachedOver>],268	idx: usize,269	guaranteed_reserve: usize,270	collector: &mut dyn CompCollector,271) -> Result<()> {272	if idx >= specs.len() {273		collector.reserve(guaranteed_reserve);274		return collector.collect(ctx);275	}276	match &specs[idx] {277		LCompSpec::If(cond) => {278			let val = evaluate(ctx.clone(), cond)?;279			let Val::Bool(b) = val else {280				bail!(TypeMismatch(281					"if spec condition",282					vec![ValType::Bool],283					val.value_type()284				))285			};286			if b {287				evaluate_compspecs_eager(ctx, specs, cached_overs, idx + 1, 0, collector)?;288			}289		}290		LCompSpec::For {291			frame_shape,292			destruct,293			over,294			..295		} => {296			let arr = if let Some(CachedOver::Arr(cached)) = &cached_overs[idx] {297				cached.clone()298			} else {299				let arr_val = evaluate(ctx.clone(), over)?;300				let Val::Arr(arr) = arr_val else {301					bail!(InComprehensionCanOnlyIterateOverArray)302				};303				arr304			};305			let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;306			match destruct {307				LDestruct::Full(slot) => {308					Context::enter_iter(&ctx, frame_shape, |it| {309						for (i, item) in arr.iter().enumerate() {310							let item = item?;311							let ctx = it.create(|f| {312								f.set(*slot, Thunk::evaluated(item));313							})?;314							evaluate_compspecs_eager(315								ctx,316								specs,317								cached_overs,318								idx + 1,319								if i == 0 { inner_reserve } else { 0 },320								collector,321							)?;322						}323						Ok(())324					})?;325				}326				// TODO: Should not be eager? CoW won't work here327				#[cfg(feature = "exp-destruct")]328				_ => unreachable!("eager compspecs are not possible with non-full patterns"),329			}330		}331		#[cfg(feature = "exp-object-iteration")]332		LCompSpec::ForObj { .. } => {333			unreachable!("eager compspecs filter rejects ForObj");334		}335	}336	Ok(())337}338339#[allow(clippy::too_many_lines)]340fn evaluate_compspecs(341	ctx: Context,342	specs: &[LCompSpec],343	cached_overs: &[Option<CachedOver>],344	idx: usize,345	guaranteed_reserve: usize,346	collector: &mut dyn CompCollector,347) -> Result<()> {348	if idx >= specs.len() {349		collector.reserve(guaranteed_reserve);350		return collector.collect(ctx);351	}352	match &specs[idx] {353		LCompSpec::If(cond) => {354			let val = evaluate(ctx.clone(), cond)?;355			let Val::Bool(b) = val else {356				bail!(TypeMismatch(357					"if spec condition",358					vec![ValType::Bool],359					val.value_type()360				))361			};362			if b {363				evaluate_compspecs(ctx, specs, cached_overs, idx + 1, 0, collector)?;364			}365		}366		LCompSpec::For {367			frame_shape,368			destruct: dst,369			over,370			..371		} => {372			let arr = if let Some(CachedOver::Arr(cached)) = &cached_overs[idx] {373				cached.clone()374			} else {375				let arr_val = evaluate(ctx.clone(), over)?;376				let Val::Arr(arr) = arr_val else {377					bail!(InComprehensionCanOnlyIterateOverArray)378				};379				arr380			};381			let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;382			for (i, item) in arr.iter().enumerate() {383				let item = item?;384				let inner_ctx = ctx.pack_captures_sup_this(frame_shape).enter(|fill, ctx| {385					destruct(dst, fill, Thunk::evaluated(item), ctx);386				});387				evaluate_compspecs(388					inner_ctx,389					specs,390					cached_overs,391					idx + 1,392					if i == 0 { inner_reserve } else { 0 },393					collector,394				)?;395			}396		}397		#[cfg(feature = "exp-object-iteration")]398		LCompSpec::ForObj {399			frame_shape,400			key,401			visibility,402			value,403			over,404			..405		} => {406			use jrsonnet_ir::Visibility;407			let obj = if let Some(CachedOver::Obj(cached)) = &cached_overs[idx] {408				cached.clone()409			} else {410				let val = evaluate(ctx.clone(), over)?;411				let Val::Obj(obj) = val else {412					bail!(TypeMismatch(413						"object iteration over",414						vec![ValType::Obj],415						val.value_type(),416					))417				};418				obj419			};420			let fields = obj.fields_with_visibility(421				#[cfg(feature = "exp-preserve-order")]422				false,423			);424			let pairs: Vec<(IStr, Visibility)> = fields425				.into_iter()426				.filter(|(_, v)| match visibility {427					Visibility::Normal => v.is_visible(),428					Visibility::Hidden => !v.is_visible(),429					Visibility::Unhide => true,430				})431				.collect();432			let inner_reserve = guaranteed_reserve.max(1) * pairs.len();433			for (i, (field_name, _)) in pairs.into_iter().enumerate() {434				let key_val = Val::string(field_name.clone());435				let value_thunk = obj436					.get_lazy(field_name.clone())437					.expect("field exists, just enumerated");438				let inner_ctx = ctx.pack_captures_sup_this(frame_shape).enter(|fill, ctx| {439					fill.set(*key, Thunk::evaluated(key_val));440					destruct(value, fill, value_thunk, &ctx);441				});442				evaluate_compspecs(443					inner_ctx,444					specs,445					cached_overs,446					idx + 1,447					if i == 0 { inner_reserve } else { 0 },448					collector,449				)?;450			}451		}452	}453	Ok(())454}