/**
 * The base class for a D symbol, which can be a module, variable, function, enum, etc.
 *
 * Copyright:   Copyright (C) 1999-2024 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/dsymbol.d, _dsymbol.d)
 * Documentation:  https://dlang.org/phobos/dmd_dsymbol.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dsymbol.d
 */

module dmd.dsymbol;

import core.stdc.stdarg;
import core.stdc.stdio;
import core.stdc.string;
import core.stdc.stdlib;

import dmd.aggregate;
import dmd.aliasthis;
import dmd.arraytypes;
import dmd.attrib;
import dmd.astenums;
import dmd.ast_node;
import dmd.gluelayer;
import dmd.dclass;
import dmd.declaration;
import dmd.denum;
import dmd.dimport;
import dmd.dmodule;
import dmd.dversion;
import dmd.dscope;
import dmd.dstruct;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.init;
import dmd.lexer;
import dmd.location;
import dmd.mtype;
import dmd.nspace;
import dmd.root.aav;
import dmd.root.rmem;
import dmd.rootobject;
import dmd.root.string;
import dmd.statement;
import dmd.staticassert;
import dmd.tokens;
import dmd.visitor;

import dmd.common.outbuffer;

/***************************************
 * Calls dg(Dsymbol *sym) for each Dsymbol.
 * If dg returns !=0, stops and returns that value else returns 0.
 * Params:
 *    symbols = Dsymbols
 *    dg = delegate to call for each Dsymbol
 * Returns:
 *    last value returned by dg()
 *
 * See_Also: $(REF each, dmd, root, array)
 */
int foreachDsymbol(Dsymbols* symbols, scope int delegate(Dsymbol) dg)
{
    assert(dg);
    if (symbols)
    {
        /* Do not use foreach, as the size of the array may expand during iteration
         */
        for (size_t i = 0; i < symbols.length; ++i)
        {
            Dsymbol s = (*symbols)[i];
            const result = dg(s);
            if (result)
                return result;
        }
    }
    return 0;
}

/***************************************
 * Calls dg(Dsymbol *sym) for each Dsymbol.
 * Params:
 *    symbols = Dsymbols
 *    dg = delegate to call for each Dsymbol
 *
 * See_Also: $(REF each, dmd, root, array)
 */
void foreachDsymbol(Dsymbols* symbols, scope void delegate(Dsymbol) dg)
{
    assert(dg);
    if (symbols)
    {
        /* Do not use foreach, as the size of the array may expand during iteration
         */
        for (size_t i = 0; i < symbols.length; ++i)
        {
            Dsymbol s = (*symbols)[i];
            dg(s);
        }
    }
}


struct Ungag
{
    uint oldgag;

    extern (D) this(uint old) nothrow @safe
    {
        this.oldgag = old;
    }

    extern (C++) ~this() nothrow
    {
        global.gag = oldgag;
    }
}

struct Visibility
{
    ///
    enum Kind : ubyte
    {
        undefined,
        none,           // no access
        private_,
        package_,
        protected_,
        public_,
        export_,
    }

    Kind kind;
    Package pkg;

    extern(C++) this(Visibility.Kind kind, Package pkg = null) pure nothrow @nogc @safe
    {
        this.kind = kind;
        this.pkg  = pkg;
    }

    extern (D):

    /**
     * Checks if `this` is less or more visible than `other`
     *
     * Params:
     *   other = Visibility to compare `this` to.
     *
     * Returns:
     *   A value `< 0` if `this` is less visible than `other`,
     *   a value `> 0` if `this` is more visible than `other`,
     *   and `0` if they are at the same level.
     *   Note that `package` visibility with different packages
     *   will also return `0`.
     */
    int opCmp(const Visibility other) const pure nothrow @nogc @safe
    {
        return this.kind - other.kind;
    }

    ///
    unittest
    {
        assert(Visibility(Visibility.Kind.public_) > Visibility(Visibility.Kind.private_));
        assert(Visibility(Visibility.Kind.private_) < Visibility(Visibility.Kind.protected_));
        assert(Visibility(Visibility.Kind.package_) >= Visibility(Visibility.Kind.package_));
    }

    /**
     * Checks if `this` is absolutely identical visibility attribute to `other`
     */
    bool opEquals(ref const Visibility other) const @safe
    {
        if (this.kind == other.kind)
        {
            if (this.kind == Visibility.Kind.package_)
                return this.pkg == other.pkg;
            return true;
        }
        return false;
    }
}

enum PASS : ubyte
{
    initial,        // initial state
    semantic,       // semantic() started
    semanticdone,   // semantic() done
    semantic2,      // semantic2() started
    semantic2done,  // semantic2() done
    semantic3,      // semantic3() started
    semantic3done,  // semantic3() done
    inline,         // inline started
    inlinedone,     // inline done
    obj,            // toObjFile() run
}

// Search options
alias SearchOptFlags = uint;
enum SearchOpt : SearchOptFlags
{
    all                     = 0x00, // search for all symbols
    ignorePrivateImports    = 0x01, // don't search private imports
    ignoreErrors            = 0x02, // don't give error messages
    ignoreAmbiguous         = 0x04, // return NULL if ambiguous
    localsOnly              = 0x08, // only look at locals (don't search imports)
    importsOnly             = 0x10, // only look in imports
    unqualifiedModule       = 0x20, // the module scope search is unqualified,
                                    // meaning don't search imports in that scope,
                                    // because qualified module searches search
                                    // their imports
    tagNameSpace            = 0x40, // search ImportC tag symbol table
    ignoreVisibility        = 0x80, // also find private and package protected symbols
}

/***********************************************************
 * Struct/Class/Union field state.
 * Used for transitory information when setting field offsets, such
 * as bit fields.
 */
struct FieldState
{
    uint offset;        /// byte offset for next field

    uint fieldOffset;   /// byte offset for the start of the bit field
    uint fieldSize;     /// byte size of field
    uint fieldAlign;    /// byte alignment of field
    uint bitOffset;     /// bit offset for field

    bool inFlight;      /// bit field is in flight

    void print() const
    {
        printf("FieldState.offset      = %d bytes\n",   offset);
        printf("          .fieldOffset = %d bytes\n",   fieldOffset);
        printf("          .bitOffset   = %d bits\n",    bitOffset);
        printf("          .fieldSize   = %d bytes\n",   fieldSize);
        printf("          .inFlight    = %d\n",         inFlight);
    }
}

// 99.9% of Dsymbols don't have attributes (at least in druntime and Phobos),
// so save memory by grouping them into a separate struct
private struct DsymbolAttributes
{
    /// C++ namespace this symbol belongs to
    CPPNamespaceDeclaration cppnamespace;
    /// customized deprecation message
    DeprecatedDeclaration depdecl_;
    /// user defined attributes
    UserAttributeDeclaration userAttribDecl;
}

/***********************************************************
 */
