/// Attempt to parse variants in order until a match is found
macro_rules! parse_any {
    ($vis:vis enum $enum:ident {
        $(
            $variant:ident($ty:ty)
        ),*
        $(,)?
    }) => {
        $vis enum $enum {
            $(
                $variant($ty)
            ),*
        }

        impl ::syn::parse::Parse for $enum {
            fn parse(input: ::syn::parse::ParseStream<'_>) -> ::syn::Result<Self> {
                $(if <<$ty as $crate::parser::KeywordToken>::Token as ::syn::token::Token>::peek(input.cursor()) {
                    input.parse().map(Self::$variant)
                } else)* {
                    let mut error = String::from("expected one of: ");
                    $(
                        error.push_str(<$ty as $crate::parser::KeywordToken>::display());
                        error.push_str(", ");
                    )*
                    error.truncate(error.len() - 2);
                    Err(input.error(error))
                }
            }
        }
    };
}

// The way this works sucks for a couple reasons which are not really worth
// dealing with right now, but maybe are worth dealing with in the future:
//
// 1. Using a separate enum just for parsing, instead of implementing parsing
// within a generated struct, shouldn’t really be necessary, but seemed to be
// the simplest to make everything work within the confines of the syn API.
// There is no way to get a `ParseStream` in syn other than to implement
// `syn::parse::Parse`, and that API return signature is `Result<Self>`, but the
// parser should to be able to return partial results instead (as it does now),
// so it’d be necessary to instead implement `Parse` for `PartialResult` and
// then go through an internal API that actually does parsing (and probably also
// reimplements other stuff like `Punctuated` since there would no longer be a
// type containing all the possible directives). It would be possible also to
// attach errors to the structs themselves, but it did not seem like the extra
// work to move non-fatal errors there was really worth the added effort since
// the current design was already written and functioning.
//
// 2. The variant-to-field mapping is awful. The `from` attributes should be
// taking types instead of idents, but can’t because then there would be no way
// to generate the enum variants. Variant names could be provided separately,
// but this would clutter the call sites for no particularly good reason—the
// types are normalised enough that it’s possible to just fill out the rest of
// the type here, even though it’s a nasty obfuscation that will confuse anyone
// that doesn’t look at what the macro is doing.
//
// So, you know… here be dragons, and I’m sorry in advance.
macro_rules! attr_struct {
    (
        @$mode:ident $mod:ident

        #[from($attr_ty:ident)]
        $(#[$meta:meta])*
        $vis:vis struct $ident:ident {
        $(
            $(#[cfg($cfg_ident:ident)])?
            $(#[from($($field_attr_id:ident),+)])?
            $field_vis:vis $field:ident : $field_ty:ty
        ),+ $(,)?
        }
    ) => {
        $(#[$meta])*
        $vis struct $ident {
            $(
                $(
                    #[cfg($cfg_ident)]
                 )?
                $field_vis $field: $field_ty,
            )+

            pub(crate) keyword_spans: Vec<proc_macro2::Span>,
        }

        impl $crate::parser::$mode::FromAttrs<$attr_ty> for $ident {
            fn try_set_attr(&mut self, attr: $attr_ty) -> ::syn::Result<()> {
                use crate::parser::KeywordToken;
                match attr {
                    $($(
                        $($attr_ty::$field_attr_id(value) => {
                            self.keyword_spans.push(value.keyword_span());
                            value.try_set(&mut self.$field)
                        },)+
                    )?)+
                }
            }
        }

        mod $mod {
            #[allow(unused_imports)]
            use super::*;
            use $crate::parser::$mode::attrs;

            parse_any! {
                $vis enum $attr_ty {
                    $($(
                        $($field_attr_id(attrs::$field_attr_id),)+
                    )?)+
                }
            }
        }

        $vis use $mod::$attr_ty;
    }
}
