git.delta.rocks / jrsonnet / refs/commits / 321e7ee3e21c

difftreelog

source

crates/jrsonnet-macros/src/lib.rs13.3 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		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}