//! #[diplomat::attr] and other attributes

use std::collections::{HashMap, HashSet};

use crate::ast;
use crate::ast::attrs::{AttrInheritContext, DiplomatBackendAttrCfg, StandardAttribute};
use crate::hir::lowering::ErrorStore;
use crate::hir::{
    EnumVariant, LoweringError, Method, Mutability, OpaqueId, ReturnType, SelfType, SuccessType,
    TraitDef, Type, TypeDef, TypeId,
};
use quote::ToTokens;
use syn::spanned::Spanned;
use syn::Meta;

pub use crate::ast::attrs::RenameAttr;

/// Diplomat attribute that can be specified on items, methods, and enum variants. These
/// can be used to control the codegen in a particular backend.
///
/// Most of these are specified via `#[diplomat::attr(some cfg here, attrname)]`, where `some cfg here`
/// can be used to pick which backends something applies to.
#[non_exhaustive]
#[derive(Clone, Default, Debug)]
pub struct Attrs {
    /// "disable" this item: do not generate code for it in the backend
    ///
    /// This attribute is always inherited except to variants
    pub disable: bool,
    /// Mark this item deprecated in FFI.
    pub deprecated: Option<String>,
    /// An optional namespace. None is equivalent to the root namespace.
    ///
    /// This attribute is inherited to types (and is not allowed elsewhere)
    pub namespace: Option<String>,
    /// Rename this item/method/variant
    ///
    /// This attribute is inherited except through methods and variants (and is not allowed on variants)
    pub rename: RenameAttr,
    /// Rename this item in the C ABI. This *must* be respected by backends.
    ///
    /// This attribute is inherited except through variants
    pub abi_rename: RenameAttr,
    /// This method is "special": it should generate something other than a regular method on the other side.
    /// This can be something like a constructor, an accessor, a stringifier etc.
    ///
    /// This attribute does not participate in inheritance and must always
    /// be specified on individual methods
    pub special_method: Option<SpecialMethod>,
    /// This user-defined type can be used as the error type in a Result.
    pub custom_errors: bool,

    /// This user-defined type has a "default" state that can be computed purely foreign-language-side
    ///
    /// Can be applied to enum variants to signal the default
    pub default: bool,

    /// From #[diplomat::demo()]. Created from [`crate::ast::attrs::Attrs::demo_attrs`].
    /// List of attributes specific to automatic demo generation.
    /// Currently just for demo_gen in diplomat-tool (which generates sample webpages), but could be used for broader purposes (i.e., demo Android apps)
    pub demo_attrs: DemoInfo,
    /// From #[diplomat::attr()]. If true, generates a mocking interface for this type.
    pub generate_mocking_interface: bool,
    /// From #[diplomat::attr()]. If true, Diplomat will check that this struct has the same memory layout in backends which support it. Allows this struct to be used in slices ([`super::Slice::Struct`]) and to be borrowed in function parameters.
    pub abi_compatible: bool,

    /// Information on if a type declaration/impl block has custom bindings, and if so, what kind.
    pub custom_extra_code: HashMap<IncludeLocation, IncludeSource>,

    /// Default value for a given [`super::methods::Param`].
    pub default_value: Option<DefaultArgValue>,
}

/// Whether the custom binding is included as a whole file or a block of code. These are mutually exclusive.
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum IncludeSource {
    /// Include as code written in the string.
    Source(String),
    /// A path to a file that contains the full code.
    File(String),
}

#[non_exhaustive]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum IncludeLocation {
    /// An extension to the definition of the class (i.e., in C++, the .d.hpp file)
    DefBlock,
    /// An extension to the definition of the class BEFORE the class is defined.
    PreDefBlock,
    /// An extension to the definition of the class AFTER the class is defined.
    PostDefBlock,
    /// An extension to the implementation of the class (i.e., in C++, the .hpp file)
    ImplBlock,
    /// Before the impl block. Used for free functions and classes
    /// (i.e., if you want to type alias and include no function definitions)
    PreImplBlock,
    /// A block for adding to an initialization function. Intended for backends that build off of C/C++.
    /// Used by the Nanobind backend to override functionality for Nanobind bindings.
    InitializationBlock,
    /// The block at the start of an initialization function.
    /// There is no post initialization block since the InitializationBlock is already after all initializations before.
    PreInitializationBlock,
}
impl IncludeLocation {
    fn pair_from_meta(
        meta: &Meta,
        errors: &mut ErrorStore,
    ) -> (Option<IncludeLocation>, Option<IncludeSource>) {
        let mut source: Option<IncludeSource> = None;
        let mut location: Option<IncludeLocation> = None;
        let list = meta.require_list()
        .and_then(|l| {
            let parser = syn::punctuated::Punctuated::<syn::ExprAssign, syn::Token![,]>::parse_separated_nonempty;
            l.parse_args_with(parser).map_err(|e| {
                syn::Error::new(l.span(), format!("Could not parse comma separated list: {e}"))
            })
        });
        let res = list.and_then(|punc| {
            for expr in punc {
                let assigned: String = match expr.right.as_ref() {
                    syn::Expr::Lit(syn::ExprLit { lit, .. })
                        if matches!(lit, syn::Lit::Str(..)) =>
                    {
                        if let syn::Lit::Str(s) = lit {
                            s.value()
                        } else {
                            unreachable!()
                        }
                    }
                    _ => {
                        return Err(syn::Error::new(
                            expr.right.span(),
                            "Expected equivalence to a file path string.",
                        ))
                    }
                };

                let ident = match expr.left.as_ref() {
                    syn::Expr::Path(p) => {
                        let ident = p.path.get_ident();
                        if let Some(i) = ident {
                            i
                        } else {
                            return Err(syn::Error::new(p.path.span(), "Expected ident."));
                        }
                    }
                    _ => return Err(syn::Error::new(expr.left.span(), "Expected a path.")),
                };

                let ident_str = ident.to_string();

                match ident_str.as_str() {
                    "location" => location = IncludeLocation::from_assign(&assigned, errors),
                    "source" => source = Some(IncludeSource::Source(assigned)),
                    "file" => source = Some(IncludeSource::File(assigned)),
                    _ => {
                        return Err(syn::Error::new(
                            ident.span(),
                            format!("Unrecognized include ident `{ident_str}`"),
                        ))
                    }
                }
            }
            Ok(())
        });
        if let Err(e) = res {
            errors.push(LoweringError::Other(format!(
                "Error parsing `{}`: {e}",
                meta.to_token_stream()
            )));
        }
        (location, source)
    }

    fn from_assign(assigned: &str, errors: &mut ErrorStore) -> Option<Self> {
        match assigned {
            "pre_def_block" => Some(IncludeLocation::PreDefBlock),
            "def_block" => Some(IncludeLocation::DefBlock),
            "post_def_block" => Some(IncludeLocation::PostDefBlock),
            "impl_block" => Some(IncludeLocation::ImplBlock),
            "pre_impl_block" => Some(IncludeLocation::PreImplBlock),
            "pre_init_block" => Some(IncludeLocation::PreInitializationBlock),
            "init_block" => Some(IncludeLocation::InitializationBlock),
            _ => {
                errors.push(LoweringError::Other(format!(
                    "Include location `{assigned}` unsupported."
                )));
                None
            }
        }
    }
}

// #region: Demo specific attributes.

/// For `#[diplomat::demo(input(...))]`, stored in [DemoInfo::input_cfg].
#[non_exhaustive]
#[derive(Clone, Default, Debug)]
pub struct DemoInputCFG {
    /// `#[diplomat::demo(input(label = "..."))]`
    /// Label that this input parameter should have. Let demo_gen pick a valid name if this is empty.
    ///
    /// For instance <label for="v">Number Here</label><input name="v"/>
    pub label: String,

    /// `#[diplomat::demo(input(default_value = "..."))]`
    /// Sets the default value for a parameter.
    ///
    /// Should ALWAYS be a string. The HTML renderer is expected to do validation for us.
    pub default_value: String,
}

