/**
 * Defines declarations of various attributes.
 *
 * The term 'attribute' refers to things that can apply to a larger scope than a single declaration.
 * Among them are:
 * - Alignment (`align(8)`)
 * - User defined attributes (`@UDA`)
 * - Function Attributes (`@safe`)
 * - Storage classes (`static`, `__gshared`)
 * - Mixin declarations  (`mixin("int x;")`)
 * - Conditional compilation (`static if`, `static foreach`)
 * - Linkage (`extern(C)`)
 * - Anonymous structs / unions
 * - Protection (`private`, `public`)
 * - Deprecated declarations (`@deprecated`)
 *
 * Copyright:   Copyright (C) 1999-2025 by The D Language Foundation, All Rights Reserved
 * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/compiler/src/dmd/attrib.d, _attrib.d)
 * Documentation:  https://dlang.org/phobos/dmd_attrib.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/attrib.d
 */

module dmd.attrib;

import dmd.aggregate;
import dmd.arraytypes;
import dmd.astenums;
import dmd.cond;
import dmd.declaration;
import dmd.dmodule;
import dmd.dscope;
import dmd.dsymbol;
import dmd.dsymbolsem : include;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.hdrgen : visibilityToBuffer;
import dmd.id;
import dmd.identifier;
import dmd.location;
import dmd.mtype;
import dmd.objc; // for objc.addSymbols
import dmd.common.outbuffer;
import dmd.root.array; // for each
import dmd.visitor;

/***********************************************************
 * Abstract attribute applied to Dsymbol's used as a common
 * ancestor for storage classes (StorageClassDeclaration),
 * linkage (LinkageDeclaration) and others.
 */
