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 | |