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.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -2,39 +2,42 @@
use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::IStr;
-use jrsonnet_ir::{
- ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, ExprParams, FieldMember,
- FieldName, ForSpecData, IfSpecData, ImportKind, LiteralType, ObjBody, ObjMembers, Spanned,
- function::ParamName,
-};
+use jrsonnet_ir::ImportKind;
use jrsonnet_types::ValType;
-use self::destructure::destruct;
+use self::{
+ compspec::{evaluate_arr_comp, evaluate_obj_comp},
+ destructure::{evaluate_locals, evaluate_locals_unbound},
+ operator::evaluate_binary_op_special,
+};
use crate::{
- Context, ContextBuilder, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result,
- ResultExt, SupThis, Unbound, Val,
- arr::ArrValue,
+ analyze::{
+ LArgsDesc, LAssertStmt, LExpr, LFieldMember, LFieldName, LFunction, LIndexPart, LObjBody,
+ LObjMembers,
+ },
bail,
- destructure::evaluate_dest,
- error::{ErrorKind::*, suggest_object_fields},
- evaluate::operator::{evaluate_binary_op_special, evaluate_unary_op},
- function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal},
- in_frame,
- typed::{FromUntyped, IntoUntyped as _, Typed},
- val::{CachedUnbound, IndexableVal, StrValue, Thunk},
- with_state,
+ error::{suggest_object_fields, ErrorKind::*},
+ evaluate::operator::evaluate_unary_op,
+ function::{prepared::PreparedFuncVal, CallLocation, FuncDesc, FuncVal},
+ in_frame, runtime_error,
+ typed::FromUntyped as _,
+ val::{CachedUnbound, Thunk},
+ with_state, Context, Error, ObjValue, ObjValueBuilder, ObjectAssertion, Result, ResultExt as _,
+ SupThis, Unbound, Val,
};
+
+pub mod compspec;
pub mod destructure;
pub mod operator;
// This is the amount of bytes that need to be left on the stack before increasing the size.
// It must be at least as large as the stack required by any code that does not call
// `ensure_sufficient_stack`.
-const RED_ZONE: usize = 100 * 1024; // 100k
+const RED_ZONE: usize = 100 * 1024;
// Only the first stack that is pushed, grows exponentially (2^n * STACK_PER_RECURSION) from then
// on. This flag has performance relevant characteristics. Don't set it too high.
-const STACK_PER_RECURSION: usize = 1024 * 1024; // 1MB
+const STACK_PER_RECURSION: usize = 1024 * 1024;
/// Grows the stack on demand to prevent stack overflow. Call this in strategic locations
/// to "break up" recursive calls. E.g. almost any call to `visit_expr` or equivalent can benefit
@@ -46,54 +49,36 @@
stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)
}
-pub fn evaluate_trivial(expr: &Expr) -> Option<Val> {
- fn is_trivial(expr: &Expr) -> bool {
- match expr {
- Expr::Str(_)
- | Expr::Num(_)
- | Expr::Literal(LiteralType::False | LiteralType::True | LiteralType::Null) => true,
- Expr::Arr(a) => a.iter().all(is_trivial),
- _ => false,
- }
- }
+pub fn evaluate_trivial(expr: &LExpr) -> Option<Val> {
+ // TODO: Eager trivial array
Some(match expr {
- Expr::Str(s) => Val::string(s.clone()),
- Expr::Num(n) => Val::Num(*n),
- Expr::Literal(LiteralType::False) => Val::Bool(false),
- Expr::Literal(LiteralType::True) => Val::Bool(true),
- Expr::Literal(LiteralType::Null) => Val::Null,
- Expr::Arr(n) => {
- if n.iter().any(|e| !is_trivial(e)) {
- return None;
- }
- Val::Arr(
- n.iter()
- .map(evaluate_trivial)
- .map(|e| e.expect("checked trivial"))
- .collect(),
- )
- }
+ LExpr::Str(s) => Val::string(s.clone()),
+ LExpr::Num(n) => Val::Num(*n),
+ LExpr::Bool(false) => Val::Bool(false),
+ LExpr::Bool(true) => Val::Bool(true),
+ LExpr::Null => Val::Null,
_ => return None,
})
}
-pub fn evaluate_method(ctx: Context, name: IStr, params: ExprParams, body: Rc<Expr>) -> Val {
+/// Evaluate a method definition.
+pub fn evaluate_method(ctx: Context, name: IStr, func: &Rc<LFunction>) -> Val {
Val::Func(FuncVal::Normal(Cc::new(FuncDesc {
name,
ctx,
- params,
- body,
+ func: func.clone(),
})))
}
-pub fn evaluate_field_name(ctx: Context, field_name: &Spanned<FieldName>) -> Result<Option<IStr>> {
- Ok(match &field_name.value {
- FieldName::Fixed(n) => Some(n.clone()),
- FieldName::Dyn(expr) => in_frame(
- CallLocation::new(&field_name.span),
+pub fn evaluate_field_name(ctx: Context, field_name: &LFieldName) -> Result<Option<IStr>> {
+ Ok(match field_name {
+ LFieldName::Fixed(n) => Some(n.clone()),
+ LFieldName::Dyn(expr) => in_frame(
+ // TODO: Spanned<LFieldName>
+ CallLocation::native(),
|| "evaluating field name".to_string(),
|| {
- let v = evaluate(ctx, expr)?;
+ let v = evaluate(ctx.clone(), expr)?;
Ok(if matches!(v, Val::Null) {
None
} else {
@@ -104,371 +89,452 @@
})
}
-pub fn evaluate_comp(
- ctx: Context,
- specs: &[CompSpec],
- mut guaranteed_reserve: usize,
- callback: &mut impl FnMut(Context, usize) -> Result<()>,
-) -> Result<()> {
- match specs.first() {
- None => callback(ctx, guaranteed_reserve)?,
- Some(CompSpec::IfSpec(IfSpecData { cond, span: _ })) => {
- if bool::from_untyped(evaluate(ctx.clone(), cond)?)? {
- evaluate_comp(ctx, &specs[1..], 0, callback)?;
- }
- }
- Some(CompSpec::ForSpec(ForSpecData {
- destruct: into,
- over,
- })) => {
- match evaluate(ctx.clone(), over)? {
- Val::Arr(list) => {
- guaranteed_reserve = guaranteed_reserve.max(1) * list.len();
- for (i, item) in list.iter_lazy().enumerate() {
- let fctx = Pending::new();
- let mut ctx = ContextBuilder::extend_fast(ctx.clone());
- destruct(into, item, fctx.clone(), &mut ctx)?;
- let ctx = ctx.build().into_future(fctx);
+pub fn evaluate_thunk(ctx: Context, expr: Rc<LExpr>, tailstrict: bool) -> Result<Thunk<Val>> {
+ Ok(if tailstrict {
+ Thunk::evaluated(evaluate(ctx, &expr)?)
+ } else {
+ Thunk!(move || { evaluate(ctx, &expr) })
+ })
+}
- let specs = &specs[1..];
- evaluate_comp(
- ctx,
- specs,
- if i == 0 || !specs.is_empty() {
- guaranteed_reserve
- } else {
- 0
- },
- callback,
- )?;
- }
- }
- Val::Obj(obj) if cfg!(feature = "exp-object-iteration") => {
- let fields = obj.fields(
- // TODO: Should there be ability to preserve iteration order?
- #[cfg(feature = "exp-preserve-order")]
- false,
- );
- guaranteed_reserve = guaranteed_reserve.max(1) * fields.len();
- for (i, field) in fields.into_iter().enumerate() {
- let fctx = Pending::new();
- let mut ctx = ContextBuilder::extend_fast(ctx.clone());
- let obj = obj.clone();
- let value = Thunk::evaluated(Val::arr(vec![
- Thunk::evaluated(Val::string(field.clone())),
- obj.get_lazy(field).expect(
- "field exists, as field name was obtained from object.fields()",
- ),
- ]));
- destruct(into, value, fctx.clone(), &mut ctx)?;
- let ctx = ctx.build().into_future(fctx);
+mod names {
+ use crate::names;
- evaluate_comp(
- ctx,
- &specs[1..],
- if i == 0 || !specs.is_empty() {
- guaranteed_reserve
- } else {
- 0
- },
- callback,
- )?;
- }
- }
- _ => bail!(InComprehensionCanOnlyIterateOverArray),
- }
- }
+ names! {
+ anonymous: "anonymous",
}
- Ok(())
}
-fn evaluate_arr_comp(ctx: Context, expr: &Rc<Expr>, comp_specs: &[CompSpec]) -> Result<ArrValue> {
- let ctx = ctx.branch_point();
- 'eager: {
- let mut out = Vec::new();
+pub fn evaluate_named(name: &IStr, ctx: Context, expr: &LExpr) -> Result<Val> {
+ if let LExpr::Function(f) = &expr {
+ return Ok(evaluate_method(
+ ctx,
+ f.name.clone().unwrap_or_else(|| name.clone()),
+ f,
+ ));
+ }
+ evaluate(ctx, expr)
+}
- if evaluate_comp(ctx.clone(), comp_specs, 0, &mut |ctx, reserve| {
- if reserve != 0 {
- out.reserve(reserve);
+pub fn evaluate(ctx: Context, expr: &LExpr) -> Result<Val> {
+ Ok(match expr {
+ LExpr::Null => Val::Null,
+ LExpr::Bool(b) => Val::Bool(*b),
+ LExpr::Str(s) => Val::string(s.clone()),
+ LExpr::Num(n) => Val::Num(*n),
+ LExpr::Local(id) => {
+ let Some(thunk) = ctx.binding(*id) else {
+ bail!("should not happen: unbound local {id:?}");
+ };
+ thunk.evaluate()?
+ }
+ LExpr::BadLocal(name) => panic!("unresolvable reference: {name}"),
+ LExpr::Arr(items) => Val::Arr(crate::arr::ArrValue::expr(ctx, items.clone())),
+ LExpr::UnaryOp(op, value) => {
+ let value = evaluate(ctx, value)?;
+ evaluate_unary_op(*op, &value)?
+ }
+ LExpr::BinaryOp { lhs, op, rhs } => evaluate_binary_op_special(ctx, lhs, *op, rhs)?,
+ LExpr::LocalExpr { binds, body } => {
+ let ctx = evaluate_locals(ctx, binds);
+ evaluate(ctx, body)?
+ }
+ LExpr::IfElse {
+ cond,
+ cond_then,
+ cond_else,
+ } => {
+ let cond_val = evaluate(ctx.clone(), cond)?;
+ let Val::Bool(b) = cond_val else {
+ bail!(TypeMismatch(
+ "if condition",
+ vec![ValType::Bool],
+ cond_val.value_type()
+ ))
+ };
+ if b {
+ evaluate(ctx, cond_then)?
+ } else if let Some(e) = cond_else {
+ evaluate(ctx, e)?
+ } else {
+ Val::Null
}
- out.push(evaluate(ctx, expr)?);
- Ok(())
- })
- .is_err()
- {
- break 'eager;
}
+ LExpr::Error(s, e) => in_frame(
+ CallLocation::new(s),
+ || "error statement".to_owned(),
+ || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
+ )?,
+ LExpr::AssertExpr { assert, rest } => {
+ evaluate_assert(ctx.clone(), assert)?;
+ evaluate(ctx, rest)?
+ }
- return Ok(ArrValue::new(out));
- };
- let mut out = Vec::new();
- evaluate_comp(ctx, comp_specs, 0, &mut |ctx, reserve| {
- if reserve != 0 {
- out.reserve(reserve);
+ LExpr::Function(func) => evaluate_method(
+ ctx,
+ func.name.clone().unwrap_or_else(names::anonymous),
+ func,
+ ),
+ LExpr::Apply {
+ applicable,
+ args,
+ tailstrict,
+ } => evaluate_apply(
+ ctx,
+ applicable,
+ args,
+ CallLocation::new(&args.span),
+ *tailstrict,
+ )?,
+ LExpr::Index { indexable, parts } => evaluate_index(ctx, indexable, parts)?,
+ LExpr::Obj(body) => evaluate_obj_body(None, ctx, body)?,
+ LExpr::ObjExtend(lhs, body) => {
+ let lhs_val = evaluate(ctx.clone(), lhs)?;
+ let Val::Obj(lhs_obj) = lhs_val else {
+ bail!(TypeMismatch(
+ "object extend lhs",
+ vec![ValType::Obj],
+ lhs_val.value_type(),
+ ))
+ };
+ evaluate_obj_body(Some(lhs_obj), ctx, body)?
+ }
+ LExpr::ArrComp(comp) => evaluate_arr_comp(ctx, comp)?,
+ LExpr::Slice(slice) => {
+ use crate::typed::BoundedUsize;
+ let val = evaluate(ctx.clone(), &slice.value)?;
+ let indexable = val.into_indexable()?;
+ let start = slice
+ .start
+ .as_ref()
+ .map(|e| evaluate(ctx.clone(), e))
+ .transpose()?
+ .map(|v| -> Result<i32> {
+ v.as_num()
+ .ok_or_else(|| {
+ TypeMismatch("slice start", vec![ValType::Num], v.value_type()).into()
+ })
+ .map(|n| n as i32)
+ })
+ .transpose()?;
+ let end = slice
+ .end
+ .as_ref()
+ .map(|e| evaluate(ctx.clone(), e))
+ .transpose()?
+ .map(|v| -> Result<i32> {
+ v.as_num()
+ .ok_or_else(|| {
+ TypeMismatch("slice end", vec![ValType::Num], v.value_type()).into()
+ })
+ .map(|n| n as i32)
+ })
+ .transpose()?;
+ let step = slice
+ .step
+ .as_ref()
+ .map(|e| evaluate(ctx, e))
+ .transpose()?
+ .map(|v| -> Result<BoundedUsize<1, { i32::MAX as usize }>> {
+ let n = v.as_num().ok_or_else(|| -> crate::Error {
+ TypeMismatch("slice step", vec![ValType::Num], v.value_type()).into()
+ })?;
+ BoundedUsize::new(n as usize)
+ .ok_or_else(|| runtime_error!("slice step must be >= 1"))
+ })
+ .transpose()?;
+ Val::from(indexable.slice(start, end, step)?)
}
- let expr = expr.clone();
- out.push(Thunk!(move || evaluate(ctx, &expr)));
- Ok(())
- })?;
- Ok(ArrValue::new(out))
+ LExpr::Super => Val::Obj(ctx.try_sup_this()?.standalone_super()?),
+ LExpr::Import {
+ kind,
+ kind_span,
+ path,
+ } => with_state(|state| {
+ let resolved = state.resolve_from(kind_span.0.source_path(), &path.clone())?;
+ Ok::<_, Error>(match kind.value {
+ ImportKind::Normal => in_frame(
+ CallLocation::new(&kind.span),
+ || "import".to_string(),
+ || state.import_resolved(resolved),
+ )?,
+ ImportKind::Str => Val::string(state.import_resolved_str(resolved)?),
+ ImportKind::Bin => Val::arr(state.import_resolved_bin(resolved)?),
+ })
+ })?,
+ })
}
-trait CloneableUnbound<T>: Unbound<Bound = T> + Clone {}
-impl<V, T> CloneableUnbound<T> for V where V: Unbound<Bound = T> + Clone {}
+fn evaluate_apply(
+ ctx: Context,
+ applicable: &LExpr,
+ args: &LArgsDesc,
+ loc: CallLocation<'_>,
+ tailstrict: bool,
+) -> Result<Val> {
+ let func_val = evaluate(ctx.clone(), applicable)?;
+ let Val::Func(func) = func_val else {
+ bail!(OnlyFunctionsCanBeCalledGot(func_val.value_type()))
+ };
-fn evaluate_object_locals(
- fctx: Context,
- locals: Rc<Vec<BindSpec>>,
-) -> impl CloneableUnbound<Context> {
- #[derive(Trace, Clone)]
- struct UnboundLocals {
- fctx: Context,
- locals: Rc<Vec<BindSpec>>,
- }
- impl Unbound for UnboundLocals {
- type Bound = Context;
+ let name = func.name();
+ let unnamed = args
+ .unnamed
+ .iter()
+ .cloned()
+ .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))
+ .collect::<Result<Vec<_>>>()?;
- fn bind(&self, sup_this: SupThis) -> Result<Context> {
- let fctx = Context::new_future();
- let ctx = self.fctx.clone();
- let mut ctx = ContextBuilder::extend(ctx);
- for b in self.locals.iter() {
- evaluate_dest(b, fctx.clone(), &mut ctx)?;
- }
-
- let ctx = ctx.build_sup_this(sup_this).into_future(fctx);
+ let named = args
+ .values
+ .iter()
+ .cloned()
+ .map(|e| evaluate_thunk(ctx.clone(), e, tailstrict))
+ .collect::<Result<Vec<_>>>()?;
+ let prepare = PreparedFuncVal::new(func, unnamed.len(), &args.names)
+ .with_description_src(loc, || format!("function <{name}> preparation"))?;
+ in_frame(
+ loc,
+ || format!("function <{name}> call"),
+ || prepare.call(CallLocation::native(), &unnamed, &named),
+ )
+}
- Ok(ctx)
+fn evaluate_index(ctx: Context, indexable: &LExpr, parts: &[LIndexPart]) -> Result<Val> {
+ let mut value = if let LExpr::Super = indexable {
+ let sup_this = ctx.try_sup_this()?;
+ // First part must be evaluated to get the super field name
+ if parts.is_empty() {
+ bail!(RuntimeError("super requires an index".into()))
}
- }
+ let key_val = evaluate(ctx.clone(), &parts[0].value)?;
+ let Val::Str(key) = &key_val else {
+ bail!(ValueIndexMustBeTypeGot(
+ ValType::Obj,
+ ValType::Str,
+ key_val.value_type(),
+ ))
+ };
+ let field = key.clone().into_flat();
+ if let Some(v) = sup_this.get_super(field.clone())? {
+ // Continue with remaining parts
+ let mut value = v;
+ for part in &parts[1..] {
+ value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;
+ }
+ return Ok(value);
+ }
+ let suggestions = suggest_object_fields(sup_this.this(), field.clone());
+ bail!(NoSuchField(field, suggestions))
+ } else {
+ evaluate(ctx.clone(), indexable)?
+ };
- UnboundLocals { fctx, locals }
+ for part in parts {
+ value = index_val(ctx.clone(), CallLocation::new(&part.span), value, part)?;
+ }
+ Ok(value)
}
-pub fn evaluate_field_member<B: Unbound<Bound = Context> + Clone>(
- builder: &mut ObjValueBuilder,
- ctx: Context,
- uctx: B,
- field: &FieldMember,
-) -> Result<()> {
- let name = evaluate_field_name(ctx, &field.name)?;
- let Some(name) = name else {
- return Ok(());
- };
-
- match field {
- FieldMember {
- plus,
- params: None,
- visibility,
- value,
- ..
- } => {
- #[derive(Trace)]
- struct UnboundValue<B: Trace> {
- uctx: B,
- value: Rc<Expr>,
- name: IStr,
+fn index_val(ctx: Context, loc: CallLocation<'_>, value: Val, part: &LIndexPart) -> Result<Val> {
+ let key_val = evaluate(ctx, &part.value)?;
+ Ok(match (&value, &key_val) {
+ (Val::Obj(obj), Val::Str(key)) => {
+ let field = key.clone().into_flat();
+ if let Some(v) = obj
+ .get(field.clone())
+ .with_description_src(loc, || format!("field <{field}> access"))?
+ {
+ v
+ } else {
+ bail!(NoSuchField(
+ field.clone(),
+ suggest_object_fields(obj, field)
+ ))
}
- impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {
- type Bound = Val;
- fn bind(&self, sup_this: SupThis) -> Result<Val> {
- evaluate_named(self.uctx.bind(sup_this)?, &self.value, self.name.clone())
- }
+ }
+ (Val::Arr(arr), Val::Num(idx)) => {
+ let n = idx.get();
+ if n.fract() > f64::EPSILON {
+ bail!(FractionalIndex)
}
-
- builder
- .field(name.clone())
- .with_add(*plus)
- .with_visibility(*visibility)
- .with_location(field.name.span.clone())
- .bindable(UnboundValue {
- uctx,
- value: value.clone(),
- name,
- })?;
+ if n < 0.0 {
+ bail!(ArrayBoundsError(
+ n as isize, // truncation is fine for error display
+ arr.len()
+ ));
+ }
+ #[expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "n is checked positive"
+ )]
+ let i = n as u32;
+ arr.get(i)
+ .with_description_src(loc, || format!("element <{i}> access"))?
+ .ok_or_else(|| ArrayBoundsError(i as isize, arr.len()))?
}
- FieldMember {
- params: Some(params),
- visibility,
- value,
- ..
- } => {
- #[derive(Trace)]
- struct UnboundMethod<B: Trace> {
- uctx: B,
- value: Rc<Expr>,
- params: ExprParams,
- name: IStr,
+ (Val::Str(s), Val::Num(idx)) => {
+ let n = idx.get();
+ if n.fract() > f64::EPSILON {
+ bail!(FractionalIndex)
}
- impl<B: Unbound<Bound = Context>> Unbound for UnboundMethod<B> {
- type Bound = Val;
- fn bind(&self, sup_this: SupThis) -> Result<Val> {
- Ok(evaluate_method(
- self.uctx.bind(sup_this)?,
- self.name.clone(),
- self.params.clone(),
- self.value.clone(),
- ))
- }
+ let flat = s.clone().into_flat();
+ if n < 0.0 {
+ bail!(ArrayBoundsError(
+ n as isize, // truncation is fine for error display
+ flat.chars().count() as u32
+ ));
}
-
- builder
- .field(name.clone())
- .with_visibility(*visibility)
- // .with_location(value.span())
- .bindable(UnboundMethod {
- uctx,
- value: value.clone(),
- params: params.clone(),
- name,
- })?;
+ #[expect(
+ clippy::cast_possible_truncation,
+ clippy::cast_sign_loss,
+ reason = "n is checked positive, overflow will truncate as expected"
+ )]
+ let i = n as usize;
+ let Some(char) = flat.chars().nth(i) else {
+ bail!(StringBoundsError(i, flat.chars().count()))
+ };
+ Val::string(char)
}
- }
- Ok(())
+ _ => bail!(ValueIndexMustBeTypeGot(
+ value.value_type(),
+ ValType::Str,
+ key_val.value_type()
+ )),
+ })
}
-#[derive(Trace, Clone)]
-struct DirectUnbound(Context);
-impl Unbound for DirectUnbound {
- type Bound = Context;
- fn bind(&self, sup_this: SupThis) -> Result<Context> {
- Ok(ContextBuilder::extend(self.0.clone()).build_sup_this(sup_this))
+fn evaluate_obj_body(super_obj: Option<ObjValue>, ctx: Context, body: &LObjBody) -> Result<Val> {
+ match body {
+ LObjBody::MemberList(members) => evaluate_obj_members(super_obj, ctx, members),
+ LObjBody::ObjComp(comp) => evaluate_obj_comp(super_obj, ctx, comp),
}
}
-#[allow(clippy::too_many_lines)]
-pub fn evaluate_member_list_object(
- super_obj: Option<ObjValue>,
+pub fn evaluate_field_member_unbound<B: Unbound<Bound = Context> + Clone>(
+ builder: &mut ObjValueBuilder,
ctx: Context,
- members: &ObjMembers,
-) -> Result<ObjValue> {
+ uctx: B,
+ field: &LFieldMember,
+) -> Result<()> {
#[derive(Trace)]
- struct ObjectAssert<B: Trace> {
+ struct UnboundValue<B: Trace> {
uctx: B,
- asserts: Rc<Vec<AssertStmt>>,
+ value: Rc<LExpr>,
+ name: IStr,
}
- impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {
- fn run(&self, sup_this: SupThis) -> Result<()> {
- let ctx = self.uctx.bind(sup_this)?;
- for assert in &*self.asserts {
- evaluate_assert(ctx.clone(), assert)?;
- }
- Ok(())
+ impl<B: Unbound<Bound = Context>> Unbound for UnboundValue<B> {
+ type Bound = Val;
+ fn bind(&self, sup_this: SupThis) -> Result<Val> {
+ evaluate(self.uctx.bind(sup_this)?, &self.value)
}
}
- let mut builder = ObjValueBuilder::new();
- if let Some(super_obj) = super_obj {
- builder.with_super(super_obj);
+ let LFieldMember {
+ name,
+ plus,
+ visibility,
+ value,
+ } = field;
+ let Some(name) = evaluate_field_name(ctx, name)? else {
+ return Ok(());
+ };
+
+ builder
+ .field(name.clone())
+ .with_add(*plus)
+ .with_visibility(*visibility)
+ .bindable(UnboundValue {
+ uctx,
+ value: value.clone(),
+ name,
+ })
+}
+pub fn evaluate_field_member_static(
+ builder: &mut ObjValueBuilder,
+ field_ctx: Context,
+ value_ctx: Context,
+ field: &LFieldMember,
+) -> Result<()> {
+ let LFieldMember {
+ name,
+ plus,
+ visibility,
+ value,
+ } = field;
+ let Some(name) = evaluate_field_name(field_ctx, name)? else {
+ return Ok(());
+ };
+
+ let value = value.clone();
+ builder
+ .field(name)
+ .with_add(*plus)
+ .with_visibility(*visibility)
+ .try_thunk(Thunk!(move || { evaluate(value_ctx, &value) }))?;
+ Ok(())
+}
+
+fn evaluate_obj_members(
+ super_obj: Option<ObjValue>,
+ ctx: Context,
+ members: &LObjMembers,
+) -> Result<Val> {
+ let mut builder = ObjValueBuilder::with_capacity(members.fields.len());
+ if let Some(sup) = super_obj {
+ builder.with_super(sup);
}
- if members.locals.is_empty() {
- // We can use the same context for all field evaluation, it doesn't depends on locals, only on this/super
- let uctx = DirectUnbound(ctx.clone());
+ let needs_unbound = members.this.is_some() || members.uses_super;
+
+ if needs_unbound {
+ let uctx = CachedUnbound::new(evaluate_locals_unbound(
+ ctx.clone(),
+ members.locals.clone(),
+ members.this,
+ ));
for field in &members.fields {
- evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;
+ evaluate_field_member_unbound(&mut builder, ctx.clone(), uctx.clone(), field)?;
}
if !members.asserts.is_empty() {
- builder.assert(ObjectAssert {
+ builder.assert(evaluate_object_assertions_unbound(
uctx,
- asserts: members.asserts.clone(),
- });
+ members.asserts.clone(),
+ ));
}
} else {
- let locals = members.locals.clone();
- // We have single context for all fields, so we can cache them together
- let uctx = CachedUnbound::new(evaluate_object_locals(ctx.clone(), locals));
+ let field_ctx = ctx;
+ let value_ctx = evaluate_locals(field_ctx.clone(), &members.locals);
for field in &members.fields {
- evaluate_field_member(&mut builder, ctx.clone(), uctx.clone(), field)?;
+ evaluate_field_member_static(
+ &mut builder,
+ field_ctx.clone(),
+ value_ctx.clone(),
+ field,
+ )?;
}
if !members.asserts.is_empty() {
- builder.assert(ObjectAssert {
- uctx,
- asserts: members.asserts.clone(),
- });
+ builder.assert(evaluate_object_assertions_static(
+ value_ctx,
+ members.asserts.clone(),
+ ));
}
}
- Ok(builder.build())
+ Ok(Val::Obj(builder.build()))
}
-pub fn evaluate_object(
- super_obj: Option<ObjValue>,
- ctx: Context,
- object: &ObjBody,
-) -> Result<ObjValue> {
- Ok(match object {
- ObjBody::MemberList(members) => evaluate_member_list_object(super_obj, ctx, members)?,
- ObjBody::ObjComp(obj) => {
- let mut builder = ObjValueBuilder::new();
- if let Some(super_obj) = super_obj {
- builder.with_super(super_obj);
- }
- let locals = obj.locals.clone();
- evaluate_comp(
- ctx.branch_point(),
- &obj.compspecs,
- 0,
- &mut |ctx, reserve| {
- let uctx = evaluate_object_locals(ctx.clone(), locals.clone());
- builder.reserve_fields(reserve);
-
- evaluate_field_member(&mut builder, ctx, uctx, &obj.field)
- },
- )?;
-
- builder.build()
- }
- })
-}
-
-pub fn evaluate_apply(
- ctx: Context,
- value: &Expr,
- args: &ArgsDesc,
- loc: CallLocation<'_>,
- tailstrict: bool,
-) -> Result<Val> {
- let value = evaluate(ctx.clone(), value)?;
- Ok(match value {
- Val::Func(f) => {
- let name = f.name();
- let unnamed = args
- .unnamed
- .iter()
- .cloned()
- .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))
- .collect::<Result<Vec<_>>>()?;
- let named = args
- .values
- .iter()
- .cloned()
- .map(|un| evaluate_thunk(ctx.clone(), un, tailstrict))
- .collect::<Result<Vec<_>>>()?;
- let prepare = PreparedFuncVal::new(f, args.unnamed.len(), &args.names)
- .with_description_src(loc, || format!("function <{name}> call"))?;
- let body = || prepare.call(loc, &unnamed, &named);
- if tailstrict {
- body()?
- } else {
- in_frame(loc, || format!("function <{name}> call"), body)?
- }
- }
- v => bail!(OnlyFunctionsCanBeCalledGot(v.value_type())),
- })
-}
-
-pub fn evaluate_assert(ctx: Context, assertion: &AssertStmt) -> Result<()> {
- let AssertStmt { assertion, message } = assertion;
+pub fn evaluate_assert(ctx: Context, assertion: &LAssertStmt) -> Result<()> {
+ let LAssertStmt { cond, message } = assertion;
let assertion_result = in_frame(
- CallLocation::new(&assertion.span),
+ CallLocation::native(),
|| "assertion condition".to_owned(),
- || bool::from_untyped(evaluate(ctx.clone(), assertion)?),
+ || bool::from_untyped(evaluate(ctx.clone(), cond)?),
)?;
if !assertion_result {
in_frame(
- CallLocation::new(&assertion.span),
+ CallLocation::new(&cond.span),
|| "assertion failure".to_owned(),
|| {
if let Some(msg) = message {
@@ -481,306 +547,42 @@
Ok(())
}
-pub fn evaluate_named_param(ctx: Context, expr: &Expr, name: ParamName) -> Result<Val> {
- match name {
- ParamName::Named(name) => evaluate_named(ctx, expr, name),
- ParamName::Unnamed => evaluate(ctx, expr),
+fn evaluate_object_assertions_unbound<B: Unbound<Bound = Context>>(
+ uctx: B,
+ asserts: Rc<Vec<LAssertStmt>>,
+) -> impl ObjectAssertion {
+ #[derive(Trace)]
+ struct ObjectAssert<B: Trace> {
+ uctx: B,
+ asserts: Rc<Vec<LAssertStmt>>,
}
-}
-
-pub fn evaluate_named(ctx: Context, expr: &Expr, name: IStr) -> Result<Val> {
- use Expr::*;
- Ok(match expr {
- Function(params, body) => evaluate_method(ctx, name, params.clone(), body.clone()),
- _ => evaluate(ctx, expr)?,
- })
-}
-
-pub fn evaluate_thunk(ctx: Context, expr: Rc<Expr>, tailstrict: bool) -> Result<Thunk<Val>> {
- Ok(if tailstrict {
- Thunk::evaluated(evaluate(ctx, &expr)?)
- } else {
- Thunk!(move || { evaluate(ctx, &expr) })
- })
-}
-#[allow(clippy::too_many_lines)]
-pub fn evaluate(ctx: Context, expr: &Expr) -> Result<Val> {
- use Expr::*;
-
- Ok(match expr {
- Literal(LiteralType::This) => Val::Obj(ctx.try_this()?),
- Literal(LiteralType::Super) => Val::Obj(ctx.try_sup_this()?.standalone_super()?),
- Literal(LiteralType::Dollar) => Val::Obj(ctx.try_dollar()?),
- Literal(LiteralType::True) => Val::Bool(true),
- Literal(LiteralType::False) => Val::Bool(false),
- Literal(LiteralType::Null) => Val::Null,
- Str(v) => Val::string(v.clone()),
- Num(v) => Val::try_num(*v)?,
- // I have tried to remove special behavior from super by implementing standalone-super
- // expresion, but looks like this case still needs special treatment.
- //
- // Note that other jsonnet implementations will fail on `if value in (super)` expression,
- // because the standalone super literal is not supported, that is because in other
- // implementations `in super` treated differently from `in smth_else`.
- BinaryOp(bin)
- if matches!(&bin.rhs, Expr::Literal(LiteralType::Super))
- && bin.op == BinaryOpType::In =>
- {
- let sup_this = ctx.try_sup_this()?;
- // In jsonnet, "field" in e is eager, LHS expression is always executed regardless of super existence.
- // In jrsonnet, however, this wasn't true, this was kept here for compatibility.
- if !sup_this.has_super() {
- return Ok(Val::Bool(false));
- }
- let field = evaluate(ctx, &bin.lhs)?;
- Val::Bool(sup_this.field_in_super(field.to_string()?))
- }
- BinaryOp(bin) => evaluate_binary_op_special(ctx, &bin.lhs, bin.op, &bin.rhs)?,
- UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(ctx, v)?)?,
- Var(name) => in_frame(
- CallLocation::new(&name.span),
- || format!("local <{}> access", &**name),
- || ctx.binding((**name).clone())?.evaluate(),
- )?,
- Index { indexable, parts } => ensure_sufficient_stack(|| {
- let mut parts = parts.iter();
- let mut indexable = if matches!(&**indexable, Expr::Literal(LiteralType::Super)) {
- let part = parts.next().expect("at least part should exist");
- // sup_this existence check might also be skipped here for null-coalesce...
- // But I believe this might cause errors.
- let sup_this = ctx.try_sup_this()?;
- if !sup_this.has_super() {
- #[cfg(feature = "exp-null-coaelse")]
- if part.null_coaelse {
- return Ok(Val::Null);
- }
- bail!(NoSuperFound)
- }
- let name = evaluate(ctx.clone(), &part.value)?;
-
- let Val::Str(name) = name else {
- bail!(ValueIndexMustBeTypeGot(
- ValType::Obj,
- ValType::Str,
- name.value_type(),
- ))
- };
-
- let name = name.into_flat();
- match sup_this
- .get_super(name.clone())
- .with_description_src(&part.span, || format!("field <{name}> access"))?
- {
- Some(v) => v,
- #[cfg(feature = "exp-null-coaelse")]
- None if part.null_coaelse => return Ok(Val::Null),
- None => {
- let suggestions = suggest_object_fields(
- &sup_this.standalone_super().expect("super exists"),
- name.clone(),
- );
-
- bail!(NoSuchField(name, suggestions))
- }
- }
- } else {
- evaluate(ctx.clone(), indexable)?
- };
-
- for part in parts {
- indexable = match (indexable, evaluate(ctx.clone(), &part.value)?) {
- (Val::Obj(v), Val::Str(key)) => match v
- .get(key.clone().into_flat())
- .with_description_src(&part.span, || format!("field <{key}> access"))?
- {
- Some(v) => v,
- #[cfg(feature = "exp-null-coaelse")]
- None if part.null_coaelse => return Ok(Val::Null),
- None => {
- let suggestions = suggest_object_fields(&v, key.into_flat());
-
- return Err(Error::from(NoSuchField(
- key.clone().into_flat(),
- suggestions,
- )))
- .with_description_src(&part.span, || format!("field <{key}> access"));
- }
- },
- (Val::Obj(_), n) => bail!(ValueIndexMustBeTypeGot(
- ValType::Obj,
- ValType::Str,
- n.value_type(),
- )),
- (Val::Arr(v), Val::Num(n)) => {
- let n = n.get();
- if n.fract() > f64::EPSILON {
- bail!(FractionalIndex)
- }
- if n < 0.0 {
- #[expect(
- clippy::cast_possible_truncation,
- reason = "it would be truncated anyway"
- )]
- let n = n as isize;
- bail!(ArrayBoundsError(n, v.len()));
- }
- #[expect(
- clippy::cast_possible_truncation,
- clippy::cast_sign_loss,
- reason = "n is checked postive"
- )]
- v.get(n as usize)?
- .ok_or_else(|| ArrayBoundsError(n as isize, v.len()))?
- }
- (Val::Arr(_), Val::Str(n)) => {
- bail!(AttemptedIndexAnArrayWithString(n.into_flat()))
- }
- (Val::Arr(_), n) => bail!(ValueIndexMustBeTypeGot(
- ValType::Arr,
- ValType::Num,
- n.value_type(),
- )),
-
- (Val::Str(s), Val::Num(n)) => Val::Str({
- let n = n.get();
- if n.fract() > f64::EPSILON {
- bail!(FractionalIndex)
- }
- if n < 0.0 {
- #[expect(
- clippy::cast_possible_truncation,
- reason = "it would be truncated anyway"
- )]
- let n = n as isize;
- bail!(ArrayBoundsError(n, s.into_flat().chars().count()));
- }
- #[expect(
- clippy::cast_sign_loss,
- clippy::cast_possible_truncation,
- reason = "n is positive, overflow will truncate as expected"
- )]
- let n = n as usize;
- let v: IStr = s
- .clone()
- .into_flat()
- .chars()
- .skip(n)
- .take(1)
- .collect::<String>()
- .into();
- if v.is_empty() {
- bail!(StringBoundsError(n, s.into_flat().chars().count()))
- }
- StrValue::Flat(v)
- }),
- (Val::Str(_), n) => bail!(ValueIndexMustBeTypeGot(
- ValType::Str,
- ValType::Num,
- n.value_type(),
- )),
- #[cfg(feature = "exp-null-coaelse")]
- (Val::Null, _) if part.null_coaelse => return Ok(Val::Null),
- (v, _) => bail!(CantIndexInto(v.value_type())),
- };
- }
- Ok(indexable)
- })?,
- LocalExpr(bindings, returned) => {
- let fctx = Context::new_future();
- let mut ctx = ContextBuilder::extend(ctx);
- for b in bindings {
- evaluate_dest(b, fctx.clone(), &mut ctx)?;
- }
- let ctx = ctx.build().into_future(fctx);
- evaluate(ctx, returned)?
- }
- Arr(items) => {
- if items.is_empty() {
- Val::arr(())
- } else {
- Val::Arr(ArrValue::expr(ctx, items.clone()))
- }
- }
- ArrComp(expr, comp_specs) => Val::Arr(evaluate_arr_comp(ctx, expr, comp_specs)?),
- Obj(body) => Val::Obj(evaluate_object(None, ctx, body)?),
- ObjExtend(a, b) => {
- let base = evaluate(ctx.clone(), a)?;
- match base {
- Val::Obj(base_obj) => Val::Obj(evaluate_object(Some(base_obj), ctx, b)?),
- _ => bail!("ObjExtend lhs should be an object value"),
- }
- }
- Apply(value, args, tailstrict) => ensure_sufficient_stack(|| {
- evaluate_apply(ctx, value, args, CallLocation::new(&args.span), *tailstrict)
- })?,
- Function(params, body) => {
- evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())
- }
- AssertExpr(assert) => {
- evaluate_assert(ctx.clone(), &assert.assert)?;
- evaluate(ctx, &assert.rest)?
- }
- ErrorStmt(s, e) => in_frame(
- CallLocation::new(s),
- || "error statement".to_owned(),
- || bail!(RuntimeError(evaluate(ctx, e)?.to_string()?,)),
- )?,
- IfElse(if_else) => {
- if in_frame(
- CallLocation::new(&if_else.cond.span),
- || "if condition".to_owned(),
- || bool::from_untyped(evaluate(ctx.clone(), &if_else.cond.cond)?),
- )? {
- evaluate(ctx, &if_else.cond_then)?
- } else {
- match &if_else.cond_else {
- Some(v) => evaluate(ctx, v)?,
- None => Val::Null,
- }
+ impl<B: Unbound<Bound = Context>> ObjectAssertion for ObjectAssert<B> {
+ fn run(&self, sup_this: SupThis) -> Result<()> {
+ let ctx = self.uctx.bind(sup_this)?;
+ for assert in &*self.asserts {
+ evaluate_assert(ctx.clone(), assert)?;
}
+ Ok(())
}
- Slice(slice) => {
- fn parse_idx<T: Typed + FromUntyped>(
- ctx: Context,
- expr: Option<&Spanned<Expr>>,
- desc: &'static str,
- ) -> Result<Option<T>> {
- if let Some(value) = expr {
- Ok(in_frame(
- CallLocation::new(&value.span),
- || format!("slice {desc}"),
- || <Option<T>>::from_untyped(evaluate(ctx, value)?),
- )?)
- } else {
- Ok(None)
- }
+ }
+ ObjectAssert { uctx, asserts }
+}
+fn evaluate_object_assertions_static(
+ ctx: Context,
+ asserts: Rc<Vec<LAssertStmt>>,
+) -> impl ObjectAssertion {
+ #[derive(Trace)]
+ struct ObjectAssert {
+ ctx: Context,
+ asserts: Rc<Vec<LAssertStmt>>,
+ }
+ impl ObjectAssertion for ObjectAssert {
+ fn run(&self, _sup_this: SupThis) -> Result<()> {
+ for assert in &*self.asserts {
+ evaluate_assert(self.ctx.clone(), assert)?;
}
-
- let indexable = evaluate(ctx.clone(), &slice.value)?;
-
- let start = parse_idx(ctx.clone(), slice.slice.start.as_ref(), "start")?;
- let end = parse_idx(ctx.clone(), slice.slice.end.as_ref(), "end")?;
- let step = parse_idx(ctx, slice.slice.step.as_ref(), "step")?;
-
- IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?)?
+ Ok(())
}
- Import(kind, path) => {
- let Expr::Str(path) = &**path else {
- bail!("computed imports are not supported")
- };
- with_state(|s| {
- let span = &kind.span;
- let resolved_path = s.resolve_from(span.0.source_path(), path)?;
- Ok(match &**kind {
- ImportKind::Normal => in_frame(
- CallLocation::new(span),
- || format!("import {:?}", path.clone()),
- || s.import_resolved(resolved_path),
- )?,
- ImportKind::Str => Val::string(s.import_resolved_str(resolved_path)?),
- ImportKind::Bin => Val::arr(s.import_resolved_bin(resolved_path)?),
- }) as Result<Val>
- })?
- }
- })
+ }
+ ObjectAssert { ctx, asserts }
}
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.snapdiffbeforeafterboth1---2source: tests/tests/golden.rs3expression: result4input_file: tests/golden/missing_binding.jsonnet5---6local is not defined: sta7There is a local with similar name present: std8 missing_binding.jsonnet:1:1-5: local <sta> access