git.delta.rocks / jrsonnet / refs/commits / 0831da3ed8d9

difftreelog

source

crates/jrsonnet-macros/src/lib.rs14.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::{self, 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	syn::custom_keyword!(ok);94}9596struct EmptyAttr;97impl Parse for EmptyAttr {98	fn parse(_input: ParseStream) -> Result<Self> {99		Ok(Self)100	}101}102103struct BuiltinAttrs {104	fields: Vec<Field>,105}106impl Parse for BuiltinAttrs {107	fn parse(input: ParseStream) -> syn::Result<Self> {108		if input.is_empty() {109			return Ok(Self { fields: Vec::new() });110		}111		input.parse::<kw::fields>()?;112		let fields;113		parenthesized!(fields in input);114		let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;115		Ok(Self {116			fields: p.into_iter().collect(),117		})118	}119}120121enum ArgInfo {122	Normal {123		ty: Box<Type>,124		is_option: bool,125		name: String,126		cfg_attrs: Vec<Attribute>,127		// ident: Ident,128	},129	Lazy {130		is_option: bool,131		name: String,132	},133	State,134	Location,135	This,136}137138impl ArgInfo {139	fn parse(name: &str, arg: &FnArg) -> Result<Self> {140		let arg = match arg {141			FnArg::Receiver(_) => unreachable!(),142			FnArg::Typed(a) => a,143		};144		let ident = match &arg.pat as &Pat {145			Pat::Ident(i) => i.ident.clone(),146			_ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),147		};148		let ty = &arg.ty;149		if type_is_path(ty, "State").is_some() {150			return Ok(Self::State);151		} else if type_is_path(ty, "CallLocation").is_some() {152			return Ok(Self::Location);153		} else if type_is_path(ty, "Thunk").is_some() {154			return Ok(Self::Lazy {155				is_option: false,156				name: ident.to_string(),157			});158		}159160		match ty as &Type {161			Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),162			_ => {}163		}164165		let (is_option, ty) = if let Some(ty) = extract_type_from_option(ty)? {166			if type_is_path(ty, "Thunk").is_some() {167				return Ok(Self::Lazy {168					is_option: true,169					name: ident.to_string(),170				});171			}172173			(true, Box::new(ty.clone()))174		} else {175			(false, ty.clone())176		};177178		let cfg_attrs = arg179			.attrs180			.iter()181			.filter(|a| a.path.is_ident("cfg"))182			.cloned()183			.collect();184185		Ok(Self::Normal {186			ty,187			is_option,188			name: ident.to_string(),189			cfg_attrs,190		})191	}192}193194#[proc_macro_attribute]195pub fn builtin(196	attr: proc_macro::TokenStream,197	item: proc_macro::TokenStream,198) -> proc_macro::TokenStream {199	let attr = parse_macro_input!(attr as BuiltinAttrs);200	let item: ItemFn = parse_macro_input!(item);201202	match builtin_inner(attr, item) {203		Ok(v) => v.into(),204		Err(e) => e.into_compile_error().into(),205	}206}207208fn builtin_inner(attr: BuiltinAttrs, fun: ItemFn) -> syn::Result<TokenStream> {209	let result = match fun.sig.output {210		ReturnType::Default => {211			return Err(Error::new(212				fun.sig.span(),213				"builtin should return something",214			))215		}216		ReturnType::Type(_, ref ty) => ty.clone(),217	};218	let result_inner = if let Some(args) = type_is_path(&result, "Result") {219		let generic_arg = match args {220			PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(),221			_ => return Err(Error::new(args.span(), "missing result generic")),222		};223		// This argument must be a type:224		match generic_arg {225			GenericArgument::Type(ty) => ty,226			_ => {227				return Err(Error::new(228					generic_arg.span(),229					"option generic should be a type",230				))231			}232		}233	} else {234		return Err(Error::new(result.span(), "return value should be result"));235	};236237	let name = fun.sig.ident.to_string();238	let args = fun239		.sig240		.inputs241		.iter()242		.map(|arg| ArgInfo::parse(&name, arg))243		.collect::<Result<Vec<_>>>()?;244245	let params_desc = args.iter().flat_map(|a| match a {246		ArgInfo::Normal {247			is_option,248			name,249			cfg_attrs,250			..251		} => Some(quote! {252			#(#cfg_attrs)*253			BuiltinParam {254				name: std::borrow::Cow::Borrowed(#name),255				has_default: #is_option,256			},257		}),258		ArgInfo::Lazy { is_option, name } => Some(quote! {259			BuiltinParam {260				name: std::borrow::Cow::Borrowed(#name),261				has_default: #is_option,262			},263		}),264		ArgInfo::State => None,265		ArgInfo::Location => None,266		ArgInfo::This => None,267	});268269	let pass = args.iter().map(|a| match a {270		ArgInfo::Normal {271			ty,272			is_option,273			name,274			cfg_attrs,275		} => {276			let eval = quote! {s.push_description(277				|| format!("argument <{}> evaluation", #name),278				|| <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),279			)?};280			let value = if *is_option {281				quote! {if let Some(value) = parsed.get(#name) {282					Some(#eval)283				} else {284					None285				},}286			} else {287				quote! {{288					let value = parsed.get(#name).expect("args shape is checked");289					#eval290				},}291			};292			quote! {293				#(#cfg_attrs)*294				#value295			}296		}297		ArgInfo::Lazy { is_option, name } => {298			if *is_option {299				quote! {if let Some(value) = parsed.get(#name) {300					Some(value.clone())301				} else {302					None303				}}304			} else {305				quote! {306					parsed.get(#name).expect("args shape is correct").clone(),307				}308			}309		}310		ArgInfo::State => quote! {s.clone(),},311		ArgInfo::Location => quote! {location,},312		ArgInfo::This => quote! {self,},313	});314315	let fields = attr.fields.iter().map(|field| {316		let name = &field.name;317		let ty = &field.ty;318		quote! {319			pub #name: #ty,320		}321	});322323	let name = &fun.sig.ident;324	let vis = &fun.vis;325	let static_ext = if attr.fields.is_empty() {326		quote! {327			impl #name {328				pub const INST: &'static dyn StaticBuiltin = &#name {};329			}330			impl StaticBuiltin for #name {}331		}332	} else {333		quote! {}334	};335	let static_derive_copy = if attr.fields.is_empty() {336		quote! {, Copy}337	} else {338		quote! {}339	};340341	Ok(quote! {342		#fun343		#[doc(hidden)]344		#[allow(non_camel_case_types)]345		#[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]346		#vis struct #name {347			#(#fields)*348		}349		const _: () = {350			use ::jrsonnet_evaluator::{351				State, Val,352				function::{builtin::{Builtin, StaticBuiltin, BuiltinParam}, CallLocation, ArgsLike, parse::parse_builtin_call},353				error::Result, Context, typed::Typed,354				parser::ExprLocation,355			};356			const PARAMS: &'static [BuiltinParam] = &[357				#(#params_desc)*358			];359360			#static_ext361			impl Builtin for #name362			where363				Self: 'static364			{365				fn name(&self) -> &str {366					stringify!(#name)367				}368				fn params(&self) -> &[BuiltinParam] {369					PARAMS370				}371				fn call(&self, s: State, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {372					let parsed = parse_builtin_call(s.clone(), ctx, &PARAMS, args, false)?;373374					let result: #result = #name(#(#pass)*);375					let result = result?;376					<#result_inner>::into_untyped(result, s)377				}378			}379		};380	})381}382383#[derive(Default)]384struct TypedAttr {385	rename: Option<String>,386	flatten: bool,387	/// flatten(ok) strategy for flattened optionals388	/// field would be None in case of any parsing error (as in serde)389	flatten_ok: bool,390}391impl Parse for TypedAttr {392	fn parse(input: ParseStream) -> syn::Result<Self> {393		let mut out = Self::default();394		loop {395			let lookahead = input.lookahead1();396			if lookahead.peek(kw::rename) {397				input.parse::<kw::rename>()?;398				input.parse::<Token![=]>()?;399				let name = input.parse::<LitStr>()?;400				if out.rename.is_some() {401					return Err(Error::new(402						name.span(),403						"rename attribute may only be specified once",404					));405				}406				out.rename = Some(name.value());407			} else if lookahead.peek(kw::flatten) {408				input.parse::<kw::flatten>()?;409				out.flatten = true;410				if input.peek(token::Paren) {411					let content;412					parenthesized!(content in input);413					let lookahead = content.lookahead1();414					if lookahead.peek(kw::ok) {415						content.parse::<kw::ok>()?;416						out.flatten_ok = true;417					} else {418						return Err(lookahead.error());419					}420				}421			} else if input.is_empty() {422				break;423			} else {424				return Err(lookahead.error());425			}426			if input.peek(Token![,]) {427				input.parse::<Token![,]>()?;428			} else {429				break;430			}431		}432		// input.parse::<kw::rename>()?;433		// input.parse::<Token![=]>()?;434		// let rename = input.parse::<LitStr>()?.value();435		Ok(out)436	}437}438439struct TypedField {440	attr: TypedAttr,441	ident: Ident,442	ty: Type,443	is_option: bool,444}445impl TypedField {446	fn parse(field: &syn::Field) -> Result<Self> {447		let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();448		let ident = if let Some(ident) = field.ident.clone() {449			ident450		} else {451			return Err(Error::new(452				field.span(),453				"this field should appear in output object, but it has no visible name",454			));455		};456		let (is_option, ty) = if let Some(ty) = extract_type_from_option(&field.ty)? {457			(true, ty.clone())458		} else {459			(false, field.ty.clone())460		};461		if is_option && attr.flatten {462			if !attr.flatten_ok {463				return Err(Error::new(464					field.span(),465					"strategy should be set when flattening Option",466				));467			}468		} else if attr.flatten_ok {469			return Err(Error::new(470				field.span(),471				"flatten(ok) is only useable on optional fields",472			));473		}474475		Ok(Self {476			attr,477			ident,478			ty,479			is_option,480		})481	}482	/// None if this field is flattened in jsonnet output483	fn name(&self) -> Option<String> {484		if self.attr.flatten {485			return None;486		}487		Some(488			self.attr489				.rename490				.clone()491				.unwrap_or_else(|| self.ident.to_string()),492		)493	}494495	fn expand_field(&self) -> Option<TokenStream> {496		if self.is_option {497			return None;498		}499		let name = self.name()?;500		let ty = &self.ty;501		Some(quote! {502			(#name, <#ty>::TYPE)503		})504	}505	fn expand_parse(&self) -> TokenStream {506		let ident = &self.ident;507		let ty = &self.ty;508		if self.attr.flatten {509			// optional flatten is handled in same way as serde510			return if self.is_option {511				quote! {512					#ident: <#ty>::parse(&obj, s.clone()).ok(),513				}514			} else {515				quote! {516					#ident: <#ty>::parse(&obj, s.clone())?,517				}518			};519		};520521		let name = self.name().unwrap();522		let value = if self.is_option {523			quote! {524				if let Some(value) = obj.get(s.clone(), #name.into())? {525					Some(<#ty>::from_untyped(value, s.clone())?)526				} else {527					None528				}529			}530		} else {531			quote! {532				<#ty>::from_untyped(obj.get(s.clone(), #name.into())?.ok_or_else(|| Error::NoSuchField(#name.into(), vec![]))?, s.clone())?533			}534		};535536		quote! {537			#ident: #value,538		}539	}540	fn expand_serialize(&self) -> Result<TokenStream> {541		let ident = &self.ident;542		let ty = &self.ty;543		Ok(if let Some(name) = self.name() {544			if self.is_option {545				quote! {546					if let Some(value) = self.#ident {547						out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(value, s.clone())?)?;548					}549				}550			} else {551				quote! {552					out.member(#name.into()).value(s.clone(), <#ty>::into_untyped(self.#ident, s.clone())?)?;553				}554			}555		} else if self.is_option {556			quote! {557				if let Some(value) = self.#ident {558					value.serialize(s.clone(), out)?;559				}560			}561		} else {562			quote! {563				self.#ident.serialize(s.clone(), out)?;564			}565		})566	}567}568569#[proc_macro_derive(Typed, attributes(typed))]570pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {571	let input = parse_macro_input!(item as DeriveInput);572573	match derive_typed_inner(input) {574		Ok(v) => v.into(),575		Err(e) => e.to_compile_error().into(),576	}577}578579fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {580	let data = match &input.data {581		syn::Data::Struct(s) => s,582		_ => return Err(Error::new(input.span(), "only structs supported")),583	};584585	let ident = &input.ident;586	let fields = data587		.fields588		.iter()589		.map(TypedField::parse)590		.collect::<Result<Vec<_>>>()?;591592	let typed = {593		let fields = fields594			.iter()595			.flat_map(TypedField::expand_field)596			.collect::<Vec<_>>();597		let len = fields.len();598		quote! {599			const ITEMS: [(&'static str, &'static ComplexValType); #len] = [600				#(#fields,)*601			];602			impl Typed for #ident {603				const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&ITEMS);604605				fn from_untyped(value: Val, s: State) -> Result<Self> {606					let obj = value.as_obj().expect("shape is correct");607					Self::parse(&obj, s)608				}609610				fn into_untyped(value: Self, s: State) -> Result<Val> {611					let mut out = ObjValueBuilder::new();612					value.serialize(s, &mut out)?;613					Ok(Val::Obj(out.build()))614				}615616			}617		}618	};619620	let fields_parse = fields.iter().map(TypedField::expand_parse);621	let fields_serialize = fields622		.iter()623		.map(TypedField::expand_serialize)624		.collect::<Result<Vec<_>>>()?;625626	Ok(quote! {627		const _: () = {628			use ::jrsonnet_evaluator::{629				typed::{ComplexValType, Typed, TypedObj, CheckType},630				Val, State,631				error::{LocError, Error, Result},632				ObjValueBuilder, ObjValue,633			};634635			#typed636637			impl TypedObj for #ident {638				fn serialize(self, s: State, out: &mut ObjValueBuilder) -> Result<(), LocError> {639					#(#fields_serialize)*640641					Ok(())642				}643				fn parse(obj: &ObjValue, s: State) -> Result<Self, LocError> {644					Ok(Self {645						#(#fields_parse)*646					})647				}648			}649		};650	})651}