git.delta.rocks / unique-network / refs/commits / 9a73a324a71e

difftreelog

source

crates/struct-versioning/src/lib.rs9.0 KiBsourcehistory
1// Copyright 2019-2022 Unique Network (Gibraltar) Ltd.2// This file is part of Unique Network.34// Unique Network is free software: you can redistribute it and/or modify5// it under the terms of the GNU General Public License as published by6// the Free Software Foundation, either version 3 of the License, or7// (at your option) any later version.89// Unique Network is distributed in the hope that it will be useful,10// but WITHOUT ANY WARRANTY; without even the implied warranty of11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the12// GNU General Public License for more details.1314// You should have received a copy of the GNU General Public License15// along with Unique Network. If not, see <http://www.gnu.org/licenses/>.1617#![doc = include_str!("../README.md")]1819use proc_macro::TokenStream;20use quote::{format_ident, quote};21use syn::{22	parenthesized,23	parse::{Parse, ParseStream},24	parse_macro_input, Error, Expr, Field, Fields, ItemStruct, LitInt, Result, Token,25};2627mod kw {28	syn::custom_keyword!(version);29	syn::custom_keyword!(first_version);30	syn::custom_keyword!(versions);31	syn::custom_keyword!(upper);32}3334struct VersionedAttrs {35	current_version: u32,36	first_version: u32,37	upper: bool,38}3940/// #[versioned(version = 2)]41impl Parse for VersionedAttrs {42	fn parse(input: ParseStream) -> Result<Self> {43		let mut current_version = None::<u32>;44		let mut first_version = None::<u32>;45		let mut upper = false;4647		loop {48			if input.is_empty() {49				break;50			}51			let lookahead = input.lookahead1();52			if lookahead.peek(kw::version) {53				input.parse::<kw::version>()?;54				input.parse::<Token![=]>()?;55				let t = input.parse::<LitInt>()?;56				if current_version.is_some() {57					return Err(Error::new_spanned(t, "version is already set"));58				}59				current_version = Some(t.base10_parse()?)60			} else if lookahead.peek(kw::first_version) {61				input.parse::<kw::first_version>()?;62				input.parse::<Token![=]>()?;63				let t = input.parse::<LitInt>()?;64				if first_version.is_some() {65					return Err(Error::new_spanned(t, "first version is already set"));66				}67				first_version = Some(t.base10_parse()?)68			} else if lookahead.peek(kw::upper) {69				input.parse::<kw::upper>()?;70				upper = true;71			} else {72				return Err(lookahead.error());73			}7475			if input.is_empty() {76				break;77			} else if input.peek(Token![,]) {78				input.parse::<Token![,]>()?;79				continue;80			} else {81				return Err(input.error("unexpected token"));82			}83		}84		let first_version = first_version.unwrap_or(1);85		let current_version = current_version.unwrap_or(first_version);8687		if current_version == 0 || first_version == 0 || first_version > current_version {88			return Err(Error::new(input.span(), "1 <= first_version <= version"));89		}9091		Ok(Self {92			current_version,93			first_version,94			upper,95		})96	}97}9899/// #[version(..3)] - field vas removed in version 3 (i.e it was exist on version 2, but doesn't on version 3)100/// #[version(3..)] - field has appeared in version 3101/// #[version(2..4)] - field was on versions 2, 3102/// #[version(1..2, upper(old_field + 1))] - when updating struct from old version to new - calculate new field value from passed expression103struct VersionAttr {104	since: u32,105	before: Option<u32>,106107	upper: Option<Expr>,108}109impl VersionAttr {110	fn exists_on(&self, version: u32) -> bool {111		version >= self.since && self.before.map_or(true, |before| version < before)112	}113}114impl Parse for VersionAttr {115	fn parse(input: ParseStream) -> Result<Self> {116		let mut since = None::<u32>;117		let mut before = None::<u32>;118		let lookahead = input.lookahead1();119120		if lookahead.peek(LitInt) {121			let t: LitInt = input.parse()?;122			since = Some(t.base10_parse()?);123		} else if !lookahead.peek(Token![..]) {124			return Err(lookahead.error());125		}126		let range = input.parse::<Token![..]>()?;127		let lookahead = input.lookahead1();128		if lookahead.peek(LitInt) {129			let t: LitInt = input.parse()?;130			before = Some(t.base10_parse()?);131		} else if !input.is_empty() && !lookahead.peek(Token![,]) {132			return Err(lookahead.error());133		}134135		let upper = if input.peek(Token![,]) {136			input.parse::<Token![,]>()?;137			input.parse::<kw::upper>()?;138			let expr;139			parenthesized!(expr in input);140141			Some(Expr::parse(&expr)?)142		} else {143			None144		};145146		if since.is_none() && before.is_none() {147			return Err(Error::new_spanned(148				range,149				"noop range, remove this version attribute",150			));151		}152		Ok(Self {153			since: since.unwrap_or(1),154			before,155			upper,156		})157	}158}159impl Default for VersionAttr {160	fn default() -> Self {161		Self {162			since: 1,163			before: None,164			upper: None,165		}166	}167}168169/// Generate versioned variants of a struct170///171/// `#[versioned(version = 1[, first_version = 1][, upper][, versions])]`172/// - *version* - current version of a struct173/// - *first_version* - allows to skip generation of structs, which predates first supported version174/// - *upper* - generate From impls, which converts old version of structs to new175/// - *versions* - generate enum, which contains all possible versions of struct176///177/// Each field may have version attribute178/// `#[version([1]..[2][, upper(old)])]`179/// - *1* - version, on which this field is appeared180/// - *2* - version, in which this field was removed181/// (i.e if set to 2, this field was exist on version 1, and no longer exist on version 2)182/// - *upper* - code, which should be executed to transform old value to new/create new value183#[proc_macro_attribute]184pub fn versioned(attr: TokenStream, input: TokenStream) -> TokenStream {185	let attr = parse_macro_input!(attr as VersionedAttrs);186	let input = parse_macro_input!(input as ItemStruct);187188	let fields = match input.fields {189		Fields::Named(named) => named.named,190		_ => {191			return Error::new_spanned(input, "expected named fields")192				.into_compile_error()193				.into()194		}195	};196	let fields = fields197		.iter()198		.map(|field| {199			let version_attr = match field.attrs.iter().find(|a| a.path.is_ident("version")) {200				Some(v) => v.parse_args::<VersionAttr>()?,201				None => return Ok((VersionAttr::default(), field.clone())),202			};203			let mut field = field.clone();204			field.attrs.retain(|a| !a.path.is_ident("version"));205			Ok((version_attr, field))206		})207		.collect::<Result<Vec<(VersionAttr, Field)>>>();208	let fields = match fields {209		Ok(f) => f,210		Err(e) => return e.into_compile_error().into(),211	};212213	let attrs = input.attrs;214	let vis = input.vis;215	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();216	let mut out = Vec::new();217	for version in attr.first_version..=attr.current_version {218		let name = if version == attr.current_version {219			input.ident.clone()220		} else {221			format_ident!("{}Version{}", &input.ident, version)222		};223		let current_fields = fields224			.iter()225			.filter_map(|(ver, field)| ver.exists_on(version).then_some(field));226227		let mut doc = Vec::new();228		if version > attr.first_version {229			doc.push(" # Versioning".into());230			doc.push(format!(" Changes between {} and {}:", version - 1, version));231			for (ver, field) in fields.iter() {232				match (ver.exists_on(version - 1), ver.exists_on(version)) {233					(true, false) => {234						let ty = &field.ty;235						doc.push(format!(236							" - {}: {} was removed",237							field.ident.as_ref().unwrap(),238							quote! {#ty}239						))240					}241					(false, true) => {242						let ty = &field.ty;243						doc.push(format!(244							" - [`{}`]: {} was added",245							field.ident.as_ref().unwrap(),246							quote! {#ty}247						))248					}249					_ => {}250				}251			}252		}253254		let upper = if attr.upper && version > attr.first_version {255			let prev_version = format_ident!("{}Version{}", &input.ident, version - 1);256			let removed_fields = fields257				.iter()258				.filter(|(v, _)| v.exists_on(version - 1) && !v.exists_on(version))259				.map(|(_, f)| f.ident.as_ref().unwrap())260				.collect::<Vec<_>>();261			let added_fields = fields262				.iter()263				.filter(|(v, _)| !v.exists_on(version - 1) && v.exists_on(version))264				.map(|(v, f)| {265					let name = f.ident.as_ref().unwrap();266					let value = v.upper.clone().unwrap_or_else(|| {267						Expr::Verbatim(268							Error::new_spanned(f, "missing upper declaration").to_compile_error(),269						)270					});271					quote! { #name: #value }272				});273			let passed_fields = fields274				.iter()275				.filter(|(v, _)| v.exists_on(version - 1) && v.exists_on(version))276				.map(|(_, f)| f.ident.as_ref().unwrap())277				.collect::<Vec<_>>();278			// let added_fields = fields;279			quote! {280				impl #impl_generics From<#prev_version #ty_generics> for #name #ty_generics #where_clause {281					fn from(old: #prev_version #ty_generics) -> Self {282						let #prev_version {283							#(#removed_fields,)*284							#(#passed_fields,)*285						} = old;286						#(let _ = &#removed_fields;)*287						Self {288							#(#added_fields,)*289							#(#passed_fields,)*290						}291					}292				}293			}294		} else {295			quote! {}296		};297298		out.push(quote! {299			#(#attrs)*300			#(#[doc = #doc])*301			#vis struct #name #impl_generics #where_clause {302				#(#current_fields,)*303			}304305			#upper306		});307	}308309	let ident = &input.ident;310	let last_version = format_ident!("{}Version{}", input.ident, attr.current_version);311312	quote! {313		#(#out)*314315		#vis type #last_version #ty_generics = #ident #ty_generics;316	}317	.into()318}