difftreelog
refactor turn Thunk structure around to reduce allocations
in: master
9 files changed
.gitignorediffbeforeafterboth--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,10 @@
.vscode
.direnv
+# Nix artifacts
+/result
+/result-*
+
cache
jsonnet-cpp
crates/jrsonnet-evaluator/src/arr/spec.rsdiffbeforeafterboth--- 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,
- Context, Error, ObjValue, Result, Thunk, Val,
+ val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val,
};
pub trait ArrayLike: Any + Trace + Debug {
@@ -191,9 +191,25 @@
ArrayThunk::Waiting(_) | ArrayThunk::Pending => {}
};
- let arr_thunk = self.clone();
- Some(Thunk!(move || {
- arr_thunk.get(index).transpose().expect("index checked")
+ #[derive(Trace)]
+ struct ExprArrThunk {
+ expr: ExprArray,
+ index: usize,
+ }
+ impl ThunkValue for ExprArrThunk {
+ type Output = Val;
+
+ fn get(&self) -> Result<Self::Output> {
+ self.expr
+ .get(self.index)
+ .transpose()
+ .expect("index checked")
+ }
+ }
+
+ Some(Thunk::new(ExprArrThunk {
+ expr: self.clone(),
+ index,
}))
}
fn get_cheap(&self, _index: usize) -> Option<Val> {
@@ -484,9 +500,22 @@
ArrayThunk::Waiting(()) | ArrayThunk::Pending => {}
};
- let arr_thunk = self.clone();
- Some(Thunk!(move || {
- arr_thunk.get(index).transpose().expect("index checked")
+ #[derive(Trace)]
+ struct MappedArrayThunk<const WITH_INDEX: bool> {
+ arr: MappedArray<WITH_INDEX>,
+ index: usize,
+ }
+ impl<const WITH_INDEX: bool> ThunkValue for MappedArrayThunk<WITH_INDEX> {
+ type Output = Val;
+
+ fn get(&self) -> Result<Self::Output> {
+ self.arr.get(self.index).transpose().expect("index checked")
+ }
+ }
+
+ Some(Thunk::new(MappedArrayThunk {
+ arr: self.clone(),
+ index,
}))
}
crates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -40,8 +40,9 @@
impl<T: Trace + Clone> ThunkValue for Pending<T> {
type Output = T;
- fn get(self: Box<Self>) -> Result<Self::Output> {
+ fn get(&self) -> Result<Self::Output> {
let Some(value) = self.0.get() else {
+ // TODO: Other error?
bail!(InfiniteRecursionDetected);
};
Ok(value.clone())
crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/obj.rs
+++ b/crates/jrsonnet-evaluator/src/obj.rs
@@ -23,7 +23,7 @@
gc::WithCapacityExt as _,
identity_hash, in_frame,
operator::evaluate_add_op,
- val::ArrValue,
+ val::{ArrValue, ThunkValue},
CcUnbound, MaybeUnbound, Result, Thunk, Unbound, Val,
};
@@ -753,13 +753,45 @@
if !self.has_field_ex(key.clone(), true) {
return None;
}
- let obj = self.clone();
+ #[derive(Trace)]
+ struct ObjFieldThunk {
+ obj: ObjValue,
+ key: IStr,
+ }
+ impl ThunkValue for ObjFieldThunk {
+ type Output = Val;
- Some(Thunk!(move || Ok(obj.get(key)?.expect("field exists"))))
+ fn get(&self) -> Result<Self::Output> {
+ self.obj
+ .get(self.key.clone())
+ .transpose()
+ .expect("field existence checked")
+ }
+ }
+
+ Some(Thunk::new(ObjFieldThunk {
+ obj: self.clone(),
+ key,
+ }))
}
pub fn get_lazy_or_bail(&self, key: IStr) -> Thunk<Val> {
- let obj = self.clone();
- Thunk!(move || obj.get_or_bail(key))
+ #[derive(Trace)]
+ struct ObjFieldThunk {
+ obj: ObjValue,
+ key: IStr,
+ }
+ impl ThunkValue for ObjFieldThunk {
+ type Output = Val;
+
+ fn get(&self) -> Result<Self::Output> {
+ self.obj.get_or_bail(self.key.clone())
+ }
+ }
+
+ Thunk::new(ObjFieldThunk {
+ obj: self.clone(),
+ key,
+ })
}
pub fn ptr_eq(a: &Self, b: &Self) -> bool {
Cc::ptr_eq(&a.0, &b.0)
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -2,13 +2,14 @@
cell::RefCell,
cmp::Ordering,
fmt::{self, Debug, Display},
+ marker::PhantomData,
mem::replace,
num::NonZeroU32,
ops::Deref,
rc::Rc,
};
-use jrsonnet_gcmodule::{Acyclic, Cc, Trace, TraceBox};
+use jrsonnet_gcmodule::{cc_dyn, Acyclic, Cc, Trace};
use jrsonnet_interner::IStr;
pub use jrsonnet_macros::Thunk;
use jrsonnet_types::ValType;
@@ -28,56 +29,106 @@
pub trait ThunkValue: Trace {
type Output;
- fn get(self: Box<Self>) -> Result<Self::Output>;
+ fn get(&self) -> Result<Self::Output>;
}
#[derive(Trace)]
-pub struct ThunkValueClosure<D: Trace, O: 'static> {
- 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<O>,
+enum MemoizedClusureThunkInner<D: Trace, T: Trace> {
+ Computed(T),
+ Errored(Error),
+ Waiting {
+ 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<T>,
+ },
+ Pending,
}
-impl<D: Trace, O: 'static> ThunkValueClosure<D, O> {
- pub fn new(env: D, closure: fn(D) -> Result<O>) -> Self {
- Self { env, closure }
+#[derive(Trace)]
+pub struct MemoizedClosureThunk<D: Trace, T: Trace>(RefCell<MemoizedClusureThunkInner<D, T>>);
+impl<D: Trace, T: Trace> MemoizedClosureThunk<D, T> {
+ pub fn new(env: D, closure: fn(D) -> Result<T>) -> Self {
+ Self(RefCell::new(MemoizedClusureThunkInner::Waiting {
+ env,
+ closure,
+ }))
}
}
-impl<D: Trace, O: 'static> ThunkValue for ThunkValueClosure<D, O> {
- type Output = O;
- fn get(self: Box<Self>) -> Result<Self::Output> {
- (self.closure)(self.env)
- }
-}
+impl<D: Trace, T: Trace + Clone> ThunkValue for MemoizedClosureThunk<D, T> {
+ type Output = T;
-#[derive(Trace)]
-enum ThunkInner<T: Trace> {
- Computed(T),
- Errored(Error),
- Waiting(TraceBox<dyn ThunkValue<Output = T>>),
- Pending,
+ fn get(&self) -> Result<Self::Output> {
+ match &*self.0.borrow() {
+ MemoizedClusureThunkInner::Computed(v) => return Ok(v.clone()),
+ MemoizedClusureThunkInner::Errored(e) => return Err(e.clone()),
+ MemoizedClusureThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),
+ MemoizedClusureThunkInner::Waiting { .. } => (),
+ };
+ let MemoizedClusureThunkInner::Waiting { env, closure } = replace(
+ &mut *self.0.borrow_mut(),
+ MemoizedClusureThunkInner::Pending,
+ ) else {
+ unreachable!();
+ };
+ let new_value = match closure(env) {
+ Ok(v) => v,
+ Err(e) => {
+ *self.0.borrow_mut() = MemoizedClusureThunkInner::Errored(e.clone());
+ return Err(e);
+ }
+ };
+ *self.0.borrow_mut() = MemoizedClusureThunkInner::Computed(new_value.clone());
+ Ok(new_value)
+ }
}
-/// Lazily evaluated value
-#[allow(clippy::module_name_repetitions)]
-#[derive(Clone, Trace)]
-pub struct Thunk<T: Trace>(Cc<RefCell<ThunkInner<T>>>);
+cc_dyn!(
+ /// Lazily evaluated value
+ #[derive(Clone)] Thunk<V: Trace>,
+ ThunkValue<Output = V>,
+ pub fn new() {...}
+);
impl<T: Trace> Thunk<T> {
- pub fn evaluated(val: T) -> Self {
- Self(Cc::new(RefCell::new(ThunkInner::Computed(val))))
- }
- pub fn new(f: impl ThunkValue<Output = T> + 'static) -> Self {
- Self(Cc::new(RefCell::new(ThunkInner::Waiting(TraceBox(
- Box::new(f),
- )))))
+ pub fn evaluated(val: T) -> Self
+ where
+ T: Clone,
+ {
+ #[derive(Trace)]
+ struct EvaluatedThunk<T: Trace>(T);
+ impl<T> ThunkValue for EvaluatedThunk<T>
+ where
+ T: Clone + Trace,
+ {
+ type Output = T;
+
+ fn get(&self) -> Result<Self::Output> {
+ Ok(self.0.clone())
+ }
+ }
+ Self::new(EvaluatedThunk(val))
}
pub fn errored(e: Error) -> Self {
- Self(Cc::new(RefCell::new(ThunkInner::Errored(e))))
+ #[derive(Trace)]
+ struct ErroredThunk<T: Trace>(Error, PhantomData<T>);
+ impl<T> ThunkValue for ErroredThunk<T>
+ where
+ T: Trace,
+ {
+ type Output = T;
+
+ fn get(&self) -> Result<Self::Output> {
+ Err(self.0.clone())
+ }
+ }
+ Self::new(ErroredThunk(e, PhantomData))
}
- pub fn result(res: Result<T, Error>) -> Self {
+ pub fn result(res: Result<T, Error>) -> Self
+ where
+ T: Clone,
+ {
match res {
Ok(o) => Self::evaluated(o),
Err(e) => Self::errored(e),
@@ -101,25 +152,7 @@
/// - Lazy value evaluation returned error
/// - This method was called during inner value evaluation
pub fn evaluate(&self) -> Result<T> {
- match &*self.0.borrow() {
- ThunkInner::Computed(v) => return Ok(v.clone()),
- ThunkInner::Errored(e) => return Err(e.clone()),
- ThunkInner::Pending => return Err(InfiniteRecursionDetected.into()),
- ThunkInner::Waiting(..) => (),
- };
- let ThunkInner::Waiting(value) = replace(&mut *self.0.borrow_mut(), ThunkInner::Pending)
- else {
- unreachable!();
- };
- let new_value = match value.0.get() {
- Ok(v) => v,
- Err(e) => {
- *self.0.borrow_mut() = ThunkInner::Errored(e.clone());
- return Err(e);
- }
- };
- *self.0.borrow_mut() = ThunkInner::Computed(new_value.clone());
- Ok(new_value)
+ self.0.get()
}
}
@@ -134,7 +167,7 @@
pub fn map<M>(self, mapper: M) -> Thunk<M::Output>
where
M: ThunkMapper<Input>,
- M::Output: Trace,
+ M::Output: Trace + Clone,
{
let inner = self;
Thunk!(move || {
@@ -145,7 +178,7 @@
}
}
-impl<T: Trace> From<Result<T>> for Thunk<T> {
+impl<T: Trace + Clone> From<Result<T>> for Thunk<T> {
fn from(value: Result<T>) -> Self {
match value {
Ok(o) => Self::evaluated(o),
@@ -162,7 +195,7 @@
}
}
-impl<T: Trace + Default> Default for Thunk<T> {
+impl<T: Trace + Default + Clone> Default for Thunk<T> {
fn default() -> Self {
Self::evaluated(T::default())
}
crates/jrsonnet-formatter/src/comments.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/comments.rs
+++ b/crates/jrsonnet-formatter/src/comments.rs
@@ -73,7 +73,10 @@
p!(out, str(" "));
}
p!(out, str("/* ") string(lines[0].trim().to_string()) str(" */"));
- if matches!(loc, CommentLocation::AboveItem | CommentLocation::EndOfItems) {
+ if matches!(
+ loc,
+ CommentLocation::AboveItem | CommentLocation::EndOfItems
+ ) {
p!(out, nl);
}
} else if !lines.is_empty() {
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth1use std::string::String;23use proc_macro2::TokenStream;4use quote::{quote, quote_spanned};5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::{self, Comma},12 Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,13 LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18 Ident: PartialEq<I>,19{20 let attrs = attrs21 .iter()22 .filter(|a| a.path().is_ident(&ident))23 .collect::<Vec<_>>();24 if attrs.len() > 1 {25 return Err(Error::new(26 attrs[1].span(),27 "this attribute may be specified only once",28 ));29 } else if attrs.is_empty() {30 return Ok(None);31 }32 let attr = attrs[0];33 let attr = attr.parse_args::<A>()?;3435 Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39 Ident: PartialEq<I>,40{41 attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45 path.leading_colon.is_none()46 && !path.segments.is_empty()47 && path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51 match ty {52 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53 let args = &path.path.segments.iter().last().unwrap().arguments;54 Some(args)55 }56 _ => None,57 }58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61 let Some(args) = type_is_path(ty, "Option") else {62 return Ok(None);63 };64 // It should have only on angle-bracketed param ("<String>"):65 let PathArguments::AngleBracketed(params) = args else {66 return Err(Error::new(args.span(), "missing option generic"));67 };68 let generic_arg = params.args.iter().next().unwrap();69 // This argument must be a type:70 let GenericArgument::Type(ty) = generic_arg else {71 return Err(Error::new(72 generic_arg.span(),73 "option generic should be a type",74 ));75 };76 Ok(Some(ty))77}7879struct Field {80 attrs: Vec<Attribute>,81 name: Ident,82 _colon: Token![:],83 ty: Type,84}85impl Parse for Field {86 fn parse(input: ParseStream) -> syn::Result<Self> {87 Ok(Self {88 attrs: input.call(Attribute::parse_outer)?,89 name: input.parse()?,90 _colon: input.parse()?,91 ty: input.parse()?,92 })93 }94}9596mod kw {97 syn::custom_keyword!(fields);98 syn::custom_keyword!(rename);99 syn::custom_keyword!(alias);100 syn::custom_keyword!(flatten);101 syn::custom_keyword!(add);102 syn::custom_keyword!(hide);103 syn::custom_keyword!(ok);104}105106struct BuiltinAttrs {107 fields: Vec<Field>,108}109impl Parse for BuiltinAttrs {110 fn parse(input: ParseStream) -> syn::Result<Self> {111 if input.is_empty() {112 return Ok(Self { fields: Vec::new() });113 }114 input.parse::<kw::fields>()?;115 let fields;116 parenthesized!(fields in input);117 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;118 Ok(Self {119 fields: p.into_iter().collect(),120 })121 }122}123124enum Optionality {125 Required,126 Optional,127 Default(Expr),128}129130enum ArgInfo {131 Normal {132 ty: Box<Type>,133 optionality: Optionality,134 name: Option<String>,135 cfg_attrs: Vec<Attribute>,136 },137 Lazy {138 is_option: bool,139 name: Option<String>,140 },141 Context,142 Location,143 This,144}145146impl ArgInfo {147 fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {148 let FnArg::Typed(arg) = arg else {149 unreachable!()150 };151 let ident = match &arg.pat as &Pat {152 Pat::Ident(i) => Some(i.ident.clone()),153 _ => None,154 };155 let ty = &arg.ty;156 if type_is_path(ty, "Context").is_some() {157 return Ok(Self::Context);158 } else if type_is_path(ty, "CallLocation").is_some() {159 return Ok(Self::Location);160 } else if type_is_path(ty, "Thunk").is_some() {161 return Ok(Self::Lazy {162 is_option: false,163 name: ident.map(|v| v.to_string()),164 });165 }166167 match ty as &Type {168 Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),169 _ => {}170 }171172 let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {173 remove_attr(&mut arg.attrs, "default");174 (Optionality::Default(default), ty.clone())175 } else if let Some(ty) = extract_type_from_option(ty)? {176 if type_is_path(ty, "Thunk").is_some() {177 return Ok(Self::Lazy {178 is_option: true,179 name: ident.map(|v| v.to_string()),180 });181 }182183 (Optionality::Optional, Box::new(ty.clone()))184 } else {185 (Optionality::Required, ty.clone())186 };187188 let cfg_attrs = arg189 .attrs190 .iter()191 .filter(|a| a.path().is_ident("cfg"))192 .cloned()193 .collect();194195 Ok(Self::Normal {196 ty,197 optionality,198 name: ident.map(|v| v.to_string()),199 cfg_attrs,200 })201 }202}203204#[proc_macro_attribute]205pub fn builtin(206 attr: proc_macro::TokenStream,207 item: proc_macro::TokenStream,208) -> proc_macro::TokenStream {209 let attr = parse_macro_input!(attr as BuiltinAttrs);210 let item_fn = parse_macro_input!(item as ItemFn);211212 match builtin_inner(attr, item_fn) {213 Ok(v) => v.into(),214 Err(e) => e.into_compile_error().into(),215 }216}217218#[allow(clippy::too_many_lines)]219fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {220 let ReturnType::Type(_, result) = &fun.sig.output else {221 return Err(Error::new(222 fun.sig.span(),223 "builtin should return something",224 ));225 };226227 let name = fun.sig.ident.to_string();228 let args = fun229 .sig230 .inputs231 .iter_mut()232 .map(|arg| ArgInfo::parse(&name, arg))233 .collect::<Result<Vec<_>>>()?;234235 let params_desc = args.iter().filter_map(|a| match a {236 ArgInfo::Normal {237 optionality,238 name,239 cfg_attrs,240 ..241 } => {242 let name = name243 .as_ref()244 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});245 let default = match optionality {246 Optionality::Required => quote!(ParamDefault::None),247 Optionality::Optional => quote!(ParamDefault::Exists),248 Optionality::Default(e) => quote!(ParamDefault::Literal(stringify!(#e))),249 };250 Some(quote! {251 #(#cfg_attrs)*252 BuiltinParam::new(#name, #default),253 })254 }255 ArgInfo::Lazy { is_option, name } => {256 let name = name257 .as_ref()258 .map_or_else(|| quote! {None}, |n| quote! {ParamName::new_static(#n)});259 Some(quote! {260 BuiltinParam::new(#name, ParamDefault::exists(#is_option)),261 })262 }263 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,264 });265266 let mut id = 0usize;267 let pass = args268 .iter()269 .map(|a| match a {270 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {271 let cid = id;272 id += 1;273 (quote! {#cid}, a)274 }275 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {276 (quote! {compile_error!("should not use id")}, a)277 }278 })279 .map(|(id, a)| match a {280 ArgInfo::Normal {281 ty,282 optionality,283 name,284 cfg_attrs,285 } => {286 let name = name.as_ref().map_or("<unnamed>", String::as_str);287 let eval = quote! {jrsonnet_evaluator::in_description_frame(288 || format!("argument <{}> evaluation", #name),289 || <#ty>::from_untyped(value.evaluate()?),290 )?};291 let value = match optionality {292 Optionality::Required => quote! {{293 let value = parsed[#id].as_ref().expect("args shape is checked");294 #eval295 },},296 Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {297 Some(#eval)298 } else {299 None300 },},301 Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {302 #eval303 } else {304 let v: #ty = #expr;305 v306 },},307 };308 quote! {309 #(#cfg_attrs)*310 #value311 }312 }313 ArgInfo::Lazy { is_option, .. } => {314 if *is_option {315 quote! {if let Some(value) = &parsed[#id] {316 Some(value.clone())317 } else {318 None319 },}320 } else {321 quote! {322 parsed[#id].as_ref().expect("args shape is correct").clone(),323 }324 }325 }326 ArgInfo::Context => quote! {ctx.clone(),},327 ArgInfo::Location => quote! {location,},328 ArgInfo::This => quote! {self,},329 });330331 let fields = attr.fields.iter().map(|field| {332 let attrs = &field.attrs;333 let name = &field.name;334 let ty = &field.ty;335 quote! {336 #(#attrs)*337 pub #name: #ty,338 }339 });340341 let name = &fun.sig.ident;342 let vis = &fun.vis;343 let static_ext = if attr.fields.is_empty() {344 quote! {345 impl #name {346 pub const INST: &'static dyn StaticBuiltin = &#name {};347 }348 impl StaticBuiltin for #name {}349 }350 } else {351 quote! {}352 };353 let static_derive_copy = if attr.fields.is_empty() {354 quote! {, Copy}355 } else {356 quote! {}357 };358359 Ok(quote! {360 #fun361362 #[doc(hidden)]363 #[allow(non_camel_case_types)]364 #[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]365 #vis struct #name {366 #(#fields)*367 }368 const _: () = {369 use ::jrsonnet_evaluator::{370 State, Val,371 function::{builtin::{Builtin, StaticBuiltin, BuiltinParam, ParamName, ParamDefault}, CallLocation, ArgsLike, parse::parse_builtin_call},372 Result, Context, typed::Typed,373 parser::Span,374 };375 const PARAMS: &'static [BuiltinParam] = &[376 #(#params_desc)*377 ];378379 #static_ext380 impl Builtin for #name381 where382 Self: 'static383 {384 fn name(&self) -> &str {385 stringify!(#name)386 }387 fn params(&self) -> &[BuiltinParam] {388 PARAMS389 }390 #[allow(unused_variables)]391 fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {392 let parsed = parse_builtin_call(ctx.clone(), &PARAMS, args, false)?;393394 let result: #result = #name(#(#pass)*);395 <_ as Typed>::into_result(result)396 }397 fn as_any(&self) -> &dyn ::std::any::Any {398 self399 }400 }401 };402 })403}404405#[derive(Default)]406#[allow(clippy::struct_excessive_bools)]407struct TypedAttr {408 rename: Option<String>,409 aliases: Vec<String>,410 flatten: bool,411 /// flatten(ok) strategy for flattened optionals412 /// field would be None in case of any parsing error (as in serde)413 flatten_ok: bool,414 // Should it be `field+:` instead of `field:`415 add: bool,416 // Should it be `field::` instead of `field:`417 hide: bool,418}419impl Parse for TypedAttr {420 fn parse(input: ParseStream) -> syn::Result<Self> {421 let mut out = Self::default();422 loop {423 let lookahead = input.lookahead1();424 if lookahead.peek(kw::rename) {425 input.parse::<kw::rename>()?;426 input.parse::<Token![=]>()?;427 let name = input.parse::<LitStr>()?;428 if out.rename.is_some() {429 return Err(Error::new(430 name.span(),431 "rename attribute may only be specified once",432 ));433 }434 out.rename = Some(name.value());435 } else if lookahead.peek(kw::alias) {436 input.parse::<kw::alias>()?;437 input.parse::<Token![=]>()?;438 let alias = input.parse::<LitStr>()?;439 out.aliases.push(alias.value());440 } else if lookahead.peek(kw::flatten) {441 input.parse::<kw::flatten>()?;442 out.flatten = true;443 if input.peek(token::Paren) {444 let content;445 parenthesized!(content in input);446 let lookahead = content.lookahead1();447 if lookahead.peek(kw::ok) {448 content.parse::<kw::ok>()?;449 out.flatten_ok = true;450 } else {451 return Err(lookahead.error());452 }453 }454 } else if lookahead.peek(kw::add) {455 input.parse::<kw::add>()?;456 out.add = true;457 } else if lookahead.peek(kw::hide) {458 input.parse::<kw::hide>()?;459 out.hide = true;460 } else if input.is_empty() {461 break;462 } else {463 return Err(lookahead.error());464 }465 if input.peek(Token![,]) {466 input.parse::<Token![,]>()?;467 } else {468 break;469 }470 }471 Ok(out)472 }473}474475struct TypedField {476 attr: TypedAttr,477 ident: Ident,478 ty: Type,479 is_option: bool,480 is_lazy: bool,481}482impl TypedField {483 fn parse(field: &syn::Field) -> Result<Self> {484 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();485 let Some(ident) = field.ident.clone() else {486 return Err(Error::new(487 field.span(),488 "this field should appear in output object, but it has no visible name",489 ));490 };491 let (is_option, ty) = extract_type_from_option(&field.ty)?492 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));493 if is_option && attr.flatten {494 if !attr.flatten_ok {495 return Err(Error::new(496 field.span(),497 "strategy should be set when flattening Option",498 ));499 }500 } else if attr.flatten_ok {501 return Err(Error::new(502 field.span(),503 "flatten(ok) is only useable on optional fields",504 ));505 }506507 let is_lazy = type_is_path(&ty, "Thunk").is_some();508509 Ok(Self {510 attr,511 ident,512 ty,513 is_option,514 is_lazy,515 })516 }517 /// None if this field is flattened in jsonnet output518 fn name(&self) -> Option<String> {519 if self.attr.flatten {520 return None;521 }522 Some(523 self.attr524 .rename525 .clone()526 .unwrap_or_else(|| self.ident.to_string()),527 )528 }529530 fn expand_field(&self) -> Option<TokenStream> {531 if self.is_option {532 return None;533 }534 let name = self.name()?;535 let ty = &self.ty;536 Some(quote! {537 (#name, <#ty as Typed>::TYPE)538 })539 }540541 fn expand_parse(&self) -> TokenStream {542 if self.is_option {543 self.expand_parse_optional()544 } else {545 self.expand_parse_mandatory()546 }547 }548549 fn expand_parse_optional(&self) -> TokenStream {550 let ident = &self.ident;551 let ty = &self.ty;552553 // optional flatten is handled in same way as serde554 if self.attr.flatten {555 return quote! {556 #ident: <#ty as TypedObj>::parse(&obj).ok(),557 };558 }559560 let name = self.name().unwrap();561 let aliases = &self.attr.aliases;562563 quote! {564 #ident: {565 let __value = if let Some(__v) = obj.get(#name.into())? {566 Some(__v)567 } #(else if let Some(__v) = obj.get(#aliases.into())? {568 Some(__v)569 })* else {570 None571 };572573 __value.map(<#ty as Typed>::from_untyped).transpose()?574 },575 }576 }577578 fn expand_parse_mandatory(&self) -> TokenStream {579 let ident = &self.ident;580 let ty = &self.ty;581582 // optional flatten is handled in same way as serde583 if self.attr.flatten {584 return quote! {585 #ident: <#ty as TypedObj>::parse(&obj)?,586 };587 }588589 let name = self.name().unwrap();590 let aliases = &self.attr.aliases;591592 let error_text = if aliases.is_empty() {593 // clippy does not understand name variable usage in quote! macro594 #[allow(clippy::redundant_clone)]595 name.clone()596 } else {597 format!("{name} (alias {})", aliases.join(", "))598 };599600 quote! {601 #ident: {602 let __value = if let Some(__v) = obj.get(#name.into())? {603 __v604 } #(else if let Some(__v) = obj.get(#aliases.into())? {605 __v606 })* else {607 return Err(ErrorKind::NoSuchField(#error_text.into(), vec![]).into());608 };609610 <#ty as Typed>::from_untyped(__value)?611 },612 }613 }614615 fn expand_serialize(&self) -> TokenStream {616 let ident = &self.ident;617 let ty = &self.ty;618 self.name().map_or_else(619 || {620 if self.is_option {621 quote! {622 if let Some(value) = self.#ident {623 <#ty as TypedObj>::serialize(value, out)?;624 }625 }626 } else {627 quote! {628 <#ty as TypedObj>::serialize(self.#ident, out)?;629 }630 }631 },632 |name| {633 let hide = if self.attr.hide {634 quote! {.hide()}635 } else {636 quote! {}637 };638 let add = if self.attr.add {639 quote! {.add()}640 } else {641 quote! {}642 };643 let value = if self.is_lazy {644 quote! {645 out.field(#name)646 #hide647 #add648 .try_thunk(<#ty as Typed>::into_lazy_untyped(value))?;649 }650 } else {651 quote! {652 out.field(#name)653 #hide654 #add655 .try_value(<#ty as Typed>::into_untyped(value)?)?;656 }657 };658 if self.is_option {659 quote! {660 if let Some(value) = self.#ident {661 #value662 }663 }664 } else {665 quote! {666 {667 let value = self.#ident;668 #value669 }670 }671 }672 },673 )674 }675}676677#[proc_macro_derive(Typed, attributes(typed))]678pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {679 let input = parse_macro_input!(item as DeriveInput);680681 match derive_typed_inner(input) {682 Ok(v) => v.into(),683 Err(e) => e.to_compile_error().into(),684 }685}686687fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {688 let syn::Data::Struct(data) = &input.data else {689 return Err(Error::new(input.span(), "only structs supported"));690 };691692 let ident = &input.ident;693 let fields = data694 .fields695 .iter()696 .map(TypedField::parse)697 .collect::<Result<Vec<_>>>()?;698699 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();700701 let typed = {702 let fields = fields703 .iter()704 .filter_map(TypedField::expand_field)705 .collect::<Vec<_>>();706 quote! {707 impl #impl_generics Typed for #ident #ty_generics #where_clause {708 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[709 #(#fields,)*710 ]);711712 fn from_untyped(value: Val) -> JrResult<Self> {713 let obj = value.as_obj().expect("shape is correct");714 Self::parse(&obj)715 }716717 fn into_untyped(value: Self) -> JrResult<Val> {718 let mut out = ObjValueBuilder::new();719 value.serialize(&mut out)?;720 Ok(Val::Obj(out.build()))721 }722723 }724 }725 };726727 let fields_parse = fields.iter().map(TypedField::expand_parse);728 let fields_serialize = fields729 .iter()730 .map(TypedField::expand_serialize)731 .collect::<Vec<_>>();732733 Ok(quote! {734 const _: () = {735 use ::jrsonnet_evaluator::{736 typed::{ComplexValType, Typed, TypedObj, CheckType},737 Val, State,738 error::{ErrorKind, Result as JrResult},739 ObjValueBuilder, ObjValue,740 };741742 #typed743744 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {745 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {746 #(#fields_serialize)*747748 Ok(())749 }750 fn parse(obj: &ObjValue) -> JrResult<Self> {751 Ok(Self {752 #(#fields_parse)*753 })754 }755 }756 };757 })758}759760struct FormatInput {761 formatting: LitStr,762 arguments: Vec<Expr>,763}764impl Parse for FormatInput {765 fn parse(input: ParseStream) -> Result<Self> {766 let formatting = input.parse()?;767 let mut arguments = Vec::new();768769 while input.peek(Token![,]) {770 input.parse::<Token![,]>()?;771 if input.is_empty() {772 // Trailing comma773 break;774 }775 let expr = input.parse()?;776 arguments.push(expr);777 }778779 if !input.is_empty() {780 return Err(syn::Error::new(input.span(), "unexpected trailing input"));781 }782783 Ok(Self {784 formatting,785 arguments,786 })787 }788}789fn is_format_str(i: &str) -> bool {790 let mut is_plain = true;791 // -1 = {792 // +1 = }793 let mut is_bracket = 0i8;794 for ele in i.chars() {795 match ele {796 '{' if is_bracket == -1 => {797 is_bracket = 0;798 }799 '}' if is_bracket == -1 => {800 is_plain = false;801 break;802 }803 '}' if is_bracket == 1 => {804 is_bracket = 0;805 }806 '{' if is_bracket == 1 => {807 is_plain = false;808 break;809 }810 '{' => {811 is_bracket = -1;812 }813 '}' => {814 is_bracket = 1;815 }816 _ if is_bracket != 0 => {817 is_plain = false;818 break;819 }820 _ => {}821 }822 }823 !is_plain || is_bracket != 0824}825impl FormatInput {826 fn expand(self) -> TokenStream {827 let format = self.formatting;828 if is_format_str(&format.value()) {829 let args = self.arguments;830 quote! {831 ::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))832 }833 } else {834 if let Some(first) = self.arguments.first() {835 return syn::Error::new(836 first.span(),837 "string has no formatting codes, it should not have the arguments",838 )839 .into_compile_error();840 }841 quote! {842 ::jrsonnet_evaluator::IStr::from(#format)843 }844 }845 }846}847848/// `IStr` formatting helper849///850/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`851/// This macro looks for formatting codes in the input string, and uses852/// `format!()` only when necessary853#[proc_macro]854pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {855 let input = parse_macro_input!(input as FormatInput);856 input.expand().into()857}858859/// Create Thunk using closure syntax860#[proc_macro]861#[allow(non_snake_case)]862pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {863 let input = parse_macro_input!(input as ExprClosure);864865 let span = input.inputs.span();866 let move_check = input.capture.is_none().then(|| {867 quote_spanned! {span => {868 compile_error!("Thunk! needs to be called with move closure");869 }}870 });871872 let (env, closure, args) = syn_dissect_closure::split_env(input);873874 let trace_check = args.iter().map(|el| {875 let span = el.span();876 quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);}877 });878879 quote! {{880 #move_check881 #(#trace_check)*882 ::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::ThunkValueClosure::new(#env, #closure))883 }}.into()884}resultdiffbeforeafterboth--- a/result
+++ /dev/null
@@ -1 +0,0 @@
-/nix/store/nd6v7jksg1dqhpx4x4vqgy5ry1nkb9lk-jrsonnet-current
\ No newline at end of file
xtask/src/sourcegen/mod.rsdiffbeforeafterboth--- a/xtask/src/sourcegen/mod.rs
+++ b/xtask/src/sourcegen/mod.rs
@@ -203,49 +203,57 @@
});
let mut type_positions: HashMap<String, usize> = HashMap::new();
- let field_positions: Vec<_> = node.fields.iter().map(|field| {
- let ty_str = field.ty().to_string();
- let pos = *type_positions.get(&ty_str).unwrap_or(&0);
- type_positions.insert(ty_str, pos + 1);
- pos
- }).collect();
+ let field_positions: Vec<_> = node
+ .fields
+ .iter()
+ .map(|field| {
+ let ty_str = field.ty().to_string();
+ let pos = *type_positions.get(&ty_str).unwrap_or(&0);
+ type_positions.insert(ty_str, pos + 1);
+ pos
+ })
+ .collect();
- let methods = node.fields.iter().zip(field_positions.iter()).map(|(field, &pos)| {
- let method_name = field.method_name(kinds);
- let ty = field.ty();
+ let methods = node
+ .fields
+ .iter()
+ .zip(field_positions.iter())
+ .map(|(field, &pos)| {
+ let method_name = field.method_name(kinds);
+ let ty = field.ty();
- if field.is_many() {
- quote! {
- pub fn #method_name(&self) -> AstChildren<#ty> {
- support::children(&self.syntax)
+ if field.is_many() {
+ quote! {
+ pub fn #method_name(&self) -> AstChildren<#ty> {
+ support::children(&self.syntax)
+ }
}
- }
- } else if let Some(token_kind) = field.token_kind(kinds) {
- quote! {
- pub fn #method_name(&self) -> Option<#ty> {
- support::token(&self.syntax, #token_kind)
+ } else if let Some(token_kind) = field.token_kind(kinds) {
+ quote! {
+ pub fn #method_name(&self) -> Option<#ty> {
+ support::token(&self.syntax, #token_kind)
+ }
}
- }
- } else if field.is_token_enum(grammar) {
- quote! {
- pub fn #method_name(&self) -> Option<#ty> {
- support::token_child(&self.syntax)
+ } else if field.is_token_enum(grammar) {
+ quote! {
+ pub fn #method_name(&self) -> Option<#ty> {
+ support::token_child(&self.syntax)
+ }
}
- }
- } else if pos == 0 {
- quote! {
- pub fn #method_name(&self) -> Option<#ty> {
- support::children(&self.syntax).next()
+ } else if pos == 0 {
+ quote! {
+ pub fn #method_name(&self) -> Option<#ty> {
+ support::children(&self.syntax).next()
+ }
}
- }
- } else {
- quote! {
- pub fn #method_name(&self) -> Option<#ty> {
- support::children(&self.syntax).nth(#pos)
+ } else {
+ quote! {
+ pub fn #method_name(&self) -> Option<#ty> {
+ support::children(&self.syntax).nth(#pos)
+ }
}
}
- }
- });
+ });
(
quote! {
#[pretty_doc_comment_placeholder_workaround]