git.delta.rocks / jrsonnet / refs/commits / 4f4be44d138e

difftreelog

source

crates/jrsonnet-macros/src/lib.rs12.4 KiBsourcehistory
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		// ident: Ident,126	},127	Lazy {128		is_option: bool,129		name: String,130	},131	Location,132	This,133}134135impl ArgInfo {136	fn parse(arg: &FnArg) -> Result<Self> {137		let typed = match arg {138			FnArg::Receiver(_) => unreachable!(),139			FnArg::Typed(a) => a,140		};141		let ident = match &typed.pat as &Pat {142			Pat::Ident(i) => i.ident.clone(),143			_ => {144				return Err(Error::new(145					typed.pat.span(),146					"arg should be plain identifier",147				))148			}149		};150		let ty = &typed.ty;151		if type_is_path(ty, "CallLocation").is_some() {152			return Ok(Self::Location);153		} else if type_is_path(ty, "Self").is_some() {154			return Ok(Self::This);155		} else if type_is_path(ty, "LazyVal").is_some() {156			return Ok(Self::Lazy {157				is_option: false,158				name: ident.to_string(),159			});160		}161162		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {163			if type_is_path(ty, "LazyVal").is_some() {164				return Ok(Self::Lazy {165					is_option: true,166					name: ident.to_string(),167				});168			}169170			(true, Box::new(ty.clone()))171		} else {172			(false, ty.clone())173		};174175		Ok(Self::Normal {176			ty,177			is_option,178			name: ident.to_string(),179			// ident,180		})181	}182}183184#[proc_macro_attribute]185pub fn builtin(186	attr: proc_macro::TokenStream,187	item: proc_macro::TokenStream,188) -> proc_macro::TokenStream {189	let attr = parse_macro_input!(attr as BuiltinAttrs);190	let item: ItemFn = parse_macro_input!(item);191192	match builtin_inner(attr, item) {193		Ok(v) => v.into(),194		Err(e) => e.into_compile_error().into(),195	}196}197198fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {199	let result = match fun.sig.output {200		ReturnType::Default => {201			return Err(Error::new(202				fun.sig.span(),203				"builtin should return something",204			))205		}206		ReturnType::Type(_, ref ty) => ty.clone(),207	};208209	let args = fun210		.sig211		.inputs212		.iter()213		.map(ArgInfo::parse)214		.collect::<Result<Vec<_>>>()?;215216	let params_desc = args.iter().flat_map(|a| match a {217		ArgInfo::Normal {218			is_option, name, ..219		}220		| ArgInfo::Lazy { is_option, name } => Some(quote! {221			BuiltinParam {222				name: std::borrow::Cow::Borrowed(#name),223				has_default: #is_option,224			}225		}),226		ArgInfo::Location => None,227		ArgInfo::This => None,228	});229230	let pass = args.iter().map(|a| match a {231		ArgInfo::Normal {232			ty,233			is_option,234			name,235			// ident,236		} => {237			let eval = quote! {::jrsonnet_evaluator::push_description_frame(238				|| format!("argument <{}> evaluation", #name),239				|| <#ty>::try_from(value.evaluate()?),240			)?};241			if *is_option {242				quote! {if let Some(value) = parsed.get(#name) {243					Some(#eval)244				} else {245					None246				}}247			} else {248				quote! {{249					let value = parsed.get(#name).expect("args shape is checked");250					#eval251				}}252			}253		}254		ArgInfo::Lazy { is_option, name } => {255			if *is_option {256				quote! {if let Some(value) = parsed.get(#name) {257					Some(value.clone())258				} else {259					None260				}}261			} else {262				quote! {263					parsed.get(#name).expect("args shape is correct").clone()264				}265			}266		}267		ArgInfo::Location => quote! {location},268		ArgInfo::This => quote! {self},269	});270271	let fields = attr.fields.iter().map(|field| {272		let name = &field.name;273		let ty = &field.ty;274		quote! {275			pub #name: #ty,276		}277	});278279	let name = &fun.sig.ident;280	let vis = &fun.vis;281	let static_ext = if attr.fields.is_empty() {282		quote! {283			impl #name {284				pub const INST: &'static dyn StaticBuiltin = &#name {};285			}286			impl StaticBuiltin for #name {}287		}288	} else {289		quote! {}290	};291	let static_derive_copy = if attr.fields.is_empty() {292		quote! {, Copy}293	} else {294		quote! {}295	};296297	Ok(quote! {298		#fun299		#[doc(hidden)]300		#[allow(non_camel_case_types)]301		#[derive(Clone, gcmodule::Trace #static_derive_copy)]302		#vis struct #name {303			#(#fields)*304		}305		const _: () = {306			use ::jrsonnet_evaluator::{307				function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},308				error::Result, Context,309				parser::ExprLocation,310			};311			const PARAMS: &'static [BuiltinParam] = &[312				#(#params_desc),*313			];314315			#static_ext316			impl Builtin for #name317			where318				Self: 'static319			{320				fn name(&self) -> &str {321					stringify!(#name)322				}323				fn params(&self) -> &[BuiltinParam] {324					PARAMS325				}326				fn call(&self, context: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {327					let parsed = parse_builtin_call(context, &PARAMS, args, false)?;328329					let result: #result = #name(#(#pass),*);330					let result = result?;331					result.try_into()332				}333			}334		};335	})336}337338#[derive(Default)]339struct TypedAttr {340	rename: Option<String>,341	flatten: bool,342}343impl Parse for TypedAttr {344	fn parse(input: ParseStream) -> syn::Result<Self> {345		let mut out = Self::default();346		loop {347			let lookahead = input.lookahead1();348			if lookahead.peek(kw::rename) {349				input.parse::<kw::rename>()?;350				input.parse::<Token![=]>()?;351				let name = input.parse::<LitStr>()?;352				if out.rename.is_some() {353					return Err(Error::new(354						name.span(),355						"rename attribute may only be specified once",356					));357				}358				out.rename = Some(name.value());359			} else if lookahead.peek(kw::flatten) {360				input.parse::<kw::flatten>()?;361				out.flatten = true;362			} else if input.is_empty() {363				break;364			} else {365				return Err(lookahead.error());366			}367			if input.peek(Token![,]) {368				input.parse::<Token![,]>()?;369			} else {370				break;371			}372		}373		// input.parse::<kw::rename>()?;374		// input.parse::<Token![=]>()?;375		// let rename = input.parse::<LitStr>()?.value();376		Ok(out)377	}378}379380struct TypedField<'f>(&'f syn::Field, TypedAttr);381impl<'f> TypedField<'f> {382	fn try_new(field: &'f syn::Field) -> Result<Self> {383		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();384		if field.ident.is_none() {385			return Err(Error::new(386				field.span(),387				"this field should appear in output object, but it has no visible name",388			));389		}390		Ok(Self(field, attr))391	}392	fn ident(&self) -> Ident {393		self.0394			.ident395			.clone()396			.expect("constructor disallows fields without name")397	}398	/// None if this field is flattened in jsonnet output399	fn name(&self) -> Option<String> {400		if self.1.flatten {401			return None;402		}403		Some(404			self.1405				.rename406				.clone()407				.unwrap_or_else(|| self.ident().to_string()),408		)409	}410411	fn expand_field(&self) -> Option<TokenStream> {412		if self.is_option() {413			return None;414		}415		let name = self.name()?;416		let ty = &self.0.ty;417		Some(quote! {418			(#name, <#ty>::TYPE)419		})420	}421	fn expand_parse(&self) -> TokenStream {422		let ident = self.ident();423		let ty = &self.0.ty;424		if self.1.flatten {425			// optional flatten is handled in same way as serde426			return if self.is_option() {427				quote! {428					#ident: <#ty>::parse(&obj).ok(),429				}430			} else {431				quote! {432					#ident: <#ty>::parse(&obj)?,433				}434			};435		};436437		let name = self.name().unwrap();438		let value = if let Some(ty) = self.as_option() {439			quote! {440				if let Some(value) = obj.get(#name.into())? {441					Some(<#ty>::try_from(vakue)?)442				} else {443					None444				}445			}446		} else {447			quote! {448				<#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?449			}450		};451452		quote! {453			#ident: #value,454		}455	}456	fn expand_serialize(&self) -> TokenStream {457		let ident = self.ident();458		if let Some(name) = self.name() {459			if self.is_option() {460				quote! {461					if let Some(value) = self.#ident {462						out.member(#name.into()).value(value.try_into()?);463					}464				}465			} else {466				quote! {467					out.member(#name.into()).value(self.#ident.try_into()?);468				}469			}470		} else if self.is_option() {471			quote! {472				if let Some(value) = self.#ident {473					value.serialize(out)?;474				}475			}476		} else {477			quote! {478				self.#ident.serialize(out)?;479			}480		}481	}482483	fn as_option(&self) -> Option<&Type> {484		extract_type_from_option(&self.0.ty).unwrap()485	}486	fn is_option(&self) -> bool {487		self.as_option().is_some()488	}489}490491#[proc_macro_derive(Typed, attributes(typed))]492pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {493	let input = parse_macro_input!(item as DeriveInput);494495	match derive_typed_inner(input) {496		Ok(v) => v.into(),497		Err(e) => e.to_compile_error().into(),498	}499}500501fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {502	let data = match &input.data {503		syn::Data::Struct(s) => s,504		_ => return Err(Error::new(input.span(), "only structs supported")),505	};506507	let ident = &input.ident;508	let fields = data509		.fields510		.iter()511		.map(TypedField::try_new)512		.collect::<Result<Vec<_>>>()?;513514	let typed = {515		let fields = fields516			.iter()517			.flat_map(TypedField::expand_field)518			.collect::<Vec<_>>();519		let len = fields.len();520		quote! {521			const ITEMS: [(&'static str, &'static ComplexValType); #len] = [522				#(#fields,)*523			];524			impl Typed for #ident {525				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);526			}527		}528	};529530	let fields_parse = fields.iter().map(TypedField::expand_parse);531	let fields_serialize = fields.iter().map(TypedField::expand_serialize);532533	Ok(quote! {534		const _: () = {535			use ::jrsonnet_evaluator::{536				typed::{ComplexValType, Typed, TypedObj, CheckType},537				Val,538				error::{LocError, Error},539				ObjValueBuilder, ObjValue,540			};541542			#typed543544			impl #ident {545				fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {546					#(#fields_serialize)*547548					Ok(())549				}550				fn parse(obj: &ObjValue) -> Result<Self, LocError> {551					Ok(Self {552						#(#fields_parse)*553					})554				}555			}556557			impl TryFrom<Val> for #ident {558				type Error = LocError;559				fn try_from(value: Val) -> Result<Self, Self::Error> {560					let obj = value.as_obj().expect("shape is correct");561					Self::parse(&obj)562				}563			}564			impl TryInto<Val> for #ident {565				type Error = LocError;566				fn try_into(self) -> Result<Val, Self::Error> {567					let mut out = ObjValueBuilder::new();568					self.serialize(&mut out)?;569					Ok(Val::Obj(out.build()))570				}571			}572			()573		};574	})575}