extern (C++) abstract class AttribDeclaration : Dsymbol
{
    Dsymbols* decl;     /// Dsymbol's affected by this AttribDeclaration

    extern (D) this(Dsymbols* decl) @safe
    {
        super(DSYM.attribDeclaration);
        this.decl = decl;
    }

    extern (D) this(Loc loc, Dsymbols* decl) @safe
    {
        super(DSYM.attribDeclaration, loc, null);
        this.decl = decl;
    }

    extern (D) this(Loc loc, Identifier ident, Dsymbols* decl) @safe
    {
        super(DSYM.attribDeclaration, loc, ident);
        this.decl = decl;
    }

    /****************************************
     * Create a new scope if one or more given attributes
     * are different from the sc's.
     * If the returned scope != sc, the caller should pop
     * the scope after it used.
     */
    extern (D) static Scope* createNewScope(Scope* sc, STC stc, LINK linkage,
        CPPMANGLE cppmangle, Visibility visibility, int explicitVisibility,
        AlignDeclaration aligndecl, PragmaDeclaration inlining)
    {
        Scope* sc2 = sc;
        if (stc != sc.stc ||
            linkage != sc.linkage ||
            cppmangle != sc.cppmangle ||
            explicitVisibility != sc.explicitVisibility ||
            visibility != sc.visibility ||
            aligndecl !is sc.aligndecl ||
            inlining != sc.inlining)
        {
            // create new one for changes
            sc2 = sc.copy();
            sc2.stc = stc;
            sc2.linkage = linkage;
            sc2.cppmangle = cppmangle;
            sc2.visibility = visibility;
            sc2.explicitVisibility = explicitVisibility;
            sc2.aligndecl = aligndecl;
            sc2.inlining = inlining;
        }
        return sc2;
    }

    override const(char)* kind() const
    {
        return "attribute";
    }

    override final bool hasPointers()
    {
        return this.include(null).foreachDsymbol( (s) { return s.hasPointers(); } ) != 0;
    }

    /****************************************
     */
    override final void addObjcSymbols(ClassDeclarations* classes, ClassDeclarations* categories)
    {
        objc.addSymbols(this, classes, categories);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Storage classes applied to Dsymbols, e.g. `const int i;`
 *
 * <stc> <decl...>
 */
extern (C++) class StorageClassDeclaration : AttribDeclaration
{
    STC stc;

    extern (D) this(STC stc, Dsymbols* decl) @safe
    {
        super(decl);
        this.stc = stc;
        this.dsym = DSYM.storageClassDeclaration;
    }

    extern (D) this(Loc loc, STC stc, Dsymbols* decl) @safe
    {
        super(loc, decl);
        this.stc = stc;
        this.dsym = DSYM.storageClassDeclaration;
    }

    override StorageClassDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new StorageClassDeclaration(stc, Dsymbol.arraySyntaxCopy(decl));
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Deprecation with an additional message applied to Dsymbols,
 * e.g. `deprecated("Superseeded by foo") int bar;`.
 * (Note that `deprecated int bar;` is currently represented as a
 * StorageClassDeclaration with STC.deprecated_)
 *
 * `deprecated(<msg>) <decl...>`
 */
extern (C++) final class DeprecatedDeclaration : StorageClassDeclaration
{
    Expression msg;         /// deprecation message
    const(char)* msgstr;    /// cached string representation of msg

    extern (D) this(Expression msg, Dsymbols* decl) @safe
    {
        super(STC.deprecated_, decl);
        this.msg = msg;
    }

    override DeprecatedDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new DeprecatedDeclaration(msg.syntaxCopy(), Dsymbol.arraySyntaxCopy(decl));
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Linkage attribute applied to Dsymbols, e.g.
 * `extern(C) void foo()`.
 *
 * `extern(<linkage>) <decl...>`
 */
extern (C++) final class LinkDeclaration : AttribDeclaration
{
    LINK linkage; /// either explicitly set or `default_`

    extern (D) this(Loc loc, LINK linkage, Dsymbols* decl) @safe
    {
        super(loc, null, decl);
        //printf("LinkDeclaration(linkage = %d, decl = %p)\n", linkage, decl);
        this.linkage = linkage;
        this.dsym = DSYM.linkDeclaration;
    }

    static LinkDeclaration create(Loc loc, LINK p, Dsymbols* decl) @safe
    {
        return new LinkDeclaration(loc, p, decl);
    }

    override LinkDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new LinkDeclaration(loc, linkage, Dsymbol.arraySyntaxCopy(decl));
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Attribute declaring whether an external aggregate should be mangled as
 * a struct or class in C++, e.g. `extern(C++, struct) class C { ... }`.
 * This is required for correct name mangling on MSVC targets,
 * see cppmanglewin.d for details.
 *
 * `extern(C++, <cppmangle>) <decl...>`
 */
extern (C++) final class CPPMangleDeclaration : AttribDeclaration
{
    CPPMANGLE cppmangle;

    extern (D) this(Loc loc, CPPMANGLE cppmangle, Dsymbols* decl) @safe
    {
        super(loc, null, decl);
        //printf("CPPMangleDeclaration(cppmangle = %d, decl = %p)\n", cppmangle, decl);
        this.cppmangle = cppmangle;
        this.dsym = DSYM.cppMangleDeclaration;
    }

    override CPPMangleDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new CPPMangleDeclaration(loc, cppmangle, Dsymbol.arraySyntaxCopy(decl));
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/**
 * A node to represent an `extern(C++)` namespace attribute
 *
 * There are two ways to declarate a symbol as member of a namespace:
 * `Nspace` and `CPPNamespaceDeclaration`.
 * The former creates a scope for the symbol, and inject them in the
 * parent scope at the same time.
 * The later, this class, has no semantic implications and is only
 * used for mangling.
 * Additionally, this class allows one to use reserved identifiers
 * (D keywords) in the namespace.
 *
 * A `CPPNamespaceDeclaration` can be created from an `Identifier`
 * (already resolved) or from an `Expression`, which is CTFE-ed
 * and can be either a `TupleExp`, in which can additional
 * `CPPNamespaceDeclaration` nodes are created, or a `StringExp`.
 *
 * Note that this class, like `Nspace`, matches only one identifier
 * part of a namespace. For the namespace `"foo::bar"`,
 * the will be a `CPPNamespaceDeclaration` with its `ident`
 * set to `"bar"`, and its `namespace` field pointing to another
 * `CPPNamespaceDeclaration` with its `ident` set to `"foo"`.
 */
extern (C++) final class CPPNamespaceDeclaration : AttribDeclaration
{
    /// CTFE-able expression, resolving to `TupleExp` or `StringExp`
    Expression exp;

    extern (D) this(Loc loc, Identifier ident, Dsymbols* decl) @safe
    {
        super(loc, ident, decl);
        this.dsym = DSYM.cppNamespaceDeclaration;
    }

    extern (D) this(Loc loc, Expression exp, Dsymbols* decl) @safe
    {
        super(loc, null, decl);
        this.dsym = DSYM.cppNamespaceDeclaration;
        this.exp = exp;
    }

    extern (D) this(Loc loc, Identifier ident, Expression exp, Dsymbols* decl,
                    CPPNamespaceDeclaration parent) @safe
    {
        super(loc, ident, decl);
        this.dsym = DSYM.cppNamespaceDeclaration;
        this.exp = exp;
        this.cppnamespace = parent;
    }

    override CPPNamespaceDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new CPPNamespaceDeclaration(
            this.loc, this.ident, this.exp, Dsymbol.arraySyntaxCopy(this.decl), this.cppnamespace);
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Visibility declaration for Dsymbols, e.g. `public int i;`
 *
 * `<visibility> <decl...>` or
 * `package(<pkg_identifiers>) <decl...>` if `pkg_identifiers !is null`
 */
extern (C++) final class VisibilityDeclaration : AttribDeclaration
{
    Visibility visibility;          /// the visibility
    Identifier[] pkg_identifiers;   /// identifiers for `package(foo.bar)` or null

    /**
     * Params:
     *  loc = source location of attribute token
     *  visibility = visibility attribute data
     *  decl = declarations which are affected by this visibility attribute
     */
    extern (D) this(Loc loc, Visibility visibility, Dsymbols* decl) @safe
    {
        super(loc, null, decl);
        this.dsym = DSYM.visibilityDeclaration;
        this.visibility = visibility;
        //printf("decl = %p\n", decl);
    }

    /**
     * Params:
     *  loc = source location of attribute token
     *  pkg_identifiers = list of identifiers for a qualified package name
     *  decl = declarations which are affected by this visibility attribute
     */
    extern (D) this(Loc loc, Identifier[] pkg_identifiers, Dsymbols* decl)
    {
        super(loc, null, decl);
        this.dsym = DSYM.visibilityDeclaration;
        this.visibility.kind = Visibility.Kind.package_;
        this.pkg_identifiers = pkg_identifiers;
        if (pkg_identifiers.length > 0)
        {
            Dsymbol tmp;
            Package.resolve(pkg_identifiers, &tmp, null);
            visibility.pkg = tmp ? tmp.isPackage() : null;
        }
    }

    override VisibilityDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);

        if (visibility.kind == Visibility.Kind.package_)
            return new VisibilityDeclaration(this.loc, pkg_identifiers, Dsymbol.arraySyntaxCopy(decl));
        else
            return new VisibilityDeclaration(this.loc, visibility, Dsymbol.arraySyntaxCopy(decl));
    }

    override const(char)* kind() const
    {
        return "visibility attribute";
    }

    override const(char)* toPrettyChars(bool)
    {
        assert(visibility.kind > Visibility.Kind.undefined);
        OutBuffer buf;
        visibilityToBuffer(buf, visibility);
        return buf.extractChars();
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Alignment attribute for aggregates, members and variables.
 *
 * `align(<ealign>) <decl...>` or
 * `align <decl...>` if `ealign` is null
 */
extern (C++) final class AlignDeclaration : AttribDeclaration
{
    Expressions* exps;                              /// Expression(s) yielding the desired alignment,
                                                    /// the largest value wins
    /// the actual alignment is Unknown until it's either set to the value of `ealign`
    /// or the default if `ealign` is null ( / an error ocurred)
    structalign_t salign;


    extern (D) this(Loc loc, Expression exp, Dsymbols* decl)
    {
        super(loc, null, decl);
        this.dsym = DSYM.alignDeclaration;
        if (exp)
        {
            exps = new Expressions();
            exps.push(exp);
        }
    }

    extern (D) this(Loc loc, Expressions* exps, Dsymbols* decl) @safe
    {
        super(loc, null, decl);
        this.dsym = DSYM.alignDeclaration;
        this.exps = exps;
    }

    extern (D) this(Loc loc, structalign_t salign, Dsymbols* decl) @safe
    {
        super(loc, null, decl);
        this.dsym = DSYM.alignDeclaration;
        this.salign = salign;
    }

    override AlignDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new AlignDeclaration(loc,
            Expression.arraySyntaxCopy(exps),
            Dsymbol.arraySyntaxCopy(decl));
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * An anonymous struct/union (defined by `isunion`).
 */
extern (C++) final class AnonDeclaration : AttribDeclaration
{
    bool isunion;           /// whether it's a union
    int sem;                /// 1 if successful semantic()
    uint anonoffset;        /// offset of anonymous struct
    uint anonstructsize;    /// size of anonymous struct
    uint anonalignsize;     /// size of anonymous struct for alignment purposes

    extern (D) this(Loc loc, bool isunion, Dsymbols* decl) @safe
    {
        super(loc, null, decl);
        this.dsym = DSYM.anonDeclaration;
        this.isunion = isunion;
    }

    override AnonDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new AnonDeclaration(loc, isunion, Dsymbol.arraySyntaxCopy(decl));
    }

    override const(char)* kind() const
    {
        return (isunion ? "anonymous union" : "anonymous struct");
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Pragma applied to Dsymbols, e.g. `pragma(inline, true) void foo`,
 * but not PragmaStatement's like `pragma(msg, "hello");`.
 *
 * pragma(<ident>, <args>)
 */
extern (C++) final class PragmaDeclaration : AttribDeclaration
{
    Expressions* args;      /// parameters of this pragma

    extern (D) this(Loc loc, Identifier ident, Expressions* args, Dsymbols* decl) @safe
    {
        super(loc, ident, decl);
        this.dsym = DSYM.pragmaDeclaration;
        this.args = args;
    }

    override PragmaDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("PragmaDeclaration::syntaxCopy(%s)\n", toChars());
        assert(!s);
        return new PragmaDeclaration(loc, ident, Expression.arraySyntaxCopy(args), Dsymbol.arraySyntaxCopy(decl));
    }

    override const(char)* kind() const
    {
        return "pragma";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * A conditional compilation declaration, used for `version`
 * / `debug` and specialized for `static if`.
 *
 * <condition> { <decl...> } else { <elsedecl> }
 */
extern (C++) class ConditionalDeclaration : AttribDeclaration
{
    Condition condition;    /// condition deciding whether decl or elsedecl applies
    Dsymbols* elsedecl;     /// array of Dsymbol's for else block

    extern (D) this(Loc loc, Condition condition, Dsymbols* decl, Dsymbols* elsedecl) @safe
    {
        super(loc, null, decl);
        this.dsym = DSYM.conditionalDeclaration;
        //printf("ConditionalDeclaration::ConditionalDeclaration()\n");
        this.condition = condition;
        this.elsedecl = elsedecl;
    }

    override ConditionalDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new ConditionalDeclaration(loc, condition.syntaxCopy(), Dsymbol.arraySyntaxCopy(decl), Dsymbol.arraySyntaxCopy(elsedecl));
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * `<scopesym> {
 *      static if (<condition>) { <decl> } else { <elsedecl> }
 * }`
 */
extern (C++) final class StaticIfDeclaration : ConditionalDeclaration
{
    ScopeDsymbol scopesym;          /// enclosing symbol (e.g. module) where symbols will be inserted
    bool addisdone = false; /// true if members have been added to scope
    bool onStack = false;   /// true if a call to `include` is currently active

    extern (D) this(Loc loc, Condition condition, Dsymbols* decl, Dsymbols* elsedecl) @safe
    {
        super(loc, condition, decl, elsedecl);
        this.dsym = DSYM.staticIfDeclaration;
        //printf("StaticIfDeclaration::StaticIfDeclaration()\n");
    }

    override StaticIfDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new StaticIfDeclaration(loc, condition.syntaxCopy(), Dsymbol.arraySyntaxCopy(decl), Dsymbol.arraySyntaxCopy(elsedecl));
    }

    override const(char)* kind() const
    {
        return "static if";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Static foreach at declaration scope, like:
 *     static foreach (i; [0, 1, 2]){ }
 */

extern (C++) final class StaticForeachDeclaration : AttribDeclaration
{
    StaticForeach sfe; /// contains `static foreach` expansion logic

    ScopeDsymbol scopesym; /// cached enclosing scope (mimics `static if` declaration)

    /++
     `include` can be called multiple times, but a `static foreach`
     should be expanded at most once.  Achieved by caching the result
     of the first call.  We need both `cached` and `cache`, because
     `null` is a valid value for `cache`.
     +/
    bool onStack = false;
    bool cached = false;
    Dsymbols* cache = null;

    extern (D) this(StaticForeach sfe, Dsymbols* decl) @safe
    {
        super(sfe.loc, null, decl);
        this.dsym = DSYM.staticForeachDeclaration;
        this.sfe = sfe;
    }

    override StaticForeachDeclaration syntaxCopy(Dsymbol s)
    {
        assert(!s);
        return new StaticForeachDeclaration(
            sfe.syntaxCopy(),
            Dsymbol.arraySyntaxCopy(decl));
    }

    override const(char)* kind() const
    {
        return "static foreach";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Collection of declarations that stores foreach index variables in a
 * local symbol table.  Other symbols declared within are forwarded to
 * another scope, like:
 *
 *      static foreach (i; 0 .. 10) // loop variables for different indices do not conflict.
 *      { // this body is expanded into 10 ForwardingAttribDeclarations, where `i` has storage class STC.local
 *          mixin("enum x" ~ to!string(i) ~ " = i"); // ok, can access current loop variable
 *      }
 *
 *      static foreach (i; 0.. 10)
 *      {
 *          pragma(msg, mixin("x" ~ to!string(i))); // ok, all 10 symbols are visible as they were forwarded to the global scope
 *      }
 *
 *      static assert(!is(typeof(i))); // loop index variable is not visible outside of the static foreach loop
 *
 * A StaticForeachDeclaration generates one
 * ForwardingAttribDeclaration for each expansion of its body.  The
 * AST of the ForwardingAttribDeclaration contains both the `static
 * foreach` variables and the respective copy of the `static foreach`
 * body.  The functionality is achieved by using a
 * ForwardingScopeDsymbol as the parent symbol for the generated
 * declarations.
 */

extern(C++) final class ForwardingAttribDeclaration : AttribDeclaration
{
    ForwardingScopeDsymbol sym = null;

    this(Dsymbols* decl) @safe
    {
        super(decl);
        this.dsym = DSYM.forwardingAttribDeclaration;
        sym = new ForwardingScopeDsymbol();
        sym.symtab = new DsymbolTable();
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * Mixin declarations, like:
 *      mixin("int x");
 * https://dlang.org/spec/module.html#mixin-declaration
 */
// Note: was CompileDeclaration
extern (C++) final class MixinDeclaration : AttribDeclaration
{
    Expressions* exps;
    ScopeDsymbol scopesym;
    bool compiled;

    extern (D) this(Loc loc, Expressions* exps) @safe
    {
        super(loc, null, null);
        //printf("MixinDeclaration(loc = %d)\n", loc.linnum);
        this.dsym = DSYM.mixinDeclaration;
        this.exps = exps;
    }

    override MixinDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("MixinDeclaration::syntaxCopy('%s')\n", toChars());
        return new MixinDeclaration(loc, Expression.arraySyntaxCopy(exps));
    }

    override const(char)* kind() const
    {
        return "mixin";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/***********************************************************
 * User defined attributes look like:
 *      @foo(args, ...)
 *      @(args, ...)
 */
extern (C++) final class UserAttributeDeclaration : AttribDeclaration
{
    Expressions* atts;

    extern (D) this(Expressions* atts, Dsymbols* decl) @safe
    {
        super(decl);
        this.dsym = DSYM.userAttributeDeclaration;
        this.atts = atts;
    }

    override UserAttributeDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("UserAttributeDeclaration::syntaxCopy('%s')\n", toChars());
        assert(!s);
        return new UserAttributeDeclaration(Expression.arraySyntaxCopy(this.atts), Dsymbol.arraySyntaxCopy(decl));
    }

    extern (D) static Expressions* concat(Expressions* udas1, Expressions* udas2)
    {
        Expressions* udas;
        if (!udas1 || udas1.length == 0)
            udas = udas2;
        else if (!udas2 || udas2.length == 0)
            udas = udas1;
        else
        {
            /* Create a new tuple that combines them
             * (do not append to left operand, as this is a copy-on-write operation)
             */
            udas = new Expressions(2);
            (*udas)[0] = new TupleExp(Loc.initial, udas1);
            (*udas)[1] = new TupleExp(Loc.initial, udas2);
        }
        return udas;
    }

    override const(char)* kind() const
    {
        return "UserAttribute";
    }

    override void accept(Visitor v)
    {
        v.visit(this);
    }
}

/**
 * Returns `true` if the given symbol is a symbol declared in
 * `core.attribute` and has the given identifier.
 *
 * This is used to determine if a symbol is a UDA declared in
 * `core.attribute`.
 *
 * Params:
 *  sym = the symbol to check
 *  ident = the name of the expected UDA
 */
bool isCoreUda(Dsymbol sym, Identifier ident)
{
    if (sym.ident != ident || !sym.parent)
        return false;

    auto _module = sym.parent.isModule();
    return _module && _module.isCoreModule(Id.attribute);
}

/**
 * Iterates the UDAs attached to the given symbol, without performing semantic
 * analysis.
 *
 * Use this function instead of `foreachUda` if semantic analysis of `sym` is
 * still in progress.
 *
 * Params:
 *  sym = the symbol to get the UDAs from
 *  dg = called once for each UDA
 *
 * Returns:
 *  If `dg` returns `!= 0`, stops the iteration and returns that value.
 *  Otherwise, returns 0.
 */
int foreachUdaNoSemantic(Dsymbol sym, int delegate(Expression) dg)
{
    if (sym.userAttribDecl is null || sym.userAttribDecl.atts is null)
        return 0;

    foreach (exp; *sym.userAttribDecl.atts)
    {
        if (auto result = dg(exp))
            return result;
    }

    return 0;
}

/**
 * Returns: true if the given expression is an enum from `core.attribute` named `id`
 */
bool isEnumAttribute(Expression e, Identifier id)
{
    import dmd.attrib : isCoreUda;
    import dmd.id : Id;

    // Logic based on dmd.objc.Supported.declaredAsOptionalCount
    auto typeExp = e.isTypeExp;
    if (!typeExp)
        return false;

    auto typeEnum = typeExp.type.isTypeEnum();
    if (!typeEnum)
        return false;

    if (isCoreUda(typeEnum.sym, id))
        return true;

    return false;
}