extern (C++) class Dsymbol : ASTNode
{
    Identifier ident;
    Dsymbol parent;
    Symbol* csym;           // symbol for code generator
    const Loc loc;          // where defined
    Scope* _scope;          // !=null means context to use for semantic()
    const(char)* prettystring;  // cached value of toPrettyChars()
    private DsymbolAttributes* atts; /// attached attribute declarations
    bool errors;            // this symbol failed to pass semantic()
    PASS semanticRun = PASS.initial;
    ushort localNum;        /// perturb mangled name to avoid collisions with those in FuncDeclaration.localsymtab

    final extern (D) this() nothrow @safe
    {
        //printf("Dsymbol::Dsymbol(%p)\n", this);
        loc = Loc(null, 0, 0);
    }

    final extern (D) this(Identifier ident) nothrow @safe
    {
        //printf("Dsymbol::Dsymbol(%p, ident)\n", this);
        this.loc = Loc(null, 0, 0);
        this.ident = ident;
    }

    final extern (D) this(const ref Loc loc, Identifier ident) nothrow @safe
    {
        //printf("Dsymbol::Dsymbol(%p, ident)\n", this);
        this.loc = loc;
        this.ident = ident;
    }

    static Dsymbol create(Identifier ident) nothrow @safe
    {
        return new Dsymbol(ident);
    }

    override const(char)* toChars() const
    {
        return ident ? ident.toChars() : "__anonymous";
    }

    // Getters / setters for fields stored in `DsymbolAttributes`
    final nothrow pure @safe
    {
        private ref DsymbolAttributes getAtts()
        {
            if (!atts)
                atts = new DsymbolAttributes();
            return *atts;
        }

        inout(DeprecatedDeclaration) depdecl() inout { return atts ? atts.depdecl_ : null; }
        inout(CPPNamespaceDeclaration) cppnamespace() inout { return atts ? atts.cppnamespace : null; }
        inout(UserAttributeDeclaration) userAttribDecl() inout { return atts ? atts.userAttribDecl : null; }

        DeprecatedDeclaration depdecl(DeprecatedDeclaration dd)
        {
            if (!dd && !atts)
                return null;
            return getAtts().depdecl_ = dd;
        }

        CPPNamespaceDeclaration cppnamespace(CPPNamespaceDeclaration ns)
        {
            if (!ns && !atts)
                return null;
            return getAtts().cppnamespace = ns;
        }

        UserAttributeDeclaration userAttribDecl(UserAttributeDeclaration uad)
        {
            if (!uad && !atts)
                return null;
            return getAtts().userAttribDecl = uad;
        }
    }

    // helper to print fully qualified (template) arguments
    const(char)* toPrettyCharsHelper()
    {
        return toChars();
    }

    final const(Loc) getLoc()
    {
        if (!loc.isValid()) // avoid bug 5861.
            if (const m = getModule())
                return Loc(m.srcfile.toChars(), 0, 0);
        return loc;
    }

    final const(char)* locToChars()
    {
        return getLoc().toChars();
    }

    override bool equals(const RootObject o) const
    {
        if (this == o)
            return true;
        const s = o.isDsymbol();
        if (!s)
            return false;
        // Overload sets don't have an ident
        // Function-local declarations may have identical names
        // if they are declared in different scopes
        if (s && ident && s.ident && ident.equals(s.ident) && localNum == s.localNum)
            return true;
        return false;
    }

    final bool isAnonymous() const
    {
        return ident is null || ident.isAnonymous;
    }

    extern(D) private const(char)[] prettyFormatHelper()
    {
        const cstr = toPrettyChars();
        return '`' ~ cstr.toDString() ~ "`\0";
    }

    /**********************************
     * Determine which Module a Dsymbol is in.
     */
    final Module getModule()
    {
        //printf("Dsymbol::getModule()\n");
        if (TemplateInstance ti = isInstantiated())
            return ti.tempdecl.getModule();
        Dsymbol s = this;
        while (s)
        {
            //printf("\ts = %s '%s'\n", s.kind(), s.toPrettyChars());
            Module m = s.isModule();
            if (m)
                return m;
            s = s.parent;
        }
        return null;
    }

    /**************************************
     * Does this Dsymbol come from a C file?
     * Returns:
     *  true if it does
     */
     final bool isCsymbol()
     {
        if (Module m = getModule())
            return m.filetype == FileType.c;
        return false;
    }

    /**********************************
     * Determine which Module a Dsymbol is in, as far as access rights go.
     */
    final Module getAccessModule()
    {
        //printf("Dsymbol::getAccessModule()\n");
        if (TemplateInstance ti = isInstantiated())
            return ti.tempdecl.getAccessModule();
        Dsymbol s = this;
        while (s)
        {
            //printf("\ts = %s '%s'\n", s.kind(), s.toPrettyChars());
            Module m = s.isModule();
            if (m)
                return m;
            TemplateInstance ti = s.isTemplateInstance();
            if (ti && ti.enclosing)
            {
                /* Because of local template instantiation, the parent isn't where the access
                 * rights come from - it's the template declaration
                 */
                s = ti.tempdecl;
            }
            else
                s = s.parent;
        }
        return null;
    }

    /**
     * `pastMixin` returns the enclosing symbol if this is a template mixin.
     *
     * `pastMixinAndNspace` does likewise, additionally skipping over Nspaces that
     * are mangleOnly.
     *
     * See also `parent`, `toParent` and `toParent2`.
     */
    final inout(Dsymbol) pastMixin() inout
    {
        //printf("Dsymbol::pastMixin() %s\n", toChars());
        if (!isTemplateMixin() && !isForwardingAttribDeclaration() && !isForwardingScopeDsymbol())
            return this;
        if (!parent)
            return null;
        return parent.pastMixin();
    }

    /**********************************
     * `parent` field returns a lexically enclosing scope symbol this is a member of.
     *
     * `toParent()` returns a logically enclosing scope symbol this is a member of.
     * It skips over TemplateMixin's.
     *
     * `toParent2()` returns an enclosing scope symbol this is living at runtime.
     * It skips over both TemplateInstance's and TemplateMixin's.
     * It's used when looking for the 'this' pointer of the enclosing function/class.
     *
     * `toParentDecl()` similar to `toParent2()` but always follows the template declaration scope
     * instead of the instantiation scope.
     *
     * `toParentLocal()` similar to `toParentDecl()` but follows the instantiation scope
     * if a template declaration is non-local i.e. global or static.
     *
     * Examples:
     * ---
     *  module mod;
     *  template Foo(alias a) { mixin Bar!(); }
     *  mixin template Bar() {
     *    public {  // VisibilityDeclaration
     *      void baz() { a = 2; }
     *    }
     *  }
     *  void test() {
     *    int v = 1;
     *    alias foo = Foo!(v);
     *    foo.baz();
     *    assert(v == 2);
     *  }
     *
     *  // s == FuncDeclaration('mod.test.Foo!().Bar!().baz()')
     *  // s.parent == TemplateMixin('mod.test.Foo!().Bar!()')
     *  // s.toParent() == TemplateInstance('mod.test.Foo!()')
     *  // s.toParent2() == FuncDeclaration('mod.test')
     *  // s.toParentDecl() == Module('mod')
     *  // s.toParentLocal() == FuncDeclaration('mod.test')
     * ---
     */
    final inout(Dsymbol) toParent() inout
    {
        return parent ? parent.pastMixin() : null;
    }

    /// ditto
    final inout(Dsymbol) toParent2() inout
    {
        if (!parent || !parent.isTemplateInstance && !parent.isForwardingAttribDeclaration() && !parent.isForwardingScopeDsymbol())
            return parent;
        return parent.toParent2;
    }

    /// ditto
    final inout(Dsymbol) toParentDecl() inout
    {
        return toParentDeclImpl(false);
    }

    /// ditto
    final inout(Dsymbol) toParentLocal() inout
    {
        return toParentDeclImpl(true);
    }

    private inout(Dsymbol) toParentDeclImpl(bool localOnly) inout
    {
        auto p = toParent();
        if (!p || !p.isTemplateInstance())
            return p;
        auto ti = p.isTemplateInstance();
        if (ti.tempdecl && (!localOnly || !(cast(TemplateDeclaration)ti.tempdecl).isstatic))
            return ti.tempdecl.toParentDeclImpl(localOnly);
        return parent.toParentDeclImpl(localOnly);
    }

    /**
     * Returns the declaration scope scope of `this` unless any of the symbols
     * `p1` or `p2` resides in its enclosing instantiation scope then the
     * latter is returned.
     */
    final Dsymbol toParentP(Dsymbol p1, Dsymbol p2 = null)
    {
        return followInstantiationContext(p1, p2) ? toParent2() : toParentLocal();
    }

    final inout(TemplateInstance) isInstantiated() inout
    {
        if (!parent)
            return null;
        auto ti = parent.isTemplateInstance();
        if (ti && !ti.isTemplateMixin())
            return ti;
        return parent.isInstantiated();
    }

    /***
     * Returns true if any of the symbols `p1` or `p2` resides in the enclosing
     * instantiation scope of `this`.
     */
    final bool followInstantiationContext(Dsymbol p1, Dsymbol p2 = null)
    {
        static bool has2This(Dsymbol s)
        {
            if (auto f = s.isFuncDeclaration())
                return f.hasDualContext();
            if (auto ad = s.isAggregateDeclaration())
                return ad.vthis2 !is null;
            return false;
        }

        if (has2This(this))
        {
            assert(p1);
            auto outer = toParent();
            while (outer)
            {
                auto ti = outer.isTemplateInstance();
                if (!ti)
                    break;
                foreach (oarg; *ti.tiargs)
                {
                    auto sa = getDsymbol(oarg);
                    if (!sa)
                        continue;
                    sa = sa.toAlias().toParent2();
                    if (!sa)
                        continue;
                    if (sa == p1)
                        return true;
                    else if (p2 && sa == p2)
                        return true;
                }
                outer = ti.tempdecl.toParent();
            }
            return false;
        }
        return false;
    }

    // Check if this function is a member of a template which has only been
    // instantiated speculatively, eg from inside is(typeof()).
    // Return the speculative template instance it is part of,
    // or NULL if not speculative.
    final inout(TemplateInstance) isSpeculative() inout
    {
        if (!parent)
            return null;
        auto ti = parent.isTemplateInstance();
        if (ti && ti.gagged)
            return ti;
        if (!parent.toParent())
            return null;
        return parent.isSpeculative();
    }

    final Ungag ungagSpeculative() const
    {
        uint oldgag = global.gag;
        if (global.gag && !isSpeculative() && !toParent2().isFuncDeclaration())
            global.gag = 0;
        return Ungag(oldgag);
    }

    // kludge for template.isSymbol()
    override final DYNCAST dyncast() const
    {
        return DYNCAST.dsymbol;
    }

    /*************************************
     * Do syntax copy of an array of Dsymbol's.
     */
    extern (D) static Dsymbols* arraySyntaxCopy(Dsymbols* a)
    {
        Dsymbols* b = null;
        if (a)
        {
            b = a.copy();
            for (size_t i = 0; i < b.length; i++)
            {
                (*b)[i] = (*b)[i].syntaxCopy(null);
            }
        }
        return b;
    }

    Identifier getIdent()
    {
        return ident;
    }

    const(char)* toPrettyChars(bool QualifyTypes = false)
    {
        if (prettystring && !QualifyTypes)
            return prettystring; // value cached for speed

        //printf("Dsymbol::toPrettyChars() '%s'\n", toChars());
        if (!parent)
        {
            auto s = toChars();
            if (!QualifyTypes)
                prettystring = s;
            return s;
        }

        OutBuffer buf;

        void addQualifiers(Dsymbol p)
        {
            if (p.parent)
            {
                addQualifiers(p.parent);
                buf.writeByte('.');
            }
            const s = QualifyTypes ? p.toPrettyCharsHelper() : p.toChars();
            buf.writestring(s);
        }

        addQualifiers(this);
        auto s = buf.extractSlice(true).ptr;

        if (!QualifyTypes)
            prettystring = s;
        return s;
    }

    const(char)* kind() const pure nothrow @nogc @safe
    {
        return "symbol";
    }

    /*********************************
     * If this symbol is really an alias for another,
     * return that other.
     * If needed, semantic() is invoked due to resolve forward reference.
     */
    Dsymbol toAlias()
    {
        return this;
    }

    /*********************************
     * Resolve recursive tuple expansion in eponymous template.
     */
    Dsymbol toAlias2()
    {
        return toAlias();
    }

    bool overloadInsert(Dsymbol s)
    {
        //printf("Dsymbol::overloadInsert('%s')\n", s.toChars());
        return false;
    }

    /*********************************
     * Returns:
     *  SIZE_INVALID when the size cannot be determined
     */
    uinteger_t size(const ref Loc loc)
    {
        .error(loc, "%s `%s` symbol `%s` has no size", kind, toPrettyChars, toChars());
        return SIZE_INVALID;
    }

    bool isforwardRef()
    {
        return false;
    }

    // is a 'this' required to access the member
    inout(AggregateDeclaration) isThis() inout
    {
        return null;
    }

    // is Dsymbol exported?
    bool isExport() const
    {
        return false;
    }

    // is Dsymbol imported?
    bool isImportedSymbol() const
    {
        return false;
    }

    // is Dsymbol deprecated?
    bool isDeprecated() @safe @nogc pure nothrow const
    {
        return false;
    }

    bool isOverloadable() const
    {
        return false;
    }

    // is this a LabelDsymbol()?
    LabelDsymbol isLabel()
    {
        return null;
    }

    /// Returns an AggregateDeclaration when toParent() is that.
    final inout(AggregateDeclaration) isMember() inout
    {
        //printf("Dsymbol::isMember() %s\n", toChars());
        auto p = toParent();
        //printf("parent is %s %s\n", p.kind(), p.toChars());
        return p ? p.isAggregateDeclaration() : null;
    }

    /// Returns an AggregateDeclaration when toParent2() is that.
    final inout(AggregateDeclaration) isMember2() inout
    {
        //printf("Dsymbol::isMember2() '%s'\n", toChars());
        auto p = toParent2();
        //printf("parent is %s %s\n", p.kind(), p.toChars());
        return p ? p.isAggregateDeclaration() : null;
    }

    /// Returns an AggregateDeclaration when toParentDecl() is that.
    final inout(AggregateDeclaration) isMemberDecl() inout
    {
        //printf("Dsymbol::isMemberDecl() '%s'\n", toChars());
        auto p = toParentDecl();
        //printf("parent is %s %s\n", p.kind(), p.toChars());
        return p ? p.isAggregateDeclaration() : null;
    }

    /// Returns an AggregateDeclaration when toParentLocal() is that.
    final inout(AggregateDeclaration) isMemberLocal() inout
    {
        //printf("Dsymbol::isMemberLocal() '%s'\n", toChars());
        auto p = toParentLocal();
        //printf("parent is %s %s\n", p.kind(), p.toChars());
        return p ? p.isAggregateDeclaration() : null;
    }

    // is this a member of a ClassDeclaration?
    final ClassDeclaration isClassMember()
    {
        auto ad = isMember();
        return ad ? ad.isClassDeclaration() : null;
    }

    // is this a type?
    Type getType()
    {
        return null;
    }

    // need a 'this' pointer?
    bool needThis()
    {
        return false;
    }

    /*************************************
     */
    Visibility visible() pure nothrow @nogc @safe
    {
        return Visibility(Visibility.Kind.public_);
    }

    /**************************************
     * Copy the syntax.
     * Used for template instantiations.
     * If s is NULL, allocate the new object, otherwise fill it in.
     */
    Dsymbol syntaxCopy(Dsymbol s)
    {
        printf("%s %s\n", kind(), toChars());
        assert(0);
    }

    /**************************************
     * Determine if this symbol is only one.
     * Returns:
     *      false, ps = null: There are 2 or more symbols
     *      true,  ps = null: There are zero symbols
     *      true,  ps = symbol: The one and only one symbol
     */
    bool oneMember(out Dsymbol ps, Identifier ident)
    {
        //printf("Dsymbol::oneMember()\n");
        ps = this;
        return true;
    }

    /*****************************************
     * Same as Dsymbol::oneMember(), but look at an array of Dsymbols.
     */
    extern (D) static bool oneMembers(Dsymbols* members, out Dsymbol ps, Identifier ident)
    {
        //printf("Dsymbol::oneMembers() %d\n", members ? members.length : 0);
        Dsymbol s = null;
        if (!members)
        {
            ps = null;
            return true;
        }

        for (size_t i = 0; i < members.length; i++)
        {
            Dsymbol sx = (*members)[i];
            bool x = sx.oneMember(ps, ident);
            //printf("\t[%d] kind %s = %d, s = %p\n", i, sx.kind(), x, *ps);
            if (!x)
            {
                //printf("\tfalse 1\n");
                assert(ps is null);
                return false;
            }
            if (ps)
            {
                assert(ident);
                if (!ps.ident || !ps.ident.equals(ident))
                    continue;
                if (!s)
                    s = ps;
                else if (s.isOverloadable() && ps.isOverloadable())
                {
                    // keep head of overload set
                    FuncDeclaration f1 = s.isFuncDeclaration();
                    FuncDeclaration f2 = ps.isFuncDeclaration();
                    if (f1 && f2)
                    {
                        assert(!f1.isFuncAliasDeclaration());
                        assert(!f2.isFuncAliasDeclaration());
                        for (; f1 != f2; f1 = f1.overnext0)
                        {
                            if (f1.overnext0 is null)
                            {
                                f1.overnext0 = f2;
                                break;
                            }
                        }
                    }
                }
                else // more than one symbol
                {
                    ps = null;
                    //printf("\tfalse 2\n");
                    return false;
                }
            }
        }
        ps = s; // s is the one symbol, null if none
        //printf("\ttrue\n");
        return true;
    }

    /*****************************************
     * Is Dsymbol a variable that contains pointers?
     */
    bool hasPointers()
    {
        //printf("Dsymbol::hasPointers() %s\n", toChars());
        return false;
    }

    bool hasStaticCtorOrDtor()
    {
        //printf("Dsymbol::hasStaticCtorOrDtor() %s\n", toChars());
        return false;
    }

    void addObjcSymbols(ClassDeclarations* classes, ClassDeclarations* categories)
    {
    }

    void checkCtorConstInit()
    {
    }

    /****************************************
     * Add documentation comment to Dsymbol.
     * Ignore NULL comments.
     */
    void addComment(const(char)* comment)
    {
        if (!comment || !*comment)
            return;

        //printf("addComment '%s' to Dsymbol %p '%s'\n", comment, this, toChars());
        void* h = cast(void*)this;      // just the pointer is the key
        auto p = h in commentHashTable;
        if (!p)
        {
            commentHashTable[h] = comment;
            return;
        }
        if (strcmp(*p, comment) != 0)
        {
            // Concatenate the two
            *p = Lexer.combineComments((*p).toDString(), comment.toDString(), true);
        }
    }

    /// get documentation comment for this Dsymbol
    final const(char)* comment()
    {
        //printf("getcomment: %p '%s'\n", this, this.toChars());
        if (auto p = cast(void*)this in commentHashTable)
        {
            //printf("comment: '%s'\n", *p);
            return *p;
        }
        return null;
    }

    /* Shell around addComment() to avoid disruption for the moment */
    final void comment(const(char)* comment) { addComment(comment); }

    private extern (D) __gshared const(char)*[void*] commentHashTable;


    /**********************************
     * Get ddoc unittest associated with this symbol.
     * (only use this with ddoc)
     * Returns: ddoc unittest, null if none
     */
    final UnitTestDeclaration ddocUnittest()
    {
        if (auto p = cast(void*)this in ddocUnittestHashTable)
            return *p;
        return null;
    }

    /**********************************
     * Set ddoc unittest associated with this symbol.
     */
    final void ddocUnittest(UnitTestDeclaration utd)
    {
        ddocUnittestHashTable[cast(void*)this] = utd;
    }

    private extern (D) __gshared UnitTestDeclaration[void*] ddocUnittestHashTable;


    /****************************************
     * Returns true if this symbol is defined in a non-root module without instantiation.
     */
    final bool inNonRoot()
    {
        Dsymbol s = parent;
        for (; s; s = s.toParent())
        {
            if (auto ti = s.isTemplateInstance())
            {
                return false;
            }
            if (auto m = s.isModule())
            {
                if (!m.isRoot())
                    return true;
                break;
            }
        }
        return false;
    }

    /**
     * Deinitializes the global state of the compiler.
     *
     * This can be used to restore the state set by `_init` to its original
     * state.
     */
    static void deinitialize()
    {
        commentHashTable = commentHashTable.init;
        ddocUnittestHashTable = ddocUnittestHashTable.init;
    }

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

  pure nothrow @safe @nogc:

    // Eliminate need for dynamic_cast
    inout(Package)                     isPackage()                     inout { return null; }
    inout(Module)                      isModule()                      inout { return null; }
    inout(EnumMember)                  isEnumMember()                  inout { return null; }
    inout(TemplateDeclaration)         isTemplateDeclaration()         inout { return null; }
    inout(TemplateInstance)            isTemplateInstance()            inout { return null; }
    inout(TemplateMixin)               isTemplateMixin()               inout { return null; }
    inout(ForwardingAttribDeclaration) isForwardingAttribDeclaration() inout { return null; }
    inout(Nspace)                      isNspace()                      inout { return null; }
    inout(Declaration)                 isDeclaration()                 inout { return null; }
    inout(StorageClassDeclaration)     isStorageClassDeclaration()     inout { return null; }
    inout(ExpressionDsymbol)           isExpressionDsymbol()           inout { return null; }
    inout(AliasAssign)                 isAliasAssign()                 inout { return null; }
    inout(ThisDeclaration)             isThisDeclaration()             inout { return null; }
    inout(BitFieldDeclaration)         isBitFieldDeclaration()         inout { return null; }
    inout(TypeInfoDeclaration)         isTypeInfoDeclaration()         inout { return null; }
    inout(TupleDeclaration)            isTupleDeclaration()            inout { return null; }
    inout(AliasDeclaration)            isAliasDeclaration()            inout { return null; }
    inout(AggregateDeclaration)        isAggregateDeclaration()        inout { return null; }
    inout(FuncDeclaration)             isFuncDeclaration()             inout { return null; }
    inout(FuncAliasDeclaration)        isFuncAliasDeclaration()        inout { return null; }
    inout(OverDeclaration)             isOverDeclaration()             inout { return null; }
    inout(FuncLiteralDeclaration)      isFuncLiteralDeclaration()      inout { return null; }
    inout(CtorDeclaration)             isCtorDeclaration()             inout { return null; }
    inout(PostBlitDeclaration)         isPostBlitDeclaration()         inout { return null; }
    inout(DtorDeclaration)             isDtorDeclaration()             inout { return null; }
    inout(StaticCtorDeclaration)       isStaticCtorDeclaration()       inout { return null; }
    inout(StaticDtorDeclaration)       isStaticDtorDeclaration()       inout { return null; }
    inout(SharedStaticCtorDeclaration) isSharedStaticCtorDeclaration() inout { return null; }
    inout(SharedStaticDtorDeclaration) isSharedStaticDtorDeclaration() inout { return null; }
    inout(InvariantDeclaration)        isInvariantDeclaration()        inout { return null; }
    inout(UnitTestDeclaration)         isUnitTestDeclaration()         inout { return null; }
    inout(NewDeclaration)              isNewDeclaration()              inout { return null; }
    inout(VarDeclaration)              isVarDeclaration()              inout { return null; }
    inout(VersionSymbol)               isVersionSymbol()               inout { return null; }
    inout(DebugSymbol)                 isDebugSymbol()                 inout { return null; }
    inout(ClassDeclaration)            isClassDeclaration()            inout { return null; }
    inout(StructDeclaration)           isStructDeclaration()           inout { return null; }
    inout(UnionDeclaration)            isUnionDeclaration()            inout { return null; }
    inout(InterfaceDeclaration)        isInterfaceDeclaration()        inout { return null; }
    inout(ScopeDsymbol)                isScopeDsymbol()                inout { return null; }
    inout(ForwardingScopeDsymbol)      isForwardingScopeDsymbol()      inout { return null; }
    inout(WithScopeSymbol)             isWithScopeSymbol()             inout { return null; }
    inout(ArrayScopeSymbol)            isArrayScopeSymbol()            inout { return null; }
    inout(Import)                      isImport()                      inout { return null; }
    inout(EnumDeclaration)             isEnumDeclaration()             inout { return null; }
    inout(SymbolDeclaration)           isSymbolDeclaration()           inout { return null; }
    inout(AttribDeclaration)           isAttribDeclaration()           inout { return null; }
    inout(AnonDeclaration)             isAnonDeclaration()             inout { return null; }
    inout(CPPNamespaceDeclaration)     isCPPNamespaceDeclaration()     inout { return null; }
    inout(VisibilityDeclaration)       isVisibilityDeclaration()       inout { return null; }
    inout(OverloadSet)                 isOverloadSet()                 inout { return null; }
    inout(MixinDeclaration)            isMixinDeclaration()            inout { return null; }
    inout(StaticAssert)                isStaticAssert()                inout { return null; }
    inout(StaticIfDeclaration)         isStaticIfDeclaration()         inout { return null; }
}

