git.delta.rocks / unique-network / refs/commits / 573a06755e55

difftreelog

source

crates/struct-versioning/src/lib.rs10.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//! struct-versioning18//! The crate contains procedural macros for versioning data structures.19//! Macros `versioned` generate versioned variants of a struct.20//!21//! Example:22//! #[struct_versioning::versioned(version = 2, upper)]23//! ...24//! pub struct ItemData {25//! 	pub const_data: BoundedVec<u8, CustomDataLimit>,26//!27//! 	#[version(..2)]28//! 	pub variable_data: BoundedVec<u8, CustomDataLimit>,29//! }30//! ...31//! `#[version(..2)]` that means that any version before 2 will be upgraded to 2 via the `upper` function32//! *upper* - generate From impls, which converts old version of structs to new33//! In this case, the upgrade is described in `on_runtime_upgrade` using the `translate_values` substrate feature34//!35//! #[pallet::hooks]36//! impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {37//! 	fn on_runtime_upgrade() -> Weight {38//! 		if StorageVersion::get::<Pallet<T>>() < StorageVersion::new(1) {39//! 			<TokenData<T>>::translate_values::<ItemDataVersion1, _>(|v| {40//! 				Some(<ItemDataVersion2>::from(v))41//! 			})42//! 		}43//! 		044//! 	}45//! }4647use proc_macro::TokenStream;48use quote::format_ident;49use syn::{50	parse::{Parse, ParseStream},51	Token, LitInt, parse_macro_input, ItemStruct, Error, Fields, Result, Field, Expr,52	parenthesized,53};54use quote::quote;5556mod kw {57	syn::custom_keyword!(version);58	syn::custom_keyword!(first_version);59	syn::custom_keyword!(versions);60	syn::custom_keyword!(upper);61}6263struct VersionedAttrs {64	current_version: u32,65	first_version: u32,66	upper: bool,67}6869/// #[versioned(version = 2)]70impl Parse for VersionedAttrs {71	fn parse(input: ParseStream) -> Result<Self> {72		let mut current_version = None::<u32>;73		let mut first_version = None::<u32>;74		let mut upper = false;7576		loop {77			if input.is_empty() {78				break;79			}80			let lookahead = input.lookahead1();81			if lookahead.peek(kw::version) {82				input.parse::<kw::version>()?;83				input.parse::<Token![=]>()?;84				let t = input.parse::<LitInt>()?;85				if current_version.is_some() {86					return Err(Error::new_spanned(t, "version is already set"));87				}88				current_version = Some(t.base10_parse()?)89			} else if lookahead.peek(kw::first_version) {90				input.parse::<kw::first_version>()?;91				input.parse::<Token![=]>()?;92				let t = input.parse::<LitInt>()?;93				if first_version.is_some() {94					return Err(Error::new_spanned(t, "first version is already set"));95				}96				first_version = Some(t.base10_parse()?)97			} else if lookahead.peek(kw::upper) {98				input.parse::<kw::upper>()?;99				upper = true;100			} else {101				return Err(lookahead.error());102			}103104			if input.is_empty() {105				break;106			} else if input.peek(Token![,]) {107				input.parse::<Token![,]>()?;108				continue;109			} else {110				return Err(input.error("unexpected token"));111			}112		}113		let first_version = first_version.unwrap_or(1);114		let current_version = current_version.unwrap_or(first_version);115116		if current_version == 0 || first_version == 0 || first_version > current_version {117			return Err(Error::new(input.span(), "1 <= first_version <= version"));118		}119120		Ok(Self {121			current_version,122			first_version,123			upper,124		})125	}126}127128/// #[version(..3)] - field vas removed in version 3 (i.e it was exist on version 2, but doesn't on version 3)129/// #[version(3..)] - field has appeared in version 3130/// #[version(2..4)] - field was on versions 2, 3131/// #[version(1..2, upper(old_field + 1))] - when updating struct from old version to new - calculate new field value from passed expression132struct VersionAttr {133	since: u32,134	before: Option<u32>,135136	upper: Option<Expr>,137}138impl VersionAttr {139	fn exists_on(&self, version: u32) -> bool {140		version >= self.since && self.before.map_or(true, |before| version < before)141	}142}143impl Parse for VersionAttr {144	fn parse(input: ParseStream) -> Result<Self> {145		let mut since = None::<u32>;146		let mut before = None::<u32>;147		let lookahead = input.lookahead1();148149		if lookahead.peek(LitInt) {150			let t: LitInt = input.parse()?;151			since = Some(t.base10_parse()?);152		} else if !lookahead.peek(Token![..]) {153			return Err(lookahead.error());154		}155		let range = input.parse::<Token![..]>()?;156		let lookahead = input.lookahead1();157		if lookahead.peek(LitInt) {158			let t: LitInt = input.parse()?;159			before = Some(t.base10_parse()?);160		} else if !input.is_empty() && !lookahead.peek(Token![,]) {161			return Err(lookahead.error());162		}163164		let upper = if input.peek(Token![,]) {165			input.parse::<Token![,]>()?;166			input.parse::<kw::upper>()?;167			let expr;168			parenthesized!(expr in input);169170			Some(Expr::parse(&expr)?)171		} else {172			None173		};174175		if since.is_none() && before.is_none() {176			return Err(Error::new_spanned(177				range,178				"noop range, remove this version attribute",179			));180		}181		Ok(Self {182			since: since.unwrap_or(1),183			before,184			upper,185		})186	}187}188impl Default for VersionAttr {189	fn default() -> Self {190		Self {191			since: 1,192			before: None,193			upper: None,194		}195	}196}197198/// Generate versioned variants of a struct199///200/// `#[versioned(version = 1[, first_version = 1][, upper][, versions])]`201/// - *version* - current version of a struct202/// - *first_version* - allows to skip generation of structs, which predates first supported version203/// - *upper* - generate From impls, which converts old version of structs to new204/// - *versions* - generate enum, which contains all possible versions of struct205///206/// Each field may have version attribute207/// `#[version([1]..[2][, upper(old)])]`208/// - *1* - version, on which this field is appeared209/// - *2* - version, in which this field was removed210/// (i.e if set to 2, this field was exist on version 1, and no longer exist on version 2)211/// - *upper* - code, which should be executed to transform old value to new/create new value212#[proc_macro_attribute]213pub fn versioned(attr: TokenStream, input: TokenStream) -> TokenStream {214	let attr = parse_macro_input!(attr as VersionedAttrs);215	let input = parse_macro_input!(input as ItemStruct);216217	let fields = match input.fields {218		Fields::Named(named) => named.named,219		_ => {220			return Error::new_spanned(input, "expected named fields")221				.into_compile_error()222				.into()223		}224	};225	let fields = fields226		.iter()227		.map(|field| {228			let version_attr = match field.attrs.iter().find(|a| a.path.is_ident("version")) {229				Some(v) => v.parse_args::<VersionAttr>()?,230				None => return Ok((VersionAttr::default(), field.clone())),231			};232			let mut field = field.clone();233			field.attrs.retain(|a| !a.path.is_ident("version"));234			Ok((version_attr, field))235		})236		.collect::<Result<Vec<(VersionAttr, Field)>>>();237	let fields = match fields {238		Ok(f) => f,239		Err(e) => return e.into_compile_error().into(),240	};241242	let attrs = input.attrs;243	let vis = input.vis;244	let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();245	let mut out = Vec::new();246	for version in attr.first_version..=attr.current_version {247		let name = if version == attr.current_version {248			input.ident.clone()249		} else {250			format_ident!("{}Version{}", &input.ident, version)251		};252		let current_fields = fields253			.iter()254			.filter_map(|(ver, field)| ver.exists_on(version).then(|| field));255256		let mut doc = Vec::new();257		if version > attr.first_version {258			doc.push(" # Versioning".into());259			doc.push(format!(" Changes between {} and {}:", version - 1, version));260			for (ver, field) in fields.iter() {261				match (ver.exists_on(version - 1), ver.exists_on(version)) {262					(true, false) => {263						let ty = &field.ty;264						doc.push(format!(265							" - {}: {} was removed",266							field.ident.as_ref().unwrap(),267							quote! {#ty}268						))269					}270					(false, true) => {271						let ty = &field.ty;272						doc.push(format!(273							" - [`{}`]: {} was added",274							field.ident.as_ref().unwrap(),275							quote! {#ty}276						))277					}278					_ => {}279				}280			}281		}282283		let upper = if attr.upper && version > attr.first_version {284			let prev_version = format_ident!("{}Version{}", &input.ident, version - 1);285			let removed_fields = fields286				.iter()287				.filter(|(v, _)| v.exists_on(version - 1) && !v.exists_on(version))288				.map(|(_, f)| f.ident.as_ref().unwrap())289				.collect::<Vec<_>>();290			let added_fields = fields291				.iter()292				.filter(|(v, _)| !v.exists_on(version - 1) && v.exists_on(version))293				.map(|(v, f)| {294					let name = f.ident.as_ref().unwrap();295					let value = v.upper.clone().unwrap_or_else(|| {296						Expr::Verbatim(297							Error::new_spanned(f, "missing upper declaration").to_compile_error(),298						)299					});300					quote! { #name: #value }301				});302			let passed_fields = fields303				.iter()304				.filter(|(v, _)| v.exists_on(version - 1) && v.exists_on(version))305				.map(|(_, f)| f.ident.as_ref().unwrap())306				.collect::<Vec<_>>();307			// let added_fields = fields;308			quote! {309				impl #impl_generics From<#prev_version #ty_generics> for #name #ty_generics #where_clause {310					fn from(old: #prev_version #ty_generics) -> Self {311						let #prev_version {312							#(#removed_fields,)*313							#(#passed_fields,)*314						} = old;315						#(let _ = &#removed_fields;)*316						Self {317							#(#added_fields,)*318							#(#passed_fields,)*319						}320					}321				}322			}323		} else {324			quote! {}325		};326327		out.push(quote! {328			#(#attrs)*329			#(#[doc = #doc])*330			#vis struct #name #impl_generics #where_clause {331				#(#current_fields,)*332			}333334			#upper335		});336	}337338	let ident = &input.ident;339	let last_version = format_ident!("{}Version{}", input.ident, attr.current_version);340341	quote! {342		#(#out)*343344		#vis type #last_version #ty_generics = #ident #ty_generics;345	}346	.into()347}