--- a/Cargo.lock +++ b/Cargo.lock @@ -492,6 +492,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527" dependencies = [ + "indexmap", "itoa", "ryu", "serde", --- a/bindings/jsonnet/Cargo.toml +++ b/bindings/jsonnet/Cargo.toml @@ -17,3 +17,4 @@ [features] interop = [] +exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"] --- a/bindings/jsonnet/src/lib.rs +++ b/bindings/jsonnet/src/lib.rs @@ -58,7 +58,11 @@ pub extern "C" fn jsonnet_string_output(vm: &EvaluationState, v: c_int) { match v { 1 => vm.set_manifest_format(ManifestFormat::String), - 0 => vm.set_manifest_format(ManifestFormat::Json(4)), + 0 => vm.set_manifest_format(ManifestFormat::Json { + padding: 4, + #[cfg(feature = "exp-preserve-order")] + preserve_order: false, + }), _ => panic!("incorrect output format"), } } --- a/bindings/jsonnet/src/val_modify.rs +++ b/bindings/jsonnet/src/val_modify.rs @@ -5,8 +5,7 @@ use std::{ffi::CStr, os::raw::c_char}; use gcmodule::Cc; -use jrsonnet_evaluator::{val::ArrValue, EvaluationState, LazyBinding, LazyVal, ObjMember, Val}; -use jrsonnet_parser::Visibility; +use jrsonnet_evaluator::{val::ArrValue, EvaluationState, LazyVal, Val}; /// # Safety /// @@ -41,19 +40,9 @@ val: &Val, ) { match obj { - Val::Obj(old) => { - let new_obj = old.clone().extend_with_field( - CStr::from_ptr(name).to_str().unwrap().into(), - ObjMember { - add: false, - visibility: Visibility::Normal, - invoke: LazyBinding::Bound(LazyVal::new_resolved(val.clone())), - location: None, - }, - ); - - *obj = Val::Obj(new_obj); - } + Val::Obj(old) => old + .extend_field(CStr::from_ptr(name).to_str().unwrap().into()) + .value(val.clone()), _ => panic!("should receive object"), } } --- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -9,6 +9,12 @@ [features] # Use mimalloc as allocator mimalloc = ["mimallocator"] +# Experimental feature, which allows to preserve order of object fields +exp-preserve-order = [ + "jrsonnet-evaluator/exp-preserve-order", + "jrsonnet-evaluator/exp-serde-preserve-order", + "jrsonnet-cli/exp-preserve-order", +] [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" } --- a/crates/jrsonnet-cli/Cargo.toml +++ b/crates/jrsonnet-cli/Cargo.toml @@ -6,6 +6,9 @@ license = "MIT" edition = "2021" +[features] +exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"] + [dependencies] jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2", features = [ "explaining-traces", --- a/crates/jrsonnet-cli/src/manifest.rs +++ b/crates/jrsonnet-cli/src/manifest.rs @@ -43,20 +43,30 @@ /// `0` for hard tabs, `-1` for single line output [default: 3 for json, 2 for yaml] #[clap(long)] line_padding: Option, + /// Preserve order in object manifestification + #[cfg(feature = "exp-preserve-order")] + #[clap(long)] + exp_preserve_order: bool, } impl ConfigureState for ManifestOpts { fn configure(&self, state: &EvaluationState) -> Result<()> { if self.string { state.set_manifest_format(ManifestFormat::String); } else { + #[cfg(feature = "exp-preserve-order")] + let preserve_order = self.exp_preserve_order; match self.format { ManifestFormatName::String => state.set_manifest_format(ManifestFormat::String), - ManifestFormatName::Json => { - state.set_manifest_format(ManifestFormat::Json(self.line_padding.unwrap_or(3))) - } - ManifestFormatName::Yaml => { - state.set_manifest_format(ManifestFormat::Yaml(self.line_padding.unwrap_or(2))) - } + ManifestFormatName::Json => state.set_manifest_format(ManifestFormat::Json { + padding: self.line_padding.unwrap_or(3), + #[cfg(feature = "exp-preserve-order")] + preserve_order, + }), + ManifestFormatName::Yaml => state.set_manifest_format(ManifestFormat::Yaml { + padding: self.line_padding.unwrap_or(2), + #[cfg(feature = "exp-preserve-order")] + preserve_order, + }), } } if self.yaml_stream { --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -15,6 +15,10 @@ # Allows library authors to throw custom errors anyhow-error = ["anyhow"] +# Allows to preserve field order in objects +exp-preserve-order = [] +exp-serde-preserve-order = ["serde_json/preserve_order"] + [dependencies] jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" } jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" } --- a/crates/jrsonnet-evaluator/src/builtin/manifest.rs +++ b/crates/jrsonnet-evaluator/src/builtin/manifest.rs @@ -21,6 +21,8 @@ pub mtype: ManifestType, pub newline: &'s str, pub key_val_sep: &'s str, + #[cfg(feature = "exp-preserve-order")] + pub preserve_order: bool, } pub fn manifest_json_ex(val: &Val, options: &ManifestJsonOptions<'_>) -> Result { @@ -85,7 +87,10 @@ Val::Obj(obj) => { obj.run_assertions()?; buf.push('{'); - let fields = obj.fields(); + let fields = obj.fields( + #[cfg(feature = "exp-preserve-order")] + options.preserve_order, + ); if !fields.is_empty() { if mtype != ManifestType::ToString && mtype != ManifestType::Minify { buf.push_str(options.newline); @@ -182,6 +187,10 @@ /// safe_key: 1 /// ``` pub quote_keys: bool, + /// If true - then order of fields is preserved as written, + /// instead of sorting alphabetically + #[cfg(feature = "exp-preserve-order")] + pub preserve_order: bool, } /// From https://github.com/chyh1990/yaml-rust/blob/da52a68615f2ecdd6b7e4567019f280c433c1521/src/emitter.rs#L289 @@ -287,7 +296,14 @@ if o.is_empty() { buf.push_str("{}"); } else { - for (i, key) in o.fields().iter().enumerate() { + for (i, key) in o + .fields( + #[cfg(feature = "exp-preserve-order")] + options.preserve_order, + ) + .iter() + .enumerate() + { if i != 0 { buf.push('\n'); buf.push_str(cur_padding); --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -162,11 +162,7 @@ Ok(match x { A(x) => x.chars().count(), B(x) => x.len(), - C(x) => x - .fields_visibility() - .into_iter() - .filter(|(_k, v)| *v) - .count(), + C(x) => x.len(), D(f) => f.args_len(), }) } @@ -191,8 +187,20 @@ } #[jrsonnet_macros::builtin] -fn builtin_object_fields_ex(obj: ObjValue, inc_hidden: bool) -> Result { - let out = obj.fields_ex(inc_hidden); +fn builtin_object_fields_ex( + obj: ObjValue, + inc_hidden: bool, + #[cfg(feature = "exp-preserve-order")] preserve_order: Option, +) -> Result { + #[cfg(not(feature = "exp-preserve-order"))] + let preserve_order = false; + #[cfg(feature = "exp-preserve-order")] + let preserve_order = preserve_order.unwrap_or(false); + let out = obj.fields_ex( + inc_hidden, + #[cfg(feature = "exp-preserve-order")] + preserve_order, + ); Ok(VecVal(Cc::new( out.into_iter().map(Val::Str).collect::>(), ))) @@ -586,6 +594,7 @@ indent: IStr, newline: Option, key_val_sep: Option, + #[cfg(feature = "exp-preserve-order")] preserve_order: Option, ) -> Result { let newline = newline.as_deref().unwrap_or("\n"); let key_val_sep = key_val_sep.as_deref().unwrap_or(": "); @@ -596,6 +605,8 @@ mtype: ManifestType::Std, newline, key_val_sep, + #[cfg(feature = "exp-preserve-order")] + preserve_order: preserve_order.unwrap_or(false), }, ) } @@ -605,6 +616,7 @@ value: Any, indent_array_in_object: Option, quote_keys: Option, + #[cfg(feature = "exp-preserve-order")] preserve_order: Option, ) -> Result { manifest_yaml_ex( &value.0, @@ -616,6 +628,8 @@ "" }, quote_keys: quote_keys.unwrap_or(true), + #[cfg(feature = "exp-preserve-order")] + preserve_order: preserve_order.unwrap_or(false), }, ) } --- a/crates/jrsonnet-evaluator/src/integrations/serde.rs +++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs @@ -28,7 +28,10 @@ } Val::Obj(o) => { let mut out = Map::new(); - for key in o.fields() { + for key in o.fields( + #[cfg(feature = "exp-preserve-order")] + cfg!(feature = "exp-serde-preserve-order"), + ) { out.insert( (&key as &str).into(), o.get(key)? --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -100,7 +100,11 @@ ext_natives: Default::default(), tla_vars: Default::default(), import_resolver: Box::new(DummyImportResolver), - manifest_format: ManifestFormat::Json(4), + manifest_format: ManifestFormat::Json { + padding: 4, + #[cfg(feature = "exp-preserve-order")] + preserve_order: false, + }, trace_format: Box::new(CompactFormat { padding: 4, resolver: trace::PathResolver::Absolute, --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -18,10 +18,82 @@ push_frame, throw, weak_ptr_eq, weak_raw, Bindable, LazyBinding, LazyVal, Result, Val, }; +#[cfg(not(feature = "exp-preserve-order"))] +pub(crate) mod ordering { + use gcmodule::Trace; + + #[derive(Clone, Copy, Default, Debug, Trace)] + pub struct FieldIndex; + impl FieldIndex { + pub fn next(self) -> Self { + Self + } + } + + #[derive(Clone, Copy, Default, Debug, Trace)] + pub struct SuperDepth; + impl SuperDepth { + pub fn deeper(self) -> Self { + Self + } + } + + #[derive(Clone, Copy)] + pub struct FieldSortKey; + impl FieldSortKey { + pub fn new(_: SuperDepth, _: FieldIndex) -> Self { + Self + } + } +} + +#[cfg(feature = "exp-preserve-order")] +mod ordering { + use std::cmp::Reverse; + + use gcmodule::Trace; + + #[derive(Clone, Copy, Default, Debug, Trace, PartialEq, Eq, PartialOrd, Ord)] + pub struct FieldIndex(u32); + impl FieldIndex { + pub fn next(self) -> Self { + Self(self.0 + 1) + } + } + + #[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Debug)] + pub struct SuperDepth(u32); + impl SuperDepth { + pub fn deeper(self) -> Self { + Self(self.0 + 1) + } + } + + #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] + pub struct FieldSortKey(Reverse, FieldIndex); + impl FieldSortKey { + pub fn new(depth: SuperDepth, index: FieldIndex) -> Self { + Self(Reverse(depth), index) + } + pub fn collide(self, other: Self) -> Self { + if self.0 .0 > other.0 .0 { + self + } else if self.0 .0 < other.0 .0 { + other + } else { + unreachable!("object can't have two fields with same name") + } + } + } +} + +pub(crate) use ordering::*; + #[derive(Debug, Trace)] pub struct ObjMember { pub add: bool, pub visibility: Visibility, + original_index: FieldIndex, pub invoke: LazyBinding, pub location: Option, } @@ -120,6 +192,14 @@ ), } } + pub(crate) fn extend_with_raw_member(self, key: IStr, value: ObjMember) -> Self { + let mut new = GcHashMap::with_capacity(1); + new.insert(key, value); + Self::new(Some(self), Cc::new(new), Cc::new(Vec::new())) + } + pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder { + ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default()) + } pub fn with_this(&self, this_obj: Self) -> Self { Self(Cc::new(ObjValueInternals { super_obj: self.0.super_obj.clone(), @@ -131,6 +211,13 @@ })) } + pub fn len(&self) -> usize { + self.fields_visibility() + .into_iter() + .filter(|(_, (visible, _))| *visible) + .count() + } + pub fn is_empty(&self) -> bool { if !self.0.this_entries.is_empty() { return false; @@ -143,51 +230,93 @@ } /// Run callback for every field found in object - pub(crate) fn enum_fields(&self, handler: &mut impl FnMut(&IStr, &ObjMember) -> bool) -> bool { + pub(crate) fn enum_fields( + &self, + depth: SuperDepth, + handler: &mut impl FnMut(SuperDepth, &IStr, &ObjMember) -> bool, + ) -> bool { if let Some(s) = &self.0.super_obj { - if s.enum_fields(handler) { + if s.enum_fields(depth.deeper(), handler) { return true; } } for (name, member) in self.0.this_entries.iter() { - if handler(name, member) { + if handler(depth, name, member) { return true; } } false } - pub fn fields_visibility(&self) -> FxHashMap { + pub fn fields_visibility(&self) -> FxHashMap { let mut out = FxHashMap::default(); - self.enum_fields(&mut |name, member| { + self.enum_fields(SuperDepth::default(), &mut |depth, name, member| { + let new_sort_key = FieldSortKey::new(depth, member.original_index); match member.visibility { Visibility::Normal => { let entry = out.entry(name.to_owned()); - entry.or_insert(true); + let v = entry.or_insert((true, new_sort_key)); + v.1 = new_sort_key; } Visibility::Hidden => { - out.insert(name.to_owned(), false); + out.insert(name.to_owned(), (false, new_sort_key)); } Visibility::Unhide => { - out.insert(name.to_owned(), true); + out.insert(name.to_owned(), (true, new_sort_key)); } }; false }); out } - pub fn fields_ex(&self, include_hidden: bool) -> Vec { + pub fn fields_ex( + &self, + include_hidden: bool, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Vec { + #[cfg(feature = "exp-preserve-order")] + if preserve_order { + let (mut fields, mut keys): (Vec<_>, Vec<_>) = self + .fields_visibility() + .into_iter() + .filter(|(_, (visible, _))| include_hidden || *visible) + .enumerate() + .map(|(idx, (k, (_, sk)))| (k, (sk, idx))) + .unzip(); + keys.sort_unstable_by_key(|v| v.0); + // Reorder in-place by resulting indexes + for i in 0..fields.len() { + let x = fields[i].clone(); + let mut j = i; + loop { + let k = keys[j].1; + keys[j].1 = j; + if k == i { + break; + } + fields[j] = fields[k].clone(); + j = k + } + fields[j] = x; + } + return fields; + } + let mut fields: Vec<_> = self .fields_visibility() .into_iter() - .filter(|(_k, v)| include_hidden || *v) + .filter(|(_, (visible, _))| include_hidden || *visible) .map(|(k, _)| k) .collect(); fields.sort_unstable(); fields } - pub fn fields(&self) -> Vec { - self.fields_ex(false) + pub fn fields(&self, #[cfg(feature = "exp-preserve-order")] preserve_order: bool) -> Vec { + self.fields_ex( + false, + #[cfg(feature = "exp-preserve-order")] + preserve_order, + ) } pub fn field_visibility(&self, name: IStr) -> Option { @@ -236,11 +365,7 @@ self.get_raw(key, self.0.this_obj.as_ref()) } - pub fn extend_with_field(self, key: IStr, value: ObjMember) -> Self { - let mut new = GcHashMap::with_capacity(1); - new.insert(key, value); - Self::new(Some(self), Cc::new(new), Cc::new(Vec::new())) - } + // pub fn extend_with(self, key: ) fn get_raw(&self, key: IStr, real_this: Option<&Self>) -> Result> { let real_this = real_this.unwrap_or(self); @@ -339,6 +464,7 @@ super_obj: Option, map: GcHashMap, assertions: Vec>, + next_field_index: FieldIndex, } impl ObjValueBuilder { pub fn new() -> Self { @@ -349,6 +475,7 @@ super_obj: None, map: GcHashMap::with_capacity(capacity), assertions: Vec::new(), + next_field_index: FieldIndex::default(), } } pub fn reserve_asserts(&mut self, capacity: usize) -> &mut Self { @@ -364,14 +491,10 @@ self.assertions.push(assertion); self } - pub fn member(&mut self, name: IStr) -> ObjMemberBuilder { - ObjMemberBuilder { - value: self, - name, - add: false, - visibility: Visibility::Normal, - location: None, - } + pub fn member(&mut self, name: IStr) -> ObjMemberBuilder { + let field_index = self.next_field_index; + self.next_field_index = self.next_field_index.next(); + ObjMemberBuilder::new(ValueBuilder(self), name, field_index) } pub fn build(self) -> ObjValue { @@ -385,16 +508,28 @@ } #[must_use = "value not added unless binding() was called"] -pub struct ObjMemberBuilder<'v> { - value: &'v mut ObjValueBuilder, +pub struct ObjMemberBuilder { + kind: Kind, name: IStr, add: bool, visibility: Visibility, + original_index: FieldIndex, location: Option, } #[allow(clippy::missing_const_for_fn)] -impl<'v> ObjMemberBuilder<'v> { +impl ObjMemberBuilder { + pub(crate) fn new(kind: Kind, name: IStr, original_index: FieldIndex) -> Self { + Self { + kind, + name, + original_index, + add: false, + visibility: Visibility::Normal, + location: None, + } + } + pub const fn with_add(mut self, add: bool) -> Self { self.add = add; self @@ -413,6 +548,23 @@ self.location = Some(location); self } + fn build_member(self, binding: LazyBinding) -> (Kind, IStr, ObjMember) { + ( + self.kind, + self.name, + ObjMember { + add: self.add, + visibility: self.visibility, + original_index: self.original_index, + invoke: binding, + location: self.location, + }, + ) + } +} + +pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder); +impl<'v> ObjMemberBuilder> { pub fn value(self, value: Val) -> Result<()> { self.binding(LazyBinding::Bound(LazyVal::new_resolved(value))) } @@ -420,22 +572,31 @@ self.binding(LazyBinding::Bindable(Cc::new(bindable))) } pub fn binding(self, binding: LazyBinding) -> Result<()> { - let old = self.value.map.insert( - self.name.clone(), - ObjMember { - add: self.add, - visibility: self.visibility, - invoke: binding, - location: self.location.clone(), - }, - ); + let (receiver, name, member) = self.build_member(binding); + let location = member.location.clone(); + let old = receiver.0.map.insert(name.clone(), member); if old.is_some() { push_frame( - CallLocation(self.location.as_ref()), - || format!("field <{}> initializtion", self.name.clone()), - || throw!(DuplicateFieldName(self.name.clone())), + CallLocation(location.as_ref()), + || format!("field <{}> initializtion", name.clone()), + || throw!(DuplicateFieldName(name.clone())), )? } Ok(()) } } + +pub struct ExtendBuilder<'v>(&'v mut ObjValue); +impl<'v> ObjMemberBuilder> { + pub fn value(self, value: Val) { + self.binding(LazyBinding::Bound(LazyVal::new_resolved(value))) + } + pub fn bindable(self, bindable: TraceBox) { + self.binding(LazyBinding::Bindable(Cc::new(bindable))) + } + pub fn binding(self, binding: LazyBinding) -> () { + let (receiver, name, member) = self.build_member(binding); + let new = receiver.0.clone(); + *receiver.0 = new.extend_with_raw_member(name, member) + } +} --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -167,11 +167,31 @@ #[derive(Clone)] pub enum ManifestFormat { YamlStream(Box), - Yaml(usize), - Json(usize), + Yaml { + padding: usize, + #[cfg(feature = "exp-preserve-order")] + preserve_order: bool, + }, + Json { + padding: usize, + #[cfg(feature = "exp-preserve-order")] + preserve_order: bool, + }, ToString, String, } +impl ManifestFormat { + #[cfg(feature = "exp-preserve-order")] + fn preserve_order(&self) -> bool { + match self { + ManifestFormat::YamlStream(s) => s.preserve_order(), + ManifestFormat::Yaml { preserve_order, .. } => *preserve_order, + ManifestFormat::Json { preserve_order, .. } => *preserve_order, + ManifestFormat::ToString => false, + ManifestFormat::String => false, + } + } +} #[derive(Debug, Clone, Trace)] pub struct Slice { @@ -559,6 +579,8 @@ mtype: ManifestType::ToString, newline: "\n", key_val_sep: ": ", + #[cfg(feature = "exp-preserve-order")] + preserve_order: false, }, )? .into(), @@ -571,7 +593,10 @@ Self::Obj(obj) => obj, _ => throw!(MultiManifestOutputIsNotAObject), }; - let keys = obj.fields(); + let keys = obj.fields( + #[cfg(feature = "exp-preserve-order")] + ty.preserve_order(), + ); let mut out = Vec::with_capacity(keys.len()); for key in keys { let value = obj @@ -622,8 +647,24 @@ out.into() } - ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?, - ManifestFormat::Json(padding) => self.to_json(*padding)?, + ManifestFormat::Yaml { + padding, + #[cfg(feature = "exp-preserve-order")] + preserve_order, + } => self.to_yaml( + *padding, + #[cfg(feature = "exp-preserve-order")] + *preserve_order, + )?, + ManifestFormat::Json { + padding, + #[cfg(feature = "exp-preserve-order")] + preserve_order, + } => self.to_json( + *padding, + #[cfg(feature = "exp-preserve-order")] + *preserve_order, + )?, ManifestFormat::ToString => self.to_string()?, ManifestFormat::String => match self { Self::Str(s) => s.clone(), @@ -633,7 +674,11 @@ } /// For manifestification - pub fn to_json(&self, padding: usize) -> Result { + pub fn to_json( + &self, + padding: usize, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Result { manifest_json_ex( self, &ManifestJsonOptions { @@ -645,13 +690,19 @@ }, newline: "\n", key_val_sep: ": ", + #[cfg(feature = "exp-preserve-order")] + preserve_order, }, ) .map(|s| s.into()) } /// Calls `std.manifestJson` - pub fn to_std_json(&self, padding: usize) -> Result> { + pub fn to_std_json( + &self, + padding: usize, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Result> { manifest_json_ex( self, &ManifestJsonOptions { @@ -659,12 +710,18 @@ mtype: ManifestType::Std, newline: "\n", key_val_sep: ": ", + #[cfg(feature = "exp-preserve-order")] + preserve_order, }, ) .map(|s| s.into()) } - pub fn to_yaml(&self, padding: usize) -> Result { + pub fn to_yaml( + &self, + padding: usize, + #[cfg(feature = "exp-preserve-order")] preserve_order: bool, + ) -> Result { let padding = &" ".repeat(padding); manifest_yaml_ex( self, @@ -672,6 +729,8 @@ padding, arr_element_padding: padding, quote_keys: false, + #[cfg(feature = "exp-preserve-order")] + preserve_order, }, ) .map(|s| s.into()) @@ -733,8 +792,15 @@ if ObjValue::ptr_eq(a, b) { return Ok(true); } - let fields = a.fields(); - if fields != b.fields() { + let fields = a.fields( + #[cfg(feature = "exp-preserve-order")] + false, + ); + if fields + != b.fields( + #[cfg(feature = "exp-preserve-order")] + false, + ) { return Ok(false); } for field in fields { --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -122,6 +122,7 @@ ty: Box, is_option: bool, name: String, + cfg_attrs: Vec, // ident: Ident, }, Lazy { @@ -134,20 +135,15 @@ impl ArgInfo { fn parse(arg: &FnArg) -> Result { - let typed = match arg { + let arg = match arg { FnArg::Receiver(_) => unreachable!(), FnArg::Typed(a) => a, }; - let ident = match &typed.pat as &Pat { + let ident = match &arg.pat as &Pat { Pat::Ident(i) => i.ident.clone(), - _ => { - return Err(Error::new( - typed.pat.span(), - "arg should be plain identifier", - )) - } + _ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")), }; - let ty = &typed.ty; + let ty = &arg.ty; if type_is_path(ty, "CallLocation").is_some() { return Ok(Self::Location); } else if type_is_path(ty, "Self").is_some() { @@ -172,11 +168,18 @@ (false, ty.clone()) }; + let cfg_attrs = arg + .attrs + .iter() + .filter(|a| a.path.is_ident("cfg")) + .cloned() + .collect(); + Ok(Self::Normal { ty, is_option, name: ident.to_string(), - // ident, + cfg_attrs, }) } } @@ -215,13 +218,22 @@ let params_desc = args.iter().flat_map(|a| match a { ArgInfo::Normal { - is_option, name, .. - } - | ArgInfo::Lazy { is_option, name } => Some(quote! { + is_option, + name, + cfg_attrs, + .. + } => Some(quote! { + #(#cfg_attrs)* + BuiltinParam { + name: std::borrow::Cow::Borrowed(#name), + has_default: #is_option, + }, + }), + ArgInfo::Lazy { is_option, name } => Some(quote! { BuiltinParam { name: std::borrow::Cow::Borrowed(#name), has_default: #is_option, - } + }, }), ArgInfo::Location => None, ArgInfo::This => None, @@ -232,23 +244,27 @@ ty, is_option, name, - // ident, + cfg_attrs, } => { let eval = quote! {::jrsonnet_evaluator::push_description_frame( || format!("argument <{}> evaluation", #name), || <#ty>::try_from(value.evaluate()?), )?}; - if *is_option { + let value = if *is_option { quote! {if let Some(value) = parsed.get(#name) { Some(#eval) } else { None - }} + },} } else { quote! {{ let value = parsed.get(#name).expect("args shape is checked"); #eval - }} + },} + }; + quote! { + #(#cfg_attrs)* + #value } } ArgInfo::Lazy { is_option, name } => { @@ -260,12 +276,12 @@ }} } else { quote! { - parsed.get(#name).expect("args shape is correct").clone() + parsed.get(#name).expect("args shape is correct").clone(), } } } - ArgInfo::Location => quote! {location}, - ArgInfo::This => quote! {self}, + ArgInfo::Location => quote! {location,}, + ArgInfo::This => quote! {self,}, }); let fields = attr.fields.iter().map(|field| { @@ -309,7 +325,7 @@ parser::ExprLocation, }; const PARAMS: &'static [BuiltinParam] = &[ - #(#params_desc),* + #(#params_desc)* ]; #static_ext @@ -326,7 +342,7 @@ fn call(&self, context: Context, location: CallLocation, args: &dyn ArgsLike) -> Result { let parsed = parse_builtin_call(context, &PARAMS, args, false)?; - let result: #result = #name(#(#pass),*); + let result: #result = #name(#(#pass)*); let result = result?; result.try_into() }