use quote::ToTokens; use syn::parse::{Parse, ParseStream}; use syn::spanned::Spanned; use syn::{parse_quote, Expr, Lit, Meta, Token, Type}; const FORMATTED_TYPE_START: &str = "static TY_AFTER_HERE:"; const FORMATTED_TYPE_END: &str = "= todo!();"; /// Attempts to convert the given literal to a string. /// Converts ints and floats to their base 10 counterparts. /// /// Returns `None` if the literal is [`Lit::Verbatim`] or if the literal is [`Lit::ByteStr`] /// and the byte string could not be converted to UTF-8. pub fn lit_to_string(lit: Lit) -> Option { match lit { Lit::Str(l) => Some(l.value()), Lit::ByteStr(l) => String::from_utf8(l.value()).ok(), Lit::Byte(l) => Some(String::from(l.value() as char)), Lit::Char(l) => Some(l.value().to_string()), Lit::Int(l) => Some(l.base10_digits().to_string()), Lit::Float(l) => Some(l.base10_digits().to_string()), Lit::Bool(l) => Some(l.value().to_string()), Lit::Verbatim(_) => None, _ => None, } } pub fn format_type_string(ty: &Type) -> String { let ty_unformatted = ty.into_token_stream().to_string(); let ty_unformatted = ty_unformatted.trim(); // This should always be valid syntax. // Not Rust code, but syntax, which is the only thing that `syn` cares about. let Ok(file_unformatted) = syn::parse_file(&format!( "{FORMATTED_TYPE_START}{ty_unformatted}{FORMATTED_TYPE_END}" )) else { return ty_unformatted.to_string(); }; let file_formatted = prettyplease::unparse(&file_unformatted); let file_trimmed = file_formatted.trim(); let start_removed = file_trimmed.trim_start_matches(FORMATTED_TYPE_START); let end_removed = start_removed.trim_end_matches(FORMATTED_TYPE_END); let ty_formatted = end_removed.trim(); ty_formatted.to_string() } /// Represents the `#[deprecated]` attribute. /// /// You can use the [`DeprecatedAttribute::from_meta`] function to try to parse an attribute to this struct. #[derive(Default)] pub struct DeprecatedAttribute { pub since: Option, pub note: Option, } impl DeprecatedAttribute { /// Returns `None` if the given attribute was not a valid form of the `#[deprecated]` attribute. pub fn from_meta(meta: &Meta) -> syn::Result { if meta.path() != &parse_quote!(deprecated) { return Err(syn::Error::new( meta.span(), "attribute path is not `deprecated`", )); } match &meta { Meta::Path(_) => Ok(Self::default()), Meta::NameValue(name_value) => { let Expr::Lit(expr_lit) = &name_value.value else { return Err(syn::Error::new( name_value.span(), "literal in `deprecated` value must be a string", )); }; Ok(Self { since: None, note: lit_to_string(expr_lit.lit.clone()).map(|s| s.trim().to_string()), }) } Meta::List(list) => { let parsed = list.parse_args::()?; Ok(Self { since: parsed.since.map(|s| s.trim().to_string()), note: parsed.note.map(|s| s.trim().to_string()), }) } } } } mod kw { use syn::custom_keyword; custom_keyword!(since); custom_keyword!(note); } struct DeprecatedAttributeArgsParser { since: Option, note: Option, } impl Parse for DeprecatedAttributeArgsParser { fn parse(input: ParseStream) -> syn::Result { let mut since: Option = None; let mut note: Option = None; if input.peek(kw::since) { input.parse::()?; input.parse::()?; since = lit_to_string(input.parse()?); } if input.peek(Token![,]) && input.peek2(kw::note) { input.parse::()?; input.parse::()?; input.parse::()?; note = lit_to_string(input.parse()?); } Ok(Self { since, note }) } }