#[non_exhaustive]
#[derive(Clone, Default, Debug)]
pub struct DemoInfo {
    /// `#[diplomat::demo(generate)]`. If automatic generation is disabled by default (see [`diplomat_tool::demo_gen::DemoConfig`]), then the below render terminus will be allowed to generate.
    pub generate: bool,

    /// `#[diplomat::demo(default_constructor)]`
    /// We search for any methods specially tagged with `Constructor`, but if there's are no default Constructors and there's NamedConstructor that you want to be default instead, use this.
    /// TODO: Should probably ignore other `Constructors` if a default has been set.
    pub default_constructor: bool,

    /// `#[diplomat::demo(external)]` represents an item that we will not evaluate, and should be passed to the rendering engine to provide.
    pub external: bool,

    /// `#[diplomat::demo(custom_func = "/file/name/here.mjs")]` can be used above any `struct` definition in the bridge. The linked `.mjs` should contain a JS definition of functions that should be bundled with demo_gen's output.
    ///
    /// We call these functions "custom functions", as they are JS functions that are not automagically generated by demo_gen, but rather included as part of its JS output in the `RenderInfo` object.
    ///
    /// For more information on custom functions (and their use), see the relevant chapter in [the book](https://rust-diplomat.github.io/diplomat/demo_gen/custom_functions.html).
    ///
    /// Files are located relative to lib.rs.
    ///
    pub custom_func: Option<String>,

    /// `#[diplomat::demo(input(...))]` represents configuration options for anywhere we might expect user input.
    pub input_cfg: DemoInputCFG,
}

// #endregion

/// Attributes that mark methods as "special"
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum SpecialMethod {
    /// A constructor.
    ///
    /// Must return Self (or Result<Self> for backends with `fallible_constructors` enabled )
    Constructor,
    /// A named constructor, with optional name. If the name isn't specified, it will be derived
    /// from the method name
    ///
    /// Must return Self (or Result<Self> for backends with `fallible_constructors` enabled )
    NamedConstructor(Option<String>),

    /// A getter, with optional name. If the name isn't specified, it will be derived
    /// from the method name
    ///
    /// Must have no parameters and must return something.
    Getter(Option<String>),
    /// A setter, with optional name. If the name isn't specified, it will be derived
    /// from the method name
    ///
    /// Must have no return type (aside from potentially a `Result<(), _>`) and must have one parameter
    Setter(Option<String>),
    /// A stringifier. Must have no parameters and return a string (DiplomatWrite)
    Stringifier,
    /// A comparison operator. Currently not universally supported
    Comparison,
    /// An iterator (a type that is mutated to produce new values)
    Iterator,
    /// An iterable (a type that can produce an iterator)
    Iterable,
    /// Indexes into the type using an integer
    Indexer,

    /// Arithmetic operators. May not return references
    Add,
    Sub,
    Mul,
    Div,

    /// In-place arithmetic operators. Must not return a value
    AddAssign,
    SubAssign,
    MulAssign,
    DivAssign,
}

impl SpecialMethod {
    pub fn from_path_and_meta(path: &str, meta: &Meta) -> Result<Option<Self>, LoweringError> {
        let parse_meta = |meta| match StandardAttribute::from_meta(meta) {
            Ok(StandardAttribute::String(s)) => Ok(Some(s)),
            Ok(StandardAttribute::Empty) => Ok(None),
            Ok(_) | Err(_) => Err(LoweringError::Other(format!(
                "`{path}` must have a single string parameter or no parameter"
            ))),
        };

        match path {
            "constructor" => Ok(Some(Self::Constructor)),
            "named_constructor" => Ok(Some(Self::NamedConstructor(parse_meta(meta)?))),
            "getter" => Ok(Some(Self::Getter(parse_meta(meta)?))),
            "setter" => Ok(Some(Self::Setter(parse_meta(meta)?))),
            "stringifier" => Ok(Some(Self::Stringifier)),
            "comparison" => Ok(Some(Self::Comparison)),
            "iterator" => Ok(Some(Self::Iterator)),
            "iterable" => Ok(Some(Self::Iterable)),
            "indexer" => Ok(Some(Self::Indexer)),
            "add" => Ok(Some(Self::Add)),
            "sub" => Ok(Some(Self::Sub)),
            "mul" => Ok(Some(Self::Mul)),
            "div" => Ok(Some(Self::Div)),
            "add_assign" => Ok(Some(Self::AddAssign)),
            "sub_assign" => Ok(Some(Self::SubAssign)),
            "mul_assign" => Ok(Some(Self::MulAssign)),
            "div_assign" => Ok(Some(Self::DivAssign)),
            _ => Ok(None),
        }
    }
    // Returns the standard operator string (if any) associated with this special method
    pub fn operator_str(&self) -> Option<&str> {
        match self {
            SpecialMethod::Add => Some("+"),
            SpecialMethod::Sub => Some("-"),
            SpecialMethod::Mul => Some("*"),
            SpecialMethod::Div => Some("/"),
            SpecialMethod::AddAssign => Some("+="),
            SpecialMethod::SubAssign => Some("-="),
            SpecialMethod::MulAssign => Some("*="),
            SpecialMethod::DivAssign => Some("/="),
            SpecialMethod::Indexer => Some("[]"),
            _ => None,
        }
    }
}

/// For special methods that affect type semantics, whether this type has this method.
///
/// This will likely only contain a subset of special methods, but feel free to add more as needed.
#[derive(Debug, Default)]
#[non_exhaustive]
pub struct SpecialMethodPresence {
    pub comparator: bool,
    /// If it is an iterator, the type it iterates over
    pub iterator: Option<SuccessType>,
    /// If it is an iterable, the iterator type it returns (*not* the type it iterates over,
    /// perform lookup on that type to access)
    pub iterable: Option<OpaqueId>,
}

#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum DefaultArgValue {
    Bool(bool),
    Char(char),
    Integer(i128),
    Float(f64),
}

impl DefaultArgValue {
    fn from_value(m: &syn::MetaNameValue) -> Result<Self, LoweringError> {
        let value_repr = match &m.value {
            syn::Expr::Lit(l) => match &l.lit {
                syn::Lit::Bool(b) => DefaultArgValue::Bool(b.value),
                syn::Lit::Byte(b) => DefaultArgValue::Char(b.value() as char),
                syn::Lit::Char(c) => DefaultArgValue::Char(c.value()),
                syn::Lit::Float(f) => {
                    let float_64 = f.base10_parse::<f64>();
                    if let Ok(f) = float_64 {
                        DefaultArgValue::Float(f)
                    } else {
                        return Err(LoweringError::Other(
                            format!(
                                "Could not convert float to f64: {}",
                                float_64.unwrap_err()
                            ),
                        ));
                    }
                }
                syn::Lit::Int(i) => {
                    let int_128 = i.base10_parse::<i128>();
                    if let Ok(i) = int_128 {
                        DefaultArgValue::Integer(i)
                    } else {
                        return Err(LoweringError::Other(
                            format!(
                                "Could not convert int to i128: {}",
                                int_128.unwrap_err()
                            ),
                        ));
                    }
                }
                _ => return Err(LoweringError::Other(format!("Default arguments does not support value {}. Try a bool, char, integer, or float.", l.lit.to_token_stream()))),
            },
            _ => return Err(LoweringError::Other("Expected literal.".into())),
        };

        Ok(value_repr)
    }
}

