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.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-evaluator/tests/typed_obj.rs
@@ -0,0 +1,194 @@
+mod common;
+
+use std::{fmt::Debug, path::PathBuf};
+
+use jrsonnet_evaluator::{error::Result, typed::Typed, State};
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct A {
+ a: u32,
+ b: u16,
+}
+
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
+ let untyped = T::into_untyped(value.clone(), s.clone())?;
+ let value2 = T::from_untyped(untyped.clone(), s.clone())?;
+ ensure_eq!(value, value2);
+ let untyped2 = T::into_untyped(value2, s.clone())?;
+ ensure_val_eq!(s, untyped, untyped2);
+
+ Ok(())
+}
+
+#[test]
+fn simple_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let a = A::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(a, A { a: 1, b: 2 });
+ test_roundtrip(a.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct B {
+ a: u32,
+ #[typed(rename = "c")]
+ b: u16,
+}
+
+#[test]
+fn renamed_field() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let b = B::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, c: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(b, B { a: 1, b: 2 });
+ ensure_eq!(
+ &B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
+ "{a: 1, c: 2}",
+ );
+ test_roundtrip(b.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct ObjectKind {
+ #[typed(rename = "apiVersion")]
+ api_version: String,
+ #[typed(rename = "kind")]
+ kind: String,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct Object {
+ #[typed(flatten)]
+ kind: ObjectKind,
+ b: u16,
+}
+
+#[test]
+fn flattened_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let obj = Object::from_untyped(
+ s.evaluate_snippet_raw(
+ PathBuf::new().into(),
+ "{apiVersion: 'ver', kind: 'kind', b: 2}".into(),
+ )?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ obj,
+ Object {
+ kind: ObjectKind {
+ api_version: "ver".into(),
+ kind: "kind".into(),
+ },
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
+ );
+ test_roundtrip(obj.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct C {
+ a: Option<u32>,
+ b: u16,
+}
+
+#[test]
+fn optional_field_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{a: 1, b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(c, C { a: Some(1), b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"a": 1, "b": 2}"#,
+ );
+ test_roundtrip(c.clone(), s.clone())?;
+ Ok(())
+}
+
+#[test]
+fn optional_field_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(c, C { a: None, b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(c.clone(), s.clone())?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct D {
+ #[typed(flatten(ok))]
+ e: Option<E>,
+ b: u16,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct E {
+ v: u32,
+}
+
+#[test]
+fn flatten_optional_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v:1}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ d,
+ D {
+ e: Some(E { v: 1 }),
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2, "v": 1}"#,
+ );
+ test_roundtrip(d.clone(), s.clone())?;
+ Ok(())
+}
+
+#[test]
+fn flatten_optional_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet_raw(PathBuf::new().into(), "{b: 2, v: '1'}".into())?,
+ s.clone(),
+ )?;
+ ensure_eq!(d, D { e: None, b: 2 });
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(d.clone(), s.clone())?;
+ Ok(())
+}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth6 parse_macro_input,6 parse_macro_input,7 punctuated::Punctuated,7 punctuated::Punctuated,8 spanned::Spanned,8 spanned::Spanned,9 token::Comma,9 token::{self, Comma},10 Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,10 Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,11 PathArguments, Result, ReturnType, Token, Type,11 PathArguments, Result, ReturnType, Token, Type,12};12};90 syn::custom_keyword!(fields);90 syn::custom_keyword!(fields);91 syn::custom_keyword!(rename);91 syn::custom_keyword!(rename);92 syn::custom_keyword!(flatten);92 syn::custom_keyword!(flatten);93 syn::custom_keyword!(ok);93}94}949595struct EmptyAttr;96struct EmptyAttr;135}136}136137137impl ArgInfo {138impl ArgInfo {138 fn parse(arg: &FnArg) -> Result<Self> {139 fn parse(name: &str, arg: &FnArg) -> Result<Self> {139 let arg = match arg {140 let arg = match arg {140 FnArg::Receiver(_) => unreachable!(),141 FnArg::Receiver(_) => unreachable!(),141 FnArg::Typed(a) => a,142 FnArg::Typed(a) => a,149 return Ok(Self::State);150 return Ok(Self::State);150 } else if type_is_path(ty, "CallLocation").is_some() {151 } else if type_is_path(ty, "CallLocation").is_some() {151 return Ok(Self::Location);152 return Ok(Self::Location);152 } else if type_is_path(ty, "Self").is_some() {153 } else if type_is_path(ty, "LazyVal").is_some() {153 return Ok(Self::This);154 } else if type_is_path(ty, "LazyVal").is_some() {155 return Ok(Self::Lazy {154 return Ok(Self::Lazy {156 is_option: false,155 is_option: false,157 name: ident.to_string(),156 name: ident.to_string(),158 });157 });159 }158 }159160 match &ty as &Type {161 Type::Reference(r) if type_is_path(&r.elem, &name).is_some() => return Ok(Self::This),162 _ => {}163 }160164161 let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {165 let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {162 if type_is_path(ty, "LazyVal").is_some() {166 if type_is_path(ty, "LazyVal").is_some() {230 return Err(Error::new(result.span(), "return value should be result"));234 return Err(Error::new(result.span(), "return value should be result"));231 };235 };232236237 let name = fun.sig.ident.to_string();233 let args = fun238 let args = fun234 .sig239 .sig235 .inputs240 .inputs236 .iter()241 .iter()237 .map(ArgInfo::parse)242 .map(|arg| ArgInfo::parse(&name, arg))238 .collect::<Result<Vec<_>>>()?;243 .collect::<Result<Vec<_>>>()?;239244240 let params_desc = args.iter().flat_map(|a| match a {245 let params_desc = args.iter().flat_map(|a| match a {343 }348 }344 const _: () = {349 const _: () = {345 use ::jrsonnet_evaluator::{350 use ::jrsonnet_evaluator::{346 State,351 State, Val,347 function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},352 function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},348 error::Result, Context,353 error::Result, Context, typed::Typed,349 parser::ExprLocation,354 parser::ExprLocation,350 };355 };351 const PARAMS: &'static [BuiltinParam] = &[356 const PARAMS: &'static [BuiltinParam] = &[379struct TypedAttr {384struct TypedAttr {380 rename: Option<String>,385 rename: Option<String>,381 flatten: bool,386 flatten: bool,387 /// flatten(ok) strategy for flattened optionals388 /// field would be None in case of any parsing error (as in serde)389 flatten_ok: bool,382}390}383impl Parse for TypedAttr {391impl Parse for TypedAttr {384 fn parse(input: ParseStream) -> syn::Result<Self> {392 fn parse(input: ParseStream) -> syn::Result<Self> {399 } else if lookahead.peek(kw::flatten) {407 } else if lookahead.peek(kw::flatten) {400 input.parse::<kw::flatten>()?;408 input.parse::<kw::flatten>()?;401 out.flatten = true;409 out.flatten = true;410 if input.peek(token::Paren) {411 let content;412 parenthesized!(content in input);413 let lookahead = content.lookahead1();414 if lookahead.peek(kw::ok) {415 content.parse::<kw::ok>()?;416 out.flatten_ok = true;417 } else {418 return Err(lookahead.error());419 }420 }402 } else if input.is_empty() {421 } else if input.is_empty() {403 break;422 break;404 } else {423 } else {417 }436 }418}437}419438420struct TypedField<'f>(&'f syn::Field, TypedAttr);439struct TypedField {440 attr: TypedAttr,441 ident: Ident,442 ty: Type,443 is_option: bool,444}421impl<'f> TypedField<'f> {445impl TypedField {422 fn try_new(field: &'f syn::Field) -> Result<Self> {446 fn parse(field: &syn::Field) -> Result<Self> {423 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();447 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();424 if field.ident.is_none() {448 let ident = if let Some(ident) = field.ident.clone() {449 ident450 } else {425 return Err(Error::new(451 return Err(Error::new(426 field.span(),452 field.span(),427 "this field should appear in output object, but it has no visible name",453 "this field should appear in output object, but it has no visible name",428 ));454 ));429 }455 };456 let (is_option, ty) = if let Some(ty) = extract_type_from_option(&field.ty)? {457 (true, ty.clone())458 } else {459 (false, field.ty.clone())460 };461 if is_option && attr.flatten {462 if !attr.flatten_ok {463 return Err(Error::new(464 field.span(),465 "strategy should be set when flattening Option",466 ));467 }468 } else {469 if attr.flatten_ok {470 return Err(Error::new(471 field.span(),472 "flatten(ok) is only useable on optional fields",473 ));474 }475 }430 Ok(Self(field, attr))476 Ok(Self {477 attr,478 ident,479 ty,480 is_option,481 })431 }482 }432 fn ident(&self) -> Ident {433 self.0434 .ident435 .clone()436 .expect("constructor disallows fields without name")437 }438 /// None if this field is flattened in jsonnet output483 /// None if this field is flattened in jsonnet output439 fn name(&self) -> Option<String> {484 fn name(&self) -> Option<String> {440 if self.1.flatten {485 if self.attr.flatten {441 return None;486 return None;442 }487 }443 Some(488 Some(444 self.1489 self.attr445 .rename490 .rename446 .clone()491 .clone()447 .unwrap_or_else(|| self.ident().to_string()),492 .unwrap_or_else(|| self.ident.to_string()),448 )493 )449 }494 }450495451 fn expand_field(&self) -> Option<TokenStream> {496 fn expand_field(&self) -> Option<TokenStream> {452 if self.is_option() {497 if self.is_option {453 return None;498 return None;454 }499 }455 let name = self.name()?;500 let name = self.name()?;456 let ty = &self.0.ty;501 let ty = &self.ty;457 Some(quote! {502 Some(quote! {458 (#name, <#ty>::TYPE)503 (#name, <#ty>::TYPE)459 })504 })460 }505 }461 fn expand_parse(&self) -> TokenStream {506 fn expand_parse(&self) -> TokenStream {462 let ident = self.ident();507 let ident = &self.ident;463 let ty = &self.0.ty;508 let ty = &self.ty;464 if self.1.flatten {509 if self.attr.flatten {465 // optional flatten is handled in same way as serde510 // optional flatten is handled in same way as serde466 return if self.is_option() {511 return if self.is_option {467 quote! {512 quote! {468 #ident: <#ty>::parse(&obj).ok(),513 #ident: <#ty>::parse(&obj, s.clone()).ok(),469 }514 }470 } else {515 } else {471 quote! {516 quote! {472 #ident: <#ty>::parse(&obj)?,517 #ident: <#ty>::parse(&obj, s.clone())?,473 }518 }474 };519 };475 };520 };476521477 let name = self.name().unwrap();522 let name = self.name().unwrap();478 let value = if let Some(ty) = self.as_option() {523 let value = if self.is_option {479 quote! {524 quote! {480 if let Some(value) = obj.get(#name.into())? {525 if let Some(value) = obj.get(s.clone(), #name.into())? {481 Some(<#ty>::try_from(vakue)?)526 Some(<#ty>::from_untyped(value, s.clone())?)482 } else {527 } else {483 None528 None484 }529 }485 }530 }486 } else {531 } else {487 quote! {532 quote! {488 <#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?533 <#ty>::from_untyped(obj.get(s.clone(), #name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?, s.clone())?489 }534 }490 };535 };491536492 quote! {537 quote! {493 #ident: #value,538 #ident: #value,494 }539 }495 }540 }496 fn expand_serialize(&self) -> TokenStream {541 fn expand_serialize(&self) -> Result<TokenStream> {497 let ident = self.ident();542 let ident = &self.ident;543 let ty = &self.ty;498 if let Some(name) = self.name() {544 Ok(if let Some(name) = self.name() {499 if self.is_option() {545 if self.is_option {500 quote! {546 quote! {501 if let Some(value) = self.#ident {547 if let Some(value) = self.#ident {502 out.member(#name.into()).value(value.try_into()?)?;548 out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(value, s.clone())?)?;503 }549 }504 }550 }505 } else {551 } else {506 quote! {552 quote! {507 out.member(#name.into()).value(self.#ident.try_into()?)?;553 out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(self.#ident, s.clone())?)?;508 }554 }509 }555 }510 } else if self.is_option() {556 } else if self.is_option {511 quote! {557 quote! {512 if let Some(value) = self.#ident {558 if let Some(value) = self.#ident {513 value.serialize(out)?;559 value.serialize(s.clone(), out)?;514 }560 }515 }561 }516 } else {562 } else {517 quote! {563 quote! {518 self.#ident.serialize(out)?;564 self.#ident.serialize(s.clone(), out)?;519 }565 }520 }566 })521 }567 }522523 fn as_option(&self) -> Option<&Type> {524 extract_type_from_option(&self.0.ty).unwrap()525 }526 fn is_option(&self) -> bool {527 self.as_option().is_some()528 }529}568}530569531#[proc_macro_derive(Typed, attributes(typed))]570#[proc_macro_derive(Typed, attributes(typed))]548 let fields = data587 let fields = data549 .fields588 .fields550 .iter()589 .iter()551 .map(TypedField::try_new)590 .map(TypedField::parse)552 .collect::<Result<Vec<_>>>()?;591 .collect::<Result<Vec<_>>>()?;553592554 let typed = {593 let typed = {566605567 fn from_untyped(value: Val, s: State) -> Result<Self> {606 fn from_untyped(value: Val, s: State) -> Result<Self> {568 let obj = value.as_obj().expect("shape is correct");607 let obj = value.as_obj().expect("shape is correct");569 Self::parse(&obj)608 Self::parse(&obj, s)570 }609 }571610572 fn into_untyped(value: Self, s: State) -> Result<Val> {611 fn into_untyped(value: Self, s: State) -> Result<Val> {573 let mut out = ObjValueBuilder::new();612 let mut out = ObjValueBuilder::new();574 value.serialize(&mut out)?;613 value.serialize(s, &mut out)?;575 Ok(Val::Obj(out.build()))614 Ok(Val::Obj(out.build()))576 }615 }577616583 let fields_serialize = fields.iter().map(TypedField::expand_serialize);622 let fields_serialize = fields623 .iter()624 .map(TypedField::expand_serialize)625 .collect::<Result<Vec<_>>>()?;584626585 Ok(quote! {627 Ok(quote! {586 const _: () = {628 const _: () = {587 use ::jrsonnet_evaluator::{629 use ::jrsonnet_evaluator::{588 typed::{ComplexValType, Typed, TypedObj, CheckType},630 typed::{ComplexValType, Typed, TypedObj, CheckType},589 Val,631 Val, State,590 error::{LocError, Error},632 error::{LocError, Error, Result},591 ObjValueBuilder, ObjValue,633 ObjValueBuilder, ObjValue,592 };634 };593635594 #typed636 #typed595637596 impl #ident {638 impl TypedObj for #ident {597 fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {639 fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<(), LocError> {598 #(#fields_serialize)*640 #(#fields_serialize)*599641600 Ok(())642 Ok(())601 }643 }602 fn parse(obj: &ObjValue) -> Result<Self, LocError> {644 fn parse(obj: &ObjValue, s: State) -> Result<Self, LocError> {603 Ok(Self {645 Ok(Self {604 #(#fields_parse)*646 #(#fields_parse)*605 })647 })