#![doc = include_str!("../README.md")]
use proc_macro::TokenStream;
use std::fmt::Debug;
use syn::{
parenthesized,
parse::{Parse, ParseStream},
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
token::Paren,
Ident, Item, ItemFn, ItemImpl, ItemStruct, ItemTrait, LitStr, Token, Type, TypePath, ItemConst,
};
trait IdentExt {
fn get_ident(&self) -> Ident;
}
#[derive(Debug)]
struct Deprecated {
name: Ident,
since: Option<String>,
note: Option<String>,
}
impl Parse for Deprecated {
fn parse(input: ParseStream) -> syn::Result<Self> {
let deprecated = input.parse::<Ident>()?;
if deprecated != "deprecated" {
return Err(syn::Error::new(
deprecated.span(),
"invalid argument, only `deprecated` is supported",
));
}
let content;
let _ = parenthesized!(content in input);
let name = content.parse::<Ident>()?;
let mut since = None;
let mut note = None;
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
while !content.is_empty() {
if content.peek(Ident) && content.peek2(Token![=]) && content.peek3(LitStr) {
let parsed_ident = content.parse::<Ident>()?;
content.parse::<Token![=]>()?;
let parsed_lit_str = content.parse::<LitStr>()?;
if parsed_ident == "since" {
since = Some(parsed_lit_str.value());
} else if parsed_ident == "note" {
note = Some(parsed_lit_str.value());
} else {
return Err(syn::Error::new(
parsed_ident.span(),
"invalid argument, only `since` and `note` are supported",
));
}
if content.peek(Token![,]) {
content.parse::<Token![,]>()?;
}
} else {
return Err(syn::Error::new(content.span(), "expected a key-value pair"));
}
}
Ok(Self {
name,
since,
note,
})
}
}
#[derive(Debug)]
enum Alias {
Deprecated(Deprecated),
Name(Ident),
}
impl Parse for Alias {
fn parse(input: ParseStream) -> syn::Result<Self> {
let res = if input.peek(Ident) && input.peek2(Paren) {
Self::Deprecated(input.parse::<Deprecated>()?)
} else {
Self::Name(input.parse::<Ident>()?)
};
Ok(res)
}
}
impl IdentExt for Alias {
fn get_ident(&self) -> Ident {
match self {
Alias::Deprecated(Deprecated { name, .. }) => name.clone(),
Alias::Name(name) => name.clone(),
}
}
}
#[derive(Debug)]
struct Aliases(Punctuated<Alias, Token![,]>);
impl Parse for Aliases {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self(input.call(Punctuated::parse_separated_nonempty)?))
}
}
impl IntoIterator for Aliases {
type Item = Alias;
type IntoIter = syn::punctuated::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
#[proc_macro_attribute]
pub fn alias(args: TokenStream, input: TokenStream) -> TokenStream {
let aliases = parse_macro_input!(args as Aliases);
let parsed_input = parse_macro_input!(input as Item);
match parsed_input {
Item::Fn(item_fn) => expand_fn(item_fn, aliases),
Item::Struct(item_struct) => expand_struct(item_struct, aliases),
Item::Impl(item_impl) => expand_impl(item_impl, aliases),
Item::Trait(item_trait) => expand_trait(item_trait, aliases),
Item::Const(item_const) => expand_const(item_const, aliases),
_ => syn::Error::new(parsed_input.span(), "unsupported item")
.to_compile_error()
.into(),
}
}
#[doc(hidden)]
macro_rules! match_deprecated {
($item_ident:ident, $matched:ident) => {
if let Alias::Deprecated(Deprecated { since, note, .. }) = $matched {
match (since, note) {
(None, None) => parse_quote!(
#[deprecated]
#$item_ident
),
(None, Some(note)) => parse_quote!(
#[deprecated(note = #note)]
#$item_ident
),
(Some(since), None) => parse_quote!(
#[deprecated(since = #since)]
#$item_ident
),
(Some(since), Some(note)) => parse_quote!(
#[deprecated(since = #since, note = #note)]
#$item_ident
),
}
} else {
$item_ident
}
};
}
fn expand_fn(item_fn: ItemFn, aliases: Aliases) -> TokenStream {
let item_fn_aliases = aliases.into_iter().map(|alias| {
let mut item_fn_alias = item_fn.clone();
item_fn_alias.sig.ident = alias.get_ident();
match_deprecated!(item_fn_alias, alias)
});
quote::quote!(
#item_fn
#(#item_fn_aliases)*
)
.into()
}
fn expand_struct(item_struct: ItemStruct, aliases: Aliases) -> TokenStream {
let item_struct_aliases = aliases.into_iter().map(|alias| {
let mut item_struct_alias = item_struct.clone();
item_struct_alias.ident = alias.get_ident();
match_deprecated!(item_struct_alias, alias)
});
quote::quote!(
#item_struct
#(#item_struct_aliases)*
)
.into()
}
fn expand_impl(item_impl: ItemImpl, aliases: Aliases) -> TokenStream {
let item_impl_aliases = aliases.into_iter().map(|alias| {
let mut item_impl_alias = item_impl.clone();
if let Type::Path(TypePath { ref mut path, .. }) = item_impl_alias.self_ty.as_mut() {
let mut first_path_segment = path.segments.first_mut().unwrap();
first_path_segment.ident = alias.get_ident();
}
match_deprecated!(item_impl_alias, alias)
});
quote::quote!(
#item_impl
#(#item_impl_aliases)*
)
.into()
}
fn expand_trait(item_trait: ItemTrait, aliases: Aliases) -> TokenStream {
let item_trait_aliases = aliases.into_iter().map(|alias| {
let mut item_trait_alias = item_trait.clone();
item_trait_alias.ident = alias.get_ident();
match_deprecated!(item_trait_alias, alias)
});
quote::quote!(
#item_trait
#(#item_trait_aliases)*
)
.into()
}
fn expand_const(item_const: ItemConst, aliases: Aliases) -> TokenStream {
let item_const_aliases = aliases.into_iter().map(|alias| {
let mut item_const_alias = item_const.clone();
item_const_alias.ident = alias.get_ident();
match_deprecated!(item_const_alias, alias)
});
quote::quote!(
#item_const
#(#item_const_aliases)*
).into()
}