getargs-derive-macros 0.1.0

Internal implementation detail of getargs-derive. Not meant for direct use; depend on getargs-derive instead.
Documentation
use super::keys;
use super::names::to_kebab_case;
use crate::model::attr::{Key, dispatch};
use crate::model::errors::Errors;
use std::collections::HashMap;
use syn::{Attribute, Data, DeriveInput, Fields, Ident, LitStr, Meta, ext::IdentExt};

const VALUE_KEYS: &[Key<RawVariant>] =
    &[Key::new("alias", keys::alias), Key::new("name", keys::name)];

#[derive(Default)]
pub(super) struct RawVariant {
    pub(super) name: Option<LitStr>,
    pub(super) aliases: Vec<LitStr>,
}

pub(crate) struct Variant {
    pub(super) ident: Ident,
    pub(super) name: String,
    pub(super) aliases: Vec<String>,
}

fn parse_attributes(attrs: &[Attribute], errors: &mut Errors) -> RawVariant {
    let mut raw = RawVariant::default();
    for attr in attrs.iter().filter(|attr| attr.path().is_ident("value")) {
        if let Meta::Path(_) = attr.meta {
            errors.push(syn::Error::new_spanned(
                attr,
                "expected `#[value(...)]`, e.g. `#[value(name = \"fast\")]`",
            ));
            continue;
        }
        if let Err(error) =
            attr.parse_nested_meta(|meta| dispatch(meta, "value", VALUE_KEYS, &mut raw))
        {
            errors.push(error);
        }
    }
    raw
}

pub(crate) fn parse(input: &DeriveInput) -> syn::Result<Vec<Variant>> {
    let data = match &input.data {
        Data::Enum(data) => data,
        Data::Struct(data) => {
            return Err(syn::Error::new_spanned(
                data.struct_token,
                "ValueEnum can only be derived for enums with unit variants",
            ));
        }
        Data::Union(data) => {
            return Err(syn::Error::new_spanned(
                data.union_token,
                "ValueEnum can only be derived for enums with unit variants",
            ));
        }
    };

    let mut errors = Errors::default();
    if !input.generics.params.is_empty() {
        errors.push(syn::Error::new_spanned(
            &input.generics,
            "ValueEnum cannot be derived for generic enums",
        ));
    }
    if let Some(attr) = input
        .attrs
        .iter()
        .find(|attr| attr.path().is_ident("value"))
    {
        errors.push(syn::Error::new_spanned(
            attr,
            "`#[value(...)]` is only supported on variants",
        ));
    }
    if data.variants.is_empty() {
        errors.push(syn::Error::new_spanned(
            &input.ident,
            "ValueEnum requires at least one variant",
        ));
    }

    let mut variants = Vec::new();
    let mut owners: HashMap<String, String> = HashMap::new();
    for variant in &data.variants {
        if !matches!(variant.fields, Fields::Unit) {
            errors.push(syn::Error::new_spanned(
                &variant.fields,
                format!(
                    "ValueEnum variants cannot have fields; `{}` must be a unit variant",
                    variant.ident
                ),
            ));
        }
        let raw = parse_attributes(&variant.attrs, &mut errors);
        let ident_name = variant.ident.unraw().to_string();
        let name = match &raw.name {
            Some(literal) => (literal.value(), literal.span()),
            None => (to_kebab_case(&ident_name), variant.ident.span()),
        };
        let aliases = raw
            .aliases
            .iter()
            .map(|literal| (literal.value(), literal.span()));
        for (value, span) in std::iter::once(name.clone()).chain(aliases) {
            match owners.get(&value) {
                Some(owner) => errors.push(syn::Error::new(
                    span,
                    format!("value `{value}` is already used by variant `{owner}`"),
                )),
                None => {
                    owners.insert(value, ident_name.clone());
                }
            }
        }
        variants.push(Variant {
            ident: variant.ident.clone(),
            name: name.0,
            aliases: raw.aliases.iter().map(LitStr::value).collect(),
        });
    }

    errors.finish(variants)
}