/// Where the attribute was found. Some attributes are only allowed in some contexts
/// (e.g. namespaces cannot be specified on methods)
#[non_exhaustive] // might add module attrs in the future
#[derive(Debug)]
pub enum AttributeContext<'a, 'b> {
    Type(TypeDef<'a>),
    Trait(&'a TraitDef),
    EnumVariant(&'a EnumVariant),
    Method(&'a Method, Option<TypeId>, &'b mut SpecialMethodPresence),
    Function(&'a Method),
    Module,
    Param,
    SelfParam,
    Field,
}

fn maybe_error_unsupported(
    auto_found: bool,
    attribute: &str,
    backend: &str,
    errors: &mut ErrorStore,
) {
    if !auto_found {
        errors.push(LoweringError::Other(format!(
            "`{attribute}` not supported in backend {backend}"
        )));
    }
}
impl Attrs {
    pub fn from_ast(
        ast: &ast::Attrs,
        validator: &(impl AttributeValidator + ?Sized),
        parent_attrs: &Attrs,
        errors: &mut ErrorStore,
    ) -> Self {
        let mut this = parent_attrs.clone();
        // Backends must support this since it applies to the macro/C code.
        // No special inheritance, was already appropriately inherited in AST
        this.abi_rename = ast.abi_rename.clone();

        this.deprecated = ast.deprecated.clone();

        let support = validator.attrs_supported();
        let backend = validator.primary_name();
        for attr in &ast.attrs {
            let mut auto_found = false;
            match validator.satisfies_cfg(&attr.cfg, Some(&mut auto_found)) {
                Ok(satisfies) if !satisfies => continue,
                Err(e) => {
                    errors.push(e);
                    continue;
                }
                Ok(_) => {}
            };

            let path = attr.meta.path();
            if let Some(path) = path.get_ident() {
                let path = path.to_string();

                let warn_auto = |errors: &mut ErrorStore| {
                    if auto_found {
                        errors.push(LoweringError::Other(format!(
                            "Diplomat attribute {path:?} gated on 'auto' but is not one that works with 'auto'"
                        )));
                    }
                };

                // Check against the set of attributes that can have platform support
                if support.check_string(&path) == Some(false) {
                    maybe_error_unsupported(auto_found, &path, backend, errors);
                    continue;
                }

                match SpecialMethod::from_path_and_meta(&path, &attr.meta) {
                    Ok(Some(kind)) => {
                        if let Some(ref existing) = this.special_method {
                            errors.push(LoweringError::Other(format!(
                                    "Multiple special method markers found on the same method, found {path} and {existing:?}"
                                )));
                        } else {
                            this.special_method = Some(kind);
                        }
                    }
                    Err(error) => errors.push(error),
                    Ok(None) => match path.as_str() {
                        // No match found in the special methods, check the other keywords
                        "disable" => {
                            if let Meta::Path(_) = attr.meta {
                                if this.disable {
                                    errors.push(LoweringError::Other(
                                        "Duplicate `disable` attribute".into(),
                                    ));
                                } else {
                                    this.disable = true;
                                }
                            } else {
                                errors.push(LoweringError::Other(
                                    "`disable` must be a simple path".into(),
                                ))
                            }
                            warn_auto(errors);
                        }
                        "default" => {
                            if let Meta::Path(_) = attr.meta {
                                if this.default {
                                    errors.push(LoweringError::Other(
                                        "Duplicate `default` attribute".into(),
                                    ));
                                } else {
                                    this.default = true;
                                }
                            } else {
                                errors.push(LoweringError::Other(
                                    "`default` must be a simple path".into(),
                                ))
                            }
                        }
                        "rename" => {
                            match RenameAttr::from_meta(&attr.meta) {
                                Ok(rename) => {
                                    // We use the override extend mode: a single ast::Attrs
                                    // will have had these attributes inherited into the list by appending
                                    // to the end; so a later attribute in the list is more pertinent.
                                    this.rename.extend(&rename);
                                }
                                Err(e) => errors.push(LoweringError::Other(format!(
                                    "`rename` attr failed to parse: {e:?}"
                                ))),
                            }
                            warn_auto(errors);
                        }
                        "namespace" => match StandardAttribute::from_meta(&attr.meta) {
                            Ok(StandardAttribute::String(s)) if s.is_empty() => {
                                this.namespace = None
                            }
                            Ok(StandardAttribute::String(s)) => this.namespace = Some(s),
                            Ok(_) | Err(_) => {
                                errors.push(LoweringError::Other(
                                    "`namespace` must have a single string parameter".to_string(),
                                ));
                            }
                        },
                        "error" => {
                            this.custom_errors = true;
                        }
                        "generate_mocking_interface" => {
                            if !support.generate_mocking_interface {
                                maybe_error_unsupported(
                                    auto_found,
                                    "generate_mocking_interface",
                                    backend,
                                    errors,
                                );
                                continue;
                            }
                            this.generate_mocking_interface = true;
                        }
                        "abi_compatible" => {
                            if !support.abi_compatibles {
                                maybe_error_unsupported(
                                    auto_found,
                                    "abi_compatible",
                                    backend,
                                    errors,
                                );
                                continue;
                            }
                            this.abi_compatible = true;
                        }
                        "custom_extra_code" => {
                            let (location, source) =
                                IncludeLocation::pair_from_meta(&attr.meta, errors);

                            match (&location, &source) {
                                (Some(l), Some(s)) => {
                                    if let Some(s) = this.custom_extra_code.get(l) {
                                        errors.push(LoweringError::Other(format!("Found existing location-source pair: {l:?} = {s:?}. Duplicates not allowed.")));
                                    } else {
                                        this.custom_extra_code.insert(l.clone(), s.clone());
                                    }
                                }
                                _ => {
                                    errors.push(LoweringError::Other(format!("Expected `source=`, `file=`, `location=`. Got: Source: {source:?} Location: {location:?}")));
                                }
                            }
                        }
                        "default_value" => {
                            if !support.default_args {
                                maybe_error_unsupported(
                                    auto_found,
                                    "default_value",
                                    backend,
                                    errors,
                                );
                                continue;
                            }
                            // Note that we do not validate that the args match here.

                            let val = attr.meta.require_name_value();
                            let res = val
                                .map_err(|e| {
                                    LoweringError::Other(format!(
                                        "default_value must be name value: {e}"
                                    ))
                                })
                                .and_then(DefaultArgValue::from_value);

                            if let Ok(l) = res {
                                this.default_value = Some(l);
                            } else {
                                errors.push(res.unwrap_err());
                            }
                        }
                        _ => {
                            errors.push(LoweringError::Other(format!(
                                "Unknown diplomat attribute {path}: expected one of: `disable, rename, namespace, constructor, stringifier, comparison, named_constructor, getter, setter, custom_extra_code, indexer, error, default_value`"
                            )));
                        }
                    },
                }
            }
        }

        for attr in &ast.demo_attrs {
            let path = attr.meta.path();
            if let Some(path_ident) = path.get_ident() {
                if path_ident == "external" {
                    this.demo_attrs.external = true;
                } else if path_ident == "default_constructor" {
                    this.demo_attrs.default_constructor = true;
                } else if path_ident == "generate" {
                    this.demo_attrs.generate = true;
                } else if path_ident == "input" {
                    let meta_list = attr
                        .meta
                        .require_list()
                        .expect("Could not get MetaList, expected #[diplomat::demo(input(...))]");

                    meta_list
                        .parse_nested_meta(|meta| {
                            if meta.path.is_ident("label") {
                                let value = meta.value()?;
                                let s: syn::LitStr = value.parse()?;
                                this.demo_attrs.input_cfg.label = s.value();
                                Ok(())
                            } else if meta.path.is_ident("default_value") {
                                let value = meta.value()?;

                                let str_val: String;

                                let ahead = value.lookahead1();
                                if ahead.peek(syn::LitFloat) {
                                    let s: syn::LitFloat = value.parse()?;
                                    str_val = s.base10_parse::<f64>()?.to_string();
                                } else if ahead.peek(syn::LitInt) {
                                    let s: syn::LitInt = value.parse()?;
                                    str_val = s.base10_parse::<i64>()?.to_string();
                                } else {
                                    let s: syn::LitStr = value.parse()?;
                                    str_val = s.value();
                                }
                                this.demo_attrs.input_cfg.default_value = str_val;
                                Ok(())
                            } else {
                                Err(meta.error(format!(
                                    "Unsupported ident {:?}",
                                    meta.path.get_ident()
                                )))
                            }
                        })
                        .expect("Could not read input(...)");
                } else if path_ident == "custom_func" {
                    let v = &attr.meta.require_name_value().unwrap().value;

                    if let syn::Expr::Lit(s) = v {
                        if let syn::Lit::Str(string) = &s.lit {
                            this.demo_attrs.custom_func = Some(string.value());
                        } else {
                            errors.push(LoweringError::Other(format!(
                                "#[diplomat::demo(custom_func={s:?}) must be a literal string."
                            )));
                        }
                    } else {
                        errors.push(LoweringError::Other(format!(
                            "#[diplomat::demo(custom_func={v:?}) must be a literal string."
                        )));
                    }
                } else {
                    errors.push(LoweringError::Other(format!(
                        "Unknown demo_attr: {path_ident:?}"
                    )));
                }
            } else {
                errors.push(LoweringError::Other(format!("Unknown demo_attr: {path:?}")));
            }
        }

        this
    }

    /// Validate that this attribute is allowed in this context
    pub(crate) fn validate(
        &self,
        validator: &(impl AttributeValidator + ?Sized),
        mut context: AttributeContext,
        errors: &mut ErrorStore,
    ) {
        // use an exhaustive destructure so new attributes are handled
        let Attrs {
            disable,
            deprecated: _deprecated,
            namespace,
            rename,
            abi_rename,
            special_method,
            custom_errors,
            default,
            demo_attrs: _,
            generate_mocking_interface,
            abi_compatible,
            custom_extra_code,
            default_value,
        } = &self;

        if *disable && matches!(context, AttributeContext::EnumVariant(..)) {
            errors.push(LoweringError::Other(
                "`disable` cannot be used on enum variants".into(),
            ))
        }

        if let Some(ref special) = special_method {
            if let AttributeContext::Method(method, self_id, ref mut special_method_presence) =
                context
            {
                let check_param_count = |name: &str, count: usize, errors: &mut ErrorStore| {
                    if method.params.len() != count {
                        errors.push(LoweringError::Other(format!(
                            "{name} must have exactly {count} parameter{}",
                            if count == 1 { "" } else { "s" }
                        )))
                    }
                };
                let check_self_param = |name: &str, need_self: bool, errors: &mut ErrorStore| {
                    if method.param_self.is_some() != need_self {
                        errors.push(LoweringError::Other(format!(
                            "{name} must{} accept a self parameter",
                            if need_self { "" } else { " not" }
                        )));
                    }
                };
                match special {
                    SpecialMethod::Constructor | SpecialMethod::NamedConstructor(..) => {
                        check_self_param("Constructors", false, errors);
                        let output = method.output.success_type();
                        match method.output {
                            ReturnType::Infallible(_) => (),
                            ReturnType::Fallible(..) => {
                                // Only report an error if constructors *are* supported but failable constructors *arent*
                                if validator.attrs_supported().constructors
                                    && !validator.attrs_supported().fallible_constructors
                                {
                                    errors.push(LoweringError::Other(
                                        "This backend doesn't support fallible constructors"
                                            .to_string(),
                                    ))
                                }
                            }
                            ReturnType::Nullable(..) => {
                                errors.push(LoweringError::Other("Diplomat doesn't support turning nullable methods into constructors".to_string()));
                            }
                        }

                        if let SuccessType::OutType(t) = &output {
                            if t.id() != self_id || self_id.is_none() {
                                errors.push(LoweringError::Other(
                                    "Constructors must return Self!".to_string(),
                                ));
                            }
                        } else {
                            errors.push(LoweringError::Other(
                                "Constructors must return Self!".to_string(),
                            ));
                        }
                    }
                    SpecialMethod::Getter(_) => {
                        if !method.params.is_empty() {
                            errors
                                .push(LoweringError::Other("Getter cannot have parameters".into()));
                        }
                        if method.param_self.is_none()
                            && !validator.attrs_supported().static_accessors
                        {
                            errors.push(LoweringError::Other(format!("No self parameter on Getter {} but static_acessors are not supported",method.name.as_str())));
                        }

                        // Currently does not forbid nullable getters, could if desired
                    }

                    SpecialMethod::Setter(_) => {
                        if !matches!(method.output.success_type(), SuccessType::Unit) {
                            errors.push(LoweringError::Other("Setters must return unit".into()));
                        }
                        if method.param_self.is_none()
                            && !validator.attrs_supported().static_accessors
                        {
                            errors.push(LoweringError::Other(format!("No self parameter on Setter {} but static_acessors are not supported",method.name.as_str())));
                        }
                        check_param_count("Setter", 1, errors);
                        // Currently does not forbid fallible setters, could if desired
                    }
                    SpecialMethod::Stringifier => {
                        if !method.params.is_empty() {
                            errors
                                .push(LoweringError::Other("Getter cannot have parameters".into()));
                        }
                        if !matches!(method.output.success_type(), SuccessType::Write) {
                            errors.push(LoweringError::Other(
                                "Stringifier must return string".into(),
                            ));
                        }
                    }
                    SpecialMethod::Comparison => {
                        check_param_count("Comparator", 1, errors);
                        if special_method_presence.comparator {
                            errors.push(LoweringError::Other(
                                "Cannot define two comparators on the same type".into(),
                            ));
                        }
                        special_method_presence.comparator = true;
                        // In the long run we can actually support heterogeneous comparators. Not a priority right now.
                        const COMPARATOR_ERROR: &str =
                            "Comparator's parameter must be identical to self";
                        check_self_param("Comparators", true, errors);

                        if let Some(ref selfty) = method.param_self {
                            if let Some(param) = method.params.first() {
                                match (&selfty.ty, &param.ty) {
                                    (SelfType::Opaque(p), Type::Opaque(p2)) => {
                                        if p.tcx_id != p2.tcx_id {
                                            errors.push(LoweringError::Other(
                                                COMPARATOR_ERROR.into(),
                                            ));
                                        }

                                        if p.owner.mutability != Mutability::Immutable
                                            || p2.owner.mutability != Mutability::Immutable
                                        {
                                            errors.push(LoweringError::Other(
                                                "comparators must accept immutable parameters"
                                                    .into(),
                                            ));
                                        }

                                        if p2.optional.0 {
                                            errors.push(LoweringError::Other(
                                                "comparators must accept non-optional parameters"
                                                    .into(),
                                            ));
                                        }
                                    }
                                    (SelfType::Struct(p), Type::Struct(p2)) => {
                                        if p.tcx_id != p2.tcx_id {
                                            errors.push(LoweringError::Other(
                                                COMPARATOR_ERROR.into(),
                                            ));
                                        }

                                        if p.owner
                                            .as_borrowed()
                                            .map(|o| !o.mutability.is_immutable())
                                            .unwrap_or(false)
                                            || p2
                                                .owner
                                                .as_borrowed()
                                                .map(|o| !o.mutability.is_immutable())
                                                .unwrap_or(false)
                                        {
                                            errors.push(LoweringError::Other(
                                                "comparators must accept immutable parameters"
                                                    .into(),
                                            ));
                                        }
                                    }
                                    (SelfType::Enum(p), Type::Enum(p2)) => {
                                        if p.tcx_id != p2.tcx_id {
                                            errors.push(LoweringError::Other(
                                                COMPARATOR_ERROR.into(),
                                            ));
                                        }
                                    }
                                    _ => {
                                        errors.push(LoweringError::Other(COMPARATOR_ERROR.into()));
                                    }
                                }
                            }
                        }
                    }
                    SpecialMethod::Iterator => {
                        if special_method_presence.iterator.is_some() {
                            errors.push(LoweringError::Other(
                                "Cannot mark type as iterator twice".into(),
                            ));
                        }
                        check_param_count("Iterator", 0, errors);
                        // In theory we could support struct and enum iterators. The benefit is slight:
                        // it generates probably inefficient code whilst being rather weird when it comes to the
                        // "structs and enums convert across the boundary" norm for backends.
                        //
                        // Essentially, the `&mut self` behavior won't work right.
                        //
                        // Furthermore, in some backends (like Dart) defining an iterator may requiring adding fields,
                        // which may not be possible for enums, and would still be an odd-one-out field for structs.g s
                        check_self_param("Iterator", true, errors);
                        if let Some(this) = &method.param_self {
                            if !matches!(this.ty, SelfType::Opaque(..)) {
                                errors.push(LoweringError::Other(
                                    "Iterators only allowed on opaques".into(),
                                ))
                            }
                        }

                        if let ReturnType::Nullable(ref o) = method.output {
                            if let SuccessType::Unit = o {
                                errors.push(LoweringError::Other(
                                    "Iterator method must return something".into(),
                                ));
                            }
                            special_method_presence.iterator = Some(o.clone());
                        } else if let ReturnType::Infallible(SuccessType::OutType(
                            crate::hir::OutType::Opaque(
                                ref o @ crate::hir::OpaquePath {
                                    optional: crate::hir::Optional(true),
                                    ..
                                },
                            ),
                        )) = method.output
                        {
                            let mut o = o.clone();
                            o.optional = crate::hir::Optional(false);

                            special_method_presence.iterator =
                                Some(SuccessType::OutType(crate::hir::OutType::Opaque(o)));
                        } else {
                            errors.push(LoweringError::Other(
                                "Iterator method must return nullable value".into(),
                            ));
                        }
                    }
                    SpecialMethod::Iterable => {
                        if special_method_presence.iterable.is_some() {
                            errors.push(LoweringError::Other(
                                "Cannot mark type as iterable twice".into(),
                            ));
                        }
                        check_param_count("Iterator", 0, errors);
                        check_self_param("Iterables", true, errors);

                        match method.output.success_type() {
                            SuccessType::OutType(ty) => {
                                if let Some(TypeId::Opaque(id)) = ty.id() {
                                    special_method_presence.iterable = Some(id);
                                } else {
                                    errors.push(LoweringError::Other(
                                        "Iterables must return a custom opaque type".into(),
                                    ))
                                }
                            }
                            _ => errors.push(LoweringError::Other(
                                "Iterables must return a custom type".into(),
                            )),
                        }
                    }
                    SpecialMethod::Indexer => {
                        check_param_count("Indexer", 1, errors);
                        check_self_param("Indexer", true, errors);

                        if method.output.success_type().is_unit() {
                            errors.push(LoweringError::Other("Indexer must return a value".into()));
                        }
                    }
                    e @ (SpecialMethod::Add
                    | SpecialMethod::Sub
                    | SpecialMethod::Mul
                    | SpecialMethod::Div) => {
                        let name = match e {
                            SpecialMethod::Add => "Add",
                            SpecialMethod::Sub => "Sub",
                            SpecialMethod::Mul => "Mul",
                            SpecialMethod::Div => "Div",
                            _ => unreachable!(),
                        };

                        check_param_count(name, 1, errors);
                        check_self_param(name, true, errors);

                        if method.output.success_type().is_unit() {
                            errors
                                .push(LoweringError::Other(format!("{name} must return a value")));
                        }
                    }
                    e @ (SpecialMethod::AddAssign
                    | SpecialMethod::SubAssign
                    | SpecialMethod::MulAssign
                    | SpecialMethod::DivAssign) => {
                        let name = match e {
                            SpecialMethod::AddAssign => "AddAssign",
                            SpecialMethod::SubAssign => "SubAssign",
                            SpecialMethod::MulAssign => "MulAssign",
                            SpecialMethod::DivAssign => "DivAssign",
                            _ => unreachable!(),
                        };
                        check_param_count(name, 1, errors);
                        check_self_param(name, true, errors);
                        if let Some(self_param) = &method.param_self {
                            if matches!(self_param.ty, SelfType::Struct(_) | SelfType::Enum(_)) {
                                errors.push(LoweringError::Other("*Assign arithmetic operations not allowed on non-opaque types. \
                                     Use the non-mutating arithmetic operators instead".to_string()));
                            } else if self_param.ty.is_immutably_borrowed() {
                                errors.push(LoweringError::Other(format!(
                                    "{name} must take self by mutable reference"
                                )));
                            }
                        }
                        if !method.output.success_type().is_unit() {
                            errors.push(LoweringError::Other(format!(
                                "{name} must not return a value"
                            )));
                        }
                    }
                }
            } else {
                errors.push(LoweringError::Other(format!("Special method (type {special:?}) not allowed on non-method context {context:?}")))
            }
        }

        if namespace.is_some()
            && matches!(
                context,
                AttributeContext::Method(..) | AttributeContext::EnumVariant(..)
            )
        {
            errors.push(LoweringError::Other(
                "`namespace` can only be used on types".to_string(),
            ));
        }
        if *default && !matches!(context, AttributeContext::EnumVariant(..)) {
            errors.push(LoweringError::Other(
                "`default` can only be used on types and enum variants".to_string(),
            ));
        }
        if matches!(
            context,
            AttributeContext::Param | AttributeContext::SelfParam | AttributeContext::Field
        ) {
            if *disable {
                errors.push(LoweringError::Other(format!(
                    "`disable`s cannot be used on an {context:?}."
                )));
            }

            if namespace.is_some() {
                errors.push(LoweringError::Other(format!(
                    "`namespace` cannot be used on an {context:?}."
                )));
            }

            if !rename.is_empty() || !abi_rename.is_empty() {
                errors.push(LoweringError::Other(format!(
                    "`rename`s cannot be used on an {context:?}."
                )));
            }

            if special_method.is_some() {
                errors.push(LoweringError::Other(format!(
                    "{context:?} cannot be special methods."
                )));
            }
        }

        if *custom_errors
            && !matches!(
                context,
                AttributeContext::Type(..)
                    | AttributeContext::Trait(..)
                    | AttributeContext::Function(..)
            )
        {
            errors.push(LoweringError::Other(
                "`error` can only be used on types".to_string(),
            ));
        }
        if *generate_mocking_interface
            && !matches!(context, AttributeContext::Type(TypeDef::Opaque(..)))
        {
            errors.push(LoweringError::Other(
                "`generate_mocking_interface` can only be used on opaque types".to_string(),
            ));
        }

        if *abi_compatible && !matches!(context, AttributeContext::Type(TypeDef::Struct(..))) {
            errors.push(LoweringError::Other(
                "`abi_compatible` can only be used on non-output-only struct types.".into(),
            ));
        }

        if !custom_extra_code.is_empty() {
            if !validator.attrs_supported().custom_bindings {
                // We only validate that the language supports the bindings. We don't validate
                // anything else, since this is an advanced feature (you have to know what you are doing).
                errors.push(LoweringError::Other(
                    "Custom bindings not supported by this language.".into(),
                ));
            }

            // Cannot verify that a given function is free or not, so we just leave it up to the backend to only add
            // extra code to free functions specifically.
            if !(matches!(context, AttributeContext::Type(..))
                || matches!(context, AttributeContext::Function(..)))
            {
                errors.push(LoweringError::Other(
                    "`custom_extra_code` only supported on type declarations or free functions."
                        .into(),
                ));
            }
        }

        if default_value.is_some() && !matches!(context, AttributeContext::Param) {
            errors.push(LoweringError::Other(
                "default_value can only be placed above parameters.".into(),
            ));
        }

        if let AttributeContext::Method(m, ..) = context {
            let mut in_defaults = false;
            for p in &m.params {
                if p.attrs.default_value.is_some() {
                    in_defaults = true;
                } else if in_defaults {
                    errors.push(LoweringError::Other(format!(
                        "Found required arg {} after default arguments.",
                        p.name
                    )))
                }
            }
        }
    }

    pub(crate) fn for_inheritance(&self, context: AttrInheritContext) -> Attrs {
        let rename = self.rename.attrs_for_inheritance(context, false);

        // Disabling shouldn't inherit to variants
        let disable = if context == AttrInheritContext::Variant {
            false
        } else {
            self.disable
        };
        let namespace = if matches!(
            context,
            AttrInheritContext::Module | AttrInheritContext::Type
        ) {
            self.namespace.clone()
        } else {
            None
        };

        Attrs {
            disable,
            deprecated: None,
            rename,
            namespace,
            // Should not inherit from enums to their variants
            default: false,
            // Was already inherited on the AST side
            abi_rename: Default::default(),
            // Never inherited
            special_method: None,
            // Not inherited
            custom_errors: false,
            demo_attrs: Default::default(),
            // Not inherited
            generate_mocking_interface: false,
            abi_compatible: false,
            // Not inherited
            custom_extra_code: Default::default(),
            // Not inherited
            default_value: None,
        }
    }
}

/// Non-exhaustive list of what attributes and other features your backend is able to handle, based on #[diplomat::attr(...)] contents.
/// Set this through an [`AttributeValidator`].
///
/// See [`SpecialMethod`] and [`Attrs`] for your specific implementation needs.
///
/// For example, the current dart backend supports [`BackendAttrSupport::constructors`]. So when it encounters:
/// ```ignore
/// struct Sample {}
/// impl Sample {
///     #[diplomat::attr(constructor)]
///     pub fn new() -> Box<Self> {
///         Box::new(Sample{})
///     }
/// }
///
/// ```
///
/// It generates
/// ```dart
/// factory Sample()
/// ```
///
/// If a backend does not support a specific `#[diplomat::attr(...)]`, it may error.
#[non_exhaustive]
#[derive(Copy, Clone, Debug, Default)]
pub struct BackendAttrSupport {
    /// Namespacing types, e.g. C++ `namespace`.
    pub namespacing: bool,
    /// Rust can directly acccess the memory of this language, like C and C++.
    /// This is not supported in any garbage-collected language.
    pub memory_sharing: bool,
    /// This language's structs are non-exhaustive by default, i.e. adding
    /// fields is not a breaking change.
    pub non_exhaustive_structs: bool,
    /// Whether the language supports method overloading
    pub method_overloading: bool,
    /// Whether the language uses UTF-8 strings
    pub utf8_strings: bool,
    /// Whether the language uses UTF-16 strings
    pub utf16_strings: bool,
    /// Whether the language supports using slices with 'static lifetimes.
    pub static_slices: bool,

    /// Whether the language supports marking types as having a default value
    pub defaults: bool,

    // Special methods
    /// Marking a method as a constructor to generate special constructor methods.
    pub constructors: bool,
    /// Marking a method as a named constructor to generate special named constructor methods.
    pub named_constructors: bool,
    /// Marking constructors as being able to return errors. This is possible in languages where
    /// errors are thrown as exceptions (Dart), but not for example in C++, where errors are
    /// returned as values (constructors usually have to return the type itself).
    pub fallible_constructors: bool,
    /// Marking methods as field getters and setters, see [`SpecialMethod::Getter`] and [`SpecialMethod::Setter`]
    pub accessors: bool,
    /// Marking *static* methods as field getters and setters, see [`SpecialMethod::Getter`] and [`SpecialMethod::Setter`]
    pub static_accessors: bool,
    /// Marking a method as the `to_string` method, which is special in this language.
    pub stringifiers: bool,
    /// Marking a method as the `compare_to` method, which is special in this language.
    pub comparators: bool,
    /// Marking a method as the `next` method, which is special in this language.
    pub iterators: bool,
    /// Marking a method as the `iterator` method, which is special in this language.
    pub iterables: bool,
    /// Marking a method as the `[]` operator, which is special in this language.
    pub indexing: bool,
    /// Marking a method as an arithmetic operator (+-*/[=])
    pub arithmetic: bool,
    /// Support for Option<Struct> and Option<Primitive>
    pub option: bool,
    /// Allowing callback arguments
    pub callbacks: bool,
    /// Allowing traits
    pub traits: bool,
    /// Marking a user-defined type as being a valid error result type.
    pub custom_errors: bool,
    /// Traits are safe to Send between threads (safe to mark as std::marker::Send)
    pub traits_are_send: bool,
    /// Traits are safe to Sync between threads (safe to mark as std::marker::Sync)
    pub traits_are_sync: bool,
    /// Whether to generate mocking interface.
    pub generate_mocking_interface: bool,
    /// Passing of structs that only hold (non-slice) primitive types
    /// (for use in slices and languages that support taking direct pointers to structs):
    pub abi_compatibles: bool,
    /// Whether or not the language supports &Struct or &mut Struct
    pub struct_refs: bool,
    /// Whether the language supports generating functions not associated with any type.
    pub free_functions: bool,
    /// Whether the language supports being able to include custom bindings.
    pub custom_bindings: bool,
    /// Whether the language supports taking in Rust-allocated slices from the given backend.
    pub owned_slices: bool,
    /// Whether the language supports default arguments.
    pub default_args: bool,
}

impl BackendAttrSupport {
    #[cfg(test)]
    fn all_true() -> Self {
        Self {
            namespacing: true,
            memory_sharing: true,
            non_exhaustive_structs: true,
            method_overloading: true,
            utf8_strings: true,
            utf16_strings: true,
            static_slices: true,
            defaults: true,

            constructors: true,
            named_constructors: true,
            fallible_constructors: true,
            static_accessors: true,
            accessors: true,
            stringifiers: true,
            comparators: true,
            iterators: true,
            iterables: true,
            indexing: true,
            arithmetic: true,
            option: true,
            callbacks: true,
            traits: true,
            custom_errors: true,
            traits_are_send: true,
            traits_are_sync: true,
            generate_mocking_interface: true,
            abi_compatibles: true,
            struct_refs: true,
            free_functions: true,
            custom_bindings: true,
            owned_slices: true,
            default_args: true,
        }
    }

    pub fn check_string(&self, v: &str) -> Option<bool> {
        match v {
            "namespacing" => Some(self.namespacing),
            "memory_sharing" => Some(self.memory_sharing),
            "non_exhaustive_structs" => Some(self.non_exhaustive_structs),
            "method_overloading" => Some(self.method_overloading),
            "utf8_strings" => Some(self.utf8_strings),
            "utf16_strings" => Some(self.utf16_strings),
            "static_slices" => Some(self.static_slices),
            "default" => Some(self.defaults),
            "constructors" => Some(self.constructors),
            "named_constructors" => Some(self.named_constructors),
            "fallible_constructors" => Some(self.fallible_constructors),
            "accessors" => Some(self.accessors),
            "stringifiers" => Some(self.stringifiers),
            "comparators" => Some(self.comparators),
            "iterators" => Some(self.iterators),
            "iterables" => Some(self.iterables),
            "indexing" => Some(self.indexing),
            "arithmetic" => Some(self.arithmetic),
            "option" => Some(self.option),
            "callbacks" => Some(self.callbacks),
            "traits" => Some(self.traits),
            "custom_errors" => Some(self.custom_errors),
            "traits_are_send" => Some(self.traits_are_send),
            "traits_are_sync" => Some(self.traits_are_sync),
            "abi_compatibles" => Some(self.abi_compatibles),
            "struct_refs" => Some(self.struct_refs),
            "free_functions" => Some(self.free_functions),
            "custom_bindings" => Some(self.custom_bindings),
            "owned_slices" => Some(self.owned_slices),
            _ => None,
        }
    }
}

/// Defined by backends when validating attributes
pub trait AttributeValidator {
    /// The primary name of the backend, for use in diagnostics
    fn primary_name(&self) -> &str;
    /// Does this backend satisfy `cfg(backend_name)`?
    /// (Backends are allowed to satisfy multiple backend names, useful when there
    /// are multiple backends for a language)
    fn is_backend(&self, backend_name: &str) -> bool;
    /// does this backend satisfy cfg(name = value)?
    fn is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError>;
    /// What backedn attrs does this support?
    fn attrs_supported(&self) -> BackendAttrSupport;

    /// Provided, checks if type satisfies a `DiplomatBackendAttrCfg`
    ///
    /// auto_found helps check for `auto`, which is only allowed within `any` and at the top level. When `None`,
    /// `auto` is not allowed.
    fn satisfies_cfg(
        &self,
        cfg: &DiplomatBackendAttrCfg,
        mut auto_found: Option<&mut bool>,
    ) -> Result<bool, LoweringError> {
        Ok(match *cfg {
            DiplomatBackendAttrCfg::Not(ref c) => !self.satisfies_cfg(c, None)?,
            DiplomatBackendAttrCfg::Any(ref cs) => {
                #[allow(clippy::needless_option_as_deref)]
                // False positive: we need this for reborrowing
                for c in cs {
                    if self.satisfies_cfg(c, auto_found.as_deref_mut())? {
                        return Ok(true);
                    }
                }
                false
            }
            DiplomatBackendAttrCfg::All(ref cs) => {
                for c in cs {
                    if !self.satisfies_cfg(c, None)? {
                        return Ok(false);
                    }
                }
                true
            }
            DiplomatBackendAttrCfg::Auto => {
                if let Some(found) = auto_found {
                    *found = true;
                    return Ok(true);
                } else {
                    return Err(LoweringError::Other("auto in diplomat::attr() is only allowed at the top level and within `any`".into()));
                }
            }
            DiplomatBackendAttrCfg::Star => true,
            DiplomatBackendAttrCfg::BackendName(ref n) => self.is_backend(n),
            DiplomatBackendAttrCfg::NameValue(ref n, ref v) => self.is_name_value(n, v)?,
        })
    }

    // Provided, constructs an attribute
    fn attr_from_ast(
        &self,
        ast: &ast::Attrs,
        parent_attrs: &Attrs,
        errors: &mut ErrorStore,
    ) -> Attrs {
        Attrs::from_ast(ast, self, parent_attrs, errors)
    }

    // Provided: validates an attribute in the context in which it was constructed
    fn validate(&self, attrs: &Attrs, context: AttributeContext, errors: &mut ErrorStore) {
        attrs.validate(self, context, errors)
    }
}

/// A basic attribute validator
#[non_exhaustive]
#[derive(Default)]
pub struct BasicAttributeValidator {
    /// The primary name of this backend (should be unique, ideally)
    pub backend_name: String,
    /// The attributes supported
    pub support: BackendAttrSupport,
    /// Additional names for this backend
    pub other_backend_names: Vec<String>,
    /// override is_name_value()
    #[allow(clippy::type_complexity)] // dyn fn is not that complex
    pub is_name_value: Option<Box<dyn Fn(&str, &str) -> bool>>,
    /// The features enabled.
    pub features_enabled: HashSet<String>,
}

impl BasicAttributeValidator {
    pub fn new(backend_name: &str) -> Self {
        BasicAttributeValidator {
            backend_name: backend_name.into(),
            ..Self::default()
        }
    }
}

impl AttributeValidator for BasicAttributeValidator {
    fn primary_name(&self) -> &str {
        &self.backend_name
    }
    fn is_backend(&self, backend_name: &str) -> bool {
        self.backend_name == backend_name
            || self.other_backend_names.iter().any(|n| n == backend_name)
    }
    fn is_name_value(&self, name: &str, value: &str) -> Result<bool, LoweringError> {
        Ok(if name == "supports" {
            // destructure so new fields are forced to be added
            let BackendAttrSupport {
                namespacing,
                memory_sharing,
                non_exhaustive_structs,
                method_overloading,
                utf8_strings,
                utf16_strings,
                static_slices,
                defaults,

                constructors,
                named_constructors,
                fallible_constructors,
                accessors,
                static_accessors,
                stringifiers,
                comparators,
                iterators,
                iterables,
                indexing,
                arithmetic,
                option,
                callbacks,
                traits,
                custom_errors,
                traits_are_send,
                traits_are_sync,
                generate_mocking_interface,
                abi_compatibles,
                struct_refs,
                free_functions,
                custom_bindings,
                owned_slices,
                default_args,
            } = self.support;
            match value {
                "namespacing" => namespacing,
                "memory_sharing" => memory_sharing,
                "non_exhaustive_structs" => non_exhaustive_structs,
                "method_overloading" => method_overloading,
                "utf8_strings" => utf8_strings,
                "utf16_strings" => utf16_strings,
                "static_slices" => static_slices,
                "defaults" => defaults,

                "constructors" => constructors,
                "named_constructors" => named_constructors,
                "fallible_constructors" => fallible_constructors,
                "accessors" => accessors,
                "static_accessors" => static_accessors,
                "stringifiers" => stringifiers,
                "comparators" => comparators,
                "iterators" => iterators,
                "iterables" => iterables,
                "indexing" => indexing,
                "arithmetic" => arithmetic,
                "option" => option,
                "callbacks" => callbacks,
                "traits" => traits,
                "custom_errors" => custom_errors,
                "traits_are_send" => traits_are_send,
                "traits_are_sync" => traits_are_sync,
                "generate_mocking_interface" => generate_mocking_interface,
                "abi_compatibles" => abi_compatibles,
                "struct_refs" => struct_refs,
                "free_functions" => free_functions,
                "custom_bindings" => custom_bindings,
                "owned_slices" => owned_slices,
                "default_args" => default_args,
                _ => {
                    return Err(LoweringError::Other(format!(
                        "Unknown supports = value found: {value}"
                    )))
                }
            }
        } else if name == "feature" {
            self.features_enabled.contains(value)
        } else if let Some(ref nv) = self.is_name_value {
            nv(name, value)
        } else {
            false
        })
    }
    fn attrs_supported(&self) -> BackendAttrSupport {
        self.support
    }
}

#[cfg(test)]
mod tests {
    use crate::hir;
    use std::fmt::Write;

    macro_rules! uitest_lowering_attr {
        ($attrs:expr, $($file:tt)*) => {
            let parsed: syn::File = syn::parse_quote! { $($file)* };

            let mut output = String::new();

            let mut attr_validator = hir::BasicAttributeValidator::new("tests");
            attr_validator.support = $attrs;
            match hir::TypeContext::from_syn(&parsed, Default::default(), attr_validator) {
                Ok(_context) => (),
                Err(e) => {
                    for (ctx, err) in e {
                        writeln!(&mut output, "Lowering error in {ctx}: {err}").unwrap();
                    }
                }
            };
            insta::with_settings!({}, {
                insta::assert_snapshot!(output)
            });
        }
    }

    #[test]
    fn test_auto() {
        uitest_lowering_attr! { hir::BackendAttrSupport { comparators: true, ..Default::default()},
            #[diplomat::bridge]
            mod ffi {
                use std::cmp;

                #[diplomat::opaque_mut]
                #[diplomat::attr(auto, namespace = "should_not_show_up")]
                struct Opaque;


                impl Opaque {
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparator_static(&self, other: &Opaque) -> cmp::Ordering {
                        todo!()
                    }
                    #[diplomat::attr(*, iterator)]
                    pub fn next(&mut self) -> Option<u8> {
                        self.0.next()
                    }
                    #[diplomat::attr(auto, rename = "bar")]
                    pub fn auto_doesnt_work_on_renames(&self) {
                    }
                    #[diplomat::attr(auto, disable)]
                    pub fn auto_doesnt_work_on_disables(&self) {
                    }
                }

            }
        }
    }

    #[test]
    fn test_comparator() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {
                use std::cmp;

                #[diplomat::opaque_mut]
                struct Opaque;

                struct Struct {
                    field: u8
                }


                impl Opaque {
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparator_static(other: &Opaque) -> cmp::Ordering {
                        todo!()
                    }
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparator_none(&self) -> cmp::Ordering {
                        todo!()
                    }
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparator_othertype(other: Struct) -> cmp::Ordering {
                        todo!()
                    }
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparator_badreturn(&self, other: &Opaque) -> u8 {
                        todo!()
                    }
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparison_correct(&self, other: &Opaque) -> cmp::Ordering {
                        todo!()
                    }
                    pub fn comparison_unmarked(&self, other: &Opaque) -> cmp::Ordering {
                        todo!()
                    }
                    pub fn ordering_wrong(&self, other: cmp::Ordering) {
                        todo!()
                    }
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparison_mut(&self, other: &mut Opaque) -> cmp::Ordering {
                        todo!()
                    }
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparison_opt(&self, other: Option<&Opaque>) -> cmp::Ordering {
                        todo!()
                    }
                }

                impl Struct {
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparison_other(self, other: &Opaque) -> cmp::Ordering {
                        todo!()
                    }
                    #[diplomat::attr(auto, comparison)]
                    pub fn comparison_correct(self, other: Self) -> cmp::Ordering {
                        todo!()
                    }

                    #[diplomat::attr(auto, comparison)]
                    pub fn comparison_ref(&self, other: &Self) -> cmp::Ordering {
                        todo!()
                    }

                    #[diplomat::attr(auto, comparison)]
                    pub fn comparison_mut(&mut self, other: &Self) -> cmp::Ordering {
                        todo!()
                    }
                }
            }
        }
    }

    #[test]
    fn test_iterator() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {

                #[diplomat::opaque]
                struct Opaque(Vec<u8>);
                #[diplomat::opaque_mut]
                struct OpaqueIterator<'a>(std::slice::Iter<'a>);


                impl Opaque {
                    #[diplomat::attr(auto, iterable)]
                    pub fn iterable<'a>(&'a self) -> Box<OpaqueIterator<'a>> {
                        Box::new(OpaqueIterator(self.0.iter()))
                    }
                }

