difftreelog
feat unify Arg and Typed handling for Thunk
in: master
8 files changed
crates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -81,7 +81,7 @@
#[must_use]
pub fn into_future(self, ctx: Pending<Self>) -> Self {
{
- ctx.0.borrow_mut().replace(self);
+ ctx.clone().fill(self);
}
ctx.unwrap()
}
crates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -1,29 +1,49 @@
-use std::cell::RefCell;
+use std::cell::OnceCell;
use jrsonnet_gcmodule::{Cc, Trace};
+use crate::{error::ErrorKind::InfiniteRecursionDetected, throw, val::ThunkValue, Result, Thunk};
+
// TODO: Replace with OnceCell once in std
#[derive(Clone, Trace)]
-pub struct Pending<V: Trace + 'static>(pub Cc<RefCell<Option<V>>>);
+pub struct Pending<V: Trace + 'static>(pub Cc<OnceCell<V>>);
impl<T: Trace + 'static> Pending<T> {
pub fn new() -> Self {
- Self(Cc::new(RefCell::new(None)))
+ Self(Cc::new(OnceCell::new()))
}
pub fn new_filled(v: T) -> Self {
- Self(Cc::new(RefCell::new(Some(v))))
+ let cell = OnceCell::new();
+ let _ = cell.set(v);
+ Self(Cc::new(cell))
}
/// # Panics
/// If wrapper is filled already
pub fn fill(self, value: T) {
- assert!(self.0.borrow().is_none(), "wrapper is filled already");
- self.0.borrow_mut().replace(value);
+ self.0
+ .set(value)
+ .map_err(|_| ())
+ .expect("wrapper is filled already")
}
}
impl<T: Clone + Trace + 'static> Pending<T> {
/// # Panics
/// If wrapper is not yet filled
pub fn unwrap(&self) -> T {
- self.0.borrow().as_ref().cloned().unwrap()
+ self.0.get().cloned().expect("pending was not filled")
+ }
+ pub fn try_get(&self) -> Option<T> {
+ self.0.get().cloned()
+ }
+}
+
+impl<T: Trace + Clone> ThunkValue for Pending<T> {
+ type Output = T;
+
+ fn get(self: Box<Self>) -> Result<Self::Output> {
+ let Some(value) = self.0.get() else {
+ throw!(InfiniteRecursionDetected);
+ };
+ Ok(value.clone())
}
}
@@ -32,3 +52,9 @@
Self::new()
}
}
+
+impl<T: Trace + Clone> Into<Thunk<T>> for Pending<T> {
+ fn into(self) -> Thunk<T> {
+ Thunk::new(self)
+ }
+}
crates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -48,28 +48,22 @@
where
T: Typed + Clone,
{
- fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result<Thunk<Val>> {
+ fn evaluate_arg(&self, _ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {
+ if T::provides_lazy() && !tailstrict {
+ return Ok(T::into_lazy_untyped(self.clone()));
+ }
let val = T::into_untyped(self.clone())?;
Ok(Thunk::evaluated(val))
}
}
impl<T> OptionalContext for T where T: Typed + Clone {}
-impl ArgLike for Thunk<Val> {
- fn evaluate_arg(&self, _ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {
- if tailstrict {
- self.force()?;
- }
- Ok(self.clone())
- }
-}
-impl OptionalContext for Thunk<Val> {}
-
#[derive(Clone, Trace)]
pub enum TlaArg {
String(IStr),
Code(LocExpr),
Val(Val),
+ Lazy(Thunk<Val>),
}
impl ArgLike for TlaArg {
fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result<Thunk<Val>> {
@@ -84,6 +78,7 @@
})
}),
TlaArg::Val(val) => Ok(Thunk::evaluated(val.clone())),
+ TlaArg::Lazy(lazy) => Ok(lazy.clone()),
}
}
}
crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -15,7 +15,9 @@
function::CallLocation,
gc::{GcHashMap, GcHashSet, TraceBox},
operator::evaluate_add_op,
- tb, throw, MaybeUnbound, Result, State, Thunk, Unbound, Val,
+ tb, throw,
+ val::ThunkValue,
+ MaybeUnbound, Result, State, Thunk, Unbound, Val,
};
#[cfg(not(feature = "exp-preserve-order"))]
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -1,6 +1,6 @@
-use std::ops::Deref;
+use std::{collections::BTreeMap, marker::PhantomData, ops::Deref};
-use jrsonnet_gcmodule::Cc;
+use jrsonnet_gcmodule::{Cc, Trace};
use jrsonnet_interner::{IBytes, IStr};
pub use jrsonnet_macros::Typed;
use jrsonnet_types::{ComplexValType, ValType};
@@ -11,10 +11,28 @@
function::{native::NativeDesc, FuncDesc, FuncVal},
throw,
typed::CheckType,
- val::{IndexableVal, StrValue},
- ObjValue, ObjValueBuilder, Val,
+ val::{IndexableVal, StrValue, ThunkMapper},
+ ObjValue, ObjValueBuilder, Thunk, Val,
};
+#[derive(Trace)]
+struct FromUntyped<K: Trace>(PhantomData<fn() -> K>);
+impl<K> ThunkMapper<Val> for FromUntyped<K>
+where
+ K: Typed + Trace,
+{
+ type Output = K;
+
+ fn map(self, from: Val) -> Result<Self::Output> {
+ K::from_untyped(from)
+ }
+}
+impl<K: Trace> Default for FromUntyped<K> {
+ fn default() -> Self {
+ Self(PhantomData)
+ }
+}
+
pub trait TypedObj: Typed {
fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;
fn parse(obj: &ObjValue) -> Result<Self>;
@@ -28,8 +46,24 @@
pub trait Typed: Sized {
const TYPE: &'static ComplexValType;
fn into_untyped(typed: Self) -> Result<Val>;
+ fn into_lazy_untyped(typed: Self) -> Thunk<Val> {
+ Thunk::from(Self::into_untyped(typed))
+ }
fn from_untyped(untyped: Val) -> Result<Self>;
+ fn from_lazy_untyped(lazy: Thunk<Val>) -> Result<Self> {
+ Self::from_untyped(lazy.evaluate()?)
+ }
+
+ // Whatever caller should use `into_lazy_untyped` instead of `into_untyped`
+ fn provides_lazy() -> bool {
+ false
+ }
+ // Whatever caller should use `from_lazy_untyped` instead of `from_untyped` when possible
+ fn wants_lazy() -> bool {
+ false
+ }
+
/// Hack to make builtins be able to return non-result values, and make macros able to convert those values to result
/// This method returns identity in impl Typed for Result, and should not be overriden
#[doc(hidden)]
@@ -39,6 +73,54 @@
}
}
+impl<T> Typed for Thunk<T>
+where
+ T: Typed + Trace + Clone,
+{
+ const TYPE: &'static ComplexValType = &ComplexValType::Lazy(T::TYPE);
+
+ fn into_untyped(typed: Self) -> Result<Val> {
+ T::into_untyped(typed.evaluate()?)
+ }
+
+ fn from_untyped(untyped: Val) -> Result<Self> {
+ Self::from_lazy_untyped(Thunk::evaluated(untyped))
+ }
+
+ fn provides_lazy() -> bool {
+ true
+ }
+
+ fn into_lazy_untyped(inner: Self) -> Thunk<Val> {
+ #[derive(Trace)]
+ struct IntoUntyped<K: Trace>(PhantomData<fn() -> K>);
+ impl<K> ThunkMapper<K> for IntoUntyped<K>
+ where
+ K: Typed + Trace,
+ {
+ type Output = Val;
+
+ fn map(self, from: K) -> Result<Self::Output> {
+ K::into_untyped(from)
+ }
+ }
+ impl<K: Trace> Default for IntoUntyped<K> {
+ fn default() -> Self {
+ Self(PhantomData)
+ }
+ }
+ inner.map(<IntoUntyped<T>>::default())
+ }
+
+ fn wants_lazy() -> bool {
+ true
+ }
+
+ fn from_lazy_untyped(inner: Thunk<Val>) -> Result<Self> {
+ Ok(inner.map(<FromUntyped<T>>::default()))
+ }
+}
+
const MAX_SAFE_INTEGER: f64 = ((1u64 << (f64::MANTISSA_DIGITS + 1)) - 1) as f64;
macro_rules! impl_int {
crates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -252,6 +252,7 @@
}
Ok(())
}
+ Self::Lazy(_lazy) => Ok(()),
}
}
}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth1use std::{2 cell::RefCell,3 fmt::{self, Debug, Display},4 mem::replace,5 rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14 error::{Error, ErrorKind::*},15 function::FuncVal,16 gc::{GcHashMap, TraceBox},17 manifest::{ManifestFormat, ToStringFormat},18 tb, throw,19 typed::BoundedUsize,20 ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24 type Output;25 fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30 Computed(T),31 Errored(Error),32 Waiting(TraceBox<dyn ThunkValue<Output = T>>),33 Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42 pub fn evaluated(val: T) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44 }45 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47 }48 pub fn errored(e: Error) -> Self {49 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50 }51}5253impl<T> Thunk<T>54where55 T: Clone + Trace,56{57 pub fn force(&self) -> Result<()> {58 self.evaluate()?;59 Ok(())60 }6162 /// Evaluate thunk, or return cached value63 ///64 /// # Errors65 ///66 /// - Lazy value evaluation returned error67 /// - This method was called during inner value evaluation68 pub fn evaluate(&self) -> Result<T> {69 match &*self.0.borrow() {70 ThunkInner::Computed(v) => return Ok(v.clone()),71 ThunkInner::Errored(e) => return Err(e.clone()),72 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73 ThunkInner::Waiting(..) => (),74 };75 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)76 else {77 unreachable!();78 };79 let new_value = match value.0.get() {80 Ok(v) => v,81 Err(e) => {82 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());83 return Err(e);84 }85 };86 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());87 Ok(new_value)88 }89}9091type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);9293#[derive(Trace, Clone)]94pub struct CachedUnbound<I, T>95where96 I: Unbound<Bound = T>,97 T: Trace,98{99 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,100 value: I,101}102impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {103 pub fn new(value: I) -> Self {104 Self {105 cache: Cc::new(RefCell::new(GcHashMap::new())),106 value,107 }108 }109}110impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {111 type Bound = T;112 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {113 let cache_key = (114 sup.as_ref().map(|s| s.clone().downgrade()),115 this.as_ref().map(|t| t.clone().downgrade()),116 );117 {118 if let Some(t) = self.cache.borrow().get(&cache_key) {119 return Ok(t.clone());120 }121 }122 let bound = self.value.bind(sup, this)?;123124 {125 let mut cache = self.cache.borrow_mut();126 cache.insert(cache_key, bound.clone());127 }128129 Ok(bound)130 }131}132133impl<T: Debug + Trace> Debug for Thunk<T> {134 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {135 write!(f, "Lazy")136 }137}138impl<T: Trace> PartialEq for Thunk<T> {139 fn eq(&self, other: &Self) -> bool {140 Cc::ptr_eq(&self.0, &other.0)141 }142}143144/// Represents a Jsonnet value, which can be sliced or indexed (string or array).145#[allow(clippy::module_name_repetitions)]146pub enum IndexableVal {147 /// String.148 Str(IStr),149 /// Array.150 Arr(ArrValue),151}152impl IndexableVal {153 pub fn to_array(self) -> ArrValue {154 match self {155 IndexableVal::Str(s) => ArrValue::chars(s.chars()),156 IndexableVal::Arr(arr) => arr,157 }158 }159 /// Slice the value.160 ///161 /// # Implementation162 ///163 /// For strings, will create a copy of specified interval.164 ///165 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.166 pub fn slice(167 self,168 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,169 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,170 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,171 ) -> Result<Self> {172 match &self {173 IndexableVal::Str(s) => {174 let index = index.as_deref().copied().unwrap_or(0);175 let end = end.as_deref().copied().unwrap_or(usize::MAX);176 let step = step.as_deref().copied().unwrap_or(1);177178 if index >= end {179 return Ok(Self::Str("".into()));180 }181182 Ok(Self::Str(183 (s.chars()184 .skip(index)185 .take(end - index)186 .step_by(step)187 .collect::<String>())188 .into(),189 ))190 }191 IndexableVal::Arr(arr) => {192 let index = index.as_deref().copied().unwrap_or(0);193 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());194 let step = step.as_deref().copied().unwrap_or(1);195196 if index >= end {197 return Ok(Self::Arr(ArrValue::empty()));198 }199200 Ok(Self::Arr(201 arr.clone()202 .slice(Some(index), Some(end), Some(step))203 .expect("arguments checked"),204 ))205 }206 }207 }208}209210#[derive(Debug, Clone, Trace)]211pub enum StrValue {212 Flat(IStr),213 Tree(Rc<(StrValue, StrValue, usize)>),214}215impl StrValue {216 pub fn concat(a: StrValue, b: StrValue) -> Self {217 // TODO: benchmark for an optimal value, currently just a arbitrary choice218 const STRING_EXTEND_THRESHOLD: usize = 100;219220 if a.is_empty() {221 b222 } else if b.is_empty() {223 a224 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {225 Self::Flat(format!("{a}{b}").into())226 } else {227 let len = a.len() + b.len();228 Self::Tree(Rc::new((a, b, len)))229 }230 }231 pub fn into_flat(self) -> IStr {232 #[cold]233 fn write_buf(s: &StrValue, out: &mut String) {234 match s {235 StrValue::Flat(f) => out.push_str(f),236 StrValue::Tree(t) => {237 write_buf(&t.0, out);238 write_buf(&t.1, out);239 }240 }241 }242 match self {243 StrValue::Flat(f) => f,244 StrValue::Tree(_) => {245 let mut buf = String::with_capacity(self.len());246 write_buf(&self, &mut buf);247 buf.into()248 }249 }250 }251 pub fn len(&self) -> usize {252 match self {253 StrValue::Flat(v) => v.len(),254 StrValue::Tree(t) => t.2,255 }256 }257 pub fn is_empty(&self) -> bool {258 match self {259 Self::Flat(v) => v.is_empty(),260 // Can't create non-flat empty string261 Self::Tree(_) => false,262 }263 }264}265impl From<&str> for StrValue {266 fn from(value: &str) -> Self {267 Self::Flat(value.into())268 }269}270impl From<String> for StrValue {271 fn from(value: String) -> Self {272 Self::Flat(value.into())273 }274}275impl Display for StrValue {276 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {277 match self {278 StrValue::Flat(v) => write!(f, "{v}"),279 StrValue::Tree(t) => {280 write!(f, "{}", t.0)?;281 write!(f, "{}", t.1)282 }283 }284 }285}286impl PartialEq for StrValue {287 fn eq(&self, other: &Self) -> bool {288 let a = self.clone().into_flat();289 let b = other.clone().into_flat();290 a == b291 }292}293impl Eq for StrValue {}294impl PartialOrd for StrValue {295 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {296 let a = self.clone().into_flat();297 let b = other.clone().into_flat();298 Some(a.cmp(&b))299 }300}301impl Ord for StrValue {302 fn cmp(&self, other: &Self) -> std::cmp::Ordering {303 self.partial_cmp(other)304 .expect("partial_cmp always returns Some")305 }306}307308/// Represents any valid Jsonnet value.309#[derive(Debug, Clone, Trace)]310pub enum Val {311 /// Represents a Jsonnet boolean.312 Bool(bool),313 /// Represents a Jsonnet null value.314 Null,315 /// Represents a Jsonnet string.316 Str(StrValue),317 /// Represents a Jsonnet number.318 /// Should be finite, and not NaN319 /// This restriction isn't enforced by enum, as enum field can't be marked as private320 Num(f64),321 /// Experimental bigint322 #[cfg(feature = "exp-bigint")]323 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),324 /// Represents a Jsonnet array.325 Arr(ArrValue),326 /// Represents a Jsonnet object.327 Obj(ObjValue),328 /// Represents a Jsonnet function.329 Func(FuncVal),330}331332#[cfg(target_pointer_width = "64")]333static_assertions::assert_eq_size!(Val, [u8; 24]);334335impl From<IndexableVal> for Val {336 fn from(v: IndexableVal) -> Self {337 match v {338 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),339 IndexableVal::Arr(a) => Self::Arr(a),340 }341 }342}343344impl Val {345 pub const fn as_bool(&self) -> Option<bool> {346 match self {347 Self::Bool(v) => Some(*v),348 _ => None,349 }350 }351 pub const fn as_null(&self) -> Option<()> {352 match self {353 Self::Null => Some(()),354 _ => None,355 }356 }357 pub fn as_str(&self) -> Option<IStr> {358 match self {359 Self::Str(s) => Some(s.clone().into_flat()),360 _ => None,361 }362 }363 pub const fn as_num(&self) -> Option<f64> {364 match self {365 Self::Num(n) => Some(*n),366 _ => None,367 }368 }369 pub fn as_arr(&self) -> Option<ArrValue> {370 match self {371 Self::Arr(a) => Some(a.clone()),372 _ => None,373 }374 }375 pub fn as_obj(&self) -> Option<ObjValue> {376 match self {377 Self::Obj(o) => Some(o.clone()),378 _ => None,379 }380 }381 pub fn as_func(&self) -> Option<FuncVal> {382 match self {383 Self::Func(f) => Some(f.clone()),384 _ => None,385 }386 }387388 /// Creates `Val::Num` after checking for numeric overflow.389 /// As numbers are `f64`, we can just check for their finity.390 pub fn new_checked_num(num: f64) -> Result<Self> {391 if num.is_finite() {392 Ok(Self::Num(num))393 } else {394 throw!("overflow")395 }396 }397398 pub const fn value_type(&self) -> ValType {399 match self {400 Self::Str(..) => ValType::Str,401 Self::Num(..) => ValType::Num,402 #[cfg(feature = "exp-bigint")]403 Self::BigInt(..) => ValType::BigInt,404 Self::Arr(..) => ValType::Arr,405 Self::Obj(..) => ValType::Obj,406 Self::Bool(_) => ValType::Bool,407 Self::Null => ValType::Null,408 Self::Func(..) => ValType::Func,409 }410 }411412 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {413 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {414 manifest.manifest(val.clone())415 }416 manifest_dyn(self, &format)417 }418419 pub fn to_string(&self) -> Result<IStr> {420 Ok(match self {421 Self::Bool(true) => "true".into(),422 Self::Bool(false) => "false".into(),423 Self::Null => "null".into(),424 Self::Str(s) => s.clone().into_flat(),425 _ => self.manifest(ToStringFormat).map(IStr::from)?,426 })427 }428429 pub fn into_indexable(self) -> Result<IndexableVal> {430 Ok(match self {431 Val::Str(s) => IndexableVal::Str(s.into_flat()),432 Val::Arr(arr) => IndexableVal::Arr(arr),433 _ => throw!(ValueIsNotIndexable(self.value_type())),434 })435 }436}437438const fn is_function_like(val: &Val) -> bool {439 matches!(val, Val::Func(_))440}441442/// Native implementation of `std.primitiveEquals`443pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {444 Ok(match (val_a, val_b) {445 (Val::Bool(a), Val::Bool(b)) => a == b,446 (Val::Null, Val::Null) => true,447 (Val::Str(a), Val::Str(b)) => a == b,448 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,449 #[cfg(feature = "exp-bigint")]450 (Val::BigInt(a), Val::BigInt(b)) => a == b,451 (Val::Arr(_), Val::Arr(_)) => {452 throw!("primitiveEquals operates on primitive types, got array")453 }454 (Val::Obj(_), Val::Obj(_)) => {455 throw!("primitiveEquals operates on primitive types, got object")456 }457 (a, b) if is_function_like(a) && is_function_like(b) => {458 throw!("cannot test equality of functions")459 }460 (_, _) => false,461 })462}463464/// Native implementation of `std.equals`465pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {466 if val_a.value_type() != val_b.value_type() {467 return Ok(false);468 }469 match (val_a, val_b) {470 (Val::Arr(a), Val::Arr(b)) => {471 if ArrValue::ptr_eq(a, b) {472 return Ok(true);473 }474 if a.len() != b.len() {475 return Ok(false);476 }477 for (a, b) in a.iter().zip(b.iter()) {478 if !equals(&a?, &b?)? {479 return Ok(false);480 }481 }482 Ok(true)483 }484 (Val::Obj(a), Val::Obj(b)) => {485 if ObjValue::ptr_eq(a, b) {486 return Ok(true);487 }488 let fields = a.fields(489 #[cfg(feature = "exp-preserve-order")]490 false,491 );492 if fields493 != b.fields(494 #[cfg(feature = "exp-preserve-order")]495 false,496 ) {497 return Ok(false);498 }499 for field in fields {500 if !equals(501 &a.get(field.clone())?.expect("field exists"),502 &b.get(field)?.expect("field exists"),503 )? {504 return Ok(false);505 }506 }507 Ok(true)508 }509 (a, b) => Ok(primitive_equals(a, b)?),510 }511}1use std::{2 cell::RefCell,3 fmt::{self, Debug, Display},4 mem::replace,5 rc::Rc,6};78use jrsonnet_gcmodule::{Cc, Trace};9use jrsonnet_interner::IStr;10use jrsonnet_types::ValType;1112pub use crate::arr::ArrValue;13use crate::{14 error::{Error, ErrorKind::*},15 function::FuncVal,16 gc::{GcHashMap, TraceBox},17 manifest::{ManifestFormat, ToStringFormat},18 tb, throw,19 typed::BoundedUsize,20 ObjValue, Result, Unbound, WeakObjValue,21};2223pub trait ThunkValue: Trace {24 type Output;25 fn get(self: Box<Self>) -> Result<Self::Output>;26}2728#[derive(Trace)]29enum ThunkInner<T: Trace> {30 Computed(T),31 Errored(Error),32 Waiting(TraceBox<dyn ThunkValue<Output = T>>),33 Pending,34}3536/// Lazily evaluated value37#[allow(clippy::module_name_repetitions)]38#[derive(Clone, Trace)]39pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);4041impl<T: Trace> Thunk<T> {42 pub fn evaluated(val: T) -> Self {43 Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))44 }45 pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {46 Self(Cc::new(RefCell::new(ThunkInner::Waiting(tb!(f)))))47 }48 pub fn errored(e: Error) -> Self {49 Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))50 }51}5253impl<T> Thunk<T>54where55 T: Clone + Trace,56{57 pub fn force(&self) -> Result<()> {58 self.evaluate()?;59 Ok(())60 }6162 /// Evaluate thunk, or return cached value63 ///64 /// # Errors65 ///66 /// - Lazy value evaluation returned error67 /// - This method was called during inner value evaluation68 pub fn evaluate(&self) -> Result<T> {69 match &*self.0.borrow() {70 ThunkInner::Computed(v) => return Ok(v.clone()),71 ThunkInner::Errored(e) => return Err(e.clone()),72 ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),73 ThunkInner::Waiting(..) => (),74 };75 let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)76 else {77 unreachable!();78 };79 let new_value = match value.0.get() {80 Ok(v) => v,81 Err(e) => {82 *self.0.borrow_mut() = ThunkInner::Errored(e.clone());83 return Err(e);84 }85 };86 *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());87 Ok(new_value)88 }89}9091pub trait ThunkMapper<Input>: Trace {92 type Output;93 fn map(self, from: Input) -> Result<Self::Output>;94}95impl<Input> Thunk<Input>96where97 Input: Trace + Clone,98{99 pub fn map<M>(self, mapper: M) -> Thunk<M::Output>100 where101 M: ThunkMapper<Input>,102 M::Output: Trace,103 {104 #[derive(Trace)]105 struct Mapped<Input: Trace, Mapper: Trace> {106 inner: Thunk<Input>,107 mapper: Mapper,108 }109 impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>110 where111 Input: Trace + Clone,112 Mapper: ThunkMapper<Input>,113 {114 type Output = Mapper::Output;115116 fn get(self: Box<Self>) -> Result<Self::Output> {117 let value = self.inner.evaluate()?;118 let mapped = self.mapper.map(value)?;119 Ok(mapped)120 }121 }122123 Thunk::new(Mapped::<Input, M> {124 inner: self,125 mapper,126 })127 }128}129130impl<T: Trace> From<Result<T>> for Thunk<T> {131 fn from(value: Result<T>) -> Self {132 match value {133 Ok(o) => Self::evaluated(o),134 Err(e) => Self::errored(e),135 }136 }137}138139type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);140141#[derive(Trace, Clone)]142pub struct CachedUnbound<I, T>143where144 I: Unbound<Bound = T>,145 T: Trace,146{147 cache: Cc<RefCell<GcHashMap<CacheKey, T>>>,148 value: I,149}150impl<I: Unbound<Bound = T>, T: Trace> CachedUnbound<I, T> {151 pub fn new(value: I) -> Self {152 Self {153 cache: Cc::new(RefCell::new(GcHashMap::new())),154 value,155 }156 }157}158impl<I: Unbound<Bound = T>, T: Clone + Trace> Unbound for CachedUnbound<I, T> {159 type Bound = T;160 fn bind(&self, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<T> {161 let cache_key = (162 sup.as_ref().map(|s| s.clone().downgrade()),163 this.as_ref().map(|t| t.clone().downgrade()),164 );165 {166 if let Some(t) = self.cache.borrow().get(&cache_key) {167 return Ok(t.clone());168 }169 }170 let bound = self.value.bind(sup, this)?;171172 {173 let mut cache = self.cache.borrow_mut();174 cache.insert(cache_key, bound.clone());175 }176177 Ok(bound)178 }179}180181impl<T: Debug + Trace> Debug for Thunk<T> {182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {183 write!(f, "Lazy")184 }185}186impl<T: Trace> PartialEq for Thunk<T> {187 fn eq(&self, other: &Self) -> bool {188 Cc::ptr_eq(&self.0, &other.0)189 }190}191192/// Represents a Jsonnet value, which can be sliced or indexed (string or array).193#[allow(clippy::module_name_repetitions)]194pub enum IndexableVal {195 /// String.196 Str(IStr),197 /// Array.198 Arr(ArrValue),199}200impl IndexableVal {201 pub fn to_array(self) -> ArrValue {202 match self {203 IndexableVal::Str(s) => ArrValue::chars(s.chars()),204 IndexableVal::Arr(arr) => arr,205 }206 }207 /// Slice the value.208 ///209 /// # Implementation210 ///211 /// For strings, will create a copy of specified interval.212 ///213 /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned.214 pub fn slice(215 self,216 index: Option<BoundedUsize<0, { i32::MAX as usize }>>,217 end: Option<BoundedUsize<0, { i32::MAX as usize }>>,218 step: Option<BoundedUsize<1, { i32::MAX as usize }>>,219 ) -> Result<Self> {220 match &self {221 IndexableVal::Str(s) => {222 let index = index.as_deref().copied().unwrap_or(0);223 let end = end.as_deref().copied().unwrap_or(usize::MAX);224 let step = step.as_deref().copied().unwrap_or(1);225226 if index >= end {227 return Ok(Self::Str("".into()));228 }229230 Ok(Self::Str(231 (s.chars()232 .skip(index)233 .take(end - index)234 .step_by(step)235 .collect::<String>())236 .into(),237 ))238 }239 IndexableVal::Arr(arr) => {240 let index = index.as_deref().copied().unwrap_or(0);241 let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());242 let step = step.as_deref().copied().unwrap_or(1);243244 if index >= end {245 return Ok(Self::Arr(ArrValue::empty()));246 }247248 Ok(Self::Arr(249 arr.clone()250 .slice(Some(index), Some(end), Some(step))251 .expect("arguments checked"),252 ))253 }254 }255 }256}257258#[derive(Debug, Clone, Trace)]259pub enum StrValue {260 Flat(IStr),261 Tree(Rc<(StrValue, StrValue, usize)>),262}263impl StrValue {264 pub fn concat(a: StrValue, b: StrValue) -> Self {265 // TODO: benchmark for an optimal value, currently just a arbitrary choice266 const STRING_EXTEND_THRESHOLD: usize = 100;267268 if a.is_empty() {269 b270 } else if b.is_empty() {271 a272 } else if a.len() + b.len() < STRING_EXTEND_THRESHOLD {273 Self::Flat(format!("{a}{b}").into())274 } else {275 let len = a.len() + b.len();276 Self::Tree(Rc::new((a, b, len)))277 }278 }279 pub fn into_flat(self) -> IStr {280 #[cold]281 fn write_buf(s: &StrValue, out: &mut String) {282 match s {283 StrValue::Flat(f) => out.push_str(f),284 StrValue::Tree(t) => {285 write_buf(&t.0, out);286 write_buf(&t.1, out);287 }288 }289 }290 match self {291 StrValue::Flat(f) => f,292 StrValue::Tree(_) => {293 let mut buf = String::with_capacity(self.len());294 write_buf(&self, &mut buf);295 buf.into()296 }297 }298 }299 pub fn len(&self) -> usize {300 match self {301 StrValue::Flat(v) => v.len(),302 StrValue::Tree(t) => t.2,303 }304 }305 pub fn is_empty(&self) -> bool {306 match self {307 Self::Flat(v) => v.is_empty(),308 // Can't create non-flat empty string309 Self::Tree(_) => false,310 }311 }312}313impl From<&str> for StrValue {314 fn from(value: &str) -> Self {315 Self::Flat(value.into())316 }317}318impl From<String> for StrValue {319 fn from(value: String) -> Self {320 Self::Flat(value.into())321 }322}323impl From<IStr> for StrValue {324 fn from(value: IStr) -> Self {325 Self::Flat(value)326 }327}328impl Display for StrValue {329 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {330 match self {331 StrValue::Flat(v) => write!(f, "{v}"),332 StrValue::Tree(t) => {333 write!(f, "{}", t.0)?;334 write!(f, "{}", t.1)335 }336 }337 }338}339impl PartialEq for StrValue {340 fn eq(&self, other: &Self) -> bool {341 let a = self.clone().into_flat();342 let b = other.clone().into_flat();343 a == b344 }345}346impl Eq for StrValue {}347impl PartialOrd for StrValue {348 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {349 let a = self.clone().into_flat();350 let b = other.clone().into_flat();351 Some(a.cmp(&b))352 }353}354impl Ord for StrValue {355 fn cmp(&self, other: &Self) -> std::cmp::Ordering {356 self.partial_cmp(other)357 .expect("partial_cmp always returns Some")358 }359}360361/// Represents any valid Jsonnet value.362#[derive(Debug, Clone, Trace)]363pub enum Val {364 /// Represents a Jsonnet boolean.365 Bool(bool),366 /// Represents a Jsonnet null value.367 Null,368 /// Represents a Jsonnet string.369 Str(StrValue),370 /// Represents a Jsonnet number.371 /// Should be finite, and not NaN372 /// This restriction isn't enforced by enum, as enum field can't be marked as private373 Num(f64),374 /// Experimental bigint375 #[cfg(feature = "exp-bigint")]376 BigInt(#[trace(skip)] Box<num_bigint::BigInt>),377 /// Represents a Jsonnet array.378 Arr(ArrValue),379 /// Represents a Jsonnet object.380 Obj(ObjValue),381 /// Represents a Jsonnet function.382 Func(FuncVal),383}384385#[cfg(target_pointer_width = "64")]386static_assertions::assert_eq_size!(Val, [u8; 24]);387388impl From<IndexableVal> for Val {389 fn from(v: IndexableVal) -> Self {390 match v {391 IndexableVal::Str(s) => Self::Str(StrValue::Flat(s)),392 IndexableVal::Arr(a) => Self::Arr(a),393 }394 }395}396397impl Val {398 pub const fn as_bool(&self) -> Option<bool> {399 match self {400 Self::Bool(v) => Some(*v),401 _ => None,402 }403 }404 pub const fn as_null(&self) -> Option<()> {405 match self {406 Self::Null => Some(()),407 _ => None,408 }409 }410 pub fn as_str(&self) -> Option<IStr> {411 match self {412 Self::Str(s) => Some(s.clone().into_flat()),413 _ => None,414 }415 }416 pub const fn as_num(&self) -> Option<f64> {417 match self {418 Self::Num(n) => Some(*n),419 _ => None,420 }421 }422 pub fn as_arr(&self) -> Option<ArrValue> {423 match self {424 Self::Arr(a) => Some(a.clone()),425 _ => None,426 }427 }428 pub fn as_obj(&self) -> Option<ObjValue> {429 match self {430 Self::Obj(o) => Some(o.clone()),431 _ => None,432 }433 }434 pub fn as_func(&self) -> Option<FuncVal> {435 match self {436 Self::Func(f) => Some(f.clone()),437 _ => None,438 }439 }440441 /// Creates `Val::Num` after checking for numeric overflow.442 /// As numbers are `f64`, we can just check for their finity.443 pub fn new_checked_num(num: f64) -> Result<Self> {444 if num.is_finite() {445 Ok(Self::Num(num))446 } else {447 throw!("overflow")448 }449 }450451 pub const fn value_type(&self) -> ValType {452 match self {453 Self::Str(..) => ValType::Str,454 Self::Num(..) => ValType::Num,455 #[cfg(feature = "exp-bigint")]456 Self::BigInt(..) => ValType::BigInt,457 Self::Arr(..) => ValType::Arr,458 Self::Obj(..) => ValType::Obj,459 Self::Bool(_) => ValType::Bool,460 Self::Null => ValType::Null,461 Self::Func(..) => ValType::Func,462 }463 }464465 pub fn manifest(&self, format: impl ManifestFormat) -> Result<String> {466 fn manifest_dyn(val: &Val, manifest: &dyn ManifestFormat) -> Result<String> {467 manifest.manifest(val.clone())468 }469 manifest_dyn(self, &format)470 }471472 pub fn to_string(&self) -> Result<IStr> {473 Ok(match self {474 Self::Bool(true) => "true".into(),475 Self::Bool(false) => "false".into(),476 Self::Null => "null".into(),477 Self::Str(s) => s.clone().into_flat(),478 _ => self.manifest(ToStringFormat).map(IStr::from)?,479 })480 }481482 pub fn into_indexable(self) -> Result<IndexableVal> {483 Ok(match self {484 Val::Str(s) => IndexableVal::Str(s.into_flat()),485 Val::Arr(arr) => IndexableVal::Arr(arr),486 _ => throw!(ValueIsNotIndexable(self.value_type())),487 })488 }489}490491const fn is_function_like(val: &Val) -> bool {492 matches!(val, Val::Func(_))493}494495/// Native implementation of `std.primitiveEquals`496pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {497 Ok(match (val_a, val_b) {498 (Val::Bool(a), Val::Bool(b)) => a == b,499 (Val::Null, Val::Null) => true,500 (Val::Str(a), Val::Str(b)) => a == b,501 (Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,502 #[cfg(feature = "exp-bigint")]503 (Val::BigInt(a), Val::BigInt(b)) => a == b,504 (Val::Arr(_), Val::Arr(_)) => {505 throw!("primitiveEquals operates on primitive types, got array")506 }507 (Val::Obj(_), Val::Obj(_)) => {508 throw!("primitiveEquals operates on primitive types, got object")509 }510 (a, b) if is_function_like(a) && is_function_like(b) => {511 throw!("cannot test equality of functions")512 }513 (_, _) => false,514 })515}516517/// Native implementation of `std.equals`518pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {519 if val_a.value_type() != val_b.value_type() {520 return Ok(false);521 }522 match (val_a, val_b) {523 (Val::Arr(a), Val::Arr(b)) => {524 if ArrValue::ptr_eq(a, b) {525 return Ok(true);526 }527 if a.len() != b.len() {528 return Ok(false);529 }530 for (a, b) in a.iter().zip(b.iter()) {531 if !equals(&a?, &b?)? {532 return Ok(false);533 }534 }535 Ok(true)536 }537 (Val::Obj(a), Val::Obj(b)) => {538 if ObjValue::ptr_eq(a, b) {539 return Ok(true);540 }541 let fields = a.fields(542 #[cfg(feature = "exp-preserve-order")]543 false,544 );545 if fields546 != b.fields(547 #[cfg(feature = "exp-preserve-order")]548 false,549 ) {550 return Ok(false);551 }552 for field in fields {553 if !equals(554 &a.get(field.clone())?.expect("field exists"),555 &b.get(field)?.expect("field exists"),556 )? {557 return Ok(false);558 }559 }560 Ok(true)561 }562 (a, b) => Ok(primitive_equals(a, b)?),563 }564}crates/jrsonnet-types/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -128,10 +128,12 @@
Array(Box<ComplexValType>),
ArrayRef(&'static ComplexValType),
ObjectRef(&'static [(&'static str, &'static ComplexValType)]),
+ AttrsOf(&'static ComplexValType),
Union(Vec<ComplexValType>),
UnionRef(&'static [&'static ComplexValType]),
Sum(Vec<ComplexValType>),
SumRef(&'static [&'static ComplexValType]),
+ Lazy(&'static ComplexValType),
}
impl From<ValType> for ComplexValType {
@@ -195,10 +197,18 @@
}
write!(f, "}}")?;
}
+ ComplexValType::AttrsOf(a) => {
+ if matches!(a, ComplexValType::Any) {
+ write!(f, "object")?;
+ } else {
+ write!(f, "AttrsOf<{a}>")?;
+ }
+ }
ComplexValType::Union(v) => write_union(f, true, v.iter())?,
ComplexValType::UnionRef(v) => write_union(f, true, v.iter().copied())?,
ComplexValType::Sum(v) => write_union(f, false, v.iter())?,
ComplexValType::SumRef(v) => write_union(f, false, v.iter().copied())?,
+ ComplexValType::Lazy(lazy) => write!(f, "Lazy<{lazy}>")?,
};
Ok(())
}