/***********************************************************
 * Dsymbol that generates a scope
 */
extern (C++) class ScopeDsymbol : Dsymbol
{
    Dsymbols* members;          // all Dsymbol's in this scope
    DsymbolTable symtab;        // members[] sorted into table
    uint endlinnum;             // the linnumber of the statement after the scope (0 if unknown)
    /// symbols whose members have been imported, i.e. imported modules and template mixins
    Dsymbols* importedScopes;
    Visibility.Kind* visibilities; // array of Visibility.Kind, one for each import

private:

    import dmd.root.bitarray;
    BitArray accessiblePackages, privateAccessiblePackages;// whitelists of accessible (imported) packages

public:
    final extern (D) this() nothrow @safe
    {
    }

    final extern (D) this(Identifier ident) nothrow @safe
    {
        super(ident);
    }

    final extern (D) this(const ref Loc loc, Identifier ident) nothrow @safe
    {
        super(loc, ident);
    }

    override ScopeDsymbol syntaxCopy(Dsymbol s)
    {
        //printf("ScopeDsymbol::syntaxCopy('%s')\n", toChars());
        ScopeDsymbol sds = s ? cast(ScopeDsymbol)s : new ScopeDsymbol(ident);
        sds.comment = comment;
        sds.members = arraySyntaxCopy(members);
        sds.endlinnum = endlinnum;
        return sds;
    }

    extern (D) final OverloadSet mergeOverloadSet(Identifier ident, OverloadSet os, Dsymbol s)
    {
        if (!os)
        {
            os = new OverloadSet(ident);
            os.parent = this;
        }
        if (OverloadSet os2 = s.isOverloadSet())
        {
            // Merge the cross-module overload set 'os2' into 'os'
            if (os.a.length == 0)
            {
                os.a.setDim(os2.a.length);
                memcpy(os.a.tdata(), os2.a.tdata(), (os.a[0]).sizeof * os2.a.length);
            }
            else
            {
                for (size_t i = 0; i < os2.a.length; i++)
                {
                    os = mergeOverloadSet(ident, os, os2.a[i]);
                }
            }
        }
        else
        {
            assert(s.isOverloadable());
            /* Don't add to os[] if s is alias of previous sym
             */
            for (size_t j = 0; j < os.a.length; j++)
            {
                Dsymbol s2 = os.a[j];
                if (s.toAlias() == s2.toAlias())
                {
                    if (s2.isDeprecated() || (s2.visible() < s.visible() && s.visible().kind != Visibility.Kind.none))
                    {
                        os.a[j] = s;
                    }
                    goto Lcontinue;
                }
            }
            os.push(s);
        Lcontinue:
        }
        return os;
    }

    void importScope(Dsymbol s, Visibility visibility) nothrow
    {
        //printf("%s.ScopeDsymbol::importScope(%s, %d)\n", toChars(), s.toChars(), visibility);
        // No circular or redundant import's
        if (s != this)
        {
            if (!importedScopes)
                importedScopes = new Dsymbols();
            else
            {
                for (size_t i = 0; i < importedScopes.length; i++)
                {
                    Dsymbol ss = (*importedScopes)[i];
                    if (ss == s) // if already imported
                    {
                        if (visibility.kind > visibilities[i])
                            visibilities[i] = visibility.kind; // upgrade access
                        return;
                    }
                }
            }
            importedScopes.push(s);
            visibilities = cast(Visibility.Kind*)mem.xrealloc(visibilities, importedScopes.length * (visibilities[0]).sizeof);
            visibilities[importedScopes.length - 1] = visibility.kind;
        }
    }


    /*****************************************
     * Returns: the symbols whose members have been imported, i.e. imported modules
     * and template mixins.
     *
     * See_Also: importScope
     */
    extern (D) final Dsymbols* getImportedScopes() nothrow @nogc @safe pure
    {
        return importedScopes;
    }

    /*****************************************
     * Returns: the array of visibilities associated with each imported scope. The
     * length of the array matches the imported scopes array.
     *
     * See_Also: getImportedScopes
     */
    extern (D) final Visibility.Kind[] getImportVisibilities() nothrow @nogc @safe pure
    {
        if (!importedScopes)
            return null;

        return (() @trusted => visibilities[0 .. importedScopes.length])();
    }

    extern (D) final void addAccessiblePackage(Package p, Visibility visibility) nothrow
    {
        auto pary = visibility.kind == Visibility.Kind.private_ ? &privateAccessiblePackages : &accessiblePackages;
        if (pary.length <= p.tag)
            pary.length = p.tag + 1;
        (*pary)[p.tag] = true;
    }

    bool isPackageAccessible(Package p, Visibility visibility, SearchOptFlags flags = SearchOpt.all) nothrow
    {
        if (p.tag < accessiblePackages.length && accessiblePackages[p.tag] ||
            visibility.kind == Visibility.Kind.private_ && p.tag < privateAccessiblePackages.length && privateAccessiblePackages[p.tag])
            return true;
        foreach (i, ss; importedScopes ? (*importedScopes)[] : null)
        {
            // only search visible scopes && imported modules should ignore private imports
            if (visibility.kind <= visibilities[i] &&
                ss.isScopeDsymbol.isPackageAccessible(p, visibility, SearchOpt.ignorePrivateImports))
                return true;
        }
        return false;
    }

    override final bool isforwardRef() nothrow
    {
        return (members is null);
    }

    static void multiplyDefined(const ref Loc loc, Dsymbol s1, Dsymbol s2)
    {
        version (none)
        {
            printf("ScopeDsymbol::multiplyDefined()\n");
            printf("s1 = %p, '%s' kind = '%s', parent = %s\n", s1, s1.toChars(), s1.kind(), s1.parent ? s1.parent.toChars() : "");
            printf("s2 = %p, '%s' kind = '%s', parent = %s\n", s2, s2.toChars(), s2.kind(), s2.parent ? s2.parent.toChars() : "");
        }
        if (loc.isValid())
        {
            .error(loc, "`%s` matches conflicting symbols:", s1.ident.toChars());
            errorSupplemental(s1.loc, "%s `%s`", s1.kind(), s1.toPrettyChars());
            errorSupplemental(s2.loc, "%s `%s`", s2.kind(), s2.toPrettyChars());

            static if (0)
            {
                if (auto so = s1.isOverloadSet())
                {
                    printf("first %p:\n", so);
                    foreach (s; so.a[])
                    {
                        printf("  %p %s `%s` at %s\n", s, s.kind(), s.toPrettyChars(), s.locToChars());
                    }
                }
                if (auto so = s2.isOverloadSet())
                {
                    printf("second %p:\n", so);
                    foreach (s; so.a[])
                    {
                        printf("  %p %s `%s` at %s\n", s, s.kind(), s.toPrettyChars(), s.locToChars());
                    }
                }
            }
        }
        else
        {
            .error(s1.loc, "%s `%s` conflicts with %s `%s` at %s", s1.kind, s1.toPrettyChars, s2.kind(), s2.toPrettyChars(), s2.locToChars());
        }
    }

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

    /********************************
     * Insert Dsymbol in table.
     * Params:
     *   s = symbol to add
     * Returns:
     *   null if already in table, `s` if inserted
     */
    Dsymbol symtabInsert(Dsymbol s) nothrow
    {
        return symtab.insert(s);
    }

    /****************************************
     * Look up identifier in symbol table.
     * Params:
     *  s = symbol
     *  id = identifier to look up
     * Returns:
     *   Dsymbol if found, null if not
     */
    Dsymbol symtabLookup(Dsymbol s, Identifier id) nothrow
    {
        return symtab.lookup(id);
    }

    /****************************************
     * Return true if any of the members are static ctors or static dtors, or if
     * any members have members that are.
     */
    override bool hasStaticCtorOrDtor()
    {
        if (members)
        {
            for (size_t i = 0; i < members.length; i++)
            {
                Dsymbol member = (*members)[i];
                if (member.hasStaticCtorOrDtor())
                    return true;
            }
        }
        return false;
    }

    override final inout(ScopeDsymbol) isScopeDsymbol() inout
    {
        return this;
    }

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

/***********************************************************
 * With statement scope
 */
extern (C++) final class WithScopeSymbol : ScopeDsymbol
{
    WithStatement withstate;

    extern (D) this(WithStatement withstate) nothrow @safe
    {
        this.withstate = withstate;
    }

    override inout(WithScopeSymbol) isWithScopeSymbol() inout
    {
        return this;
    }

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

/***********************************************************
 * Array Index/Slice scope
 */
extern (C++) final class ArrayScopeSymbol : ScopeDsymbol
{
    // either a SliceExp, an IndexExp, an ArrayExp, a TypeTuple or a TupleDeclaration.
    // Discriminated using DYNCAST and, for expressions, also EXP
    RootObject arrayContent;

    extern (D) this(Scope* sc, Expression exp) nothrow @safe
    {
        super(exp.loc, null);
        assert(exp.op == EXP.index || exp.op == EXP.slice || exp.op == EXP.array);
        this._scope = sc;
        this.arrayContent = exp;
    }

    extern (D) this(Scope* sc, TypeTuple type) nothrow @safe
    {
        this._scope = sc;
        this.arrayContent = type;
    }

    extern (D) this(Scope* sc, TupleDeclaration td) nothrow @safe
    {
        this._scope = sc;
        this.arrayContent = td;
    }

    override inout(ArrayScopeSymbol) isArrayScopeSymbol() inout
    {
        return this;
    }

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

/***********************************************************
 * Overload Sets
 */
extern (C++) final class OverloadSet : Dsymbol
{
    Dsymbols a;     // array of Dsymbols

    extern (D) this(Identifier ident, OverloadSet os = null) nothrow
    {
        super(ident);
        if (os)
        {
            a.pushSlice(os.a[]);
        }
    }

    void push(Dsymbol s) nothrow
    {
        a.push(s);
    }

    override inout(OverloadSet) isOverloadSet() inout
    {
        return this;
    }

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

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

/***********************************************************
 * Forwarding ScopeDsymbol.  Used by ForwardingAttribDeclaration and
 * ForwardingScopeDeclaration to forward symbol insertions to another
 * scope.  See `dmd.attrib.ForwardingAttribDeclaration` for more
 * details.
 */
extern (C++) final class ForwardingScopeDsymbol : ScopeDsymbol
{
    extern (D) this() nothrow @safe
    {
        super();
    }

    override Dsymbol symtabInsert(Dsymbol s) nothrow
    {
        if (auto d = s.isDeclaration())
        {
            if (d.storage_class & STC.local)
            {
                // Symbols with storage class STC.local are not
                // forwarded, but stored in the local symbol
                // table. (Those are the `static foreach` variables.)
                if (!symtab)
                {
                    symtab = new DsymbolTable();
                }
                return super.symtabInsert(s); // insert locally
            }
        }
        auto forward = parent.isScopeDsymbol();
        assert(forward);
        if (!forward.symtab)
        {
            forward.symtab = new DsymbolTable();
        }
        // Non-STC.local symbols are forwarded to `forward`.
        return forward.symtabInsert(s);
    }

    /************************
     * This override handles the following two cases:
     *     static foreach (i, i; [0]) { ... }
     * and
     *     static foreach (i; [0]) { enum i = 2; }
     */
    override Dsymbol symtabLookup(Dsymbol s, Identifier id) nothrow
    {
        // correctly diagnose clashing foreach loop variables.
        if (auto d = s.isDeclaration())
        {
            if (d.storage_class & STC.local)
            {
                if (!symtab)
                {
                    symtab = new DsymbolTable();
                }
                return super.symtabLookup(s,id);
            }
        }
        // Declarations within `static foreach` do not clash with
        // `static foreach` loop variables.
        auto forward = parent.isScopeDsymbol();
        assert(forward);
        if (!forward.symtab)
        {
            forward.symtab = new DsymbolTable();
        }
        return forward.symtabLookup(s,id);
    }

    override void importScope(Dsymbol s, Visibility visibility)
    {
        auto forward = parent.isScopeDsymbol();
        assert(forward);
        forward.importScope(s, visibility);
    }

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

    override inout(ForwardingScopeDsymbol) isForwardingScopeDsymbol() inout nothrow
    {
        return this;
    }

}

/**
 * Class that holds an expression in a Dsymbol wrapper.
 * This is not an AST node, but a class used to pass
 * an expression as a function parameter of type Dsymbol.
 */
extern (C++) final class ExpressionDsymbol : Dsymbol
{
    Expression exp;
    this(Expression exp) nothrow @safe
    {
        super();
        this.exp = exp;
    }

    override inout(ExpressionDsymbol) isExpressionDsymbol() inout nothrow
    {
        return this;
    }
}

/**********************************************
 * Encapsulate assigning to an alias:
 *      `identifier = type;`
 *      `identifier = symbol;`
 * where `identifier` is an AliasDeclaration in scope.
 */
extern (C++) final class AliasAssign : Dsymbol
{
    Identifier ident; /// Dsymbol's ident will be null, as this class is anonymous
    Type type;        /// replace previous RHS of AliasDeclaration with `type`
    Dsymbol aliassym; /// replace previous RHS of AliasDeclaration with `aliassym`
                      /// only one of type and aliassym can be != null

    extern (D) this(const ref Loc loc, Identifier ident, Type type, Dsymbol aliassym) nothrow @safe
    {
        super(loc, null);
        this.ident = ident;
        this.type = type;
        this.aliassym = aliassym;
    }

    override AliasAssign syntaxCopy(Dsymbol s)
    {
        assert(!s);
        AliasAssign aa = new AliasAssign(loc, ident,
                type     ? type.syntaxCopy()         : null,
                aliassym ? aliassym.syntaxCopy(null) : null);
        return aa;
    }

    override inout(AliasAssign) isAliasAssign() inout
    {
        return this;
    }

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

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

/***********************************************************
 * Table of Dsymbol's
 */
extern (C++) final class DsymbolTable : RootObject
{
    AssocArray!(Identifier, Dsymbol) tab;

  nothrow:

   /***************************
    * Look up Identifier in symbol table
    * Params:
    *   ident = identifer to look up
    * Returns:
    *   Dsymbol if found, null if not
    */
    Dsymbol lookup(const Identifier ident)
    {
        //printf("DsymbolTable::lookup(%s)\n", ident.toChars());
        return tab[ident];
    }

    /**********
     * Replace existing symbol in symbol table with `s`.
     * If it's not there, add it.
     * Params:
     *   s = replacement symbol with same identifier
     */
    void update(Dsymbol s)
    {
        *tab.getLvalue(s.ident) = s;
    }

    /**************************
     * Insert Dsymbol in table.
     * Params:
     *   s = symbol to add
     * Returns:
     *   null if already in table, `s` if inserted
     */
    Dsymbol insert(Dsymbol s)
    {
        return insert(s.ident, s);
    }

    /**************************
     * Insert Dsymbol in table.
     * Params:
     *   ident = identifier to serve as index
     *   s = symbol to add
     * Returns:
     *   null if already in table, `s` if inserted
     */
    Dsymbol insert(const Identifier ident, Dsymbol s)
    {
        //printf("DsymbolTable.insert(this = %p, '%s')\n", this, s.ident.toChars());
        Dsymbol* ps = tab.getLvalue(ident);
        if (*ps)
            return null; // already in table
        *ps = s;
        return s;
    }

    /*****************
     * Returns:
     *  number of symbols in symbol table
     */
    size_t length() const pure
    {
        return tab.length;
    }
}

/**********************************************
 * ImportC tag symbols sit in a parallel symbol table,
 * so that this C code works:
 * ---
 * struct S { a; };
 * int S;
 * struct S s;
 * ---
 * But there are relatively few such tag symbols, so that would be
 * a waste of memory and complexity. An additional problem is we'd like the D side
 * to find the tag symbols with ordinary lookup, not lookup in both
 * tables, if the tag symbol is not conflicting with an ordinary symbol.
 * The solution is to put the tag symbols that conflict into an associative
 * array, indexed by the address of the ordinary symbol that conflicts with it.
 * C has no modules, so this associative array is tagSymTab[] in ModuleDeclaration.
 * A side effect of our approach is that D code cannot access a tag symbol that is
 * hidden by an ordinary symbol. This is more of a theoretical problem, as nobody
 * has mentioned it when importing C headers. If someone wants to do it,
 * too bad so sad. Change the C code.
 * This function fixes up the symbol table when faced with adding a new symbol
 * `s` when there is an existing symbol `s2` with the same name.
 * C also allows forward and prototype declarations of tag symbols,
 * this function merges those.
 * Params:
 *      sc = context
 *      s = symbol to add to symbol table
 *      s2 = existing declaration
 *      sds = symbol table
 * Returns:
 *      if s and s2 are successfully put in symbol table then return the merged symbol,
 *      null if they conflict
 */
Dsymbol handleTagSymbols(ref Scope sc, Dsymbol s, Dsymbol s2, ScopeDsymbol sds)
{
    enum log = false;
    if (log) printf("handleTagSymbols('%s') add %p existing %p\n", s.toChars(), s, s2);
    if (log) printf("  add %s %s, existing %s %s\n", s.kind(), s.toChars(), s2.kind(), s2.toChars());
    auto sd = s.isScopeDsymbol(); // new declaration
    auto sd2 = s2.isScopeDsymbol(); // existing declaration

    static if (log) void print(EnumDeclaration sd)
    {
        printf("members: %p\n", sd.members);
        printf("symtab: %p\n", sd.symtab);
        printf("endlinnum: %d\n", sd.endlinnum);
        printf("type: %s\n", sd.type.toChars());
        printf("memtype: %s\n", sd.memtype.toChars());
    }

    if (!sd2)
    {
        /* Look in tag table
         */
        if (log) printf(" look in tag table\n");
        if (auto p = cast(void*)s2 in sc._module.tagSymTab)
        {
            Dsymbol s2tag = *p;
            sd2 = s2tag.isScopeDsymbol();
            assert(sd2);        // only tags allowed in tag symbol table
        }
    }

    if (sd && sd2) // `s` is a tag, `sd2` is the same tag
    {
        if (log) printf(" tag is already defined\n");

        if (sd.kind() != sd2.kind())  // being enum/struct/union must match
            return null;              // conflict

        /* Not a redeclaration if one is a forward declaration.
         * Move members to the first declared type, which is sd2.
         */
        if (sd2.members)
        {
            if (!sd.members)
                return sd2;  // ignore the sd redeclaration
        }
        else if (sd.members)
        {
            sd2.members = sd.members; // transfer definition to sd2
            sd.members = null;
            if (auto ed2 = sd2.isEnumDeclaration())
            {
                auto ed = sd.isEnumDeclaration();
                if (ed.memtype != ed2.memtype)
                    return null;        // conflict

                // transfer ed's members to sd2
                ed2.members.foreachDsymbol( (s)
                {
                    if (auto em = s.isEnumMember())
                        em.ed = ed2;
                });

                ed2.type = ed.type;
                ed2.memtype = ed.memtype;
                ed2.added = false;
            }
            return sd2;
        }
        else
            return sd2; // ignore redeclaration
    }
    else if (sd) // `s` is a tag, `s2` is not
    {
        if (log) printf(" s is tag, s2 is not\n");
        /* add `s` as tag indexed by s2
         */
        sc._module.tagSymTab[cast(void*)s2] = s;
        return s;
    }
    else if (s2 is sd2) // `s2` is a tag, `s` is not
    {
        if (log) printf(" s2 is tag, s is not\n");
        /* replace `s2` in symbol table with `s`,
         * then add `s2` as tag indexed by `s`
         */
        sds.symtab.update(s);
        sc._module.tagSymTab[cast(void*)s] = s2;
        return s;
    }
    // neither s2 nor s is a tag
    if (log) printf(" collision\n");
    return null;
}


/**********************************************
 * ImportC allows redeclarations of C variables, functions and typedefs.
 *    extern int x;
 *    int x = 3;
 * and:
 *    extern void f();
 *    void f() { }
 * Attempt to merge them.
 * Params:
 *      sc = context
 *      s = symbol to add to symbol table
 *      s2 = existing declaration
 *      sds = symbol table
 * Returns:
 *      if s and s2 are successfully put in symbol table then return the merged symbol,
 *      null if they conflict
 */
Dsymbol handleSymbolRedeclarations(ref Scope sc, Dsymbol s, Dsymbol s2, ScopeDsymbol sds)
{
    enum log = false;
    if (log) printf("handleSymbolRedeclarations('%s')\n", s.toChars());
    if (log) printf("  add %s %s, existing %s %s\n", s.kind(), s.toChars(), s2.kind(), s2.toChars());

    static Dsymbol collision()
    {
        if (log) printf(" collision\n");
        return null;
    }
    /*
    Handle merging declarations with asm("foo") and their definitions
    */
    static void mangleWrangle(Declaration oldDecl, Declaration newDecl)
    {
        if (oldDecl && newDecl)
        {
            newDecl.mangleOverride = oldDecl.mangleOverride ? oldDecl.mangleOverride : null;
        }
    }

    auto vd = s.isVarDeclaration(); // new declaration
    auto vd2 = s2.isVarDeclaration(); // existing declaration

    if (vd && vd.isCmacro())
        return vd2;

    assert(!(vd2 && vd2.isCmacro()));

    if (vd && vd2)
    {
        /* if one is `static` and the other isn't, the result is undefined
         * behavior, C11 6.2.2.7
         */
        if ((vd.storage_class ^ vd2.storage_class) & STC.static_)
            return collision();

        const i1 =  vd._init && ! vd._init.isVoidInitializer();
        const i2 = vd2._init && !vd2._init.isVoidInitializer();

        if (i1 && i2)
            return collision();         // can't both have initializers

        mangleWrangle(vd2, vd);

        if (i1)                         // vd is the definition
        {
            vd2.storage_class |= STC.extern_;  // so toObjFile() won't emit it
            sds.symtab.update(vd);      // replace vd2 with the definition
            return vd;
        }

        /* BUG: the types should match, which needs semantic() to be run on it
         *    extern int x;
         *    int x;  // match
         *    typedef int INT;
         *    INT x;  // match
         *    long x; // collision
         * We incorrectly ignore these collisions
         */
        return vd2;
    }

    auto fd = s.isFuncDeclaration(); // new declaration
    auto fd2 = s2.isFuncDeclaration(); // existing declaration
    if (fd && fd2)
    {
        /* if one is `static` and the other isn't, the result is undefined
         * behavior, C11 6.2.2.7
         * However, match what gcc allows:
         *    static int sun1(); int sun1() { return 0; }
         * and:
         *    static int sun2() { return 0; } int sun2();
         * Both produce a static function.
         *
         * Both of these should fail:
         *    int sun3(); static int sun3() { return 0; }
         * and:
         *    int sun4() { return 0; } static int sun4();
         */
        // if adding `static`
        if (   fd.storage_class & STC.static_ &&
            !(fd2.storage_class & STC.static_))
        {
            return collision();
        }

        if (fd.fbody && fd2.fbody)
            return collision();         // can't both have bodies

        mangleWrangle(fd2, fd);

        if (fd.fbody)                   // fd is the definition
        {
            if (log) printf(" replace existing with new\n");
            sds.symtab.update(fd);      // replace fd2 in symbol table with fd
            fd.overnext = fd2;

            /* If fd2 is covering a tag symbol, then fd has to cover the same one
             */
            auto ps = cast(void*)fd2 in sc._module.tagSymTab;
            if (ps)
                sc._module.tagSymTab[cast(void*)fd] = *ps;

            return fd;
        }

        /* Just like with VarDeclaration, the types should match, which needs semantic() to be run on it.
         * FuncDeclaration::semantic() detects this, but it relies on .overnext being set.
         */
        fd2.overloadInsert(fd);

        return fd2;
    }

    auto td  = s.isAliasDeclaration();  // new declaration
    auto td2 = s2.isAliasDeclaration(); // existing declaration
    if (td && td2)
    {
        /* BUG: just like with variables and functions, the types should match, which needs semantic() to be run on it.
         * FuncDeclaration::semantic2() can detect this, but it relies overnext being set.
         */
        return td2;
    }

    return collision();
}
