| 1 | use proc_macro2::TokenStream; |
| 2 | use quote::ToTokens; |
| 3 | use syn::{parse::Error, MetaNameValue}; |
| 4 | |
| 5 | use crate::util::find_only; |
| 6 | |
| 7 | #[derive (Debug, Clone, Copy)] |
| 8 | pub enum ConversionStrategy { |
| 9 | NoConversion, |
| 10 | Into, |
| 11 | } |
| 12 | |
| 13 | pub struct DefaultAttr { |
| 14 | pub code: Option<TokenStream>, |
| 15 | conversion_strategy: Option<ConversionStrategy>, |
| 16 | } |
| 17 | |
| 18 | impl DefaultAttr { |
| 19 | pub fn find_in_attributes(attrs: &[syn::Attribute]) -> Result<Option<Self>, Error> { |
| 20 | if let Some(default_attr) = |
| 21 | find_only(attrs.iter(), |attr| Ok(attr.path().is_ident("default" )))? |
| 22 | { |
| 23 | match &default_attr.meta { |
| 24 | syn::Meta::Path(_) => Ok(Some(Self { |
| 25 | code: None, |
| 26 | conversion_strategy: None, |
| 27 | })), |
| 28 | syn::Meta::List(meta) => { |
| 29 | // If the meta contains exactly (_code = "...") take the string literal as the |
| 30 | // expression |
| 31 | if let Ok(ParseCodeHack(code_hack)) = syn::parse(meta.tokens.clone().into()) { |
| 32 | Ok(Some(Self { |
| 33 | code: Some(code_hack), |
| 34 | conversion_strategy: Some(ConversionStrategy::NoConversion), |
| 35 | })) |
| 36 | } else { |
| 37 | Ok(Some(Self { |
| 38 | code: Some(meta.tokens.clone()), |
| 39 | conversion_strategy: None, |
| 40 | })) |
| 41 | } |
| 42 | } |
| 43 | syn::Meta::NameValue(MetaNameValue { value, .. }) => Ok(Some(Self { |
| 44 | code: Some(value.into_token_stream()), |
| 45 | conversion_strategy: None, |
| 46 | })), |
| 47 | } |
| 48 | } else { |
| 49 | Ok(None) |
| 50 | } |
| 51 | } |
| 52 | |
| 53 | pub fn conversion_strategy(&self) -> ConversionStrategy { |
| 54 | if let Some(conversion_strategy) = self.conversion_strategy { |
| 55 | // Conversion strategy already set |
| 56 | return conversion_strategy; |
| 57 | } |
| 58 | let code = if let Some(code) = &self.code { |
| 59 | code |
| 60 | } else { |
| 61 | // #[default] - so no conversion (`Default::default()` already has the correct type) |
| 62 | return ConversionStrategy::NoConversion; |
| 63 | }; |
| 64 | match syn::parse::<syn::Lit>(code.clone().into()) { |
| 65 | Ok(syn::Lit::Str(_)) | Ok(syn::Lit::ByteStr(_)) => { |
| 66 | // A string literal - so we need a conversion in case we need to make it a `String` |
| 67 | return ConversionStrategy::Into; |
| 68 | } |
| 69 | _ => {} |
| 70 | } |
| 71 | // Not handled by one of the rules, so we don't convert it to avoid causing trouble |
| 72 | ConversionStrategy::NoConversion |
| 73 | } |
| 74 | } |
| 75 | |
| 76 | struct ParseCodeHack(TokenStream); |
| 77 | |
| 78 | impl syn::parse::Parse for ParseCodeHack { |
| 79 | fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { |
| 80 | let ident: syn::Ident = input.parse()?; |
| 81 | if ident != "_code" { |
| 82 | return Err(Error::new(ident.span(), message:"Expected `_code`" )); |
| 83 | } |
| 84 | input.parse::<syn::token::Eq>()?; |
| 85 | let code: syn::LitStr = input.parse()?; |
| 86 | let code: TokenStream = code.parse()?; |
| 87 | Ok(ParseCodeHack(code)) |
| 88 | } |
| 89 | } |
| 90 | |