difftreelog
refactor(evaluator) use static analysis
in: master
31 files changed
Cargo.lockdiffbeforeafterboth815dependencies = [815dependencies = [816 "annotate-snippets",816 "annotate-snippets",817 "anyhow",817 "anyhow",818 "drop_bomb",818 "educe",819 "educe",819 "hi-doc",820 "hi-doc",820 "im-rc",821 "im-rc",822 "insta",821 "jrsonnet-gcmodule",823 "jrsonnet-gcmodule",822 "jrsonnet-interner",824 "jrsonnet-interner",823 "jrsonnet-ir",825 "jrsonnet-ir",830 "rustc-hash 2.1.2",832 "rustc-hash 2.1.2",831 "rustversion",833 "rustversion",832 "serde",834 "serde",835 "smallvec 1.15.1",833 "stacker",836 "stacker",834 "static_assertions",837 "static_assertions",838 "strip-ansi-escapes",835 "strsim",839 "strsim",836 "thiserror",840 "thiserror",837]841]899 "jrsonnet-interner",903 "jrsonnet-interner",900 "peg",904 "peg",901 "static_assertions",905 "static_assertions",906 "thiserror",902]907]903908904[[package]]909[[package]]960 "base64",965 "base64",961 "jrsonnet-evaluator",966 "jrsonnet-evaluator",962 "jrsonnet-gcmodule",967 "jrsonnet-gcmodule",963 "jrsonnet-ir",964 "jrsonnet-macros",968 "jrsonnet-macros",965 "lru",969 "lru",966 "md5",970 "md5",bindings/jsonnet/src/val_modify.rsdiffbeforeafterboth445use std::{ffi::CStr, os::raw::c_char};5use std::{ffi::CStr, os::raw::c_char};667use jrsonnet_evaluator::{Thunk, Val, val::ArrValue};7use jrsonnet_evaluator::{Thunk, Val};889use crate::VM;9use crate::VM;1010crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth77 "PartialEq",77 "PartialEq",78] }78] }79im-rc = { version = "15.1.0", features = ["pool"] }79im-rc = { version = "15.1.0", features = ["pool"] }80smallvec = "1.15.1"81drop_bomb.workspace = true808281[build-dependencies]83[build-dependencies]82rustversion = "1.0.22"84rustversion = "1.0.22"8586[dev-dependencies]87insta.workspace = true88strip-ansi-escapes = "0.2.1"8389crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth5 rc::Rc,5 rc::Rc,6};6};778use jrsonnet_gcmodule::{Cc, cc_dyn};8use jrsonnet_gcmodule::{cc_dyn, Cc};9use jrsonnet_ir::Expr;10911use crate::{Context, Result, Thunk, Val, function::NativeFn, typed::IntoUntyped};10use crate::{analyze::LExpr, function::NativeFn, typed::IntoUntyped, Context, Result, Thunk, Val};121113mod spec;12mod spec;14pub use spec::{ArrayLike, *};13pub use spec::{ArrayLike, *};37 Self::new(())36 Self::new(())38 }37 }393840 pub fn expr(ctx: Context, exprs: Rc<Vec<Expr>>) -> Self {39 pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {41 Self::new(ExprArray::new(ctx, exprs))40 Self::new(ExprArray::new(ctx, exprs))42 }41 }434244 pub fn repeated(data: Self, repeats: usize) -> Option<Self> {43 pub fn repeated(data: Self, repeats: u32) -> Option<Self> {45 Some(Self::new(RepeatedArray::new(data, repeats)?))44 Some(Self::new(RepeatedArray::new(data, repeats)?))46 }45 }4647 pub fn make(len: u32, cb: NativeFn!((u32,)->Val)) -> Self {48 Self::new(MakeArray::new(len, cb))49 }475048 #[must_use]51 #[must_use]49 pub fn map(self, mapper: NativeFn!((Val) -> Val)) -> Self {52 pub fn map(self, mapper: NativeFn!((Val) -> Val)) -> Self {79 Ok(Self::new(out))82 Ok(Self::new(out))80 }83 }818482 pub fn extended(a: Self, b: Self) -> Self {85 pub fn extended(a: Self, b: Self) -> Option<Self> {83 if a.is_empty() {86 Some(if a.is_empty() {84 b87 b85 } else if b.is_empty() {88 } else if b.is_empty() {86 a89 a87 } else {90 } else {88 Self::new(ExtendedArray::new(a, b))91 Self::new(ExtendedArray::new(a, b)?)89 }92 })90 }93 }919492 pub fn range_exclusive(a: i32, b: i32) -> Self {95 pub fn range_exclusive(a: i32, b: i32) -> Self {9810199 #[must_use]102 #[must_use]100 pub fn slice(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {103 pub fn slice(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {101 let get_idx = |pos: Option<i32>, len: usize, default| match pos {104 let get_idx = |pos: Option<i32>, len: u32, default| match pos {102 #[expect(105 #[expect(103 clippy::cast_sign_loss,106 clippy::cast_sign_loss,104 reason = "abs value is used, len is limited to u31"107 reason = "abs value is used, len is limited to u31"105 )]108 )]106 Some(v) if v < 0 => len.saturating_sub((-v) as usize),109 Some(v) if v < 0 => len.saturating_add_signed(v),107 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]110 #[expect(clippy::cast_sign_loss, reason = "abs value is used")]108 Some(v) => (v as usize).min(len),111 Some(v) => (v as u32).min(len),109 None => default,112 None => default,110 };113 };111 let index = get_idx(index, self.len(), 0);114 let index = get_idx(index, self.len(), 0);127 }130 }128131129 /// Array length.132 /// Array length.130 pub fn len(&self) -> usize {133 pub fn len(&self) -> u32 {131 self.0.len()134 self.0.len()132 }135 }133136143 /// Get array element by index, evaluating it, if it is lazy.146 /// Get array element by index, evaluating it, if it is lazy.144 ///147 ///145 /// Returns `None` on out-of-bounds condition.148 /// Returns `None` on out-of-bounds condition.146 pub fn get(&self, index: usize) -> Result<Option<Val>> {149 pub fn get(&self, index: u32) -> Result<Option<Val>> {147 self.0.get(index)150 self.0.get(index)148 }151 }149152150 /// Get array element by index, without evaluation.153 /// Get array element by index, without evaluation.151 ///154 ///152 /// Returns `None` on out-of-bounds condition.155 /// Returns `None` on out-of-bounds condition.153 pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {156 pub fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {154 self.0.get_lazy(index)157 self.0.get_lazy(index)155 }158 }156159crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth889use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_gcmodule::{Cc, Trace};10use jrsonnet_interner::{IBytes, IStr};10use jrsonnet_interner::{IBytes, IStr};11use jrsonnet_ir::Expr;121113use super::ArrValue;12use super::ArrValue;14use crate::{13use crate::{15 Context, Error, ObjValue, Result, Thunk, Val,14 analyze::LExpr,16 error::ErrorKind::InfiniteRecursionDetected,15 error::ErrorKind::InfiniteRecursionDetected,17 evaluate,16 evaluate::evaluate,18 function::NativeFn,17 function::NativeFn,19 typed::{IntoUntyped, Typed},18 typed::{IntoUntyped, Typed},20 val::ThunkValue,19 val::ThunkValue,20 Context, Error, ObjValue, Result, Thunk, Val,21};21};222223pub trait ArrayLike: Any + Trace + Debug {23pub trait ArrayLike: Any + Trace + Debug {24 fn len(&self) -> usize;24 fn len(&self) -> u32;25 fn is_empty(&self) -> bool {25 fn is_empty(&self) -> bool {26 self.len() == 026 self.len() == 027 }27 }28 fn get(&self, index: usize) -> Result<Option<Val>>;28 fn get(&self, index: u32) -> Result<Option<Val>>;29 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;29 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>>;303031 fn is_cheap(&self) -> bool {31 fn is_cheap(&self) -> bool {32 false32 false33 }33 }34}34}35trait ArrayCheap {35trait ArrayCheap {36 fn get(&self, index: usize) -> Option<Val>;36 fn get(&self, index: u32) -> Option<Val>;37 fn len(&self) -> usize;37 fn len(&self) -> u32;38}38}39impl<T> ArrayLike for T39impl<T> ArrayLike for T40where40where41 T: Any + Trace + Debug + ArrayCheap,41 T: Any + Trace + Debug + ArrayCheap,42{42{43 fn len(&self) -> usize {43 fn len(&self) -> u32 {44 <T as ArrayCheap>::len(self)44 <T as ArrayCheap>::len(self)45 }45 }464647 fn get(&self, index: usize) -> Result<Option<Val>> {47 fn get(&self, index: u32) -> Result<Option<Val>> {48 Ok(<T as ArrayCheap>::get(self, index))48 Ok(<T as ArrayCheap>::get(self, index))49 }49 }505051 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {51 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {52 <T as ArrayCheap>::get(self, index).map(Thunk::evaluated)52 <T as ArrayCheap>::get(self, index).map(Thunk::evaluated)53 }53 }545458}58}595960impl ArrayCheap for () {60impl ArrayCheap for () {61 fn len(&self) -> usize {61 fn len(&self) -> u32 {62 062 063 }63 }64 fn get(&self, _index: usize) -> Option<Val> {64 fn get(&self, _index: u32) -> Option<Val> {65 None65 None66 }66 }67}67}75}75}767677impl SliceArray {77impl SliceArray {78 fn map_idx(&self, index: usize) -> usize {78 fn map_idx(&self, index: u32) -> u32 {79 self.from as usize + self.step as usize * index79 self.from + self.step * index80 }80 }81}81}82impl ArrayLike for SliceArray {82impl ArrayLike for SliceArray {83 fn len(&self) -> usize {83 fn len(&self) -> u32 {84 (self.to - self.from).div_ceil(self.step) as usize84 (self.to - self.from).div_ceil(self.step)85 }85 }868687 fn get(&self, index: usize) -> Result<Option<Val>> {87 fn get(&self, index: u32) -> Result<Option<Val>> {88 self.inner.get(self.map_idx(index))88 self.inner.get(self.map_idx(index))89 }89 }909091 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {91 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {92 self.inner.get_lazy(self.map_idx(index))92 self.inner.get_lazy(self.map_idx(index))93 }93 }949498}98}9999100impl ArrayCheap for IBytes {100impl ArrayCheap for IBytes {101 fn len(&self) -> usize {101 fn len(&self) -> u32 {102 self.as_slice().len()102 self.as_slice().len() as u32103 }103 }104 fn get(&self, index: usize) -> Option<Val> {104 fn get(&self, index: u32) -> Option<Val> {105 self.as_slice().get(index).map(|v| Val::Num((*v).into()))105 self.as_slice()106 .get(index as usize)107 .map(|v| Val::Num((*v).into()))106 }108 }107}109}117#[derive(Debug, Trace, Clone)]119#[derive(Debug, Trace, Clone)]118pub struct ExprArray {120pub struct ExprArray {119 ctx: Context,121 ctx: Context,120 src: Rc<Vec<Expr>>,122 src: Rc<Vec<LExpr>>,121 cached: Cc<RefCell<Vec<ArrayThunk>>>,123 cached: Cc<RefCell<Vec<ArrayThunk>>>,122}124}123impl ExprArray {125impl ExprArray {124 pub fn new(ctx: Context, src: Rc<Vec<Expr>>) -> Self {126 pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {125 Self {127 Self {126 ctx,128 ctx,127 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),129 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),130 }132 }131}133}132impl ArrayLike for ExprArray {134impl ArrayLike for ExprArray {133 fn len(&self) -> usize {135 fn len(&self) -> u32 {134 self.cached.borrow().len()136 self.cached.borrow().len() as u32135 }137 }136 fn get(&self, index: usize) -> Result<Option<Val>> {138 fn get(&self, index: u32) -> Result<Option<Val>> {137 if index >= self.len() {139 if index >= self.len() {138 return Ok(None);140 return Ok(None);139 }141 }140 match &self.cached.borrow()[index] {142 match &self.cached.borrow()[index as usize] {141 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),143 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),142 ArrayThunk::Errored(e) => return Err(e.clone()),144 ArrayThunk::Errored(e) => return Err(e.clone()),143 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),145 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),144 ArrayThunk::Waiting => {}146 ArrayThunk::Waiting => {}145 }147 }146148147 let ArrayThunk::Waiting =149 let ArrayThunk::Waiting = replace(148 replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)150 &mut self.cached.borrow_mut()[index as usize],151 ArrayThunk::Pending,149 else {152 ) else {150 unreachable!()153 unreachable!()151 };154 };152155153 let new_value = match evaluate(self.ctx.clone(), &self.src[index]) {156 let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;154 Ok(v) => v,155 Err(e) => {156 self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());157 return Err(e);158 }159 };160 self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());157 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());161 Ok(Some(new_value))158 Ok(Some(new_value))162 }159 }163 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {160 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {164 #[derive(Trace)]161 #[derive(Trace)]165 struct ExprArrThunk {162 struct ExprArrThunk {166 expr: ExprArray,163 expr: ExprArray,167 index: usize,164 index: u32,168 }165 }169 impl ThunkValue for ExprArrThunk {166 impl ThunkValue for ExprArrThunk {170 type Output = Val;167 type Output = Val;180 if index >= self.len() {177 if index >= self.len() {181 return None;178 return None;182 }179 }183 match &self.cached.borrow()[index] {180 match &self.cached.borrow()[index as usize] {184 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),181 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),185 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),182 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),186 ArrayThunk::Waiting | ArrayThunk::Pending => {}183 ArrayThunk::Waiting | ArrayThunk::Pending => {}200pub struct ExtendedArray {197pub struct ExtendedArray {201 pub a: ArrValue,198 pub a: ArrValue,202 pub b: ArrValue,199 pub b: ArrValue,203 split: usize,200 split: u32,204 len: usize,201 len: u32,205}202}206impl ExtendedArray {203impl ExtendedArray {207 pub fn new(a: ArrValue, b: ArrValue) -> Self {204 pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {208 let a_len = a.len();205 let a_len = a.len();209 let b_len = b.len();206 let b_len = b.len();207 let len = a_len.checked_add(b_len)?;210 Self {208 Some(Self {211 a,209 a,212 b,210 b,213 split: a_len,211 split: a_len,214 len: a_len.checked_add(b_len).expect("too large array value"),212 len,215 }213 })216 }214 }217}215}218216253 }251 }254}252}255impl ArrayLike for ExtendedArray {253impl ArrayLike for ExtendedArray {256 fn get(&self, index: usize) -> Result<Option<Val>> {254 fn get(&self, index: u32) -> Result<Option<Val>> {257 if self.split > index {255 if self.split > index {258 self.a.get(index)256 self.a.get(index)259 } else {257 } else {260 self.b.get(index - self.split)258 self.b.get(index - self.split)261 }259 }262 }260 }263 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {261 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {264 if self.split > index {262 if self.split > index {265 self.a.get_lazy(index)263 self.a.get_lazy(index)266 } else {264 } else {267 self.b.get_lazy(index - self.split)265 self.b.get_lazy(index - self.split)268 }266 }269 }267 }270268271 fn len(&self) -> usize {269 fn len(&self) -> u32 {272 self.len270 self.len273 }271 }274272282 T: IntoUntyped + Trace + fmt::Debug,280 T: IntoUntyped + Trace + fmt::Debug,283 for<'a> &'a T: IntoUntyped,281 for<'a> &'a T: IntoUntyped,284{282{285 fn len(&self) -> usize {283 fn len(&self) -> u32 {286 self.as_slice().len()284 self.as_slice().len().try_into().unwrap_or(u32::MAX)287 }285 }288286289 fn get(&self, index: usize) -> Result<Option<Val>> {287 fn get(&self, index: u32) -> Result<Option<Val>> {290 let Some(elem) = self.as_slice().get(index) else {288 let Some(elem) = self.as_slice().get(index as usize) else {291 return Ok(None);289 return Ok(None);292 };290 };293 IntoUntyped::into_untyped(elem).map(Some)291 IntoUntyped::into_untyped(elem).map(Some)294 }292 }295293296 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {294 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {297 let elem = self.as_slice().get(index)?;295 let elem = self.as_slice().get(index as usize)?;298 Some(IntoUntyped::into_lazy_untyped(elem))296 Some(IntoUntyped::into_lazy_untyped(elem))299 }297 }300298324 clippy::cast_sign_loss,322 clippy::cast_sign_loss,325 reason = "the math is valid with wrapping, sign loss works as intended"323 reason = "the math is valid with wrapping, sign loss works as intended"326 )]324 )]327 fn size(&self) -> usize {325 fn size(&self) -> u32 {328 (self.end as usize)326 (self.end as u32)329 .wrapping_sub(self.start as usize)327 .wrapping_sub(self.start as u32)330 .wrapping_add(1)328 .wrapping_add(1)331 }329 }332 fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {330 fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {333 WithExactSize(self.start..=self.end, self.size())331 WithExactSize(self.start..=self.end, self.size() as usize)334 }332 }335}333}336impl ArrayCheap for RangeArray {334impl ArrayCheap for RangeArray {337 fn get(&self, index: usize) -> Option<Val> {335 fn get(&self, index: u32) -> Option<Val> {338 self.range().nth(index).map(|i| Val::Num(i.into()))336 self.range().nth(index as usize).map(|i| Val::Num(i.into()))339 }337 }340 fn len(&self) -> usize {338 fn len(&self) -> u32 {341 self.size()339 self.size()342 }340 }343}341}344342345#[derive(Debug, Trace)]343#[derive(Debug, Trace)]346pub struct ReverseArray(pub ArrValue);344pub struct ReverseArray(pub ArrValue);347impl ArrayLike for ReverseArray {345impl ArrayLike for ReverseArray {348 fn len(&self) -> usize {346 fn len(&self) -> u32 {349 self.0.len()347 self.0.len()350 }348 }351349352 fn get(&self, index: usize) -> Result<Option<Val>> {350 fn get(&self, index: u32) -> Result<Option<Val>> {353 self.0.get(self.0.len() - index - 1)351 self.0.get(self.0.len() - index - 1)354 }352 }355353356 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {354 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {357 self.0.get_lazy(self.0.len() - index - 1)355 self.0.get_lazy(self.0.len() - index - 1)358 }356 }359357379 let len = inner.len();377 let len = inner.len();380 Self {378 Self {381 inner,379 inner,382 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len])),380 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),383 mapper,381 mapper,384 }382 }385 }383 }386 fn evaluate(&self, index: usize, value: Val) -> Result<Val> {384 fn evaluate(&self, index: u32, value: Val) -> Result<Val> {387 match &self.mapper {385 match &self.mapper {388 ArrayMapper::Plain(f) => f.call(value),386 ArrayMapper::Plain(f) => f.call(value),389 #[expect(390 clippy::cast_possible_truncation,391 reason = "array len is limited to u31"392 )]393 ArrayMapper::WithIndex(f) => f.call(index as u32, value),387 ArrayMapper::WithIndex(f) => f.call(index, value),394 }388 }395 }389 }396}390}397impl ArrayLike for MappedArray {391impl ArrayLike for MappedArray {398 fn len(&self) -> usize {392 fn len(&self) -> u32 {399 self.cached.borrow().len()393 self.cached.borrow().len() as u32400 }394 }401395402 fn get(&self, index: usize) -> Result<Option<Val>> {396 fn get(&self, index: u32) -> Result<Option<Val>> {403 if index >= self.len() {397 if index >= self.len() {404 return Ok(None);398 return Ok(None);405 }399 }406 match &self.cached.borrow()[index] {400 match &self.cached.borrow()[index as usize] {407 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),401 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),408 ArrayThunk::Errored(e) => return Err(e.clone()),402 ArrayThunk::Errored(e) => return Err(e.clone()),409 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),403 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),410 ArrayThunk::Waiting => {}404 ArrayThunk::Waiting => {}411 }405 }412406413 let ArrayThunk::Waiting =407 let ArrayThunk::Waiting = replace(414 replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)408 &mut self.cached.borrow_mut()[index as usize],409 ArrayThunk::Pending,415 else {410 ) else {416 unreachable!()411 unreachable!()426 let new_value = match val {421 let new_value = match val {427 Ok(v) => v,422 Ok(v) => v,428 Err(e) => {423 Err(e) => {429 self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());424 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());430 return Err(e);425 return Err(e);431 }426 }432 };427 };433 self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());428 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());434 Ok(Some(new_value))429 Ok(Some(new_value))435 }430 }436 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {431 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {437 #[derive(Trace)]432 #[derive(Trace)]438 struct MappedArrayThunk {433 struct MappedArrayThunk {439 arr: MappedArray,434 arr: MappedArray,440 index: usize,435 index: u32,441 }436 }442 impl ThunkValue for MappedArrayThunk {437 impl ThunkValue for MappedArrayThunk {443 type Output = Val;438 type Output = Val;450 if index >= self.len() {445 if index >= self.len() {451 return None;446 return None;452 }447 }453 match &self.cached.borrow()[index] {448 match &self.cached.borrow()[index as usize] {454 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),449 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),455 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),450 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),456 ArrayThunk::Waiting | ArrayThunk::Pending => {}451 ArrayThunk::Waiting | ArrayThunk::Pending => {}462 }))457 }))463 }458 }464}459}460#[derive(Trace, Debug, Clone)]461pub struct MakeArray {462 cached: Cc<RefCell<Vec<ArrayThunk>>>,463 mapper: NativeFn!((u32,)->Val),464}465impl MakeArray {466 pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {467 Self {468 cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),469 mapper,470 }471 }472}473impl ArrayLike for MakeArray {474 fn len(&self) -> u32 {475 self.cached.borrow().len() as u32476 }477478 fn get(&self, index: u32) -> Result<Option<Val>> {479 if index >= self.len() {480 return Ok(None);481 }482 match &self.cached.borrow()[index as usize] {483 ArrayThunk::Computed(c) => return Ok(Some(c.clone())),484 ArrayThunk::Errored(e) => return Err(e.clone()),485 ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),486 ArrayThunk::Waiting => {}487 }488489 let ArrayThunk::Waiting = replace(490 &mut self.cached.borrow_mut()[index as usize],491 ArrayThunk::Pending,492 ) else {493 unreachable!()494 };495496 let val = self.mapper.call(index as u32);497498 let new_value = match val {499 Ok(v) => v,500 Err(e) => {501 self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());502 return Err(e);503 }504 };505 self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());506 Ok(Some(new_value))507 }508 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {509 #[derive(Trace)]510 struct MakeArrayThunk {511 arr: MakeArray,512 index: u32,513 }514 impl ThunkValue for MakeArrayThunk {515 type Output = Val;516517 fn get(&self) -> Result<Self::Output> {518 self.arr.get(self.index).transpose().expect("index checked")519 }520 }521522 if index >= self.len() {523 return None;524 }525 match &self.cached.borrow()[index as usize] {526 ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),527 ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),528 ArrayThunk::Waiting | ArrayThunk::Pending => {}529 }530531 Some(Thunk::new(MakeArrayThunk {532 arr: self.clone(),533 index,534 }))535 }536}465537466#[derive(Trace, Debug)]538#[derive(Trace, Debug)]467pub struct RepeatedArray {539pub struct RepeatedArray {468 data: ArrValue,540 data: ArrValue,469 repeats: usize,541 repeats: u32,470 total_len: usize,542 total_len: u32,471}543}472impl RepeatedArray {544impl RepeatedArray {473 pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {545 pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {474 let total_len = data.len().checked_mul(repeats)?;546 let total_len = data.len().checked_mul(repeats)?;475 Some(Self {547 Some(Self {476 data,548 data,477 repeats,549 repeats,478 total_len,550 total_len,479 })551 })480 }552 }481 fn map_idx(&self, index: usize) -> Option<usize> {553 fn map_idx(&self, index: u32) -> Option<u32> {482 if index > self.total_len {554 if index > self.total_len {483 return None;555 return None;484 }556 }487}559}488560489impl ArrayLike for RepeatedArray {561impl ArrayLike for RepeatedArray {490 fn len(&self) -> usize {562 fn len(&self) -> u32 {491 self.total_len563 self.total_len492 }564 }493565494 fn get(&self, index: usize) -> Result<Option<Val>> {566 fn get(&self, index: u32) -> Result<Option<Val>> {495 let Some(idx) = self.map_idx(index) else {567 let Some(idx) = self.map_idx(index) else {496 return Ok(None);568 return Ok(None);497 };569 };498 self.data.get(idx)570 self.data.get(idx)499 }571 }500572501 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {573 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {502 let idx = self.map_idx(index)?;574 let idx = self.map_idx(index)?;503 self.data.get_lazy(idx)575 self.data.get_lazy(idx)504 }576 }521}593}522594523impl ArrayLike for PickObjectValues {595impl ArrayLike for PickObjectValues {524 fn len(&self) -> usize {596 fn len(&self) -> u32 {525 self.keys.len()597 self.keys.len() as u32526 }598 }527599528 fn get(&self, index: usize) -> Result<Option<Val>> {600 fn get(&self, index: u32) -> Result<Option<Val>> {529 let Some(key) = self.keys.as_slice().get(index) else {601 let Some(key) = self.keys.as_slice().get(index as usize) else {530 return Ok(None);602 return Ok(None);531 };603 };532 Ok(Some(self.obj.get_or_bail(key.clone())?))604 Ok(Some(self.obj.get_or_bail(key.clone())?))533 }605 }534606535 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {607 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {536 let key = self.keys.as_slice().get(index)?;608 let key = self.keys.as_slice().get(index as usize)?;537 Some(self.obj.get_lazy_or_bail(key.clone()))609 Some(self.obj.get_lazy_or_bail(key.clone()))538 }610 }539611561}633}562634563impl ArrayLike for PickObjectKeyValues {635impl ArrayLike for PickObjectKeyValues {564 fn len(&self) -> usize {636 fn len(&self) -> u32 {565 self.keys.len()637 self.keys.len() as u32566 }638 }567639568 fn get(&self, index: usize) -> Result<Option<Val>> {640 fn get(&self, index: u32) -> Result<Option<Val>> {569 let Some(key) = self.keys.as_slice().get(index) else {641 let Some(key) = self.keys.as_slice().get(index as usize) else {570 return Ok(None);642 return Ok(None);571 };643 };572 Ok(Some(644 Ok(Some(578 ))650 ))579 }651 }580652581 fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {653 fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {582 let key = self.keys.as_slice().get(index)?;654 let key = self.keys.as_slice().get(index as usize)?;583 // Nothing can fail in the key part, yet value is still655 // Nothing can fail in the key part, yet value is still584 // lazy-evaluated656 // lazy-evaluated585 Some(Thunk::evaluated(657 Some(Thunk::evaluated(crates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth1use std::fmt::Debug;1use std::{clone::Clone, fmt::Debug};223use educe::Educe;3use educe::Educe;4use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;5use jrsonnet_interner::IStr;6use rustc_hash::{FxHashMap, FxHashSet};768use crate::{7use crate::{analyze::LocalId, error, error::ErrorKind::*, Pending, Result, SupThis, Thunk, Val};9 ObjValue, Pending, Result, SupThis, Thunk, Val, bail, error::ErrorKind::*,810 gc::WithCapacityExt as _,11};12/// Context keeps information about current lexical code location13///14/// This information includes local variables, top-level object (`$`), current object (`this`), and super object (`super`)15#[derive(Debug, Trace, Clone, Educe)]9#[derive(Debug, Trace, Clone, Educe)]16#[educe(PartialEq)]10#[educe(PartialEq)]17pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] Cc<ContextInternal>);11pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] Cc<ContextInternal>);181219#[derive(Debug, Trace)]13#[derive(Debug, Trace, Clone)]20struct ContextInternal {14struct ContextInternal {21 dollar: Option<ObjValue>,22 sup_this: Option<SupThis>,15 sup_this: Option<SupThis>,16 /// `bindings[i]` corresponds to `LocalId(offset + i)`.23 bindings: FxHashMap<IStr, Thunk<Val>>,17 bindings: Vec<Option<Thunk<Val>>>,2418 offset: u32,25 branch_point: Option<Context>,19 parent: Option<Context>,26}20}2127impl Context {22impl Context {28 pub fn new_future() -> Pending<Self> {23 pub fn new_future() -> Pending<Self> {29 Pending::new()24 Pending::new()30 }25 }312632 pub fn dollar(&self) -> Option<&ObjValue> {27 pub fn sup_this(&self) -> Option<&SupThis> {33 self.0.dollar.as_ref()28 self.0.sup_this.as_ref()34 }29 }353036 pub fn try_dollar(&self) -> Result<ObjValue> {31 pub fn try_sup_this(&self) -> Result<SupThis> {37 self.032 self.038 .dollar33 .sup_this39 .clone()34 .clone()40 .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())35 .ok_or_else(|| error!(CantUseSelfSupOutsideOfObject))41 }36 }423738 /// Update binding in `CoW` fashion. Only useful for eager comprehension39 /// fast-path, as it requires Cc refcount to be 1; Use `ContextBuilder` otherwise.43 pub fn this(&self) -> Option<&ObjValue> {40 pub(crate) fn cow_fill_binding(&mut self, id: LocalId, value: Thunk<Val>) {44 self.0.sup_this.as_ref().map(SupThis::this)41 let mut value = Some(Some(value));45 }4647 pub fn try_this(&self) -> Result<ObjValue> {48 self.049 .sup_this50 .as_ref()51 .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())52 .map(SupThis::this)53 .cloned()54 }5556 pub fn sup_this(&self) -> Option<&SupThis> {57 self.0.sup_this.as_ref()58 }594260 pub fn try_sup_this(&self) -> Result<SupThis> {43 self.0.update_with(|inner| {61 self.044 let local_idx = (id.0 - inner.offset) as usize;62 .sup_this63 .clone()64 .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())45 while inner.bindings.len() <= local_idx {65 }46 inner.bindings.push(None);47 }48 inner.bindings[local_idx] = value.take().expect("called once");49 });50 }665167 pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {52 pub fn binding(&self, id: LocalId) -> Option<Thunk<Val>> {68 use std::cmp::Ordering;53 let id_num = id.0;6954 if id_num >= self.0.offset {70 use crate::bail;55 let local_idx = (id_num - self.0.offset) as usize;7172 if let Some(val) = self.0.bindings.get(&name).cloned() {56 if let Some(Some(thunk)) = self.0.bindings.get(local_idx) {73 return Ok(val);57 return Some(thunk.clone());74 }58 }7559 }76 if let Some(branch_point) = &self.0.branch_point {60 if let Some(parent) = &self.0.parent {77 return branch_point.binding(name);61 return parent.binding(id);78 }62 }7963 None80 let mut heap = Vec::new();81 for k in self.0.bindings.keys() {82 let conf = strsim::jaro_winkler(k as &str, &name as &str);83 if conf < 0.8 {84 continue;85 }86 heap.push((conf, k.clone()));87 }88 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));8990 bail!(VariableIsNotDefined(91 name,92 heap.into_iter().map(|(_, k)| k).collect()93 ))94 }64 }95 pub fn contains_binding(&self, name: IStr) -> bool {6596 self.0.bindings.contains_key(&name)97 }98 #[must_use]66 #[must_use]99 pub fn into_future(self, ctx: Pending<Self>) -> Self {67 pub fn into_future(self, ctx: Pending<Self>) -> Self {100 {68 {103 ctx.unwrap()71 ctx.unwrap()104 }72 }105106 #[must_use]107 pub fn branch_point(self) -> Self {108 if self.0.bindings.is_empty() {109 self110 } else {111 ContextBuilder::extend(self).build()112 }113 }114}73}11574116#[derive(Clone)]75#[derive(Clone)]117pub struct ContextBuilder {76pub struct ContextBuilder {118 dollar: Option<ObjValue>,119 sup_this: Option<SupThis>,77 sup_this: Option<SupThis>,120 bindings: FxHashMap<IStr, Thunk<Val>>,78 bindings: Vec<Option<Thunk<Val>>>,121 filled: FxHashSet<IStr>,79 offset: u32,122 branch_point: Option<Context>,80 parent: Option<Context>,123}81}12482125impl ContextBuilder {83impl ContextBuilder {126 pub fn new() -> Self {84 pub fn new() -> Self {127 Self {85 Self {128 dollar: None,129 sup_this: None,86 sup_this: None,130 bindings: FxHashMap::new(),87 bindings: Vec::new(),131 filled: FxHashSet::new(),88 offset: 0,132 branch_point: None,89 parent: None,133 }90 }134 }91 }13592136 pub fn extend_fast(parent: Context) -> Self {93 pub(crate) fn extend(parent: Context, capacity: usize) -> Self {137 Self {94 let offset = parent.0.offset + parent.0.bindings.len() as u32;138 dollar: parent.0.dollar.clone(),139 sup_this: parent.0.sup_this.clone(),140 bindings: parent.0.bindings.clone(),141 filled: FxHashSet::new(),142 branch_point: parent.0.branch_point.clone(),143 }144 }145146 pub fn extend(parent: Context) -> Self {147 Self {95 Self {148 dollar: parent.0.dollar.clone(),149 sup_this: parent.0.sup_this.clone(),96 sup_this: parent.0.sup_this.clone(),150 bindings: FxHashMap::new(),97 bindings: Vec::with_capacity(capacity),151 filled: FxHashSet::new(),98 offset,152 branch_point: Some(parent.clone()),99 parent: Some(parent),153 }100 }154 }101 }155102156 pub fn bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) {103 pub(crate) fn bind(&mut self, id: LocalId, value: Thunk<Val>) {157 let _ = self.bindings.insert(name.into(), value);158 }159 /// After commit, binds would shadow the previous declarations160 #[must_use]161 pub fn commit(mut self) -> Self {162 self.filled.clear();163 self164 }165 pub fn try_bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) -> Result<()> {104 debug_assert!(105 id.0 >= self.offset,106 "cannot bind {id:?} below offset {}",107 self.offset,108 );166 let name = name.into();109 let local_idx = (id.0 - self.offset) as usize;110 self.bindings.reserve(local_idx);167 if !self.filled.insert(name.clone()) {111 while self.bindings.len() <= local_idx {168 bail!(DuplicateLocalVar(name))112 self.bindings.push(None);169 }113 }170 self.bind(name, value);114 self.bindings[local_idx] = Some(value);171 Ok(())115 }172 }116173 pub fn build(self) -> Context {117 pub(crate) fn build(self) -> Context {174 Context(Cc::new(ContextInternal {118 Context(Cc::new(ContextInternal {175 dollar: self.dollar,176 sup_this: self.sup_this,119 sup_this: self.sup_this,177 bindings: self.bindings,120 bindings: self.bindings,178 branch_point: self.branch_point,121 offset: self.offset,122 parent: self.parent,179 }))123 }))180 }124 }125181 pub fn build_sup_this(mut self, st: SupThis) -> Context {126 pub(crate) fn build_sup_this(mut self, st: SupThis) -> Context {182 if self.dollar.is_none() {183 self.dollar = Some(st.this().clone());184 }185 self.sup_this = Some(st);127 self.sup_this = Some(st);186 self.build()128 self.build()187 }129 }193 }135 }194}136}137138pub struct InitialContextBuilder {139 builder: ContextBuilder,140 externals: Vec<(IStr, LocalId)>,141 next_id: u32,142}143144impl InitialContextBuilder {145 pub(crate) fn new() -> Self {146 Self {147 builder: ContextBuilder::new(),148 externals: Vec::new(),149 next_id: 0,150 }151 }152153 pub fn bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) {154 let name = name.into();155 let id = LocalId(self.next_id);156 self.next_id += 1;157 self.externals.push((name, id));158 self.builder.bind(id, value);159 }160161 pub(crate) fn build(self) -> (ContextBuilder, Vec<(IStr, LocalId)>) {162 (self.builder, self.externals)163 }164}165166impl Default for InitialContextBuilder {167 fn default() -> Self {168 Self::new()169 }170}195171crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth9use thiserror::Error;9use thiserror::Error;101011use crate::{11use crate::{12 ObjValue, ResolvePathOwned,13 function::{CallLocation, FunctionSignature, ParamName},12 function::{CallLocation, FunctionSignature, ParamName},14 stdlib::format::FormatError,13 stdlib::format::FormatError,15 typed::TypeLocError,14 typed::TypeLocError,15 ObjValue, ResolvePathOwned,16};16};171718#[derive(Debug, Clone)]18#[derive(Debug, Clone, Acyclic)]19pub struct SyntaxError {19pub struct SyntaxError {20 pub message: String,20 pub message: String,21 pub location: (u32, u32),21 pub location: Span,22}22}23impl fmt::Display for SyntaxError {23impl fmt::Display for SyntaxError {24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {63 }63 }64}64}656566pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {66pub(crate) fn suggest_names<'a, 'b>(67 name: &'a IStr,68 names: impl IntoIterator<Item = &'b IStr>,69) -> Vec<IStr> {67 let mut heap = Vec::new();70 let mut heap: Vec<(f64, IStr)> = names68 for field in v.fields_ex(71 .into_iter()69 true,70 #[cfg(feature = "exp-preserve-order")]71 false,72 ) {72 .filter_map(|def| {73 let conf = strsim::jaro_winkler(field.as_str(), key.as_str());73 let conf = strsim::jaro_winkler(def.as_str(), name.as_str());74 if conf < 0.8 {74 if conf < 0.8 {75 continue;75 return None;76 }76 }77 assert!(77 debug_assert!(78 field.as_str() != key.as_str(),78 def.as_str() != name.as_str(),79 "looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!"79 "string pooling failure: look for DOC(string-pooling) comment in jrsonnet-interner"80 );80 );818182 heap.push((conf, field));82 Some((conf, def.clone()))83 }83 })84 .collect();84 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));85 heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));85 heap.into_iter().map(|v| v.1).collect()86 heap.into_iter().map(|v| v.1).collect()86}87}8889pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {90 let fields = v.fields_ex(91 true,92 #[cfg(feature = "exp-preserve-order")]93 false,94 );95 suggest_names(&key, &fields)96}879788/// Possible errors98/// Possible errors89#[allow(missing_docs)]99#[allow(missing_docs)]101 #[error("self/super/$ are only usable inside objects")]111 #[error("self/super/$ are only usable inside objects")]102 CantUseSelfSupOutsideOfObject,112 CantUseSelfSupOutsideOfObject,113114 #[error("static analysis errors: {}", .0.iter().map(|d| d.message.as_str()).collect::<Vec<_>>().join("; "))]115 StaticAnalysisError(Vec<crate::analyze::Diagnostic>),103 #[error("no super found")]116 #[error("no super found")]104 NoSuperFound,117 NoSuperFound,105118106 #[error("for loop can only iterate over arrays")]119 #[error("for loop can only iterate over arrays")]107 InComprehensionCanOnlyIterateOverArray,120 InComprehensionCanOnlyIterateOverArray,108121109 #[error("array out of bounds: {0} is not within [0,{1})")]122 #[error("array out of bounds: {0} is not within [0,{1})")]110 ArrayBoundsError(isize, usize),123 ArrayBoundsError(isize, u32),111 #[error("string out of bounds: {0} is not within [0,{1})")]124 #[error("string out of bounds: {0} is not within [0,{1})")]112 StringBoundsError(usize, usize),125 StringBoundsError(usize, usize),113126114 #[error("assert failed: {}", format_empty_str(.0))]127 #[error("assert failed: {}", format_empty_str(.0))]115 AssertionFailed(IStr),128 AssertionFailed(IStr),116117 #[error("local is not defined: {0}{found}", found = format_found(.1, "local"))]118 VariableIsNotDefined(IStr, Vec<IStr>),119 #[error("duplicate local var: {0}")]120 DuplicateLocalVar(IStr),121129122 #[error("type mismatch: expected {expected}, got {2} {0}", expected = .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]130 #[error("type mismatch: expected {expected}, got {2} {0}", expected = .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]123 TypeMismatch(&'static str, Vec<ValType>, ValType),131 TypeMismatch(&'static str, Vec<ValType>, ValType),172 #[error("syntax error: {error}")]180 #[error("syntax error: {error}")]173 ImportSyntaxError {181 ImportSyntaxError {174 path: Source,182 path: Source,175 #[trace(skip)]176 error: Box<SyntaxError>,183 error: Box<SyntaxError>,177 },184 },178185279}286}280impl fmt::Display for Error {287impl fmt::Display for Error {281 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {288 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {282 writeln!(f, "{}", self.0.0)?;289 writeln!(f, "{}", self.0 .0)?;283 for el in &self.0.1.0 {290 for el in &self.0 .1 .0 {284 write!(f, "\t{}", el.desc)?;291 write!(f, "\t{}", el.desc)?;285 if let Some(loc) = &el.location {292 if let Some(loc) = &el.location {286 write!(f, "at {}", loc.0.0.0)?;293 write!(f, "at {}", loc.0 .0 .0)?;287 loc.0.map_source_locations(&[loc.1, loc.2]);294 loc.0.map_source_locations(&[loc.1, loc.2]);288 }295 }289 writeln!(f)?;296 writeln!(f)?;377 return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())384 return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())378 };385 };379}386}387#[macro_export]388macro_rules! error {389 ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {390 $crate::error::Error::from($w$(::$i)*$(($($tt)*))?)391 };392 ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {393 $crate::error::Error::from($w$(::$i)*$({$($tt)*})?)394 };395 ($l:literal$(, $($tt:tt)*)?) => {396 <$crate::error::Error as From<$crate::error::ErrorKind>>::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())397 };398}380399381#[macro_export]400#[macro_export]382macro_rules! runtime_error {401macro_rules! runtime_error {crates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterbothno changes
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth1use jrsonnet_ir::{BindSpec, Destruct};1use std::rc::Rc;223use jrsonnet_gcmodule::Trace;43use crate::{5use crate::{4 Context, ContextBuilder, Pending, Thunk, Val, error::Result, evaluate_method,6 analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},7 bail,8 evaluate::evaluate,5 evaluate_named_param,9 Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,6};10};7118#[allow(clippy::too_many_lines)]12#[allow(dead_code, reason = "not dead in exp-destruct")]13fn destruct_array(9#[allow(unused_variables)]14 start: &[LDestruct],10pub fn destruct(15 rest: Option<LDestructRest>,11 d: &Destruct,16 end: &[LDestruct],1712 parent: Thunk<Val>,18 value: Thunk<Val>,13 fctx: Pending<Context>,19 fctx: Pending<Context>,14 new_bindings: &mut ContextBuilder,20 builder: &mut ContextBuilder,15) -> Result<()> {21) {22 let min_len = start.len() + end.len();16 match d {23 let has_rest = rest.is_some();24 let full = Thunk!(move || {17 Destruct::Full(v) => {25 let v = value.evaluate()?;26 let Val::Arr(arr) = v else {27 bail!("expected array");28 };29 if !has_rest {30 if arr.len() as usize != min_len {18 new_bindings.try_bind(v.clone(), parent)?;31 bail!("expected {} elements, got {}", min_len, arr.len())32 }33 } else if (arr.len() as usize) < min_len {34 bail!(35 "expected at least {} elements, but array was only {}",36 min_len,37 arr.len()38 )19 }39 }20 #[cfg(feature = "exp-destruct")]40 Ok(arr)21 Destruct::Skip => {}41 });22 #[cfg(feature = "exp-destruct")]23 Destruct::Array { start, rest, end } => {24 use jrsonnet_ir::DestructRest;254226 use crate::bail;43 for (i, d) in start.iter().enumerate() {44 let full = full.clone();45 destruct(46 d,47 Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),48 fctx.clone(),49 builder,50 );51 }275228 let min_len = start.len() + end.len();53 let start_len = start.len() as u32;29 let has_rest = rest.is_some();54 let end_len = end.len() as u32;30 let full = Thunk!(move || {31 let v = parent.evaluate()?;32 let Val::Arr(arr) = v else {33 bail!("expected array");34 };35 if !has_rest {36 if arr.len() != min_len {37 bail!("expected {} elements, got {}", min_len, arr.len())38 }39 } else if arr.len() < min_len {40 bail!(41 "expected at least {} elements, but array was only {}",42 min_len,43 arr.len()44 )45 }46 Ok(arr)47 });485549 {56 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {50 for (i, d) in start.iter().enumerate() {51 let full = full.clone();57 let full = full.clone();52 destruct(58 builder.bind(53 d,59 id,54 Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),60 Thunk!(move || {61 let full = full.evaluate()?;62 let to = full.len() - end_len;63 Ok(Val::Arr(full.slice(64 Some(start_len as i32),55 fctx.clone(),65 Some(to as i32),56 new_bindings,66 None,57 )?;67 )))58 }68 }),59 }69 );70 }607161 match rest {72 for (i, d) in end.iter().enumerate() {62 Some(DestructRest::Keep(v)) => {63 let start = start.len();64 let end = end.len();65 let full = full.clone();73 let full = full.clone();66 destruct(74 destruct(67 &Destruct::Full(v.clone()),75 d,68 Thunk!(move || {76 Thunk!(move || {69 let full = full.evaluate()?;77 let full = full.evaluate()?;70 let to = full.len() - end;78 Ok(full71 Ok(Val::Arr(full.slice(79 .get(full.len() - end_len + i as u32)?72 Some(start as i32),73 Some(to as i32),80 .expect("length is checked"))74 None,75 )))76 }),81 }),77 fctx.clone(),82 fctx.clone(),78 new_bindings,83 builder,79 )?;84 );80 }85 }81 Some(DestructRest::Drop) | None => {}82 }86}838784 {85 for (i, d) in end.iter().enumerate() {88#[allow(dead_code, reason = "not dead in exp-destruct")]86 let full = full.clone();89fn destruct_object(87 let end = end.len();88 destruct(89 d,90 Thunk!(move || {91 let full = full.evaluate()?;92 Ok(full.get(full.len() - end + i)?.expect("length is checked"))93 }),94 fctx.clone(),95 new_bindings,96 )?;97 }98 }99 }100 #[cfg(feature = "exp-destruct")]101 Destruct::Object { fields, rest } => {90 fields: &[LDestructField],102 use jrsonnet_ir::DestructRest;91 rest: Option<LDestructRest>,103 use rustc_hash::FxHashSet;10492105 use crate::{ObjValueBuilder, bail};93 value: Thunk<Val>,94 fctx: Pending<Context>,95 builder: &mut ContextBuilder,96) {97 use jrsonnet_interner::IStr;98 use rustc_hash::FxHashSet;10699107 let captured_fields: FxHashSet<_> = fields.iter().map(|f| f.0.clone()).collect();100 use crate::{bail, ObjValueBuilder};108 let field_names: Vec<_> = fields109 .iter()110 .map(|f| (f.0.clone(), f.2.is_some()))111 .collect();112 let has_rest = rest.is_some();113 let full = Thunk!(move || {114 let v = parent.evaluate()?;115 let Val::Obj(obj) = v else {116 bail!("expected object");117 };118 for (field, has_default) in &field_names {119 if !has_default && !obj.has_field_ex(field.clone(), true) {120 bail!("missing field: {field}");121 }122 }123 if !has_rest {124 let len = obj.len();125 if len > field_names.len() {126 bail!("too many fields, and rest not found");127 }128 }129 Ok(obj)130 });131101132 match rest {102 let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();133 Some(DestructRest::Keep(v)) => {103 let field_names: Vec<(IStr, bool)> = fields134 let full = full.clone();104 .iter()105 .map(|f| (f.name.clone(), f.default.is_some()))135 destruct(106 .collect();136 &Destruct::Full(v.clone()),107 let has_rest = rest.is_some();137 Thunk!(move || {108 let full = Thunk!(move || {138 let full = full.evaluate()?;109 let v = value.evaluate()?;139 let mut builder = ObjValueBuilder::new();110 let Val::Obj(obj) = v else {140 builder.extend_with_core(full.as_standalone());111 bail!("expected object");141 builder.with_fields_omitted(captured_fields);112 };142 Ok(Val::Obj(builder.build()))113 for (field, has_default) in &field_names {143 }),114 if !has_default && !obj.has_field_ex(field.clone(), true) {144 fctx.clone(),145 new_bindings,146 )?;147 }115 bail!("missing field: {field}");148 Some(DestructRest::Drop) | None => {}149 }116 }117 }118 if !has_rest {119 let len = obj.len();120 if len as usize > field_names.len() {121 bail!("too many fields, and rest not found");122 }123 }124 Ok(obj)125 });150126151 for (field, d, default) in fields {127 if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {152 let default = default.clone().map(|e| (fctx.clone(), e));153 let value = {154 let field = field.clone();128 let full = full.clone();155 let full = full.clone();129 builder.bind(156 Thunk!(move || {130 id,131 Thunk!(move || {157 let full = full.evaluate()?;132 let full = full.evaluate()?;158 if let Some(field) = full.get(field)? {133 let mut out = ObjValueBuilder::new();159 Ok(field)134 out.extend_with_core(full.as_standalone());160 } else {135 out.with_fields_omitted(captured_fields);161 let (fctx, expr) = default.as_ref().expect("shape is checked");162 Ok(crate::evaluate(fctx.clone().unwrap(), expr)?)136 Ok(Val::Obj(out.build()))163 }137 }),164 })138 );165 };139 }166140167 if let Some(d) = d {141 for field in fields {142 let field_name = field.name.clone();143 let default: Option<(Pending<Context>, Rc<LExpr>)> =168 destruct(d, value, fctx.clone(), new_bindings)?;144 field.default.as_ref().map(|e| (fctx.clone(), e.clone()));169 } else {145 let field_full = full.clone();146 let value_thunk = Thunk!(move || {170 destruct(147 let obj = field_full.evaluate()?;171 &Destruct::Full(field.clone()),148 obj.get(field_name)?.map_or_else(172 value,149 || {150 let (fctx, expr) = default.as_ref().expect("shape is checked");173 fctx.clone(),151 evaluate(fctx.unwrap(), expr)174 new_bindings,152 },153 Ok,175 )?;154 )155 });156176 }157 if let Some(into) = &field.into {158 destruct(into, value_thunk, fctx.clone(), builder);159 } else {177 }160 unreachable!("analyzer lowers object-destruct shorthands into `into`");178 }161 }179 }162 }180 Ok(())181}163}182164165/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one166/// binding per [`LocalId`] the pattern introduces.167///168/// `fctx` is needed for object-destruct defaults (feature `exp-destruct`).169#[allow(unused_variables)]183pub fn evaluate_dest(170pub fn destruct(184 d: &BindSpec,171 d: &LDestruct,172 value: Thunk<Val>,185 fctx: Pending<Context>,173 fctx: Pending<Context>,186 new_bindings: &mut ContextBuilder,174 builder: &mut ContextBuilder,187) -> Result<()> {175) {188 match d {176 match d {189 BindSpec::Field { into, value } => {177 LDestruct::Full(id) => builder.bind(*id, value),190 let name = into.name();178 #[cfg(feature = "exp-destruct")]191 let value = value.clone();179 LDestruct::Skip => {}192 let data = {180 #[cfg(feature = "exp-destruct")]193 let fctx = fctx.clone();181 LDestruct::Array { start, rest, end } => destruct_array(start, rest, end, value, fctx, builder),194 Thunk!(move || evaluate_named_param(fctx.unwrap(), &value, name))182 #[cfg(feature = "exp-destruct")]195 };183 LDestruct::Object { fields, rest } => destruct_object(fields, rest, value, fctx, builder),196 destruct(into, data, fctx, new_bindings)?;184 }185}186187/// Bind one [`LBind`] as a lazy thunk that evaluates in the given188/// future context. Mirrors the old `evaluate_dest` — one entry per189/// binding in a `local … ;` frame.190pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {191 let value = bind.value.clone();192 let fctx_clone = fctx.clone();193 let thunk = Thunk!(move || {194 let ctx = fctx_clone.unwrap();195 evaluate(ctx, &value)196 });197 destruct(&bind.destruct, thunk, fctx, builder);198}199200/// Bind each LBind's value as a lazy thunk. Mutually recursive locals201/// resolve lazily through the shared Pending<Context>.202pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {203 if binds.is_empty() {204 return parent;205 }206 let fctx = Context::new_future();207 let mut builder =208 ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());209 for bind in binds {210 evaluate_dest(bind, fctx.clone(), &mut builder);211 }212 builder.build().into_future(fctx)213}214215pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}216impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}217218pub fn evaluate_locals_unbound(219 fctx: Context,220 locals: Rc<Vec<LBind>>,221 this_id: Option<LocalId>,222) -> impl CloneableUnbound<Context> {223 #[derive(Trace, Clone)]224 struct UnboundLocals {225 fctx: Context,226 locals: Rc<Vec<LBind>>,227 this_id: Option<LocalId>,228 }229 impl Unbound for UnboundLocals {230 type Bound = Context;231232 fn bind(&self, sup_this: SupThis) -> Result<Context> {233 let parent = self.fctx.clone();234235 let fctx = Context::new_future();236 let mut builder = ContextBuilder::extend(237 parent,238 self.locals.iter().map(|b| b.destruct.ids().len()).sum(),239 );240 for b in self.locals.iter() {241 evaluate_dest(b, fctx.clone(), &mut builder);242 }243 if let Some(this_id) = self.this_id {244 builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));245 }246 let ctx = builder.build_sup_this(sup_this).into_future(fctx);247 Ok(ctx)197 }248 }198 BindSpec::Function {249 }199 name,250200 params,251 UnboundLocals {201 value,202 } => {203 let params = params.clone();252 fctx,204 let name = name.clone();205 let value = value.clone();206 new_bindings.try_bind(207 name.clone(),208 Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value))),253 locals,209 )?;254 this_id,210 }211 }255 }212 Ok(())213}256}214257crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth223use jrsonnet_gcmodule::{Cc, Trace};3use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;4use jrsonnet_interner::IStr;5use jrsonnet_ir::{5use jrsonnet_ir::ImportKind;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;6use jrsonnet_types::ValType;11712use self::destructure::destruct;8use self::{9 compspec::{evaluate_arr_comp, evaluate_obj_comp},10 destructure::{evaluate_locals, evaluate_locals_unbound},11 operator::evaluate_binary_op_special,12};13use crate::{13use crate::{14 Context, ContextBuilder, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,14 analyze::{15 LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction, LIndexPart, LObjBody,15 ResultExt, SupThis, Unbound, Val,16 LObjMembers,16 arr::ArrValue,17 },17 bail,18 bail,18 destructure::evaluate_dest,19 error::{suggest_object_fields, ErrorKind::*},19 error::{ErrorKind::*, suggest_object_fields},20 evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op},20 evaluate::operator::evaluate_unary_op,21 function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal},21 function::{prepared::PreparedFuncVal, CallLocation, FuncDesc, FuncVal},22 in_frame,22 in_frame, runtime_error,23 typed::{FromUntyped, IntoUntyped as _, Typed},23 typed::FromUntyped as _,24 val::{CachedUnbound, IndexableVal, StrValue, Thunk},24 val::{CachedUnbound, Thunk},25 with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _,25 with_state,26 SupThis, Unbound, Val,26};27};2829pub mod compspec;27pub mod destructure;30pub mod destructure;28pub mod operator;31pub mod operator;293230// This is the amount of bytes that need to be left on the stack before increasing the size.33// 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 call34// It must be at least as large as the stack required by any code that does not call32// `ensure_sufficient_stack`.35// `ensure_sufficient_stack`.33const RED_ZONE: usize = 100 * 1024; // 100k36const RED_ZONE: usize = 100 * 1024;343735// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then38// 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.39// on. This flag has performance relevant characteristics. Don't set it too high.37const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB40const STACK_PER_RECURSION: usize = 1024 * 1024;384139/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations42/// 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 benefit43/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit46 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)49 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)47}50}485149pub fn evaluate_trivial(expr: &Expr) -> Option<Val> {52pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {50 fn is_trivial(expr: &Expr) -> bool {53 // TODO: Eager trivial array51 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 {54 Some(match expr {60 Expr::Str(s) => Val::string(s.clone()),55 LExpr::Str(s) => Val::string(s.clone()),61 Expr::Num(n) => Val::Num(*n),56 LExpr::Num(n) => Val::Num(*n),62 Expr::Literal(LiteralType::False) => Val::Bool(false),57 LExpr::Bool(false) => Val::Bool(false),63 Expr::Literal(LiteralType::True) => Val::Bool(true),58 LExpr::Bool(true) => Val::Bool(true),64 Expr::Literal(LiteralType::Null) => Val::Null,59 LExpr::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,60 _ => return None,77 })61 })78}62}796364/// Evaluate a method definition.80pub fn evaluate_method(ctx: Context, name: IStr, params: ExprParams, body: Rc<Expr>) -> Val {65pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {81 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {66 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {82 name,67 name,83 ctx,68 ctx,84 params,69 func: func.clone(),85 body,86 })))70 })))87}71}887289pub fn evaluate_field_name(ctx: Context, field_name: &Spanned<FieldName>) -> Result<Option<IStr>> {73pub fn evaluate_field_name(ctx: Context, field_name: &LFieldName) -> Result<Option<IStr>> {90 Ok(match &field_name.value {74 Ok(match field_name {91 FieldName::Fixed(n) => Some(n.clone()),75 LFieldName::Fixed(n) => Some(n.clone()),92 FieldName::Dyn(expr) => in_frame(76 LFieldName::Dyn(expr) => in_frame(93 CallLocation::new(&field_name.span),77 // TODO: Spanned<LFieldName>78 CallLocation::native(),94 || "evaluating field name".to_string(),79 || "evaluating field name".to_string(),95 || {80 || {96 let v = evaluate(ctx, expr)?;81 let v = evaluate(ctx.clone(), expr)?;97 Ok(if matches!(v, Val::Null) {82 Ok(if matches!(v, Val::Null) {98 None83 None99 } else {84 } else {104 })89 })105}90}10691107pub fn evaluate_comp(92pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {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() {93 Ok(if tailstrict {114 None => callback(ctx, guaranteed_reserve)?,115 Some(CompSpec::IfSpec(IfSpecData { cond, span: _ })) => {116 if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {94 Thunk::evaluated(evaluate(ctx, &expr)?)117 evaluate_comp(ctx, &specs[1..], 0, callback)?;95 } else {118 }119 }120 Some(CompSpec::ForSpec(ForSpecData {121 destruct: into,122 over,123 })) => {124 match evaluate(ctx.clone(), over)? {96 Thunk!(move || { evaluate(ctx, &expr) })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);97 })98}13299133 let specs = &specs[1..];100mod names {134 evaluate_comp(135 ctx,136 specs,137 if i == 0 || !specs.is_empty() {138 guaranteed_reserve101 use crate::names;139 } 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);165102166 evaluate_comp(103 names! {167 ctx,168 &specs[1..],169 if i == 0 || !specs.is_empty() {170 guaranteed_reserve104 anonymous: "anonymous",171 } else {172 0173 },174 callback,175 )?;176 }177 }178 _ => bail!(InComprehensionCanOnlyIterateOverArray),179 }180 }181 }105 }182 Ok(())183}106}184107185fn evaluate_arr_comp(ctx: Context, expr: &Rc<Expr>, comp_specs: &[CompSpec]) -> Result<ArrValue> {108pub fn evaluate_named(name: &IStr, ctx: Context, expr: &LExpr) -> Result<Val> {186 let ctx = ctx.branch_point();109 if let LExpr::Function(f) = &expr {110 return Ok(evaluate_method(111 ctx,112 f.name.clone().unwrap_or_else(|| name.clone()),113 f,114 ));187 'eager: {115 }188 let mut out = Vec::new();116 evaluate(ctx, expr)117}189118119pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {190 if evaluate_comp(ctx.clone(), comp_specs, 0, &mut |ctx, reserve| {120 Ok(match expr {121 LExpr::Null => Val::Null,122 LExpr::Bool(b) => Val::Bool(*b),123 LExpr::Str(s) => Val::string(s.clone()),124 LExpr::Num(n) => Val::Num(*n),125 LExpr::Local(id) => {126 let Some(thunk) = ctx.binding(*id) else {127 bail!("should not happen: unbound local {id:?}");128 };129 thunk.evaluate()?130 }131 LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),132 LExpr::Arr(items) => Val::Arr(crate::arr::ArrValue::expr(ctx, items.clone())),133 LExpr::UnaryOp(op, value) => {134 let value = evaluate(ctx, value)?;135 evaluate_unary_op(*op, &value)?136 }137 LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,138 LExpr::LocalExpr { binds, body } => {191 if reserve != 0 {139 let ctx = evaluate_locals(ctx, binds);140 evaluate(ctx, body)?141 }142 LExpr::IfElse {192 out.reserve(reserve);143 cond,144 cond_then,145 cond_else,146 } => {147 let cond_val = evaluate(ctx.clone(), cond)?;148 let Val::Bool(b) = cond_val else {149 bail!(TypeMismatch(150 "if condition",151 vec![ValType::Bool],152 cond_val.value_type()153 ))154 };155 if b {156 evaluate(ctx, cond_then)?157 } else if let Some(e) = cond_else {158 evaluate(ctx, e)?159 } else {160 Val::Null193 }161 }194 out.push(evaluate(ctx, expr)?);195 Ok(())196 })197 .is_err()198 {199 break 'eager;200 }162 }163 LExpr::Error(s, e) => in_frame(164 CallLocation::new(s),165 || "error statement".to_owned(),166 || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),167 )?,168 LExpr::AssertExpr { assert, rest } => {169 evaluate_assert(ctx.clone(), assert)?;170 evaluate(ctx, rest)?171 }201172202 return Ok(ArrValue::new(out));173 LExpr::Function(func) => evaluate_method(174 ctx,175 func.name.clone().unwrap_or_else(names::anonymous),176 func,177 ),203 };178 LExpr::Apply {204 let mut out = Vec::new();179 applicable,180 args,181 tailstrict,182 } => evaluate_apply(183 ctx,184 applicable,185 args,186 CallLocation::new(&args.span),205 evaluate_comp(ctx, comp_specs, 0, &mut |ctx, reserve| {187 *tailstrict,188 )?,189 LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,190 LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,191 LExpr::ObjExtend(lhs, body) => {206 if reserve != 0 {192 let lhs_val = evaluate(ctx.clone(), lhs)?;193 let Val::Obj(lhs_obj) = lhs_val else {207 out.reserve(reserve);194 bail!(TypeMismatch(195 "object extend lhs",196 vec![ValType::Obj],197 lhs_val.value_type(),198 ))199 };200 evaluate_obj_body(Some(lhs_obj), ctx, body)?208 }201 }209 let expr = expr.clone();202 LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,210 out.push(Thunk!(move || evaluate(ctx, &expr)));203 LExpr::Slice(slice) => {211 Ok(())204 use crate::typed::BoundedUsize;212 })?;205 let val = evaluate(ctx.clone(), &slice.value)?;213 Ok(ArrValue::new(out))206 let indexable = val.into_indexable()?;207 let start = slice208 .start209 .as_ref()210 .map(|e| evaluate(ctx.clone(), e))211 .transpose()?212 .map(|v| -> Result<i32> {213 v.as_num()214 .ok_or_else(|| {215 TypeMismatch("slice start", vec![ValType::Num], v.value_type()).into()216 })217 .map(|n| n as i32)218 })219 .transpose()?;220 let end = slice221 .end222 .as_ref()223 .map(|e| evaluate(ctx.clone(), e))224 .transpose()?225 .map(|v| -> Result<i32> {226 v.as_num()227 .ok_or_else(|| {228 TypeMismatch("slice end", vec![ValType::Num], v.value_type()).into()229 })230 .map(|n| n as i32)231 })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 let n = v.as_num().ok_or_else(|| -> crate::Error {240 TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()241 })?;242 BoundedUsize::new(n as usize)243 .ok_or_else(|| runtime_error!("slice step must be >= 1"))244 })245 .transpose()?;246 Val::from(indexable.slice(start, end, step)?)247 }248 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super()?),249 LExpr::Import {250 kind,251 kind_span,252 path,253 } => with_state(|state| {254 let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;255 Ok::<_, Error>(match kind.value {256 ImportKind::Normal => in_frame(257 CallLocation::new(&kind.span),258 || "import".to_string(),259 || state.import_resolved(resolved),260 )?,261 ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),262 ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),263 })264 })?,265 })214}266}215267216trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}268fn evaluate_apply(269 ctx: Context,270 applicable: &LExpr,271 args: &LArgsDesc,217impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}272 loc: CallLocation<'_>,273 tailstrict: bool,274) -> Result<Val> {275 let func_val = evaluate(ctx.clone(), applicable)?;276 let Val::Func(func) = func_val else {277 bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))278 };218279219fn evaluate_object_locals(280 let name = func.name();220 fctx: Context,281 let unnamed = args221 locals: Rc<Vec<BindSpec>>,282 .unnamed222) -> impl CloneableUnbound<Context> {283 .iter()223 #[derive(Trace, Clone)]284 .cloned()224 struct UnboundLocals {285 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))225 fctx: Context,226 locals: Rc<Vec<BindSpec>>,286 .collect::<Result<Vec<_>>>()?;227 }228 impl Unbound for UnboundLocals {229 type Bound = Context;230287231 fn bind(&self, sup_this: SupThis) -> Result<Context> {288 let named = args232 let fctx = Context::new_future();289 .values290 .iter()233 let ctx = self.fctx.clone();291 .cloned()292 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))293 .collect::<Result<Vec<_>>>()?;234 let mut ctx = ContextBuilder::extend(ctx);294 let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)235 for b in self.locals.iter() {295 .with_description_src(loc, || format!("function <{name}> preparation"))?;236 evaluate_dest(b, fctx.clone(), &mut ctx)?;296 in_frame(297 loc,298 || format!("function <{name}> call"),299 || prepare.call(CallLocation::native(), &unnamed, &named),237 }300 )301}238302303fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {239 let ctx = ctx.build_sup_this(sup_this).into_future(fctx);304 let mut value = if let LExpr::Super = indexable {240305 let sup_this = ctx.try_sup_this()?;306 // First part must be evaluated to get the super field name307 if parts.is_empty() {241 Ok(ctx)308 bail!(RuntimeError("super requires an index".into()))242 }309 }243 }310 let key_val = evaluate(ctx.clone(), &parts[0].value)?;311 let Val::Str(key) = &key_val else {312 bail!(ValueIndexMustBeTypeGot(313 ValType::Obj,314 ValType::Str,315 key_val.value_type(),316 ))317 };318 let field = key.clone().into_flat();319 if let Some(v) = sup_this.get_super(field.clone())? {320 // Continue with remaining parts321 let mut value = v;322 for part in &parts[1..] {323 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;324 }325 return Ok(value);326 }327 let suggestions = suggest_object_fields(sup_this.this(), field.clone());328 bail!(NoSuchField(field, suggestions))329 } else {330 evaluate(ctx.clone(), indexable)?331 };244332245 UnboundLocals { fctx, locals }333 for part in parts {334 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;335 }336 Ok(value)246}337}247338248pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(339fn index_val(ctx: Context, loc: CallLocation<'_>, value: Val, part: &LIndexPart) -> Result<Val> {249 builder: &mut ObjValueBuilder,250 ctx: Context,251 uctx: B,252 field: &FieldMember,253) -> Result<()> {254 let name = evaluate_field_name(ctx, &field.name)?;340 let key_val = evaluate(ctx, &part.value)?;255 let Some(name) = name else {341 Ok(match (&value, &key_val) {342 (Val::Obj(obj), Val::Str(key)) => {256 return Ok(());343 let field = key.clone().into_flat();257 };344 if let Some(v) = obj258259 match field {260 FieldMember {345 .get(field.clone())261 plus,262 params: None,346 .with_description_src(loc, || format!("field <{field}> access"))?263 visibility,347 {264 value,348 v265 ..349 } else {266 } => {267 #[derive(Trace)]350 bail!(NoSuchField(268 struct UnboundValue<B: Trace> {351 field.clone(),269 uctx: B,270 value: Rc<Expr>,352 suggest_object_fields(obj, field)271 name: IStr,353 ))272 }354 }273 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {355 }356 (Val::Arr(arr), Val::Num(idx)) => {274 type Bound = Val;357 let n = idx.get();275 fn bind(&self, sup_this: SupThis) -> Result<Val> {358 if n.fract() > f64::EPSILON {276 evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())359 bail!(FractionalIndex)277 }278 }360 }279280 builder361 if n < 0.0 {281 .field(name.clone())362 bail!(ArrayBoundsError(282 .with_add(*plus)363 n as isize, // truncation is fine for error display283 .with_visibility(*visibility)364 arr.len()284 .with_location(field.name.span.clone())365 ));285 .bindable(UnboundValue {366 }367 #[expect(286 uctx,368 clippy::cast_possible_truncation,369 clippy::cast_sign_loss,287 value: value.clone(),370 reason = "n is checked positive"371 )]372 let i = n as u32;373 arr.get(i)288 name,374 .with_description_src(loc, || format!("element <{i}> access"))?289 })?;375 .ok_or_else(|| ArrayBoundsError(i as isize, arr.len()))?290 }376 }291 FieldMember {377 (Val::Str(s), Val::Num(idx)) => {292 params: Some(params),293 visibility,294 value,295 ..296 } => {297 #[derive(Trace)]378 let n = idx.get();298 struct UnboundMethod<B: Trace> {379 if n.fract() > f64::EPSILON {299 uctx: B,300 value: Rc<Expr>,301 params: ExprParams,380 bail!(FractionalIndex)302 name: IStr,303 }381 }304 impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {382 let flat = s.clone().into_flat();305 type Bound = Val;306 fn bind(&self, sup_this: SupThis) -> Result<Val> {383 if n < 0.0 {307 Ok(evaluate_method(384 bail!(ArrayBoundsError(308 self.uctx.bind(sup_this)?,385 n as isize, // truncation is fine for error display309 self.name.clone(),386 flat.chars().count() as u32310 self.params.clone(),311 self.value.clone(),387 ));312 ))313 }314 }388 }315316 builder389 #[expect(317 .field(name.clone())390 clippy::cast_possible_truncation,318 .with_visibility(*visibility)391 clippy::cast_sign_loss,319 // .with_location(value.span())392 reason = "n is checked positive, overflow will truncate as expected"320 .bindable(UnboundMethod {393 )]321 uctx,394 let i = n as usize;322 value: value.clone(),395 let Some(char) = flat.chars().nth(i) else {323 params: params.clone(),396 bail!(StringBoundsError(i, flat.chars().count()))324 name,397 };325 })?;398 Val::string(char)326 }399 }327 }400 _ => bail!(ValueIndexMustBeTypeGot(328 Ok(())401 value.value_type(),402 ValType::Str,403 key_val.value_type()404 )),405 })329}406}330407331#[derive(Trace, Clone)]408fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {332struct DirectUnbound(Context);333impl Unbound for DirectUnbound {334 type Bound = Context;409 match body {335 fn bind(&self, sup_this: SupThis) -> Result<Context> {410 LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),336 Ok(ContextBuilder::extend(self.0.clone()).build_sup_this(sup_this))411 LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),337 }412 }338}413}339414340#[allow(clippy::too_many_lines)]341pub fn evaluate_member_list_object(415pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(342 super_obj: Option<ObjValue>,416 builder: &mut ObjValueBuilder,343 ctx: Context,417 ctx: Context,344 members: &ObjMembers,418 uctx: B,419 field: &LFieldMember,345) -> Result<ObjValue> {420) -> Result<()> {346 #[derive(Trace)]421 #[derive(Trace)]347 struct ObjectAssert<B: Trace> {422 struct UnboundValue<B: Trace> {348 uctx: B,423 uctx: B,349 asserts: Rc<Vec<AssertStmt>>,424 value: Rc<LExpr>,425 name: IStr,350 }426 }351 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {427 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {352 fn run(&self, sup_this: SupThis) -> Result<()> {428 type Bound = Val;429 fn bind(&self, sup_this: SupThis) -> Result<Val> {353 let ctx = self.uctx.bind(sup_this)?;430 evaluate(self.uctx.bind(sup_this)?, &self.value)354 for assert in &*self.asserts {355 evaluate_assert(ctx.clone(), assert)?;356 }357 Ok(())358 }431 }359 }432 }360433361 let mut builder = ObjValueBuilder::new();434 let LFieldMember {435 name,436 plus,437 visibility,438 value,439 } = field;440 let Some(name) = evaluate_field_name(ctx, name)? else {441 return Ok(());442 };443444 builder445 .field(name.clone())446 .with_add(*plus)447 .with_visibility(*visibility)448 .bindable(UnboundValue {449 uctx,450 value: value.clone(),451 name,452 })453}454pub fn evaluate_field_member_static(455 builder: &mut ObjValueBuilder,456 field_ctx: Context,457 value_ctx: Context,458 field: &LFieldMember,459) -> Result<()> {460 let LFieldMember {461 name,462 plus,463 visibility,464 value,465 } = field;466 let Some(name) = evaluate_field_name(field_ctx, name)? else {467 return Ok(());468 };469470 let value = value.clone();471 builder472 .field(name)473 .with_add(*plus)474 .with_visibility(*visibility)475 .try_thunk(Thunk!(move || { evaluate(value_ctx, &value) }))?;476 Ok(())477}478479fn evaluate_obj_members(480 super_obj: Option<ObjValue>,481 ctx: Context,482 members: &LObjMembers,483) -> Result<Val> {484 let mut builder = ObjValueBuilder::with_capacity(members.fields.len());362 if let Some(super_obj) = super_obj {485 if let Some(sup) = super_obj {363 builder.with_super(super_obj);486 builder.with_super(sup);364 }487 }365488366 if members.locals.is_empty() {489 let needs_unbound = members.this.is_some() || members.uses_super;490367 // We can use the same context for all field evaluation, it doesn't depends on locals, only on this/super491 if needs_unbound {368 let uctx = DirectUnbound(ctx.clone());492 let uctx = CachedUnbound::new(evaluate_locals_unbound(493 ctx.clone(),494 members.locals.clone(),495 members.this,496 ));369 for field in &members.fields {497 for field in &members.fields {370 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;498 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;371 }499 }372 if !members.asserts.is_empty() {500 if !members.asserts.is_empty() {373 builder.assert(ObjectAssert {501 builder.assert(evaluate_object_assertions_unbound(374 uctx,502 uctx,375 asserts: members.asserts.clone(),503 members.asserts.clone(),376 });504 ));377 }505 }378 } else {506 } else {379 let locals = members.locals.clone();507 let field_ctx = ctx;380 // We have single context for all fields, so we can cache them together508 let value_ctx = evaluate_locals(field_ctx.clone(), &members.locals);381 let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));382 for field in &members.fields {509 for field in &members.fields {383 evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;510 evaluate_field_member_static(511 &mut builder,512 field_ctx.clone(),513 value_ctx.clone(),514 field,515 )?;384 }516 }385 if !members.asserts.is_empty() {517 if !members.asserts.is_empty() {386 builder.assert(ObjectAssert {518 builder.assert(evaluate_object_assertions_static(387 uctx,519 value_ctx,388 asserts: members.asserts.clone(),520 members.asserts.clone(),389 });521 ));390 }522 }391 }523 }392524393 Ok(builder.build())525 Ok(Val::Obj(builder.build()))394}526}395527396pub fn evaluate_object(528pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {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;529 let LAssertStmt { cond, message } = assertion;464 let assertion_result = in_frame(530 let assertion_result = in_frame(465 CallLocation::new(&assertion.span),531 CallLocation::native(),466 || "assertion condition".to_owned(),532 || "assertion condition".to_owned(),467 || bool::from_untyped(evaluate(ctx.clone(), assertion)?),533 || bool::from_untyped(evaluate(ctx.clone(), cond)?),468 )?;534 )?;469 if !assertion_result {535 if !assertion_result {470 in_frame(536 in_frame(471 CallLocation::new(&assertion.span),537 CallLocation::new(&cond.span),472 || "assertion failure".to_owned(),538 || "assertion failure".to_owned(),473 || {539 || {474 if let Some(msg) = message {540 if let Some(msg) = message {481 Ok(())547 Ok(())482}548}483549484pub fn evaluate_named_param(ctx: Context, expr: &Expr, name: ParamName) -> Result<Val> {550fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(551 uctx: B,552 asserts: Rc<Vec<LAssertStmt>>,553) -> impl ObjectAssertion {485 match name {554 #[derive(Trace)]555 struct ObjectAssert<B: Trace> {486 ParamName::Named(name) => evaluate_named(ctx, expr, name),556 uctx: B,487 ParamName::Unnamed => evaluate(ctx, expr),557 asserts: Rc<Vec<LAssertStmt>>,488 }558 }489}490491pub fn evaluate_named(ctx: Context, expr: &Expr, name: IStr) -> Result<Val> {559 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {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 {560 fn run(&self, sup_this: SupThis) -> Result<()> {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::*;561 let ctx = self.uctx.bind(sup_this)?;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()?),562 for assert in &*self.asserts {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()?;563 evaluate_assert(ctx.clone(), assert)?;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 }564 }535 let field = evaluate(ctx, &bin.lhs)?;565 Ok(())536 Val::Bool(sup_this.field_in_super(field.to_string()?))537 }566 }538 BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,567 }539 UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,568 ObjectAssert { uctx, asserts }540 Var(name) => in_frame(569}541 CallLocation::new(&name.span),570fn evaluate_object_assertions_static(542 || format!("local <{}> access", &**name),571 ctx: Context,543 || ctx.binding((**name).clone())?.evaluate(),572 asserts: Rc<Vec<LAssertStmt>>,544 )?,573) -> impl ObjectAssertion {545 Index { indexable, parts } => ensure_sufficient_stack(|| {574 #[derive(Trace)]546 let mut parts = parts.iter();575 struct ObjectAssert {547 let mut indexable = if matches!(&**indexable, Expr::Literal(LiteralType::Super)) {576 ctx: Context,548 let part = parts.next().expect("at least part should exist");577 asserts: Rc<Vec<LAssertStmt>>,549 // sup_this existence check might also be skipped here for null-coalesce...578 }550 // But I believe this might cause errors.579 impl ObjectAssertion for ObjectAssert {551 let sup_this = ctx.try_sup_this()?;580 fn run(&self, _sup_this: SupThis) -> Result<()> {552 if !sup_this.has_super() {581 for assert in &*self.asserts {553 #[cfg(feature = "exp-null-coaelse")]582 evaluate_assert(self.ctx.clone(), assert)?;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 }583 }686 Ok(indexable)584 Ok(())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 }585 }697 Arr(items) => {586 }698 if items.is_empty() {587 ObjectAssert { ctx, asserts }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}588}787589crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth1use std::cmp::Ordering;1use std::cmp::Ordering;223use jrsonnet_ir::{BinaryOpType, Expr, UnaryOpType};3use jrsonnet_ir::{BinaryOpType, UnaryOpType};445use crate::{5use crate::{6 Context, Result, Val,6 analyze::LExpr,7 arr::ArrValue,7 arr::ArrValue,8 bail,8 bail, error,9 error::ErrorKind::*,9 error::ErrorKind::*,10 evaluate,10 evaluate::evaluate,11 stdlib::std_format,11 stdlib::std_format,12 typed::IntoUntyped as _,12 typed::IntoUntyped as _,13 val::{StrValue, equals},13 val::{equals, StrValue},14 Context, Result, Val,14};15};151616pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {17pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {404141 (Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),42 (Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),42 (Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),43 (Arr(a), Arr(b)) => Val::Arr(44 ArrValue::extended(a.clone(), b.clone()).ok_or_else(|| error!("array is too large"))?,45 ),434644 (Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,47 (Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,158161159pub fn evaluate_binary_op_special(162pub fn evaluate_binary_op_special(160 ctx: Context,163 ctx: Context,161 a: &Expr,164 a: &LExpr,162 op: BinaryOpType,165 op: BinaryOpType,163 b: &Expr,166 b: &LExpr,164) -> Result<Val> {167) -> Result<Val> {165 use BinaryOpType::*;168 use BinaryOpType::*;166 use Val::*;169 use Val::*;170167 Ok(match (evaluate(ctx.clone(), a)?, op, b) {171 Ok(match (evaluate(ctx.clone(), a)?, op, b) {168 (Bool(true), Or, _o) => Val::Bool(true),172 (Bool(true), Or, _) => Val::Bool(true),169 (Bool(false), And, _o) => Val::Bool(false),173 (Bool(false), And, _) => Val::Bool(false),170 #[cfg(feature = "exp-null-coaelse")]174 #[cfg(feature = "exp-null-coaelse")]171 (Null, NullCoaelse, eb) => evaluate(ctx, eb)?,175 (Null, NullCoaelse, eb) => evaluate(ctx, eb)?,172 #[cfg(feature = "exp-null-coaelse")]176 #[cfg(feature = "exp-null-coaelse")]173 (a, NullCoaelse, _o) => a,177 (a, NullCoaelse, _) => a,178 (a, In, LExpr::Super) => {179 let sup_this = ctx.try_sup_this()?;180 if !sup_this.has_super() {181 return Ok(Val::Bool(false));182 }183 return Ok(Val::Bool(sup_this.field_in_super(a.to_string()?)));184 }174 (a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(ctx, eb)?)?,185 (a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(ctx, eb)?)?,175 })186 })176}187}crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth19 };19 };20}20}2122#[macro_export]23macro_rules! names {24 ($($name:ident: $val:literal),* $(,)?) => {25 struct Names {26 $($name: $crate::IStr,)*27 }28 thread_local! {29 static NAMES: Names = Names {30 $($name: $crate::IStr::from($val)),*31 };32 }33 $(pub fn $name() -> $crate::IStr {34 NAMES.with(|n| n.$name.clone())35 })*36 }37}213822cc_dyn!(39cc_dyn!(23 #[derive(Clone)]40 #[derive(Clone)]crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth3use educe::Educe;3use educe::Educe;4use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_gcmodule::{Cc, Trace};5use jrsonnet_interner::IStr;5use jrsonnet_interner::IStr;6use jrsonnet_ir::{Destruct, Expr, ExprParams, Span};6use jrsonnet_ir::Span;7pub use jrsonnet_macros::builtin;7pub use jrsonnet_macros::builtin;889use self::{9use self::{10 builtin::Builtin,10 builtin::Builtin,11 parse::parse_default_function_call,12 prepared::{PreparedCall, parse_prepared_builtin_call, parse_prepared_function_call},11 prepared::{parse_prepared_builtin_call, PreparedCall},13};12};14use crate::{13use crate::{15 Context, Result, Thunk, Val, evaluate, evaluate_trivial, function::builtin::BuiltinFunc,14 analyze::{LDestruct, LExpr, LFunction},15 evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},16 function::builtin::BuiltinFunc,17 Context, ContextBuilder, Result, Thunk, Val,16};18};171918pub mod builtin;20pub mod builtin;19mod native;21mod native;20mod parse;22mod parse;21mod prepared;23pub(crate) mod prepared;222423pub use jrsonnet_ir::function::*;25pub use jrsonnet_ir::function::*;24pub use native::NativeFn;26pub use native::NativeFn;66 /// context will contain `a`.68 /// context will contain `a`.67 pub ctx: Context,69 pub ctx: Context,687069 /// Function parameter definition71 #[educe(PartialEq(method = Rc::ptr_eq))]70 pub params: ExprParams,71 /// Function body72 pub body: Rc<Expr>,72 pub func: Rc<LFunction>,73}73}7474impl FuncDesc {75impl FuncDesc {75 /// Create body context, but fill arguments without defaults with lazy error76 pub fn signature(&self) -> FunctionSignature {77 self.func.signature.clone()78 }7976 pub fn default_body_context(&self) -> Result<Context> {80 pub fn call(81 &self,82 unnamed: &[Thunk<Val>],83 named: &[Thunk<Val>],84 prepared: &PreparedCall,85 ) -> Result<Val> {86 let has_defaults = !prepared.defaults().is_empty();77 parse_default_function_call(self.ctx.clone(), &self.params)87 let mut builder = ContextBuilder::extend(self.ctx.clone(), self.func.params.len());8889 let fctx = Context::new_future();90 for (param_idx, thunk) in unnamed.iter().enumerate() {91 destruct(92 &self.func.params[param_idx].destruct,93 thunk.clone(),94 fctx.clone(),95 &mut builder,96 );97 }9899 for &(param_idx, arg_idx) in prepared.named() {100 destruct(101 &self.func.params[param_idx].destruct,102 named[arg_idx].clone(),103 fctx.clone(),104 &mut builder,105 );106 }107108 if has_defaults {109 for ¶m_idx in prepared.defaults() {110 let param = &self.func.params[param_idx];111 if let Some(default_expr) = ¶m.default {112 let default_expr = default_expr.clone();113 let fctxc = fctx.clone();114 let thunk = Thunk!(move || {115 let ctx = fctxc.unwrap();116 evaluate(ctx, &default_expr)117 });118 destruct(¶m.destruct, thunk, fctx.clone(), &mut builder);119 }120 }121 };122 let ctx = builder.build().into_future(fctx);123 ensure_sufficient_stack(|| evaluate(ctx, &self.func.body))78 }124 }7912580 pub fn evaluate_trivial(&self) -> Option<Val> {126 pub fn evaluate_trivial(&self) -> Option<Val> {81 evaluate_trivial(&self.body)127 evaluate_trivial(&self.func.body)82 }128 }83}129}84130115 pub fn params(&self) -> FunctionSignature {161 pub fn params(&self) -> FunctionSignature {116 match self {162 match self {117 Self::Builtin(i) => i.params(),163 Self::Builtin(i) => i.params(),118 Self::Normal(p) => p.params.signature.clone(),164 Self::Normal(p) => p.signature(),119 }165 }120 }166 }121 /// Amount of non-default required arguments167 /// Amount of non-default required arguments122 pub fn params_len(&self) -> usize {168 pub fn params_len(&self) -> u32 {123 self.params().iter().filter(|p| !p.has_default()).count()169 self.params().iter().filter(|p| !p.has_default()).count() as u32124 }170 }125 /// Function name, as defined in code.171 /// Function name, as defined in code.126 pub fn name(&self) -> IStr {172 pub fn name(&self) -> IStr {139 _tailstrict: bool,185 _tailstrict: bool,140 ) -> Result<Val> {186 ) -> Result<Val> {141 match self {187 match self {142 FuncVal::Normal(func) => {188 FuncVal::Normal(func) => func.call(unnamed, named, prepared),143 let body_ctx = parse_prepared_function_call(144 func.ctx.clone(),145 prepared,146 &func.params,147 unnamed,148 named,149 )?;150 evaluate(body_ctx, &func.body)151 }152 FuncVal::Builtin(b) => {189 FuncVal::Builtin(b) => {153 let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named);190 let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named);154 b.call(loc, &args)191 b.call(loc, &args)155 }192 }156 }193 }157 }194 }158195159 /// Is this function an indentity function.196 /// Is this function an identity function.160 ///197 ///161 /// Currently only works for builtin `std.id`, aka `Self::Id` value, and `function(x) x`.198 /// Currently only works for builtin `std.id`, aka `Self::Id` value, and `function(x) x`.162 ///199 ///165 match self {202 match self {166 Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),203 Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),167 Self::Normal(desc) => {204 Self::Normal(desc) => {168 if desc.params.len() != 1 {205 if desc.func.params.len() != 1 {169 return false;206 return false;170 }207 }171 let param = &desc.params.exprs[0];208 let param = &desc.func.params[0];172 if param.default.is_some() {209 if param.default.is_some() {173 return false;210 return false;174 }211 }175176 #[allow(clippy::infallible_destructuring_match)]212 #[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]177 let id = match ¶m.destruct {213 let LDestruct::Full(id) = ¶m.destruct178 Destruct::Full(id) => id,214 else {179 #[cfg(feature = "exp-destruct")]180 _ => return false,215 return false;181 };216 };182 matches!(&*desc.body, Expr::Var(v) if &**v == id)217 matches!(&*desc.func.body, LExpr::Local(v) if v == id)183 }218 }184 }219 }185 }220 }crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth1use jrsonnet_ir::ExprParams;1use std::rc::Rc;223use crate::{3use crate::{4 Context, ContextBuilder, Thunk,5 destructure::destruct,4 analyze::LFunction,6 error::{ErrorKind::*, Result},5 evaluate::{destructure::destruct, evaluate},7 evaluate_named_param,6 Context, ContextBuilder, Result, Thunk,8};7};9810/// Creates Context, which has all argument default values applied9/// Creates Context with all argument default values applied11/// and with unbound values causing error to be returned10/// and with unbound values causing error to be returned.12pub fn parse_default_function_call(body_ctx: Context, params: &ExprParams) -> Result<Context> {11pub fn parse_default_function_call(body_ctx: Context, func: &Rc<LFunction>) -> Result<Context> {13 let fctx = Context::new_future();12 let fctx = Context::new_future();1415 let mut ctx = ContextBuilder::extend(body_ctx);13 let mut builder = ContextBuilder::extend(body_ctx, func.params.len());161417 for param in params.exprs.iter() {15 for param in &func.params {18 if let Some(v) = ¶m.default {16 if let Some(default_expr) = ¶m.default {19 destruct(17 let default_expr = default_expr.clone();20 ¶m.destruct.clone(),21 {22 let ctx = fctx.clone();18 let fctxc = fctx.clone();23 let name = param.destruct.name();19 let thunk = Thunk!(move || {24 let value = v.clone();25 Thunk!(move || evaluate_named_param(ctx.unwrap(), &value, name))20 let ctx = fctxc.unwrap();21 evaluate(ctx, &default_expr)22 });26 },23 destruct(¶m.destruct, thunk, fctx.clone(), &mut builder);27 fctx.clone(),28 &mut ctx,29 )?;30 } else {24 } else {25 let name = param.name.clone().unwrap_or_else(|| "<param>".into());26 let thunk = Thunk::errored(27 crate::error::ErrorKind::FunctionParameterNotBoundInCall(28 jrsonnet_ir::function::ParamName::Named(name),29 jrsonnet_ir::function::FunctionSignature::empty(),30 )31 .into(),32 );31 destruct(33 destruct(¶m.destruct, thunk, fctx.clone(), &mut builder);32 ¶m.destruct,33 {34 let param_name = param.destruct.name();35 let params = params.clone();36 Thunk!(move || Err(FunctionParameterNotBoundInCall(37 param_name,38 params.signature39 )40 .into()))41 },42 fctx.clone(),43 &mut ctx,44 )?;45 }34 }46 }35 }473648 Ok(ctx.build().into_future(fctx))37 let ctx = builder.build().into_future(fctx);38 Ok(ctx)49}39}5040crates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth1use std::rc::Rc;1use std::rc::Rc;223use jrsonnet_gcmodule::{Acyclic, Trace};3use jrsonnet_gcmodule::{Acyclic, Trace};4use jrsonnet_ir::{ExprParams, IStr, function::FunctionSignature};4use jrsonnet_ir::{IStr, function::FunctionSignature};5use rustc_hash::FxHashSet;5use rustc_hash::FxHashSet;667use super::{CallLocation, FuncVal};7use super::{CallLocation, FuncVal};8use crate::{8use crate::{9 Context, ContextBuilder, Pending, Result, Thunk, Val, bail, destructure::destruct,9 Result, Thunk, Val, bail,10 error::ErrorKind::*, evaluate_named_param,10 error::ErrorKind::*,11};11};121213#[derive(Debug, Trace, Clone)]13#[derive(Debug, Trace, Clone)]42 defaults: Vec<usize>,42 defaults: Vec<usize>,43}43}4445impl PreparedCall {46 pub fn named(&self) -> &[(usize, usize)] {47 &self.named48 }49 pub fn defaults(&self) -> &[usize] {50 &self.defaults51 }52}445345pub fn prepare_call(54pub fn prepare_call(46 params: FunctionSignature,55 params: FunctionSignature,51 bail!(TooManyArgsFunctionHas(params.len(), params))60 bail!(TooManyArgsFunctionHas(params.len(), params))52 }61 }6263 // Fast path: positional-only (no named args). Avoids HashMap entirely.64 if named.is_empty() {65 let mut defaults = Vec::new();66 for (param_id, param) in params.iter().enumerate().skip(unnamed) {67 if param.has_default() {68 defaults.push(param_id);69 } else {70 bail!(FunctionParameterNotBoundInCall(71 param.name().clone(),72 params.clone(),73 ))74 }75 }76 return Ok(PreparedCall {77 named: Vec::new(),78 defaults,79 });80 }538154 let expected_defaults = (params.len() - unnamed).saturating_sub(named.len());82 let expected_defaults = (params.len() - unnamed).saturating_sub(named.len());55 let mut ops = PreparedCall {83 let mut ops = PreparedCall {111139112 Ok(ops)140 Ok(ops)113}141}114pub fn parse_prepared_function_call(115 body_ctx: Context,116 prepared: &PreparedCall,117 params: &ExprParams,118 unnamed: &[Thunk<Val>],119 named: &[Thunk<Val>],120) -> Result<Context> {121 let mut ctx = ContextBuilder::extend(body_ctx);122123 let destruct_ctx = Pending::new();124125 for (param_idx, unnamed) in unnamed.iter().enumerate() {126 destruct(127 ¶ms.exprs[param_idx].destruct,128 unnamed.clone(),129 destruct_ctx.clone(),130 &mut ctx,131 )?;132 }133134 for (param_idx, arg_idx) in prepared.named.iter().copied() {135 destruct(136 ¶ms.exprs[param_idx].destruct,137 named[arg_idx].clone(),138 destruct_ctx.clone(),139 &mut ctx,140 )?;141 }142143 if prepared.defaults.is_empty() {144 let body_ctx = ctx.build().into_future(destruct_ctx);145 Ok(body_ctx)146 } else {147 let fctx = Context::new_future();148 let mut ctx = ctx.commit();149 for param_idx in prepared.defaults.iter().copied() {150 // let param = params.0.rc_idx(param_idx);151 destruct(152 ¶ms.exprs[param_idx].destruct,153 {154 let ctx = fctx.clone();155 let params = params.clone();156 Thunk!(move || {157 let param = ¶ms.exprs[param_idx];158 let name = param.destruct.name();159 let value = param.default.as_ref().expect("default exists");160 evaluate_named_param(ctx.unwrap(), value, name)161 })162 },163 fctx.clone(),164 &mut ctx,165 )?;166 }167168 Ok(ctx.build().into_future(fctx).into_future(destruct_ctx))169 }170}171pub fn parse_prepared_builtin_call(142pub fn parse_prepared_builtin_call(172 prepared: &PreparedCall,143 prepared: &PreparedCall,173 params: FunctionSignature,144 params: FunctionSignature,crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth3use jrsonnet_interner::{IBytes, IStr};3use jrsonnet_interner::{IBytes, IStr};4use jrsonnet_ir::NumValue;4use jrsonnet_ir::NumValue;5use serde::{5use serde::{6 Deserialize, Serialize, Serializer,7 de::{self, Visitor},6 de::{self, Visitor},8 ser::{7 ser::{9 Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,8 Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,10 SerializeTupleStruct, SerializeTupleVariant,9 SerializeTupleStruct, SerializeTupleVariant,11 },10 },11 Deserialize, Serialize, Serializer,12};12};131314use crate::{14use crate::{15 Error as JrError, ObjValue, ObjValueBuilder, Result, Val, in_description_frame, runtime_error,15 in_description_frame, runtime_error, Error as JrError, ObjValue, ObjValueBuilder, Result, Val,16};16};171718impl<'de> Deserialize<'de> for Val {18impl<'de> Deserialize<'de> for Val {182 #[cfg(feature = "exp-bigint")]182 #[cfg(feature = "exp-bigint")]183 Self::BigInt(b) => b.serialize(serializer),183 Self::BigInt(b) => b.serialize(serializer),184 Self::Arr(arr) => {184 Self::Arr(arr) => {185 let mut seq = serializer.serialize_seq(Some(arr.len()))?;185 let mut seq = serializer.serialize_seq(Some(arr.len() as usize))?;186 for (i, element) in arr.iter().enumerate() {186 for (i, element) in arr.iter().enumerate() {187 let mut serde_error = None;187 let mut serde_error = None;188 in_description_frame(188 in_description_frame(203 seq.end()203 seq.end()204 }204 }205 Self::Obj(obj) => {205 Self::Obj(obj) => {206 let mut map = serializer.serialize_map(Some(obj.len()))?;206 let mut map = serializer.serialize_map(Some(obj.len() as usize))?;207 for (field, value) in obj.iter(207 for (field, value) in obj.iter(208 #[cfg(feature = "exp-preserve-order")]208 #[cfg(feature = "exp-preserve-order")]209 true,209 true,crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth36pub use ctx::*;36pub use ctx::*;37pub use dynamic::*;37pub use dynamic::*;38pub use error::{Error, ErrorKind::*, Result, ResultExt};38pub use error::{Error, ErrorKind::*, Result, ResultExt};39pub use evaluate::*;39pub use evaluate::ensure_sufficient_stack;40use function::CallLocation;40use function::CallLocation;41pub use import::*;41pub use import::*;42use jrsonnet_gcmodule::{Cc, Trace, cc_dyn};42use jrsonnet_gcmodule::{cc_dyn, Cc, Trace};43pub use jrsonnet_interner::{IBytes, IStr};43pub use jrsonnet_interner::{IBytes, IStr};44pub use jrsonnet_ir as parser;45pub use jrsonnet_ir::NumValue;44use jrsonnet_ir::Expr;46use jrsonnet_ir::{Expr, Source, SourcePath};45pub use jrsonnet_ir::{NumValue, Source, SourcePath, Span};47#[doc(hidden)]46#[doc(hidden)]48pub use jrsonnet_macros;47pub use jrsonnet_macros;494858pub use tla::apply_tla;57pub use tla::apply_tla;59pub use val::{Thunk, Val};58pub use val::{Thunk, Val};605960pub mod analyze;61use crate::gc::WithCapacityExt as _;61use crate::gc::WithCapacityExt as _;626263#[allow(clippy::needless_return)]63#[allow(clippy::needless_return)]87 jrsonnet_ir_parser::parse(code, &jrsonnet_ir_parser::ParserSettings { source }).map_err(|e| {87 jrsonnet_ir_parser::parse(code, &jrsonnet_ir_parser::ParserSettings { source }).map_err(|e| {88 SyntaxError {88 SyntaxError {89 message: e.message,89 message: e.message,90 location: (e.location.0, e.location.1),90 location: e.location,91 }91 }92 })92 })93}93}165pub trait ContextInitializer {165pub trait ContextInitializer {166 /// For composability: extend builder. May panic if this initialization is not supported,166 /// For composability: extend builder. May panic if this initialization is not supported,167 /// and the context may only be created via `initialize`.167 /// and the context may only be created via `initialize`.168 fn populate(&self, for_file: Source, builder: &mut ContextBuilder);168 fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder);169 /// Allows upcasting from abstract to concrete context initializer.169 /// Allows upcasting from abstract to concrete context initializer.170 /// jrsonnet by itself doesn't use this method, it is allowed for it to panic.170 /// jrsonnet by itself doesn't use this method, it is allowed for it to panic.171 fn as_any(&self) -> &dyn Any;171 fn as_any(&self) -> &dyn Any;174where174where175 T: ContextInitializer,175 T: ContextInitializer,176{176{177 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {177 fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder) {178 (*self).populate(for_file, builder);178 (*self).populate(for_file, builder);179 }179 }180180185185186/// Context initializer which adds nothing.186/// Context initializer which adds nothing.187impl ContextInitializer for () {187impl ContextInitializer for () {188 fn populate(&self, _for_file: Source, _builder: &mut ContextBuilder) {}188 fn populate(&self, _for_file: Source, _builder: &mut InitialContextBuilder) {}189 fn as_any(&self) -> &dyn Any {189 fn as_any(&self) -> &dyn Any {190 self190 self191 }191 }195where195where196 T: ContextInitializer + 'static,196 T: ContextInitializer + 'static,197{197{198 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {198 fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder) {199 if let Some(ctx) = self {199 if let Some(ctx) = self {200 ctx.populate(for_file, builder);200 ctx.populate(for_file, builder);201 }201 }210 ($($gen:ident)*) => {210 ($($gen:ident)*) => {211 #[allow(non_snake_case)]211 #[allow(non_snake_case)]212 impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {212 impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {213 fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {213 fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder) {214 let ($($gen,)*) = self;214 let ($($gen,)*) = self;215 $($gen.populate(for_file.clone(), builder);)*215 $($gen.populate(for_file.clone(), builder);)*216 }216 }408 file.evaluating = true;408 file.evaluating = true;409 // Dropping file cache guard here, as evaluation may use this map too409 // Dropping file cache guard here, as evaluation may use this map too410 drop(file_cache);410 drop(file_cache);411 let (ctx, externals) = self.create_default_context(file_name.clone()).build();412 let report = analyze::analyze_root(&parsed, externals);413 if report.errored {414 return Err(StaticAnalysisError(report.diagnostics_list).into());415 }411 let res = evaluate(self.create_default_context(file_name), &parsed);416 let res = evaluate::evaluate(ctx.build(), &report.lir);412417413 let mut file_cache = self.file_cache();418 let mut file_cache = self.file_cache();414 let mut file = file_cache.entry(path);419 let mut file = file_cache.entry(path);438 }443 }439444440 /// Creates context with all passed global variables445 /// Creates context with all passed global variables441 pub fn create_default_context(&self, source: Source) -> Context {446 pub fn create_default_context(&self, source: Source) -> InitialContextBuilder {442 self.create_default_context_with(source, &())447 self.create_default_context_with(source, &())443 }448 }444449447 &self,452 &self,448 source: Source,453 source: Source,449 context_initializer: &dyn ContextInitializer,454 context_initializer: &dyn ContextInitializer,450 ) -> Context {455 ) -> InitialContextBuilder {451 let default_initializer = self.context_initializer();456 let default_initializer = self.context_initializer();452 let mut builder = ContextBuilder::new();457 let mut builder = InitialContextBuilder::new();453 default_initializer.populate(source.clone(), &mut builder);458 default_initializer.populate(source.clone(), &mut builder);454 context_initializer.populate(source, &mut builder);459 context_initializer.populate(source, &mut builder);455460456 builder.build()461 builder457 }462 }458}463}459464487#[derive(Trace)]492#[derive(Trace)]488pub struct InitialUnderscore(pub Thunk<Val>);493pub struct InitialUnderscore(pub Thunk<Val>);489impl ContextInitializer for InitialUnderscore {494impl ContextInitializer for InitialUnderscore {490 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {495 fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {491 builder.bind("_", self.0.clone());496 builder.bind("_", self.0.clone());492 }497 }493498515 path: source.clone(),520 path: source.clone(),516 error: Box::new(e),521 error: Box::new(e),517 })?;522 })?;518 evaluate(519 self.create_default_context_with(source, context_initializer),523 let (ctx, externals) = self520 &parsed,524 .create_default_context_with(source.clone(), context_initializer)521 )525 .build();526 let report = analyze::analyze_root(&parsed, externals);527 if report.errored {528 return Err(StaticAnalysisError(report.diagnostics_list).into());529 }530 evaluate::evaluate(ctx.build(), &report.lir)522 }531 }523}532}524533crates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth11};11};121213use educe::Educe;13use educe::Educe;14use im_rc::{Vector, vector};14use im_rc::{vector, Vector};15use jrsonnet_gcmodule::{Acyclic, Cc, Trace, Weak, cc_dyn};15use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};16use jrsonnet_interner::IStr;16use jrsonnet_interner::IStr;17use jrsonnet_ir::Span;17use jrsonnet_ir::Span;18use rustc_hash::{FxHashMap, FxHashSet};18use rustc_hash::{FxHashMap, FxHashSet};23pub use oop::ObjValueBuilder;23pub use oop::ObjValueBuilder;242425use crate::{25use crate::{26 CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,27 arr::{PickObjectKeyValues, PickObjectValues},26 arr::{PickObjectKeyValues, PickObjectValues},28 bail,27 bail,29 error::{ErrorKind::*, suggest_object_fields},28 error::{suggest_object_fields, ErrorKind::*},30 identity_hash,31 operator::evaluate_add_op,29 evaluate::operator::evaluate_add_op,30 identity_hash,32 val::{ArrValue, ThunkValue},31 val::{ArrValue, ThunkValue},32 CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,33};33};343435#[cfg(not(feature = "exp-preserve-order"))]35#[cfg(not(feature = "exp-preserve-order"))]400 this: ObjValue,400 this: ObjValue,401}401}402impl SupThis {402impl SupThis {403 /// Create a `SupThis` for a freshly constructed object (no super).404 pub fn new(this: ObjValue) -> Self {405 Self {406 sup: CoreIdx {407 idx: this.0.cores.len(),408 },409 this,410 }411 }403 pub fn has_super(&self) -> bool {412 pub fn has_super(&self) -> bool {404 self.sup.super_exists()413 self.sup.super_exists()405 }414 }501 // }510 // }502 /// Returns amount of visible object fields511 /// Returns amount of visible object fields503 /// If object only contains hidden fields - may return zero.512 /// If object only contains hidden fields - may return zero.504 pub fn len(&self) -> usize {513 pub fn len(&self) -> u32 {505 self.fields_visibility()514 self.fields_visibility()506 .values()515 .values()507 .filter(|d| d.visible())516 .filter(|d| d.visible())508 .count()517 .count() as u32509 }518 }510 /// For each field, calls callback.519 /// For each field, calls callback.511 /// If callback returns false - ends iteration prematurely.520 /// If callback returns false - ends iteration prematurely.crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth10#[cfg(feature = "explaining-traces")]10#[cfg(feature = "explaining-traces")]11use jrsonnet_ir::Span;11use jrsonnet_ir::Span;121213use crate::{Error, error::ErrorKind};13use crate::{error::ErrorKind, Error};141415/// The way paths should be displayed15/// The way paths should be displayed16#[derive(Clone, Trace)]16#[derive(Clone, Trace)]122 || path.source_path().to_string(),122 || path.source_path().to_string(),123 |r| self.resolver.resolve(r),123 |r| self.resolver.resolve(r),124 );124 );125 let mut offset = error.location.0 as usize;125 let mut offset = error.location.1 as usize;126 let is_eof = if offset >= path.code().len() {126 let is_eof = if offset >= path.code().len() {127 offset = path.code().len().saturating_sub(1);127 offset = path.code().len().saturating_sub(1);128 true128 true259 struct ResetData {259 struct ResetData {260 loc: Span,260 loc: Span,261 }261 }262 use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi};262 use hi_doc::{source_to_ansi, Formatting, SnippetBuilder, Text};263263264 write!(out, "{}", error.error())?;264 write!(out, "{}", error.error())?;265 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {265 if let ErrorKind::ImportSyntaxError { path, error } = error.error() {266 writeln!(out)?;266 writeln!(out)?;267 let mut offset = error.location;268 // To inclusive range269 if offset.1 > offset.0 {270 offset.1 -= 1;271 }272 let mut builder = SnippetBuilder::new(path.code());267 let mut builder = SnippetBuilder::new(path.code());273 builder268 builder274 .error(Text::fragment("syntax error", Formatting::default()))269 .error(Text::fragment("syntax error", Formatting::default()))275 .range(offset.0 as usize..=offset.1 as usize)270 .range(error.location.range())276 .build();271 .build();277 let source = builder.build();272 let source = builder.build();278 let ansi = source_to_ansi(&source);273 let ansi = source_to_ansi(&source);279 write!(out, "{ansi}")?;274 write!(out, "{ansi}")?;280 }275 }276 if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() {277 use crate::analyze::DiagLevel;278 let mut builder: Option<SnippetBuilder> = None;279 let mut current_src: Option<&str> = None;280 let flush =281 |builder: Option<SnippetBuilder>, out: &mut dyn std::fmt::Write| -> Result<(), std::fmt::Error> {282 if let Some(b) = builder {283 let ansi = source_to_ansi(&b.build());284 write!(out, "\n{}", ansi.trim_end())?;285 }286 Ok(())287 };288 for diag in diagnostics {289 if let Some(span) = &diag.span {290 let src = span.0.code();291 if current_src != Some(src) {292 flush(builder.take(), out)?;293 builder = Some(SnippetBuilder::new(src));294 current_src = Some(src);295 }296 let b = builder.as_mut().unwrap();297 let ab = match diag.level {298 DiagLevel::Error => b.error(Text::fragment(299 diag.message.clone(),300 Formatting::default(),301 )),302 DiagLevel::Warning => b.warning(Text::fragment(303 diag.message.clone(),304 Formatting::default(),305 )),306 };307 ab.range(span.range()).build();308 } else {309 flush(builder.take(), out)?;310 current_src = None;311 let prefix = match diag.level {312 DiagLevel::Error => "error",313 DiagLevel::Warning => "warning",314 };315 write!(out, "\n{prefix}: {}", diag.message)?;316 }317 }318 flush(builder, out)?;319 }281 let trace = &error.trace();320 let trace = &error.trace();282 let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);321 let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);283 let mut last_location: Option<Span> = None;322 let mut last_location: Option<Span> = None;crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth637 }637 }638 <Self as Typed>::TYPE.check(&value)?;638 <Self as Typed>::TYPE.check(&value)?;639 // Any::downcast_ref::<ByteArray>(&a);639 // Any::downcast_ref::<ByteArray>(&a);640 let mut out = Vec::with_capacity(a.len());640 let mut out = Vec::with_capacity(a.len() as usize);641 for e in a.iter() {641 for e in a.iter() {642 let r = e?;642 let r = e?;643 out.push(u8::from_untyped(r)?);643 out.push(u8::from_untyped(r)?);crates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth171 let mut pool = pool.borrow_mut();171 let mut pool = pool.borrow_mut();172172173 if pool.remove(inner).is_none() {173 if pool.remove(inner).is_none() {174 // DOC(string-pooling)174 // On some platforms (i.e i686-windows), try_with will not fail after TLS175 // On some platforms (i.e i686-windows), try_with will not fail after TLS175 // destructor is called, but instead re-initialize the TLS with the empty pool.176 // destructor is called, but instead re-initialize the TLS with the empty pool.176 // Allow non-pooled Drop in this case.177 // Allow non-pooled Drop in this case.crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth3use proc_macro2::TokenStream;3use proc_macro2::TokenStream;4use quote::{quote, quote_spanned};4use quote::{quote, quote_spanned};5use syn::{5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::Comma,6 Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,12 Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,7 LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type, parenthesized,13 LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type,8 parse::{Parse, ParseStream},9 parse_macro_input,10 punctuated::Punctuated,11 spanned::Spanned,12 token::Comma,13};14};141515use self::typed::{derive_from_untyped_inner, derive_into_untyped_inner, derive_typed_inner};16use self::typed::{derive_from_untyped_inner, derive_into_untyped_inner, derive_typed_inner};402 State, Val,403 State, Val,403 function::{builtin::Builtin, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation},404 function::{builtin::Builtin, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation},404 Result, Context, typed::{Typed, FromUntyped, IntoUntypedResult},405 Result, Context, typed::{Typed, FromUntyped, IntoUntypedResult},405 parser::Span, params, Thunk,406 Span, params, Thunk,406 };407 };407 params!(408 params!(408 #(#params_desc)*409 #(#params_desc)*crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth1#![allow(non_snake_case)]1#![allow(non_snake_case)]223use jrsonnet_evaluator::{3use jrsonnet_evaluator::{4 Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, bail,4 bail, error,5 function::{FuncVal, NativeFn, builtin},5 function::{builtin, NativeFn},6 runtime_error,6 runtime_error,7 typed::{BoundedI32, BoundedUsize, Either2, FromUntyped},7 typed::{BoundedUsize, Either2, FromUntyped},8 val::{ArrValue, IndexableVal, equals},8 val::{equals, ArrValue, IndexableVal},9 Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,9};10};101111pub fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {12pub fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {17}18}181919#[builtin]20#[builtin]20pub fn builtin_make_array(21pub fn builtin_make_array(sz: u32, func: NativeFn!((u32,) -> Val)) -> Result<ArrValue> {21 // Can't use usize because range_exclusive is over i3222 sz: BoundedI32<0, { i32::MAX }>,23 func: FuncVal,24) -> Result<ArrValue> {25 if *sz == 0 {22 if sz == 0 {26 return Ok(ArrValue::empty());23 return Ok(ArrValue::empty());27 }24 }28 func.evaluate_trivial().map_or_else(25 // Try eager evaluation: call func(i) immediately for each element.29 // TODO: Different mapped array impl avoiding allocating unnecessary vals30 || Ok(ArrValue::range_exclusive(0, *sz).map(FromUntyped::from_untyped(Val::Func(func))?)),31 |trivial| {26 'eager: {32 #[expect(clippy::cast_sign_loss, reason = "sz is bounded to be larger than 0")]33 let mut out = Vec::with_capacity(*sz as usize);27 let mut out = Vec::with_capacity(sz as usize);34 for _ in 0..*sz {28 for i in 0..sz {29 match func.call(i) {35 out.push(trivial.clone());30 Ok(v) => out.push(v),31 Err(_) => break 'eager,36 }32 }33 }37 Ok(ArrValue::new(out))34 return Ok(ArrValue::new(out));38 },35 }39 )36 Ok(ArrValue::make(sz, func))40}37}413842#[builtin]39#[builtin]43pub fn builtin_repeat(what: Either![IStr, ArrValue], count: usize) -> Result<Val> {40pub fn builtin_repeat(what: Either![IStr, ArrValue], count: u32) -> Result<Val> {44 Ok(match what {41 Ok(match what {45 Either2::A(s) => Val::string(s.repeat(count)),42 Either2::A(s) => Val::string(s.repeat(count as usize)),46 Either2::B(arr) => Val::Arr(43 Either2::B(arr) => Val::Arr(47 ArrValue::repeated(arr, count)44 ArrValue::repeated(arr, count)48 .ok_or_else(|| runtime_error!("repeated length overflow"))?,45 .ok_or_else(|| runtime_error!("repeated length overflow"))?,210 let item = item?.clone();207 let item = item?.clone();211 if let Val::Arr(items) = item {208 if let Val::Arr(items) = item {212 if !first {209 if !first {213 out.reserve(joiner_items.len());210 out.reserve(joiner_items.len() as usize);214 // TODO: extend211 // TODO: extend215 for item in joiner_items.iter() {212 for item in joiner_items.iter() {216 out.push(item?);213 out.push(item?);217 }214 }218 }215 }219 first = false;216 first = false;220 out.reserve(items.len());217 out.reserve(items.len() as usize);221 for item in items.iter() {218 for item in items.iter() {222 out.push(item?);219 out.push(item?);223 }220 }257 builtin_join(254 builtin_join(258 IndexableVal::Str("\n".into()),255 IndexableVal::Str("\n".into()),259 ArrValue::extended(arr, ArrValue::new(vec![Val::string("")])),256 ArrValue::extended(arr, ArrValue::new(vec![Val::string("")]))257 .ok_or_else(|| error!("array is too large"))?,260 )258 )261}259}262260380 let newArrLeft = arr.clone().slice(None, Some(at), None);378 let newArrLeft = arr.clone().slice(None, Some(at), None);381 let newArrRight = arr.slice(Some(at + 1), None, None);379 let newArrRight = arr.slice(Some(at + 1), None, None);382380383 Ok(ArrValue::extended(newArrLeft, newArrRight))381 Ok(ArrValue::extended(newArrLeft, newArrRight).ok_or_else(|| error!("array is too large"))?)384}382}385383386#[builtin]384#[builtin]399}397}400398401#[builtin]399#[builtin]402pub fn builtin_flatten_arrays(arrs: Vec<ArrValue>) -> ArrValue {400pub fn builtin_flatten_arrays(arrs: Vec<ArrValue>) -> Result<ArrValue> {403 pub fn flatten_inner(values: &[ArrValue]) -> ArrValue {401 pub fn flatten_inner(values: &[ArrValue]) -> Result<ArrValue> {404 if values.len() == 1 {402 if values.len() == 1 {405 return values[0].clone();403 return Ok(values[0].clone());406 } else if values.len() == 2 {404 } else if values.len() == 2 {407 return ArrValue::extended(values[0].clone(), values[1].clone());405 return ArrValue::extended(values[0].clone(), values[1].clone())406 .ok_or_else(|| error!("array is too large"));408 }407 }409 let (a, b) = values.split_at(values.len() / 2);408 let (a, b) = values.split_at(values.len() / 2);410 ArrValue::extended(flatten_inner(a), flatten_inner(b))409 ArrValue::extended(flatten_inner(a)?, flatten_inner(b)?)410 .ok_or_else(|| error!("array is too large"))411 }411 }412 if arrs.is_empty() {412 if arrs.is_empty() {413 return ArrValue::empty();413 return Ok(ArrValue::empty());414 } else if arrs.len() == 1 {414 } else if arrs.len() == 1 {415 return arrs.into_iter().next().expect("single");415 return Ok(arrs.into_iter().next().expect("single"));416 }416 }417 flatten_inner(&arrs)417 flatten_inner(&arrs)418}418}crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth12pub use encoding::*;12pub use encoding::*;13pub use hash::*;13pub use hash::*;14use jrsonnet_evaluator::{14use jrsonnet_evaluator::{15 ContextBuilder, IStr, NumValue, ObjValue, ObjValueBuilder, Thunk, Val,15 IStr, InitialContextBuilder, NumValue, ObjValue, ObjValueBuilder, Source, Thunk, Val, error::Result, function::{CallLocation, FuncVal, builtin_id}, tla::TlaArg, trace::PathResolver, typed::SerializeTypedObj as _16 error::Result,17 function::{CallLocation, FuncVal, builtin_id},18 tla::TlaArg,19 trace::PathResolver,20 typed::SerializeTypedObj as _,21};16};22use jrsonnet_gcmodule::{Acyclic, Cc, Trace};17use jrsonnet_gcmodule::{Acyclic, Cc, Trace};23use jrsonnet_ir::Source;24use jrsonnet_macros::{IntoUntyped, Typed};18use jrsonnet_macros::{IntoUntyped, Typed};25pub use manifest::*;19pub use manifest::*;26pub use math::*;20pub use math::*;544 }538 }545}539}546impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {540impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {547 fn populate(&self, source: Source, builder: &mut ContextBuilder) {541 fn populate(&self, source: Source, builder: &mut InitialContextBuilder) {548 let mut std = ObjValueBuilder::new();542 let mut std = ObjValueBuilder::new();549 std.with_super(self.stdlib_obj.clone());543 std.with_super(self.stdlib_obj.clone());550 std.field("thisFile").hide().value({544 std.field("thisFile").hide().value({crates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth1use std::{cell::RefCell, collections::BTreeSet};1use std::{cell::RefCell, collections::BTreeSet};223use jrsonnet_evaluator::{3use jrsonnet_evaluator::{4 Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val, bail,4 bail,5 error::{ErrorKind::*, Result},5 error::{ErrorKind::*, Result},6 function::{CallLocation, FuncVal, builtin},6 function::{builtin, CallLocation, FuncVal},7 manifest::JsonFormat,7 manifest::JsonFormat,8 typed::{Either2, Either4},8 typed::{Either2, Either4},9 val::{ArrValue, equals},9 val::{equals, ArrValue},10 Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,10};11};11use jrsonnet_gcmodule::Cc;12use jrsonnet_gcmodule::Cc;121313use crate::Settings;14use crate::Settings;141515#[builtin]16#[builtin]16pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> usize {17pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> u32 {17 use Either4::*;18 use Either4::*;18 match x {19 match x {19 A(x) => x.chars().count(),20 A(x) => x.chars().count() as u32,20 B(x) => x.len(),21 B(x) => x.len(),21 C(x) => x.len(),22 C(x) => x.len(),22 D(f) => f.params_len(),23 D(f) => f.params_len(),102 } else if b.len() == a.len() {103 } else if b.len() == a.len() {103 return equals(&Val::Arr(a), &Val::Arr(b));104 return equals(&Val::Arr(a), &Val::Arr(b));104 }105 }105 for (a, b) in a.iter().take(b.len()).zip(b.iter()) {106 for (a, b) in a.iter().take(b.len() as usize).zip(b.iter()) {106 let a = a?;107 let a = a?;107 let b = b?;108 let b = b?;108 if !equals(&a, &b)? {109 if !equals(&a, &b)? {127 return equals(&Val::Arr(a), &Val::Arr(b));128 return equals(&Val::Arr(a), &Val::Arr(b));128 }129 }129 let a_len = a.len();130 let a_len = a.len();130 for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {131 for (a, b) in a.iter().skip((a_len - b.len()) as usize).zip(b.iter()) {131 let a = a?;132 let a = a?;132 let b = b?;133 let b = b?;133 if !equals(&a, &b)? {134 if !equals(&a, &b)? {tests/tests/builtin.rsdiffbeforeafterboth1mod common;1mod common;223use jrsonnet_evaluator::{3use jrsonnet_evaluator::{4 ContextBuilder, ContextInitializer, FileImportResolver, Result, State, Thunk, Val,4 ContextInitializer, FileImportResolver, InitialContextBuilder, Result, Source, State, Thunk, Val, function::{CallLocation, FuncVal, builtin, builtin::{Builtin}}, trace::PathResolver, typed::FromUntyped5 function::{CallLocation, FuncVal, builtin, builtin::Builtin},6 parser::Source,7 trace::PathResolver,8 typed::FromUntyped,9};5};10use jrsonnet_gcmodule::Trace;6use jrsonnet_gcmodule::Trace;11use jrsonnet_stdlib::ContextInitializer as StdContextInitializer;7use jrsonnet_stdlib::ContextInitializer as StdContextInitializer;31#[derive(Trace)]27#[derive(Trace)]32struct NativeAddContextInitializer;28struct NativeAddContextInitializer;33impl ContextInitializer for NativeAddContextInitializer {29impl ContextInitializer for NativeAddContextInitializer {34 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {30 fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {35 builder.bind("nativeAdd", Thunk::evaluated(Val::function(native_add {})));31 builder.bind("nativeAdd", Thunk::evaluated(Val::function(native_add {})));36 }32 }373376#[derive(Trace)]72#[derive(Trace)]77struct CurryAddContextInitializer;73struct CurryAddContextInitializer;78impl ContextInitializer for CurryAddContextInitializer {74impl ContextInitializer for CurryAddContextInitializer {79 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {75 fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {80 builder.bind("curryAdd", Thunk::evaluated(Val::function(curry_add {})));76 builder.bind("curryAdd", Thunk::evaluated(Val::function(curry_add {})));81 }77 }8278tests/tests/common.rsdiffbeforeafterboth1use jrsonnet_evaluator::{1use jrsonnet_evaluator::{2 ContextBuilder, ContextInitializer as ContextInitializerT, ObjValueBuilder, Result, Thunk, Val,2 ContextBuilder, ContextInitializer as ContextInitializerT, InitialContextBuilder, ObjValueBuilder, Result, Thunk, Val, bail, function::{FuncVal, builtin}, Source3 bail,4 function::{FuncVal, builtin},5 parser::Source,6};3};7use jrsonnet_gcmodule::Trace;4use jrsonnet_gcmodule::Trace;8568#[allow(dead_code)]65#[allow(dead_code)]69pub struct ContextInitializer;66pub struct ContextInitializer;70impl ContextInitializerT for ContextInitializer {67impl ContextInitializerT for ContextInitializer {71 fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {68 fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {72 let mut bobj = ObjValueBuilder::new();69 let mut bobj = ObjValueBuilder::new();73 bobj.method("assertThrow", assert_throw {});70 bobj.method("assertThrow", assert_throw {});74 bobj.method("paramNames", param_names {});71 bobj.method("paramNames", param_names {});tests/tests/cpp_test_suite.rsdiffbeforeafterboth9 gc::WithCapacityExt as _,9 gc::WithCapacityExt as _,10 manifest::JsonFormat,10 manifest::JsonFormat,11 rustc_hash::FxHashMap,11 rustc_hash::FxHashMap,12 stack::limit_stack_depth,12 tla::TlaArg,13 tla::TlaArg,13 trace::{CompactFormat, PathResolver, TraceFormat},14 trace::{CompactFormat, PathResolver, TraceFormat},14};15};16use jrsonnet_gcmodule::ObjectSpace;15use jrsonnet_stdlib::ContextInitializer;17use jrsonnet_stdlib::ContextInitializer;16mod common;18mod common;17use common::ContextInitializer as TestContextInitializer;19use common::ContextInitializer as TestContextInitializer;179 continue;181 continue;180 }182 }183184 let _stack = if entry.path().file_stem().is_some_and(|e| e == "recursive_function" || e == "tailstrict"|| e == "tailstrict5") {185 Some(limit_stack_depth(100_000))186 } else {187 None188 };181189182 if entry190 if entry183 .path()191 .path()188 continue;196 continue;189 }197 }190198191 println!("test: {}", entry.path().display());199 eprintln!("test: {}", entry.path().display());192200193 let result = run(&entry.path(), &root);201 let result = run(&entry.path(), &root);194202213 golden = Some(golden_path);221 golden = Some(golden_path);214 }222 }215216 // ir-parser has its own override layer217 #[cfg(feature = "ir-parser")]218 let ir_parser_override_path = {219 let p = root_tests220 .join(format!("{root_dir}_golden_override_ir_parser"))221 .join(golden_path.file_name().expect("file has basename"));222 if let Some(golden_path) = read_file(&p)? {223 golden = Some(golden_path);224 }225 p226 };227223228 // Otherwise assume test should just not fail and return true.224 // Otherwise assume test should just not fail and return true.229 let golden = golden.unwrap_or_else(|| "true".to_owned());225 let golden = golden.unwrap_or_else(|| "true".to_owned());230226231 #[cfg(feature = "ir-parser")]232 let update_golden_path = &ir_parser_override_path;233 #[cfg(not(feature = "ir-parser"))]234 let update_golden_path = &golden_override;227 let update_golden_path = &golden_override;235228236 match (serde_json::from_str::<serde_json::Value>(&result), serde_json::from_str::<serde_json::Value>(&golden)) {229 match (serde_json::from_str::<serde_json::Value>(&result), serde_json::from_str::<serde_json::Value>(&golden)) {270 }263 }271 }264 }272 }265 }266 println!("done!");273 }267 }274 }268 }269270 jrsonnet_gcmodule::with_thread_object_space(ObjectSpace::leak);275271276 Ok(())272 Ok(())277}273}tests/tests/snapshots/golden__golden@issue172.jsonnet.snapdiffbeforeafterboth3expression: result3expression: result4input_file: tests/golden/issue172.jsonnet4input_file: tests/golden/issue172.jsonnet5---5---6local is not defined: b6static analysis errors: undefined local: b7 issue172.jsonnet:1:45-47: local <b> access8 issue172.jsonnet:1:4-10: field <value> access9 elem <0> evaluation107tests/tests/snapshots/golden__golden@issue23.jsonnet.snapdiffbeforeafterboth4input_file: tests/golden/issue23.jsonnet4input_file: tests/golden/issue23.jsonnet5---5---6infinite recursion detected6infinite recursion detected7 issue23.jsonnet:1:1-8: import "issue23.jsonnet"7 issue23.jsonnet:1:1-8: import88tests/tests/snapshots/golden__golden@missing_binding.jsonnet.snapdiffbeforeafterboth3expression: result3expression: result4input_file: tests/golden/missing_binding.jsonnet4input_file: tests/golden/missing_binding.jsonnet5---5---6local is not defined: sta6static analysis errors: undefined local: sta7There is a local with similar name present: std7There is a local with similar name present: std8 missing_binding.jsonnet:1:1-5: local <sta> access98