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}