difftreelog
feat derive(Typed) for struct
in: master
9 files changed
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -70,8 +70,9 @@
[[package]]
name = "clap"
-version = "3.0.0-beta.2"
-source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83"
+version = "3.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
dependencies = [
"atty",
"bitflags",
@@ -82,27 +83,28 @@
"strsim",
"termcolor",
"textwrap",
- "vec_map",
]
[[package]]
+name = "clap_complete"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df6f3613c0a3cddfd78b41b10203eb322cb29b600cbdf808a7d3db95691b8e25"
+dependencies = [
+ "clap",
+]
+
+[[package]]
name = "clap_derive"
-version = "3.0.0-beta.2"
-source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83"
+version = "3.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
-]
-
-[[package]]
-name = "clap_generate"
-version = "3.0.0-beta.2"
-source = "git+https://github.com/clap-rs/clap?rev=f0c5ea5e1503de5c8e74d8c047a799cf51498e83#f0c5ea5e1503de5c8e74d8c047a799cf51498e83"
-dependencies = [
- "clap",
]
[[package]]
@@ -148,12 +150,9 @@
[[package]]
name = "heck"
-version = "0.3.3"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
-dependencies = [
- "unicode-segmentation",
-]
+checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
@@ -185,7 +184,7 @@
version = "0.4.2"
dependencies = [
"clap",
- "clap_generate",
+ "clap_complete",
"gcmodule",
"jrsonnet-cli",
"jrsonnet-evaluator",
@@ -214,6 +213,7 @@
"bincode",
"gcmodule",
"jrsonnet-interner",
+ "jrsonnet-macros",
"jrsonnet-parser",
"jrsonnet-stdlib",
"jrsonnet-types",
@@ -236,6 +236,15 @@
]
[[package]]
+name = "jrsonnet-macros"
+version = "0.4.2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "jrsonnet-parser"
version = "0.4.2"
dependencies = [
@@ -301,6 +310,12 @@
checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]]
+name = "memchr"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
+
+[[package]]
name = "mimalloc-sys"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -321,9 +336,12 @@
[[package]]
name = "os_str_bytes"
-version = "3.1.0"
+version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d"
+checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
+dependencies = [
+ "memchr",
+]
[[package]]
name = "parking_lot"
@@ -357,9 +375,9 @@
[[package]]
name = "peg"
-version = "0.7.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07c0b841ea54f523f7aa556956fbd293bcbe06f2e67d2eb732b7278aaf1d166a"
+checksum = "af728fe826811af3b38c37e93de6d104485953ea373d656eebae53d6987fcd2c"
dependencies = [
"peg-macros",
"peg-runtime",
@@ -367,9 +385,9 @@
[[package]]
name = "peg-macros"
-version = "0.7.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5aa52829b8decbef693af90202711348ab001456803ba2a98eb4ec8fb70844c"
+checksum = "4536be147b770b824895cbad934fccce8e49f14b4c4946eaa46a6e4a12fcdc16"
dependencies = [
"peg-runtime",
"proc-macro2",
@@ -378,9 +396,9 @@
[[package]]
name = "peg-runtime"
-version = "0.7.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c719dcf55f09a3a7e764c6649ab594c18a177e3599c467983cdf644bfc0a4088"
+checksum = "f9b0efd3ba03c3a409d44d60425f279ec442bcf0b9e63ff4e410da31c8b0f69f"
[[package]]
name = "proc-macro-error"
@@ -536,12 +554,9 @@
[[package]]
name = "textwrap"
-version = "0.14.2"
+version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80"
-dependencies = [
- "unicode-width",
-]
+checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
[[package]]
name = "thiserror"
@@ -562,12 +577,6 @@
"quote",
"syn",
]
-
-[[package]]
-name = "unicode-segmentation"
-version = "1.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
[[package]]
name = "unicode-width"
@@ -580,12 +589,6 @@
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
-
-[[package]]
-name = "vec_map"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -179,7 +179,7 @@
}
}
-pub type Result<V> = std::result::Result<V, LocError>;
+pub type Result<V, E = LocError> = std::result::Result<V, E>;
#[macro_export]
macro_rules! throw {
crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -8,6 +8,7 @@
};
use gcmodule::Trace;
use jrsonnet_interner::IStr;
+pub use jrsonnet_macros::builtin;
use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc};
use std::{borrow::Cow, collections::HashMap, convert::TryFrom};
@@ -377,6 +378,7 @@
pub has_default: bool,
}
+/// Do not implement it directly, instead use #[builtin] macro
pub trait Builtin: Trace {
fn name(&self) -> &str;
fn params(&self) -> &[BuiltinParam];
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -174,7 +174,11 @@
pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)
}
pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {
- EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))
+ EVAL_STATE.with(|s| {
+ f(s.borrow().as_ref().expect(
+ "missing evaluation state, some functions should be called inside of run_in_state call",
+ ))
+ })
}
pub fn push_frame<T>(
e: Option<&ExprLocation>,
@@ -728,12 +732,15 @@
}
macro_rules! eval {
- ($str: expr) => {
- EvaluationState::default()
- .with_stdlib()
- .evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into())
- .unwrap()
- };
+ ($str: expr) => {{
+ let evaluator = EvaluationState::default();
+ evaluator.with_stdlib();
+ evaluator.run_in_state(|| {
+ evaluator
+ .evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into())
+ .unwrap()
+ })
+ }};
}
macro_rules! eval_json {
($str: expr) => {{
@@ -1265,4 +1272,47 @@
assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "d"), 0)"#);
assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "a"), 2)"#);
}
+
+ mod derive_typed {
+ use crate::{typed::Typed, EvaluationState};
+ use std::path::PathBuf;
+
+ #[derive(Typed, PartialEq, Debug)]
+ struct MyTyped {
+ a: u32,
+ b: String,
+ }
+
+ #[test]
+ fn test() {
+ let es = EvaluationState::default();
+ let val = eval!("{a: 14, b: 'Hello, world!'}");
+ let typed = es.run_in_state(|| MyTyped::try_from(val).unwrap());
+
+ assert_eq!(
+ typed,
+ MyTyped {
+ a: 14,
+ b: "Hello, world!".to_string()
+ }
+ );
+ es.settings_mut().globals.insert(
+ "mytyped".into(),
+ es.run_in_state(|| typed.try_into()).unwrap(),
+ );
+
+ let v = es
+ .evaluate_snippet_raw(
+ PathBuf::from("raw.jsonnet").into(),
+ "
+ mytyped == {a: 14, b: 'Hello, world!'}
+ "
+ .into(),
+ )
+ .unwrap()
+ .as_bool()
+ .unwrap();
+ assert!(v)
+ }
+ }
}
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,7 @@
use std::convert::{TryFrom, TryInto};
use jrsonnet_interner::IStr;
+pub use jrsonnet_macros::Typed;
use jrsonnet_types::{ComplexValType, ValType};
use crate::{
crates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -8,20 +8,8 @@
push_description_frame, Val,
};
use gcmodule::Trace;
-use jrsonnet_types::{ComplexValType, ValType};
+pub use jrsonnet_types::{ComplexValType, ValType};
use thiserror::Error;
-
-#[macro_export]
-macro_rules! unwrap_type {
- ($desc:expr, $value:expr, $typ:expr => $match:path) => {{
- use $crate::{push_frame, typed::CheckType};
- push_frame(None, $desc, || Ok($typ.check(&$value)?))?;
- match $value {
- $match(v) => v,
- _ => unreachable!(),
- }
- }};
-}
#[derive(Debug, Error, Clone, Trace)]
pub enum TypeError {
@@ -136,7 +124,7 @@
impl Display for ValuePathItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Self::Field(name) => write!(f, ".{}", name)?,
+ Self::Field(name) => write!(f, ".{:?}", name)?,
Self::Index(idx) => write!(f, "[{}]", idx)?,
}
Ok(())
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -91,7 +91,7 @@
Normal(Cc<FuncDesc>),
/// Standard library function
StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin),
-
+ /// User-provided function
Builtin(Cc<TraceBox<dyn Builtin>>),
}
@@ -99,8 +99,10 @@
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(),
- Self::StaticBuiltin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(),
- Self::Builtin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(),
+ Self::StaticBuiltin(arg0) => {
+ f.debug_tuple("StaticBuiltin").field(&arg0.name()).finish()
+ }
+ Self::Builtin(arg0) => f.debug_tuple("Builtin").field(&arg0.name()).finish(),
}
}
}
@@ -338,6 +340,49 @@
}
impl Val {
+ pub fn as_bool(&self) -> Option<bool> {
+ match self {
+ Val::Bool(v) => Some(*v),
+ _ => None,
+ }
+ }
+ pub fn as_null(&self) -> Option<()> {
+ match self {
+ Val::Null => Some(()),
+ _ => None,
+ }
+ }
+ pub fn as_str(&self) -> Option<IStr> {
+ match self {
+ Val::Str(s) => Some(s.clone()),
+ _ => None,
+ }
+ }
+ pub fn as_num(&self) -> Option<f64> {
+ match self {
+ Val::Num(n) => Some(*n),
+ _ => None,
+ }
+ }
+ pub fn as_arr(&self) -> Option<ArrValue> {
+ match self {
+ Val::Arr(a) => Some(a.clone()),
+ _ => None,
+ }
+ }
+ pub fn as_obj(&self) -> Option<ObjValue> {
+ match self {
+ Val::Obj(o) => Some(o.clone()),
+ _ => None,
+ }
+ }
+ pub fn as_func(&self) -> Option<FuncVal> {
+ match self {
+ Val::Func(f) => Some(f.clone()),
+ _ => None,
+ }
+ }
+
/// Creates `Val::Num` after checking for numeric overflow.
/// As numbers are `f64`, we can just check for their finity.
pub fn new_checked_num(num: f64) -> Result<Self> {
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth1use quote::{quote, quote_spanned};2use syn::{3 parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, spanned::Spanned,4 token::Comma, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments, Token,5 Type,6};78fn is_location_arg(t: &PatType) -> bool {9 t.attrs.iter().any(|a| a.path.is_ident("location"))10}11fn is_self_arg(t: &PatType) -> bool {12 t.attrs.iter().any(|a| a.path.is_ident("self"))13}1415trait RetainHad<T> {16 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;17}18impl<T> RetainHad<T> for Vec<T> {19 fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {20 let before = self.len();21 self.retain(h);22 let after = self.len();23 before != after24 }25}2627fn extract_type_from_option(ty: &Type) -> Option<&Type> {28 fn path_is_option(path: &Path) -> bool {29 path.leading_colon.is_none()30 && path.segments.len() == 131 && path.segments.iter().next().unwrap().ident == "Option"32 }3334 match ty {35 Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {36 // Get the first segment of the path (there is only one, in fact: "Option"):37 let type_params = &typepath.path.segments.iter().next().unwrap().arguments;38 // It should have only on angle-bracketed param ("<String>"):39 let generic_arg = match type_params {40 PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),41 _ => panic!("missing option generic"),42 };43 // This argument must be a type:44 match generic_arg {45 GenericArgument::Type(ty) => Some(ty),46 _ => panic!("option generic should be a type"),47 }48 }49 _ => None,50 }51}5253struct Field {54 name: Ident,55 _colon: Token![:],56 ty: Type,57}58impl Parse for Field {59 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {60 Ok(Self {61 name: input.parse()?,62 _colon: input.parse()?,63 ty: input.parse()?,64 })65 }66}6768mod kw {69 syn::custom_keyword!(fields);70}7172struct BuiltinAttrs {73 fields: Vec<Field>,74}75impl Parse for BuiltinAttrs {76 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {77 if input.is_empty() {78 return Ok(Self { fields: Vec::new() });79 }80 input.parse::<kw::fields>()?;81 let fields;82 parenthesized!(fields in input);83 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;84 Ok(Self {85 fields: p.into_iter().collect(),86 })87 }88}8990#[proc_macro_attribute]91pub fn builtin(92 attr: proc_macro::TokenStream,93 item: proc_macro::TokenStream,94) -> proc_macro::TokenStream {95 let attrs = parse_macro_input!(attr as BuiltinAttrs);96 let mut fun: ItemFn = parse_macro_input!(item);9798 let result = match fun.sig.output {99 syn::ReturnType::Default => {100 return quote_spanned! { fun.sig.span() =>101 compile_error!("builtins should return something");102 }103 .into()104 }105 syn::ReturnType::Type(_, ref ty) => ty.clone(),106 };107108 let params = fun109 .sig110 .inputs111 .iter()112 .map(|i| match i {113 FnArg::Receiver(_) => unreachable!(),114 FnArg::Typed(t) => t,115 })116 .filter(|a| !is_location_arg(a) && !is_self_arg(a))117 .map(|t| {118 let ident = match &t.pat as &Pat {119 Pat::Ident(i) => i.ident.to_string(),120 _ => {121 return quote_spanned! { t.pat.span() =>122 compile_error!("args should be plain identifiers")123 }124 .into()125 }126 };127 let optional = extract_type_from_option(&t.ty).is_some();128 quote! {129 BuiltinParam {130 name: std::borrow::Cow::Borrowed(#ident),131 has_default: #optional,132 }133 }134 })135 .collect::<Vec<_>>();136137 let args = fun138 .sig139 .inputs140 .iter_mut()141 .map(|i| match i {142 FnArg::Receiver(_) => unreachable!(),143 FnArg::Typed(t) => t,144 })145 .map(|t| {146 if t.attrs.retain_had(|a| !a.path.is_ident("location")) {147 quote! {{148 loc149 }}150 } else if t.attrs.retain_had(|a| !a.path.is_ident("self")) {151 quote! {{152 self153 }}154 } else {155 let ident = match &t.pat as &Pat {156 Pat::Ident(i) => i.ident.to_string(),157 _ => {158 return quote_spanned! { t.pat.span() =>159 compile_error!("args should be plain identifiers")160 }161 .into()162 }163 };164 let ty = &t.ty;165 if let Some(opt_ty) = extract_type_from_option(&t.ty) {166 quote! {{167 if let Some(value) = parsed.get(#ident) {168 Some(::jrsonnet_evaluator::push_description_frame(169 || format!("argument <{}> evaluation", #ident),170 || <#opt_ty>::try_from(value.evaluate()?),171 )?)172 } else {173 None174 }175 }}176 } else {177 quote! {{178 let value = parsed.get(#ident).unwrap();179180 ::jrsonnet_evaluator::push_description_frame(181 || format!("argument <{}> evaluation", #ident),182 || <#ty>::try_from(value.evaluate()?),183 )?184 }}185 }186 }187 })188 .collect::<Vec<_>>();189190 let fields = attrs.fields.iter().map(|field| {191 let name = &field.name;192 let ty = &field.ty;193 quote! {194 pub #name: #ty,195 }196 });197198 let name = &fun.sig.ident;199 let vis = &fun.vis;200 let static_ext = if attrs.fields.is_empty() {201 quote! {202 impl #name {203 pub const INST: &'static dyn StaticBuiltin = &#name {};204 }205 impl StaticBuiltin for #name {}206 }207 } else {208 quote! {}209 };210 let static_derive_copy = if attrs.fields.is_empty() {211 quote! {, Copy}212 } else {213 quote! {}214 };215216 (quote! {217 #fun218 #[doc(hidden)]219 #[allow(non_camel_case_types)]220 #[derive(Clone, gcmodule::Trace #static_derive_copy)]221 #vis struct #name {222 #(#fields)*223 }224 const _: () = {225 use ::jrsonnet_evaluator::{226 function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},227 error::Result, Context,228 parser::ExprLocation,229 };230 const PARAMS: &'static [BuiltinParam] = &[231 #(#params),*232 ];233234 #static_ext235 impl Builtin for #name236 where237 Self: 'static238 {239 fn name(&self) -> &str {240 stringify!(#name)241 }242 fn params(&self) -> &[BuiltinParam] {243 PARAMS244 }245 fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {246 let parsed = parse_builtin_call(context, &PARAMS, args, false)?;247248 let result: #result = #name(#(#args),*);249 let result = result?;250 result.try_into()251 }252 }253 };254 })255 .into()256}crates/jrsonnet-types/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-types/src/lib.rs
+++ b/crates/jrsonnet-types/src/lib.rs
@@ -122,7 +122,7 @@
BoundedNumber(Option<f64>, Option<f64>),
Array(Box<ComplexValType>),
ArrayRef(&'static ComplexValType),
- ObjectRef(&'static [(&'static str, ComplexValType)]),
+ ObjectRef(&'static [(&'static str, &'static ComplexValType)]),
Union(Vec<ComplexValType>),
UnionRef(&'static [&'static ComplexValType]),
Sum(Vec<ComplexValType>),