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

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/mod.rs17.1 KiBsourcehistory
1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::ImportKind;6use jrsonnet_types::ValType;78use self::{9	compspec::{evaluate_arr_comp, evaluate_obj_comp},10	destructure::evaluate_locals_unbound,11	operator::evaluate_binary_op_special,12};13use crate::{14	Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _, SupThis,15	Unbound, Val,16	analyze::{17		ClosureShape, LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction,18		LIndexPart, LObjAsserts, LObjBody, LObjMembers, LSlot,19	},20	arr::ArrValue,21	bail,22	error::{ErrorKind::*, suggest_object_fields},23	evaluate::{destructure::fill_letrec_binds, operator::evaluate_unary_op},24	function::{CallLocation, FuncDesc, FuncVal, prepared::PreparedFuncVal},25	in_frame,26	typed::{BoundedUsize, FromUntyped as _},27	val::{CachedUnbound, Thunk},28	with_state,29};3031pub mod compspec;32pub mod destructure;33pub mod operator;3435// This is the amount of bytes that need to be left on the stack before increasing the size.36// It must be at least as large as the stack required by any code that does not call37// `ensure_sufficient_stack`.38const RED_ZONE: usize = 100 * 1024;3940// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then41// on. This flag has performance relevant characteristics. Don't set it too high.42const STACK_PER_RECURSION: usize = 1024 * 1024;4344/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations45/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit46/// from this.47///48/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.49#[inline]50pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {51	stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)52}5354pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {55	if let LExpr::Trivial(tv) = expr {56		Some(tv.clone().into())57	} else {58		None59	}60}6162pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {63	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {64		name,65		body_captures: ctx.pack_captures_sup_this(&func.body_shape),66		func: func.clone(),67	})))68}6970pub fn evaluate_field_name(ctx: Context, field_name: &LFieldName) -> Result<Option<IStr>> {71	Ok(match field_name {72		LFieldName::Fixed(n) => Some(n.clone()),73		LFieldName::Dyn(expr) => in_frame(74			// TODO: Spanned<LFieldName>75			CallLocation::native(),76			|| "evaluating field name".to_string(),77			|| {78				let v = evaluate(ctx.clone(), expr)?;79				Ok(if matches!(v, Val::Null) {80					None81				} else {82					Some(IStr::from_untyped(v)?)83				})84			},85		)?,86	})87}8889pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {90	match &*expr {91		LExpr::Slot(LSlot::Local(i)) => return Ok(ctx.local(*i)),92		LExpr::Slot(LSlot::Capture(i)) => return Ok(ctx.capture(*i)),93		_ => {94			if let Some(v) = evaluate_trivial(&expr) {95				return Ok(Thunk::evaluated(v));96			}97		}98	}99	Ok(if tailstrict {100		Thunk::evaluated(evaluate(ctx, &expr)?)101	} else {102		Thunk!(move || { evaluate(ctx, &expr) })103	})104}105106mod names {107	use crate::names;108109	names! {110		anonymous: "anonymous",111	}112}113114#[allow(clippy::too_many_lines)]115pub fn evaluate(mut ctx: Context, mut expr: &LExpr) -> Result<Val> {116	loop {117		return Ok(match expr {118			LExpr::Trivial(tv) => tv.clone().into(),119			LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,120			LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),121			LExpr::ArrConst(rc) => Val::Arr(ArrValue::new(rc.clone())),122			LExpr::Arr { shape, items } => {123				let inner = Context::enter_using(&ctx, shape);124				'eager: {125					let mut out: Vec<Val> = Vec::with_capacity(items.len());126					for item in items.iter() {127						let Ok(r) = evaluate(inner.clone(), item) else {128							break 'eager;129						};130						out.push(r);131					}132					return Ok(Val::Arr(ArrValue::new(out)));133				}134				Val::Arr(ArrValue::expr(inner, items.clone()))135			}136			LExpr::UnaryOp(op, value) => {137				let value = evaluate(ctx, value)?;138				evaluate_unary_op(*op, &value)?139			}140			LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,141			LExpr::LocalExpr(l) => {142				ctx = ctx143					.pack_captures_sup_this(&l.frame_shape)144					.enter(|fill, ctx| {145						fill_letrec_binds(fill, ctx, &l.binds);146					});147				expr = &l.body;148				continue;149			}150			LExpr::IfElse {151				cond,152				cond_then,153				cond_else,154			} => {155				let cond_val = evaluate(ctx.clone(), cond)?;156				let Val::Bool(b) = cond_val else {157					bail!(TypeMismatch(158						"if condition",159						vec![ValType::Bool],160						cond_val.value_type()161					))162				};163				if b {164					expr = cond_then;165					continue;166				} else if let Some(e) = cond_else {167					expr = e;168					continue;169				}170				Val::Null171			}172			LExpr::Error(s, e) => in_frame(173				CallLocation::new(s),174				|| "error statement".to_owned(),175				|| bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),176			)?,177			LExpr::AssertExpr { assert, rest } => {178				evaluate_assert(ctx.clone(), assert)?;179				expr = rest;180				continue;181			}182183			LExpr::Function(func) => evaluate_method(184				ctx,185				func.name.clone().unwrap_or_else(names::anonymous),186				func,187			),188			LExpr::IdentityFunction => Val::Func(FuncVal::identity()),189			LExpr::Apply {190				applicable,191				args,192				tailstrict,193			} => evaluate_apply(194				ctx,195				applicable,196				args,197				CallLocation::new(&args.span),198				*tailstrict,199			)?,200			LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,201			LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,202			LExpr::ObjExtend(lhs, body) => {203				let lhs_val = evaluate(ctx.clone(), lhs)?;204				let Val::Obj(lhs_obj) = lhs_val else {205					bail!(TypeMismatch(206						"object extend lhs",207						vec![ValType::Obj],208						lhs_val.value_type(),209					))210				};211				evaluate_obj_body(Some(lhs_obj), ctx, body)?212			}213			LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,214			LExpr::Slice(slice) => {215				let val = evaluate(ctx.clone(), &slice.value)?;216				let indexable = val.into_indexable()?;217				let start = slice218					.start219					.as_ref()220					.map(|e| evaluate(ctx.clone(), e))221					.transpose()?222					.map(|v| -> Result<i32> {223						i32::from_untyped(v).description("slice start value")224					})225					.transpose()?;226				let end = slice227					.end228					.as_ref()229					.map(|e| evaluate(ctx.clone(), e))230					.transpose()?231					.map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })232					.transpose()?;233				let step = slice234					.step235					.as_ref()236					.map(|e| evaluate(ctx, e))237					.transpose()?238					.map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {239						BoundedUsize::from_untyped(v).description("slice step value")240					})241					.transpose()?;242				Val::from(indexable.slice32(start, end, step)?)243			}244			LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),245			LExpr::Import {246				kind,247				kind_span,248				path,249			} => with_state(|state| {250				let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;251				Ok::<_, Error>(match kind.value {252					ImportKind::Normal => in_frame(253						CallLocation::new(&kind.span),254						|| "import".to_string(),255						|| state.import_resolved(resolved),256					)?,257					ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),258					ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),259				})260			})?,261		});262	}263}264265fn evaluate_apply(266	ctx: Context,267	applicable: &LExpr,268	args: &LArgsDesc,269	loc: CallLocation<'_>,270	tailstrict: bool,271) -> Result<Val> {272	let func_val = evaluate(ctx.clone(), applicable)?;273	let Val::Func(func) = func_val else {274		bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))275	};276277	if func.is_identity() && args.names.is_empty() && args.unnamed.len() == 1 {278		return evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?.evaluate();279	}280281	let name = func.name();282283	if args.names.is_empty() && args.unnamed.len() == 1 && func.params().len() == 1 {284		use crate::function::prepared::PreparedCall;285		let prepared_inline = PreparedCall::empty();286		let arg = evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?;287		let arg_slice = std::slice::from_ref(&arg);288		return in_frame(289			loc,290			|| format!("function <{name}> call"),291			|| {292				func.evaluate_prepared(293					&prepared_inline,294					CallLocation::native(),295					arg_slice,296					&[],297					tailstrict,298				)299			},300		);301	}302303	let unnamed = args304		.unnamed305		.iter()306		.cloned()307		.map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))308		.collect::<Result<Vec<_>>>()?;309310	// Fast path: positional-only multi-arg call fully covering the311	// params, no defaults.312	if args.names.is_empty() && unnamed.len() == func.params().len() {313		use crate::function::prepared::PreparedCall;314		let prepared_inline = PreparedCall::empty();315		return in_frame(316			loc,317			|| format!("function <{name}> call"),318			|| {319				func.evaluate_prepared(320					&prepared_inline,321					CallLocation::native(),322					&unnamed,323					&[],324					tailstrict,325				)326			},327		);328	}329330	let named = args331		.values332		.iter()333		.cloned()334		.map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))335		.collect::<Result<Vec<_>>>()?;336	let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)337		.with_description_src(loc, || format!("function <{name}> preparation"))?;338	in_frame(339		loc,340		|| format!("function <{name}> call"),341		|| prepare.call(CallLocation::native(), &unnamed, &named),342	)343}344345#[allow(clippy::too_many_lines)]346fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {347	let mut parts = parts.iter();348	let mut indexable = if matches!(indexable, LExpr::Super) {349		let part = parts.next().expect("at least part should exist");350		// sup_this existence check might also be skipped here for null-coalesce...351		// But I believe this might cause errors.352		let sup_this = ctx.try_sup_this()?;353354		if !sup_this.has_super() {355			#[cfg(feature = "exp-null-coaelse")]356			if part.null_coaelse {357				return Ok(Val::Null);358			}359			bail!(NoSuperFound);360		}361		let name = evaluate(ctx.clone(), &part.value)?;362363		let Val::Str(name) = name else {364			bail!(ValueIndexMustBeTypeGot(365				ValType::Obj,366				ValType::Str,367				name.value_type(),368			))369		};370371		let name = name.into_flat();372		match sup_this373			.get_super(name.clone())374			.with_description_src(&part.span, || format!("super field <{name}> access"))?375		{376			Some(v) => v,377			#[cfg(feature = "exp-null-coaelse")]378			None if part.null_coaelse => return Ok(Val::Null),379			None => {380				let suggestions = suggest_object_fields(381					&sup_this.standalone_super().expect("super exists"),382					name.clone(),383				);384				bail!(NoSuchField(name, suggestions))385			}386		}387	} else {388		evaluate(ctx.clone(), indexable)?389	};390391	for part in parts {392		let ctx = ctx.clone();393		let loc = CallLocation::new(&part.span);394		let value = indexable;395		let key_val = evaluate(ctx, &part.value)?;396		indexable = match (&value, &key_val) {397			(Val::Obj(obj), Val::Str(key)) => {398				let key = key.clone().into_flat();399				match obj400					.get(key.clone())401					.with_description_src(loc, || format!("field <{key}> access"))?402				{403					Some(v) => v,404					#[cfg(feature = "exp-null-coaelse")]405					None if part.null_coaelse => return Ok(Val::Null),406					None => {407						return Err(Error::from(NoSuchField(408							key.clone(),409							suggest_object_fields(obj, key.clone()),410						)))411						.with_description_src(loc, || format!("field <{key}> access"));412					}413				}414			}415			(Val::Arr(arr), Val::Num(idx)) => {416				let n = idx.get();417				if n.fract() > f64::EPSILON {418					bail!(FractionalIndex)419				}420				let len = arr.len32();421				if n < 0.0 || n > f64::from(len) {422					bail!(ArrayBoundsError(n, len));423				}424				#[expect(425					clippy::cast_possible_truncation,426					clippy::cast_sign_loss,427					reason = "n is checked range"428				)]429				let i = n as u32;430				arr.get32(i)431					.with_description_src(loc, || format!("element <{i}> access"))?432					.ok_or_else(|| ArrayBoundsError(n, len))?433			}434			(Val::Str(s), Val::Num(idx)) => {435				let n = idx.get();436				if n.fract() > f64::EPSILON {437					bail!(FractionalIndex)438				}439				#[expect(440					clippy::cast_possible_truncation,441					clippy::cast_sign_loss,442					reason = "n is checked positive, overflow will truncate as expected"443				)]444				let i = n as usize;445				let flat = s.clone().into_flat();446				#[allow(clippy::cast_possible_truncation, reason = "string is max 4g")]447				if n >= 0.0448					&& n <= f64::from(u32::MAX)449					&& let Some(char) = flat.chars().nth(i)450				{451					Val::string(char)452				} else {453					let len = flat.chars().count();454					bail!(StringBoundsError(n, len as u32))455				}456			}457			#[cfg(feature = "exp-null-coaelse")]458			(Val::Null, _) if part.null_coaelse => return Ok(Val::Null),459			_ => bail!(ValueIndexMustBeTypeGot(460				value.value_type(),461				ValType::Str,462				key_val.value_type()463			)),464		};465	}466	Ok(indexable)467}468469fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {470	match body {471		LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),472		LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),473	}474}475476pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(477	builder: &mut ObjValueBuilder,478	ctx: Context,479	uctx: B,480	field: &LFieldMember,481) -> Result<()> {482	#[derive(Trace)]483	struct UnboundValue<B: Trace> {484		uctx: B,485		value: Rc<(ClosureShape, LExpr)>,486		name: IStr,487	}488	impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {489		type Bound = Val;490		fn bind(&self, sup_this: SupThis) -> Result<Val> {491			let a_ctx = self.uctx.bind(sup_this)?;492			let b_ctx = Context::enter_using(&a_ctx, &self.value.0);493			evaluate(b_ctx, &self.value.1)494		}495	}496497	let LFieldMember {498		name,499		plus,500		visibility,501		value,502	} = field;503	let Some(name) = evaluate_field_name(ctx, name)? else {504		return Ok(());505	};506507	builder508		.field(name.clone())509		.with_add(*plus)510		.with_visibility(*visibility)511		.bindable(UnboundValue {512			uctx,513			value: value.clone(),514			name,515		})516}517pub fn evaluate_field_member_static(518	builder: &mut ObjValueBuilder,519	field_ctx: Context,520	value_ctx: Context,521	field: &LFieldMember,522) -> Result<()> {523	let LFieldMember {524		name,525		plus,526		visibility,527		value,528	} = field;529	let Some(name) = evaluate_field_name(field_ctx, name)? else {530		return Ok(());531	};532533	let env = Context::enter_using(&value_ctx, &value.0);534	let value = value.clone();535	builder536		.field(name)537		.with_add(*plus)538		.with_visibility(*visibility)539		.try_thunk(Thunk!(move || evaluate(env, &value.1)))?;540	Ok(())541}542543fn evaluate_obj_members(544	super_obj: Option<ObjValue>,545	ctx: Context,546	members: &LObjMembers,547) -> Result<Val> {548	let mut builder = ObjValueBuilder::with_capacity(members.fields.len());549	if let Some(sup) = super_obj {550		builder.with_super(sup);551	}552553	let needs_unbound = members.this.is_some() || members.uses_super;554555	if needs_unbound {556		let uctx = CachedUnbound::new(evaluate_locals_unbound(557			&ctx,558			&members.frame_shape,559			members.this,560			members.locals.clone(),561		));562		for field in &members.fields {563			evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;564		}565		if let Some(asserts_block) = &members.asserts {566			builder.assert(evaluate_object_assertions_unbound(567				uctx,568				asserts_block.clone(),569			));570		}571	} else {572		let a_ctx = ctx573			.pack_captures_sup_this(&members.frame_shape)574			.enter(|fill, ctx| {575				fill_letrec_binds(fill, ctx, &members.locals);576			});577		for field in &members.fields {578			evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;579		}580		if let Some(asserts_block) = &members.asserts {581			builder.assert(evaluate_object_assertions_static(582				a_ctx,583				asserts_block.clone(),584			));585		}586	}587588	Ok(Val::Obj(builder.build()))589}590591pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {592	let LAssertStmt { cond, message } = assertion;593	let assertion_result = in_frame(594		CallLocation::new(&cond.span),595		|| "assertion condition".to_owned(),596		|| bool::from_untyped(evaluate(ctx.clone(), cond)?),597	)?;598	if !assertion_result {599		in_frame(600			CallLocation::new(&cond.span),601			|| "assertion failure".to_owned(),602			|| {603				if let Some(msg) = message {604					bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));605				}606				bail!(AssertionFailed(Val::Null.to_string()?));607			},608		)?;609	}610	Ok(())611}612613fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(614	uctx: B,615	asserts: Rc<LObjAsserts>,616) -> impl ObjectAssertion {617	#[derive(Trace)]618	struct ObjectAssert<B: Trace> {619		uctx: B,620		asserts: Rc<LObjAsserts>,621	}622	impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {623		fn run(&self, sup_this: SupThis) -> Result<()> {624			let a_ctx = self.uctx.bind(sup_this)?;625			let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);626			for assert in &self.asserts.asserts {627				evaluate_assert(assert_env.clone(), assert)?;628			}629			Ok(())630		}631	}632	ObjectAssert { uctx, asserts }633}634fn evaluate_object_assertions_static(635	a_ctx: Context,636	asserts: Rc<LObjAsserts>,637) -> impl ObjectAssertion {638	#[derive(Trace)]639	struct ObjectAssert {640		assert_env: Context,641		asserts: Rc<LObjAsserts>,642	}643	impl ObjectAssertion for ObjectAssert {644		fn run(&self, _sup_this: SupThis) -> Result<()> {645			for assert in &self.asserts.asserts {646				evaluate_assert(self.assert_env.clone(), assert)?;647			}648			Ok(())649		}650	}651	let assert_env = Context::enter_using(&a_ctx, &asserts.shape);652	ObjectAssert {653		assert_env,654		asserts,655	}656}