                impl OpaqueIterator {
                    #[diplomat::attr(auto, iterator)]
                    pub fn next(&mut self) -> Option<u8> {
                        self.0.next()
                    }
                }

                #[diplomat::opaque]
                struct Broken;

                impl Broken {
                    #[diplomat::attr(auto, iterable)]
                    pub fn iterable_no_return(&self) {}
                    #[diplomat::attr(auto, iterable)]
                    pub fn iterable_no_self() -> Box<BrokenIterator> { todo!() }

                    #[diplomat::attr(auto, iterable)]
                    pub fn iterable_non_custom(&self) -> u8 { todo!() }
                }

                #[diplomat::opaque]
                struct BrokenIterator;

                impl BrokenIterator {
                    #[diplomat::attr(auto, iterator)]
                    pub fn iterator_no_return(&self) {}
                    #[diplomat::attr(auto, iterator)]
                    pub fn iterator_no_self() -> Option<u8> { todo!() }

                    #[diplomat::attr(auto, iterator)]
                    pub fn iterator_no_option(&self) -> u8 { todo!() }
                }
            }
        }
    }

    #[test]
    fn test_unsupported_features() {
        uitest_lowering_attr! { hir::BackendAttrSupport::default(),
            #[diplomat::bridge]
            mod ffi {
                use std::cmp;
                use diplomat_runtime::DiplomatOption;

                #[diplomat::opaque]
                struct Opaque;

                struct Struct {
                    pub a: u8,
                    pub b: u8,
                    pub c: DiplomatOption<u8>,
                }

                struct Struct2 {
                    pub a: DiplomatOption<Struct>,
                }

                #[diplomat::out]
                struct OutStruct {
                    pub option: DiplomatOption<u8>
                }


                impl Opaque {
                    pub fn take_option(&self, option: DiplomatOption<u8>) {
                        todo!()
                    }
                    // Always ok since this translates to a Resulty return
                    pub fn returning_option_is_ok(&self) -> Option<u8> {
                        todo!()
                    }
                }

            }
        }
    }

    #[test]
    fn test_mocking_interface_for_opaque_type() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::opaque]
                #[diplomat::attr(tests, generate_mocking_interface)]
                pub struct Foo {
                    pub x: u32,
                    pub y: u32,
                }

                impl Foo {
                    pub fn new() -> Box<Self> {
                        Box::new(Self { x: 0, y: 0 })
                    }

                    pub fn get_x(&self) -> u32 {
                        self.x
                    }

                    pub fn get_y(&self) -> u32 {
                        self.y
                    }
                }
            }
        }
    }

    #[test]
    fn test_mocking_interface_for_non_opaque_type() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::attr(tests, generate_mocking_interface)]
                pub struct Foo {
                    pub x: u32,
                    pub y: u32,
                }

                impl Foo {
                    pub fn new() -> Self {
                        Self { x: 0, y: 0 }
                    }

                    pub fn get_x(self) -> u32 {
                        self.x
                    }

                    pub fn get_y(self) -> u32 {
                        self.y
                    }
                }
            }
        }
    }

    #[test]
    fn test_mocking_interface_for_unsupported_backend() {
        uitest_lowering_attr! { hir::BackendAttrSupport::default(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::attr(tests, generate_mocking_interface)]
                pub struct Foo {
                    pub x: u32,
                    pub y: u32,
                }

                impl Foo {
                    pub fn new() -> Self {
                        Self { x: 0, y: 0 }
                    }

                    pub fn get_x(self) -> u32 {
                        self.x
                    }

                    pub fn get_y(self) -> u32 {
                        self.y
                    }
                }
            }
        }
    }

    #[test]
    fn test_primitive_struct_slices() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::attr(auto, abi_compatible)]
                pub struct Foo {
                    pub x: u32,
                    pub y: u32
                }

                impl Foo {
                    pub fn takes_slice(sl : &[Foo]) {
                        todo!()
                    }
                }
            }
        }
    }

    #[test]
    fn test_primitive_struct_slices_for_unsupported_backend() {
        uitest_lowering_attr! { hir::BackendAttrSupport::default(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::attr(auto, abi_compatible)]
                pub struct Foo {
                    pub x: u32,
                    pub y: u32
                }

                impl Foo {
                    pub fn takes_slice(sl : &[Foo]) {
                        todo!()
                    }
                }
            }
        }
    }

    #[test]
    fn test_struct_ref_for_unsupported_backend() {
        uitest_lowering_attr! { hir::BackendAttrSupport::default(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::attr(auto, abi_compatible)]
                pub struct Foo {
                    pub x: u32,
                    pub y: u32
                }

                impl Foo {
                    pub fn takes_mut(&mut self) {
                        todo!()
                    }
                }
            }
        }
    }

    #[test]
    fn test_custom_extra_code() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::attr(tests, custom_extra_code(source="std::string test;", location="impl_block"))]
                #[diplomat::attr(tests, custom_extra_code(file="test_file.hpp", location="def_block"))]
                pub struct IncludeDef {}

                impl IncludeDef {}

                #[diplomat::attr(tests, custom_extra_code(file="testing.d.hpp", location="def_block"))]
                #[diplomat::attr(tests, custom_extra_code(source="void test() {}", location="impl_block"))]
                #[diplomat::opaque]
                pub struct IncludeBlock();

                impl IncludeBlock {}
            }

        }
    }

    #[test]
    fn test_custom_extra_code_fail() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {
                pub struct IncludeDef {
                    a : i32
                }

                impl IncludeDef {
                    #[diplomat::attr(tests, custom_extra_code(a="", location="unknown"))]
                    #[diplomat::attr(tests, custom_extra_code(location="unknown", source="test"))]
                    pub fn test() {

                    }
                }
            }

        }
    }

    #[test]
    fn test_custom_extra_code_fail_unsupported() {
        uitest_lowering_attr! { hir::BackendAttrSupport::default(),
            #[diplomat::bridge]
            mod ffi {
                pub struct IncludeDef {
                    a : i32
                }

                impl IncludeDef {
                    #[diplomat::attr(tests, custom_extra_code(source="", location="def_block"))]
                    pub fn test() {

                    }
                }
            }

        }
    }

    #[test]
    fn test_default_args() {
        uitest_lowering_attr! { hir::BackendAttrSupport::all_true(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::opaque]
                pub struct Test;

                impl Test {
                    pub fn test(a : i32, b : i64,
                    #[diplomat::attr(*, default_value=100)]
                    c : i128) {}
                }
            }
        }
    }
    #[test]
    fn test_default_args_unsupport() {
        uitest_lowering_attr! { hir::BackendAttrSupport::default(),
            #[diplomat::bridge]
            mod ffi {
                #[diplomat::opaque]
                pub struct Test;

                impl Test {
                    pub fn test(a : i32, b : i64,
                    #[diplomat::attr(*, default_value=100)]
                    c : i128) {}
                }
            }
        }
    }
}
