git.delta.rocks / jrsonnet / refs/commits / 590966465ed7

difftreelog

test basic interop checks

Yaroslav Bolyukin2022-04-22parent: #321e7ee.patch.diff
in: master

7 files changed

modifiedcrates/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>;
 
modifiedcrates/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
modifiedcrates/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())
 	}
 }
addedcrates/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(())
+}
addedcrates/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)?,
+			)
+		}
+	}};
+}
addedcrates/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(())
+}
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
6 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}
9495
95struct EmptyAttr;96struct EmptyAttr;
135}136}
136137
137impl 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 }
159
160 match &ty as &Type {
161 Type::Reference(r) if type_is_path(&r.elem, &name).is_some() => return Ok(Self::This),
162 _ => {}
163 }
160164
161 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 };
232236
237 let name = fun.sig.ident.to_string();
233 let args = fun238 let args = fun
234 .sig239 .sig
235 .inputs240 .inputs
236 .iter()241 .iter()
237 .map(ArgInfo::parse)242 .map(|arg| ArgInfo::parse(&name, arg))
238 .collect::<Result<Vec<_>>>()?;243 .collect::<Result<Vec<_>>>()?;
239244
240 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 optionals
388 /// 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}
419438
420struct 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 ident
450 } 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.0
434 .ident
435 .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 output
439 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.attr
445 .rename490 .rename
446 .clone()491 .clone()
447 .unwrap_or_else(|| self.ident().to_string()),492 .unwrap_or_else(|| self.ident.to_string()),
448 )493 )
449 }494 }
450495
451 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 serde
466 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 };
476521
477 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 None
484 }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 };
491536
492 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 }
522
523 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}
530569
531#[proc_macro_derive(Typed, attributes(typed))]570#[proc_macro_derive(Typed, attributes(typed))]
548 let fields = data587 let fields = data
549 .fields588 .fields
550 .iter()589 .iter()
551 .map(TypedField::try_new)590 .map(TypedField::parse)
552 .collect::<Result<Vec<_>>>()?;591 .collect::<Result<Vec<_>>>()?;
553592
554 let typed = {593 let typed = {
566605
567 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 }
571610
572 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 }
577616
583 let fields_serialize = fields.iter().map(TypedField::expand_serialize);622 let fields_serialize = fields
623 .iter()
624 .map(TypedField::expand_serialize)
625 .collect::<Result<Vec<_>>>()?;
584626
585 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 };
593635
594 #typed636 #typed
595637
596 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)*
599641
600 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 })