use proc_macro2::{Ident, Span};
use syn::{spanned::Spanned, Attribute, Error, Fields, Lit, Result, Variant};
use crate::parse::{
attribute::{NamedAttrs, ParseAttribute, ParseSpanned},
parsers::{ChoiceName, FunctionPath},
syntax::find_attr,
};
pub struct ParsedVariant {
pub span: Span,
pub ident: Ident,
pub attribute: VariantAttribute,
pub kind: ChoiceKind,
}
impl ParsedVariant {
pub fn from_variants(
variants: impl IntoIterator<Item = Variant>,
input_span: Span,
) -> Result<(Vec<Self>, ChoiceKind)> {
let mut iter = variants.into_iter();
let first = match iter.next() {
Some(variant) => Self::from_variant(variant, None)?,
None => {
return Err(Error::new(
input_span,
"enum must have at least one variant",
))
}
};
let choice_kind = first.kind;
let mut variants = vec![first];
for variant in iter {
variants.push(Self::from_variant(variant, Some(choice_kind))?);
}
Ok((variants, choice_kind))
}
fn from_variant(variant: Variant, kind: Option<ChoiceKind>) -> Result<Self> {
if !matches!(variant.fields, Fields::Unit) {
return Err(Error::new_spanned(
variant,
"variant must be a unit variant",
));
}
let attribute = match find_attr(&variant.attrs, "option") {
Some(attr) => VariantAttribute::parse(attr, kind)?,
None => {
return Err(Error::new(
variant.span(),
"missing required #[option(...)] attribute",
))
}
};
Ok(Self {
span: variant.span(),
ident: variant.ident,
kind: attribute.value.kind(),
attribute,
})
}
}
pub struct VariantAttribute {
pub name: ChoiceName,
pub name_localizations: Option<FunctionPath>,
pub value: ChoiceValue,
}
impl VariantAttribute {
pub fn parse(attr: &Attribute, kind: Option<ChoiceKind>) -> Result<Self> {
let mut parser = NamedAttrs::parse(attr, &["name", "name_localizations", "value"])?;
let value: ParseSpanned<ChoiceValue> = parser.required("value")?;
if let Some(kind) = kind {
if value.inner.kind() != kind {
return Err(Error::new(
value.span,
format!("invalid attribute type, expected {}", kind.name()),
));
}
}
Ok(Self {
name: parser.required("name")?,
name_localizations: parser.optional("name_localizations")?,
value: value.inner,
})
}
}
#[derive(Debug, Clone)]
pub enum ChoiceValue {
String(String),
Int(i64),
Number(f64),
}
impl ChoiceValue {
pub fn kind(&self) -> ChoiceKind {
match self {
ChoiceValue::String(_) => ChoiceKind::String,
ChoiceValue::Int(_) => ChoiceKind::Integer,
ChoiceValue::Number(_) => ChoiceKind::Number,
}
}
}
impl ParseAttribute for ChoiceValue {
fn parse_attribute(input: Lit) -> Result<Self> {
let parsed = match input {
Lit::Str(inner) => Self::String(inner.value()),
Lit::Int(inner) => Self::Int(inner.base10_parse()?),
Lit::Float(inner) => Self::Number(inner.base10_parse()?),
_ => {
return Err(Error::new_spanned(
input,
"expected string, integer or float point literal",
))
}
};
Ok(parsed)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChoiceKind {
String,
Integer,
Number,
}
impl ChoiceKind {
fn name(&self) -> &'static str {
match self {
ChoiceKind::String => "string",
ChoiceKind::Integer => "integer",
ChoiceKind::Number => "float",
}
}
}