difftreelog
test basic interop checks
in: master
7 files changed
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -1,4 +1,5 @@
use std::{
+ fmt::Debug,
path::{Path, PathBuf},
rc::Rc,
};
@@ -166,7 +167,7 @@
#[derive(Debug, Clone, Trace)]
pub struct StackTrace(pub Vec<StackTraceElement>);
-#[derive(Debug, Clone, Trace)]
+#[derive(Clone, Trace)]
pub struct LocError(Box<(Error, StackTrace)>);
impl LocError {
pub fn new(e: Error) -> Self {
@@ -186,6 +187,15 @@
&mut (self.0).1
}
}
+impl Debug for LocError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ writeln!(f, "{}", self.0 .0)?;
+ for el in self.0 .1 .0.iter() {
+ writeln!(f, "\t{:?}", el)?;
+ }
+ Ok(())
+ }
+}
pub type Result<V, E = LocError> = std::result::Result<V, E>;
crates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -45,7 +45,6 @@
evaluate_named(s, self.ctx.unwrap(), &self.value, self.name)
}
}
-
pub trait ArgLike {
fn evaluate_arg(&self, s: State, ctx: Context, tailstrict: bool) -> Result<LazyVal>;
}
@@ -169,6 +168,34 @@
}
}
+impl ArgsLike for [(); 0] {
+ fn unnamed_len(&self) -> usize {
+ 0
+ }
+
+ fn unnamed_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+
+ fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+}
+
impl<A: ArgLike> ArgsLike for [(IStr, A)] {
fn unnamed_len(&self) -> usize {
0
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -14,11 +14,11 @@
};
pub trait TypedObj: Typed {
- fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;
- fn parse(obj: &ObjValue) -> Result<Self>;
- fn into_object(self) -> Result<ObjValue> {
+ fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<()>;
+ fn parse(obj: &ObjValue, s: State) -> Result<Self>;
+ fn into_object(self, s: State) -> Result<ObjValue> {
let mut builder = ObjValueBuilder::new();
- self.serialize(&mut builder)?;
+ self.serialize(s, &mut builder)?;
Ok(builder.build())
}
}
crates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/builtin.rs
@@ -0,0 +1,105 @@
+mod common;
+
+use std::path::PathBuf;
+
+use gcmodule::Cc;
+use jrsonnet_evaluator::{
+ error::Result,
+ function::{builtin, Builtin, CallLocation},
+ gc::TraceBox,
+ typed::Typed,
+ val::FuncVal,
+ State, Val,
+};
+
+#[builtin]
+fn a() -> Result<u32> {
+ Ok(1)
+}
+
+#[test]
+fn basic_function() -> Result<()> {
+ let s = State::default();
+ let a: a = a {};
+ let v = u32::from_untyped(
+ a.call(
+ s.clone(),
+ s.create_default_context(),
+ CallLocation::native(),
+ &[],
+ )?,
+ s.clone(),
+ )?;
+
+ ensure_eq!(v, 1);
+ Ok(())
+}
+
+#[builtin]
+fn native_add(a: u32, b: u32) -> Result<u32> {
+ Ok(a + b)
+}
+
+#[test]
+fn call_from_code() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.settings_mut().globals.insert(
+ "nativeAdd".into(),
+ Val::Func(FuncVal::StaticBuiltin(native_add::INST)),
+ );
+
+ let v = s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "
+ assert nativeAdd(1, 2) == 3;
+ assert nativeAdd(100, 200) == 300;
+ null
+ "
+ .into(),
+ )?;
+ ensure_val_eq!(s.clone(), v, Val::Null);
+ Ok(())
+}
+
+#[builtin(fields(
+ a: u32
+))]
+fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
+ Ok(this.a + b)
+}
+
+#[builtin]
+fn curry_add(a: u32) -> Result<FuncVal> {
+ Ok(FuncVal::Builtin(Cc::new(TraceBox(Box::new(curried_add {
+ a,
+ })))))
+}
+
+#[test]
+fn nonstatic_builtin() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.settings_mut().globals.insert(
+ "curryAdd".into(),
+ Val::Func(FuncVal::StaticBuiltin(curry_add::INST)),
+ );
+
+ let v = s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "
+ local a = curryAdd(1);
+ local b = curryAdd(4);
+
+ assert a(2) == 3;
+ assert a(200) == 201;
+
+ assert b(2) == 6;
+ assert b(200) == 204;
+ null
+ "
+ .into(),
+ )?;
+ ensure_val_eq!(s.clone(), v, Val::Null);
+ Ok(())
+}
crates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/common.rs
@@ -0,0 +1,25 @@
+#[macro_export]
+macro_rules! ensure_eq {
+ ($a:expr, $b:expr $(,)?) => {{
+ if $a != $b {
+ ::jrsonnet_evaluator::throw_runtime!(
+ "assertion failed: a != b\na={:#?}\nb={:#?}",
+ $a,
+ $b,
+ )
+ }
+ }};
+}
+
+#[macro_export]
+macro_rules! ensure_val_eq {
+ ($s:expr, $a:expr, $b:expr) => {{
+ if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
+ ::jrsonnet_evaluator::throw_runtime!(
+ "assertion failed: a != b\na={:#?}\nb={:#?}",
+ $a.to_json($s.clone(), 2)?,
+ $b.to_json($s.clone(), 2)?,
+ )
+ }
+ }};
+}
crates/jrsonnet-evaluator/tests/typed_obj.rsdiffbeforeafterbothno changes
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -6,7 +6,7 @@
parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
- token::Comma,
+ token::{self, Comma},
Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,
PathArguments, Result, ReturnType, Token, Type,
};
@@ -90,6 +90,7 @@
syn::custom_keyword!(fields);
syn::custom_keyword!(rename);
syn::custom_keyword!(flatten);
+ syn::custom_keyword!(ok);
}
struct EmptyAttr;
@@ -135,7 +136,7 @@
}
impl ArgInfo {
- fn parse(arg: &FnArg) -> Result<Self> {
+ fn parse(name: &str, arg: &FnArg) -> Result<Self> {
let arg = match arg {
FnArg::Receiver(_) => unreachable!(),
FnArg::Typed(a) => a,
@@ -149,8 +150,6 @@
return Ok(Self::State);
} else if type_is_path(ty, "CallLocation").is_some() {
return Ok(Self::Location);
- } else if type_is_path(ty, "Self").is_some() {
- return Ok(Self::This);
} else if type_is_path(ty, "LazyVal").is_some() {
return Ok(Self::Lazy {
is_option: false,
@@ -158,6 +157,11 @@
});
}
+ match &ty as &Type {
+ Type::Reference(r) if type_is_path(&r.elem, &name).is_some() => return Ok(Self::This),
+ _ => {}
+ }
+
let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {
if type_is_path(ty, "LazyVal").is_some() {
return Ok(Self::Lazy {
@@ -230,11 +234,12 @@
return Err(Error::new(result.span(), "return value should be result"));
};
+ let name = fun.sig.ident.to_string();
let args = fun
.sig
.inputs
.iter()
- .map(ArgInfo::parse)
+ .map(|arg| ArgInfo::parse(&name, arg))
.collect::<Result<Vec<_>>>()?;
let params_desc = args.iter().flat_map(|a| match a {
@@ -343,9 +348,9 @@
}
const _: () = {
use ::jrsonnet_evaluator::{
- State,
+ State, Val,
function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
- error::Result, Context,
+ error::Result, Context, typed::Typed,
parser::ExprLocation,
};
const PARAMS: &'static [BuiltinParam] = &[
@@ -379,6 +384,9 @@
struct TypedAttr {
rename: Option<String>,
flatten: bool,
+ /// flatten(ok) strategy for flattened optionals
+ /// field would be None in case of any parsing error (as in serde)
+ flatten_ok: bool,
}
impl Parse for TypedAttr {
fn parse(input: ParseStream) -> syn::Result<Self> {
@@ -399,6 +407,17 @@
} else if lookahead.peek(kw::flatten) {
input.parse::<kw::flatten>()?;
out.flatten = true;
+ if input.peek(token::Paren) {
+ let content;
+ parenthesized!(content in input);
+ let lookahead = content.lookahead1();
+ if lookahead.peek(kw::ok) {
+ content.parse::<kw::ok>()?;
+ out.flatten_ok = true;
+ } else {
+ return Err(lookahead.error());
+ }
+ }
} else if input.is_empty() {
break;
} else {
@@ -417,75 +436,101 @@
}
}
-struct TypedField<'f>(&'f syn::Field, TypedAttr);
-impl<'f> TypedField<'f> {
- fn try_new(field: &'f syn::Field) -> Result<Self> {
+struct TypedField {
+ attr: TypedAttr,
+ ident: Ident,
+ ty: Type,
+ is_option: bool,
+}
+impl TypedField {
+ fn parse(field: &syn::Field) -> Result<Self> {
let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();
- if field.ident.is_none() {
+ let ident = if let Some(ident) = field.ident.clone() {
+ ident
+ } else {
return Err(Error::new(
field.span(),
"this field should appear in output object, but it has no visible name",
));
+ };
+ let (is_option, ty) = if let Some(ty) = extract_type_from_option(&field.ty)? {
+ (true, ty.clone())
+ } else {
+ (false, field.ty.clone())
+ };
+ if is_option && attr.flatten {
+ if !attr.flatten_ok {
+ return Err(Error::new(
+ field.span(),
+ "strategy should be set when flattening Option",
+ ));
+ }
+ } else {
+ if attr.flatten_ok {
+ return Err(Error::new(
+ field.span(),
+ "flatten(ok) is only useable on optional fields",
+ ));
+ }
}
- Ok(Self(field, attr))
- }
- fn ident(&self) -> Ident {
- self.0
- .ident
- .clone()
- .expect("constructor disallows fields without name")
+ Ok(Self {
+ attr,
+ ident,
+ ty,
+ is_option,
+ })
}
/// None if this field is flattened in jsonnet output
fn name(&self) -> Option<String> {
- if self.1.flatten {
+ if self.attr.flatten {
return None;
}
Some(
- self.1
+ self.attr
.rename
.clone()
- .unwrap_or_else(|| self.ident().to_string()),
+ .unwrap_or_else(|| self.ident.to_string()),
)
}
fn expand_field(&self) -> Option<TokenStream> {
- if self.is_option() {
+ if self.is_option {
return None;
}
let name = self.name()?;
- let ty = &self.0.ty;
+ let ty = &self.ty;
Some(quote! {
(#name, <#ty>::TYPE)
})
}
fn expand_parse(&self) -> TokenStream {
- let ident = self.ident();
- let ty = &self.0.ty;
- if self.1.flatten {
+ let ident = &self.ident;
+ let ty = &self.ty;
+ if self.attr.flatten {
// optional flatten is handled in same way as serde
- return if self.is_option() {
+ return if self.is_option {
quote! {
- #ident: <#ty>::parse(&obj).ok(),
+ #ident: <#ty>::parse(&obj, s.clone()).ok(),
}
} else {
quote! {
- #ident: <#ty>::parse(&obj)?,
+ #ident: <#ty>::parse(&obj, s.clone())?,
}
};
};
let name = self.name().unwrap();
- let value = if let Some(ty) = self.as_option() {
+ let value = if self.is_option {
quote! {
- if let Some(value) = obj.get(#name.into())? {
- Some(<#ty>::try_from(vakue)?)
+ if let Some(value) = obj.get(s.clone(), #name.into())? {
+ Some(<#ty>::from_untyped(value, s.clone())?)
} else {
None
}
}
} else {
quote! {
- <#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?
+ <#ty>::from_untyped(obj.get(s.clone(), #name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?, s.clone())?
}
};
@@ -493,39 +538,33 @@
#ident: #value,
}
}
- fn expand_serialize(&self) -> TokenStream {
- let ident = self.ident();
- if let Some(name) = self.name() {
- if self.is_option() {
+ fn expand_serialize(&self) -> Result<TokenStream> {
+ let ident = &self.ident;
+ let ty = &self.ty;
+ Ok(if let Some(name) = self.name() {
+ if self.is_option {
quote! {
if let Some(value) = self.#ident {
- out.member(#name.into()).value(value.try_into()?)?;
+ out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(value, s.clone())?)?;
}
}
} else {
quote! {
- out.member(#name.into()).value(self.#ident.try_into()?)?;
+ out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(self.#ident, s.clone())?)?;
}
}
- } else if self.is_option() {
+ } else if self.is_option {
quote! {
if let Some(value) = self.#ident {
- value.serialize(out)?;
+ value.serialize(s.clone(), out)?;
}
}
} else {
quote! {
- self.#ident.serialize(out)?;
+ self.#ident.serialize(s.clone(), out)?;
}
- }
- }
-
- fn as_option(&self) -> Option<&Type> {
- extract_type_from_option(&self.0.ty).unwrap()
+ })
}
- fn is_option(&self) -> bool {
- self.as_option().is_some()
- }
}
#[proc_macro_derive(Typed, attributes(typed))]
@@ -548,7 +587,7 @@
let fields = data
.fields
.iter()
- .map(TypedField::try_new)
+ .map(TypedField::parse)
.collect::<Result<Vec<_>>>()?;
let typed = {
@@ -566,12 +605,12 @@
fn from_untyped(value: Val, s: State) -> Result<Self> {
let obj = value.as_obj().expect("shape is correct");
- Self::parse(&obj)
+ Self::parse(&obj, s)
}
fn into_untyped(value: Self, s: State) -> Result<Val> {
let mut out = ObjValueBuilder::new();
- value.serialize(&mut out)?;
+ value.serialize(s, &mut out)?;
Ok(Val::Obj(out.build()))
}
@@ -580,26 +619,29 @@
};
let fields_parse = fields.iter().map(TypedField::expand_parse);
- let fields_serialize = fields.iter().map(TypedField::expand_serialize);
+ let fields_serialize = fields
+ .iter()
+ .map(TypedField::expand_serialize)
+ .collect::<Result<Vec<_>>>()?;
Ok(quote! {
const _: () = {
use ::jrsonnet_evaluator::{
typed::{ComplexValType, Typed, TypedObj, CheckType},
- Val,
- error::{LocError, Error},
+ Val, State,
+ error::{LocError, Error, Result},
ObjValueBuilder, ObjValue,
};
#typed
- impl #ident {
- fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {
+ impl TypedObj for #ident {
+ fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<(), LocError> {
#(#fields_serialize)*
Ok(())
}
- fn parse(obj: &ObjValue) -> Result<Self, LocError> {
+ fn parse(obj: &ObjValue, s: State) -> Result<Self, LocError> {
Ok(Self {
#(#fields_parse)*
})