89 lines
3.1 KiB
Rust
89 lines
3.1 KiB
Rust
use proc_macro2::TokenStream;
|
|
use quote::ToTokens;
|
|
use syn::{parse::Error, MetaNameValue};
|
|
|
|
use crate::smart_default::util::find_only;
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub enum ConversionStrategy {
|
|
NoConversion,
|
|
Into,
|
|
}
|
|
|
|
pub struct DefaultAttr {
|
|
pub code: Option<TokenStream>,
|
|
conversion_strategy: Option<ConversionStrategy>,
|
|
}
|
|
|
|
impl DefaultAttr {
|
|
pub fn find_in_attributes(attrs: &[syn::Attribute]) -> Result<Option<Self>, Error> {
|
|
if let Some(default_attr) =
|
|
find_only(attrs.iter(), |attr| Ok(attr.path().is_ident("default")))?
|
|
{
|
|
match &default_attr.meta {
|
|
syn::Meta::Path(_) => Ok(Some(Self {
|
|
code: None,
|
|
conversion_strategy: None,
|
|
})),
|
|
syn::Meta::List(meta) => {
|
|
// If the meta contains exactly (_code = "...") take the string literal as the
|
|
// expression
|
|
if let Ok(ParseCodeHack(code_hack)) = syn::parse(meta.tokens.clone().into()) {
|
|
Ok(Some(Self {
|
|
code: Some(code_hack),
|
|
conversion_strategy: Some(ConversionStrategy::NoConversion),
|
|
}))
|
|
} else {
|
|
Ok(Some(Self {
|
|
code: Some(meta.tokens.clone()),
|
|
conversion_strategy: None,
|
|
}))
|
|
}
|
|
}
|
|
syn::Meta::NameValue(MetaNameValue { value, .. }) => Ok(Some(Self {
|
|
code: Some(value.into_token_stream()),
|
|
conversion_strategy: None,
|
|
})),
|
|
}
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub fn conversion_strategy(&self) -> ConversionStrategy {
|
|
if let Some(conversion_strategy) = self.conversion_strategy {
|
|
// Conversion strategy already set
|
|
return conversion_strategy;
|
|
}
|
|
let code = if let Some(code) = &self.code {
|
|
code
|
|
} else {
|
|
// #[default] - so no conversion (`Default::default()` already has the correct type)
|
|
return ConversionStrategy::NoConversion;
|
|
};
|
|
match syn::parse::<syn::Lit>(code.clone().into()) {
|
|
Ok(syn::Lit::Str(_)) | Ok(syn::Lit::ByteStr(_)) => {
|
|
// A string literal - so we need a conversion in case we need to make it a `String`
|
|
return ConversionStrategy::Into;
|
|
}
|
|
_ => {}
|
|
}
|
|
// Not handled by one of the rules, so we don't convert it to avoid causing trouble
|
|
ConversionStrategy::NoConversion
|
|
}
|
|
}
|
|
|
|
struct ParseCodeHack(TokenStream);
|
|
|
|
impl syn::parse::Parse for ParseCodeHack {
|
|
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
|
let ident: syn::Ident = input.parse()?;
|
|
if ident != "_code" {
|
|
return Err(Error::new(ident.span(), "Expected `_code`"));
|
|
}
|
|
input.parse::<syn::token::Eq>()?;
|
|
let code: syn::LitStr = input.parse()?;
|
|
let code: TokenStream = code.parse()?;
|
|
Ok(ParseCodeHack(code))
|
|
}
|
|
}
|