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::{build_b_thunk_uno, evaluate_local_expr, 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;3435363738const RED_ZONE: usize = 100 * 1024;39404142const STACK_PER_RECURSION: usize = 1024 * 1024;43444546474849#[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 56 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 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}117118pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {119 Ok(match expr {120 LExpr::Null => Val::Null,121 LExpr::Bool(b) => Val::Bool(*b),122 LExpr::Str(s) => Val::string(s.clone()),123 LExpr::Num(n) => Val::Num(*n),124 LExpr::Slot(slot) => ctx.slot(*slot).evaluate()?,125 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),126 LExpr::Arr { shape, items } => Val::Arr(ArrValue::expr(ctx, shape, items.clone())),127 LExpr::UnaryOp(op, value) => {128 let value = evaluate(ctx, value)?;129 evaluate_unary_op(*op, &value)?130 }131 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,132 LExpr::LocalExpr(local_expr) => evaluate_local_expr(ctx, local_expr)?,133 LExpr::IfElse {134 cond,135 cond_then,136 cond_else,137 } => {138 let cond_val = evaluate(ctx.clone(), cond)?;139 let Val::Bool(b) = cond_val else {140 bail!(TypeMismatch(141 "if condition",142 vec![ValType::Bool],143 cond_val.value_type()144 ))145 };146 if b {147 evaluate(ctx, cond_then)?148 } else if let Some(e) = cond_else {149 evaluate(ctx, e)?150 } else {151 Val::Null152 }153 }154 LExpr::Error(s, e) => in_frame(155 CallLocation::new(s),156 || "error statement".to_owned(),157 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),158 )?,159 LExpr::AssertExpr { assert, rest } => {160 evaluate_assert(ctx.clone(), assert)?;161 evaluate(ctx, rest)?162 }163164 LExpr::Function(func) => evaluate_method(165 ctx,166 func.name.clone().unwrap_or_else(names::anonymous),167 func,168 ),169 LExpr::IdentityFunction => Val::Func(FuncVal::identity()),170 LExpr::Apply {171 applicable,172 args,173 tailstrict,174 } => evaluate_apply(175 ctx,176 applicable,177 args,178 CallLocation::new(&args.span),179 *tailstrict,180 )?,181 LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,182 LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,183 LExpr::ObjExtend(lhs, body) => {184 let lhs_val = evaluate(ctx.clone(), lhs)?;185 let Val::Obj(lhs_obj) = lhs_val else {186 bail!(TypeMismatch(187 "object extend lhs",188 vec![ValType::Obj],189 lhs_val.value_type(),190 ))191 };192 evaluate_obj_body(Some(lhs_obj), ctx, body)?193 }194 LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,195 LExpr::Slice(slice) => {196 let val = evaluate(ctx.clone(), &slice.value)?;197 let indexable = val.into_indexable()?;198 let start = slice199 .start200 .as_ref()201 .map(|e| evaluate(ctx.clone(), e))202 .transpose()?203 .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice start value") })204 .transpose()?;205 let end = slice206 .end207 .as_ref()208 .map(|e| evaluate(ctx.clone(), e))209 .transpose()?210 .map(|v| -> Result<i32> { i32::from_untyped(v).description("slice end value") })211 .transpose()?;212 let step = slice213 .step214 .as_ref()215 .map(|e| evaluate(ctx, e))216 .transpose()?217 .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {218 BoundedUsize::from_untyped(v).description("slice step value")219 })220 .transpose()?;221 Val::from(indexable.slice(start, end, step)?)222 }223 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super().ok_or(NoSuperFound)?),224 LExpr::Import {225 kind,226 kind_span,227 path,228 } => with_state(|state| {229 let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;230 Ok::<_, Error>(match kind.value {231 ImportKind::Normal => in_frame(232 CallLocation::new(&kind.span),233 || "import".to_string(),234 || state.import_resolved(resolved),235 )?,236 ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),237 ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),238 })239 })?,240 })241}242243fn evaluate_apply(244 ctx: Context,245 applicable: &LExpr,246 args: &LArgsDesc,247 loc: CallLocation<'_>,248 tailstrict: bool,249) -> Result<Val> {250 let func_val = evaluate(ctx.clone(), applicable)?;251 let Val::Func(func) = func_val else {252 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))253 };254255 if func.is_identity() && args.names.is_empty() && args.unnamed.len() == 1 {256 return evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?.evaluate();257 }258259 let name = func.name();260261 if args.names.is_empty() && args.unnamed.len() == 1 && func.params().len() == 1 {262 use crate::function::prepared::PreparedCall;263 let prepared_inline = PreparedCall::empty();264 let arg = evaluate_thunk(ctx, args.unnamed[0].clone(), tailstrict)?;265 let arg_slice = std::slice::from_ref(&arg);266 return in_frame(267 loc,268 || format!("function <{name}> call"),269 || {270 func.evaluate_prepared(271 &prepared_inline,272 CallLocation::native(),273 arg_slice,274 &[],275 tailstrict,276 )277 },278 );279 }280281 let unnamed = args282 .unnamed283 .iter()284 .cloned()285 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))286 .collect::<Result<Vec<_>>>()?;287288 289 290 if args.names.is_empty() && unnamed.len() == func.params().len() {291 use crate::function::prepared::PreparedCall;292 let prepared_inline = PreparedCall::empty();293 return in_frame(294 loc,295 || format!("function <{name}> call"),296 || {297 func.evaluate_prepared(298 &prepared_inline,299 CallLocation::native(),300 &unnamed,301 &[],302 tailstrict,303 )304 },305 );306 }307308 let named = args309 .values310 .iter()311 .cloned()312 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))313 .collect::<Result<Vec<_>>>()?;314 let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)315 .with_description_src(loc, || format!("function <{name}> preparation"))?;316 in_frame(317 loc,318 || format!("function <{name}> call"),319 || prepare.call(CallLocation::native(), &unnamed, &named),320 )321}322323fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {324 let mut parts = parts.iter();325 let mut indexable = if matches!(indexable, LExpr::Super) {326 let part = parts.next().expect("at least part should exist");327 328 329 let sup_this = ctx.try_sup_this()?;330331 if !sup_this.has_super() {332 #[cfg(feature = "exp-null-coaelse")]333 if part.null_coaelse {334 return Ok(Val::Null);335 }336 bail!(NoSuperFound);337 }338 let name = evaluate(ctx.clone(), &part.value)?;339340 let Val::Str(name) = name else {341 bail!(ValueIndexMustBeTypeGot(342 ValType::Obj,343 ValType::Str,344 name.value_type(),345 ))346 };347348 let name = name.into_flat();349 match sup_this350 .get_super(name.clone())351 .with_description_src(&part.span, || format!("super field <{name}> access"))?352 {353 Some(v) => v,354 #[cfg(feature = "exp-null-coaelse")]355 None if part.null_coaelse => return Ok(Val::Null),356 None => {357 let suggestions = suggest_object_fields(358 &sup_this.standalone_super().expect("super exists"),359 name.clone(),360 );361 bail!(NoSuchField(name, suggestions))362 }363 }364 } else {365 evaluate(ctx.clone(), indexable)?366 };367368 for part in parts {369 let ctx = ctx.clone();370 let loc = CallLocation::new(&part.span);371 let value = indexable;372 let key_val = evaluate(ctx, &part.value)?;373 indexable = match (&value, &key_val) {374 (Val::Obj(obj), Val::Str(key)) => {375 let key = key.clone().into_flat();376 match obj377 .get(key.clone())378 .with_description_src(loc, || format!("field <{key}> access"))?379 {380 Some(v) => v,381 #[cfg(feature = "exp-null-coaelse")]382 None if part.null_coaelse => return Ok(Val::Null),383 None => {384 return Err(Error::from(NoSuchField(385 key.clone(),386 suggest_object_fields(obj, key.clone()),387 )))388 .with_description_src(loc, || format!("field <{key}> access"));389 }390 }391 }392 (Val::Arr(arr), Val::Num(idx)) => {393 let n = idx.get();394 if n.fract() > f64::EPSILON {395 bail!(FractionalIndex)396 }397 let len = arr.len();398 if n < 0.0 || n > f64::from(len) {399 bail!(ArrayBoundsError(n, len));400 }401 #[expect(402 clippy::cast_possible_truncation,403 clippy::cast_sign_loss,404 reason = "n is checked positive"405 )]406 let i = n as u32;407 arr.get(i)408 .with_description_src(loc, || format!("element <{i}> access"))?409 .ok_or_else(|| ArrayBoundsError(n, len))?410 }411 (Val::Str(s), Val::Num(idx)) => {412 let n = idx.get();413 if n.fract() > f64::EPSILON {414 bail!(FractionalIndex)415 }416 #[expect(417 clippy::cast_possible_truncation,418 clippy::cast_sign_loss,419 reason = "n is checked positive, overflow will truncate as expected"420 )]421 let i = n as usize;422 let flat = s.clone().into_flat();423 #[allow(clippy::cast_possible_truncation, reason = "string is max 4g")]424 if n >= 0.0425 && n <= f64::from(u32::MAX)426 && let Some(char) = flat.chars().nth(i)427 {428 Val::string(char)429 } else {430 let len = flat.chars().count();431 bail!(StringBoundsError(n, len as u32))432 }433 }434 #[cfg(feature = "exp-null-coaelse")]435 (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),436 _ => bail!(ValueIndexMustBeTypeGot(437 value.value_type(),438 ValType::Str,439 key_val.value_type()440 )),441 };442 }443 Ok(indexable)444}445446fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {447 match body {448 LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),449 LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),450 }451}452453pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(454 builder: &mut ObjValueBuilder,455 ctx: Context,456 uctx: B,457 field: &LFieldMember,458) -> Result<()> {459 #[derive(Trace)]460 struct UnboundValue<B: Trace> {461 uctx: B,462 value: Rc<(ClosureShape, LExpr)>,463 name: IStr,464 }465 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {466 type Bound = Val;467 fn bind(&self, sup_this: SupThis) -> Result<Val> {468 let a_ctx = self.uctx.bind(sup_this)?;469 let b_ctx = Context::enter_using(&a_ctx, &self.value.0);470 evaluate(b_ctx, &self.value.1)471 }472 }473474 let LFieldMember {475 name,476 plus,477 visibility,478 value,479 } = field;480 let Some(name) = evaluate_field_name(ctx, name)? else {481 return Ok(());482 };483484 builder485 .field(name.clone())486 .with_add(*plus)487 .with_visibility(*visibility)488 .bindable(UnboundValue {489 uctx,490 value: value.clone(),491 name,492 })493}494pub fn evaluate_field_member_static(495 builder: &mut ObjValueBuilder,496 field_ctx: Context,497 value_ctx: Context,498 field: &LFieldMember,499) -> Result<()> {500 let LFieldMember {501 name,502 plus,503 visibility,504 value,505 } = field;506 let Some(name) = evaluate_field_name(field_ctx, name)? else {507 return Ok(());508 };509510 let thunk = build_b_thunk_uno(&value_ctx, value.clone());511 builder512 .field(name)513 .with_add(*plus)514 .with_visibility(*visibility)515 .try_thunk(thunk)?;516 Ok(())517}518519fn evaluate_obj_members(520 super_obj: Option<ObjValue>,521 ctx: Context,522 members: &LObjMembers,523) -> Result<Val> {524 let mut builder = ObjValueBuilder::with_capacity(members.fields.len());525 if let Some(sup) = super_obj {526 builder.with_super(sup);527 }528529 let needs_unbound = members.this.is_some() || members.uses_super;530531 if needs_unbound {532 let uctx = CachedUnbound::new(evaluate_locals_unbound(533 &ctx,534 &members.frame_shape,535 members.this,536 members.locals.clone(),537 ));538 for field in &members.fields {539 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;540 }541 if let Some(asserts_block) = &members.asserts {542 builder.assert(evaluate_object_assertions_unbound(543 uctx,544 asserts_block.clone(),545 ));546 }547 } else {548 let a_ctx = ctx549 .pack_captures_sup_this(&members.frame_shape)550 .enter(|fill, ctx| {551 fill_letrec_binds(fill, ctx, &members.locals);552 });553 for field in &members.fields {554 evaluate_field_member_static(&mut builder, ctx.clone(), a_ctx.clone(), field)?;555 }556 if let Some(asserts_block) = &members.asserts {557 builder.assert(evaluate_object_assertions_static(558 a_ctx,559 asserts_block.clone(),560 ));561 }562 }563564 Ok(Val::Obj(builder.build()))565}566567pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {568 let LAssertStmt { cond, message } = assertion;569 let assertion_result = in_frame(570 CallLocation::new(&cond.span),571 || "assertion condition".to_owned(),572 || bool::from_untyped(evaluate(ctx.clone(), cond)?),573 )?;574 if !assertion_result {575 in_frame(576 CallLocation::new(&cond.span),577 || "assertion failure".to_owned(),578 || {579 if let Some(msg) = message {580 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));581 }582 bail!(AssertionFailed(Val::Null.to_string()?));583 },584 )?;585 }586 Ok(())587}588589fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(590 uctx: B,591 asserts: Rc<LObjAsserts>,592) -> impl ObjectAssertion {593 #[derive(Trace)]594 struct ObjectAssert<B: Trace> {595 uctx: B,596 asserts: Rc<LObjAsserts>,597 }598 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {599 fn run(&self, sup_this: SupThis) -> Result<()> {600 let a_ctx = self.uctx.bind(sup_this)?;601 let assert_env = Context::enter_using(&a_ctx, &self.asserts.shape);602 for assert in &self.asserts.asserts {603 evaluate_assert(assert_env.clone(), assert)?;604 }605 Ok(())606 }607 }608 ObjectAssert { uctx, asserts }609}610fn evaluate_object_assertions_static(611 a_ctx: Context,612 asserts: Rc<LObjAsserts>,613) -> impl ObjectAssertion {614 #[derive(Trace)]615 struct ObjectAssert {616 assert_env: Context,617 asserts: Rc<LObjAsserts>,618 }619 impl ObjectAssertion for ObjectAssert {620 fn run(&self, _sup_this: SupThis) -> Result<()> {621 for assert in &self.asserts.asserts {622 evaluate_assert(self.assert_env.clone(), assert)?;623 }624 Ok(())625 }626 }627 let assert_env = Context::enter_using(&a_ctx, &asserts.shape);628 ObjectAssert {629 assert_env,630 asserts,631 }632}