git.delta.rocks / jrsonnet / refs/commits / d5225b820ddc

difftreelog

refactor(evaluator) use static analysis

qpqzvqtlYaroslav Bolyukin2026-04-25parent: #1979dbd.patch.diff
in: master

31 files changed

modifiedCargo.lockdiffbeforeafterboth
815dependencies = [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]
903908
904[[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",
modifiedbindings/jsonnet/src/val_modify.rsdiffbeforeafterboth
44
5use std::{ffi::CStr, os::raw::c_char};5use std::{ffi::CStr, os::raw::c_char};
66
7use jrsonnet_evaluator::{Thunk, Val, val::ArrValue};7use jrsonnet_evaluator::{Thunk, Val};
88
9use crate::VM;9use crate::VM;
1010
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
77 "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 = true
8082
81[build-dependencies]83[build-dependencies]
82rustversion = "1.0.22"84rustversion = "1.0.22"
85
86[dev-dependencies]
87insta.workspace = true
88strip-ansi-escapes = "0.2.1"
8389
modifiedcrates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth
5 rc::Rc,5 rc::Rc,
6};6};
77
8use jrsonnet_gcmodule::{Cc, cc_dyn};8use jrsonnet_gcmodule::{cc_dyn, Cc};
9use jrsonnet_ir::Expr;
109
11use crate::{Context, Result, Thunk, Val, function::NativeFn, typed::IntoUntyped};10use crate::{analyze::LExpr, function::NativeFn, typed::IntoUntyped, Context, Result, Thunk, Val};
1211
13mod spec;12mod spec;
14pub use spec::{ArrayLike, *};13pub use spec::{ArrayLike, *};
37 Self::new(())36 Self::new(())
38 }37 }
3938
40 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 }
4342
44 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 }
46
47 pub fn make(len: u32, cb: NativeFn!((u32,)->Val)) -> Self {
48 Self::new(MakeArray::new(len, cb))
49 }
4750
48 #[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 }
8184
82 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 b
85 } else if b.is_empty() {88 } else if b.is_empty() {
86 a89 a
87 } else {90 } else {
88 Self::new(ExtendedArray::new(a, b))91 Self::new(ExtendedArray::new(a, b)?)
89 }92 })
90 }93 }
9194
92 pub fn range_exclusive(a: i32, b: i32) -> Self {95 pub fn range_exclusive(a: i32, b: i32) -> Self {
98101
99 #[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 }
128131
129 /// 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 }
133136
143 /// 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 }
149152
150 /// 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 }
156159
modifiedcrates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth
88
9use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_gcmodule::{Cc, Trace};
10use jrsonnet_interner::{IBytes, IStr};10use jrsonnet_interner::{IBytes, IStr};
11use jrsonnet_ir::Expr;
1211
13use 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};
2222
23pub 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() == 0
27 }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>>;
3030
31 fn is_cheap(&self) -> bool {31 fn is_cheap(&self) -> bool {
32 false32 false
33 }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 T
40where40where
41 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 }
4646
47 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 }
5050
51 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 }
5454
58}58}
5959
60impl ArrayCheap for () {60impl ArrayCheap for () {
61 fn len(&self) -> usize {61 fn len(&self) -> u32 {
62 062 0
63 }63 }
64 fn get(&self, _index: usize) -> Option<Val> {64 fn get(&self, _index: u32) -> Option<Val> {
65 None65 None
66 }66 }
67}67}
75}75}
7676
77impl 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 * index
80 }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 }
8686
87 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 }
9090
91 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 }
9494
98}98}
9999
100impl 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 u32
103 }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 u32
135 }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 }
146148
147 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 };
152155
153 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}
218216
253 }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 }
270268
271 fn len(&self) -> usize {269 fn len(&self) -> u32 {
272 self.len270 self.len
273 }271 }
274272
282 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 }
288286
289 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 }
295293
296 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 }
300298
324 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}
344342
345#[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 }
351349
352 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 }
355353
356 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 }
359357
379 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 u32
400 }394 }
401395
402 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 }
412406
413 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 u32
476 }
477
478 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 }
488
489 let ArrayThunk::Waiting = replace(
490 &mut self.cached.borrow_mut()[index as usize],
491 ArrayThunk::Pending,
492 ) else {
493 unreachable!()
494 };
495
496 let val = self.mapper.call(index as u32);
497
498 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;
516
517 fn get(&self) -> Result<Self::Output> {
518 self.arr.get(self.index).transpose().expect("index checked")
519 }
520 }
521
522 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 }
530
531 Some(Thunk::new(MakeArrayThunk {
532 arr: self.clone(),
533 index,
534 }))
535 }
536}
465537
466#[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}
488560
489impl ArrayLike for RepeatedArray {561impl ArrayLike for RepeatedArray {
490 fn len(&self) -> usize {562 fn len(&self) -> u32 {
491 self.total_len563 self.total_len
492 }564 }
493565
494 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 }
500572
501 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}
522594
523impl 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 u32
526 }598 }
527599
528 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 }
534606
535 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 }
539611
561}633}
562634
563impl 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 u32
566 }638 }
567639
568 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 }
580652
581 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 still
584 // lazy-evaluated656 // lazy-evaluated
585 Some(Thunk::evaluated(657 Some(Thunk::evaluated(
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
1use std::fmt::Debug;1use std::{clone::Clone, fmt::Debug};
22
3use 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};
76
8use crate::{7use crate::{analyze::LocalId, error, error::ErrorKind::*, Pending, Result, SupThis, Thunk, Val};
9 ObjValue, Pending, Result, SupThis, Thunk, Val, bail, error::ErrorKind::*,8
10 gc::WithCapacityExt as _,
11};
12/// Context keeps information about current lexical code location
13///
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>);
1812
19#[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}
21
27impl 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 }
3126
32 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 }
3530
36 pub fn try_dollar(&self) -> Result<ObjValue> {31 pub fn try_sup_this(&self) -> Result<SupThis> {
37 self.032 self.0
38 .dollar33 .sup_this
39 .clone()34 .clone()
40 .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())35 .ok_or_else(|| error!(CantUseSelfSupOutsideOfObject))
41 }36 }
4237
38 /// Update binding in `CoW` fashion. Only useful for eager comprehension
39 /// 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 }
46
47 pub fn try_this(&self) -> Result<ObjValue> {
48 self.0
49 .sup_this
50 .as_ref()
51 .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())
52 .map(SupThis::this)
53 .cloned()
54 }
55
56 pub fn sup_this(&self) -> Option<&SupThis> {
57 self.0.sup_this.as_ref()
58 }
5942
60 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_this
63 .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 }
6651
67 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;
71
72 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 None
80 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));
89
90 bail!(VariableIsNotDefined(
91 name,
92 heap.into_iter().map(|(_, k)| k).collect()
93 ))
94 }64 }
95 pub fn contains_binding(&self, name: IStr) -> bool {65
96 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 }
105
106 #[must_use]
107 pub fn branch_point(self) -> Self {
108 if self.0.bindings.is_empty() {
109 self
110 } else {
111 ContextBuilder::extend(self).build()
112 }
113 }
114}73}
11574
116#[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}
12482
125impl 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 }
13592
136 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 }
145
146 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 }
155102
156 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 declarations
160 #[must_use]
161 pub fn commit(mut self) -> Self {
162 self.filled.clear();
163 self
164 }
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 }116
173 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 }
125
181 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}
137
138pub struct InitialContextBuilder {
139 builder: ContextBuilder,
140 externals: Vec<(IStr, LocalId)>,
141 next_id: u32,
142}
143
144impl InitialContextBuilder {
145 pub(crate) fn new() -> Self {
146 Self {
147 builder: ContextBuilder::new(),
148 externals: Vec::new(),
149 next_id: 0,
150 }
151 }
152
153 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 }
160
161 pub(crate) fn build(self) -> (ContextBuilder, Vec<(IStr, LocalId)>) {
162 (self.builder, self.externals)
163 }
164}
165
166impl Default for InitialContextBuilder {
167 fn default() -> Self {
168 Self::new()
169 }
170}
195171
modifiedcrates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth
9use thiserror::Error;9use thiserror::Error;
1010
11use 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};
1717
18#[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}
6565
66pub(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)> = names
68 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 );
8181
82 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}
88
89pub(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}
8797
88/// Possible errors98/// Possible errors
89#[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,
113
114 #[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,
105118
106 #[error("for loop can only iterate over arrays")]119 #[error("for loop can only iterate over arrays")]
107 InComprehensionCanOnlyIterateOverArray,120 InComprehensionCanOnlyIterateOverArray,
108121
109 #[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),
113126
114 #[error("assert failed: {}", format_empty_str(.0))]127 #[error("assert failed: {}", format_empty_str(.0))]
115 AssertionFailed(IStr),128 AssertionFailed(IStr),
116
117 #[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),
121129
122 #[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 },
178185
279}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}
380399
381#[macro_export]400#[macro_export]
382macro_rules! runtime_error {401macro_rules! runtime_error {
addedcrates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth

no changes

modifiedcrates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth
1use jrsonnet_ir::{BindSpec, Destruct};1use std::rc::Rc;
22
3use jrsonnet_gcmodule::Trace;
4
3use 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};
711
8#[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],
17
12 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;
2542
26 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 }
2752
28 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 });
4855
49 {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 }
6071
61 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(full
71 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}
8387
84 {
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;
10492
105 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;
10699
107 let captured_fields: FxHashSet<_> = fields.iter().map(|f| f.0.clone()).collect();100 use crate::{bail, ObjValueBuilder};
108 let field_names: Vec<_> = fields
109 .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 });
131101
132 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)> = fields
134 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 });
150126
151 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 }
166140
167 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 });
156
176 }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}
182164
165/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one
166/// 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}
186
187/// Bind one [`LBind`] as a lazy thunk that evaluates in the given
188/// future context. Mirrors the old `evaluate_dest` — one entry per
189/// 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}
199
200/// Bind each LBind's value as a lazy thunk. Mutually recursive locals
201/// 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}
214
215pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}
216impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}
217
218pub 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;
231
232 fn bind(&self, sup_this: SupThis) -> Result<Context> {
233 let parent = self.fctx.clone();
234
235 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,250
200 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}
214257
modifiedcrates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth
22
3use 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;
117
12use 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};
28
29pub mod compspec;
27pub mod destructure;30pub mod destructure;
28pub mod operator;31pub mod operator;
2932
30// 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 call
32// `ensure_sufficient_stack`.35// `ensure_sufficient_stack`.
33const RED_ZONE: usize = 100 * 1024; // 100k36const RED_ZONE: usize = 100 * 1024;
3437
35// 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 then
36// 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;
3841
39/// 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 locations
40/// 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 benefit
46 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)49 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)
47}50}
4851
49pub 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 array
51 match expr {
52 Expr::Str(_)
53 | Expr::Num(_)
54 | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,
55 Expr::Arr(a) => a.iter().all(is_trivial),
56 _ => false,
57 }
58 }
59 Some(match expr {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}
7963
64/// 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}
8872
89pub 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 None
99 } else {84 } else {
104 })89 })
105}90}
10691
107pub 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}
13299
133 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 0
141 },
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);
165102
166 evaluate_comp(103 names! {
167 ctx,
168 &specs[1..],
169 if i == 0 || !specs.is_empty() {
170 guaranteed_reserve104 anonymous: "anonymous",
171 } else {
172 0
173 },
174 callback,
175 )?;
176 }
177 }
178 _ => bail!(InComprehensionCanOnlyIterateOverArray),
179 }
180 }
181 }105 }
182 Ok(())
183}106}
184107
185fn 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}
189118
119pub 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::Null
193 }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 }
201172
202 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 = slice
208 .start
209 .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 = slice
221 .end
222 .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 = slice
234 .step
235 .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}
215267
216trait 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 };
218279
219fn evaluate_object_locals(280 let name = func.name();
220 fctx: Context,281 let unnamed = args
221 locals: Rc<Vec<BindSpec>>,282 .unnamed
222) -> 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;
230287
231 fn bind(&self, sup_this: SupThis) -> Result<Context> {288 let named = args
232 let fctx = Context::new_future();289 .values
290 .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}
238302
303fn 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 name
307 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 parts
321 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 };
244332
245 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}
247338
248pub 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) = obj
258
259 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 v
265 ..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 }
279
280 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 display
283 .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 display
309 self.name.clone(),386 flat.chars().count() as u32
310 self.params.clone(),
311 self.value.clone(),387 ));
312 ))
313 }
314 }388 }
315
316 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}
330407
331#[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}
339414
340#[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 }
360433
361 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 };
443
444 builder
445 .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 };
469
470 let value = value.clone();
471 builder
472 .field(name)
473 .with_add(*plus)
474 .with_visibility(*visibility)
475 .try_thunk(Thunk!(move || { evaluate(value_ctx, &value) }))?;
476 Ok(())
477}
478
479fn 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 }
365488
366 if members.locals.is_empty() {489 let needs_unbound = members.this.is_some() || members.uses_super;
490
367 // 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 }
392524
393 Ok(builder.build())525 Ok(Val::Obj(builder.build()))
394}526}
395527
396pub 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);
416
417 evaluate_field_member(&mut builder, ctx, uctx, &obj.field)
418 },
419 )?;
420
421 builder.build()
422 }
423 })
424}
425
426pub 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 = args
438 .unnamed
439 .iter()
440 .cloned()
441 .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))
442 .collect::<Result<Vec<_>>>()?;
443 let named = args
444 .values
445 .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}
461
462pub 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}
483549
484pub 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}
490
491pub 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}
498
499pub 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)?;
509
510 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-super
520 // 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 other
524 // 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)?;
560
561 let Val::Str(name) = name else {
562 bail!(ValueIndexMustBeTypeGot(
563 ValType::Obj,
564 ValType::Str,
565 name.value_type(),
566 ))
567 };
568
569 let name = name.into_flat();
570 match sup_this
571 .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 );
582
583 bail!(NoSuchField(name, suggestions))
584 }
585 }
586 } else {
587 evaluate(ctx.clone(), indexable)?
588 };
589
590 for part in parts {
591 indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {
592 (Val::Obj(v), Val::Str(key)) => match v
593 .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());
601
602 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 )),
643
644 (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 = s
664 .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 }
758
759 let indexable = evaluate(ctx.clone(), &slice.value)?;
760
761 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")?;
764
765 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}
787589
modifiedcrates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth
1use std::cmp::Ordering;1use std::cmp::Ordering;
22
3use jrsonnet_ir::{BinaryOpType, Expr, UnaryOpType};3use jrsonnet_ir::{BinaryOpType, UnaryOpType};
44
5use 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};
1516
16pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {17pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {
4041
41 (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 ),
4346
44 (Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,47 (Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,
158161
159pub 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::*;
170
167 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}
modifiedcrates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth
19 };19 };
20}20}
21
22#[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}
2138
22cc_dyn!(39cc_dyn!(
23 #[derive(Clone)]40 #[derive(Clone)]
modifiedcrates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth
3use 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;
88
9use 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};
1719
18pub mod builtin;20pub mod builtin;
19mod native;21mod native;
20mod parse;22mod parse;
21mod prepared;23pub(crate) mod prepared;
2224
23pub 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,
6870
69 /// Function parameter definition71 #[educe(PartialEq(method = Rc::ptr_eq))]
70 pub params: ExprParams,
71 /// Function body
72 pub body: Rc<Expr>,72 pub func: Rc<LFunction>,
73}73}
74
74impl 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 }
79
76 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());
88
89 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 }
98
99 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 }
107
108 if has_defaults {
109 for &param_idx in prepared.defaults() {
110 let param = &self.func.params[param_idx];
111 if let Some(default_expr) = &param.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(&param.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 }
79125
80 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}
84130
115 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 arguments
122 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 u32
124 }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 }
158195
159 /// 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 }
175
176 #[allow(clippy::infallible_destructuring_match)]212 #[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]
177 let id = match &param.destruct {213 let LDestruct::Full(id) = &param.destruct
178 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 }
modifiedcrates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth
1use jrsonnet_ir::ExprParams;1use std::rc::Rc;
22
3use 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};
98
10/// Creates Context, which has all argument default values applied9/// Creates Context with all argument default values applied
11/// 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();
14
15 let mut ctx = ContextBuilder::extend(body_ctx);13 let mut builder = ContextBuilder::extend(body_ctx, func.params.len());
1614
17 for param in params.exprs.iter() {15 for param in &func.params {
18 if let Some(v) = &param.default {16 if let Some(default_expr) = &param.default {
19 destruct(17 let default_expr = default_expr.clone();
20 &param.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(&param.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(&param.destruct, thunk, fctx.clone(), &mut builder);
32 &param.destruct,
33 {
34 let param_name = param.destruct.name();
35 let params = params.clone();
36 Thunk!(move || Err(FunctionParameterNotBoundInCall(
37 param_name,
38 params.signature
39 )
40 .into()))
41 },
42 fctx.clone(),
43 &mut ctx,
44 )?;
45 }34 }
46 }35 }
4736
48 Ok(ctx.build().into_future(fctx))37 let ctx = builder.build().into_future(fctx);
38 Ok(ctx)
49}39}
5040
modifiedcrates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth
1use std::rc::Rc;1use std::rc::Rc;
22
3use 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;
66
7use 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};
1212
13#[derive(Debug, Trace, Clone)]13#[derive(Debug, Trace, Clone)]
42 defaults: Vec<usize>,42 defaults: Vec<usize>,
43}43}
44
45impl PreparedCall {
46 pub fn named(&self) -> &[(usize, usize)] {
47 &self.named
48 }
49 pub fn defaults(&self) -> &[usize] {
50 &self.defaults
51 }
52}
4453
45pub 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 }
62
63 // 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 }
5381
54 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 {
111139
112 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);
122
123 let destruct_ctx = Pending::new();
124
125 for (param_idx, unnamed) in unnamed.iter().enumerate() {
126 destruct(
127 &params.exprs[param_idx].destruct,
128 unnamed.clone(),
129 destruct_ctx.clone(),
130 &mut ctx,
131 )?;
132 }
133
134 for (param_idx, arg_idx) in prepared.named.iter().copied() {
135 destruct(
136 &params.exprs[param_idx].destruct,
137 named[arg_idx].clone(),
138 destruct_ctx.clone(),
139 &mut ctx,
140 )?;
141 }
142
143 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 &params.exprs[param_idx].destruct,
153 {
154 let ctx = fctx.clone();
155 let params = params.clone();
156 Thunk!(move || {
157 let param = &params.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 }
167
168 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,
modifiedcrates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth
3use 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};
1313
14use 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};
1717
18impl<'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,
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
36pub 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;
4948
58pub use tla::apply_tla;57pub use tla::apply_tla;
59pub use val::{Thunk, Val};58pub use val::{Thunk, Val};
6059
60pub mod analyze;
61use crate::gc::WithCapacityExt as _;61use crate::gc::WithCapacityExt as _;
6262
63#[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;
174where174where
175 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 }
180180
185185
186/// 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 self
191 }191 }
195where195where
196 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 too
410 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);
412417
413 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 }
439444
440 /// Creates context with all passed global variables445 /// Creates context with all passed global variables
441 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 }
444449
447 &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);
455460
456 builder.build()461 builder
457 }462 }
458}463}
459464
487#[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 }
493498
515 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) = self
520 &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}
524533
modifiedcrates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth
11};11};
1212
13use 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;
2424
25use 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};
3434
35#[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 fields
503 /// 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 u32
509 }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.
modifiedcrates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth
10#[cfg(feature = "explaining-traces")]10#[cfg(feature = "explaining-traces")]
11use jrsonnet_ir::Span;11use jrsonnet_ir::Span;
1212
13use crate::{Error, error::ErrorKind};13use crate::{error::ErrorKind, Error};
1414
15/// The way paths should be displayed15/// The way paths should be displayed
16#[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 true
259 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};
263263
264 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 range
269 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 builder
274 .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;
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
637 }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)?);
modifiedcrates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth
171 let mut pool = pool.borrow_mut();171 let mut pool = pool.borrow_mut();
172172
173 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 TLS
175 // 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.
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
3use 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};
1415
15use 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)*
modifiedcrates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth
1#![allow(non_snake_case)]1#![allow(non_snake_case)]
22
3use 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};
1011
11pub 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}
1819
19#[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 i32
22 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 vals
30 || 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}
4138
42#[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: extend
215 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}
262260
380 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);
382380
383 Ok(ArrValue::extended(newArrLeft, newArrRight))381 Ok(ArrValue::extended(newArrLeft, newArrRight).ok_or_else(|| error!("array is too large"))?)
384}382}
385383
386#[builtin]384#[builtin]
399}397}
400398
401#[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}
modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
12pub 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({
modifiedcrates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth
1use std::{cell::RefCell, collections::BTreeSet};1use std::{cell::RefCell, collections::BTreeSet};
22
3use 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;
1213
13use crate::Settings;14use crate::Settings;
1415
15#[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)? {
modifiedtests/tests/builtin.rsdiffbeforeafterboth
1mod common;1mod common;
22
3use 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::FromUntyped
5 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 }
3733
76#[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 }
8278
modifiedtests/tests/common.rsdiffbeforeafterboth
1use 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}, Source
3 bail,
4 function::{FuncVal, builtin},
5 parser::Source,
6};3};
7use jrsonnet_gcmodule::Trace;4use jrsonnet_gcmodule::Trace;
85
68#[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 {});
modifiedtests/tests/cpp_test_suite.rsdiffbeforeafterboth
9 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 }
183
184 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 None
188 };
181189
182 if entry190 if entry
183 .path()191 .path()
188 continue;196 continue;
189 }197 }
190198
191 println!("test: {}", entry.path().display());199 eprintln!("test: {}", entry.path().display());
192200
193 let result = run(&entry.path(), &root);201 let result = run(&entry.path(), &root);
194202
213 golden = Some(golden_path);221 golden = Some(golden_path);
214 }222 }
215
216 // ir-parser has its own override layer
217 #[cfg(feature = "ir-parser")]
218 let ir_parser_override_path = {
219 let p = root_tests
220 .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 p
226 };
227223
228 // 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());
230226
231 #[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;
235228
236 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 }
269
270 jrsonnet_gcmodule::with_thread_object_space(ObjectSpace::leak);
275271
276 Ok(())272 Ok(())
277}273}
modifiedtests/tests/snapshots/golden__golden@issue172.jsonnet.snapdiffbeforeafterboth
3expression: result3expression: result
4input_file: tests/golden/issue172.jsonnet4input_file: tests/golden/issue172.jsonnet
5---5---
6local is not defined: b6static analysis errors: undefined local: b
7 issue172.jsonnet:1:45-47: local <b> access
8 issue172.jsonnet:1:4-10: field <value> access
9 elem <0> evaluation
107
modifiedtests/tests/snapshots/golden__golden@issue23.jsonnet.snapdiffbeforeafterboth
4input_file: tests/golden/issue23.jsonnet4input_file: tests/golden/issue23.jsonnet
5---5---
6infinite recursion detected6infinite recursion detected
7 issue23.jsonnet:1:1-8: import "issue23.jsonnet"7 issue23.jsonnet:1:1-8: import
88
modifiedtests/tests/snapshots/golden__golden@missing_binding.jsonnet.snapdiffbeforeafterboth
3expression: result3expression: result
4input_file: tests/golden/missing_binding.jsonnet4input_file: tests/golden/missing_binding.jsonnet
5---5---
6local is not defined: sta6static analysis errors: undefined local: sta
7There is a local with similar name present: std7There is a local with similar name present: std
8 missing_binding.jsonnet:1:1-5: local <sta> access
98