frame_support_procedural/
deprecation.rs
use proc_macro2::TokenStream;
use quote::quote;
use syn::{
punctuated::Punctuated, spanned::Spanned, Error, Expr, ExprLit, Lit, Meta, MetaNameValue,
Result, Token, Variant,
};
fn deprecation_msg_formatter(msg: &str) -> String {
format!(
r#"{msg}
help: the following are the possible correct uses
|
| #[deprecated = "reason"]
|
| #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
|
| #[deprecated]
|"#
)
}
#[derive(Copy, Clone, PartialEq)]
enum DeprecationTarget {
Item,
Variant,
}
fn parse_deprecated_meta(
crate_: &TokenStream,
attr: &syn::Attribute,
target: DeprecationTarget,
) -> Result<TokenStream> {
let target = match target {
DeprecationTarget::Item =>
quote! { #crate_::__private::metadata_ir::ItemDeprecationInfoIR },
DeprecationTarget::Variant =>
quote! { #crate_::__private::metadata_ir::VariantDeprecationInfoIR },
};
match &attr.meta {
Meta::List(meta_list) => {
let parsed = meta_list
.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
.map_err(|e| Error::new(attr.span(), e.to_string()))?;
let (note, since) = parsed.iter().try_fold((None, None), |mut acc, item| {
let value = match &item.value {
Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }) => Ok(lit),
_ => Err(Error::new(
attr.span(),
deprecation_msg_formatter(
"Invalid deprecation attribute: expected string literal",
),
)),
}?;
if item.path.is_ident("note") {
acc.0.replace(value);
} else if item.path.is_ident("since") {
acc.1.replace(value);
}
Ok::<(Option<&syn::Lit>, Option<&syn::Lit>), Error>(acc)
})?;
note.map_or_else(
|| {
Err(Error::new(
attr.span(),
deprecation_msg_formatter("Invalid deprecation attribute: missing `note`"),
))
},
|note| {
let since = if let Some(str) = since {
quote! { Some(#str) }
} else {
quote! { None }
};
let doc = quote! { #target::Deprecated { note: #note, since: #since }};
Ok(doc)
},
)
},
Meta::NameValue(MetaNameValue {
value: Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }),
..
}) => {
let doc = quote! { #target::Deprecated { note: #lit, since: None } };
Ok(doc)
},
Meta::Path(_) => {
Ok(quote! { #target::DeprecatedWithoutNote })
},
_ => Err(Error::new(
attr.span(),
deprecation_msg_formatter("Invalid deprecation attribute: expected string literal"),
)),
}
}
fn find_deprecation_attr(attrs: &[syn::Attribute]) -> Option<&syn::Attribute> {
attrs.iter().find(|a| a.path().is_ident("deprecated"))
}
fn parse_deprecation(
path: &TokenStream,
attrs: &[syn::Attribute],
target: DeprecationTarget,
) -> Result<Option<TokenStream>> {
find_deprecation_attr(attrs)
.map(|a| parse_deprecated_meta(path, a, target))
.transpose()
}
pub fn get_deprecation(path: &TokenStream, attrs: &[syn::Attribute]) -> Result<TokenStream> {
parse_deprecation(path, attrs, DeprecationTarget::Item).map(|item| {
item.unwrap_or_else(|| {
quote! {#path::__private::metadata_ir::ItemDeprecationInfoIR::NotDeprecated}
})
})
}
pub fn prevent_deprecation_attr_on_outer_enum(parent_attrs: &[syn::Attribute]) -> Result<()> {
if let Some(attr) = find_deprecation_attr(parent_attrs) {
return Err(Error::new(
attr.span(),
"The `#[deprecated]` attribute should be applied to individual variants, not the enum as a whole.",
));
}
Ok(())
}
pub fn get_deprecation_enum<'a>(
path: &TokenStream,
children_attrs: impl Iterator<Item = (u8, &'a [syn::Attribute])>,
) -> Result<TokenStream> {
let children = children_attrs
.filter_map(|(key, attributes)| {
let deprecation_status =
parse_deprecation(path, attributes, DeprecationTarget::Variant).transpose();
deprecation_status.map(|item| item.map(|item| quote::quote! { (#key, #item) }))
})
.collect::<Result<Vec<TokenStream>>>()?;
if children.is_empty() {
Ok(
quote::quote! { #path::__private::metadata_ir::EnumDeprecationInfoIR::nothing_deprecated() },
)
} else {
let children = quote::quote! { #path::__private::scale_info::prelude::collections::BTreeMap::from([#( #children),*]) };
Ok(quote::quote! { #path::__private::metadata_ir::EnumDeprecationInfoIR(#children) })
}
}
pub fn variant_index_for_deprecation(index: u8, item: &Variant) -> u8 {
let index: u8 =
if let Some((_, Expr::Lit(ExprLit { lit: Lit::Int(num_lit), .. }))) = &item.discriminant {
num_lit.base10_parse::<u8>().unwrap_or(index as u8)
} else {
index as u8
};
item.attrs
.iter()
.find(|attr| attr.path().is_ident("codec"))
.and_then(|attr| {
if let Meta::List(meta_list) = &attr.meta {
meta_list
.parse_args_with(Punctuated::<MetaNameValue, syn::Token![,]>::parse_terminated)
.ok()
} else {
None
}
})
.and_then(|parsed| {
parsed.iter().fold(None, |mut acc, item| {
if let Expr::Lit(ExprLit { lit: Lit::Int(num_lit), .. }) = &item.value {
num_lit.base10_parse::<u8>().iter().for_each(|val| {
if item.path.is_ident("index") {
acc.replace(*val);
}
})
};
acc
})
})
.unwrap_or(index)
}
pub fn extract_or_return_allow_attrs(
items: &[syn::Attribute],
) -> impl Iterator<Item = syn::Attribute> + '_ {
items.iter().filter_map(|attr| {
attr.path().is_ident("allow").then(|| attr.clone()).or_else(|| {
attr.path().is_ident("deprecated").then(|| {
syn::parse_quote! {
#[allow(deprecated)]
}
})
})
})
}