git.delta.rocks / jrsonnet / refs/commits / 7efefe23889c

difftreelog

feature: non-static builtins

Yaroslav Bolyukin2022-03-07parent: #86c60d8.patch.diff
in: master

1 file changed

modifiedcrates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth
before · crates/jrsonnet-macros/src/lib.rs
1use quote::quote;2use syn::{3	parse_macro_input, FnArg, GenericArgument, ItemFn, Pat, PatType, Path, PathArguments, Type,4};56fn is_location_arg(t: &PatType) -> bool {7	t.attrs.iter().any(|a| a.path.is_ident("location"))8}910trait RetainHad<T> {11	fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool;12}13impl<T> RetainHad<T> for Vec<T> {14	fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool {15		let before = self.len();16		self.retain(h);17		let after = self.len();18		before != after19	}20}2122fn extract_type_from_option(ty: &Type) -> Option<&Type> {23	fn path_is_option(path: &Path) -> bool {24		path.leading_colon.is_none()25			&& path.segments.len() == 126			&& path.segments.iter().next().unwrap().ident == "Option"27	}2829	match ty {30		Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => {31			// Get the first segment of the path (there is only one, in fact: "Option"):32			let type_params = &typepath.path.segments.iter().next().unwrap().arguments;33			// It should have only on angle-bracketed param ("<String>"):34			let generic_arg = match type_params {35				PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),36				_ => panic!("missing option generic"),37			};38			// This argument must be a type:39			match generic_arg {40				GenericArgument::Type(ty) => Some(ty),41				_ => panic!("option generic should be a type"),42			}43		}44		_ => None,45	}46}4748#[proc_macro_attribute]49pub fn builtin(50	_attr: proc_macro::TokenStream,51	item: proc_macro::TokenStream,52) -> proc_macro::TokenStream {53	// syn::ItemFn::parse(input)54	let mut fun: ItemFn = parse_macro_input!(item);5556	let result = match fun.sig.output {57		syn::ReturnType::Default => panic!("builtin should return something"),58		syn::ReturnType::Type(_, ref ty) => ty.clone(),59	};6061	let params = fun62		.sig63		.inputs64		.iter()65		.map(|i| match i {66			FnArg::Receiver(_) => unreachable!(),67			FnArg::Typed(t) => t,68		})69		.filter(|a| !is_location_arg(a))70		.map(|t| {71			let ident = match &t.pat as &Pat {72				Pat::Ident(i) => i.ident.to_string(),73				_ => panic!("only idents supported yet"),74			};75			let optional = extract_type_from_option(&t.ty).is_some();76			quote! {77				BuiltinParam {78					name: std::borrow::Cow::Borrowed(#ident),79					has_default: #optional,80				}81			}82		})83		.collect::<Vec<_>>();8485	let args = fun86		.sig87		.inputs88		.iter_mut()89		.map(|i| match i {90			FnArg::Receiver(_) => unreachable!(),91			FnArg::Typed(t) => t,92		})93		.map(|t| {94			let is_location = t.attrs.retain_had(|a| !a.path.is_ident("location"));95			if is_location {96				quote! {{97					loc98				}}99			} else {100				let ident = match &t.pat as &Pat {101					Pat::Ident(i) => i.ident.to_string(),102					_ => panic!("only idents supported yet"),103				};104				let ty = &t.ty;105				if let Some(opt_ty) = extract_type_from_option(&t.ty) {106					quote! {{107						if let Some(value) = parsed.get(#ident) {108							Some(::jrsonnet_evaluator::push_description_frame(109								|| format!("argument <{}> evaluation", #ident),110								|| <#opt_ty>::try_from(value.evaluate()?),111							)?)112						} else {113							None114						}115					}}116				} else {117					quote! {{118						let value = parsed.get(#ident).unwrap();119120						::jrsonnet_evaluator::push_description_frame(121							|| format!("argument <{}> evaluation", #ident),122							|| <#ty>::try_from(value.evaluate()?),123						)?124					}}125				}126			}127		})128		.collect::<Vec<_>>();129130	let name = &fun.sig.ident;131	let vis = &fun.vis;132	(quote! {133		#fun134		#[doc(hidden)]135		#[allow(non_camel_case_types)]136		#[derive(Clone, Copy, gcmodule::Trace)]137		#vis struct #name {}138		const _: () = {139			use ::jrsonnet_evaluator::{140				function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},141				error::Result, Context,142				parser::ExprLocation,143			};144			const PARAMS: &'static [BuiltinParam] = &[145				#(#params),*146			];147148			impl #name {149				pub const INST: &'static dyn StaticBuiltin = &#name {};150			}151			impl StaticBuiltin for #name {}152			impl Builtin for #name153			where154				Self: 'static155			{156				fn name(&self) -> &str {157					stringify!(#name)158				}159				fn params(&self) -> &[BuiltinParam] {160					PARAMS161				}162				fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {163					let parsed = parse_builtin_call(context, &PARAMS, args, false)?;164165					let result: #result = #name(#(#args),*);166					let result = result?;167					result.try_into()168				}169			}170		};171	})172	.into()173}
after · 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, FnArg, GenericArgument, Ident, ItemFn, Pat, PatType, Path, PathArguments, Token,5	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}