git.delta.rocks / jrsonnet / refs/commits / 1979dbda1bc0

difftreelog

source

crates/jrsonnet-evaluator/src/evaluate/mod.rs21.6 KiBsourcehistory
1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::{6	ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprParams, FieldMember,7	FieldName, ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, Spanned,8	function::ParamName,9};10use jrsonnet_types::ValType;1112use self::destructure::destruct;13use crate::{14	Context, ContextBuilder, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,15	ResultExt, SupThis, Unbound, Val,16	arr::ArrValue,17	bail,18	destructure::evaluate_dest,19	error::{ErrorKind::*, suggest_object_fields},20	evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op},21	function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal},22	in_frame,23	typed::{FromUntyped, IntoUntyped as _, Typed},24	val::{CachedUnbound, IndexableVal, StrValue, Thunk},25	with_state,26};27pub mod destructure;28pub mod operator;2930// This is the amount of bytes that need to be left on the stack before increasing the size.31// It must be at least as large as the stack required by any code that does not call32// `ensure_sufficient_stack`.33const RED_ZONE: usize = 100 * 1024; // 100k3435// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then36// on. This flag has performance relevant characteristics. Don't set it too high.37const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB3839/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations40/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit41/// from this.42///43/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.44#[inline]45pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {46	stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)47}4849pub fn evaluate_trivial(expr: &Expr) -> Option<Val> {50	fn is_trivial(expr: &Expr) -> bool {51		match expr {52			Expr::Str(_)53			| Expr::Num(_)54			| Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,55			Expr::Arr(a) => a.iter().all(is_trivial),56			_ => false,57		}58	}59	Some(match expr {60		Expr::Str(s) => Val::string(s.clone()),61		Expr::Num(n) => Val::Num(*n),62		Expr::Literal(LiteralType::False) => Val::Bool(false),63		Expr::Literal(LiteralType::True) => Val::Bool(true),64		Expr::Literal(LiteralType::Null) => Val::Null,65		Expr::Arr(n) => {66			if n.iter().any(|e| !is_trivial(e)) {67				return None;68			}69			Val::Arr(70				n.iter()71					.map(evaluate_trivial)72					.map(|e| e.expect("checked trivial"))73					.collect(),74			)75		}76		_ => return None,77	})78}7980pub fn evaluate_method(ctx: Context, name: IStr, params: ExprParams, body: Rc<Expr>) -> Val {81	Val::Func(FuncVal::Normal(Cc::new(FuncDesc {82		name,83		ctx,84		params,85		body,86	})))87}8889pub fn evaluate_field_name(ctx: Context, field_name: &Spanned<FieldName>) -> Result<Option<IStr>> {90	Ok(match &field_name.value {91		FieldName::Fixed(n) => Some(n.clone()),92		FieldName::Dyn(expr) => in_frame(93			CallLocation::new(&field_name.span),94			|| "evaluating field name".to_string(),95			|| {96				let v = evaluate(ctx, expr)?;97				Ok(if matches!(v, Val::Null) {98					None99				} else {100					Some(IStr::from_untyped(v)?)101				})102			},103		)?,104	})105}106107pub fn evaluate_comp(108	ctx: Context,109	specs: &[CompSpec],110	mut guaranteed_reserve: usize,111	callback: &mut impl FnMut(Context, usize) -> Result<()>,112) -> Result<()> {113	match specs.first() {114		None => callback(ctx, guaranteed_reserve)?,115		Some(CompSpec::IfSpec(IfSpecData { cond, span: _ })) => {116			if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {117				evaluate_comp(ctx, &specs[1..], 0, callback)?;118			}119		}120		Some(CompSpec::ForSpec(ForSpecData {121			destruct: into,122			over,123		})) => {124			match evaluate(ctx.clone(), over)? {125				Val::Arr(list) => {126					guaranteed_reserve = guaranteed_reserve.max(1) * list.len();127					for (i, item) in list.iter_lazy().enumerate() {128						let fctx = Pending::new();129						let mut ctx = ContextBuilder::extend_fast(ctx.clone());130						destruct(into, item, fctx.clone(), &mut ctx)?;131						let ctx = ctx.build().into_future(fctx);132133						let specs = &specs[1..];134						evaluate_comp(135							ctx,136							specs,137							if i == 0 || !specs.is_empty() {138								guaranteed_reserve139							} else {140								0141							},142							callback,143						)?;144					}145				}146				Val::Obj(obj) if cfg!(feature = "exp-object-iteration") => {147					let fields = obj.fields(148						// TODO: Should there be ability to preserve iteration order?149						#[cfg(feature = "exp-preserve-order")]150						false,151					);152					guaranteed_reserve = guaranteed_reserve.max(1) * fields.len();153					for (i, field) in fields.into_iter().enumerate() {154						let fctx = Pending::new();155						let mut ctx = ContextBuilder::extend_fast(ctx.clone());156						let obj = obj.clone();157						let value = Thunk::evaluated(Val::arr(vec![158							Thunk::evaluated(Val::string(field.clone())),159							obj.get_lazy(field).expect(160								"field exists, as field name was obtained from object.fields()",161							),162						]));163						destruct(into, value, fctx.clone(), &mut ctx)?;164						let ctx = ctx.build().into_future(fctx);165166						evaluate_comp(167							ctx,168							&specs[1..],169							if i == 0 || !specs.is_empty() {170								guaranteed_reserve171							} else {172								0173							},174							callback,175						)?;176					}177				}178				_ => bail!(InComprehensionCanOnlyIterateOverArray),179			}180		}181	}182	Ok(())183}184185fn evaluate_arr_comp(ctx: Context, expr: &Rc<Expr>, comp_specs: &[CompSpec]) -> Result<ArrValue> {186	let ctx = ctx.branch_point();187	'eager: {188		let mut out = Vec::new();189190		if evaluate_comp(ctx.clone(), comp_specs, 0, &mut |ctx, reserve| {191			if reserve != 0 {192				out.reserve(reserve);193			}194			out.push(evaluate(ctx, expr)?);195			Ok(())196		})197		.is_err()198		{199			break 'eager;200		}201202		return Ok(ArrValue::new(out));203	};204	let mut out = Vec::new();205	evaluate_comp(ctx, comp_specs, 0, &mut |ctx, reserve| {206		if reserve != 0 {207			out.reserve(reserve);208		}209		let expr = expr.clone();210		out.push(Thunk!(move || evaluate(ctx, &expr)));211		Ok(())212	})?;213	Ok(ArrValue::new(out))214}215216trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}217impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}218219fn evaluate_object_locals(220	fctx: Context,221	locals: Rc<Vec<BindSpec>>,222) -> impl CloneableUnbound<Context> {223	#[derive(Trace, Clone)]224	struct UnboundLocals {225		fctx: Context,226		locals: Rc<Vec<BindSpec>>,227	}228	impl Unbound for UnboundLocals {229		type Bound = Context;230231		fn bind(&self, sup_this: SupThis) -> Result<Context> {232			let fctx = Context::new_future();233			let ctx = self.fctx.clone();234			let mut ctx = ContextBuilder::extend(ctx);235			for b in self.locals.iter() {236				evaluate_dest(b, fctx.clone(), &mut ctx)?;237			}238239			let ctx = ctx.build_sup_this(sup_this).into_future(fctx);240241			Ok(ctx)242		}243	}244245	UnboundLocals { fctx, locals }246}247248pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(249	builder: &mut ObjValueBuilder,250	ctx: Context,251	uctx: B,252	field: &FieldMember,253) -> Result<()> {254	let name = evaluate_field_name(ctx, &field.name)?;255	let Some(name) = name else {256		return Ok(());257	};258259	match field {260		FieldMember {261			plus,262			params: None,263			visibility,264			value,265			..266		} => {267			#[derive(Trace)]268			struct UnboundValue<B: Trace> {269				uctx: B,270				value: Rc<Expr>,271				name: IStr,272			}273			impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {274				type Bound = Val;275				fn bind(&self, sup_this: SupThis) -> Result<Val> {276					evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())277				}278			}279280			builder281				.field(name.clone())282				.with_add(*plus)283				.with_visibility(*visibility)284				.with_location(field.name.span.clone())285				.bindable(UnboundValue {286					uctx,287					value: value.clone(),288					name,289				})?;290		}291		FieldMember {292			params: Some(params),293			visibility,294			value,295			..296		} => {297			#[derive(Trace)]298			struct UnboundMethod<B: Trace> {299				uctx: B,300				value: Rc<Expr>,301				params: ExprParams,302				name: IStr,303			}304			impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {305				type Bound = Val;306				fn bind(&self, sup_this: SupThis) -> Result<Val> {307					Ok(evaluate_method(308						self.uctx.bind(sup_this)?,309						self.name.clone(),310						self.params.clone(),311						self.value.clone(),312					))313				}314			}315316			builder317				.field(name.clone())318				.with_visibility(*visibility)319				// .with_location(value.span())320				.bindable(UnboundMethod {321					uctx,322					value: value.clone(),323					params: params.clone(),324					name,325				})?;326		}327	}328	Ok(())329}330331#[derive(Trace, Clone)]332struct DirectUnbound(Context);333impl Unbound for DirectUnbound {334	type Bound = Context;335	fn bind(&self, sup_this: SupThis) -> Result<Context> {336		Ok(ContextBuilder::extend(self.0.clone()).build_sup_this(sup_this))337	}338}339340#[allow(clippy::too_many_lines)]341pub fn evaluate_member_list_object(342	super_obj: Option<ObjValue>,343	ctx: Context,344	members: &ObjMembers,345) -> Result<ObjValue> {346	#[derive(Trace)]347	struct ObjectAssert<B: Trace> {348		uctx: B,349		asserts: Rc<Vec<AssertStmt>>,350	}351	impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {352		fn run(&self, sup_this: SupThis) -> Result<()> {353			let ctx = self.uctx.bind(sup_this)?;354			for assert in &*self.asserts {355				evaluate_assert(ctx.clone(), assert)?;356			}357			Ok(())358		}359	}360361	let mut builder = ObjValueBuilder::new();362	if let Some(super_obj) = super_obj {363		builder.with_super(super_obj);364	}365366	if members.locals.is_empty() {367		// We can use the same context for all field evaluation, it doesn't depends on locals, only on this/super368		let uctx = DirectUnbound(ctx.clone());369		for field in &members.fields {370			evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;371		}372		if !members.asserts.is_empty() {373			builder.assert(ObjectAssert {374				uctx,375				asserts: members.asserts.clone(),376			});377		}378	} else {379		let locals = members.locals.clone();380		// We have single context for all fields, so we can cache them together381		let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));382		for field in &members.fields {383			evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;384		}385		if !members.asserts.is_empty() {386			builder.assert(ObjectAssert {387				uctx,388				asserts: members.asserts.clone(),389			});390		}391	}392393	Ok(builder.build())394}395396pub fn evaluate_object(397	super_obj: Option<ObjValue>,398	ctx: Context,399	object: &ObjBody,400) -> Result<ObjValue> {401	Ok(match object {402		ObjBody::MemberList(members) => evaluate_member_list_object(super_obj, ctx, members)?,403		ObjBody::ObjComp(obj) => {404			let mut builder = ObjValueBuilder::new();405			if let Some(super_obj) = super_obj {406				builder.with_super(super_obj);407			}408			let locals = obj.locals.clone();409			evaluate_comp(410				ctx.branch_point(),411				&obj.compspecs,412				0,413				&mut |ctx, reserve| {414					let uctx = evaluate_object_locals(ctx.clone(), locals.clone());415					builder.reserve_fields(reserve);416417					evaluate_field_member(&mut builder, ctx, uctx, &obj.field)418				},419			)?;420421			builder.build()422		}423	})424}425426pub fn evaluate_apply(427	ctx: Context,428	value: &Expr,429	args: &ArgsDesc,430	loc: CallLocation<'_>,431	tailstrict: bool,432) -> Result<Val> {433	let value = evaluate(ctx.clone(), value)?;434	Ok(match value {435		Val::Func(f) => {436			let name = f.name();437			let unnamed = args438				.unnamed439				.iter()440				.cloned()441				.map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))442				.collect::<Result<Vec<_>>>()?;443			let named = args444				.values445				.iter()446				.cloned()447				.map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))448				.collect::<Result<Vec<_>>>()?;449			let prepare = PreparedFuncVal::new(f, args.unnamed.len(), &args.names)450				.with_description_src(loc, || format!("function <{name}> call"))?;451			let body = || prepare.call(loc, &unnamed, &named);452			if tailstrict {453				body()?454			} else {455				in_frame(loc, || format!("function <{name}> call"), body)?456			}457		}458		v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),459	})460}461462pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {463	let AssertStmt { assertion, message } = assertion;464	let assertion_result = in_frame(465		CallLocation::new(&assertion.span),466		|| "assertion condition".to_owned(),467		|| bool::from_untyped(evaluate(ctx.clone(), assertion)?),468	)?;469	if !assertion_result {470		in_frame(471			CallLocation::new(&assertion.span),472			|| "assertion failure".to_owned(),473			|| {474				if let Some(msg) = message {475					bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));476				}477				bail!(AssertionFailed(Val::Null.to_string()?));478			},479		)?;480	}481	Ok(())482}483484pub fn evaluate_named_param(ctx: Context, expr: &Expr, name: ParamName) -> Result<Val> {485	match name {486		ParamName::Named(name) => evaluate_named(ctx, expr, name),487		ParamName::Unnamed => evaluate(ctx, expr),488	}489}490491pub fn evaluate_named(ctx: Context, expr: &Expr, name: IStr) -> Result<Val> {492	use Expr::*;493	Ok(match expr {494		Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),495		_ => evaluate(ctx, expr)?,496	})497}498499pub fn evaluate_thunk(ctx: Context, expr: Rc<Expr>, tailstrict: bool) -> Result<Thunk<Val>> {500	Ok(if tailstrict {501		Thunk::evaluated(evaluate(ctx, &expr)?)502	} else {503		Thunk!(move || { evaluate(ctx, &expr) })504	})505}506#[allow(clippy::too_many_lines)]507pub fn evaluate(ctx: Context, expr: &Expr) -> Result<Val> {508	use Expr::*;509510	Ok(match expr {511		Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),512		Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),513		Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),514		Literal(LiteralType::True) => Val::Bool(true),515		Literal(LiteralType::False) => Val::Bool(false),516		Literal(LiteralType::Null) => Val::Null,517		Str(v) => Val::string(v.clone()),518		Num(v) => Val::try_num(*v)?,519		// I have tried to remove special behavior from super by implementing standalone-super520		// expresion, but looks like this case still needs special treatment.521		//522		// Note that other jsonnet implementations will fail on `if value in (super)` expression,523		// because the standalone super literal is not supported, that is because in other524		// implementations `in super` treated differently from `in smth_else`.525		BinaryOp(bin)526			if matches!(&bin.rhs, Expr::Literal(LiteralType::Super))527				&& bin.op == BinaryOpType::In =>528		{529			let sup_this = ctx.try_sup_this()?;530			// In jsonnet, "field" in e is eager, LHS expression is always executed regardless of super existence.531			// In jrsonnet, however, this wasn't true, this was kept here for compatibility.532			if !sup_this.has_super() {533				return Ok(Val::Bool(false));534			}535			let field = evaluate(ctx, &bin.lhs)?;536			Val::Bool(sup_this.field_in_super(field.to_string()?))537		}538		BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,539		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,540		Var(name) => in_frame(541			CallLocation::new(&name.span),542			|| format!("local <{}> access", &**name),543			|| ctx.binding((**name).clone())?.evaluate(),544		)?,545		Index { indexable, parts } => ensure_sufficient_stack(|| {546			let mut parts = parts.iter();547			let mut indexable = if matches!(&**indexable, Expr::Literal(LiteralType::Super)) {548				let part = parts.next().expect("at least part should exist");549				// sup_this existence check might also be skipped here for null-coalesce...550				// But I believe this might cause errors.551				let sup_this = ctx.try_sup_this()?;552				if !sup_this.has_super() {553					#[cfg(feature = "exp-null-coaelse")]554					if part.null_coaelse {555						return Ok(Val::Null);556					}557					bail!(NoSuperFound)558				}559				let name = evaluate(ctx.clone(), &part.value)?;560561				let Val::Str(name) = name else {562					bail!(ValueIndexMustBeTypeGot(563						ValType::Obj,564						ValType::Str,565						name.value_type(),566					))567				};568569				let name = name.into_flat();570				match sup_this571					.get_super(name.clone())572					.with_description_src(&part.span, || format!("field <{name}> access"))?573				{574					Some(v) => v,575					#[cfg(feature = "exp-null-coaelse")]576					None if part.null_coaelse => return Ok(Val::Null),577					None => {578						let suggestions = suggest_object_fields(579							&sup_this.standalone_super().expect("super exists"),580							name.clone(),581						);582583						bail!(NoSuchField(name, suggestions))584					}585				}586			} else {587				evaluate(ctx.clone(), indexable)?588			};589590			for part in parts {591				indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {592					(Val::Obj(v), Val::Str(key)) => match v593						.get(key.clone().into_flat())594						.with_description_src(&part.span, || format!("field <{key}> access"))?595					{596						Some(v) => v,597						#[cfg(feature = "exp-null-coaelse")]598						None if part.null_coaelse => return Ok(Val::Null),599						None => {600							let suggestions = suggest_object_fields(&v, key.into_flat());601602							return Err(Error::from(NoSuchField(603								key.clone().into_flat(),604								suggestions,605							)))606							.with_description_src(&part.span, || format!("field <{key}> access"));607						}608					},609					(Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(610						ValType::Obj,611						ValType::Str,612						n.value_type(),613					)),614					(Val::Arr(v), Val::Num(n)) => {615						let n = n.get();616						if n.fract() > f64::EPSILON {617							bail!(FractionalIndex)618						}619						if n < 0.0 {620							#[expect(621								clippy::cast_possible_truncation,622								reason = "it would be truncated anyway"623							)]624							let n = n as isize;625							bail!(ArrayBoundsError(n, v.len()));626						}627						#[expect(628							clippy::cast_possible_truncation,629							clippy::cast_sign_loss,630							reason = "n is checked postive"631						)]632						v.get(n as usize)?633							.ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?634					}635					(Val::Arr(_), Val::Str(n)) => {636						bail!(AttemptedIndexAnArrayWithString(n.into_flat()))637					}638					(Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(639						ValType::Arr,640						ValType::Num,641						n.value_type(),642					)),643644					(Val::Str(s), Val::Num(n)) => Val::Str({645						let n = n.get();646						if n.fract() > f64::EPSILON {647							bail!(FractionalIndex)648						}649						if n < 0.0 {650							#[expect(651								clippy::cast_possible_truncation,652								reason = "it would be truncated anyway"653							)]654							let n = n as isize;655							bail!(ArrayBoundsError(n, s.into_flat().chars().count()));656						}657						#[expect(658							clippy::cast_sign_loss,659							clippy::cast_possible_truncation,660							reason = "n is positive, overflow will truncate as expected"661						)]662						let n = n as usize;663						let v: IStr = s664							.clone()665							.into_flat()666							.chars()667							.skip(n)668							.take(1)669							.collect::<String>()670							.into();671						if v.is_empty() {672							bail!(StringBoundsError(n, s.into_flat().chars().count()))673						}674						StrValue::Flat(v)675					}),676					(Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(677						ValType::Str,678						ValType::Num,679						n.value_type(),680					)),681					#[cfg(feature = "exp-null-coaelse")]682					(Val::Null, _) if part.null_coaelse => return Ok(Val::Null),683					(v, _) => bail!(CantIndexInto(v.value_type())),684				};685			}686			Ok(indexable)687		})?,688		LocalExpr(bindings, returned) => {689			let fctx = Context::new_future();690			let mut ctx = ContextBuilder::extend(ctx);691			for b in bindings {692				evaluate_dest(b, fctx.clone(), &mut ctx)?;693			}694			let ctx = ctx.build().into_future(fctx);695			evaluate(ctx, returned)?696		}697		Arr(items) => {698			if items.is_empty() {699				Val::arr(())700			} else {701				Val::Arr(ArrValue::expr(ctx, items.clone()))702			}703		}704		ArrComp(expr, comp_specs) => Val::Arr(evaluate_arr_comp(ctx, expr, comp_specs)?),705		Obj(body) => Val::Obj(evaluate_object(None, ctx, body)?),706		ObjExtend(a, b) => {707			let base = evaluate(ctx.clone(), a)?;708			match base {709				Val::Obj(base_obj) => Val::Obj(evaluate_object(Some(base_obj), ctx, b)?),710				_ => bail!("ObjExtend lhs should be an object value"),711			}712		}713		Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {714			evaluate_apply(ctx, value, args, CallLocation::new(&args.span), *tailstrict)715		})?,716		Function(params, body) => {717			evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())718		}719		AssertExpr(assert) => {720			evaluate_assert(ctx.clone(), &assert.assert)?;721			evaluate(ctx, &assert.rest)?722		}723		ErrorStmt(s, e) => in_frame(724			CallLocation::new(s),725			|| "error statement".to_owned(),726			|| bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),727		)?,728		IfElse(if_else) => {729			if in_frame(730				CallLocation::new(&if_else.cond.span),731				|| "if condition".to_owned(),732				|| bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.cond)?),733			)? {734				evaluate(ctx, &if_else.cond_then)?735			} else {736				match &if_else.cond_else {737					Some(v) => evaluate(ctx, v)?,738					None => Val::Null,739				}740			}741		}742		Slice(slice) => {743			fn parse_idx<T: Typed + FromUntyped>(744				ctx: Context,745				expr: Option<&Spanned<Expr>>,746				desc: &'static str,747			) -> Result<Option<T>> {748				if let Some(value) = expr {749					Ok(in_frame(750						CallLocation::new(&value.span),751						|| format!("slice {desc}"),752						|| <Option<T>>::from_untyped(evaluate(ctx, value)?),753					)?)754				} else {755					Ok(None)756				}757			}758759			let indexable = evaluate(ctx.clone(), &slice.value)?;760761			let start = parse_idx(ctx.clone(), slice.slice.start.as_ref(), "start")?;762			let end = parse_idx(ctx.clone(), slice.slice.end.as_ref(), "end")?;763			let step = parse_idx(ctx, slice.slice.step.as_ref(), "step")?;764765			IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?766		}767		Import(kind, path) => {768			let Expr::Str(path) = &**path else {769				bail!("computed imports are not supported")770			};771			with_state(|s| {772				let span = &kind.span;773				let resolved_path = s.resolve_from(span.0.source_path(), path)?;774				Ok(match &**kind {775					ImportKind::Normal => in_frame(776						CallLocation::new(span),777						|| format!("import {:?}", path.clone()),778						|| s.import_resolved(resolved_path),779					)?,780					ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),781					ImportKind::Bin => Val::arr(s.import_resolved_bin(resolved_path)?),782				}) as Result<Val>783			})?784		}785	})786}