1234567891011121314151617#![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}394041impl 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}9899100101102103struct 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}168169170171172173174175176177178179180181182183#[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 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}