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

difftreelog

source

crates/jrsonnet-macros/src/lib.rs12.7 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	Location,133	This,134}135136impl ArgInfo {137	fn parse(arg: &FnArg) -> Result<Self> {138		let arg = match arg {139			FnArg::Receiver(_) => unreachable!(),140			FnArg::Typed(a) => a,141		};142		let ident = match &arg.pat as &Pat {143			Pat::Ident(i) => i.ident.clone(),144			_ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),145		};146		let ty = &arg.ty;147		if type_is_path(ty, "CallLocation").is_some() {148			return Ok(Self::Location);149		} else if type_is_path(ty, "Self").is_some() {150			return Ok(Self::This);151		} else if type_is_path(ty, "LazyVal").is_some() {152			return Ok(Self::Lazy {153				is_option: false,154				name: ident.to_string(),155			});156		}157158		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {159			if type_is_path(ty, "LazyVal").is_some() {160				return Ok(Self::Lazy {161					is_option: true,162					name: ident.to_string(),163				});164			}165166			(true, Box::new(ty.clone()))167		} else {168			(false, ty.clone())169		};170171		let cfg_attrs = arg172			.attrs173			.iter()174			.filter(|a| a.path.is_ident("cfg"))175			.cloned()176			.collect();177178		Ok(Self::Normal {179			ty,180			is_option,181			name: ident.to_string(),182			cfg_attrs,183		})184	}185}186187#[proc_macro_attribute]188pub fn builtin(189	attr: proc_macro::TokenStream,190	item: proc_macro::TokenStream,191) -> proc_macro::TokenStream {192	let attr = parse_macro_input!(attr as BuiltinAttrs);193	let item: ItemFn = parse_macro_input!(item);194195	match builtin_inner(attr, item) {196		Ok(v) => v.into(),197		Err(e) => e.into_compile_error().into(),198	}199}200201fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {202	let result = match fun.sig.output {203		ReturnType::Default => {204			return Err(Error::new(205				fun.sig.span(),206				"builtin should return something",207			))208		}209		ReturnType::Type(_, ref ty) => ty.clone(),210	};211212	let args = fun213		.sig214		.inputs215		.iter()216		.map(ArgInfo::parse)217		.collect::<Result<Vec<_>>>()?;218219	let params_desc = args.iter().flat_map(|a| match a {220		ArgInfo::Normal {221			is_option,222			name,223			cfg_attrs,224			..225		} => Some(quote! {226			#(#cfg_attrs)*227			BuiltinParam {228				name: std::borrow::Cow::Borrowed(#name),229				has_default: #is_option,230			},231		}),232		ArgInfo::Lazy { is_option, name } => Some(quote! {233			BuiltinParam {234				name: std::borrow::Cow::Borrowed(#name),235				has_default: #is_option,236			},237		}),238		ArgInfo::Location => None,239		ArgInfo::This => None,240	});241242	let pass = args.iter().map(|a| match a {243		ArgInfo::Normal {244			ty,245			is_option,246			name,247			cfg_attrs,248		} => {249			let eval = quote! {::jrsonnet_evaluator::push_description_frame(250				|| format!("argument <{}> evaluation", #name),251				|| <#ty>::try_from(value.evaluate()?),252			)?};253			let value = if *is_option {254				quote! {if let Some(value) = parsed.get(#name) {255					Some(#eval)256				} else {257					None258				},}259			} else {260				quote! {{261					let value = parsed.get(#name).expect("args shape is checked");262					#eval263				},}264			};265			quote! {266				#(#cfg_attrs)*267				#value268			}269		}270		ArgInfo::Lazy { is_option, name } => {271			if *is_option {272				quote! {if let Some(value) = parsed.get(#name) {273					Some(value.clone())274				} else {275					None276				}}277			} else {278				quote! {279					parsed.get(#name).expect("args shape is correct").clone(),280				}281			}282		}283		ArgInfo::Location => quote! {location,},284		ArgInfo::This => quote! {self,},285	});286287	let fields = attr.fields.iter().map(|field| {288		let name = &field.name;289		let ty = &field.ty;290		quote! {291			pub #name: #ty,292		}293	});294295	let name = &fun.sig.ident;296	let vis = &fun.vis;297	let static_ext = if attr.fields.is_empty() {298		quote! {299			impl #name {300				pub const INST: &'static dyn StaticBuiltin = &#name {};301			}302			impl StaticBuiltin for #name {}303		}304	} else {305		quote! {}306	};307	let static_derive_copy = if attr.fields.is_empty() {308		quote! {, Copy}309	} else {310		quote! {}311	};312313	Ok(quote! {314		#fun315		#[doc(hidden)]316		#[allow(non_camel_case_types)]317		#[derive(Clone, gcmodule::Trace #static_derive_copy)]318		#vis struct #name {319			#(#fields)*320		}321		const _: () = {322			use ::jrsonnet_evaluator::{323				function::{Builtin, CallLocation, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},324				error::Result, Context,325				parser::ExprLocation,326			};327			const PARAMS: &'static [BuiltinParam] = &[328				#(#params_desc)*329			];330331			#static_ext332			impl Builtin for #name333			where334				Self: 'static335			{336				fn name(&self) -> &str {337					stringify!(#name)338				}339				fn params(&self) -> &[BuiltinParam] {340					PARAMS341				}342				fn call(&self, context: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {343					let parsed = parse_builtin_call(context, &PARAMS, args, false)?;344345					let result: #result = #name(#(#pass)*);346					let result = result?;347					result.try_into()348				}349			}350		};351	})352}353354#[derive(Default)]355struct TypedAttr {356	rename: Option<String>,357	flatten: bool,358}359impl Parse for TypedAttr {360	fn parse(input: ParseStream) -> syn::Result<Self> {361		let mut out = Self::default();362		loop {363			let lookahead = input.lookahead1();364			if lookahead.peek(kw::rename) {365				input.parse::<kw::rename>()?;366				input.parse::<Token![=]>()?;367				let name = input.parse::<LitStr>()?;368				if out.rename.is_some() {369					return Err(Error::new(370						name.span(),371						"rename attribute may only be specified once",372					));373				}374				out.rename = Some(name.value());375			} else if lookahead.peek(kw::flatten) {376				input.parse::<kw::flatten>()?;377				out.flatten = true;378			} else if input.is_empty() {379				break;380			} else {381				return Err(lookahead.error());382			}383			if input.peek(Token![,]) {384				input.parse::<Token![,]>()?;385			} else {386				break;387			}388		}389		// input.parse::<kw::rename>()?;390		// input.parse::<Token![=]>()?;391		// let rename = input.parse::<LitStr>()?.value();392		Ok(out)393	}394}395396struct TypedField<'f>(&'f syn::Field, TypedAttr);397impl<'f> TypedField<'f> {398	fn try_new(field: &'f syn::Field) -> Result<Self> {399		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();400		if field.ident.is_none() {401			return Err(Error::new(402				field.span(),403				"this field should appear in output object, but it has no visible name",404			));405		}406		Ok(Self(field, attr))407	}408	fn ident(&self) -> Ident {409		self.0410			.ident411			.clone()412			.expect("constructor disallows fields without name")413	}414	/// None if this field is flattened in jsonnet output415	fn name(&self) -> Option<String> {416		if self.1.flatten {417			return None;418		}419		Some(420			self.1421				.rename422				.clone()423				.unwrap_or_else(|| self.ident().to_string()),424		)425	}426427	fn expand_field(&self) -> Option<TokenStream> {428		if self.is_option() {429			return None;430		}431		let name = self.name()?;432		let ty = &self.0.ty;433		Some(quote! {434			(#name, <#ty>::TYPE)435		})436	}437	fn expand_parse(&self) -> TokenStream {438		let ident = self.ident();439		let ty = &self.0.ty;440		if self.1.flatten {441			// optional flatten is handled in same way as serde442			return if self.is_option() {443				quote! {444					#ident: <#ty>::parse(&obj).ok(),445				}446			} else {447				quote! {448					#ident: <#ty>::parse(&obj)?,449				}450			};451		};452453		let name = self.name().unwrap();454		let value = if let Some(ty) = self.as_option() {455			quote! {456				if let Some(value) = obj.get(#name.into())? {457					Some(<#ty>::try_from(vakue)?)458				} else {459					None460				}461			}462		} else {463			quote! {464				<#ty>::try_from(obj.get(#name.into())?.ok_or_else(|| Error::NoSuchField(#name.into()))?)?465			}466		};467468		quote! {469			#ident: #value,470		}471	}472	fn expand_serialize(&self) -> TokenStream {473		let ident = self.ident();474		if let Some(name) = self.name() {475			if self.is_option() {476				quote! {477					if let Some(value) = self.#ident {478						out.member(#name.into()).value(value.try_into()?)?;479					}480				}481			} else {482				quote! {483					out.member(#name.into()).value(self.#ident.try_into()?)?;484				}485			}486		} else if self.is_option() {487			quote! {488				if let Some(value) = self.#ident {489					value.serialize(out)?;490				}491			}492		} else {493			quote! {494				self.#ident.serialize(out)?;495			}496		}497	}498499	fn as_option(&self) -> Option<&Type> {500		extract_type_from_option(&self.0.ty).unwrap()501	}502	fn is_option(&self) -> bool {503		self.as_option().is_some()504	}505}506507#[proc_macro_derive(Typed, attributes(typed))]508pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {509	let input = parse_macro_input!(item as DeriveInput);510511	match derive_typed_inner(input) {512		Ok(v) => v.into(),513		Err(e) => e.to_compile_error().into(),514	}515}516517fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {518	let data = match &input.data {519		syn::Data::Struct(s) => s,520		_ => return Err(Error::new(input.span(), "only structs supported")),521	};522523	let ident = &input.ident;524	let fields = data525		.fields526		.iter()527		.map(TypedField::try_new)528		.collect::<Result<Vec<_>>>()?;529530	let typed = {531		let fields = fields532			.iter()533			.flat_map(TypedField::expand_field)534			.collect::<Vec<_>>();535		let len = fields.len();536		quote! {537			const ITEMS: [(&'static str, &'static ComplexValType); #len] = [538				#(#fields,)*539			];540			impl Typed for #ident {541				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);542			}543		}544	};545546	let fields_parse = fields.iter().map(TypedField::expand_parse);547	let fields_serialize = fields.iter().map(TypedField::expand_serialize);548549	Ok(quote! {550		const _: () = {551			use ::jrsonnet_evaluator::{552				typed::{ComplexValType, Typed, TypedObj, CheckType},553				Val,554				error::{LocError, Error},555				ObjValueBuilder, ObjValue,556			};557558			#typed559560			impl #ident {561				fn serialize(self, out: &mut ObjValueBuilder) -> Result<(), LocError> {562					#(#fields_serialize)*563564					Ok(())565				}566				fn parse(obj: &ObjValue) -> Result<Self, LocError> {567					Ok(Self {568						#(#fields_parse)*569					})570				}571			}572573			impl TryFrom<Val> for #ident {574				type Error = LocError;575				fn try_from(value: Val) -> Result<Self, Self::Error> {576					let obj = value.as_obj().expect("shape is correct");577					Self::parse(&obj)578				}579			}580			impl TryInto<Val> for #ident {581				type Error = LocError;582				fn try_into(self) -> Result<Val, Self::Error> {583					let mut out = ObjValueBuilder::new();584					self.serialize(&mut out)?;585					Ok(Val::Obj(out.build()))586				}587			}588			()589		};590	})591}