git.delta.rocks / unique-network / refs/commits / d837c88ea76e

difftreelog

source

crates/struct-versioning/src/lib.rs9.2 KiBsourcehistory
1use proc_macro::TokenStream;2use quote::format_ident;3use syn::{4	parse::{Parse, ParseStream},5	Token, LitInt, parse_macro_input, ItemStruct, Error, Fields, Result, Field,6	punctuated::Punctuated, Expr, parenthesized,7};8use quote::quote;910mod kw {11	syn::custom_keyword!(version);12	syn::custom_keyword!(first_version);13	syn::custom_keyword!(versions);14	syn::custom_keyword!(upper);15}1617struct VersionedAttrs {18	current_version: u32,19	first_version: u32,20	upper: bool,21	versions: bool,22}2324/// #[versioned(version = 2)]25impl Parse for VersionedAttrs {26	fn parse(input: ParseStream) -> Result<Self> {27		let mut current_version = None::<u32>;28		let mut first_version = None::<u32>;29		let mut upper = false;30		let mut versions = false;3132		loop {33			if input.is_empty() {34				break;35			}36			let lookahead = input.lookahead1();37			if lookahead.peek(kw::version) {38				input.parse::<kw::version>()?;39				input.parse::<Token![=]>()?;40				let t = input.parse::<LitInt>()?;41				if current_version.is_some() {42					return Err(Error::new_spanned(t, "version is already set"));43				}44				current_version = Some(t.base10_parse()?)45			} else if lookahead.peek(kw::first_version) {46				input.parse::<kw::first_version>()?;47				input.parse::<Token![=]>()?;48				let t = input.parse::<LitInt>()?;49				if first_version.is_some() {50					return Err(Error::new_spanned(t, "first version is already set"));51				}52				first_version = Some(t.base10_parse()?)53			} else if lookahead.peek(kw::upper) {54				input.parse::<kw::upper>()?;55				upper = true;56			} else if lookahead.peek(kw::versions) {57				input.parse::<kw::version>()?;58				versions = true;59			} else {60				return Err(lookahead.error())61			}6263			if input.is_empty() {64				break65			} else if input.peek(Token![,]) {66				input.parse::<Token![,]>()?;67				continue68			} else {69				return Err(input.error("unexpected token"))70			}71		}72		let first_version = first_version.unwrap_or(1);73		let current_version = current_version.unwrap_or(first_version);7475		if current_version == 0 || first_version == 0 || first_version > current_version {76			return Err(Error::new(input.span(), "1 <= first_version <= version"))77		}7879		Ok(Self {80			current_version,81			first_version,82			upper,83			versions,84		})85	}86}8788/// #[version(..3)] - field vas removed in version 3 (i.e it was exist on version 2, but doesn't on version 3)89/// #[version(3..)] - field has appeared in version 390/// #[version(2..4)] - field was on versions 2, 391/// #[version(1..2, upper(old_field + 1))] - when updating struct from old version to new - calculate new field value from passed expression92struct VersionAttr {93	since: u32,94	before: Option<u32>,9596	upper: Option<Expr>,97}98impl VersionAttr {99	fn exists_on(&self, version: u32) -> bool {100		version >= self.since && self.before.map_or(true, |before| version < before)101	}102}103impl Parse for VersionAttr {104	fn parse(input: ParseStream) -> Result<Self> {105		let mut since = None::<u32>;106		let mut before = None::<u32>;107		let lookahead = input.lookahead1();108109		if lookahead.peek(LitInt) {110			let t: LitInt = input.parse()?;111			since = Some(t.base10_parse()?);112		} else if !lookahead.peek(Token![..]) {113			return Err(lookahead.error());114		}115		let range = input.parse::<Token![..]>()?;116		let lookahead = input.lookahead1();117		if lookahead.peek(LitInt) {118			let t: LitInt = input.parse()?;119			before = Some(t.base10_parse()?);120		} else if !input.is_empty() && !lookahead.peek(Token![,]) {121			return Err(lookahead.error());122		}123124		let upper = if input.peek(Token![,]) {125			input.parse::<Token![,]>()?;126			input.parse::<kw::upper>()?;127			let expr;128			parenthesized!(expr in input);129130			Some(Expr::parse(&expr)?)131		} else {None};132133		if since.is_none() && before.is_none() {134			return Err(Error::new_spanned(135				range,136				"noop range, remove this version attribute",137			));138		}139		Ok(Self {140			since: since.unwrap_or(1),141			before,142			upper,143		})144	}145}146impl Default for VersionAttr {147	fn default() -> Self {148		Self {149			since: 1,150			before: None,151			upper: None,152		}153	}154}155156/// Generate versioned variants of a struct157///158/// `#[versioned(version = 1[, first_version = 1][, upper][, versions])]`159/// - *version* - current version of a struct160/// - *first_version* - allows to skip generation of structs, which predates first supported version161/// - *upper* - generate From impls, which converts old version of structs to new162/// - *versions* - generate enum, which contains all possible versions of struct163///164/// Each field may have version attribute165/// `#[version([1]..[2][, upper(old)])]`166/// - *1* - version, on which this field is appeared167/// - *2* - version, in which this field was removed168/// (i.e if set to 2, this field was exist on version 1, and no longer exist on version 2)169/// - *upper* - code, which should be executed to transform old value to new/create new value170#[proc_macro_attribute]171pub fn versioned(attr: TokenStream, input: TokenStream) -> TokenStream {172	let attr = parse_macro_input!(attr as VersionedAttrs);173	let input = parse_macro_input!(input as ItemStruct);174175	let fields = match input.fields {176		Fields::Named(named) => named.named,177		_ => {178			return Error::new_spanned(input, "expected named fields")179				.into_compile_error()180				.into()181		}182	};183	let fields = fields184		.iter()185		.map(|field| {186			let version_attr = match field.attrs.iter().find(|a| a.path.is_ident("version")) {187				Some(v) => v.parse_args::<VersionAttr>()?,188				None => return Ok((VersionAttr::default(), field.clone())),189			};190			let mut field = field.clone();191			field.attrs.retain(|a| !a.path.is_ident("version"));192			Ok((version_attr, field))193		})194		.collect::<Result<Vec<(VersionAttr, Field)>>>();195	let fields = match fields {196		Ok(f) => f,197		Err(e) => return e.into_compile_error().into(),198	};199200	let attrs = input.attrs;201	let vis = input.vis;202	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();203	let mut out = Vec::new();204	for version in attr.first_version..=attr.current_version {205		let name = format_ident!("{}Version{}", &input.ident, version);206		let current_fields = fields207			.iter()208			.filter_map(|(ver, field)| ver.exists_on(version).then(|| field));209210		let mut doc = Vec::new();211		if version > attr.first_version {212			doc.push(" # Versioning".into());213			doc.push(format!(" Changes between {} and {}:", version - 1, version));214			for (ver, field) in fields.iter() {215				match (ver.exists_on(version - 1), ver.exists_on(version)) {216					(true, false) => {217						let ty = &field.ty;218						doc.push(format!(" - {}: {} was removed", field.ident.as_ref().unwrap(), quote!{#ty}))219					}220					(false, true) => {221						let ty = &field.ty;222						doc.push(format!(" - [`{}`]: {} was added", field.ident.as_ref().unwrap(), quote!{#ty}))223					}224					_ => {},225				}226			}227		}228229		let upper = if attr.upper && version > attr.first_version {230			let prev_version = format_ident!("{}Version{}", &input.ident, version - 1);231			let removed_fields = fields.iter()232				.filter(|(v, _)| v.exists_on(version - 1) && !v.exists_on(version))233				.map(|(_, f)| f.ident.as_ref().unwrap())234				.collect::<Vec<_>>();235			let added_fields = fields.iter()236				.filter(|(v, _)| !v.exists_on(version - 1) && v.exists_on(version))237				.map(|(v, f)| {238					let name = f.ident.as_ref().unwrap();239					let value = v.upper.clone().unwrap_or_else(|| Expr::Verbatim(Error::new_spanned(f, "missing upper declaration").to_compile_error()));240					quote! { #name: #value }241				});242			let passed_fields = fields.iter()243				.filter(|(v, _)| v.exists_on(version - 1) && v.exists_on(version))244				.map(|(_, f)| f.ident.as_ref().unwrap())245				.collect::<Vec<_>>();246			// let added_fields = fields;247			quote! {248				impl #impl_generics From<#prev_version #ty_generics> for #name #ty_generics #where_clause {249					fn from(old: #prev_version #ty_generics) -> Self {250						let #prev_version {251							#(#removed_fields,)*252							#(#passed_fields,)*253						} = old;254						#(let _ = &#removed_fields;)*255						Self {256							#(#added_fields,)*257							#(#passed_fields,)*258						}259					}260				}261			}262		} else {quote!{}};263264		out.push(quote! {265			#(#attrs)*266			#(#[doc = #doc])*267			#vis struct #name #impl_generics #where_clause {268				#(#current_fields,)*269			}270271			#upper272		});273	}274275	let ident = &input.ident;276	let last_version = format_ident!("{}Version{}", input.ident, attr.current_version);277278	let versions = if attr.versions {279		let name = format_ident!("{}Versions", input.ident);280		let versions = (attr.first_version..=attr.current_version).map(|v| {281			let var_name = format_ident!("V{}", v);282			let var_type = format_ident!("{}Version{}", input.ident, v);283			quote! {284				#var_name(#var_type),285			}286		});287		let versions = (attr.first_version..=attr.current_version).map(|this_version| {288			let var_name = format_ident!("V{}", this_version);289			let stages = (this_version+1..=attr.current_version).map(|v| {290				let var_type = format_ident!("{}Version{}", input.ident, v);291				quote! {292					let v = #var_type::from(v);293				}294			});295			quote! {296				#var_name(v) => {297					#(#stages)*298					v299				}300			}301		}).collect::<Vec<_>>();302		quote! {303			#vis enum #name {304				#(#versions)*305			}306			impl struct_versioning::Versions for #name {307				type Last = #last_version;308				fn up(self) -> Self::Last {309					match self {310						#(#versions)*311					}312				}313			}314		}315	} else {quote!{}};316317	quote! {318		#(#out)*319320		#vis type #ident #ty_generics = #last_version #ty_generics;321322		#versions323	}324	.into()325}