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.rsdiffbeforeafterboth1use std::{fmt::Display, rc::Rc};23mod conversions;4pub use conversions::*;5use jrsonnet_gcmodule::Trace;6pub use jrsonnet_types::{ComplexValType, ValType};7use thiserror::Error;89use crate::{10 error::{Error, ErrorKind, Result},11 State, Val,12};1314#[derive(Debug, Error, Clone, Trace)]15pub enum TypeError {16 #[error("expected {0}, got {1}")]17 ExpectedGot(ComplexValType, ValType),18 #[error("missing property {0} from {1}")]19 MissingProperty(#[trace(skip)] Rc<str>, ComplexValType),20 #[error("every failed from {0}:\n{1}")]21 UnionFailed(ComplexValType, TypeLocErrorList),22 #[error(23 "number out of bounds: {0} not in {}..{}",24 .1.map(|v|v.to_string()).unwrap_or_default(),25 .2.map(|v|v.to_string()).unwrap_or_default(),26 )]27 BoundsFailed(f64, Option<f64>, Option<f64>),28}29impl From<TypeError> for Error {30 fn from(e: TypeError) -> Self {31 ErrorKind::TypeError(e.into()).into()32 }33}3435#[derive(Debug, Clone, Trace)]36pub struct TypeLocError(Box<TypeError>, ValuePathStack);37impl From<TypeError> for TypeLocError {38 fn from(e: TypeError) -> Self {39 Self(Box::new(e), ValuePathStack(Vec::new()))40 }41}42impl From<TypeLocError> for Error {43 fn from(e: TypeLocError) -> Self {44 ErrorKind::TypeError(e).into()45 }46}47impl Display for TypeLocError {48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {49 write!(f, "{}", self.0)?;50 if !(self.1).0.is_empty() {51 write!(f, " at {}", self.1)?;52 }53 Ok(())54 }55}5657#[derive(Debug, Clone, Trace)]58pub struct TypeLocErrorList(Vec<TypeLocError>);59impl Display for TypeLocErrorList {60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {61 use std::fmt::Write;62 let mut out = String::new();63 for (i, err) in self.0.iter().enumerate() {64 if i != 0 {65 writeln!(f)?;66 }67 out.clear();68 write!(out, "{err}")?;6970 for (i, line) in out.lines().enumerate() {71 if line.trim().is_empty() {72 continue;73 }74 if i == 0 {75 write!(f, " - ")?;76 } else {77 writeln!(f)?;78 write!(f, " ")?;79 }80 write!(f, "{line}")?;81 }82 }83 Ok(())84 }85}8687fn push_type_description(88 error_reason: impl Fn() -> String,89 path: impl Fn() -> ValuePathItem,90 item: impl Fn() -> Result<()>,91) -> Result<()> {92 State::push_description(error_reason, || match item() {93 Ok(_) => Ok(()),94 Err(mut e) => {95 if let ErrorKind::TypeError(e) = &mut e.error_mut() {96 (e.1).0.push(path());97 }98 Err(e)99 }100 })101}102103// TODO: check_fast for fast path of union type checking104pub trait CheckType {105 fn check(&self, value: &Val) -> Result<()>;106}107108impl CheckType for ValType {109 fn check(&self, value: &Val) -> Result<()> {110 let got = value.value_type();111 if got != *self {112 let loc_error: TypeLocError = TypeError::ExpectedGot((*self).into(), got).into();113 return Err(loc_error.into());114 }115 Ok(())116 }117}118119#[derive(Clone, Debug, Trace)]120enum ValuePathItem {121 Field(#[trace(skip)] Rc<str>),122 Index(u64),123}124impl Display for ValuePathItem {125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {126 match self {127 Self::Field(name) => write!(f, ".{name:?}")?,128 Self::Index(idx) => write!(f, "[{idx}]")?,129 }130 Ok(())131 }132}133134#[derive(Clone, Debug, Trace)]135struct ValuePathStack(Vec<ValuePathItem>);136impl Display for ValuePathStack {137 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {138 write!(f, "self")?;139 for elem in self.0.iter().rev() {140 write!(f, "{elem}")?;141 }142 Ok(())143 }144}145146impl CheckType for ComplexValType {147 #[allow(clippy::too_many_lines)]148 fn check(&self, value: &Val) -> Result<()> {149 match self {150 Self::Any => Ok(()),151 Self::Simple(t) => t.check(value),152 Self::Char => match value {153 Val::Str(s) if s.len() == 1 || s.clone().into_flat().chars().count() == 1 => Ok(()),154 v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),155 },156 Self::BoundedNumber(from, to) => {157 if let Val::Num(n) = value {158 if from.map(|from| from > *n).unwrap_or(false)159 || to.map(|to| to < *n).unwrap_or(false)160 {161 return Err(TypeError::BoundsFailed(*n, *from, *to).into());162 }163 Ok(())164 } else {165 Err(TypeError::ExpectedGot(self.clone(), value.value_type()).into())166 }167 }168 Self::Array(elem_type) => match value {169 Val::Arr(a) => {170 for (i, item) in a.iter().enumerate() {171 push_type_description(172 || format!("array index {i}"),173 || ValuePathItem::Index(i as u64),174 || elem_type.check(&item.clone()?),175 )?;176 }177 Ok(())178 }179 v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),180 },181 Self::ArrayRef(elem_type) => match value {182 Val::Arr(a) => {183 for (i, item) in a.iter().enumerate() {184 push_type_description(185 || format!("array index {i}"),186 || ValuePathItem::Index(i as u64),187 || elem_type.check(&item.clone()?),188 )?;189 }190 Ok(())191 }192 v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),193 },194 Self::ObjectRef(elems) => match value {195 Val::Obj(obj) => {196 for (k, v) in elems.iter() {197 if let Some(got_v) = obj.get((*k).into())? {198 push_type_description(199 || format!("property {k}"),200 || ValuePathItem::Field((*k).into()),201 || v.check(&got_v),202 )?;203 } else {204 return Err(205 TypeError::MissingProperty((*k).into(), self.clone()).into()206 );207 }208 }209 Ok(())210 }211 v => Err(TypeError::ExpectedGot(self.clone(), v.value_type()).into()),212 },213 Self::Union(types) => {214 let mut errors = Vec::new();215 for ty in types.iter() {216 match ty.check(value) {217 Ok(()) => {218 return Ok(());219 }220 Err(e) => match e.error() {221 ErrorKind::TypeError(e) => errors.push(e.clone()),222 _ => return Err(e),223 },224 }225 }226 Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())227 }228 Self::UnionRef(types) => {229 let mut errors = Vec::new();230 for ty in types.iter() {231 match ty.check(value) {232 Ok(()) => {233 return Ok(());234 }235 Err(e) => match e.error() {236 ErrorKind::TypeError(e) => errors.push(e.clone()),237 _ => return Err(e),238 },239 }240 }241 Err(TypeError::UnionFailed(self.clone(), TypeLocErrorList(errors)).into())242 }243 Self::Sum(types) => {244 for ty in types.iter() {245 ty.check(value)?;246 }247 Ok(())248 }249 Self::SumRef(types) => {250 for ty in types.iter() {251 ty.check(value)?;252 }253 Ok(())254 }255 }256 }257}crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -88,6 +88,54 @@
}
}
+pub trait ThunkMapper<Input>: Trace {
+ type Output;
+ fn map(self, from: Input) -> Result<Self::Output>;
+}
+impl<Input> Thunk<Input>
+where
+ Input: Trace + Clone,
+{
+ pub fn map<M>(self, mapper: M) -> Thunk<M::Output>
+ where
+ M: ThunkMapper<Input>,
+ M::Output: Trace,
+ {
+ #[derive(Trace)]
+ struct Mapped<Input: Trace, Mapper: Trace> {
+ inner: Thunk<Input>,
+ mapper: Mapper,
+ }
+ impl<Input, Mapper> ThunkValue for Mapped<Input, Mapper>
+ where
+ Input: Trace + Clone,
+ Mapper: ThunkMapper<Input>,
+ {
+ type Output = Mapper::Output;
+
+ fn get(self: Box<Self>) -> Result<Self::Output> {
+ let value = self.inner.evaluate()?;
+ let mapped = self.mapper.map(value)?;
+ Ok(mapped)
+ }
+ }
+
+ Thunk::new(Mapped::<Input, M> {
+ inner: self,
+ mapper,
+ })
+ }
+}
+
+impl<T: Trace> From<Result<T>> for Thunk<T> {
+ fn from(value: Result<T>) -> Self {
+ match value {
+ Ok(o) => Self::evaluated(o),
+ Err(e) => Self::errored(e),
+ }
+ }
+}
+
type CacheKey = (Option<WeakObjValue>, Option<WeakObjValue>);
#[derive(Trace, Clone)]
@@ -272,6 +320,11 @@
Self::Flat(value.into())
}
}
+impl From<IStr> for StrValue {
+ fn from(value: IStr) -> Self {
+ Self::Flat(value)
+ }
+}
impl Display for StrValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
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(())
}