git.delta.rocks / jrsonnet / refs/commits / efea8379ff42

difftreelog

feat rename/flatten field in derive(Typed)

Yaroslav Bolyukin2022-04-17parent: #78d82df.patch.diff
in: master

4 files changed

modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/lib.rs
+++ b/crates/jrsonnet-evaluator/src/lib.rs
@@ -1280,7 +1280,8 @@
 		#[derive(Typed, PartialEq, Debug)]
 		struct MyTyped {
 			a: u32,
-			b: String,
+			#[typed(rename = "b")]
+			c: String,
 		}
 
 		#[test]
@@ -1293,7 +1294,7 @@
 				typed,
 				MyTyped {
 					a: 14,
-					b: "Hello, world!".to_string()
+					c: "Hello, world!".to_string()
 				}
 			);
 			es.settings_mut().globals.insert(
modifiedcrates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -8,9 +8,19 @@
 	error::{Error::*, LocError, Result},
 	throw,
 	typed::CheckType,
-	ArrValue, FuncVal, IndexableVal, ObjValue, Val,
+	ArrValue, FuncVal, IndexableVal, ObjValue, ObjValueBuilder, Val,
 };
 
+pub trait TypedObj: Typed {
+	fn serialize(self, out: &mut ObjValueBuilder) -> Result<()>;
+	fn parse(obj: &ObjValue) -> Result<Self>;
+	fn into_object(self) -> Result<ObjValue> {
+		let mut builder = ObjValueBuilder::new();
+		self.serialize(&mut builder)?;
+		Ok(builder.build())
+	}
+}
+
 pub trait Typed: TryFrom<Val, Error = LocError> + TryInto<Val, Error = LocError> {
 	const TYPE: &'static ComplexValType;
 }
