/**
 * Performs the semantic2 stage, which deals with initializer expressions.
 *
 * Copyright:   Copyright (C) 1999-2023 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/src/dmd/semantic2.d, _semantic2.d)
 * Documentation:  https://dlang.org/phobos/dmd_semantic2.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/semantic2.d
 */

module dmd.semantic2;

import core.stdc.stdio;
import core.stdc.string;

import dmd.aggregate;
import dmd.aliasthis;
import dmd.arraytypes;
import dmd.astcodegen;
import dmd.astenums;
import dmd.attrib;
import dmd.blockexit;
import dmd.clone;
import dmd.dcast;
import dmd.dclass;
import dmd.declaration;
import dmd.denum;
import dmd.dimport;
import dmd.dinterpret;
import dmd.dmodule;
import dmd.dscope;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.dversion;
import dmd.errors;
import dmd.escape;
import dmd.expression;
import dmd.expressionsem;
import dmd.func;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.init;
import dmd.initsem;
import dmd.hdrgen;
import dmd.mtype;
import dmd.nogc;
import dmd.nspace;
import dmd.objc;
import dmd.opover;
import dmd.parse;
import dmd.root.filename;
import dmd.common.outbuffer;
import dmd.root.rmem;
import dmd.rootobject;
import dmd.root.utf;
import dmd.sideeffect;
import dmd.statementsem;
import dmd.staticassert;
import dmd.tokens;
import dmd.statement;
import dmd.target;
import dmd.templateparamsem;
import dmd.typesem;
import dmd.visitor;

enum LOG = false;


/*************************************
 * Does semantic analysis on initializers and members of aggregates.
 */
extern(C++) void semantic2(Dsymbol dsym, Scope* sc)
{
    scope v = new Semantic2Visitor(sc);
    dsym.accept(v);
}

