1use proc_macro2::TokenStream;
2use quote::ToTokens;
3use syn::{parse::Error, MetaNameValue};
4
5use crate::util::find_only;
6
7#[derive(Debug, Clone, Copy)]
8pub enum ConversionStrategy {
9 NoConversion,
10 Into,
11}
12
13pub struct DefaultAttr {
14 pub code: Option<TokenStream>,
15 conversion_strategy: Option<ConversionStrategy>,
16}
17
18impl 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
76struct ParseCodeHack(TokenStream);
77
78impl 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