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

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/mod.rs19.5 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	CcObjectAssertion, CcUnbound, Context, Error, MaybeUnbound, ObjValue, ObjValueBuilder,15	ObjectAssertion, Result, ResultExt as _, StaticShapeOopObject, SupThis, Unbound, Val,16	analyze::{17		ClosureShape, LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction,18		LIndexPart, LObjAsserts, LObjBody, LObjMembers, LObjStaticMembers, 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::StaticMembers(members) => {473			Ok(evaluate_static_obj_members(super_obj, ctx, members))474		}475		LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),476	}477}478479pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(480	builder: &mut ObjValueBuilder,481	ctx: Context,482	uctx: B,483	field: &LFieldMember,484) -> Result<()> {485	#[derive(Trace)]486	struct UnboundValue<B: Trace> {487		uctx: B,488		value: Rc<(ClosureShape, LExpr)>,489		name: IStr,490	}491	impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {492		type Bound = Val;493		fn bind(&self, sup_this: SupThis) -> Result<Val> {494			let a_ctx = self.uctx.bind(sup_this)?;495			let b_ctx = Context::enter_using(&a_ctx, &self.value.0);496			evaluate(b_ctx, &self.value.1)497		}498	}499500	let LFieldMember {501		name,502		plus,503		visibility,504		value,505	} = field;506	let Some(name) = evaluate_field_name(ctx, name)? else {507		return Ok(());508	};509510	builder511		.field(name.clone())512		.with_add(*plus)513		.with_visibility(*visibility)514		.bindable(UnboundValue {515			uctx,516			value: value.clone(),517			name,518		})519}520pub fn evaluate_field_member_static(521	builder: &mut ObjValueBuilder,522	field_ctx: Context,523	value_ctx: Context,524	field: &LFieldMember,525) -> Result<()> {526	let LFieldMember {527		name,528		plus,529		visibility,530		value,531	} = field;532	let Some(name) = evaluate_field_name(field_ctx, name)? else {533		return Ok(());534	};535536	let env = Context::enter_using(&value_ctx, &value.0);537	let value = value.clone();538	builder539		.field(name)540		.with_add(*plus)541		.with_visibility(*visibility)542		.try_thunk(Thunk!(move || evaluate(env, &value.1)))?;543	Ok(())544}545546fn evaluate_static_obj_members(547	super_obj: Option<ObjValue>,548	ctx: Context,549	members: &LObjStaticMembers,550) -> Val {551	#[derive(Trace)]552	struct UnboundField<B: Trace> {553		uctx: B,554		value: Rc<(ClosureShape, LExpr)>,555		name: IStr,556	}557	impl<B: Unbound<Bound = Context>> Unbound for UnboundField<B> {558		type Bound = Val;559		fn bind(&self, sup_this: SupThis) -> Result<Val> {560			let a_ctx = self.uctx.bind(sup_this)?;561			let b_ctx = Context::enter_using(&a_ctx, &self.value.0);562			evaluate(b_ctx, &self.value.1)563		}564	}565566	let needs_unbound = members.this.is_some() || members.uses_super;567568	let mut bindings: Vec<MaybeUnbound> = Vec::with_capacity(members.bindings.len());569570	let assertion = if needs_unbound {571		let uctx = CachedUnbound::new(evaluate_locals_unbound(572			&ctx,573			&members.frame_shape,574			members.this,575			members.locals.clone(),576		));577		for (binding, field) in members.bindings.iter().zip(members.shape.fields()) {578			if let Some(v) = evaluate_trivial(&binding.1) {579				bindings.push(MaybeUnbound::Const(v));580				continue;581			}582			bindings.push(MaybeUnbound::Unbound(CcUnbound::new(UnboundField {583				uctx: uctx.clone(),584				value: binding.clone(),585				name: field.name.clone(),586			})));587		}588		members589			.asserts590			.as_ref()591			.map(|a| CcObjectAssertion::new(evaluate_object_assertions_unbound(uctx, a.clone())))592	} else {593		let a_ctx = ctx594			.pack_captures_sup_this(&members.frame_shape)595			.enter(|fill, ctx| {596				fill_letrec_binds(fill, ctx, &members.locals);597			});598		for binding in &members.bindings {599			if let Some(v) = evaluate_trivial(&binding.1) {600				bindings.push(MaybeUnbound::Const(v));601				continue;602			}603			let env = Context::enter_using(&a_ctx, &binding.0);604			let value = binding.clone();605			bindings.push(MaybeUnbound::Bound(Thunk!(move || evaluate(env, &value.1))));606		}607		members.asserts.as_ref().map(|a| {608			CcObjectAssertion::new(evaluate_object_assertions_static(a_ctx.clone(), a.clone()))609		})610	};611612	let mut builder = ObjValueBuilder::with_capacity(0);613	if let Some(sup) = super_obj {614		builder.with_super(sup);615	}616	builder.extend_with_core(StaticShapeOopObject::new(617		members.shape.clone(),618		bindings,619		assertion,620	));621	Val::Obj(builder.build())622}623624fn evaluate_obj_members(625	super_obj: Option<ObjValue>,626	ctx: Context,627	members: &LObjMembers,628) -> Result<Val> {629	let mut builder = ObjValueBuilder::with_capacity(members.fields.len());630	if let Some(sup) = super_obj {631		builder.with_super(sup);632	}633634	let needs_unbound = members.this.is_some() || members.uses_super;635636	if needs_unbound {637		let uctx = CachedUnbound::new(evaluate_locals_unbound(638			&ctx,639			&members.frame_shape,640			members.this,641			members.locals.clone(),642		));643		for field in &members.fields {644			evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;645		}646		if let Some(asserts_block) = &members.asserts {647			builder.assert(evaluate_object_assertions_unbound(648				uctx,649				asserts_block.clone(),650			));651		}652	} else {653		let a_ctx = ctx654			.pack_captures_sup_this(&members.frame_shape)655			.enter(|fill, ctx| {656				fill_letrec_binds(fill, ctx, &members.locals);657			});658		for field in &members.fields {659			evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;660		}661		if let Some(asserts_block) = &members.asserts {662			builder.assert(evaluate_object_assertions_static(663				a_ctx,664				asserts_block.clone(),665			));666		}667	}668669	Ok(Val::Obj(builder.build()))670}671672pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {673	let LAssertStmt { cond, message } = assertion;674	let assertion_result = in_frame(675		CallLocation::new(&cond.span),676		|| "assertion condition".to_owned(),677		|| bool::from_untyped(evaluate(ctx.clone(), cond)?),678	)?;679	if !assertion_result {680		in_frame(681			CallLocation::new(&cond.span),682			|| "assertion failure".to_owned(),683			|| {684				if let Some(msg) = message {685					bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));686				}687				bail!(AssertionFailed(Val::Null.to_string()?));688			},689		)?;690	}691	Ok(())692}693694fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(695	uctx: B,696	asserts: Rc<LObjAsserts>,697) -> impl ObjectAssertion {698	#[derive(Trace)]699	struct ObjectAssert<B: Trace> {700		uctx: B,701		asserts: Rc<LObjAsserts>,702	}703	impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {704		fn run(&self, sup_this: SupThis) -> Result<()> {705			let a_ctx = self.uctx.bind(sup_this)?;706			let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);707			for assert in &self.asserts.asserts {708				evaluate_assert(assert_env.clone(), assert)?;709			}710			Ok(())711		}712	}713	ObjectAssert { uctx, asserts }714}715fn evaluate_object_assertions_static(716	a_ctx: Context,717	asserts: Rc<LObjAsserts>,718) -> impl ObjectAssertion {719	#[derive(Trace)]720	struct ObjectAssert {721		assert_env: Context,722		asserts: Rc<LObjAsserts>,723	}724	impl ObjectAssertion for ObjectAssert {725		fn run(&self, _sup_this: SupThis) -> Result<()> {726			for assert in &self.asserts.asserts {727				evaluate_assert(self.assert_env.clone(), assert)?;728			}729			Ok(())730		}731	}732	let assert_env = Context::enter_using(&a_ctx, &asserts.shape);733	ObjectAssert {734		assert_env,735		asserts,736	}737}