private extern(C++) final class Semantic2Visitor : Visitor
{
    alias visit = Visitor.visit;
    Scope* sc;
    this(Scope* sc) scope @safe
    {
        this.sc = sc;
    }

    override void visit(Dsymbol) {}

    override void visit(StaticAssert sa)
    {
        //printf("StaticAssert::semantic2() %s\n", sa.toChars());
        auto sds = new ScopeDsymbol();
        sc = sc.push(sds);
        sc.tinst = null;
        sc.minst = null;

        import dmd.staticcond;
        bool errors;
        bool result = evalStaticCondition(sc, sa.exp, sa.exp, errors);
        sc = sc.pop();
        if (errors)
        {
            errorSupplemental(sa.loc, "while evaluating: `static assert(%s)`", sa.exp.toChars());
            return;
        }
        else if (result)
            return;

        if (sa.msgs)
        {
            OutBuffer msgbuf;
            for (size_t i = 0; i < sa.msgs.length; i++)
            {
                Expression e = (*sa.msgs)[i];
                sc = sc.startCTFE();
                e = e.expressionSemantic(sc);
                e = resolveProperties(sc, e);
                sc = sc.endCTFE();
                e = ctfeInterpretForPragmaMsg(e);
                if (e.op == EXP.error)
                {
                    errorSupplemental(sa.loc, "while evaluating `static assert` argument `%s`", (*sa.msgs)[i].toChars());
                    return;
                }
                StringExp se = e.toStringExp();
                if (se)
                {
                    const slice = se.toUTF8(sc).peekString();
                    // Hack to keep old formatting to avoid changing error messages everywhere
                    if (sa.msgs.length == 1)
                        msgbuf.printf("\"%.*s\"", cast(int)slice.length, slice.ptr);
                    else
                        msgbuf.printf("%.*s", cast(int)slice.length, slice.ptr);
                }
                else
                    msgbuf.printf("%s", e.toChars());
            }
            error(sa.loc, "static assert:  %s", msgbuf.extractChars());
        }
        else
            error(sa.loc, "static assert:  `%s` is false", sa.exp.toChars());
        if (sc.tinst)
            sc.tinst.printInstantiationTrace();
        if (!global.gag)
            fatal();
    }

    override void visit(TemplateInstance tempinst)
    {
        if (tempinst.semanticRun >= PASS.semantic2)
            return;
        tempinst.semanticRun = PASS.semantic2;
        static if (LOG)
        {
            printf("+TemplateInstance.semantic2('%s')\n", tempinst.toChars());
            scope(exit) printf("-TemplateInstance.semantic2('%s')\n", tempinst.toChars());
        }
        if (tempinst.errors || !tempinst.members)
            return;

        TemplateDeclaration tempdecl = tempinst.tempdecl.isTemplateDeclaration();
        assert(tempdecl);

        sc = tempdecl._scope;
        assert(sc);
        sc = sc.push(tempinst.argsym);
        sc = sc.push(tempinst);
        sc.tinst = tempinst;
        sc.minst = tempinst.minst;

        int needGagging = (tempinst.gagged && !global.gag);
        uint olderrors = global.errors;
        int oldGaggedErrors = -1; // dead-store to prevent spurious warning
        if (needGagging)
            oldGaggedErrors = global.startGagging();

        for (size_t i = 0; i < tempinst.members.length; i++)
        {
            Dsymbol s = (*tempinst.members)[i];
            static if (LOG)
            {
                printf("\tmember '%s', kind = '%s'\n", s.toChars(), s.kind());
            }
            s.semantic2(sc);
            if (tempinst.gagged && global.errors != olderrors)
                break;
        }

        if (global.errors != olderrors)
        {
            if (!tempinst.errors)
            {
                if (!tempdecl.literal)
                    .error(tempinst.loc, "%s `%s` error instantiating", tempinst.kind, tempinst.toPrettyChars);
                if (tempinst.tinst)
                    tempinst.tinst.printInstantiationTrace();
            }
            tempinst.errors = true;
        }
        if (needGagging)
            global.endGagging(oldGaggedErrors);

        sc = sc.pop();
        sc.pop();
    }

    override void visit(TemplateMixin tmix)
    {
        if (tmix.semanticRun >= PASS.semantic2)
            return;
        tmix.semanticRun = PASS.semantic2;
        static if (LOG)
        {
            printf("+TemplateMixin.semantic2('%s')\n", tmix.toChars());
            scope(exit) printf("-TemplateMixin.semantic2('%s')\n", tmix.toChars());
        }
        if (!tmix.members)
            return;

        assert(sc);
        sc = sc.push(tmix.argsym);
        sc = sc.push(tmix);
        sc.tinst = tmix;
        sc.minst = tmix.minst;
        for (size_t i = 0; i < tmix.members.length; i++)
        {
            Dsymbol s = (*tmix.members)[i];
            static if (LOG)
            {
                printf("\tmember '%s', kind = '%s'\n", s.toChars(), s.kind());
            }
            s.semantic2(sc);
        }
        sc = sc.pop();
        sc.pop();
    }

    override void visit(VarDeclaration vd)
    {
        if (vd.semanticRun < PASS.semanticdone && vd.inuse)
            return;

        //printf("VarDeclaration::semantic2('%s')\n", toChars());
        sc = sc.push();
        sc.varDecl = vd;
        scope(exit) sc = sc.pop();

        if (vd.aliasTuple)        // if it's a tuple
        {
            vd.aliasTuple.accept(this);
            vd.semanticRun = PASS.semantic2done;
            return;
        }

        UserAttributeDeclaration.checkGNUABITag(vd, vd._linkage);

        if (vd._init && !vd.toParent().isFuncDeclaration())
        {
            vd.inuse++;

            /* https://issues.dlang.org/show_bug.cgi?id=20280
             *
             * Template instances may import modules that have not
             * finished semantic1.
             */
            if (!vd.type)
                vd.dsymbolSemantic(sc);


            // https://issues.dlang.org/show_bug.cgi?id=14166
            // https://issues.dlang.org/show_bug.cgi?id=20417
            // Don't run CTFE for the temporary variables inside typeof or __traits(compiles)
            vd._init = vd._init.initializerSemantic(sc, vd.type, sc.intypeof == 1 || sc.flags & SCOPE.compile ? INITnointerpret : INITinterpret);
            lowerStaticAAs(vd, sc);
            vd.inuse--;
        }
        if (vd._init && vd.storage_class & STC.manifest)
        {
            /* Cannot initializer enums with CTFE classreferences and addresses of struct literals.
             * Scan initializer looking for them. Issue error if found.
             */
            if (ExpInitializer ei = vd._init.isExpInitializer())
            {
                static bool hasInvalidEnumInitializer(Expression e)
                {
                    static bool arrayHasInvalidEnumInitializer(Expressions* elems)
                    {
                        foreach (e; *elems)
                        {
                            if (e && hasInvalidEnumInitializer(e))
                                return true;
                        }
                        return false;
                    }

                    if (e.op == EXP.classReference)
                        return true;
                    if (e.op == EXP.address && (cast(AddrExp)e).e1.op == EXP.structLiteral)
                        return true;
                    if (e.op == EXP.arrayLiteral)
                        return arrayHasInvalidEnumInitializer((cast(ArrayLiteralExp)e).elements);
                    if (e.op == EXP.structLiteral)
                        return arrayHasInvalidEnumInitializer((cast(StructLiteralExp)e).elements);
                    if (e.op == EXP.assocArrayLiteral)
                    {
                        AssocArrayLiteralExp ae = cast(AssocArrayLiteralExp)e;
                        return arrayHasInvalidEnumInitializer(ae.values) ||
                               arrayHasInvalidEnumInitializer(ae.keys);
                    }
                    return false;
                }

                if (hasInvalidEnumInitializer(ei.exp))
                    .error(vd.loc, "%s `%s` : Unable to initialize enum with class or pointer to struct. Use static const variable instead.", vd.kind, vd.toPrettyChars);
            }
        }
        else if (vd._init && vd.isThreadlocal())
        {
            // Cannot initialize a thread-local class or pointer to struct variable with a literal
            // that itself is a thread-local reference and would need dynamic initialization also.
            if ((vd.type.ty == Tclass) && vd.type.isMutable() && !vd.type.isShared())
            {
                ExpInitializer ei = vd._init.isExpInitializer();
                if (ei && ei.exp.op == EXP.classReference)
                    .error(vd.loc, "%s `%s` is a thread-local class and cannot have a static initializer. Use `static this()` to initialize instead.", vd.kind, vd.toPrettyChars);
            }
            else if (vd.type.ty == Tpointer && vd.type.nextOf().ty == Tstruct && vd.type.nextOf().isMutable() && !vd.type.nextOf().isShared())
            {
                ExpInitializer ei = vd._init.isExpInitializer();
                if (ei && ei.exp.op == EXP.address && (cast(AddrExp)ei.exp).e1.op == EXP.structLiteral)
                    .error(vd.loc, "%s `%s` is a thread-local pointer to struct and cannot have a static initializer. Use `static this()` to initialize instead.", vd.kind, vd.toPrettyChars);
            }
        }
        vd.semanticRun = PASS.semantic2done;
    }

    override void visit(Module mod)
    {
        //printf("Module::semantic2('%s'): parent = %p\n", toChars(), parent);
        if (mod.semanticRun != PASS.semanticdone) // semantic() not completed yet - could be recursive call
            return;
        mod.semanticRun = PASS.semantic2;
        // Note that modules get their own scope, from scratch.
        // This is so regardless of where in the syntax a module
        // gets imported, it is unaffected by context.
        Scope* sc = Scope.createGlobal(mod, global.errorSink); // create root scope
        //printf("Module = %p\n", sc.scopesym);
        if (mod.members)
        {
            // Pass 2 semantic routines: do initializers and function bodies
            for (size_t i = 0; i < mod.members.length; i++)
            {
                Dsymbol s = (*mod.members)[i];
                s.semantic2(sc);
            }
        }
        if (mod.userAttribDecl)
        {
            mod.userAttribDecl.semantic2(sc);
        }
        sc = sc.pop();
        sc.pop();
        mod.semanticRun = PASS.semantic2done;
        //printf("-Module::semantic2('%s'): parent = %p\n", toChars(), parent);
    }

    override void visit(FuncDeclaration fd)
    {
        if (fd.semanticRun >= PASS.semantic2done)
            return;

        if (fd.semanticRun < PASS.semanticdone && !fd.errors)
        {
            /* https://issues.dlang.org/show_bug.cgi?id=21614
             *
             * Template instances may import modules that have not
             * finished semantic1.
             */
            fd.dsymbolSemantic(sc);
        }
        assert(fd.semanticRun <= PASS.semantic2);
        fd.semanticRun = PASS.semantic2;

        //printf("FuncDeclaration::semantic2 [%s] fd: %s type: %s\n", fd.loc.toChars(), fd.toChars(), fd.type ? fd.type.toChars() : "".ptr);

        // Only check valid functions which have a body to avoid errors
        // for multiple declarations, e.g.
        // void foo();
        // void foo();
        if (fd.fbody && fd.overnext && !fd.errors)
        {
            // Always starts the lookup from 'this', because the conflicts with
            // previous overloads are already reported.
            alias f1 = fd;
            auto tf1 = cast(TypeFunction) f1.type;
            auto parent1 = f1.toParent2();
            const linkage1 = f1.resolvedLinkage();

            overloadApply(f1, (Dsymbol s)
            {
                auto f2 = s.isFuncDeclaration();
                if (!f2 || f1 == f2 || f2.errors)
                    return 0;

                // Don't have to check conflict between declaration and definition.
                if (f2.fbody is null)
                    return 0;

                // Functions with different manglings can never conflict
                if (linkage1 != f2.resolvedLinkage())
                    return 0;

                // Functions with different names never conflict
                // (they can form overloads sets introduced by an alias)
                if (f1.ident != f2.ident)
                    return 0;

                // Functions with different parents never conflict
                // (E.g. when aliasing a free function into a struct)
                if (parent1 != f2.toParent2())
                    return 0;

                /* Check for overload merging with base class member functions.
                 *
                 *  class B { void foo() {} }
                 *  class D : B {
                 *    override void foo() {}    // B.foo appears as f2
                 *    alias foo = B.foo;
                 *  }
                 */
                if (f1.overrides(f2))
                    return 0;

                auto tf2 = cast(TypeFunction) f2.type;

                // Overloading based on storage classes
                if (tf1.mod != tf2.mod || ((f1.storage_class ^ f2.storage_class) & STC.static_))
                    return 0;

                // @@@DEPRECATED_2.112@@@
                // This test doesn't catch identical functions that differ only
                // in explicit/implicit `@system` - a deprecation has now been
                // added below, remove `false` after deprecation period is over.
                const sameAttr = tf1.attributesEqual(tf2, false);
                const sameParams = tf1.parameterList == tf2.parameterList;

                // Allow the hack to declare overloads with different parameters/STC's
                if (parent1.isModule() &&
                    linkage1 != LINK.d && linkage1 != LINK.cpp &&
                    (!sameAttr || !sameParams)
                )
                {
                    .error(f2.loc, "%s `%s` cannot overload `extern(%s)` function at %s", f2.kind, f2.toPrettyChars,
                            linkageToChars(f1._linkage),
                            f1.loc.toChars());
                    return 0;
                }

                // Different parameters don't conflict in extern(C++/D)
                if (!sameParams)
                    return 0;

                // Different attributes don't conflict in extern(D)
                if (!sameAttr && linkage1 == LINK.d)
                {
                    // @@@DEPRECATED_2.112@@@
                    // Same as 2.104 deprecation, but also catching explicit/implicit `@system`
                    // At the end of deprecation period, fix Type.attributesEqual and remove
                    // this condition, as well as the error for extern(C) functions above.
                    if (sameAttr != tf1.attributesEqual(tf2))
                    {
                        .deprecation(f2.loc, "%s `%s` cannot overload `extern(%s)` function at %s", f2.kind, f2.toPrettyChars,
                                linkageToChars(f1._linkage),
                                f1.loc.toChars());
                    }
                    return 0;
                }

                .error(f2.loc, "%s `%s%s` conflicts with previous declaration at %s",
                        f2.kind(),
                        f2.toPrettyChars(),
                        parametersTypeToChars(tf2.parameterList),
                        f1.loc.toChars());
                f2.type = Type.terror;
                f2.errors = true;
                return 0;
            });
        }
        if (!fd.type || fd.type.ty != Tfunction)
            return;
        TypeFunction f = cast(TypeFunction) fd.type;

        UserAttributeDeclaration.checkGNUABITag(fd, fd._linkage);
        //semantic for parameters' UDAs
        foreach (i, param; f.parameterList)
        {
            if (param && param.userAttribDecl)
                param.userAttribDecl.semantic2(sc);
        }
    }

    override void visit(Import i)
    {
        //printf("Import::semantic2('%s')\n", toChars());
        if (!i.mod)
            return;

        i.mod.semantic2(null);
        if (i.mod.needmoduleinfo)
        {
            //printf("module5 %s because of %s\n", sc._module.toChars(), mod.toChars());
            if (sc)
                sc._module.needmoduleinfo = 1;
        }
    }

    override void visit(Nspace ns)
    {
        if (ns.semanticRun >= PASS.semantic2)
            return;
        ns.semanticRun = PASS.semantic2;
        static if (LOG)
        {
            printf("+Nspace::semantic2('%s')\n", ns.toChars());
            scope(exit) printf("-Nspace::semantic2('%s')\n", ns.toChars());
        }
        UserAttributeDeclaration.checkGNUABITag(ns, LINK.cpp);
        if (!ns.members)
            return;

        assert(sc);
        sc = sc.push(ns);
        sc.linkage = LINK.cpp;
        foreach (s; *ns.members)
        {
            static if (LOG)
            {
                printf("\tmember '%s', kind = '%s'\n", s.toChars(), s.kind());
            }
            s.semantic2(sc);
        }
        sc.pop();
    }

    override void visit(AttribDeclaration ad)
    {
        Dsymbols* d = ad.include(sc);
        if (!d)
            return;

        Scope* sc2 = ad.newScope(sc);
        for (size_t i = 0; i < d.length; i++)
        {
            Dsymbol s = (*d)[i];
            s.semantic2(sc2);
        }
        if (sc2 != sc)
            sc2.pop();
    }

    /**
     * Run the DeprecatedDeclaration's semantic2 phase then its members.
     *
     * The message set via a `DeprecatedDeclaration` can be either of:
     * - a string literal
     * - an enum
     * - a static immutable
     * So we need to call ctfe to resolve it.
     * Afterward forwards to the members' semantic2.
     */
    override void visit(DeprecatedDeclaration dd)
    {
        getMessage(dd);
        visit(cast(AttribDeclaration)dd);
    }

    override void visit(AlignDeclaration ad)
    {
        ad.getAlignment(sc);
        visit(cast(AttribDeclaration)ad);
    }

    override void visit(CPPNamespaceDeclaration decl)
    {
        UserAttributeDeclaration.checkGNUABITag(decl, LINK.cpp);
        visit(cast(AttribDeclaration)decl);
    }

    override void visit(UserAttributeDeclaration uad)
    {
        if (!uad.decl || !uad.atts || !uad.atts.length || !uad._scope)
            return visit(cast(AttribDeclaration)uad);

        Expression* lastTag;
        static void eval(Scope* sc, Expressions* exps, ref Expression* lastTag)
        {
            foreach (ref Expression e; *exps)
            {
                if (!e)
                    continue;

                e = e.expressionSemantic(sc);
                if (definitelyValueParameter(e))
                    e = e.ctfeInterpret();
                if (e.op == EXP.tuple)
                {
                    TupleExp te = cast(TupleExp)e;
                    eval(sc, te.exps, lastTag);
                }

                // Handles compiler-recognized `core.attribute.gnuAbiTag`
                if (UserAttributeDeclaration.isGNUABITag(e))
                    doGNUABITagSemantic(e, lastTag);
            }
        }

        uad._scope = null;
        eval(sc, uad.atts, lastTag);
        visit(cast(AttribDeclaration)uad);
    }

    override void visit(AggregateDeclaration ad)
    {
        //printf("AggregateDeclaration::semantic2(%s) type = %s, errors = %d\n", ad.toChars(), ad.type.toChars(), ad.errors);
        if (!ad.members)
            return;

        if (ad._scope)
        {
            .error(ad.loc, "%s `%s` has forward references", ad.kind, ad.toPrettyChars);
            return;
        }

        UserAttributeDeclaration.checkGNUABITag(
            ad, ad.classKind == ClassKind.cpp ? LINK.cpp : LINK.d);

        auto sc2 = ad.newScope(sc);

        ad.determineSize(ad.loc);

        for (size_t i = 0; i < ad.members.length; i++)
        {
            Dsymbol s = (*ad.members)[i];
            //printf("\t[%d] %s\n", i, s.toChars());
            s.semantic2(sc2);
        }

        sc2.pop();
    }

    override void visit(ClassDeclaration cd)
    {
        /// Checks that the given class implements all methods of its interfaces.
        static void checkInterfaceImplementations(ClassDeclaration cd)
        {
            foreach (base; cd.interfaces)
            {
                // https://issues.dlang.org/show_bug.cgi?id=22729
                // interfaces that have errors or that
                // inherit from interfaces that have errors
                // might have an uninitialized vtable
                if (!base.sym.vtbl.length)
                    continue;

                // first entry is ClassInfo reference
                auto methods = base.sym.vtbl[base.sym.vtblOffset .. $];

                foreach (m; methods)
                {
                    auto ifd = m.isFuncDeclaration;
                    assert(ifd);

                    if (ifd.objc.isOptional)
                        continue;

                    auto type = ifd.type.toTypeFunction();
                    auto fd = cd.findFunc(ifd.ident, type);

                    if (fd && !fd.isAbstract)
                    {
                        //printf("            found\n");
                        // Check that calling conventions match
                        if (fd._linkage != ifd._linkage)
                            .error(fd.loc, "%s `%s` linkage doesn't match interface function", fd.kind, fd.toPrettyChars);

                        // Check that it is current
                        //printf("newinstance = %d fd.toParent() = %s ifd.toParent() = %s\n",
                            //newinstance, fd.toParent().toChars(), ifd.toParent().toChars());
                        if (fd.toParent() != cd && ifd.toParent() == base.sym)
                            .error(cd.loc, "%s `%s` interface function `%s` is not implemented", cd.kind, cd.toPrettyChars, ifd.toFullSignature());
                    }
                    else
                    {
                        //printf("            not found %p\n", fd);
                        // BUG: should mark this class as abstract?
                        if (!cd.isAbstract())
                            .error(cd.loc, "%s `%s` interface function `%s` is not implemented", cd.kind, cd.toPrettyChars, ifd.toFullSignature());
                    }
                }
            }
        }

        if (cd.semanticRun >= PASS.semantic2done)
            return;
        assert(cd.semanticRun <= PASS.semantic2);
        cd.semanticRun = PASS.semantic2;

        checkInterfaceImplementations(cd);
        visit(cast(AggregateDeclaration) cd);
    }

    override void visit(InterfaceDeclaration cd)
    {
        visit(cast(AggregateDeclaration) cd);
    }

    override void visit(TupleDeclaration td)
    {
        td.foreachVar((s) { s.accept(this); });
    }
}

