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
before · crates/jrsonnet-macros/src/lib.rs
1use proc_macro2::TokenStream;2use quote::quote;3use syn::{4	parenthesized,5	parse::{Parse, ParseStream},6	parse_macro_input,7	punctuated::Punctuated,8	spanned::Spanned,9	token::Comma,10	Attribute, DeriveInput, Error, FnArg, GenericArgument, Ident, ItemFn, LitStr, Pat, Path,11	PathArguments, Result, ReturnType, Token, Type,12};1314fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>15where16	Ident: PartialEq<I>,17{18	let attrs = attrs19		.iter()20		.filter(|a| a.path.is_ident(&ident))21		.collect::<Vec<_>>();22	if attrs.len() > 1 {23		return Err(Error::new(24			attrs[1].span(),25			"this attribute may be specified only once",26		));27	} else if attrs.is_empty() {28		return Ok(None);29	}30	let attr = attrs[0];31	let attr = attr.parse_args::<A>()?;3233	Ok(Some(attr))34}3536fn path_is(path: &Path, needed: &str) -> bool {37	path.leading_colon.is_none()38		&& !path.segments.is_empty()39		&& path.segments.iter().last().unwrap().ident == needed40}4142fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {43	match ty {44		Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {45			let args = &path.path.segments.iter().last().unwrap().arguments;46			Some(args)47		}48		_ => None,49	}50}5152fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {53	Ok(if let Some(args) = type_is_path(ty, "Option") {54		// It should have only on angle-bracketed param ("<String>"):55		let generic_arg = match args {56			PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),57			_ => return Err(Error::new(args.span(), "missing option generic")),58		};59		// This argument must be a type:60		match generic_arg {61			GenericArgument::Type(ty) => Some(ty),62			_ => {63				return Err(Error::new(64					generic_arg.span(),65					"option generic should be a type",66				))67			}68		}69	} else {70		None71	})72}7374struct Field {75	name: Ident,76	_colon: Token![:],77	ty: Type,78}79impl Parse for Field {80	fn parse(input: ParseStream) -> syn::Result<Self> {81		Ok(Self {82			name: input.parse()?,83			_colon: input.parse()?,84			ty: input.parse()?,85		})86	}87}8889mod kw {90	syn::custom_keyword!(fields);91	syn::custom_keyword!(rename);92	syn::custom_keyword!(flatten);93}9495struct EmptyAttr;96impl Parse for EmptyAttr {97	fn parse(_input: ParseStream) -> Result<Self> {98		Ok(Self)99	}100}101102struct BuiltinAttrs {103	fields: Vec<Field>,104}105impl Parse for BuiltinAttrs {106	fn parse(input: ParseStream) -> syn::Result<Self> {107		if input.is_empty() {108			return Ok(Self { fields: Vec::new() });109		}110		input.parse::<kw::fields>()?;111		let fields;112		parenthesized!(fields in input);113		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;114		Ok(Self {115			fields: p.into_iter().collect(),116		})117	}118}119120enum ArgInfo {121	Normal {122		ty: Box<Type>,123		is_option: bool,124		name: String,125		cfg_attrs: Vec<Attribute>,126		// ident: Ident,127	},128	Lazy {129		is_option: bool,130		name: String,131	},132	State,133	Location,134	This,135}136137impl ArgInfo {138	fn parse(arg: &FnArg) -> Result<Self> {139		let arg = match arg {140			FnArg::Receiver(_) => unreachable!(),141			FnArg::Typed(a) => a,142		};143		let ident = match &arg.pat as &Pat {144			Pat::Ident(i) => i.ident.clone(),145			_ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),146		};147		let ty = &arg.ty;148		if type_is_path(ty, "State").is_some() {149			return Ok(Self::State);150		} else if type_is_path(ty, "CallLocation").is_some() {151			return Ok(Self::Location);152		} else if type_is_path(ty, "Self").is_some() {153			return Ok(Self::This);154		} else if type_is_path(ty, "LazyVal").is_some() {155			return Ok(Self::Lazy {156				is_option: false,157				name: ident.to_string(),158			});159		}160161		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {162			if type_is_path(ty, "LazyVal").is_some() {163				return Ok(Self::Lazy {164					is_option: true,165					name: ident.to_string(),166				});167			}168169			(true, Box::new(ty.clone()))170		} else {171			(false, ty.clone())172		};173174		let cfg_attrs = arg175			.attrs176			.iter()177			.filter(|a| a.path.is_ident("cfg"))178			.cloned()179			.collect();180181		Ok(Self::Normal {182			ty,183			is_option,184			name: ident.to_string(),185			cfg_attrs,186		})187	}188}189190#[proc_macro_attribute]191pub fn builtin(192	attr: proc_macro::TokenStream,193	item: proc_macro::TokenStream,194) -> proc_macro::TokenStream {195	let attr = parse_macro_input!(attr as BuiltinAttrs);196	let item: ItemFn = parse_macro_input!(item);197198	match builtin_inner(attr, item) {199		Ok(v) => v.into(),200		Err(e) => e.into_compile_error().into(),201	}202}203204fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {205	let result = match fun.sig.output {206		ReturnType::Default => {207			return Err(Error::new(208				fun.sig.span(),209				"builtin should return something",210			))211		}212		ReturnType::Type(_, ref ty) => ty.clone(),213	};214	let result_inner = if let Some(args) = type_is_path(&result, "Result") {215		let generic_arg = match args {216			PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),217			_ => return Err(Error::new(args.span(), "missing result generic")),218		};219		// This argument must be a type:220		match generic_arg {221			GenericArgument::Type(ty) => ty,222			_ => {223				return Err(Error::new(224					generic_arg.span(),225					"option generic should be a type",226				))227			}228		}229	} else {230		return Err(Error::new(result.span(), "return value should be result"));231	};232233	let args = fun234		.sig235		.inputs236		.iter()237		.map(ArgInfo::parse)238		.collect::<Result<Vec<_>>>()?;239240	let params_desc = args.iter().flat_map(|a| match a {241		ArgInfo::Normal {242			is_option,243			name,244			cfg_attrs,245			..246		} => Some(quote! {247			#(#cfg_attrs)*248			BuiltinParam {249				name: std::borrow::Cow::Borrowed(#name),250				has_default: #is_option,251			},252		}),253		ArgInfo::Lazy { is_option, name } => Some(quote! {254			BuiltinParam {255				name: std::borrow::Cow::Borrowed(#name),256				has_default: #is_option,257			},258		}),259		ArgInfo::State => None,260		ArgInfo::Location => None,261		ArgInfo::This => None,262	});263264	let pass = args.iter().map(|a| match a {265		ArgInfo::Normal {266			ty,267			is_option,268			name,269			cfg_attrs,270		} => {271			let eval = quote! {s.push_description(272				|| format!("argument <{}> evaluation", #name),273				|| <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),274			)?};275			let value = if *is_option {276				quote! {if let Some(value) = parsed.get(#name) {277					Some(#eval)278				} else {279					None280				},}281			} else {282				quote! {{283					let value = parsed.get(#name).expect("args shape is checked");284					#eval285				},}286			};287			quote! {288				#(#cfg_attrs)*289				#value290			}291		}292		ArgInfo::Lazy { is_option, name } => {293			if *is_option {294				quote! {if let Some(value) = parsed.get(#name) {295					Some(value.clone())296				} else {297					None298				}}299			} else {300				quote! {301					parsed.get(#name).expect("args shape is correct").clone(),302				}303			}304		}305		ArgInfo::State => quote! {s.clone(),},306		ArgInfo::Location => quote! {location,},307		ArgInfo::This => quote! {self,},308	});309310	let fields = attr.fields.iter().map(|field| {311		let name = &field.name;312		let ty = &field.ty;313		quote! {314			pub #name: #ty,315		}316	});317318	let name = &fun.sig.ident;319	let vis = &fun.vis;320	let static_ext = if attr.fields.is_empty() {321		quote! {322			impl #name {323				pub const INST: &'static dyn StaticBuiltin = &#name {};324			}325			impl StaticBuiltin for #name {}326		}327	} else {328		quote! {}329	};330	let static_derive_copy = if attr.fields.is_empty() {331		quote! {, Copy}332	} else {333		quote! {}334	};335336	Ok(quote! {337		#fun338		#[doc(hidden)]339		#[allow(non_camel_case_types)]340		#[derive(Clone, gcmodule::Trace #static_derive_copy)]341		#vis struct #name {342			#(#fields)*343		}344		const _: () = {345			use ::jrsonnet_evaluator::{346				State,347				function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},348				error::Result, Context,349				parser::ExprLocation,350			};351			const PARAMS: &'static [BuiltinParam] = &[352				#(#params_desc)*353			];354355			#static_ext356			impl Builtin for #name357			where358				Self: 'static359			{360				fn name(&self) -> &str {361					stringify!(#name)362				}363				fn params(&self) -> &[BuiltinParam] {364					PARAMS365				}366				fn call(&self, s: State, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {367					let parsed = parse_builtin_call(s.clone(), ctx, &PARAMS, args, false)?;368369					let result: #result = #name(#(#pass)*);370					let result = result?;371					<#result_inner>::into_untyped(result, s)372				}373			}374		};375	})376}377378#[derive(Default)]379struct TypedAttr {380	rename: Option<String>,381	flatten: bool,382}383impl Parse for TypedAttr {384	fn parse(input: ParseStream) -> syn::Result<Self> {385		let mut out = Self::default();386		loop {387			let lookahead = input.lookahead1();388			if lookahead.peek(kw::rename) {389				input.parse::<kw::rename>()?;390				input.parse::<Token![=]>()?;391				let name = input.parse::<LitStr>()?;392				if out.rename.is_some() {393					return Err(Error::new(394						name.span(),395						"rename attribute may only be specified once",396					));397				}398				out.rename = Some(name.value());399			} else if lookahead.peek(kw::flatten) {400				input.parse::<kw::flatten>()?;401				out.flatten = true;402			} else if input.is_empty() {403				break;404			} else {405				return Err(lookahead.error());406			}407			if input.peek(Token![,]) {408				input.parse::<Token![,]>()?;409			} else {410				break;411			}412		}413		// input.parse::<kw::rename>()?;414		// input.parse::<Token![=]>()?;415		// let rename = input.parse::<LitStr>()?.value();416		Ok(out)417	}418}419420struct TypedField<'f>(&'f syn::Field, TypedAttr);421impl<'f> TypedField<'f> {422	fn try_new(field: &'f syn::Field) -> Result<Self> {423		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();424		if field.ident.is_none() {425			return Err(Error::new(426				field.span(),427				"this field should appear in output object, but it has no visible name",428			));429		}430		Ok(Self(field, attr))431	}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 output439	fn name(&self) -> Option<String> {440		if self.1.flatten {441			return None;442		}443		Some(444			self.1445				.rename446				.clone()447				.unwrap_or_else(|| self.ident().to_string()),448		)449	}450451	fn expand_field(&self) -> Option<TokenStream> {452		if self.is_option() {453			return None;454		}455		let name = self.name()?;456		let ty = &self.0.ty;457		Some(quote! {458			(#name, <#ty>::TYPE)459		})460	}461	fn expand_parse(&self) -> TokenStream {462		let ident = self.ident();463		let ty = &self.0.ty;464		if self.1.flatten {465			// optional flatten is handled in same way as serde466			return if self.is_option() {467				quote! {468					#ident: <#ty>::parse(&obj).ok(),469				}470			} else {471				quote! {472					#ident: <#ty>::parse(&obj)?,473				}474			};475		};476477		let name = self.name().unwrap();478		let value = if let Some(ty) = self.as_option() {479			quote! {480				if let Some(value) = obj.get(#name.into())? {481					Some(<#ty>::try_from(vakue)?)482				} else {483					None484				}485			}486		} else {487			quote! {488				<#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?489			}490		};491492		quote! {493			#ident: #value,494		}495	}496	fn expand_serialize(&self) -> TokenStream {497		let ident = self.ident();498		if let Some(name) = self.name() {499			if self.is_option() {500				quote! {501					if let Some(value) = self.#ident {502						out.member(#name.into()).value(value.try_into()?)?;503					}504				}505			} else {506				quote! {507					out.member(#name.into()).value(self.#ident.try_into()?)?;508				}509			}510		} else if self.is_option() {511			quote! {512				if let Some(value) = self.#ident {513					value.serialize(out)?;514				}515			}516		} else {517			quote! {518				self.#ident.serialize(out)?;519			}520		}521	}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}530531#[proc_macro_derive(Typed, attributes(typed))]532pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {533	let input = parse_macro_input!(item as DeriveInput);534535	match derive_typed_inner(input) {536		Ok(v) => v.into(),537		Err(e) => e.to_compile_error().into(),538	}539}540541fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {542	let data = match &input.data {543		syn::Data::Struct(s) => s,544		_ => return Err(Error::new(input.span(), "only structs supported")),545	};546547	let ident = &input.ident;548	let fields = data549		.fields550		.iter()551		.map(TypedField::try_new)552		.collect::<Result<Vec<_>>>()?;553554	let typed = {555		let fields = fields556			.iter()557			.flat_map(TypedField::expand_field)558			.collect::<Vec<_>>();559		let len = fields.len();560		quote! {561			const ITEMS: [(&'static str, &'static ComplexValType); #len] = [562				#(#fields,)*563			];564			impl Typed for #ident {565				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);566567				fn from_untyped(value: Val, s: State) -> Result<Self> {568					let obj = value.as_obj().expect("shape is correct");569					Self::parse(&obj)570				}571572				fn into_untyped(value: Self, s: State) -> Result<Val> {573					let mut out = ObjValueBuilder::new();574					value.serialize(&mut out)?;575					Ok(Val::Obj(out.build()))576				}577578			}579		}580	};581582	let fields_parse = fields.iter().map(TypedField::expand_parse);583	let fields_serialize = fields.iter().map(TypedField::expand_serialize);584585	Ok(quote! {586		const _: () = {587			use ::jrsonnet_evaluator::{588				typed::{ComplexValType, Typed, TypedObj, CheckType},589				Val,590				error::{LocError, Error},591				ObjValueBuilder, ObjValue,592			};593594			#typed595596			impl #ident {597				fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {598					#(#fields_serialize)*599600					Ok(())601				}602				fn parse(obj: &ObjValue) -> Result<Self, LocError> {603					Ok(Self {604						#(#fields_parse)*605					})606				}607			}608		};609	})610}