difftreelog
refactor(evaluator) use static analysis
in: master
31 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -815,9 +815,11 @@
dependencies = [
"annotate-snippets",
"anyhow",
+ "drop_bomb",
"educe",
"hi-doc",
"im-rc",
+ "insta",
"jrsonnet-gcmodule",
"jrsonnet-interner",
"jrsonnet-ir",
@@ -830,8 +832,10 @@
"rustc-hash 2.1.2",
"rustversion",
"serde",
+ "smallvec 1.15.1",
"stacker",
"static_assertions",
+ "strip-ansi-escapes",
"strsim",
"thiserror",
]
@@ -899,6 +903,7 @@
"jrsonnet-interner",
"peg",
"static_assertions",
+ "thiserror",
]
[[package]]
@@ -960,7 +965,6 @@
"base64",
"jrsonnet-evaluator",
"jrsonnet-gcmodule",
- "jrsonnet-ir",
"jrsonnet-macros",
"lru",
"md5",
bindings/jsonnet/src/val_modify.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/val_modify.rs
+++ b/bindings/jsonnet/src/val_modify.rs
@@ -4,7 +4,7 @@
use std::{ffi::CStr, os::raw::c_char};
-use jrsonnet_evaluator::{Thunk, Val, val::ArrValue};
+use jrsonnet_evaluator::{Thunk, Val};
use crate::VM;
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -77,6 +77,12 @@
"PartialEq",
] }
im-rc = { version = "15.1.0", features = ["pool"] }
+smallvec = "1.15.1"
+drop_bomb.workspace = true
[build-dependencies]
rustversion = "1.0.22"
+
+[dev-dependencies]
+insta.workspace = true
+strip-ansi-escapes = "0.2.1"
crates/jrsonnet-evaluator/src/arr/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/mod.rs
+++ b/crates/jrsonnet-evaluator/src/arr/mod.rs
@@ -5,10 +5,9 @@
rc::Rc,
};
-use jrsonnet_gcmodule::{Cc, cc_dyn};
-use jrsonnet_ir::Expr;
+use jrsonnet_gcmodule::{cc_dyn, Cc};
-use crate::{Context, Result, Thunk, Val, function::NativeFn, typed::IntoUntyped};
+use crate::{analyze::LExpr, function::NativeFn, typed::IntoUntyped, Context, Result, Thunk, Val};
mod spec;
pub use spec::{ArrayLike, *};
@@ -37,14 +36,18 @@
Self::new(())
}
- pub fn expr(ctx: Context, exprs: Rc<Vec<Expr>>) -> Self {
+ pub fn expr(ctx: Context, exprs: Rc<Vec<LExpr>>) -> Self {
Self::new(ExprArray::new(ctx, exprs))
}
- pub fn repeated(data: Self, repeats: usize) -> Option<Self> {
+ pub fn repeated(data: Self, repeats: u32) -> Option<Self> {
Some(Self::new(RepeatedArray::new(data, repeats)?))
}
+ pub fn make(len: u32, cb: NativeFn!((u32,)->Val)) -> Self {
+ Self::new(MakeArray::new(len, cb))
+ }
+
#[must_use]
pub fn map(self, mapper: NativeFn!((Val) -> Val)) -> Self {
Self::new(<MappedArray>::new(self, ArrayMapper::Plain(mapper)))
@@ -79,14 +82,14 @@
Ok(Self::new(out))
}
- pub fn extended(a: Self, b: Self) -> Self {
- if a.is_empty() {
+ pub fn extended(a: Self, b: Self) -> Option<Self> {
+ Some(if a.is_empty() {
b
} else if b.is_empty() {
a
} else {
- Self::new(ExtendedArray::new(a, b))
- }
+ Self::new(ExtendedArray::new(a, b)?)
+ })
}
pub fn range_exclusive(a: i32, b: i32) -> Self {
@@ -98,14 +101,14 @@
#[must_use]
pub fn slice(self, index: Option<i32>, end: Option<i32>, step: Option<NonZeroU32>) -> Self {
- let get_idx = |pos: Option<i32>, len: usize, default| match pos {
+ let get_idx = |pos: Option<i32>, len: u32, default| match pos {
#[expect(
clippy::cast_sign_loss,
reason = "abs value is used, len is limited to u31"
)]
- Some(v) if v < 0 => len.saturating_sub((-v) as usize),
+ Some(v) if v < 0 => len.saturating_add_signed(v),
#[expect(clippy::cast_sign_loss, reason = "abs value is used")]
- Some(v) => (v as usize).min(len),
+ Some(v) => (v as u32).min(len),
None => default,
};
let index = get_idx(index, self.len(), 0);
@@ -127,7 +130,7 @@
}
/// Array length.
- pub fn len(&self) -> usize {
+ pub fn len(&self) -> u32 {
self.0.len()
}
@@ -143,14 +146,14 @@
/// Get array element by index, evaluating it, if it is lazy.
///
/// Returns `None` on out-of-bounds condition.
- pub fn get(&self, index: usize) -> Result<Option<Val>> {
+ pub fn get(&self, index: u32) -> Result<Option<Val>> {
self.0.get(index)
}
/// Get array element by index, without evaluation.
///
/// Returns `None` on out-of-bounds condition.
- pub fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ pub fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
self.0.get_lazy(index)
}
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/arr/spec.rs
+++ b/crates/jrsonnet-evaluator/src/arr/spec.rs
@@ -8,47 +8,47 @@
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::{IBytes, IStr};
-use jrsonnet_ir::Expr;
use super::ArrValue;
use crate::{
- Context, Error, ObjValue, Result, Thunk, Val,
+ analyze::LExpr,
error::ErrorKind::InfiniteRecursionDetected,
- evaluate,
+ evaluate::evaluate,
function::NativeFn,
typed::{IntoUntyped, Typed},
val::ThunkValue,
+ Context, Error, ObjValue, Result, Thunk, Val,
};
pub trait ArrayLike: Any + Trace + Debug {
- fn len(&self) -> usize;
+ fn len(&self) -> u32;
fn is_empty(&self) -> bool {
self.len() == 0
}
- fn get(&self, index: usize) -> Result<Option<Val>>;
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>>;
+ fn get(&self, index: u32) -> Result<Option<Val>>;
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>>;
fn is_cheap(&self) -> bool {
false
}
}
trait ArrayCheap {
- fn get(&self, index: usize) -> Option<Val>;
- fn len(&self) -> usize;
+ fn get(&self, index: u32) -> Option<Val>;
+ fn len(&self) -> u32;
}
impl<T> ArrayLike for T
where
T: Any + Trace + Debug + ArrayCheap,
{
- fn len(&self) -> usize {
+ fn len(&self) -> u32 {
<T as ArrayCheap>::len(self)
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
Ok(<T as ArrayCheap>::get(self, index))
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
<T as ArrayCheap>::get(self, index).map(Thunk::evaluated)
}
@@ -58,10 +58,10 @@
}
impl ArrayCheap for () {
- fn len(&self) -> usize {
+ fn len(&self) -> u32 {
0
}
- fn get(&self, _index: usize) -> Option<Val> {
+ fn get(&self, _index: u32) -> Option<Val> {
None
}
}
@@ -75,20 +75,20 @@
}
impl SliceArray {
- fn map_idx(&self, index: usize) -> usize {
- self.from as usize + self.step as usize * index
+ fn map_idx(&self, index: u32) -> u32 {
+ self.from + self.step * index
}
}
impl ArrayLike for SliceArray {
- fn len(&self) -> usize {
- (self.to - self.from).div_ceil(self.step) as usize
+ fn len(&self) -> u32 {
+ (self.to - self.from).div_ceil(self.step)
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
self.inner.get(self.map_idx(index))
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
self.inner.get_lazy(self.map_idx(index))
}
@@ -98,11 +98,13 @@
}
impl ArrayCheap for IBytes {
- fn len(&self) -> usize {
- self.as_slice().len()
+ fn len(&self) -> u32 {
+ self.as_slice().len() as u32
}
- fn get(&self, index: usize) -> Option<Val> {
- self.as_slice().get(index).map(|v| Val::Num((*v).into()))
+ fn get(&self, index: u32) -> Option<Val> {
+ self.as_slice()
+ .get(index as usize)
+ .map(|v| Val::Num((*v).into()))
}
}
@@ -117,11 +119,11 @@
#[derive(Debug, Trace, Clone)]
pub struct ExprArray {
ctx: Context,
- src: Rc<Vec<Expr>>,
+ src: Rc<Vec<LExpr>>,
cached: Cc<RefCell<Vec<ArrayThunk>>>,
}
impl ExprArray {
- pub fn new(ctx: Context, src: Rc<Vec<Expr>>) -> Self {
+ pub fn new(ctx: Context, src: Rc<Vec<LExpr>>) -> Self {
Self {
ctx,
cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; src.len()])),
@@ -130,41 +132,36 @@
}
}
impl ArrayLike for ExprArray {
- fn len(&self) -> usize {
- self.cached.borrow().len()
+ fn len(&self) -> u32 {
+ self.cached.borrow().len() as u32
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
if index >= self.len() {
return Ok(None);
}
- match &self.cached.borrow()[index] {
+ match &self.cached.borrow()[index as usize] {
ArrayThunk::Computed(c) => return Ok(Some(c.clone())),
ArrayThunk::Errored(e) => return Err(e.clone()),
ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),
ArrayThunk::Waiting => {}
}
- let ArrayThunk::Waiting =
- replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
- else {
+ let ArrayThunk::Waiting = replace(
+ &mut self.cached.borrow_mut()[index as usize],
+ ArrayThunk::Pending,
+ ) else {
unreachable!()
};
- let new_value = match evaluate(self.ctx.clone(), &self.src[index]) {
- Ok(v) => v,
- Err(e) => {
- self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());
- return Err(e);
- }
- };
- self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());
+ let new_value: Val = evaluate(self.ctx.clone(), &self.src[index as usize])?;
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());
Ok(Some(new_value))
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
#[derive(Trace)]
struct ExprArrThunk {
expr: ExprArray,
- index: usize,
+ index: u32,
}
impl ThunkValue for ExprArrThunk {
type Output = Val;
@@ -180,7 +177,7 @@
if index >= self.len() {
return None;
}
- match &self.cached.borrow()[index] {
+ match &self.cached.borrow()[index as usize] {
ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),
ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),
ArrayThunk::Waiting | ArrayThunk::Pending => {}
@@ -200,19 +197,20 @@
pub struct ExtendedArray {
pub a: ArrValue,
pub b: ArrValue,
- split: usize,
- len: usize,
+ split: u32,
+ len: u32,
}
impl ExtendedArray {
- pub fn new(a: ArrValue, b: ArrValue) -> Self {
+ pub fn new(a: ArrValue, b: ArrValue) -> Option<Self> {
let a_len = a.len();
let b_len = b.len();
- Self {
+ let len = a_len.checked_add(b_len)?;
+ Some(Self {
a,
b,
split: a_len,
- len: a_len.checked_add(b_len).expect("too large array value"),
- }
+ len,
+ })
}
}
@@ -253,14 +251,14 @@
}
}
impl ArrayLike for ExtendedArray {
- fn get(&self, index: usize) -> Result<Option<Val>> {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
if self.split > index {
self.a.get(index)
} else {
self.b.get(index - self.split)
}
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
if self.split > index {
self.a.get_lazy(index)
} else {
@@ -268,7 +266,7 @@
}
}
- fn len(&self) -> usize {
+ fn len(&self) -> u32 {
self.len
}
@@ -282,19 +280,19 @@
T: IntoUntyped + Trace + fmt::Debug,
for<'a> &'a T: IntoUntyped,
{
- fn len(&self) -> usize {
- self.as_slice().len()
+ fn len(&self) -> u32 {
+ self.as_slice().len().try_into().unwrap_or(u32::MAX)
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
- let Some(elem) = self.as_slice().get(index) else {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
+ let Some(elem) = self.as_slice().get(index as usize) else {
return Ok(None);
};
IntoUntyped::into_untyped(elem).map(Some)
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
- let elem = self.as_slice().get(index)?;
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
+ let elem = self.as_slice().get(index as usize)?;
Some(IntoUntyped::into_lazy_untyped(elem))
}
@@ -324,20 +322,20 @@
clippy::cast_sign_loss,
reason = "the math is valid with wrapping, sign loss works as intended"
)]
- fn size(&self) -> usize {
- (self.end as usize)
- .wrapping_sub(self.start as usize)
+ fn size(&self) -> u32 {
+ (self.end as u32)
+ .wrapping_sub(self.start as u32)
.wrapping_add(1)
}
fn range(&self) -> impl ExactSizeIterator<Item = i32> + DoubleEndedIterator {
- WithExactSize(self.start..=self.end, self.size())
+ WithExactSize(self.start..=self.end, self.size() as usize)
}
}
impl ArrayCheap for RangeArray {
- fn get(&self, index: usize) -> Option<Val> {
- self.range().nth(index).map(|i| Val::Num(i.into()))
+ fn get(&self, index: u32) -> Option<Val> {
+ self.range().nth(index as usize).map(|i| Val::Num(i.into()))
}
- fn len(&self) -> usize {
+ fn len(&self) -> u32 {
self.size()
}
}
@@ -345,15 +343,15 @@
#[derive(Debug, Trace)]
pub struct ReverseArray(pub ArrValue);
impl ArrayLike for ReverseArray {
- fn len(&self) -> usize {
+ fn len(&self) -> u32 {
self.0.len()
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
self.0.get(self.0.len() - index - 1)
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
self.0.get_lazy(self.0.len() - index - 1)
}
@@ -379,40 +377,37 @@
let len = inner.len();
Self {
inner,
- cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len])),
+ cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),
mapper,
}
}
- fn evaluate(&self, index: usize, value: Val) -> Result<Val> {
+ fn evaluate(&self, index: u32, value: Val) -> Result<Val> {
match &self.mapper {
ArrayMapper::Plain(f) => f.call(value),
- #[expect(
- clippy::cast_possible_truncation,
- reason = "array len is limited to u31"
- )]
- ArrayMapper::WithIndex(f) => f.call(index as u32, value),
+ ArrayMapper::WithIndex(f) => f.call(index, value),
}
}
}
impl ArrayLike for MappedArray {
- fn len(&self) -> usize {
- self.cached.borrow().len()
+ fn len(&self) -> u32 {
+ self.cached.borrow().len() as u32
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
if index >= self.len() {
return Ok(None);
}
- match &self.cached.borrow()[index] {
+ match &self.cached.borrow()[index as usize] {
ArrayThunk::Computed(c) => return Ok(Some(c.clone())),
ArrayThunk::Errored(e) => return Err(e.clone()),
ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),
ArrayThunk::Waiting => {}
}
- let ArrayThunk::Waiting =
- replace(&mut self.cached.borrow_mut()[index], ArrayThunk::Pending)
- else {
+ let ArrayThunk::Waiting = replace(
+ &mut self.cached.borrow_mut()[index as usize],
+ ArrayThunk::Pending,
+ ) else {
unreachable!()
};
@@ -426,18 +421,18 @@
let new_value = match val {
Ok(v) => v,
Err(e) => {
- self.cached.borrow_mut()[index] = ArrayThunk::Errored(e.clone());
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());
return Err(e);
}
};
- self.cached.borrow_mut()[index] = ArrayThunk::Computed(new_value.clone());
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());
Ok(Some(new_value))
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
#[derive(Trace)]
struct MappedArrayThunk {
arr: MappedArray,
- index: usize,
+ index: u32,
}
impl ThunkValue for MappedArrayThunk {
type Output = Val;
@@ -450,7 +445,7 @@
if index >= self.len() {
return None;
}
- match &self.cached.borrow()[index] {
+ match &self.cached.borrow()[index as usize] {
ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),
ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),
ArrayThunk::Waiting | ArrayThunk::Pending => {}
@@ -462,15 +457,92 @@
}))
}
}
+#[derive(Trace, Debug, Clone)]
+pub struct MakeArray {
+ cached: Cc<RefCell<Vec<ArrayThunk>>>,
+ mapper: NativeFn!((u32,)->Val),
+}
+impl MakeArray {
+ pub fn new(len: u32, mapper: NativeFn!((u32)->Val)) -> Self {
+ Self {
+ cached: Cc::new(RefCell::new(vec![ArrayThunk::Waiting; len as usize])),
+ mapper,
+ }
+ }
+}
+impl ArrayLike for MakeArray {
+ fn len(&self) -> u32 {
+ self.cached.borrow().len() as u32
+ }
+
+ fn get(&self, index: u32) -> Result<Option<Val>> {
+ if index >= self.len() {
+ return Ok(None);
+ }
+ match &self.cached.borrow()[index as usize] {
+ ArrayThunk::Computed(c) => return Ok(Some(c.clone())),
+ ArrayThunk::Errored(e) => return Err(e.clone()),
+ ArrayThunk::Pending => return Err(InfiniteRecursionDetected.into()),
+ ArrayThunk::Waiting => {}
+ }
+
+ let ArrayThunk::Waiting = replace(
+ &mut self.cached.borrow_mut()[index as usize],
+ ArrayThunk::Pending,
+ ) else {
+ unreachable!()
+ };
+
+ let val = self.mapper.call(index as u32);
+
+ let new_value = match val {
+ Ok(v) => v,
+ Err(e) => {
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Errored(e.clone());
+ return Err(e);
+ }
+ };
+ self.cached.borrow_mut()[index as usize] = ArrayThunk::Computed(new_value.clone());
+ Ok(Some(new_value))
+ }
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
+ #[derive(Trace)]
+ struct MakeArrayThunk {
+ arr: MakeArray,
+ index: u32,
+ }
+ impl ThunkValue for MakeArrayThunk {
+ type Output = Val;
+
+ fn get(&self) -> Result<Self::Output> {
+ self.arr.get(self.index).transpose().expect("index checked")
+ }
+ }
+
+ if index >= self.len() {
+ return None;
+ }
+ match &self.cached.borrow()[index as usize] {
+ ArrayThunk::Computed(c) => return Some(Thunk::evaluated(c.clone())),
+ ArrayThunk::Errored(e) => return Some(Thunk::errored(e.clone())),
+ ArrayThunk::Waiting | ArrayThunk::Pending => {}
+ }
+
+ Some(Thunk::new(MakeArrayThunk {
+ arr: self.clone(),
+ index,
+ }))
+ }
+}
#[derive(Trace, Debug)]
pub struct RepeatedArray {
data: ArrValue,
- repeats: usize,
- total_len: usize,
+ repeats: u32,
+ total_len: u32,
}
impl RepeatedArray {
- pub fn new(data: ArrValue, repeats: usize) -> Option<Self> {
+ pub fn new(data: ArrValue, repeats: u32) -> Option<Self> {
let total_len = data.len().checked_mul(repeats)?;
Some(Self {
data,
@@ -478,7 +550,7 @@
total_len,
})
}
- fn map_idx(&self, index: usize) -> Option<usize> {
+ fn map_idx(&self, index: u32) -> Option<u32> {
if index > self.total_len {
return None;
}
@@ -487,18 +559,18 @@
}
impl ArrayLike for RepeatedArray {
- fn len(&self) -> usize {
+ fn len(&self) -> u32 {
self.total_len
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
let Some(idx) = self.map_idx(index) else {
return Ok(None);
};
self.data.get(idx)
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
let idx = self.map_idx(index)?;
self.data.get_lazy(idx)
}
@@ -521,19 +593,19 @@
}
impl ArrayLike for PickObjectValues {
- fn len(&self) -> usize {
- self.keys.len()
+ fn len(&self) -> u32 {
+ self.keys.len() as u32
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
- let Some(key) = self.keys.as_slice().get(index) else {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
+ let Some(key) = self.keys.as_slice().get(index as usize) else {
return Ok(None);
};
Ok(Some(self.obj.get_or_bail(key.clone())?))
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
- let key = self.keys.as_slice().get(index)?;
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
+ let key = self.keys.as_slice().get(index as usize)?;
Some(self.obj.get_lazy_or_bail(key.clone()))
}
@@ -561,12 +633,12 @@
}
impl ArrayLike for PickObjectKeyValues {
- fn len(&self) -> usize {
- self.keys.len()
+ fn len(&self) -> u32 {
+ self.keys.len() as u32
}
- fn get(&self, index: usize) -> Result<Option<Val>> {
- let Some(key) = self.keys.as_slice().get(index) else {
+ fn get(&self, index: u32) -> Result<Option<Val>> {
+ let Some(key) = self.keys.as_slice().get(index as usize) else {
return Ok(None);
};
Ok(Some(
@@ -578,8 +650,8 @@
))
}
- fn get_lazy(&self, index: usize) -> Option<Thunk<Val>> {
- let key = self.keys.as_slice().get(index)?;
+ fn get_lazy(&self, index: u32) -> Option<Thunk<Val>> {
+ let key = self.keys.as_slice().get(index as usize)?;
// Nothing can fail in the key part, yet value is still
// lazy-evaluated
Some(Thunk::evaluated(
crates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -1,58 +1,29 @@
-use std::fmt::Debug;
+use std::{clone::Clone, fmt::Debug};
use educe::Educe;
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
-use rustc_hash::{FxHashMap, FxHashSet};
-use crate::{
- ObjValue, Pending, Result, SupThis, Thunk, Val, bail, error::ErrorKind::*,
- gc::WithCapacityExt as _,
-};
-/// Context keeps information about current lexical code location
-///
-/// This information includes local variables, top-level object (`$`), current object (`this`), and super object (`super`)
+use crate::{analyze::LocalId, error, error::ErrorKind::*, Pending, Result, SupThis, Thunk, Val};
+
#[derive(Debug, Trace, Clone, Educe)]
#[educe(PartialEq)]
pub struct Context(#[educe(PartialEq(method = Cc::ptr_eq))] Cc<ContextInternal>);
-#[derive(Debug, Trace)]
+#[derive(Debug, Trace, Clone)]
struct ContextInternal {
- dollar: Option<ObjValue>,
sup_this: Option<SupThis>,
- bindings: FxHashMap<IStr, Thunk<Val>>,
-
- branch_point: Option<Context>,
+ /// `bindings[i]` corresponds to `LocalId(offset + i)`.
+ bindings: Vec<Option<Thunk<Val>>>,
+ offset: u32,
+ parent: Option<Context>,
}
+
impl Context {
pub fn new_future() -> Pending<Self> {
Pending::new()
- }
-
- pub fn dollar(&self) -> Option<&ObjValue> {
- self.0.dollar.as_ref()
}
- pub fn try_dollar(&self) -> Result<ObjValue> {
- self.0
- .dollar
- .clone()
- .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())
- }
-
- pub fn this(&self) -> Option<&ObjValue> {
- self.0.sup_this.as_ref().map(SupThis::this)
- }
-
- pub fn try_this(&self) -> Result<ObjValue> {
- self.0
- .sup_this
- .as_ref()
- .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())
- .map(SupThis::this)
- .cloned()
- }
-
pub fn sup_this(&self) -> Option<&SupThis> {
self.0.sup_this.as_ref()
}
@@ -61,127 +32,98 @@
self.0
.sup_this
.clone()
- .ok_or_else(|| CantUseSelfSupOutsideOfObject.into())
+ .ok_or_else(|| error!(CantUseSelfSupOutsideOfObject))
}
- pub fn binding(&self, name: IStr) -> Result<Thunk<Val>> {
- use std::cmp::Ordering;
-
- use crate::bail;
+ /// Update binding in `CoW` fashion. Only useful for eager comprehension
+ /// fast-path, as it requires Cc refcount to be 1; Use `ContextBuilder` otherwise.
+ pub(crate) fn cow_fill_binding(&mut self, id: LocalId, value: Thunk<Val>) {
+ let mut value = Some(Some(value));
- if let Some(val) = self.0.bindings.get(&name).cloned() {
- return Ok(val);
- }
-
- if let Some(branch_point) = &self.0.branch_point {
- return branch_point.binding(name);
- }
+ self.0.update_with(|inner| {
+ let local_idx = (id.0 - inner.offset) as usize;
+ while inner.bindings.len() <= local_idx {
+ inner.bindings.push(None);
+ }
+ inner.bindings[local_idx] = value.take().expect("called once");
+ });
+ }
- let mut heap = Vec::new();
- for k in self.0.bindings.keys() {
- let conf = strsim::jaro_winkler(k as &str, &name as &str);
- if conf < 0.8 {
- continue;
+ pub fn binding(&self, id: LocalId) -> Option<Thunk<Val>> {
+ let id_num = id.0;
+ if id_num >= self.0.offset {
+ let local_idx = (id_num - self.0.offset) as usize;
+ if let Some(Some(thunk)) = self.0.bindings.get(local_idx) {
+ return Some(thunk.clone());
}
- heap.push((conf, k.clone()));
}
- heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
+ if let Some(parent) = &self.0.parent {
+ return parent.binding(id);
+ }
+ None
+ }
- bail!(VariableIsNotDefined(
- name,
- heap.into_iter().map(|(_, k)| k).collect()
- ))
- }
- pub fn contains_binding(&self, name: IStr) -> bool {
- self.0.bindings.contains_key(&name)
- }
#[must_use]
pub fn into_future(self, ctx: Pending<Self>) -> Self {
{
ctx.clone().fill(self);
}
ctx.unwrap()
- }
-
- #[must_use]
- pub fn branch_point(self) -> Self {
- if self.0.bindings.is_empty() {
- self
- } else {
- ContextBuilder::extend(self).build()
- }
}
}
#[derive(Clone)]
pub struct ContextBuilder {
- dollar: Option<ObjValue>,
sup_this: Option<SupThis>,
- bindings: FxHashMap<IStr, Thunk<Val>>,
- filled: FxHashSet<IStr>,
- branch_point: Option<Context>,
+ bindings: Vec<Option<Thunk<Val>>>,
+ offset: u32,
+ parent: Option<Context>,
}
impl ContextBuilder {
pub fn new() -> Self {
Self {
- dollar: None,
sup_this: None,
- bindings: FxHashMap::new(),
- filled: FxHashSet::new(),
- branch_point: None,
+ bindings: Vec::new(),
+ offset: 0,
+ parent: None,
}
}
- pub fn extend_fast(parent: Context) -> Self {
+ pub(crate) fn extend(parent: Context, capacity: usize) -> Self {
+ let offset = parent.0.offset + parent.0.bindings.len() as u32;
Self {
- dollar: parent.0.dollar.clone(),
sup_this: parent.0.sup_this.clone(),
- bindings: parent.0.bindings.clone(),
- filled: FxHashSet::new(),
- branch_point: parent.0.branch_point.clone(),
+ bindings: Vec::with_capacity(capacity),
+ offset,
+ parent: Some(parent),
}
}
- pub fn extend(parent: Context) -> Self {
- Self {
- dollar: parent.0.dollar.clone(),
- sup_this: parent.0.sup_this.clone(),
- bindings: FxHashMap::new(),
- filled: FxHashSet::new(),
- branch_point: Some(parent.clone()),
+ pub(crate) fn bind(&mut self, id: LocalId, value: Thunk<Val>) {
+ debug_assert!(
+ id.0 >= self.offset,
+ "cannot bind {id:?} below offset {}",
+ self.offset,
+ );
+ let local_idx = (id.0 - self.offset) as usize;
+ self.bindings.reserve(local_idx);
+ while self.bindings.len() <= local_idx {
+ self.bindings.push(None);
}
+ self.bindings[local_idx] = Some(value);
}
- pub fn bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) {
- let _ = self.bindings.insert(name.into(), value);
- }
- /// After commit, binds would shadow the previous declarations
- #[must_use]
- pub fn commit(mut self) -> Self {
- self.filled.clear();
- self
- }
- pub fn try_bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) -> Result<()> {
- let name = name.into();
- if !self.filled.insert(name.clone()) {
- bail!(DuplicateLocalVar(name))
- }
- self.bind(name, value);
- Ok(())
- }
- pub fn build(self) -> Context {
+ pub(crate) fn build(self) -> Context {
Context(Cc::new(ContextInternal {
- dollar: self.dollar,
sup_this: self.sup_this,
bindings: self.bindings,
- branch_point: self.branch_point,
+ offset: self.offset,
+ parent: self.parent,
}))
}
- pub fn build_sup_this(mut self, st: SupThis) -> Context {
- if self.dollar.is_none() {
- self.dollar = Some(st.this().clone());
- }
+
+ pub(crate) fn build_sup_this(mut self, st: SupThis) -> Context {
self.sup_this = Some(st);
self.build()
}
@@ -192,3 +134,37 @@
Self::new()
}
}
+
+pub struct InitialContextBuilder {
+ builder: ContextBuilder,
+ externals: Vec<(IStr, LocalId)>,
+ next_id: u32,
+}
+
+impl InitialContextBuilder {
+ pub(crate) fn new() -> Self {
+ Self {
+ builder: ContextBuilder::new(),
+ externals: Vec::new(),
+ next_id: 0,
+ }
+ }
+
+ pub fn bind(&mut self, name: impl Into<IStr>, value: Thunk<Val>) {
+ let name = name.into();
+ let id = LocalId(self.next_id);
+ self.next_id += 1;
+ self.externals.push((name, id));
+ self.builder.bind(id, value);
+ }
+
+ pub(crate) fn build(self) -> (ContextBuilder, Vec<(IStr, LocalId)>) {
+ (self.builder, self.externals)
+ }
+}
+
+impl Default for InitialContextBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -9,16 +9,16 @@
use thiserror::Error;
use crate::{
- ObjValue, ResolvePathOwned,
function::{CallLocation, FunctionSignature, ParamName},
stdlib::format::FormatError,
typed::TypeLocError,
+ ObjValue, ResolvePathOwned,
};
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Acyclic)]
pub struct SyntaxError {
pub message: String,
- pub location: (u32, u32),
+ pub location: Span,
}
impl fmt::Display for SyntaxError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -63,26 +63,36 @@
}
}
+pub(crate) fn suggest_names<'a, 'b>(
+ name: &'a IStr,
+ names: impl IntoIterator<Item = &'b IStr>,
+) -> Vec<IStr> {
+ let mut heap: Vec<(f64, IStr)> = names
+ .into_iter()
+ .filter_map(|def| {
+ let conf = strsim::jaro_winkler(def.as_str(), name.as_str());
+ if conf < 0.8 {
+ return None;
+ }
+ debug_assert!(
+ def.as_str() != name.as_str(),
+ "string pooling failure: look for DOC(string-pooling) comment in jrsonnet-interner"
+ );
+
+ Some((conf, def.clone()))
+ })
+ .collect();
+ heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
+ heap.into_iter().map(|v| v.1).collect()
+}
+
pub(crate) fn suggest_object_fields(v: &ObjValue, key: IStr) -> Vec<IStr> {
- let mut heap = Vec::new();
- for field in v.fields_ex(
+ let fields = v.fields_ex(
true,
#[cfg(feature = "exp-preserve-order")]
false,
- ) {
- let conf = strsim::jaro_winkler(field.as_str(), key.as_str());
- if conf < 0.8 {
- continue;
- }
- assert!(
- field.as_str() != key.as_str(),
- "looks like string pooling failure, please write any info regarding this crash to https://github.com/CertainLach/jrsonnet/issues/113, thanks!"
- );
-
- heap.push((conf, field));
- }
- heap.sort_by(|a, b| b.0.partial_cmp(&a.0).unwrap_or(Ordering::Equal));
- heap.into_iter().map(|v| v.1).collect()
+ );
+ suggest_names(&key, &fields)
}
/// Possible errors
@@ -100,6 +110,9 @@
#[error("self/super/$ are only usable inside objects")]
CantUseSelfSupOutsideOfObject,
+
+ #[error("static analysis errors: {}", .0.iter().map(|d| d.message.as_str()).collect::<Vec<_>>().join("; "))]
+ StaticAnalysisError(Vec<crate::analyze::Diagnostic>),
#[error("no super found")]
NoSuperFound,
@@ -107,17 +120,12 @@
InComprehensionCanOnlyIterateOverArray,
#[error("array out of bounds: {0} is not within [0,{1})")]
- ArrayBoundsError(isize, usize),
+ ArrayBoundsError(isize, u32),
#[error("string out of bounds: {0} is not within [0,{1})")]
StringBoundsError(usize, usize),
#[error("assert failed: {}", format_empty_str(.0))]
AssertionFailed(IStr),
-
- #[error("local is not defined: {0}{found}", found = format_found(.1, "local"))]
- VariableIsNotDefined(IStr, Vec<IStr>),
- #[error("duplicate local var: {0}")]
- DuplicateLocalVar(IStr),
#[error("type mismatch: expected {expected}, got {2} {0}", expected = .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]
TypeMismatch(&'static str, Vec<ValType>, ValType),
@@ -172,7 +180,6 @@
#[error("syntax error: {error}")]
ImportSyntaxError {
path: Source,
- #[trace(skip)]
error: Box<SyntaxError>,
},
@@ -279,11 +286,11 @@
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- writeln!(f, "{}", self.0.0)?;
- for el in &self.0.1.0 {
+ writeln!(f, "{}", self.0 .0)?;
+ for el in &self.0 .1 .0 {
write!(f, "\t{}", el.desc)?;
if let Some(loc) = &el.location {
- write!(f, "at {}", loc.0.0.0)?;
+ write!(f, "at {}", loc.0 .0 .0)?;
loc.0.map_source_locations(&[loc.1, loc.2]);
}
writeln!(f)?;
@@ -377,6 +384,18 @@
return Err($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())
};
}
+#[macro_export]
+macro_rules! error {
+ ($w:ident$(::$i:ident)*$(($($tt:tt)*))?) => {
+ $crate::error::Error::from($w$(::$i)*$(($($tt)*))?)
+ };
+ ($w:ident$(::$i:ident)*$({$($tt:tt)*})?) => {
+ $crate::error::Error::from($w$(::$i)*$({$($tt)*})?)
+ };
+ ($l:literal$(, $($tt:tt)*)?) => {
+ <$crate::error::Error as From<$crate::error::ErrorKind>>::from($crate::error::ErrorKind::RuntimeError($crate::jrsonnet_macros::format_istr!($l$(, $($tt)*)?)).into())
+ };
+}
#[macro_export]
macro_rules! runtime_error {
crates/jrsonnet-evaluator/src/evaluate/compspec.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/src/evaluate/compspec.rs
@@ -0,0 +1,330 @@
+use std::rc::Rc;
+
+use jrsonnet_types::ValType;
+
+use super::{
+ destructure::{self, evaluate_locals, evaluate_locals_unbound},
+ evaluate_field_member_static, evaluate_field_member_unbound,
+};
+use crate::{
+ analyze::{LArrComp, LBind, LCompSpec, LDestruct, LExpr, LFieldMember, LObjComp, LocalId},
+ arr::ArrValue,
+ bail,
+ error::ErrorKind::*,
+ evaluate::evaluate,
+ Context, ContextBuilder, ObjValue, ObjValueBuilder, Pending, Result, Thunk, Val,
+};
+
+trait CompCollector {
+ fn reserve(&mut self, _guaranteed: usize) {}
+ fn collect(&mut self, ctx: Context) -> Result<()>;
+}
+
+struct EagerArrCollector<'a> {
+ out: &'a mut Vec<Val>,
+ value: &'a LExpr,
+}
+impl CompCollector for EagerArrCollector<'_> {
+ fn reserve(&mut self, size_hint: usize) {
+ self.out.reserve(size_hint);
+ }
+ fn collect(&mut self, ctx: Context) -> Result<()> {
+ self.out.push(evaluate(ctx, self.value)?);
+ Ok(())
+ }
+}
+
+struct LazyArrCollector<'a> {
+ out: &'a mut Vec<Thunk<Val>>,
+ value: &'a Rc<LExpr>,
+}
+impl CompCollector for LazyArrCollector<'_> {
+ fn reserve(&mut self, size_hint: usize) {
+ self.out.reserve(size_hint);
+ }
+ fn collect(&mut self, ctx: Context) -> Result<()> {
+ let value_expr = self.value.clone();
+ self.out.push(Thunk!(move || evaluate(ctx, &value_expr)));
+ Ok(())
+ }
+}
+
+struct ObjCompCollectorStatic<'a> {
+ builder: &'a mut ObjValueBuilder,
+ locals: &'a [LBind],
+ field: &'a LFieldMember,
+}
+impl CompCollector for ObjCompCollectorStatic<'_> {
+ fn reserve(&mut self, guaranteed: usize) {
+ self.builder.reserve_fields(guaranteed);
+ }
+ fn collect(&mut self, inner_ctx: Context) -> Result<()> {
+ let value_ctx = evaluate_locals(inner_ctx.clone(), self.locals);
+ evaluate_field_member_static(self.builder, inner_ctx, value_ctx, self.field)
+ }
+}
+
+struct ObjCompCollectorUnbound<'a> {
+ builder: &'a mut ObjValueBuilder,
+ locals: Rc<Vec<LBind>>,
+ this_id: Option<LocalId>,
+ field: &'a LFieldMember,
+}
+impl CompCollector for ObjCompCollectorUnbound<'_> {
+ fn reserve(&mut self, guaranteed: usize) {
+ self.builder.reserve_fields(guaranteed);
+ }
+ fn collect(&mut self, inner_ctx: Context) -> Result<()> {
+ let uctx = evaluate_locals_unbound(inner_ctx.clone(), self.locals.clone(), self.this_id);
+ evaluate_field_member_unbound(self.builder, inner_ctx, uctx, self.field)
+ }
+}
+
+pub fn evaluate_obj_comp(
+ super_obj: Option<ObjValue>,
+ ctx: Context,
+ comp: &LObjComp,
+) -> Result<Val> {
+ let mut builder = ObjValueBuilder::new();
+ if let Some(super_obj) = super_obj {
+ builder.with_super(super_obj);
+ }
+
+ let cached_overs = cache_overs(&ctx, &comp.compspecs)?;
+ if comp.this.is_some() || comp.uses_super {
+ evaluate_compspecs(
+ ctx,
+ &comp.compspecs,
+ &cached_overs,
+ 0,
+ 0,
+ &mut ObjCompCollectorUnbound {
+ builder: &mut builder,
+ locals: comp.locals.clone(),
+ this_id: comp.this,
+ field: &comp.field,
+ },
+ )?;
+ } else {
+ evaluate_compspecs(
+ ctx,
+ &comp.compspecs,
+ &cached_overs,
+ 0,
+ 0,
+ &mut ObjCompCollectorStatic {
+ builder: &mut builder,
+ locals: &comp.locals,
+ field: &comp.field,
+ },
+ )?;
+ }
+
+ Ok(Val::Obj(builder.build()))
+}
+
+pub fn evaluate_arr_comp(ctx: Context, comp: &LArrComp) -> Result<Val> {
+ let cached_overs = cache_overs(&ctx, &comp.compspecs)?;
+
+ // In eager evaluation, Context is not captured, thus updates in CoW fashion will likely to success
+ 'eager: {
+ let mut out = Vec::new();
+
+ if evaluate_compspecs_eager(
+ ctx.clone(),
+ &comp.compspecs,
+ &cached_overs,
+ 0,
+ 0,
+ &mut EagerArrCollector {
+ out: &mut out,
+ value: &comp.value,
+ },
+ )
+ .is_err()
+ {
+ break 'eager;
+ }
+ return Ok(Val::arr(out));
+ }
+
+ let mut items: Vec<Thunk<Val>> = Vec::new();
+ evaluate_compspecs(
+ ctx,
+ &comp.compspecs,
+ &cached_overs,
+ 0,
+ 0,
+ &mut LazyArrCollector {
+ out: &mut items,
+ value: &comp.value,
+ },
+ )?;
+ Ok(Val::arr(items))
+}
+
+fn cache_overs(ctx: &Context, specs: &[LCompSpec]) -> Result<Vec<Option<ArrValue>>> {
+ specs
+ .iter()
+ .map(|spec| {
+ Ok(match spec {
+ LCompSpec::For {
+ over,
+ loop_invariant: true,
+ ..
+ } => {
+ let val = evaluate(ctx.clone(), over)?;
+ let Val::Arr(arr) = val else {
+ bail!(InComprehensionCanOnlyIterateOverArray)
+ };
+ Some(arr)
+ }
+ _ => None,
+ })
+ })
+ .collect::<Result<_>>()
+}
+
+fn evaluate_compspecs_eager(
+ ctx: Context,
+ specs: &[LCompSpec],
+ cached_overs: &[Option<ArrValue>],
+ idx: usize,
+ guaranteed_reserve: usize,
+ collector: &mut dyn CompCollector,
+) -> Result<()> {
+ if idx >= specs.len() {
+ collector.reserve(guaranteed_reserve);
+ return collector.collect(ctx.clone());
+ }
+ match &specs[idx] {
+ LCompSpec::If(cond) => {
+ let val = evaluate(ctx.clone(), cond)?;
+ let Val::Bool(b) = val else {
+ bail!(TypeMismatch(
+ "if spec condition",
+ vec![ValType::Bool],
+ val.value_type()
+ ))
+ };
+ if b {
+ evaluate_compspecs_eager(ctx, specs, cached_overs, idx + 1, 0, collector)?;
+ }
+ }
+ LCompSpec::For { destruct, over, .. } => {
+ let arr = if let Some(cached) = &cached_overs[idx] {
+ cached.clone()
+ } else {
+ let arr_val = evaluate(ctx.clone(), over)?;
+ let Val::Arr(arr) = arr_val else {
+ bail!(InComprehensionCanOnlyIterateOverArray)
+ };
+ arr
+ };
+ let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;
+ match destruct {
+ LDestruct::Full(id) => {
+ let id = *id;
+ let mut inner_ctx = ContextBuilder::extend(ctx, 1).build();
+ for (i, item) in arr.iter().enumerate() {
+ // TODO: reuse one ContextBuilder for full evaluate_compspecs pipeline
+ inner_ctx.cow_fill_binding(id, Thunk::evaluated(item?));
+ evaluate_compspecs_eager(
+ inner_ctx.clone(),
+ specs,
+ cached_overs,
+ idx + 1,
+ if i == 0 { inner_reserve } else { 0 },
+ collector,
+ )?;
+ }
+ }
+ #[cfg(feature = "exp-destruct")]
+ _ => {
+ for (i, item) in arr.iter().enumerate() {
+ let item_val = item?;
+ let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);
+ destructure::destruct(
+ destruct,
+ Thunk::evaluated(item_val),
+ None,
+ &mut inner_builder,
+ );
+ let inner_ctx = inner_builder.build();
+ evaluate_compspecs_eager(
+ inner_ctx,
+ specs,
+ cached_overs,
+ idx + 1,
+ if i == 0 { inner_reserve } else { 0 },
+ collector,
+ )?;
+ }
+ }
+ }
+ }
+ }
+ Ok(())
+}
+
+fn evaluate_compspecs(
+ ctx: Context,
+ specs: &[LCompSpec],
+ cached_overs: &[Option<ArrValue>],
+ idx: usize,
+ guaranteed_reserve: usize,
+ collector: &mut dyn CompCollector,
+) -> Result<()> {
+ if idx >= specs.len() {
+ collector.reserve(guaranteed_reserve);
+ return collector.collect(ctx);
+ }
+ match &specs[idx] {
+ LCompSpec::If(cond) => {
+ let val = evaluate(ctx.clone(), cond)?;
+ let Val::Bool(b) = val else {
+ bail!(TypeMismatch(
+ "if spec condition",
+ vec![ValType::Bool],
+ val.value_type()
+ ))
+ };
+ if b {
+ evaluate_compspecs(ctx, specs, cached_overs, idx + 1, 0, collector)?;
+ }
+ }
+ LCompSpec::For { destruct, over, .. } => {
+ let arr = if let Some(cached) = &cached_overs[idx] {
+ cached.clone()
+ } else {
+ let arr_val = evaluate(ctx.clone(), over)?;
+ let Val::Arr(arr) = arr_val else {
+ bail!(InComprehensionCanOnlyIterateOverArray)
+ };
+ arr
+ };
+ let inner_reserve = guaranteed_reserve.max(1) * arr.len() as usize;
+ for (i, item) in arr.iter().enumerate() {
+ let item_val = item?;
+ let mut inner_builder = ContextBuilder::extend(ctx.clone(), 1);
+ let fctx = Pending::new();
+ destructure::destruct(
+ destruct,
+ Thunk::evaluated(item_val),
+ fctx.clone(),
+ &mut inner_builder,
+ );
+ let inner_ctx = inner_builder.build().into_future(fctx);
+ evaluate_compspecs(
+ inner_ctx,
+ specs,
+ cached_overs,
+ idx + 1,
+ if i == 0 { inner_reserve } else { 0 },
+ collector,
+ )?;
+ }
+ }
+ }
+ Ok(())
+}
crates/jrsonnet-evaluator/src/evaluate/destructure.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs
@@ -1,213 +1,256 @@
-use jrsonnet_ir::{BindSpec, Destruct};
+use std::rc::Rc;
+
+use jrsonnet_gcmodule::Trace;
use crate::{
- Context, ContextBuilder, Pending, Thunk, Val, error::Result, evaluate_method,
- evaluate_named_param,
+ analyze::{LBind, LDestruct, LDestructField, LDestructRest, LExpr, LocalId},
+ bail,
+ evaluate::evaluate,
+ Context, ContextBuilder, Pending, Result, SupThis, Thunk, Unbound, Val,
};
-#[allow(clippy::too_many_lines)]
-#[allow(unused_variables)]
-pub fn destruct(
- d: &Destruct,
- parent: Thunk<Val>,
+#[allow(dead_code, reason = "not dead in exp-destruct")]
+fn destruct_array(
+ start: &[LDestruct],
+ rest: Option<LDestructRest>,
+ end: &[LDestruct],
+
+ value: Thunk<Val>,
fctx: Pending<Context>,
- new_bindings: &mut ContextBuilder,
-) -> Result<()> {
- match d {
- Destruct::Full(v) => {
- new_bindings.try_bind(v.clone(), parent)?;
+ builder: &mut ContextBuilder,
+) {
+ let min_len = start.len() + end.len();
+ let has_rest = rest.is_some();
+ let full = Thunk!(move || {
+ let v = value.evaluate()?;
+ let Val::Arr(arr) = v else {
+ bail!("expected array");
+ };
+ if !has_rest {
+ if arr.len() as usize != min_len {
+ bail!("expected {} elements, got {}", min_len, arr.len())
+ }
+ } else if (arr.len() as usize) < min_len {
+ bail!(
+ "expected at least {} elements, but array was only {}",
+ min_len,
+ arr.len()
+ )
}
- #[cfg(feature = "exp-destruct")]
- Destruct::Skip => {}
- #[cfg(feature = "exp-destruct")]
- Destruct::Array { start, rest, end } => {
- use jrsonnet_ir::DestructRest;
+ Ok(arr)
+ });
- use crate::bail;
+ for (i, d) in start.iter().enumerate() {
+ let full = full.clone();
+ destruct(
+ d,
+ Thunk!(move || Ok(full.evaluate()?.get(i as u32)?.expect("length is checked"))),
+ fctx.clone(),
+ builder,
+ );
+ }
- let min_len = start.len() + end.len();
- let has_rest = rest.is_some();
- let full = Thunk!(move || {
- let v = parent.evaluate()?;
- let Val::Arr(arr) = v else {
- bail!("expected array");
- };
- if !has_rest {
- if arr.len() != min_len {
- bail!("expected {} elements, got {}", min_len, arr.len())
- }
- } else if arr.len() < min_len {
- bail!(
- "expected at least {} elements, but array was only {}",
- min_len,
- arr.len()
- )
- }
- Ok(arr)
- });
+ let start_len = start.len() as u32;
+ let end_len = end.len() as u32;
- {
- for (i, d) in start.iter().enumerate() {
- let full = full.clone();
- destruct(
- d,
- Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))),
- fctx.clone(),
- new_bindings,
- )?;
- }
- }
+ if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {
+ let full = full.clone();
+ builder.bind(
+ id,
+ Thunk!(move || {
+ let full = full.evaluate()?;
+ let to = full.len() - end_len;
+ Ok(Val::Arr(full.slice(
+ Some(start_len as i32),
+ Some(to as i32),
+ None,
+ )))
+ }),
+ );
+ }
- match rest {
- Some(DestructRest::Keep(v)) => {
- let start = start.len();
- let end = end.len();
- let full = full.clone();
- destruct(
- &Destruct::Full(v.clone()),
- Thunk!(move || {
- let full = full.evaluate()?;
- let to = full.len() - end;
- Ok(Val::Arr(full.slice(
- Some(start as i32),
- Some(to as i32),
- None,
- )))
- }),
- fctx.clone(),
- new_bindings,
- )?;
- }
- Some(DestructRest::Drop) | None => {}
- }
+ for (i, d) in end.iter().enumerate() {
+ let full = full.clone();
+ destruct(
+ d,
+ Thunk!(move || {
+ let full = full.evaluate()?;
+ Ok(full
+ .get(full.len() - end_len + i as u32)?
+ .expect("length is checked"))
+ }),
+ fctx.clone(),
+ builder,
+ );
+ }
+}
- {
- for (i, d) in end.iter().enumerate() {
- let full = full.clone();
- let end = end.len();
- destruct(
- d,
- Thunk!(move || {
- let full = full.evaluate()?;
- Ok(full.get(full.len() - end + i)?.expect("length is checked"))
- }),
- fctx.clone(),
- new_bindings,
- )?;
- }
- }
- }
- #[cfg(feature = "exp-destruct")]
- Destruct::Object { fields, rest } => {
- use jrsonnet_ir::DestructRest;
- use rustc_hash::FxHashSet;
+#[allow(dead_code, reason = "not dead in exp-destruct")]
+fn destruct_object(
+ fields: &[LDestructField],
+ rest: Option<LDestructRest>,
- use crate::{ObjValueBuilder, bail};
+ value: Thunk<Val>,
+ fctx: Pending<Context>,
+ builder: &mut ContextBuilder,
+) {
+ use jrsonnet_interner::IStr;
+ use rustc_hash::FxHashSet;
- let captured_fields: FxHashSet<_> = fields.iter().map(|f| f.0.clone()).collect();
- let field_names: Vec<_> = fields
- .iter()
- .map(|f| (f.0.clone(), f.2.is_some()))
- .collect();
- let has_rest = rest.is_some();
- let full = Thunk!(move || {
- let v = parent.evaluate()?;
- let Val::Obj(obj) = v else {
- bail!("expected object");
- };
- for (field, has_default) in &field_names {
- if !has_default && !obj.has_field_ex(field.clone(), true) {
- bail!("missing field: {field}");
- }
- }
- if !has_rest {
- let len = obj.len();
- if len > field_names.len() {
- bail!("too many fields, and rest not found");
- }
- }
- Ok(obj)
- });
+ use crate::{bail, ObjValueBuilder};
- match rest {
- Some(DestructRest::Keep(v)) => {
- let full = full.clone();
- destruct(
- &Destruct::Full(v.clone()),
- Thunk!(move || {
- let full = full.evaluate()?;
- let mut builder = ObjValueBuilder::new();
- builder.extend_with_core(full.as_standalone());
- builder.with_fields_omitted(captured_fields);
- Ok(Val::Obj(builder.build()))
- }),
- fctx.clone(),
- new_bindings,
- )?;
- }
- Some(DestructRest::Drop) | None => {}
+ let captured_fields: FxHashSet<IStr> = fields.iter().map(|f| f.name.clone()).collect();
+ let field_names: Vec<(IStr, bool)> = fields
+ .iter()
+ .map(|f| (f.name.clone(), f.default.is_some()))
+ .collect();
+ let has_rest = rest.is_some();
+ let full = Thunk!(move || {
+ let v = value.evaluate()?;
+ let Val::Obj(obj) = v else {
+ bail!("expected object");
+ };
+ for (field, has_default) in &field_names {
+ if !has_default && !obj.has_field_ex(field.clone(), true) {
+ bail!("missing field: {field}");
+ }
+ }
+ if !has_rest {
+ let len = obj.len();
+ if len as usize > field_names.len() {
+ bail!("too many fields, and rest not found");
}
+ }
+ Ok(obj)
+ });
+
+ if let Some(crate::analyze::LDestructRest::Keep(id)) = rest {
+ let full = full.clone();
+ builder.bind(
+ id,
+ Thunk!(move || {
+ let full = full.evaluate()?;
+ let mut out = ObjValueBuilder::new();
+ out.extend_with_core(full.as_standalone());
+ out.with_fields_omitted(captured_fields);
+ Ok(Val::Obj(out.build()))
+ }),
+ );
+ }
- for (field, d, default) in fields {
- let default = default.clone().map(|e| (fctx.clone(), e));
- let value = {
- let field = field.clone();
- let full = full.clone();
- Thunk!(move || {
- let full = full.evaluate()?;
- if let Some(field) = full.get(field)? {
- Ok(field)
- } else {
- let (fctx, expr) = default.as_ref().expect("shape is checked");
- Ok(crate::evaluate(fctx.clone().unwrap(), expr)?)
- }
- })
- };
+ for field in fields {
+ let field_name = field.name.clone();
+ let default: Option<(Pending<Context>, Rc<LExpr>)> =
+ field.default.as_ref().map(|e| (fctx.clone(), e.clone()));
+ let field_full = full.clone();
+ let value_thunk = Thunk!(move || {
+ let obj = field_full.evaluate()?;
+ obj.get(field_name)?.map_or_else(
+ || {
+ let (fctx, expr) = default.as_ref().expect("shape is checked");
+ evaluate(fctx.unwrap(), expr)
+ },
+ Ok,
+ )
+ });
- if let Some(d) = d {
- destruct(d, value, fctx.clone(), new_bindings)?;
- } else {
- destruct(
- &Destruct::Full(field.clone()),
- value,
- fctx.clone(),
- new_bindings,
- )?;
- }
- }
+ if let Some(into) = &field.into {
+ destruct(into, value_thunk, fctx.clone(), builder);
+ } else {
+ unreachable!("analyzer lowers object-destruct shorthands into `into`");
}
}
- Ok(())
}
-pub fn evaluate_dest(
- d: &BindSpec,
+/// Bind a pre-built thunk to an [`LDestruct`] pattern, inserting one
+/// binding per [`LocalId`] the pattern introduces.
+///
+/// `fctx` is needed for object-destruct defaults (feature `exp-destruct`).
+#[allow(unused_variables)]
+pub fn destruct(
+ d: &LDestruct,
+ value: Thunk<Val>,
fctx: Pending<Context>,
- new_bindings: &mut ContextBuilder,
-) -> Result<()> {
+ builder: &mut ContextBuilder,
+) {
match d {
- BindSpec::Field { into, value } => {
- let name = into.name();
- let value = value.clone();
- let data = {
- let fctx = fctx.clone();
- Thunk!(move || evaluate_named_param(fctx.unwrap(), &value, name))
- };
- destruct(into, data, fctx, new_bindings)?;
+ LDestruct::Full(id) => builder.bind(*id, value),
+ #[cfg(feature = "exp-destruct")]
+ LDestruct::Skip => {}
+ #[cfg(feature = "exp-destruct")]
+ LDestruct::Array { start, rest, end } => destruct_array(start, rest, end, value, fctx, builder),
+ #[cfg(feature = "exp-destruct")]
+ LDestruct::Object { fields, rest } => destruct_object(fields, rest, value, fctx, builder),
+ }
+}
+
+/// Bind one [`LBind`] as a lazy thunk that evaluates in the given
+/// future context. Mirrors the old `evaluate_dest` — one entry per
+/// binding in a `local … ;` frame.
+pub fn evaluate_dest(bind: &LBind, fctx: Pending<Context>, builder: &mut ContextBuilder) {
+ let value = bind.value.clone();
+ let fctx_clone = fctx.clone();
+ let thunk = Thunk!(move || {
+ let ctx = fctx_clone.unwrap();
+ evaluate(ctx, &value)
+ });
+ destruct(&bind.destruct, thunk, fctx, builder);
+}
+
+/// Bind each LBind's value as a lazy thunk. Mutually recursive locals
+/// resolve lazily through the shared Pending<Context>.
+pub fn evaluate_locals(parent: Context, binds: &[LBind]) -> Context {
+ if binds.is_empty() {
+ return parent;
+ }
+ let fctx = Context::new_future();
+ let mut builder =
+ ContextBuilder::extend(parent, binds.iter().map(|b| b.destruct.ids().len()).sum());
+ for bind in binds {
+ evaluate_dest(bind, fctx.clone(), &mut builder);
+ }
+ builder.build().into_future(fctx)
+}
+
+pub trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}
+impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}
+
+pub fn evaluate_locals_unbound(
+ fctx: Context,
+ locals: Rc<Vec<LBind>>,
+ this_id: Option<LocalId>,
+) -> impl CloneableUnbound<Context> {
+ #[derive(Trace, Clone)]
+ struct UnboundLocals {
+ fctx: Context,
+ locals: Rc<Vec<LBind>>,
+ this_id: Option<LocalId>,
+ }
+ impl Unbound for UnboundLocals {
+ type Bound = Context;
+
+ fn bind(&self, sup_this: SupThis) -> Result<Context> {
+ let parent = self.fctx.clone();
+
+ let fctx = Context::new_future();
+ let mut builder = ContextBuilder::extend(
+ parent,
+ self.locals.iter().map(|b| b.destruct.ids().len()).sum(),
+ );
+ for b in self.locals.iter() {
+ evaluate_dest(b, fctx.clone(), &mut builder);
+ }
+ if let Some(this_id) = self.this_id {
+ builder.bind(this_id, Thunk::evaluated(Val::Obj(sup_this.this().clone())));
+ }
+ let ctx = builder.build_sup_this(sup_this).into_future(fctx);
+ Ok(ctx)
}
- BindSpec::Function {
- name,
- params,
- value,
- } => {
- let params = params.clone();
- let name = name.clone();
- let value = value.clone();
- new_bindings.try_bind(
- name.clone(),
- Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value))),
- )?;
- }
}
- Ok(())
+
+ UnboundLocals {
+ fctx,
+ locals,
+ this_id,
+ }
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth1use std::rc::Rc;23use jrsonnet_gcmodule::{Cc, Trace};4use jrsonnet_interner::IStr;5use jrsonnet_ir::ImportKind;6use jrsonnet_types::ValType;78use self::{9 compspec::{evaluate_arr_comp, evaluate_obj_comp},10 destructure::{evaluate_locals, evaluate_locals_unbound},11 operator::evaluate_binary_op_special,12};13use crate::{14 analyze::{15 LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction, LIndexPart, LObjBody,16 LObjMembers,17 },18 bail,19 error::{suggest_object_fields, ErrorKind::*},20 evaluate::operator::evaluate_unary_op,21 function::{prepared::PreparedFuncVal, CallLocation, FuncDesc, FuncVal},22 in_frame, runtime_error,23 typed::FromUntyped as _,24 val::{CachedUnbound, Thunk},25 with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _,26 SupThis, Unbound, Val,27};2829pub mod compspec;30pub mod destructure;31pub mod operator;3233// This is the amount of bytes that need to be left on the stack before increasing the size.34// It must be at least as large as the stack required by any code that does not call35// `ensure_sufficient_stack`.36const RED_ZONE: usize = 100 * 1024;3738// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then39// on. This flag has performance relevant characteristics. Don't set it too high.40const STACK_PER_RECURSION: usize = 1024 * 1024;4142/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations43/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit44/// from this.45///46/// Should not be sprinkled around carelessly, as it causes a little bit of overhead.47#[inline]48pub fn ensure_sufficient_stack<R>(f: impl FnOnce() -> R) -> R {49 stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)50}5152pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {53 // TODO: Eager trivial array54 Some(match expr {55 LExpr::Str(s) => Val::string(s.clone()),56 LExpr::Num(n) => Val::Num(*n),57 LExpr::Bool(false) => Val::Bool(false),58 LExpr::Bool(true) => Val::Bool(true),59 LExpr::Null => Val::Null,60 _ => return None,61 })62}6364/// Evaluate a method definition.65pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {66 Val::Func(FuncVal::Normal(Cc::new(FuncDesc {67 name,68 ctx,69 func: func.clone(),70 })))71}7273pub fn evaluate_field_name(ctx: Context, field_name: &LFieldName) -> Result<Option<IStr>> {74 Ok(match field_name {75 LFieldName::Fixed(n) => Some(n.clone()),76 LFieldName::Dyn(expr) => in_frame(77 // TODO: Spanned<LFieldName>78 CallLocation::native(),79 || "evaluating field name".to_string(),80 || {81 let v = evaluate(ctx.clone(), expr)?;82 Ok(if matches!(v, Val::Null) {83 None84 } else {85 Some(IStr::from_untyped(v)?)86 })87 },88 )?,89 })90}9192pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {93 Ok(if tailstrict {94 Thunk::evaluated(evaluate(ctx, &expr)?)95 } else {96 Thunk!(move || { evaluate(ctx, &expr) })97 })98}99100mod names {101 use crate::names;102103 names! {104 anonymous: "anonymous",105 }106}107108pub fn evaluate_named(name: &IStr, ctx: Context, expr: &LExpr) -> Result<Val> {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 ));115 }116 evaluate(ctx, expr)117}118119pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {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 } => {139 let ctx = evaluate_locals(ctx, binds);140 evaluate(ctx, body)?141 }142 LExpr::IfElse {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::Null161 }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 }172173 LExpr::Function(func) => evaluate_method(174 ctx,175 func.name.clone().unwrap_or_else(names::anonymous),176 func,177 ),178 LExpr::Apply {179 applicable,180 args,181 tailstrict,182 } => evaluate_apply(183 ctx,184 applicable,185 args,186 CallLocation::new(&args.span),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) => {192 let lhs_val = evaluate(ctx.clone(), lhs)?;193 let Val::Obj(lhs_obj) = lhs_val else {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)?201 }202 LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,203 LExpr::Slice(slice) => {204 use crate::typed::BoundedUsize;205 let val = evaluate(ctx.clone(), &slice.value)?;206 let indexable = val.into_indexable()?;207 let start = slice208 .start209 .as_ref()210 .map(|e| evaluate(ctx.clone(), e))211 .transpose()?212 .map(|v| -> Result<i32> {213 v.as_num()214 .ok_or_else(|| {215 TypeMismatch("slice start", vec![ValType::Num], v.value_type()).into()216 })217 .map(|n| n as i32)218 })219 .transpose()?;220 let end = slice221 .end222 .as_ref()223 .map(|e| evaluate(ctx.clone(), e))224 .transpose()?225 .map(|v| -> Result<i32> {226 v.as_num()227 .ok_or_else(|| {228 TypeMismatch("slice end", vec![ValType::Num], v.value_type()).into()229 })230 .map(|n| n as i32)231 })232 .transpose()?;233 let step = slice234 .step235 .as_ref()236 .map(|e| evaluate(ctx, e))237 .transpose()?238 .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {239 let n = v.as_num().ok_or_else(|| -> crate::Error {240 TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()241 })?;242 BoundedUsize::new(n as usize)243 .ok_or_else(|| runtime_error!("slice step must be >= 1"))244 })245 .transpose()?;246 Val::from(indexable.slice(start, end, step)?)247 }248 LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super()?),249 LExpr::Import {250 kind,251 kind_span,252 path,253 } => with_state(|state| {254 let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;255 Ok::<_, Error>(match kind.value {256 ImportKind::Normal => in_frame(257 CallLocation::new(&kind.span),258 || "import".to_string(),259 || state.import_resolved(resolved),260 )?,261 ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),262 ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),263 })264 })?,265 })266}267268fn evaluate_apply(269 ctx: Context,270 applicable: &LExpr,271 args: &LArgsDesc,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 };279280 let name = func.name();281 let unnamed = args282 .unnamed283 .iter()284 .cloned()285 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))286 .collect::<Result<Vec<_>>>()?;287288 let named = args289 .values290 .iter()291 .cloned()292 .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))293 .collect::<Result<Vec<_>>>()?;294 let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)295 .with_description_src(loc, || format!("function <{name}> preparation"))?;296 in_frame(297 loc,298 || format!("function <{name}> call"),299 || prepare.call(CallLocation::native(), &unnamed, &named),300 )301}302303fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {304 let mut value = if let LExpr::Super = indexable {305 let sup_this = ctx.try_sup_this()?;306 // First part must be evaluated to get the super field name307 if parts.is_empty() {308 bail!(RuntimeError("super requires an index".into()))309 }310 let key_val = evaluate(ctx.clone(), &parts[0].value)?;311 let Val::Str(key) = &key_val else {312 bail!(ValueIndexMustBeTypeGot(313 ValType::Obj,314 ValType::Str,315 key_val.value_type(),316 ))317 };318 let field = key.clone().into_flat();319 if let Some(v) = sup_this.get_super(field.clone())? {320 // Continue with remaining parts321 let mut value = v;322 for part in &parts[1..] {323 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;324 }325 return Ok(value);326 }327 let suggestions = suggest_object_fields(sup_this.this(), field.clone());328 bail!(NoSuchField(field, suggestions))329 } else {330 evaluate(ctx.clone(), indexable)?331 };332333 for part in parts {334 value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;335 }336 Ok(value)337}338339fn index_val(ctx: Context, loc: CallLocation<'_>, value: Val, part: &LIndexPart) -> Result<Val> {340 let key_val = evaluate(ctx, &part.value)?;341 Ok(match (&value, &key_val) {342 (Val::Obj(obj), Val::Str(key)) => {343 let field = key.clone().into_flat();344 if let Some(v) = obj345 .get(field.clone())346 .with_description_src(loc, || format!("field <{field}> access"))?347 {348 v349 } else {350 bail!(NoSuchField(351 field.clone(),352 suggest_object_fields(obj, field)353 ))354 }355 }356 (Val::Arr(arr), Val::Num(idx)) => {357 let n = idx.get();358 if n.fract() > f64::EPSILON {359 bail!(FractionalIndex)360 }361 if n < 0.0 {362 bail!(ArrayBoundsError(363 n as isize, // truncation is fine for error display364 arr.len()365 ));366 }367 #[expect(368 clippy::cast_possible_truncation,369 clippy::cast_sign_loss,370 reason = "n is checked positive"371 )]372 let i = n as u32;373 arr.get(i)374 .with_description_src(loc, || format!("element <{i}> access"))?375 .ok_or_else(|| ArrayBoundsError(i as isize, arr.len()))?376 }377 (Val::Str(s), Val::Num(idx)) => {378 let n = idx.get();379 if n.fract() > f64::EPSILON {380 bail!(FractionalIndex)381 }382 let flat = s.clone().into_flat();383 if n < 0.0 {384 bail!(ArrayBoundsError(385 n as isize, // truncation is fine for error display386 flat.chars().count() as u32387 ));388 }389 #[expect(390 clippy::cast_possible_truncation,391 clippy::cast_sign_loss,392 reason = "n is checked positive, overflow will truncate as expected"393 )]394 let i = n as usize;395 let Some(char) = flat.chars().nth(i) else {396 bail!(StringBoundsError(i, flat.chars().count()))397 };398 Val::string(char)399 }400 _ => bail!(ValueIndexMustBeTypeGot(401 value.value_type(),402 ValType::Str,403 key_val.value_type()404 )),405 })406}407408fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {409 match body {410 LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),411 LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),412 }413}414415pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(416 builder: &mut ObjValueBuilder,417 ctx: Context,418 uctx: B,419 field: &LFieldMember,420) -> Result<()> {421 #[derive(Trace)]422 struct UnboundValue<B: Trace> {423 uctx: B,424 value: Rc<LExpr>,425 name: IStr,426 }427 impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {428 type Bound = Val;429 fn bind(&self, sup_this: SupThis) -> Result<Val> {430 evaluate(self.uctx.bind(sup_this)?, &self.value)431 }432 }433434 let LFieldMember {435 name,436 plus,437 visibility,438 value,439 } = field;440 let Some(name) = evaluate_field_name(ctx, name)? else {441 return Ok(());442 };443444 builder445 .field(name.clone())446 .with_add(*plus)447 .with_visibility(*visibility)448 .bindable(UnboundValue {449 uctx,450 value: value.clone(),451 name,452 })453}454pub fn evaluate_field_member_static(455 builder: &mut ObjValueBuilder,456 field_ctx: Context,457 value_ctx: Context,458 field: &LFieldMember,459) -> Result<()> {460 let LFieldMember {461 name,462 plus,463 visibility,464 value,465 } = field;466 let Some(name) = evaluate_field_name(field_ctx, name)? else {467 return Ok(());468 };469470 let value = value.clone();471 builder472 .field(name)473 .with_add(*plus)474 .with_visibility(*visibility)475 .try_thunk(Thunk!(move || { evaluate(value_ctx, &value) }))?;476 Ok(())477}478479fn evaluate_obj_members(480 super_obj: Option<ObjValue>,481 ctx: Context,482 members: &LObjMembers,483) -> Result<Val> {484 let mut builder = ObjValueBuilder::with_capacity(members.fields.len());485 if let Some(sup) = super_obj {486 builder.with_super(sup);487 }488489 let needs_unbound = members.this.is_some() || members.uses_super;490491 if needs_unbound {492 let uctx = CachedUnbound::new(evaluate_locals_unbound(493 ctx.clone(),494 members.locals.clone(),495 members.this,496 ));497 for field in &members.fields {498 evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;499 }500 if !members.asserts.is_empty() {501 builder.assert(evaluate_object_assertions_unbound(502 uctx,503 members.asserts.clone(),504 ));505 }506 } else {507 let field_ctx = ctx;508 let value_ctx = evaluate_locals(field_ctx.clone(), &members.locals);509 for field in &members.fields {510 evaluate_field_member_static(511 &mut builder,512 field_ctx.clone(),513 value_ctx.clone(),514 field,515 )?;516 }517 if !members.asserts.is_empty() {518 builder.assert(evaluate_object_assertions_static(519 value_ctx,520 members.asserts.clone(),521 ));522 }523 }524525 Ok(Val::Obj(builder.build()))526}527528pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {529 let LAssertStmt { cond, message } = assertion;530 let assertion_result = in_frame(531 CallLocation::native(),532 || "assertion condition".to_owned(),533 || bool::from_untyped(evaluate(ctx.clone(), cond)?),534 )?;535 if !assertion_result {536 in_frame(537 CallLocation::new(&cond.span),538 || "assertion failure".to_owned(),539 || {540 if let Some(msg) = message {541 bail!(AssertionFailed(evaluate(ctx, msg)?.to_string()?));542 }543 bail!(AssertionFailed(Val::Null.to_string()?));544 },545 )?;546 }547 Ok(())548}549550fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(551 uctx: B,552 asserts: Rc<Vec<LAssertStmt>>,553) -> impl ObjectAssertion {554 #[derive(Trace)]555 struct ObjectAssert<B: Trace> {556 uctx: B,557 asserts: Rc<Vec<LAssertStmt>>,558 }559 impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {560 fn run(&self, sup_this: SupThis) -> Result<()> {561 let ctx = self.uctx.bind(sup_this)?;562 for assert in &*self.asserts {563 evaluate_assert(ctx.clone(), assert)?;564 }565 Ok(())566 }567 }568 ObjectAssert { uctx, asserts }569}570fn evaluate_object_assertions_static(571 ctx: Context,572 asserts: Rc<Vec<LAssertStmt>>,573) -> impl ObjectAssertion {574 #[derive(Trace)]575 struct ObjectAssert {576 ctx: Context,577 asserts: Rc<Vec<LAssertStmt>>,578 }579 impl ObjectAssertion for ObjectAssert {580 fn run(&self, _sup_this: SupThis) -> Result<()> {581 for assert in &*self.asserts {582 evaluate_assert(self.ctx.clone(), assert)?;583 }584 Ok(())585 }586 }587 ObjectAssert { ctx, asserts }588}crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -1,16 +1,17 @@
use std::cmp::Ordering;
-use jrsonnet_ir::{BinaryOpType, Expr, UnaryOpType};
+use jrsonnet_ir::{BinaryOpType, UnaryOpType};
use crate::{
- Context, Result, Val,
+ analyze::LExpr,
arr::ArrValue,
- bail,
+ bail, error,
error::ErrorKind::*,
- evaluate,
+ evaluate::evaluate,
stdlib::std_format,
typed::IntoUntyped as _,
- val::{StrValue, equals},
+ val::{equals, StrValue},
+ Context, Result, Val,
};
pub fn evaluate_unary_op(op: UnaryOpType, b: &Val) -> Result<Val> {
@@ -39,7 +40,9 @@
(o, Str(a)) => Val::string(format!("{}{a}", o.clone().to_string()?)),
(Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),
- (Arr(a), Arr(b)) => Val::Arr(ArrValue::extended(a.clone(), b.clone())),
+ (Arr(a), Arr(b)) => Val::Arr(
+ ArrValue::extended(a.clone(), b.clone()).ok_or_else(|| error!("array is too large"))?,
+ ),
(Num(v1), Num(v2)) => Val::try_num(v1.get() + v2.get())?,
@@ -158,19 +161,27 @@
pub fn evaluate_binary_op_special(
ctx: Context,
- a: &Expr,
+ a: &LExpr,
op: BinaryOpType,
- b: &Expr,
+ b: &LExpr,
) -> Result<Val> {
use BinaryOpType::*;
use Val::*;
+
Ok(match (evaluate(ctx.clone(), a)?, op, b) {
- (Bool(true), Or, _o) => Val::Bool(true),
- (Bool(false), And, _o) => Val::Bool(false),
+ (Bool(true), Or, _) => Val::Bool(true),
+ (Bool(false), And, _) => Val::Bool(false),
#[cfg(feature = "exp-null-coaelse")]
(Null, NullCoaelse, eb) => evaluate(ctx, eb)?,
#[cfg(feature = "exp-null-coaelse")]
- (a, NullCoaelse, _o) => a,
+ (a, NullCoaelse, _) => a,
+ (a, In, LExpr::Super) => {
+ let sup_this = ctx.try_sup_this()?;
+ if !sup_this.has_super() {
+ return Ok(Val::Bool(false));
+ }
+ return Ok(Val::Bool(sup_this.field_in_super(a.to_string()?)));
+ }
(a, op, eb) => evaluate_binary_op_normal(&a, op, &evaluate(ctx, eb)?)?,
})
}
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -19,6 +19,23 @@
};
}
+#[macro_export]
+macro_rules! names {
+ ($($name:ident: $val:literal),* $(,)?) => {
+ struct Names {
+ $($name: $crate::IStr,)*
+ }
+ thread_local! {
+ static NAMES: Names = Names {
+ $($name: $crate::IStr::from($val)),*
+ };
+ }
+ $(pub fn $name() -> $crate::IStr {
+ NAMES.with(|n| n.$name.clone())
+ })*
+ }
+}
+
cc_dyn!(
#[derive(Clone)]
BuiltinFunc,
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -3,22 +3,24 @@
use educe::Educe;
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
-use jrsonnet_ir::{Destruct, Expr, ExprParams, Span};
+use jrsonnet_ir::Span;
pub use jrsonnet_macros::builtin;
use self::{
builtin::Builtin,
- parse::parse_default_function_call,
- prepared::{PreparedCall, parse_prepared_builtin_call, parse_prepared_function_call},
+ prepared::{parse_prepared_builtin_call, PreparedCall},
};
use crate::{
- Context, Result, Thunk, Val, evaluate, evaluate_trivial, function::builtin::BuiltinFunc,
+ analyze::{LDestruct, LExpr, LFunction},
+ evaluate::{destructure::destruct, ensure_sufficient_stack, evaluate, evaluate_trivial},
+ function::builtin::BuiltinFunc,
+ Context, ContextBuilder, Result, Thunk, Val,
};
pub mod builtin;
mod native;
mod parse;
-mod prepared;
+pub(crate) mod prepared;
pub use jrsonnet_ir::function::*;
pub use native::NativeFn;
@@ -66,19 +68,63 @@
/// context will contain `a`.
pub ctx: Context,
- /// Function parameter definition
- pub params: ExprParams,
- /// Function body
- pub body: Rc<Expr>,
+ #[educe(PartialEq(method = Rc::ptr_eq))]
+ pub func: Rc<LFunction>,
}
+
impl FuncDesc {
- /// Create body context, but fill arguments without defaults with lazy error
- pub fn default_body_context(&self) -> Result<Context> {
- parse_default_function_call(self.ctx.clone(), &self.params)
+ pub fn signature(&self) -> FunctionSignature {
+ self.func.signature.clone()
}
+ pub fn call(
+ &self,
+ unnamed: &[Thunk<Val>],
+ named: &[Thunk<Val>],
+ prepared: &PreparedCall,
+ ) -> Result<Val> {
+ let has_defaults = !prepared.defaults().is_empty();
+ let mut builder = ContextBuilder::extend(self.ctx.clone(), self.func.params.len());
+
+ let fctx = Context::new_future();
+ for (param_idx, thunk) in unnamed.iter().enumerate() {
+ destruct(
+ &self.func.params[param_idx].destruct,
+ thunk.clone(),
+ fctx.clone(),
+ &mut builder,
+ );
+ }
+
+ for &(param_idx, arg_idx) in prepared.named() {
+ destruct(
+ &self.func.params[param_idx].destruct,
+ named[arg_idx].clone(),
+ fctx.clone(),
+ &mut builder,
+ );
+ }
+
+ if has_defaults {
+ for ¶m_idx in prepared.defaults() {
+ let param = &self.func.params[param_idx];
+ if let Some(default_expr) = ¶m.default {
+ let default_expr = default_expr.clone();
+ let fctxc = fctx.clone();
+ let thunk = Thunk!(move || {
+ let ctx = fctxc.unwrap();
+ evaluate(ctx, &default_expr)
+ });
+ destruct(¶m.destruct, thunk, fctx.clone(), &mut builder);
+ }
+ }
+ };
+ let ctx = builder.build().into_future(fctx);
+ ensure_sufficient_stack(|| evaluate(ctx, &self.func.body))
+ }
+
pub fn evaluate_trivial(&self) -> Option<Val> {
- evaluate_trivial(&self.body)
+ evaluate_trivial(&self.func.body)
}
}
@@ -115,12 +161,12 @@
pub fn params(&self) -> FunctionSignature {
match self {
Self::Builtin(i) => i.params(),
- Self::Normal(p) => p.params.signature.clone(),
+ Self::Normal(p) => p.signature(),
}
}
/// Amount of non-default required arguments
- pub fn params_len(&self) -> usize {
- self.params().iter().filter(|p| !p.has_default()).count()
+ pub fn params_len(&self) -> u32 {
+ self.params().iter().filter(|p| !p.has_default()).count() as u32
}
/// Function name, as defined in code.
pub fn name(&self) -> IStr {
@@ -139,16 +185,7 @@
_tailstrict: bool,
) -> Result<Val> {
match self {
- FuncVal::Normal(func) => {
- let body_ctx = parse_prepared_function_call(
- func.ctx.clone(),
- prepared,
- &func.params,
- unnamed,
- named,
- )?;
- evaluate(body_ctx, &func.body)
- }
+ FuncVal::Normal(func) => func.call(unnamed, named, prepared),
FuncVal::Builtin(b) => {
let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named);
b.call(loc, &args)
@@ -156,7 +193,7 @@
}
}
- /// Is this function an indentity function.
+ /// Is this function an identity function.
///
/// Currently only works for builtin `std.id`, aka `Self::Id` value, and `function(x) x`.
///
@@ -165,21 +202,19 @@
match self {
Self::Builtin(b) => b.as_any().downcast_ref::<builtin_id>().is_some(),
Self::Normal(desc) => {
- if desc.params.len() != 1 {
+ if desc.func.params.len() != 1 {
return false;
}
- let param = &desc.params.exprs[0];
+ let param = &desc.func.params[0];
if param.default.is_some() {
return false;
}
-
- #[allow(clippy::infallible_destructuring_match)]
- let id = match ¶m.destruct {
- Destruct::Full(id) => id,
- #[cfg(feature = "exp-destruct")]
- _ => return false,
+ #[allow(irrefutable_let_patterns, reason = "refutable with exp-destruct")]
+ let LDestruct::Full(id) = ¶m.destruct
+ else {
+ return false;
};
- matches!(&*desc.body, Expr::Var(v) if &**v == id)
+ matches!(&*desc.func.body, LExpr::Local(v) if v == id)
}
}
}
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,49 +1,39 @@
-use jrsonnet_ir::ExprParams;
+use std::rc::Rc;
use crate::{
- Context, ContextBuilder, Thunk,
- destructure::destruct,
- error::{ErrorKind::*, Result},
- evaluate_named_param,
+ analyze::LFunction,
+ evaluate::{destructure::destruct, evaluate},
+ Context, ContextBuilder, Result, Thunk,
};
-/// Creates Context, which has all argument default values applied
-/// and with unbound values causing error to be returned
-pub fn parse_default_function_call(body_ctx: Context, params: &ExprParams) -> Result<Context> {
+/// Creates Context with all argument default values applied
+/// and with unbound values causing error to be returned.
+pub fn parse_default_function_call(body_ctx: Context, func: &Rc<LFunction>) -> Result<Context> {
let fctx = Context::new_future();
-
- let mut ctx = ContextBuilder::extend(body_ctx);
+ let mut builder = ContextBuilder::extend(body_ctx, func.params.len());
- for param in params.exprs.iter() {
- if let Some(v) = ¶m.default {
- destruct(
- ¶m.destruct.clone(),
- {
- let ctx = fctx.clone();
- let name = param.destruct.name();
- let value = v.clone();
- Thunk!(move || evaluate_named_param(ctx.unwrap(), &value, name))
- },
- fctx.clone(),
- &mut ctx,
- )?;
+ for param in &func.params {
+ if let Some(default_expr) = ¶m.default {
+ let default_expr = default_expr.clone();
+ let fctxc = fctx.clone();
+ let thunk = Thunk!(move || {
+ let ctx = fctxc.unwrap();
+ evaluate(ctx, &default_expr)
+ });
+ destruct(¶m.destruct, thunk, fctx.clone(), &mut builder);
} else {
- destruct(
- ¶m.destruct,
- {
- let param_name = param.destruct.name();
- let params = params.clone();
- Thunk!(move || Err(FunctionParameterNotBoundInCall(
- param_name,
- params.signature
- )
- .into()))
- },
- fctx.clone(),
- &mut ctx,
- )?;
+ let name = param.name.clone().unwrap_or_else(|| "<param>".into());
+ let thunk = Thunk::errored(
+ crate::error::ErrorKind::FunctionParameterNotBoundInCall(
+ jrsonnet_ir::function::ParamName::Named(name),
+ jrsonnet_ir::function::FunctionSignature::empty(),
+ )
+ .into(),
+ );
+ destruct(¶m.destruct, thunk, fctx.clone(), &mut builder);
}
}
- Ok(ctx.build().into_future(fctx))
+ let ctx = builder.build().into_future(fctx);
+ Ok(ctx)
}
crates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/prepared.rs
+++ b/crates/jrsonnet-evaluator/src/function/prepared.rs
@@ -1,13 +1,13 @@
use std::rc::Rc;
use jrsonnet_gcmodule::{Acyclic, Trace};
-use jrsonnet_ir::{ExprParams, IStr, function::FunctionSignature};
+use jrsonnet_ir::{IStr, function::FunctionSignature};
use rustc_hash::FxHashSet;
use super::{CallLocation, FuncVal};
use crate::{
- Context, ContextBuilder, Pending, Result, Thunk, Val, bail, destructure::destruct,
- error::ErrorKind::*, evaluate_named_param,
+ Result, Thunk, Val, bail,
+ error::ErrorKind::*,
};
#[derive(Debug, Trace, Clone)]
@@ -42,6 +42,15 @@
defaults: Vec<usize>,
}
+impl PreparedCall {
+ pub fn named(&self) -> &[(usize, usize)] {
+ &self.named
+ }
+ pub fn defaults(&self) -> &[usize] {
+ &self.defaults
+ }
+}
+
pub fn prepare_call(
params: FunctionSignature,
unnamed: usize,
@@ -51,6 +60,25 @@
bail!(TooManyArgsFunctionHas(params.len(), params))
}
+ // Fast path: positional-only (no named args). Avoids HashMap entirely.
+ if named.is_empty() {
+ let mut defaults = Vec::new();
+ for (param_id, param) in params.iter().enumerate().skip(unnamed) {
+ if param.has_default() {
+ defaults.push(param_id);
+ } else {
+ bail!(FunctionParameterNotBoundInCall(
+ param.name().clone(),
+ params.clone(),
+ ))
+ }
+ }
+ return Ok(PreparedCall {
+ named: Vec::new(),
+ defaults,
+ });
+ }
+
let expected_defaults = (params.len() - unnamed).saturating_sub(named.len());
let mut ops = PreparedCall {
named: Vec::with_capacity(named.len()),
@@ -110,63 +138,6 @@
}
Ok(ops)
-}
-pub fn parse_prepared_function_call(
- body_ctx: Context,
- prepared: &PreparedCall,
- params: &ExprParams,
- unnamed: &[Thunk<Val>],
- named: &[Thunk<Val>],
-) -> Result<Context> {
- let mut ctx = ContextBuilder::extend(body_ctx);
-
- let destruct_ctx = Pending::new();
-
- for (param_idx, unnamed) in unnamed.iter().enumerate() {
- destruct(
- ¶ms.exprs[param_idx].destruct,
- unnamed.clone(),
- destruct_ctx.clone(),
- &mut ctx,
- )?;
- }
-
- for (param_idx, arg_idx) in prepared.named.iter().copied() {
- destruct(
- ¶ms.exprs[param_idx].destruct,
- named[arg_idx].clone(),
- destruct_ctx.clone(),
- &mut ctx,
- )?;
- }
-
- if prepared.defaults.is_empty() {
- let body_ctx = ctx.build().into_future(destruct_ctx);
- Ok(body_ctx)
- } else {
- let fctx = Context::new_future();
- let mut ctx = ctx.commit();
- for param_idx in prepared.defaults.iter().copied() {
- // let param = params.0.rc_idx(param_idx);
- destruct(
- ¶ms.exprs[param_idx].destruct,
- {
- let ctx = fctx.clone();
- let params = params.clone();
- Thunk!(move || {
- let param = ¶ms.exprs[param_idx];
- let name = param.destruct.name();
- let value = param.default.as_ref().expect("default exists");
- evaluate_named_param(ctx.unwrap(), value, name)
- })
- },
- fctx.clone(),
- &mut ctx,
- )?;
- }
-
- Ok(ctx.build().into_future(fctx).into_future(destruct_ctx))
- }
}
pub fn parse_prepared_builtin_call(
prepared: &PreparedCall,
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -3,16 +3,16 @@
use jrsonnet_interner::{IBytes, IStr};
use jrsonnet_ir::NumValue;
use serde::{
- Deserialize, Serialize, Serializer,
de::{self, Visitor},
ser::{
Error, SerializeMap, SerializeSeq, SerializeStruct, SerializeStructVariant, SerializeTuple,
SerializeTupleStruct, SerializeTupleVariant,
},
+ Deserialize, Serialize, Serializer,
};
use crate::{
- Error as JrError, ObjValue, ObjValueBuilder, Result, Val, in_description_frame, runtime_error,
+ in_description_frame, runtime_error, Error as JrError, ObjValue, ObjValueBuilder, Result, Val,
};
impl<'de> Deserialize<'de> for Val {
@@ -182,7 +182,7 @@
#[cfg(feature = "exp-bigint")]
Self::BigInt(b) => b.serialize(serializer),
Self::Arr(arr) => {
- let mut seq = serializer.serialize_seq(Some(arr.len()))?;
+ let mut seq = serializer.serialize_seq(Some(arr.len() as usize))?;
for (i, element) in arr.iter().enumerate() {
let mut serde_error = None;
in_description_frame(
@@ -203,7 +203,7 @@
seq.end()
}
Self::Obj(obj) => {
- let mut map = serializer.serialize_map(Some(obj.len()))?;
+ let mut map = serializer.serialize_map(Some(obj.len() as usize))?;
for (field, value) in obj.iter(
#[cfg(feature = "exp-preserve-order")]
true,
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -36,14 +36,13 @@
pub use ctx::*;
pub use dynamic::*;
pub use error::{Error, ErrorKind::*, Result, ResultExt};
-pub use evaluate::*;
+pub use evaluate::ensure_sufficient_stack;
use function::CallLocation;
pub use import::*;
-use jrsonnet_gcmodule::{Cc, Trace, cc_dyn};
+use jrsonnet_gcmodule::{cc_dyn, Cc, Trace};
pub use jrsonnet_interner::{IBytes, IStr};
-pub use jrsonnet_ir as parser;
-pub use jrsonnet_ir::NumValue;
-use jrsonnet_ir::{Expr, Source, SourcePath};
+use jrsonnet_ir::Expr;
+pub use jrsonnet_ir::{NumValue, Source, SourcePath, Span};
#[doc(hidden)]
pub use jrsonnet_macros;
@@ -58,6 +57,7 @@
pub use tla::apply_tla;
pub use val::{Thunk, Val};
+pub mod analyze;
use crate::gc::WithCapacityExt as _;
#[allow(clippy::needless_return)]
@@ -87,7 +87,7 @@
jrsonnet_ir_parser::parse(code, &jrsonnet_ir_parser::ParserSettings { source }).map_err(|e| {
SyntaxError {
message: e.message,
- location: (e.location.0, e.location.1),
+ location: e.location,
}
})
}
@@ -165,7 +165,7 @@
pub trait ContextInitializer {
/// For composability: extend builder. May panic if this initialization is not supported,
/// and the context may only be created via `initialize`.
- fn populate(&self, for_file: Source, builder: &mut ContextBuilder);
+ fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder);
/// Allows upcasting from abstract to concrete context initializer.
/// jrsonnet by itself doesn't use this method, it is allowed for it to panic.
fn as_any(&self) -> &dyn Any;
@@ -174,7 +174,7 @@
where
T: ContextInitializer,
{
- fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder) {
(*self).populate(for_file, builder);
}
@@ -185,7 +185,7 @@
/// Context initializer which adds nothing.
impl ContextInitializer for () {
- fn populate(&self, _for_file: Source, _builder: &mut ContextBuilder) {}
+ fn populate(&self, _for_file: Source, _builder: &mut InitialContextBuilder) {}
fn as_any(&self) -> &dyn Any {
self
}
@@ -195,7 +195,7 @@
where
T: ContextInitializer + 'static,
{
- fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder) {
if let Some(ctx) = self {
ctx.populate(for_file, builder);
}
@@ -210,7 +210,7 @@
($($gen:ident)*) => {
#[allow(non_snake_case)]
impl<$($gen: ContextInitializer + Trace,)*> ContextInitializer for ($($gen,)*) {
- fn populate(&self, for_file: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, for_file: Source, builder: &mut InitialContextBuilder) {
let ($($gen,)*) = self;
$($gen.populate(for_file.clone(), builder);)*
}
@@ -408,7 +408,12 @@
file.evaluating = true;
// Dropping file cache guard here, as evaluation may use this map too
drop(file_cache);
- let res = evaluate(self.create_default_context(file_name), &parsed);
+ let (ctx, externals) = self.create_default_context(file_name.clone()).build();
+ let report = analyze::analyze_root(&parsed, externals);
+ if report.errored {
+ return Err(StaticAnalysisError(report.diagnostics_list).into());
+ }
+ let res = evaluate::evaluate(ctx.build(), &report.lir);
let mut file_cache = self.file_cache();
let mut file = file_cache.entry(path);
@@ -438,7 +443,7 @@
}
/// Creates context with all passed global variables
- pub fn create_default_context(&self, source: Source) -> Context {
+ pub fn create_default_context(&self, source: Source) -> InitialContextBuilder {
self.create_default_context_with(source, &())
}
@@ -447,13 +452,13 @@
&self,
source: Source,
context_initializer: &dyn ContextInitializer,
- ) -> Context {
+ ) -> InitialContextBuilder {
let default_initializer = self.context_initializer();
- let mut builder = ContextBuilder::new();
+ let mut builder = InitialContextBuilder::new();
default_initializer.populate(source.clone(), &mut builder);
context_initializer.populate(source, &mut builder);
- builder.build()
+ builder
}
}
@@ -487,7 +492,7 @@
#[derive(Trace)]
pub struct InitialUnderscore(pub Thunk<Val>);
impl ContextInitializer for InitialUnderscore {
- fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {
builder.bind("_", self.0.clone());
}
@@ -515,10 +520,14 @@
path: source.clone(),
error: Box::new(e),
})?;
- evaluate(
- self.create_default_context_with(source, context_initializer),
- &parsed,
- )
+ let (ctx, externals) = self
+ .create_default_context_with(source.clone(), context_initializer)
+ .build();
+ let report = analyze::analyze_root(&parsed, externals);
+ if report.errored {
+ return Err(StaticAnalysisError(report.diagnostics_list).into());
+ }
+ evaluate::evaluate(ctx.build(), &report.lir)
}
}
crates/jrsonnet-evaluator/src/obj/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj/mod.rs
+++ b/crates/jrsonnet-evaluator/src/obj/mod.rs
@@ -11,8 +11,8 @@
};
use educe::Educe;
-use im_rc::{Vector, vector};
-use jrsonnet_gcmodule::{Acyclic, Cc, Trace, Weak, cc_dyn};
+use im_rc::{vector, Vector};
+use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace, Weak};
use jrsonnet_interner::IStr;
use jrsonnet_ir::Span;
use rustc_hash::{FxHashMap, FxHashSet};
@@ -23,13 +23,13 @@
pub use oop::ObjValueBuilder;
use crate::{
- CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,
arr::{PickObjectKeyValues, PickObjectValues},
bail,
- error::{ErrorKind::*, suggest_object_fields},
+ error::{suggest_object_fields, ErrorKind::*},
+ evaluate::operator::evaluate_add_op,
identity_hash,
- operator::evaluate_add_op,
val::{ArrValue, ThunkValue},
+ CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,
};
#[cfg(not(feature = "exp-preserve-order"))]
@@ -400,6 +400,15 @@
this: ObjValue,
}
impl SupThis {
+ /// Create a `SupThis` for a freshly constructed object (no super).
+ pub fn new(this: ObjValue) -> Self {
+ Self {
+ sup: CoreIdx {
+ idx: this.0.cores.len(),
+ },
+ this,
+ }
+ }
pub fn has_super(&self) -> bool {
self.sup.super_exists()
}
@@ -501,11 +510,11 @@
// }
/// Returns amount of visible object fields
/// If object only contains hidden fields - may return zero.
- pub fn len(&self) -> usize {
+ pub fn len(&self) -> u32 {
self.fields_visibility()
.values()
.filter(|d| d.visible())
- .count()
+ .count() as u32
}
/// For each field, calls callback.
/// If callback returns false - ends iteration prematurely.
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -10,7 +10,7 @@
#[cfg(feature = "explaining-traces")]
use jrsonnet_ir::Span;
-use crate::{Error, error::ErrorKind};
+use crate::{error::ErrorKind, Error};
/// The way paths should be displayed
#[derive(Clone, Trace)]
@@ -122,7 +122,7 @@
|| path.source_path().to_string(),
|r| self.resolver.resolve(r),
);
- let mut offset = error.location.0 as usize;
+ let mut offset = error.location.1 as usize;
let is_eof = if offset >= path.code().len() {
offset = path.code().len().saturating_sub(1);
true
@@ -259,25 +259,64 @@
struct ResetData {
loc: Span,
}
- use hi_doc::{Formatting, SnippetBuilder, Text, source_to_ansi};
+ use hi_doc::{source_to_ansi, Formatting, SnippetBuilder, Text};
write!(out, "{}", error.error())?;
if let ErrorKind::ImportSyntaxError { path, error } = error.error() {
writeln!(out)?;
- let mut offset = error.location;
- // To inclusive range
- if offset.1 > offset.0 {
- offset.1 -= 1;
- }
let mut builder = SnippetBuilder::new(path.code());
builder
.error(Text::fragment("syntax error", Formatting::default()))
- .range(offset.0 as usize..=offset.1 as usize)
+ .range(error.location.range())
.build();
let source = builder.build();
let ansi = source_to_ansi(&source);
write!(out, "{ansi}")?;
}
+ if let ErrorKind::StaticAnalysisError(diagnostics) = error.error() {
+ use crate::analyze::DiagLevel;
+ let mut builder: Option<SnippetBuilder> = None;
+ let mut current_src: Option<&str> = None;
+ let flush =
+ |builder: Option<SnippetBuilder>, out: &mut dyn std::fmt::Write| -> Result<(), std::fmt::Error> {
+ if let Some(b) = builder {
+ let ansi = source_to_ansi(&b.build());
+ write!(out, "\n{}", ansi.trim_end())?;
+ }
+ Ok(())
+ };
+ for diag in diagnostics {
+ if let Some(span) = &diag.span {
+ let src = span.0.code();
+ if current_src != Some(src) {
+ flush(builder.take(), out)?;
+ builder = Some(SnippetBuilder::new(src));
+ current_src = Some(src);
+ }
+ let b = builder.as_mut().unwrap();
+ let ab = match diag.level {
+ DiagLevel::Error => b.error(Text::fragment(
+ diag.message.clone(),
+ Formatting::default(),
+ )),
+ DiagLevel::Warning => b.warning(Text::fragment(
+ diag.message.clone(),
+ Formatting::default(),
+ )),
+ };
+ ab.range(span.range()).build();
+ } else {
+ flush(builder.take(), out)?;
+ current_src = None;
+ let prefix = match diag.level {
+ DiagLevel::Error => "error",
+ DiagLevel::Warning => "warning",
+ };
+ write!(out, "\n{prefix}: {}", diag.message)?;
+ }
+ }
+ flush(builder, out)?;
+ }
let trace = &error.trace();
let snippet_builder: RefCell<Option<SnippetBuilder>> = RefCell::new(None);
let mut last_location: Option<Span> = None;
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -637,7 +637,7 @@
}
<Self as Typed>::TYPE.check(&value)?;
// Any::downcast_ref::<ByteArray>(&a);
- let mut out = Vec::with_capacity(a.len());
+ let mut out = Vec::with_capacity(a.len() as usize);
for e in a.iter() {
let r = e?;
out.push(u8::from_untyped(r)?);
crates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -171,6 +171,7 @@
let mut pool = pool.borrow_mut();
if pool.remove(inner).is_none() {
+ // DOC(string-pooling)
// On some platforms (i.e i686-windows), try_with will not fail after TLS
// destructor is called, but instead re-initialize the TLS with the empty pool.
// Allow non-pooled Drop in this case.
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -3,13 +3,14 @@
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::{
- Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,
- LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type, parenthesized,
+ parenthesized,
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
+ Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,
+ LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type,
};
use self::typed::{derive_from_untyped_inner, derive_into_untyped_inner, derive_typed_inner};
@@ -402,7 +403,7 @@
State, Val,
function::{builtin::Builtin, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation},
Result, Context, typed::{Typed, FromUntyped, IntoUntypedResult},
- parser::Span, params, Thunk,
+ Span, params, Thunk,
};
params!(
#(#params_desc)*
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -1,11 +1,12 @@
#![allow(non_snake_case)]
use jrsonnet_evaluator::{
- Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, bail,
- function::{FuncVal, NativeFn, builtin},
+ bail, error,
+ function::{builtin, NativeFn},
runtime_error,
- typed::{BoundedI32, BoundedUsize, Either2, FromUntyped},
- val::{ArrValue, IndexableVal, equals},
+ typed::{BoundedUsize, Either2, FromUntyped},
+ val::{equals, ArrValue, IndexableVal},
+ Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val,
};
pub fn eval_on_empty(on_empty: Option<Thunk<Val>>) -> Result<Val> {
@@ -17,32 +18,28 @@
}
#[builtin]
-pub fn builtin_make_array(
- // Can't use usize because range_exclusive is over i32
- sz: BoundedI32<0, { i32::MAX }>,
- func: FuncVal,
-) -> Result<ArrValue> {
- if *sz == 0 {
+pub fn builtin_make_array(sz: u32, func: NativeFn!((u32,) -> Val)) -> Result<ArrValue> {
+ if sz == 0 {
return Ok(ArrValue::empty());
}
- func.evaluate_trivial().map_or_else(
- // TODO: Different mapped array impl avoiding allocating unnecessary vals
- || Ok(ArrValue::range_exclusive(0, *sz).map(FromUntyped::from_untyped(Val::Func(func))?)),
- |trivial| {
- #[expect(clippy::cast_sign_loss, reason = "sz is bounded to be larger than 0")]
- let mut out = Vec::with_capacity(*sz as usize);
- for _ in 0..*sz {
- out.push(trivial.clone());
+ // Try eager evaluation: call func(i) immediately for each element.
+ 'eager: {
+ let mut out = Vec::with_capacity(sz as usize);
+ for i in 0..sz {
+ match func.call(i) {
+ Ok(v) => out.push(v),
+ Err(_) => break 'eager,
}
- Ok(ArrValue::new(out))
- },
- )
+ }
+ return Ok(ArrValue::new(out));
+ }
+ Ok(ArrValue::make(sz, func))
}
#[builtin]
-pub fn builtin_repeat(what: Either![IStr, ArrValue], count: usize) -> Result<Val> {
+pub fn builtin_repeat(what: Either![IStr, ArrValue], count: u32) -> Result<Val> {
Ok(match what {
- Either2::A(s) => Val::string(s.repeat(count)),
+ Either2::A(s) => Val::string(s.repeat(count as usize)),
Either2::B(arr) => Val::Arr(
ArrValue::repeated(arr, count)
.ok_or_else(|| runtime_error!("repeated length overflow"))?,
@@ -210,14 +207,14 @@
let item = item?.clone();
if let Val::Arr(items) = item {
if !first {
- out.reserve(joiner_items.len());
+ out.reserve(joiner_items.len() as usize);
// TODO: extend
for item in joiner_items.iter() {
out.push(item?);
}
}
first = false;
- out.reserve(items.len());
+ out.reserve(items.len() as usize);
for item in items.iter() {
out.push(item?);
}
@@ -256,7 +253,8 @@
pub fn builtin_lines(arr: ArrValue) -> Result<IndexableVal> {
builtin_join(
IndexableVal::Str("\n".into()),
- ArrValue::extended(arr, ArrValue::new(vec![Val::string("")])),
+ ArrValue::extended(arr, ArrValue::new(vec![Val::string("")]))
+ .ok_or_else(|| error!("array is too large"))?,
)
}
@@ -380,7 +378,7 @@
let newArrLeft = arr.clone().slice(None, Some(at), None);
let newArrRight = arr.slice(Some(at + 1), None, None);
- Ok(ArrValue::extended(newArrLeft, newArrRight))
+ Ok(ArrValue::extended(newArrLeft, newArrRight).ok_or_else(|| error!("array is too large"))?)
}
#[builtin]
@@ -399,20 +397,22 @@
}
#[builtin]
-pub fn builtin_flatten_arrays(arrs: Vec<ArrValue>) -> ArrValue {
- pub fn flatten_inner(values: &[ArrValue]) -> ArrValue {
+pub fn builtin_flatten_arrays(arrs: Vec<ArrValue>) -> Result<ArrValue> {
+ pub fn flatten_inner(values: &[ArrValue]) -> Result<ArrValue> {
if values.len() == 1 {
- return values[0].clone();
+ return Ok(values[0].clone());
} else if values.len() == 2 {
- return ArrValue::extended(values[0].clone(), values[1].clone());
+ return ArrValue::extended(values[0].clone(), values[1].clone())
+ .ok_or_else(|| error!("array is too large"));
}
let (a, b) = values.split_at(values.len() / 2);
- ArrValue::extended(flatten_inner(a), flatten_inner(b))
+ ArrValue::extended(flatten_inner(a)?, flatten_inner(b)?)
+ .ok_or_else(|| error!("array is too large"))
}
if arrs.is_empty() {
- return ArrValue::empty();
+ return Ok(ArrValue::empty());
} else if arrs.len() == 1 {
- return arrs.into_iter().next().expect("single");
+ return Ok(arrs.into_iter().next().expect("single"));
}
flatten_inner(&arrs)
}
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -12,15 +12,9 @@
pub use encoding::*;
pub use hash::*;
use jrsonnet_evaluator::{
- ContextBuilder, IStr, NumValue, ObjValue, ObjValueBuilder, Thunk, Val,
- error::Result,
- function::{CallLocation, FuncVal, builtin_id},
- tla::TlaArg,
- trace::PathResolver,
- typed::SerializeTypedObj as _,
+ IStr, InitialContextBuilder, NumValue, ObjValue, ObjValueBuilder, Source, Thunk, Val, error::Result, function::{CallLocation, FuncVal, builtin_id}, tla::TlaArg, trace::PathResolver, typed::SerializeTypedObj as _
};
use jrsonnet_gcmodule::{Acyclic, Cc, Trace};
-use jrsonnet_ir::Source;
use jrsonnet_macros::{IntoUntyped, Typed};
pub use manifest::*;
pub use math::*;
@@ -544,7 +538,7 @@
}
}
impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {
- fn populate(&self, source: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, source: Source, builder: &mut InitialContextBuilder) {
let mut std = ObjValueBuilder::new();
std.with_super(self.stdlib_obj.clone());
std.field("thisFile").hide().value({
crates/jrsonnet-stdlib/src/misc.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/misc.rs
+++ b/crates/jrsonnet-stdlib/src/misc.rs
@@ -1,22 +1,23 @@
use std::{cell::RefCell, collections::BTreeSet};
use jrsonnet_evaluator::{
- Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val, bail,
+ bail,
error::{ErrorKind::*, Result},
- function::{CallLocation, FuncVal, builtin},
+ function::{builtin, CallLocation, FuncVal},
manifest::JsonFormat,
typed::{Either2, Either4},
- val::{ArrValue, equals},
+ val::{equals, ArrValue},
+ Either, IStr, ObjValue, ObjValueBuilder, ResultExt, Thunk, Val,
};
use jrsonnet_gcmodule::Cc;
use crate::Settings;
#[builtin]
-pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> usize {
+pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> u32 {
use Either4::*;
match x {
- A(x) => x.chars().count(),
+ A(x) => x.chars().count() as u32,
B(x) => x.len(),
C(x) => x.len(),
D(f) => f.params_len(),
@@ -102,7 +103,7 @@
} else if b.len() == a.len() {
return equals(&Val::Arr(a), &Val::Arr(b));
}
- for (a, b) in a.iter().take(b.len()).zip(b.iter()) {
+ for (a, b) in a.iter().take(b.len() as usize).zip(b.iter()) {
let a = a?;
let b = b?;
if !equals(&a, &b)? {
@@ -127,7 +128,7 @@
return equals(&Val::Arr(a), &Val::Arr(b));
}
let a_len = a.len();
- for (a, b) in a.iter().skip(a_len - b.len()).zip(b.iter()) {
+ for (a, b) in a.iter().skip((a_len - b.len()) as usize).zip(b.iter()) {
let a = a?;
let b = b?;
if !equals(&a, &b)? {
tests/tests/builtin.rsdiffbeforeafterboth--- a/tests/tests/builtin.rs
+++ b/tests/tests/builtin.rs
@@ -1,11 +1,7 @@
mod common;
use jrsonnet_evaluator::{
- ContextBuilder, ContextInitializer, FileImportResolver, Result, State, Thunk, Val,
- function::{CallLocation, FuncVal, builtin, builtin::Builtin},
- parser::Source,
- trace::PathResolver,
- typed::FromUntyped,
+ ContextInitializer, FileImportResolver, InitialContextBuilder, Result, Source, State, Thunk, Val, function::{CallLocation, FuncVal, builtin, builtin::{Builtin}}, trace::PathResolver, typed::FromUntyped
};
use jrsonnet_gcmodule::Trace;
use jrsonnet_stdlib::ContextInitializer as StdContextInitializer;
@@ -31,7 +27,7 @@
#[derive(Trace)]
struct NativeAddContextInitializer;
impl ContextInitializer for NativeAddContextInitializer {
- fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {
builder.bind("nativeAdd", Thunk::evaluated(Val::function(native_add {})));
}
@@ -76,7 +72,7 @@
#[derive(Trace)]
struct CurryAddContextInitializer;
impl ContextInitializer for CurryAddContextInitializer {
- fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {
builder.bind("curryAdd", Thunk::evaluated(Val::function(curry_add {})));
}
tests/tests/common.rsdiffbeforeafterboth--- a/tests/tests/common.rs
+++ b/tests/tests/common.rs
@@ -1,8 +1,5 @@
use jrsonnet_evaluator::{
- ContextBuilder, ContextInitializer as ContextInitializerT, ObjValueBuilder, Result, Thunk, Val,
- bail,
- function::{FuncVal, builtin},
- parser::Source,
+ ContextBuilder, ContextInitializer as ContextInitializerT, InitialContextBuilder, ObjValueBuilder, Result, Thunk, Val, bail, function::{FuncVal, builtin}, Source
};
use jrsonnet_gcmodule::Trace;
@@ -68,7 +65,7 @@
#[allow(dead_code)]
pub struct ContextInitializer;
impl ContextInitializerT for ContextInitializer {
- fn populate(&self, _for_file: Source, builder: &mut ContextBuilder) {
+ fn populate(&self, _for_file: Source, builder: &mut InitialContextBuilder) {
let mut bobj = ObjValueBuilder::new();
bobj.method("assertThrow", assert_throw {});
bobj.method("paramNames", param_names {});
tests/tests/cpp_test_suite.rsdiffbeforeafterboth--- a/tests/tests/cpp_test_suite.rs
+++ b/tests/tests/cpp_test_suite.rs
@@ -9,9 +9,11 @@
gc::WithCapacityExt as _,
manifest::JsonFormat,
rustc_hash::FxHashMap,
+ stack::limit_stack_depth,
tla::TlaArg,
trace::{CompactFormat, PathResolver, TraceFormat},
};
+use jrsonnet_gcmodule::ObjectSpace;
use jrsonnet_stdlib::ContextInitializer;
mod common;
use common::ContextInitializer as TestContextInitializer;
@@ -179,6 +181,12 @@
continue;
}
+ let _stack = if entry.path().file_stem().is_some_and(|e| e == "recursive_function" || e == "tailstrict"|| e == "tailstrict5") {
+ Some(limit_stack_depth(100_000))
+ } else {
+ None
+ };
+
if entry
.path()
.file_name()
@@ -188,7 +196,7 @@
continue;
}
- println!("test: {}", entry.path().display());
+ eprintln!("test: {}", entry.path().display());
let result = run(&entry.path(), &root);
@@ -212,25 +220,10 @@
if let Some(golden_path) = read_file(&golden_override)? {
golden = Some(golden_path);
}
-
- // ir-parser has its own override layer
- #[cfg(feature = "ir-parser")]
- let ir_parser_override_path = {
- let p = root_tests
- .join(format!("{root_dir}_golden_override_ir_parser"))
- .join(golden_path.file_name().expect("file has basename"));
- if let Some(golden_path) = read_file(&p)? {
- golden = Some(golden_path);
- }
- p
- };
// Otherwise assume test should just not fail and return true.
let golden = golden.unwrap_or_else(|| "true".to_owned());
- #[cfg(feature = "ir-parser")]
- let update_golden_path = &ir_parser_override_path;
- #[cfg(not(feature = "ir-parser"))]
let update_golden_path = &golden_override;
match (serde_json::from_str::<serde_json::Value>(&result), serde_json::from_str::<serde_json::Value>(&golden)) {
@@ -270,8 +263,11 @@
}
}
}
+ println!("done!");
}
}
+ jrsonnet_gcmodule::with_thread_object_space(ObjectSpace::leak);
+
Ok(())
}
tests/tests/snapshots/golden__golden@issue172.jsonnet.snapdiffbeforeafterboth--- a/tests/tests/snapshots/golden__golden@issue172.jsonnet.snap
+++ b/tests/tests/snapshots/golden__golden@issue172.jsonnet.snap
@@ -3,7 +3,4 @@
expression: result
input_file: tests/golden/issue172.jsonnet
---
-local is not defined: b
- issue172.jsonnet:1:45-47: local <b> access
- issue172.jsonnet:1:4-10: field <value> access
- elem <0> evaluation
+static analysis errors: undefined local: b
tests/tests/snapshots/golden__golden@issue23.jsonnet.snapdiffbeforeafterboth--- a/tests/tests/snapshots/golden__golden@issue23.jsonnet.snap
+++ b/tests/tests/snapshots/golden__golden@issue23.jsonnet.snap
@@ -4,4 +4,4 @@
input_file: tests/golden/issue23.jsonnet
---
infinite recursion detected
- issue23.jsonnet:1:1-8: import "issue23.jsonnet"
+ issue23.jsonnet:1:1-8: import
tests/tests/snapshots/golden__golden@missing_binding.jsonnet.snapdiffbeforeafterboth--- a/tests/tests/snapshots/golden__golden@missing_binding.jsonnet.snap
+++ b/tests/tests/snapshots/golden__golden@missing_binding.jsonnet.snap
@@ -3,6 +3,5 @@
expression: result
input_file: tests/golden/missing_binding.jsonnet
---
-local is not defined: sta
+static analysis errors: undefined local: sta
There is a local with similar name present: std
- missing_binding.jsonnet:1:1-5: local <sta> access