From 7cdcae351387fbdc68c224ee537b033495197859 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Mon, 26 Aug 2024 01:09:32 +0000 Subject: [PATCH] feat: simplify Thunk creation with closure syntax --- --- a/crates/jrsonnet-evaluator/src/arr/spec.rs +++ b/crates/jrsonnet-evaluator/src/arr/spec.rs @@ -7,7 +7,7 @@ use super::ArrValue; use crate::{ error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, typed::Typed, - val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val, + Context, Error, ObjValue, Result, Thunk, Val, }; pub trait ArrayLike: Any + Trace + Debug { @@ -182,23 +182,6 @@ Ok(Some(new_value)) } fn get_lazy(&self, index: usize) -> Option> { - #[derive(Trace)] - struct ArrayElement { - arr_thunk: ExprArray, - index: usize, - } - - impl ThunkValue for ArrayElement { - type Output = Val; - - fn get(self: Box) -> Result { - self.arr_thunk - .get(self.index) - .transpose() - .expect("index checked") - } - } - if index >= self.len() { return None; } @@ -208,9 +191,9 @@ ArrayThunk::Waiting(_) | ArrayThunk::Pending => {} }; - Some(Thunk::new(ArrayElement { - arr_thunk: self.clone(), - index, + let arr_thunk = self.clone(); + Some(Thunk!(move || { + arr_thunk.get(index).transpose().expect("index checked") })) } fn get_cheap(&self, _index: usize) -> Option { @@ -492,23 +475,6 @@ Ok(Some(new_value)) } fn get_lazy(&self, index: usize) -> Option> { - #[derive(Trace)] - struct ArrayElement { - arr_thunk: MappedArray, - index: usize, - } - - impl ThunkValue for ArrayElement { - type Output = Val; - - fn get(self: Box) -> Result { - self.arr_thunk - .get(self.index) - .transpose() - .expect("index checked") - } - } - if index >= self.len() { return None; } @@ -518,9 +484,9 @@ ArrayThunk::Waiting(()) | ArrayThunk::Pending => {} }; - Some(Thunk::new(ArrayElement { - arr_thunk: self.clone(), - index, + let arr_thunk = self.clone(); + Some(Thunk!(move || { + arr_thunk.get(index).transpose().expect("index checked") })) } --- a/crates/jrsonnet-evaluator/src/evaluate/destructure.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/destructure.rs @@ -1,13 +1,11 @@ -use jrsonnet_gcmodule::Trace; use jrsonnet_interner::IStr; -use jrsonnet_parser::{BindSpec, Destruct, LocExpr, ParamsDesc}; +use jrsonnet_parser::{BindSpec, Destruct}; use crate::{ bail, error::{ErrorKind::*, Result}, evaluate, evaluate_method, evaluate_named, gc::GcHashMap, - val::ThunkValue, Context, Pending, Thunk, Val, }; @@ -31,65 +29,34 @@ #[cfg(feature = "exp-destruct")] Destruct::Array { start, rest, end } => { use jrsonnet_parser::DestructRest; - - use crate::arr::ArrValue; - - #[derive(Trace)] - struct DataThunk { - parent: Thunk, - min_len: usize, - has_rest: bool, - } - impl ThunkValue for DataThunk { - type Output = ArrValue; - fn get(self: Box) -> Result { - let v = self.parent.evaluate()?; - let Val::Arr(arr) = v else { - bail!("expected array"); - }; - if !self.has_rest { - if arr.len() != self.min_len { - bail!("expected {} elements, got {}", self.min_len, arr.len()) - } - } else if arr.len() < self.min_len { - bail!( - "expected at least {} elements, but array was only {}", - self.min_len, - arr.len() - ) + 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()) } - Ok(arr) + } else if arr.len() < min_len { + bail!( + "expected at least {} elements, but array was only {}", + min_len, + arr.len() + ) } - } - - let full = Thunk::new(DataThunk { - min_len: start.len() + end.len(), - has_rest: rest.is_some(), - parent, + Ok(arr) }); { - #[derive(Trace)] - struct BaseThunk { - full: Thunk, - index: usize, - } - impl ThunkValue for BaseThunk { - type Output = Val; - - fn get(self: Box) -> Result { - let full = self.full.evaluate()?; - Ok(full.get(self.index)?.expect("length is checked")) - } - } for (i, d) in start.iter().enumerate() { + let full = full.clone(); destruct( d, - Thunk::new(BaseThunk { - full: full.clone(), - index: i, - }), + Thunk!(move || Ok(full.evaluate()?.get(i)?.expect("length is checked"))), fctx.clone(), new_bindings, )?; @@ -98,32 +65,19 @@ match rest { Some(DestructRest::Keep(v)) => { - #[derive(Trace)] - struct RestThunk { - full: Thunk, - start: usize, - end: usize, - } - impl ThunkValue for RestThunk { - type Output = Val; - - fn get(self: Box) -> Result { - let full = self.full.evaluate()?; - let to = full.len() - self.end; + 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(self.start as i32), + Some(start as i32), Some(to as i32), None, ))) - } - } - - destruct( - &Destruct::Full(v.clone()), - Thunk::new(RestThunk { - full: full.clone(), - start: start.len(), - end: end.len(), }), fctx.clone(), new_bindings, @@ -133,29 +87,14 @@ } { - #[derive(Trace)] - struct EndThunk { - full: Thunk, - index: usize, - end: usize, - } - impl ThunkValue for EndThunk { - type Output = Val; - - fn get(self: Box) -> Result { - let full = self.full.evaluate()?; - Ok(full - .get(full.len() - self.end + self.index)? - .expect("length is checked")) - } - } for (i, d) in end.iter().enumerate() { + let full = full.clone(); + let end = end.len(); destruct( d, - Thunk::new(EndThunk { - full: full.clone(), - index: i, - end: end.len(), + Thunk!(move || { + let full = full.evaluate()?; + Ok(full.get(full.len() - end + i)?.expect("length is checked")) }), fctx.clone(), new_bindings, @@ -165,71 +104,46 @@ } #[cfg(feature = "exp-destruct")] Destruct::Object { fields, rest } => { - use crate::obj::ObjValue; - - #[derive(Trace)] - struct DataThunk { - parent: Thunk, - field_names: Vec<(IStr, bool)>, - has_rest: bool, - } - impl ThunkValue for DataThunk { - type Output = ObjValue; - - fn get(self: Box) -> Result { - let v = self.parent.evaluate()?; - let Val::Obj(obj) = v else { - bail!("expected object"); - }; - for (field, has_default) in &self.field_names { - if !has_default && !obj.has_field_ex(field.clone(), true) { - bail!("missing field: {field}"); - } - } - if !self.has_rest { - let len = obj.len(); - if len > self.field_names.len() { - bail!("too many fields, and rest not found"); - } - } - Ok(obj) - } - } let field_names: Vec<_> = fields .iter() .map(|f| (f.0.clone(), f.2.is_some())) .collect(); - let full = Thunk::new(DataThunk { - parent, - field_names, - has_rest: rest.is_some(), + 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) }); for (field, d, default) in fields { - #[derive(Trace)] - struct FieldThunk { - full: Thunk, - field: IStr, - default: Option<(Pending, LocExpr)>, - } - impl ThunkValue for FieldThunk { - type Output = Val; - - fn get(self: Box) -> Result { - let full = self.full.evaluate()?; - if let Some(field) = full.get(self.field)? { + 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) = self.default.as_ref().expect("shape is checked"); + let (fctx, expr) = default.as_ref().expect("shape is checked"); Ok(evaluate(fctx.clone().unwrap(), expr)?) } - } - } - let value = Thunk::new(FieldThunk { - full: full.clone(), - field: field.clone(), - default: default.clone().map(|e| (fctx.clone(), e)), - }); + }) + }; + if let Some(d) = d { destruct(d, value, fctx.clone(), new_bindings)?; } else { @@ -253,26 +167,15 @@ ) -> Result<()> { match d { BindSpec::Field { into, value } => { - #[derive(Trace)] - struct EvaluateThunkValue { - name: Option, - fctx: Pending, - expr: LocExpr, - } - impl ThunkValue for EvaluateThunkValue { - type Output = Val; - fn get(self: Box) -> Result { - self.name.map_or_else( - || evaluate(self.fctx.unwrap(), &self.expr), - |name| evaluate_named(self.fctx.unwrap(), &self.expr, name), - ) - } - } - let data = Thunk::new(EvaluateThunkValue { - name: into.name(), - fctx: fctx.clone(), - expr: value.clone(), - }); + let name = into.name(); + let value = value.clone(); + let data = { + let fctx = fctx.clone(); + Thunk!(move || name.map_or_else( + || evaluate(fctx.unwrap(), &value), + |name| evaluate_named(fctx.unwrap(), &value, name), + )) + }; destruct(into, data, fctx, new_bindings)?; } BindSpec::Function { @@ -280,37 +183,15 @@ params, value, } => { - #[derive(Trace)] - struct MethodThunk { - fctx: Pending, - name: IStr, - params: ParamsDesc, - value: LocExpr, - } - impl ThunkValue for MethodThunk { - type Output = Val; - - fn get(self: Box) -> Result { - Ok(evaluate_method( - self.fctx.unwrap(), - self.name, - self.params, - self.value, - )) - } - } - - let old = new_bindings.insert( - name.clone(), - Thunk::new(MethodThunk { - fctx, - name: name.clone(), - params: params.clone(), - value: value.clone(), - }), - ); + let params = params.clone(); + let name = name.clone(); + let value = value.clone(); + let old = new_bindings.insert(name.clone(), { + let name = name.clone(); + Thunk!(move || Ok(evaluate_method(fctx.unwrap(), name, params, value))) + }); if old.is_some() { - bail!(DuplicateLocalVar(name.clone())) + bail!(DuplicateLocalVar(name)) } } } --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -18,7 +18,7 @@ function::{CallLocation, FuncDesc, FuncVal}, in_frame, typed::Typed, - val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk, ThunkValue}, + val::{CachedUnbound, IndexableVal, NumValue, StrValue, Thunk}, Context, Error, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, ResultExt, Unbound, Val, }; @@ -139,29 +139,14 @@ #[cfg(feature = "exp-preserve-order")] false, ) { - #[derive(Trace)] - struct ObjectFieldThunk { - obj: ObjValue, - field: IStr, - } - impl ThunkValue for ObjectFieldThunk { - type Output = Val; - - fn get(self: Box) -> Result { - self.obj.get(self.field).transpose().expect( - "field exists, as field name was obtained from object.fields()", - ) - } - } - let fctx = Pending::new(); let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint()); + let obj = obj.clone(); let value = Thunk::evaluated(Val::Arr(ArrValue::lazy(vec![ Thunk::evaluated(Val::string(field.clone())), - Thunk::new(ObjectFieldThunk { - field: field.clone(), - obj: obj.clone(), - }), + Thunk!(move || obj.get(field).transpose().expect( + "field exists, as field name was obtained from object.fields()", + )), ]))); destruct(var, value, fctx.clone(), &mut new_bindings)?; let ctx = ctx @@ -609,21 +594,8 @@ if items.is_empty() { Val::Arr(ArrValue::empty()) } else if items.len() == 1 { - #[derive(Trace)] - struct ArrayElement { - ctx: Context, - item: LocExpr, - } - impl ThunkValue for ArrayElement { - type Output = Val; - fn get(self: Box) -> Result { - evaluate(self.ctx, &self.item) - } - } - Val::Arr(ArrValue::lazy(vec![Thunk::new(ArrayElement { - ctx, - item: items[0].clone(), - })])) + let item = items[0].clone(); + Val::Arr(ArrValue::lazy(vec![Thunk!(move || evaluate(ctx, &item))])) } else { Val::Arr(ArrValue::expr(ctx, items.iter().cloned())) } @@ -631,21 +603,8 @@ ArrComp(expr, comp_specs) => { let mut out = Vec::new(); evaluate_comp(ctx, comp_specs, &mut |ctx| { - #[derive(Trace)] - struct EvaluateThunk { - ctx: Context, - expr: LocExpr, - } - impl ThunkValue for EvaluateThunk { - type Output = Val; - fn get(self: Box) -> Result { - evaluate(self.ctx, &self.expr) - } - } - out.push(Thunk::new(EvaluateThunk { - ctx, - expr: expr.clone(), - })); + let expr = expr.clone(); + out.push(Thunk!(move || evaluate(ctx, &expr))); Ok(()) })?; Val::Arr(ArrValue::lazy(out)) --- a/crates/jrsonnet-evaluator/src/function/parse.rs +++ b/crates/jrsonnet-evaluator/src/function/parse.rs @@ -90,7 +90,8 @@ let fctx = Context::new_future(); let mut defaults = GcHashMap::with_capacity( params.iter().map(|p| p.0.capacity_hint()).sum::() - - filled_named - filled_positionals, + - filled_named + - filled_positionals, ); for (idx, param) in params.iter().enumerate().filter(|p| p.1 .1.is_some()) { @@ -232,22 +233,6 @@ /// 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: &ParamsDesc) -> Result { - #[derive(Trace)] - struct DependsOnUnbound(IStr, ParamsDesc); - impl ThunkValue for DependsOnUnbound { - type Output = Val; - fn get(self: Box) -> Result { - Err(FunctionParameterNotBoundInCall( - Some(self.0.clone()), - self.1 - .iter() - .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some()))) - .collect(), - ) - .into()) - } - } - let fctx = Context::new_future(); let mut bindings = GcHashMap::with_capacity(params.iter().map(|p| p.0.capacity_hint()).sum()); @@ -267,10 +252,18 @@ } else { destruct( ¶m.0, - Thunk::new(DependsOnUnbound( - param.0.name().unwrap_or_else(|| "".into()), - params.clone(), - )), + { + let param_name = param.0.name().unwrap_or_else(|| "".into()); + let params = params.clone(); + Thunk!(move || Err(FunctionParameterNotBoundInCall( + Some(param_name), + params + .iter() + .map(|p| (p.0.name(), ParamDefault::exists(p.1.is_some()))) + .collect(), + ) + .into())) + }, fctx.clone(), &mut bindings, )?; --- a/crates/jrsonnet-evaluator/src/gc.rs +++ b/crates/jrsonnet-evaluator/src/gc.rs @@ -158,3 +158,5 @@ Self::new() } } + +pub fn assert_trace(_v: &T) {} --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -20,7 +20,7 @@ in_frame, operator::evaluate_add_op, tb, - val::{ArrValue, ThunkValue}, + val::ArrValue, MaybeUnbound, Result, Thunk, Unbound, Val, }; @@ -444,45 +444,16 @@ }) } pub fn get_lazy(&self, key: IStr) -> Option> { - #[derive(Trace)] - struct ThunkGet { - obj: ObjValue, - key: IStr, - } - impl ThunkValue for ThunkGet { - type Output = Val; - - fn get(self: Box) -> Result { - Ok(self.obj.get(self.key)?.expect("field exists")) - } - } - if !self.has_field_ex(key.clone(), true) { return None; } - Some(Thunk::new(ThunkGet { - obj: self.clone(), - key, - })) + let obj = self.clone(); + + Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists")))) } pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk { - #[derive(Trace)] - struct ThunkGet { - obj: ObjValue, - key: IStr, - } - impl ThunkValue for ThunkGet { - type Output = Val; - - fn get(self: Box) -> Result { - self.obj.get_or_bail(self.key) - } - } - - Thunk::new(ThunkGet { - obj: self.clone(), - key, - }) + let obj = self.clone(); + Thunk!(move || obj.get_or_bail(key)) } pub fn ptr_eq(a: &Self, b: &Self) -> bool { Cc::ptr_eq(&a.0, &b.0) @@ -733,11 +704,10 @@ self.value_cache .borrow_mut() .insert(cache_key.clone(), CacheValue::Pending); - let value = self.get_for_uncached(key, this).map_err(|e| { + let value = self.get_for_uncached(key, this).inspect_err(|e| { self.value_cache .borrow_mut() .insert(cache_key.clone(), CacheValue::Errored(e.clone())); - e })?; self.value_cache.borrow_mut().insert( cache_key, --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -11,6 +11,7 @@ use derivative::Derivative; use jrsonnet_gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; +pub use jrsonnet_macros::Thunk; use jrsonnet_types::ValType; use thiserror::Error; @@ -32,6 +33,27 @@ } #[derive(Trace)] +pub struct ThunkValueClosure { + env: D, + // Carries no data, as it is not a real closure, all the + // captured environment is stored in `env` field. + #[trace(skip)] + closure: fn(D) -> Result, +} +impl ThunkValueClosure { + pub fn new(env: D, closure: fn(D) -> Result) -> Self { + Self { env, closure } + } +} +impl ThunkValue for ThunkValueClosure { + type Output = O; + + fn get(self: Box) -> Result { + (self.closure)(self.env) + } +} + +#[derive(Trace)] enum ThunkInner { Computed(T), Errored(Error), @@ -113,28 +135,11 @@ M: ThunkMapper, M::Output: Trace, { - #[derive(Trace)] - struct Mapped { - inner: Thunk, - mapper: Mapper, - } - impl ThunkValue for Mapped - where - Input: Trace + Clone, - Mapper: ThunkMapper, - { - type Output = Mapper::Output; - - fn get(self: Box) -> Result { - let value = self.inner.evaluate()?; - let mapped = self.mapper.map(value)?; - Ok(mapped) - } - } - - Thunk::new(Mapped:: { - inner: self, - mapper, + let inner = self; + Thunk!(move || { + let value = inner.evaluate()?; + let mapped = mapper.map(value)?; + Ok(mapped) }) } } --- a/crates/jrsonnet-macros/Cargo.toml +++ b/crates/jrsonnet-macros/Cargo.toml @@ -17,3 +17,4 @@ proc-macro2.workspace = true quote.workspace = true syn = { workspace = true, features = ["full"] } +syn-dissect-closure.workspace = true --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -1,7 +1,7 @@ use std::string::String; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, quote_spanned}; use syn::{ parenthesized, parse::{Parse, ParseStream}, @@ -9,8 +9,8 @@ punctuated::Punctuated, spanned::Spanned, token::{self, Comma}, - Attribute, DeriveInput, Error, Expr, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path, - PathArguments, Result, ReturnType, Token, Type, + Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn, + LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type, }; fn parse_attr(attrs: &[Attribute], ident: I) -> Result> @@ -815,3 +815,30 @@ let input = parse_macro_input!(input as FormatInput); input.expand().into() } + +/// Create Thunk using closure syntax +#[proc_macro] +#[allow(non_snake_case)] +pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as ExprClosure); + + let span = input.inputs.span(); + let move_check = input.capture.is_none().then(|| { + quote_spanned! {span => { + compile_error!("Thunk! needs to be called with move closure"); + }} + }); + + let (env, closure, args) = syn_dissect_closure::split_env(input); + + let trace_check = args.iter().map(|el| { + let span = el.span(); + quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);} + }); + + quote! {{ + #move_check + #(#trace_check)* + ::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure)) + }}.into() +} -- gitstuff