1234567891011121314151617181920212223242526272829303132333435363738394041424344454647use 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}686970impl 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}127128129130131132struct 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}197198199200201202203204205206207208209210211212#[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 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}