modifiedcrates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -15,7 +15,7 @@
 pub enum TypeError {
 	#[error("expected {0}, got {1}")]
 	ExpectedGot(ComplexValType, ValType),
-	#[error("missing property {0} from {1:?}")]
+	#[error("missing property {0} from {1}")]
 	MissingProperty(#[skip_trace] Rc<str>, ComplexValType),
 	#[error("every failed from {0}:\n{1}")]
 	UnionFailed(ComplexValType, TypeLocErrorList),
modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-macros/src/lib.rs
1use quote::{quote, quote_spanned};2use syn::{3	parenthesized, parse::Parse, parse_macro_input, punctuated::Punctuated, spanned::Spanned,4	token::Comma, DeriveInput, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path,5	PathArguments, Token, 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}257258#[proc_macro_derive(Typed)]259pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {260	let input = parse_macro_input!(item as DeriveInput);261	let data = match &input.data {262		syn::Data::Struct(s) => s,263		_ => {264			return syn::Error::new(input.span(), "only structs supported")265				.to_compile_error()266				.into()267		}268	};269270	let ident = &input.ident;271272	let fields_def = data.fields.iter().map(|f| {273		let name = f274			.ident275			.as_ref()276			.expect("only named fields supported")277			.to_string();278		let ty = &f.ty;279		quote! {280			(#name, #ty::TYPE),281		}282	});283	let fields_parse = data.fields.iter().map(|f| {284		let ident = f.ident.as_ref().unwrap();285		let name = ident.to_string();286		let ty = &f.ty;287		quote! {288			#ident: #ty::try_from(obj.get(#name.into())?.expect("shape is correct"))?,289		}290	});291	let fields_serialize = data.fields.iter().map(|f| {292		let ident = f.ident.as_ref().unwrap();293		let name = ident.to_string();294		quote! {295			out.member(#name.into()).value(self.#ident.try_into()?);296		}297	});298	let field_count = data.fields.len();299300	quote! {301		const _: () = {302			use ::jrsonnet_evaluator::{303				typed::{ComplexValType, Typed, CheckType},304				Val,305				error::LocError,306				obj::ObjValueBuilder,307			};308309			const ITEMS: [(&'static str, &'static ComplexValType); #field_count] = [310				#(#fields_def)*311			];312			impl Typed for #ident {313				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);314			}315316			impl TryFrom<Val> for #ident {317				type Error = LocError;318				fn try_from(value: Val) -> Result<Self, Self::Error> {319					<Self as Typed>::TYPE.check(&value)?;320					let obj = value.as_obj().expect("shape is correct");321322					Ok(Self {323						#(#fields_parse)*324					})325				}326			}327			impl TryInto<Val> for #ident {328				type Error = LocError;329				fn try_into(self) -> Result<Val, Self::Error> {330					let mut out = ObjValueBuilder::new();331					#(#fields_serialize)*332					Ok(Val::Obj(out.build()))333				}334			}335			()336		};337	}338	.into()339}
after · crates/jrsonnet-macros/src/lib.rs
1use proc_macro2::TokenStream;2use quote::{quote, quote_spanned};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, PatType,11	Path, PathArguments, Result, 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 is_location_arg(t: &PatType) -> bool {37	t.attrs.iter().any(|a| a.path.is_ident("location"))38}39fn is_self_arg(t: &PatType) -> bool {40	t.attrs.iter().any(|a| a.path.is_ident("self"))41}4243trait RetainHad<T> {44	fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;45}46impl<T> RetainHad<T> for Vec<T> {47	fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {48		let before = self.len();49		self.retain(h);50		let after = self.len();51		before != after52	}53}5455fn extract_type_from_option(ty: &Type) -> Option<&Type> {56	fn path_is_option(path: &Path) -> bool {57		path.leading_colon.is_none()58			&& path.segments.len() == 159			&& path.segments.iter().next().unwrap().ident == "Option"60	}6162	match ty {63		Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {64			// Get the first segment of the path (there is only one, in fact: "Option"):65			let type_params = &typepath.path.segments.iter().next().unwrap().arguments;66			// It should have only on angle-bracketed param ("<String>"):67			let generic_arg = match type_params {68				PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),69				_ => panic!("missing option generic"),70			};71			// This argument must be a type:72			match generic_arg {73				GenericArgument::Type(ty) => Some(ty),74				_ => panic!("option generic should be a type"),75			}76		}77		_ => None,78	}79}8081struct Field {82	name: Ident,83	_colon: Token![:],84	ty: Type,85}86impl Parse for Field {87	fn parse(input: ParseStream) -> syn::Result<Self> {88		Ok(Self {89			name: input.parse()?,90			_colon: input.parse()?,91			ty: input.parse()?,92		})93	}94}9596mod kw {97	syn::custom_keyword!(fields);98	syn::custom_keyword!(rename);99	syn::custom_keyword!(flatten);100}101102struct EmptyAttr;103impl Parse for EmptyAttr {104	fn parse(input: ParseStream) -> Result<Self> {105		Ok(Self)106	}107}108109struct BuiltinAttrs {110	fields: Vec<Field>,111}112impl Parse for BuiltinAttrs {113	fn parse(input: ParseStream) -> syn::Result<Self> {114		if input.is_empty() {115			return Ok(Self { fields: Vec::new() });116		}117		input.parse::<kw::fields>()?;118		let fields;119		parenthesized!(fields in input);120		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;121		Ok(Self {122			fields: p.into_iter().collect(),123		})124	}125}126127#[proc_macro_attribute]128pub fn builtin(129	attr: proc_macro::TokenStream,130	item: proc_macro::TokenStream,131) -> proc_macro::TokenStream {132	let attrs = parse_macro_input!(attr as BuiltinAttrs);133	let mut fun: ItemFn = parse_macro_input!(item);134135	let result = match fun.sig.output {136		syn::ReturnType::Default => {137			return quote_spanned! { fun.sig.span() =>138				compile_error!("builtins should return something");139			}140			.into()141		}142		syn::ReturnType::Type(_, ref ty) => ty.clone(),143	};144145	let params = fun146		.sig147		.inputs148		.iter()149		.map(|i| match i {150			FnArg::Receiver(_) => unreachable!(),151			FnArg::Typed(t) => t,152		})153		.filter(|a| !is_location_arg(a) && !is_self_arg(a))154		.map(|t| {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 optional = extract_type_from_option(&t.ty).is_some();165			quote! {166				BuiltinParam {167					name: std::borrow::Cow::Borrowed(#ident),168					has_default: #optional,169				}170			}171		})172		.collect::<Vec<_>>();173174	let args = fun175		.sig176		.inputs177		.iter_mut()178		.map(|i| match i {179			FnArg::Receiver(_) => unreachable!(),180			FnArg::Typed(t) => t,181		})182		.map(|t| {183			if t.attrs.retain_had(|a| !a.path.is_ident("location")) {184				quote! {{185					loc186				}}187			} else if t.attrs.retain_had(|a| !a.path.is_ident("self")) {188				quote! {{189					self190				}}191			} else {192				let ident = match &t.pat as &Pat {193					Pat::Ident(i) => i.ident.to_string(),194					_ => {195						return quote_spanned! { t.pat.span() =>196							compile_error!("args should be plain identifiers")197						}198						.into()199					}200				};201				let ty = &t.ty;202				if let Some(opt_ty) = extract_type_from_option(&t.ty) {203					quote! {{204						if let Some(value) = parsed.get(#ident) {205							Some(::jrsonnet_evaluator::push_description_frame(206								|| format!("argument <{}> evaluation", #ident),207								|| <#opt_ty>::try_from(value.evaluate()?),208							)?)209						} else {210							None211						}212					}}213				} else {214					quote! {{215						let value = parsed.get(#ident).unwrap();216217						::jrsonnet_evaluator::push_description_frame(218							|| format!("argument <{}> evaluation", #ident),219							|| <#ty>::try_from(value.evaluate()?),220						)?221					}}222				}223			}224		})225		.collect::<Vec<_>>();226227	let fields = attrs.fields.iter().map(|field| {228		let name = &field.name;229		let ty = &field.ty;230		quote! {231			pub #name: #ty,232		}233	});234235	let name = &fun.sig.ident;236	let vis = &fun.vis;237	let static_ext = if attrs.fields.is_empty() {238		quote! {239			impl #name {240				pub const INST: &'static dyn StaticBuiltin = &#name {};241			}242			impl StaticBuiltin for #name {}243		}244	} else {245		quote! {}246	};247	let static_derive_copy = if attrs.fields.is_empty() {248		quote! {, Copy}249	} else {250		quote! {}251	};252253	(quote! {254		#fun255		#[doc(hidden)]256		#[allow(non_camel_case_types)]257		#[derive(Clone, gcmodule::Trace #static_derive_copy)]258		#vis struct #name {259			#(#fields)*260		}261		const _: () = {262			use ::jrsonnet_evaluator::{263				function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},264				error::Result, Context,265				parser::ExprLocation,266			};267			const PARAMS: &'static [BuiltinParam] = &[268				#(#params),*269			];270271			#static_ext272			impl Builtin for #name273			where274				Self: 'static275			{276				fn name(&self) -> &str {277					stringify!(#name)278				}279				fn params(&self) -> &[BuiltinParam] {280					PARAMS281				}282				fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {283					let parsed = parse_builtin_call(context, &PARAMS, args, false)?;284285					let result: #result = #name(#(#args),*);286					let result = result?;287					result.try_into()288				}289			}290		};291	})292	.into()293}294295#[derive(Default)]296struct TypedAttr {297	rename: Option<String>,298	flatten: bool,299}300impl Parse for TypedAttr {301	fn parse(input: ParseStream) -> syn::Result<Self> {302		let mut out = Self::default();303		loop {304			let lookahead = input.lookahead1();305			if lookahead.peek(kw::rename) {306				input.parse::<kw::rename>()?;307				input.parse::<Token![=]>()?;308				let name = input.parse::<LitStr>()?;309				if out.rename.is_some() {310					return Err(Error::new(311						name.span(),312						"rename attribute may only be specified once",313					));314				}315				out.rename = Some(name.value());316			} else if lookahead.peek(kw::flatten) {317				input.parse::<kw::flatten>()?;318				out.flatten = true;319			} else if input.is_empty() {320				break;321			} else {322				return Err(lookahead.error());323			}324			if input.peek(Token![,]) {325				input.parse::<Token![,]>()?;326			} else {327				break;328			}329		}330		// input.parse::<kw::rename>()?;331		// input.parse::<Token![=]>()?;332		// let rename = input.parse::<LitStr>()?.value();333		Ok(out)334	}335}336337struct TypedField<'f>(&'f syn::Field, TypedAttr);338impl<'f> TypedField<'f> {339	fn try_new(field: &'f syn::Field) -> Result<Self> {340		let attr =341			parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_else(Default::default);342		if field.ident.is_none() {343			return Err(Error::new(344				field.span(),345				"this field should appear in output object, but it has no visible name",346			));347		}348		Ok(Self(field, attr))349	}350	fn ident(&self) -> Ident {351		self.0352			.ident353			.clone()354			.expect("constructor disallows fields without name")355	}356	/// None if this field is flattened in jsonnet output357	fn name(&self) -> Option<String> {358		if self.1.flatten {359			return None;360		}361		Some(362			self.1363				.rename364				.clone()365				.unwrap_or_else(|| self.ident().to_string()),366		)367	}368369	fn expand_shallow_field(&self) -> Option<TokenStream> {370		if self.is_option() {371			return None;372		}373		let name = self.name()?;374		Some(quote! {375			(#name, ComplexValType::Any)376		})377	}378	fn expand_field(&self) -> Option<TokenStream> {379		if self.is_option() {380			return None;381		}382		let name = self.name()?;383		let ty = &self.0.ty;384		Some(quote! {385			(#name, <#ty>::TYPE)386		})387	}388	fn expand_parse(&self) -> TokenStream {389		let ident = self.ident();390		let ty = &self.0.ty;391		if self.1.flatten {392			// optional flatten is handled in same way as serde393			return if self.is_option() {394				quote! {395					#ident: <#ty>::parse(&obj).ok(),396				}397			} else {398				quote! {399					#ident: <#ty>::parse(&obj)?,400				}401			};402		};403404		let name = self.name().unwrap();405		let value = if let Some(ty) = self.as_option() {406			quote! {407				if let Some(value) = obj.get(#name.into())? {408					Some(<#ty>::try_from(vakue)?)409				} else {410					None411				}412			}413		} else {414			quote! {415				<#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?416			}417		};418419		quote! {420			#ident: #value,421		}422	}423	fn expand_serialize(&self) -> TokenStream {424		let ident = self.ident();425		if let Some(name) = self.name() {426			if self.is_option() {427				quote! {428					if let Some(value) = self.#ident {429						out.member(#name.into()).value(value.try_into()?);430					}431				}432			} else {433				quote! {434					out.member(#name.into()).value(self.#ident.try_into()?);435				}436			}437		} else {438			if self.is_option() {439				quote! {440					if let Some(value) = self.#ident {441						value.serialize(out)?;442					}443				}444			} else {445				quote! {446					self.#ident.serialize(out)?;447				}448			}449		}450	}451452	fn as_option(&self) -> Option<&Type> {453		extract_type_from_option(&self.0.ty)454	}455	fn is_option(&self) -> bool {456		self.as_option().is_some()457	}458}459460#[proc_macro_derive(Typed, attributes(typed))]461pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {462	let input = parse_macro_input!(item as DeriveInput);463	let data = match &input.data {464		syn::Data::Struct(s) => s,465		_ => {466			return syn::Error::new(input.span(), "only structs supported")467				.to_compile_error()468				.into()469		}470	};471472	let ident = &input.ident;473	let fields = match data474		.fields475		.iter()476		.map(TypedField::try_new)477		.collect::<Result<Vec<_>>>()478	{479		Ok(v) => v,480		Err(e) => return e.to_compile_error().into(),481	};482483	let typed = {484		let fields = fields485			.iter()486			.flat_map(TypedField::expand_field)487			.collect::<Vec<_>>();488		let len = fields.len();489		quote! {490			const ITEMS: [(&'static str, &'static ComplexValType); #len] = [491				#(#fields,)*492			];493			impl Typed for #ident {494				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);495			}496		}497	};498499	let fields_parse = fields.iter().map(TypedField::expand_parse);500	let fields_serialize = fields.iter().map(TypedField::expand_serialize);501502	quote! {503		const _: () = {504			use ::jrsonnet_evaluator::{505				typed::{ComplexValType, Typed, TypedObj, CheckType},506				Val,507				error::{LocError, Error},508				ObjValueBuilder, ObjValue,509			};510511			#typed512513			impl #ident {514				fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {515					#(#fields_serialize)*516517					Ok(())518				}519				fn parse(obj: &ObjValue) -> Result<Self, LocError> {520					Ok(Self {521						#(#fields_parse)*522					})523				}524			}525526			impl TryFrom<Val> for #ident {527				type Error = LocError;528				fn try_from(value: Val) -> Result<Self, Self::Error> {529					let obj = value.as_obj().expect("shape is correct");530					Self::parse(&obj)531				}532			}533			impl TryInto<Val> for #ident {534				type Error = LocError;535				fn try_into(self) -> Result<Val, Self::Error> {536					let mut out = ObjValueBuilder::new();537					self.serialize(&mut out)?;538					Ok(Val::Obj(out.build()))539				}540			}541			()542		};543	}544	.into()545}