difftreelog
refactor trivial arrays
in: master
5 files changed
crates/jrsonnet-evaluator/src/analyze.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/analyze.rs
+++ b/crates/jrsonnet-evaluator/src/analyze.rs
@@ -138,14 +138,12 @@
#[derive(Debug, Acyclic)]
pub enum LExpr {
Slot(LSlot),
- Null,
- Bool(bool),
- Str(IStr),
- Num(NumValue),
+ Trivial(TrivialVal),
Arr {
shape: ClosureShape,
items: Rc<Vec<LExpr>>,
},
+ ArrConst(Rc<Vec<TrivialVal>>),
ArrComp(Box<LArrComp>),
Obj(LObjBody),
ObjExtend(Box<LExpr>, LObjBody),
@@ -1345,15 +1343,15 @@
#[allow(clippy::too_many_lines)]
pub fn analyze(expr: &Expr, stack: &mut AnalysisStack, taint: &mut AnalysisResult) -> LExpr {
match expr {
- Expr::Literal(span, l) => match l {
- LiteralType::This => stack.use_this(taint).map_or_else(
+ Expr::Identity(span, l) => match l {
+ IdentityKind::This => stack.use_this(taint).map_or_else(
|| {
stack.report_error("`self` used outside of object", Some(span.clone()));
LExpr::BadLocal("self")
},
LExpr::Slot,
),
- LiteralType::Super => {
+ IdentityKind::Super => {
if stack.use_super(taint).is_some() {
LExpr::Super
} else {
@@ -1361,25 +1359,34 @@
LExpr::BadLocal("super")
}
}
- LiteralType::Dollar => stack.use_dollar(taint).map_or_else(
+ IdentityKind::Dollar => stack.use_dollar(taint).map_or_else(
|| {
stack.report_error("`$` used outside of object", Some(span.clone()));
LExpr::BadLocal("$")
},
LExpr::Slot,
),
- LiteralType::Null => LExpr::Null,
- LiteralType::True => LExpr::Bool(true),
- LiteralType::False => LExpr::Bool(false),
},
- Expr::Str(s) => LExpr::Str(s.clone()),
- Expr::Num(n) => LExpr::Num(*n),
+ Expr::Trivial(tv) => LExpr::Trivial(tv.clone()),
Expr::Var(v) => stack
.use_local(&v.value, v.span.clone(), taint)
.map_or_else(|| LExpr::BadLocal("ref"), LExpr::Slot),
Expr::Arr(a) => {
- let (shape, items) = stack
- .in_using_closure(|stack| a.iter().map(|v| analyze(v, stack, taint)).collect());
+ if a.iter().all(|i| matches!(i, Expr::Trivial(_))) {
+ let trivials: Vec<_> = a
+ .iter()
+ .map(|i| match i {
+ Expr::Trivial(tv) => tv.clone(),
+ _ => unreachable!("checked above"),
+ })
+ .collect();
+ return LExpr::ArrConst(Rc::new(trivials));
+ }
+ let (shape, items) = stack.in_using_closure(|stack| {
+ a.iter()
+ .map(|v| analyze(v, stack, taint))
+ .collect::<Vec<_>>()
+ });
LExpr::Arr {
shape,
items: Rc::new(items),
@@ -1412,7 +1419,7 @@
}
Expr::LocalExpr(binds, body) => analyze_local_expr(binds, body, stack, taint),
Expr::Import(kind, path_expr) => {
- let Expr::Str(path) = &**path_expr else {
+ let Expr::Trivial(TrivialVal::Str(path)) = &**path_expr else {
stack.report_error(
"import path must be a string literal",
Some(kind.span.clone()),
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -8,12 +8,7 @@
use jrsonnet_gcmodule::{Cc, cc_dyn};
-use crate::{
- Context, Result, Thunk, Val,
- analyze::{ClosureShape, LExpr},
- function::NativeFn,
- typed::IntoUntyped,
-};
+use crate::{Context, Result, Thunk, Val, analyze::LExpr, function::NativeFn, typed::IntoUntyped};
mod spec;
pub use spec::{ArrayLike, *};
@@ -42,8 +37,8 @@
Self::new(())
}
- pub fn expr(ctx: Context, shape: &ClosureShape, exprs: Rc<Vec<LExpr>>) -> Self {
- Self::new(ExprArray::new(ctx, shape, exprs))
+ pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {
+ Self::new(ExprArray::new(ctx, exprs))
}
pub fn repeated(data: Self, repeats: u32) -> Option<Self> {
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -8,11 +8,12 @@
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::{IBytes, IStr};
+use jrsonnet_ir::TrivialVal;
use super::{ArrValue, arridx};
use crate::{
Context, Error, ObjValue, Result, Thunk, Val,
- analyze::{ClosureShape, LExpr},
+ analyze::LExpr,
error::ErrorKind::InfiniteRecursionDetected,
evaluate::evaluate,
function::NativeFn,
@@ -108,6 +109,18 @@
}
}
+impl ArrayCheap for Rc<Vec<TrivialVal>> {
+ fn get(&self, index: u32) -> Option<Val> {
+ self.as_slice()
+ .get(index as usize)
+ .map(|tv| tv.clone().into())
+ }
+
+ fn len(&self) -> u32 {
+ arridx(self.as_slice().len())
+ }
+}
+
#[derive(Debug, Trace, Clone)]
enum ArrayThunk {
Computed(Val),
@@ -123,9 +136,9 @@
cached: Cc<RefCell<Vec<ArrayThunk>>>,
}
impl ExprArray {
- pub fn new(outer: Context, shape: &ClosureShape, src: Rc<Vec<LExpr>>) -> Self {
+ pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {
Self {
- ctx: Context::enter_using(&outer, shape),
+ ctx,
cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),
src,
}
@@ -153,9 +166,17 @@
unreachable!()
};
- let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;
- self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());
- Ok(Some(new_value))
+ let result = evaluate(self.ctx.clone(), &self.src[index as usize]);
+ match result {
+ Ok(new_value) => {
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());
+ Ok(Some(new_value))
+ }
+ Err(e) => {
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Waiting;
+ Err(e)
+ }
+ }
}
fn get_lazy32(&self, index: u32) -> Option<Thunk<Val>> {
#[derive(Trace)]
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use 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 // TODO: Eager trivial array56 Some(match expr {57 LExpr::Str(s) => Val::string(s.clone()),58 LExpr::Num(n) => Val::Num(*n),59 LExpr::Bool(false) => Val::Bool(false),60 LExpr::Bool(true) => Val::Bool(true),61 LExpr::Null => Val::Null,62 _ => return None,63 })64}6566pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {67 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {68 name,69 body_captures: ctx.pack_captures_sup_this(&func.body_shape),70 func: func.clone(),71 })))72}7374pub fn evaluate_field_name(ctx: Context, field_name: &LFieldName) -> Result<Option<IStr>> {75 Ok(match field_name {76 LFieldName::Fixed(n) => Some(n.clone()),77 LFieldName::Dyn(expr) => in_frame(78 // TODO: Spanned<LFieldName>79 CallLocation::native(),80 || "evaluating field name".to_string(),81 || {82 let v = evaluate(ctx.clone(), expr)?;83 Ok(if matches!(v, Val::Null) {84 None85 } else {86 Some(IStr::from_untyped(v)?)87 })88 },89 )?,90 })91}9293pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {94 match &*expr {95 LExpr::Slot(LSlot::Local(i)) => return Ok(ctx.local(*i)),96 LExpr::Slot(LSlot::Capture(i)) => return Ok(ctx.capture(*i)),97 _ => {98 if let Some(v) = evaluate_trivial(&expr) {99 return Ok(Thunk::evaluated(v));100 }101 }102 }103 Ok(if tailstrict {104 Thunk::evaluated(evaluate(ctx, &expr)?)105 } else {106 Thunk!(move || { evaluate(ctx, &expr) })107 })108}109110mod names {111 use crate::names;112113 names! {114 anonymous: "anonymous",115 }116}117118#[allow(clippy::too_many_lines)]119pub fn evaluate(mut ctx: Context, mut expr: &LExpr) -> Result<Val> {120 loop {121 return Ok(match expr {122 LExpr::Null => Val::Null,123 LExpr::Bool(b) => Val::Bool(*b),124 LExpr::Str(s) => Val::string(s.clone()),125 LExpr::Num(n) => Val::Num(*n),126 LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,127 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),128 LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),129 LExpr::UnaryOp(op, value) => {130 let value = evaluate(ctx, value)?;131 evaluate_unary_op(*op, &value)?132 }133 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,134 LExpr::LocalExpr(l) => {135 ctx = ctx136 .pack_captures_sup_this(&l.frame_shape)137 .enter(|fill, ctx| {138 fill_letrec_binds(fill, ctx, &l.binds);139 });140 expr = &l.body;141 continue;142 }143 LExpr::IfElse {144 cond,145 cond_then,146 cond_else,147 } => {148 let cond_val = evaluate(ctx.clone(), cond)?;149 let Val::Bool(b) = cond_val else {150 bail!(TypeMismatch(151 "if condition",152 vec![ValType::Bool],153 cond_val.value_type()154 ))155 };156 if b {157 expr = cond_then;158 continue;159 } else if let Some(e) = cond_else {160 expr = e;161 continue;162 }163 Val::Null164 }165 LExpr::Error(s, e) => in_frame(166 CallLocation::new(s),167 || "error statement".to_owned(),168 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),169 )?,170 LExpr::AssertExpr { assert, rest } => {171 evaluate_assert(ctx.clone(), assert)?;172 expr = rest;173 continue;174 }175176 LExpr::Function(func) => evaluate_method(177 ctx,178 func.name.clone().unwrap_or_else(names::anonymous),179 func,180 ),181 LExpr::IdentityFunction => Val::Func(FuncVal::identity()),182 LExpr::Apply {183 applicable,184 args,185 tailstrict,186 } => evaluate_apply(187 ctx,188 applicable,189 args,190 CallLocation::new(&args.span),191 *tailstrict,192 )?,193 LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,194 LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,195 LExpr::ObjExtend(lhs, body) => {196 let lhs_val = evaluate(ctx.clone(), lhs)?;197 let Val::Obj(lhs_obj) = lhs_val else {198 bail!(TypeMismatch(199 "object extend lhs",200 vec![ValType::Obj],201 lhs_val.value_type(),202 ))203 };204 evaluate_obj_body(Some(lhs_obj), ctx, body)?205 }206 LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,207 LExpr::Slice(slice) => {208 let val = evaluate(ctx.clone(), &slice.value)?;209 let indexable = val.into_indexable()?;210 let start = slice211 .start212 .as_ref()213 .map(|e| evaluate(ctx.clone(), e))214 .transpose()?215 .map(|v| -> Result<i32> {216 i32::from_untyped(v).description("slice start value")217 })218 .transpose()?;219 let end = slice220 .end221 .as_ref()222 .map(|e| evaluate(ctx.clone(), e))223 .transpose()?224 .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })225 .transpose()?;226 let step = slice227 .step228 .as_ref()229 .map(|e| evaluate(ctx, e))230 .transpose()?231 .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {232 BoundedUsize::from_untyped(v).description("slice step value")233 })234 .transpose()?;235 Val::from(indexable.slice32(start, end, step)?)236 }237 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),238 LExpr::Import {239 kind,240 kind_span,241 path,242 } => with_state(|state| {243 let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;244 Ok::<_, Error>(match kind.value {245 ImportKind::Normal => in_frame(246 CallLocation::new(&kind.span),247 || "import".to_string(),248 || state.import_resolved(resolved),249 )?,250 ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),251 ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),252 })253 })?,254 });255 }256}257258fn evaluate_apply(259 ctx: Context,260 applicable: &LExpr,261 args: &LArgsDesc,262 loc: CallLocation<'_>,263 tailstrict: bool,264) -> Result<Val> {265 let func_val = evaluate(ctx.clone(), applicable)?;266 let Val::Func(func) = func_val else {267 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))268 };269270 if func.is_identity() && args.names.is_empty() && args.unnamed.len() == 1 {271 return evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?.evaluate();272 }273274 let name = func.name();275276 if args.names.is_empty() && args.unnamed.len() == 1 && func.params().len() == 1 {277 use crate::function::prepared::PreparedCall;278 let prepared_inline = PreparedCall::empty();279 let arg = evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?;280 let arg_slice = std::slice::from_ref(&arg);281 return in_frame(282 loc,283 || format!("function <{name}> call"),284 || {285 func.evaluate_prepared(286 &prepared_inline,287 CallLocation::native(),288 arg_slice,289 &[],290 tailstrict,291 )292 },293 );294 }295296 let unnamed = args297 .unnamed298 .iter()299 .cloned()300 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))301 .collect::<Result<Vec<_>>>()?;302303 // Fast path: positional-only multi-arg call fully covering the304 // params, no defaults.305 if args.names.is_empty() && unnamed.len() == func.params().len() {306 use crate::function::prepared::PreparedCall;307 let prepared_inline = PreparedCall::empty();308 return in_frame(309 loc,310 || format!("function <{name}> call"),311 || {312 func.evaluate_prepared(313 &prepared_inline,314 CallLocation::native(),315 &unnamed,316 &[],317 tailstrict,318 )319 },320 );321 }322323 let named = args324 .values325 .iter()326 .cloned()327 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))328 .collect::<Result<Vec<_>>>()?;329 let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)330 .with_description_src(loc, || format!("function <{name}> preparation"))?;331 in_frame(332 loc,333 || format!("function <{name}> call"),334 || prepare.call(CallLocation::native(), &unnamed, &named),335 )336}337338#[allow(clippy::too_many_lines)]339fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {340 let mut parts = parts.iter();341 let mut indexable = if matches!(indexable, LExpr::Super) {342 let part = parts.next().expect("at least part should exist");343 // sup_this existence check might also be skipped here for null-coalesce...344 // But I believe this might cause errors.345 let sup_this = ctx.try_sup_this()?;346347 if !sup_this.has_super() {348 #[cfg(feature = "exp-null-coaelse")]349 if part.null_coaelse {350 return Ok(Val::Null);351 }352 bail!(NoSuperFound);353 }354 let name = evaluate(ctx.clone(), &part.value)?;355356 let Val::Str(name) = name else {357 bail!(ValueIndexMustBeTypeGot(358 ValType::Obj,359 ValType::Str,360 name.value_type(),361 ))362 };363364 let name = name.into_flat();365 match sup_this366 .get_super(name.clone())367 .with_description_src(&part.span, || format!("super field <{name}> access"))?368 {369 Some(v) => v,370 #[cfg(feature = "exp-null-coaelse")]371 None if part.null_coaelse => return Ok(Val::Null),372 None => {373 let suggestions = suggest_object_fields(374 &sup_this.standalone_super().expect("super exists"),375 name.clone(),376 );377 bail!(NoSuchField(name, suggestions))378 }379 }380 } else {381 evaluate(ctx.clone(), indexable)?382 };383384 for part in parts {385 let ctx = ctx.clone();386 let loc = CallLocation::new(&part.span);387 let value = indexable;388 let key_val = evaluate(ctx, &part.value)?;389 indexable = match (&value, &key_val) {390 (Val::Obj(obj), Val::Str(key)) => {391 let key = key.clone().into_flat();392 match obj393 .get(key.clone())394 .with_description_src(loc, || format!("field <{key}> access"))?395 {396 Some(v) => v,397 #[cfg(feature = "exp-null-coaelse")]398 None if part.null_coaelse => return Ok(Val::Null),399 None => {400 return Err(Error::from(NoSuchField(401 key.clone(),402 suggest_object_fields(obj, key.clone()),403 )))404 .with_description_src(loc, || format!("field <{key}> access"));405 }406 }407 }408 (Val::Arr(arr), Val::Num(idx)) => {409 let n = idx.get();410 if n.fract() > f64::EPSILON {411 bail!(FractionalIndex)412 }413 let len = arr.len32();414 if n < 0.0 || n > f64::from(len) {415 bail!(ArrayBoundsError(n, len));416 }417 #[expect(418 clippy::cast_possible_truncation,419 clippy::cast_sign_loss,420 reason = "n is checked range"421 )]422 let i = n as u32;423 arr.get32(i)424 .with_description_src(loc, || format!("element <{i}> access"))?425 .ok_or_else(|| ArrayBoundsError(n, len))?426 }427 (Val::Str(s), Val::Num(idx)) => {428 let n = idx.get();429 if n.fract() > f64::EPSILON {430 bail!(FractionalIndex)431 }432 #[expect(433 clippy::cast_possible_truncation,434 clippy::cast_sign_loss,435 reason = "n is checked positive, overflow will truncate as expected"436 )]437 let i = n as usize;438 let flat = s.clone().into_flat();439 #[allow(clippy::cast_possible_truncation, reason = "string is max 4g")]440 if n >= 0.0441 && n <= f64::from(u32::MAX)442 && let Some(char) = flat.chars().nth(i)443 {444 Val::string(char)445 } else {446 let len = flat.chars().count();447 bail!(StringBoundsError(n, len as u32))448 }449 }450 #[cfg(feature = "exp-null-coaelse")]451 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),452 _ => bail!(ValueIndexMustBeTypeGot(453 value.value_type(),454 ValType::Str,455 key_val.value_type()456 )),457 };458 }459 Ok(indexable)460}461462fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {463 match body {464 LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),465 LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),466 }467}468469pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(470 builder: &mut ObjValueBuilder,471 ctx: Context,472 uctx: B,473 field: &LFieldMember,474) -> Result<()> {475 #[derive(Trace)]476 struct UnboundValue<B: Trace> {477 uctx: B,478 value: Rc<(ClosureShape, LExpr)>,479 name: IStr,480 }481 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {482 type Bound = Val;483 fn bind(&self, sup_this: SupThis) -> Result<Val> {484 let a_ctx = self.uctx.bind(sup_this)?;485 let b_ctx = Context::enter_using(&a_ctx, &self.value.0);486 evaluate(b_ctx, &self.value.1)487 }488 }489490 let LFieldMember {491 name,492 plus,493 visibility,494 value,495 } = field;496 let Some(name) = evaluate_field_name(ctx, name)? else {497 return Ok(());498 };499500 builder501 .field(name.clone())502 .with_add(*plus)503 .with_visibility(*visibility)504 .bindable(UnboundValue {505 uctx,506 value: value.clone(),507 name,508 })509}510pub fn evaluate_field_member_static(511 builder: &mut ObjValueBuilder,512 field_ctx: Context,513 value_ctx: Context,514 field: &LFieldMember,515) -> Result<()> {516 let LFieldMember {517 name,518 plus,519 visibility,520 value,521 } = field;522 let Some(name) = evaluate_field_name(field_ctx, name)? else {523 return Ok(());524 };525526 let env = Context::enter_using(&value_ctx, &value.0);527 let value = value.clone();528 builder529 .field(name)530 .with_add(*plus)531 .with_visibility(*visibility)532 .try_thunk(Thunk!(move || evaluate(env, &value.1)))?;533 Ok(())534}535536fn evaluate_obj_members(537 super_obj: Option<ObjValue>,538 ctx: Context,539 members: &LObjMembers,540) -> Result<Val> {541 let mut builder = ObjValueBuilder::with_capacity(members.fields.len());542 if let Some(sup) = super_obj {543 builder.with_super(sup);544 }545546 let needs_unbound = members.this.is_some() || members.uses_super;547548 if needs_unbound {549 let uctx = CachedUnbound::new(evaluate_locals_unbound(550 &ctx,551 &members.frame_shape,552 members.this,553 members.locals.clone(),554 ));555 for field in &members.fields {556 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;557 }558 if let Some(asserts_block) = &members.asserts {559 builder.assert(evaluate_object_assertions_unbound(560 uctx,561 asserts_block.clone(),562 ));563 }564 } else {565 let a_ctx = ctx566 .pack_captures_sup_this(&members.frame_shape)567 .enter(|fill, ctx| {568 fill_letrec_binds(fill, ctx, &members.locals);569 });570 for field in &members.fields {571 evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;572 }573 if let Some(asserts_block) = &members.asserts {574 builder.assert(evaluate_object_assertions_static(575 a_ctx,576 asserts_block.clone(),577 ));578 }579 }580581 Ok(Val::Obj(builder.build()))582}583584pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {585 let LAssertStmt { cond, message } = assertion;586 let assertion_result = in_frame(587 CallLocation::new(&cond.span),588 || "assertion condition".to_owned(),589 || bool::from_untyped(evaluate(ctx.clone(), cond)?),590 )?;591 if !assertion_result {592 in_frame(593 CallLocation::new(&cond.span),594 || "assertion failure".to_owned(),595 || {596 if let Some(msg) = message {597 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));598 }599 bail!(AssertionFailed(Val::Null.to_string()?));600 },601 )?;602 }603 Ok(())604}605606fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(607 uctx: B,608 asserts: Rc<LObjAsserts>,609) -> impl ObjectAssertion {610 #[derive(Trace)]611 struct ObjectAssert<B: Trace> {612 uctx: B,613 asserts: Rc<LObjAsserts>,614 }615 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {616 fn run(&self, sup_this: SupThis) -> Result<()> {617 let a_ctx = self.uctx.bind(sup_this)?;618 let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);619 for assert in &self.asserts.asserts {620 evaluate_assert(assert_env.clone(), assert)?;621 }622 Ok(())623 }624 }625 ObjectAssert { uctx, asserts }626}627fn evaluate_object_assertions_static(628 a_ctx: Context,629 asserts: Rc<LObjAsserts>,630) -> impl ObjectAssertion {631 #[derive(Trace)]632 struct ObjectAssert {633 assert_env: Context,634 asserts: Rc<LObjAsserts>,635 }636 impl ObjectAssertion for ObjectAssert {637 fn run(&self, _sup_this: SupThis) -> Result<()> {638 for assert in &self.asserts.asserts {639 evaluate_assert(self.assert_env.clone(), assert)?;640 }641 Ok(())642 }643 }644 let assert_env = Context::enter_using(&a_ctx, &asserts.shape);645 ObjectAssert {646 assert_env,647 asserts,648 }649}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}crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -10,7 +10,7 @@
use jrsonnet_gcmodule::{Acyclic, Cc, Trace, cc_dyn};
use jrsonnet_interner::IStr;
-use jrsonnet_ir::BinaryOpType;
+use jrsonnet_ir::{BinaryOpType, TrivialVal};
pub use jrsonnet_macros::Thunk;
use jrsonnet_types::ValType;
use rustc_hash::FxHashMap;
@@ -621,6 +621,16 @@
Self::Bool(value)
}
}
+impl From<TrivialVal> for Val {
+ fn from(tv: TrivialVal) -> Self {
+ match tv {
+ TrivialVal::Null => Self::Null,
+ TrivialVal::Bool(b) => Self::Bool(b),
+ TrivialVal::Num(n) => Self::Num(n),
+ TrivialVal::Str(s) => Self::string(s),
+ }
+ }
+}
const fn is_function_like(val: &Val) -> bool {
matches!(val, Val::Func(_))