/**
 * Perform semantic analysis specific to the GNU ABI tags
 *
 * The GNU ABI tags are a feature introduced in C++11, specific to g++
 * and the Itanium ABI.
 * They are mandatory for C++ interfacing, simply because the templated struct
 *`std::basic_string`, of which the ubiquitous `std::string` is a instantiation
 * of, uses them.
 *
 * Params:
 *   e = Expression to perform semantic on
 *       See `Semantic2Visitor.visit(UserAttributeDeclaration)`
 *   lastTag = When `!is null`, we already saw an ABI tag.
 *            To simplify implementation and reflection code,
 *            only one ABI tag object is allowed per symbol
 *            (but it can have multiple tags as it's an array exp).
 */
private void doGNUABITagSemantic(ref Expression e, ref Expression* lastTag)
{
    import dmd.dmangle;

    // When `@gnuAbiTag` is used, the type will be the UDA, not the struct literal
    if (e.op == EXP.type)
    {
        error(e.loc, "`@%s` at least one argument expected", Id.udaGNUAbiTag.toChars());
        return;
    }

    // Definition is in `core.attributes`. If it's not a struct literal,
    // it shouldn't have passed semantic, hence the `assert`.
    auto sle = e.isStructLiteralExp();
    if (sle is null)
    {
        assert(global.errors);
        return;
    }
    // The definition of `gnuAttributes` only have 1 member, `string[] tags`
    assert(sle.elements && sle.elements.length == 1);
    // `gnuAbiTag`'s constructor is defined as `this(string[] tags...)`
    auto ale = (*sle.elements)[0].isArrayLiteralExp();
    if (ale is null)
    {
        error(e.loc, "`@%s` at least one argument expected", Id.udaGNUAbiTag.toChars());
        return;
    }

    // Check that it's the only tag on the symbol
    if (lastTag !is null)
    {
        const str1 = (*lastTag.isStructLiteralExp().elements)[0].toString();
        const str2 = ale.toString();
        error(e.loc, "only one `@%s` allowed per symbol", Id.udaGNUAbiTag.toChars());
        errorSupplemental(e.loc, "instead of `@%s @%s`, use `@%s(%.*s, %.*s)`",
            lastTag.toChars(), e.toChars(), Id.udaGNUAbiTag.toChars(),
            // Avoid [ ... ]
            cast(int)str1.length - 2, str1.ptr + 1,
            cast(int)str2.length - 2, str2.ptr + 1);
        return;
    }
    lastTag = &e;

    // We already know we have a valid array literal of strings.
    // Now checks that elements are valid.
    foreach (idx, elem; *ale.elements)
    {
        const str = elem.toStringExp().peekString();
        if (!str.length)
        {
            error(e.loc, "argument `%d` to `@%s` cannot be %s", cast(int)(idx + 1),
                    Id.udaGNUAbiTag.toChars(),
                    elem.isNullExp() ? "`null`".ptr : "empty".ptr);
            continue;
        }

        foreach (c; str)
        {
            if (!c.isValidMangling())
            {
                error(e.loc, "`@%s` char `0x%02x` not allowed in mangling",
                        Id.udaGNUAbiTag.toChars(), c);
                break;
            }
        }
        // Valid element
    }
    // Since ABI tags need to be sorted, we sort them in place
    // It might be surprising for users that inspects the UDAs,
    // but it's a concession to practicality.
    // Casts are unfortunately necessary as `implicitConvTo` is not
    // `const` (and nor is `StringExp`, by extension).
    static int predicate(const scope Expression* e1, const scope Expression* e2)
    {
        return (cast(Expression*)e1).toStringExp().compare((cast(Expression*)e2).toStringExp());
    }
    ale.elements.sort!predicate;
}

