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

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;21use syn::{22	parse::{Parse, ParseStream},23	Token, LitInt, parse_macro_input, ItemStruct, Error, Fields, Result, Field, Expr,24	parenthesized,25};26use quote::quote;2728mod kw {29	syn::custom_keyword!(version);30	syn::custom_keyword!(first_version);31	syn::custom_keyword!(versions);32	syn::custom_keyword!(upper);33}3435struct VersionedAttrs {36	current_version: u32,37	first_version: u32,38	upper: bool,39}4041/// #[versioned(version = 2)]42impl Parse for VersionedAttrs {43	fn parse(input: ParseStream) -> Result<Self> {44		let mut current_version = None::<u32>;45		let mut first_version = None::<u32>;46		let mut upper = false;4748		loop {49			if input.is_empty() {50				break;51			}52			let lookahead = input.lookahead1();53			if lookahead.peek(kw::version) {54				input.parse::<kw::version>()?;55				input.parse::<Token![=]>()?;56				let t = input.parse::<LitInt>()?;57				if current_version.is_some() {58					return Err(Error::new_spanned(t, "version is already set"));59				}60				current_version = Some(t.base10_parse()?)61			} else if lookahead.peek(kw::first_version) {62				input.parse::<kw::first_version>()?;63				input.parse::<Token![=]>()?;64				let t = input.parse::<LitInt>()?;65				if first_version.is_some() {66					return Err(Error::new_spanned(t, "first version is already set"));67				}68				first_version = Some(t.base10_parse()?)69			} else if lookahead.peek(kw::upper) {70				input.parse::<kw::upper>()?;71				upper = true;72			} else {73				return Err(lookahead.error());74			}7576			if input.is_empty() {77				break;78			} else if input.peek(Token![,]) {79				input.parse::<Token![,]>()?;80				continue;81			} else {82				return Err(input.error("unexpected token"));83			}84		}85		let first_version = first_version.unwrap_or(1);86		let current_version = current_version.unwrap_or(first_version);8788		if current_version == 0 || first_version == 0 || first_version > current_version {89			return Err(Error::new(input.span(), "1 <= first_version <= version"));90		}9192		Ok(Self {93			current_version,94			first_version,95			upper,96		})97	}98}99100/// #[version(..3)] - field vas removed in version 3 (i.e it was exist on version 2, but doesn't on version 3)101/// #[version(3..)] - field has appeared in version 3102/// #[version(2..4)] - field was on versions 2, 3103/// #[version(1..2, upper(old_field + 1))] - when updating struct from old version to new - calculate new field value from passed expression104struct VersionAttr {105	since: u32,106	before: Option<u32>,107108	upper: Option<Expr>,109}110impl VersionAttr {111	fn exists_on(&self, version: u32) -> bool {112		version >= self.since && self.before.map_or(true, |before| version < before)113	}114}115impl Parse for VersionAttr {116	fn parse(input: ParseStream) -> Result<Self> {117		let mut since = None::<u32>;118		let mut before = None::<u32>;119		let lookahead = input.lookahead1();120121		if lookahead.peek(LitInt) {122			let t: LitInt = input.parse()?;123			since = Some(t.base10_parse()?);124		} else if !lookahead.peek(Token![..]) {125			return Err(lookahead.error());126		}127		let range = input.parse::<Token![..]>()?;128		let lookahead = input.lookahead1();129		if lookahead.peek(LitInt) {130			let t: LitInt = input.parse()?;131			before = Some(t.base10_parse()?);132		} else if !input.is_empty() && !lookahead.peek(Token![,]) {133			return Err(lookahead.error());134		}135136		let upper = if input.peek(Token![,]) {137			input.parse::<Token![,]>()?;138			input.parse::<kw::upper>()?;139			let expr;140			parenthesized!(expr in input);141142			Some(Expr::parse(&expr)?)143		} else {144			None145		};146147		if since.is_none() && before.is_none() {148			return Err(Error::new_spanned(149				range,150				"noop range, remove this version attribute",151			));152		}153		Ok(Self {154			since: since.unwrap_or(1),155			before,156			upper,157		})158	}159}160impl Default for VersionAttr {161	fn default() -> Self {162		Self {163			since: 1,164			before: None,165			upper: None,166		}167	}168}169170/// Generate versioned variants of a struct171///172/// `#[versioned(version = 1[, first_version = 1][, upper][, versions])]`173/// - *version* - current version of a struct174/// - *first_version* - allows to skip generation of structs, which predates first supported version175/// - *upper* - generate From impls, which converts old version of structs to new176/// - *versions* - generate enum, which contains all possible versions of struct177///178/// Each field may have version attribute179/// `#[version([1]..[2][, upper(old)])]`180/// - *1* - version, on which this field is appeared181/// - *2* - version, in which this field was removed182/// (i.e if set to 2, this field was exist on version 1, and no longer exist on version 2)183/// - *upper* - code, which should be executed to transform old value to new/create new value184#[proc_macro_attribute]185pub fn versioned(attr: TokenStream, input: TokenStream) -> TokenStream {186	let attr = parse_macro_input!(attr as VersionedAttrs);187	let input = parse_macro_input!(input as ItemStruct);188189	let fields = match input.fields {190		Fields::Named(named) => named.named,191		_ => {192			return Error::new_spanned(input, "expected named fields")193				.into_compile_error()194				.into()195		}196	};197	let fields = fields198		.iter()199		.map(|field| {200			let version_attr = match field.attrs.iter().find(|a| a.path.is_ident("version")) {201				Some(v) => v.parse_args::<VersionAttr>()?,202				None => return Ok((VersionAttr::default(), field.clone())),203			};204			let mut field = field.clone();205			field.attrs.retain(|a| !a.path.is_ident("version"));206			Ok((version_attr, field))207		})208		.collect::<Result<Vec<(VersionAttr, Field)>>>();209	let fields = match fields {210		Ok(f) => f,211		Err(e) => return e.into_compile_error().into(),212	};213214	let attrs = input.attrs;215	let vis = input.vis;216	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();217	let mut out = Vec::new();218	for version in attr.first_version..=attr.current_version {219		let name = if version == attr.current_version {220			input.ident.clone()221		} else {222			format_ident!("{}Version{}", &input.ident, version)223		};224		let current_fields = fields225			.iter()226			.filter_map(|(ver, field)| ver.exists_on(version).then_some(field));227228		let mut doc = Vec::new();229		if version > attr.first_version {230			doc.push(" # Versioning".into());231			doc.push(format!(" Changes between {} and {}:", version - 1, version));232			for (ver, field) in fields.iter() {233				match (ver.exists_on(version - 1), ver.exists_on(version)) {234					(true, false) => {235						let ty = &field.ty;236						doc.push(format!(237							" - {}: {} was removed",238							field.ident.as_ref().unwrap(),239							quote! {#ty}240						))241					}242					(false, true) => {243						let ty = &field.ty;244						doc.push(format!(245							" - [`{}`]: {} was added",246							field.ident.as_ref().unwrap(),247							quote! {#ty}248						))249					}250					_ => {}251				}252			}253		}254255		let upper = if attr.upper && version > attr.first_version {256			let prev_version = format_ident!("{}Version{}", &input.ident, version - 1);257			let removed_fields = fields258				.iter()259				.filter(|(v, _)| v.exists_on(version - 1) && !v.exists_on(version))260				.map(|(_, f)| f.ident.as_ref().unwrap())261				.collect::<Vec<_>>();262			let added_fields = fields263				.iter()264				.filter(|(v, _)| !v.exists_on(version - 1) && v.exists_on(version))265				.map(|(v, f)| {266					let name = f.ident.as_ref().unwrap();267					let value = v.upper.clone().unwrap_or_else(|| {268						Expr::Verbatim(269							Error::new_spanned(f, "missing upper declaration").to_compile_error(),270						)271					});272					quote! { #name: #value }273				});274			let passed_fields = fields275				.iter()276				.filter(|(v, _)| v.exists_on(version - 1) && v.exists_on(version))277				.map(|(_, f)| f.ident.as_ref().unwrap())278				.collect::<Vec<_>>();279			// let added_fields = fields;280			quote! {281				impl #impl_generics From<#prev_version #ty_generics> for #name #ty_generics #where_clause {282					fn from(old: #prev_version #ty_generics) -> Self {283						let #prev_version {284							#(#removed_fields,)*285							#(#passed_fields,)*286						} = old;287						#(let _ = &#removed_fields;)*288						Self {289							#(#added_fields,)*290							#(#passed_fields,)*291						}292					}293				}294			}295		} else {296			quote! {}297		};298299		out.push(quote! {300			#(#attrs)*301			#(#[doc = #doc])*302			#vis struct #name #impl_generics #where_clause {303				#(#current_fields,)*304			}305306			#upper307		});308	}309310	let ident = &input.ident;311	let last_version = format_ident!("{}Version{}", input.ident, attr.current_version);312313	quote! {314		#(#out)*315316		#vis type #last_version #ty_generics = #ident #ty_generics;317	}318	.into()319}