/**
 * Try lower a variable's static Associative Array to a newaa struct.
 * Params:
 *   vd = Variable to lower
 *   sc = Scope
 */
void lowerStaticAAs(VarDeclaration vd, Scope* sc)
{
    if (vd.storage_class & STC.manifest)
        return;
    if (auto ei = vd._init.isExpInitializer())
    {
        scope v = new StaticAAVisitor(sc);
        v.vd = vd;
        ei.exp.accept(v);
    }
}

/// Visit Associative Array literals and lower them to structs for static initialization
private extern(C++) final class StaticAAVisitor : SemanticTimeTransitiveVisitor
{
    alias visit = SemanticTimeTransitiveVisitor.visit;
    Scope* sc;
    VarDeclaration vd;

    this(Scope* sc) scope @safe
    {
        this.sc = sc;
    }

    override void visit(AssocArrayLiteralExp aaExp)
    {
        if (!verifyHookExist(aaExp.loc, *sc, Id._aaAsStruct, "initializing static associative arrays", Id.object))
            return;

        Expression hookFunc = new IdentifierExp(aaExp.loc, Id.empty);
        hookFunc = new DotIdExp(aaExp.loc, hookFunc, Id.object);
        hookFunc = new DotIdExp(aaExp.loc, hookFunc, Id._aaAsStruct);
        auto arguments = new Expressions();
        arguments.push(aaExp.syntaxCopy());
        Expression loweredExp = new CallExp(aaExp.loc, hookFunc, arguments);

        sc = sc.startCTFE();
        loweredExp = loweredExp.expressionSemantic(sc);
        loweredExp = resolveProperties(sc, loweredExp);
        sc = sc.endCTFE();
        loweredExp = loweredExp.ctfeInterpret();

        aaExp.lowering = loweredExp;
    }
}
