/**
 * Miscellaneous declarations, including typedef, alias, variable declarations including the
 * implicit this declaration, type tuples, ClassInfo, ModuleInfo and various TypeInfos.
 *
 * 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/declaration.d, _declaration.d)
 * Documentation:  https://dlang.org/phobos/dmd_declaration.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/declaration.d
 */

module dmd.declaration;

import core.stdc.stdio;
import dmd.aggregate;
import dmd.arraytypes;
import dmd.astenums;
import dmd.attrib;
import dmd.ctorflow;
import dmd.dclass;
import dmd.delegatize;
import dmd.dscope;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.errors;
import dmd.expression;
import dmd.func;
import dmd.globals;
import dmd.gluelayer;
import dmd.id;
import dmd.identifier;
import dmd.init;
import dmd.initsem;
import dmd.intrange;
import dmd.location;
import dmd.mtype;
import dmd.common.outbuffer;
import dmd.root.rootobject;
import dmd.target;
import dmd.tokens;
import dmd.typesem;
import dmd.visitor;

/************************************
 * Check to see the aggregate type is nested and its context pointer is
 * accessible from the current scope.
 * Returns true if error occurs.
 */
bool checkFrameAccess(Loc loc, Scope* sc, AggregateDeclaration ad, size_t iStart = 0)
{
    Dsymbol sparent = ad.toParentLocal();
    Dsymbol sparent2 = ad.toParent2();
    Dsymbol s = sc.func;
    if (ad.isNested() && s)
    {
        //printf("ad = %p %s [%s], parent:%p\n", ad, ad.toChars(), ad.loc.toChars(), ad.parent);
        //printf("sparent = %p %s [%s], parent: %s\n", sparent, sparent.toChars(), sparent.loc.toChars(), sparent.parent,toChars());
        //printf("sparent2 = %p %s [%s], parent: %s\n", sparent2, sparent2.toChars(), sparent2.loc.toChars(), sparent2.parent,toChars());
        if (!ensureStaticLinkTo(s, sparent) || sparent != sparent2 && !ensureStaticLinkTo(s, sparent2))
        {
            error(loc, "cannot access frame pointer of `%s`", ad.toPrettyChars());
            return true;
        }
    }

    bool result = false;
    for (size_t i = iStart; i < ad.fields.length; i++)
    {
        VarDeclaration vd = ad.fields[i];
        Type tb = vd.type.baseElemOf();
        if (tb.ty == Tstruct)
        {
            result |= checkFrameAccess(loc, sc, (cast(TypeStruct)tb).sym);
        }
    }
    return result;
}

/***********************************************
 * Mark variable v as modified if it is inside a constructor that var
 * is a field in.
 * Also used to allow immutable globals to be initialized inside a static constructor.
 * Returns:
 *    true if it's an initialization of v
 */
bool modifyFieldVar(Loc loc, Scope* sc, VarDeclaration var, Expression e1)
{
    //printf("modifyFieldVar(var = %s)\n", var.toChars());
    Dsymbol s = sc.func;
    while (1)
    {
        FuncDeclaration fd = null;
        if (s)
            fd = s.isFuncDeclaration();
        if (fd &&
            ((fd.isCtorDeclaration() && var.isField()) ||
             ((fd.isStaticCtorDeclaration() || fd.isCrtCtor) && !var.isField())) &&
            fd.toParentDecl() == var.toParent2() &&
            (!e1 || e1.op == EXP.this_))
        {
            bool result = true;

            var.ctorinit = true;
            //printf("setting ctorinit\n");

            if (var.isField() && sc.ctorflow.fieldinit.length && !sc.intypeof)
            {
                assert(e1);
                auto mustInit = ((var.storage_class & STC.nodefaultctor) != 0 ||
                                 var.type.needsNested());

                const dim = sc.ctorflow.fieldinit.length;
                auto ad = fd.isMemberDecl();
                assert(ad);
                size_t i;
                for (i = 0; i < dim; i++) // same as findFieldIndexByName in ctfeexp.c ?
                {
                    if (ad.fields[i] == var)
                        break;
                }
                assert(i < dim);
                auto fieldInit = &sc.ctorflow.fieldinit[i];
                const fi = fieldInit.csx;

                if (fi & CSX.this_ctor)
                {
                    if (var.type.isMutable() && e1.type.isMutable())
                        result = false;
                    else
                    {
                        const(char)* modStr = !var.type.isMutable() ? MODtoChars(var.type.mod) : MODtoChars(e1.type.mod);
                        .error(loc, "%s field `%s` initialized multiple times", modStr, var.toChars());
                        .errorSupplemental(fieldInit.loc, "Previous initialization is here.");
                    }
                }
                else if (sc.inLoop || (fi & CSX.label))
                {
                    if (!mustInit && var.type.isMutable() && e1.type.isMutable())
                        result = false;
                    else
                    {
                        const(char)* modStr = !var.type.isMutable() ? MODtoChars(var.type.mod) : MODtoChars(e1.type.mod);
                        .error(loc, "%s field `%s` initialization is not allowed in loops or after labels", modStr, var.toChars());
                    }
                }

                fieldInit.csx |= CSX.this_ctor;
                fieldInit.loc = e1.loc;
                if (var.overlapped) // https://issues.dlang.org/show_bug.cgi?id=15258
                {
                    foreach (j, v; ad.fields)
                    {
                        if (v is var || !var.isOverlappedWith(v))
                            continue;
                        v.ctorinit = true;
                        sc.ctorflow.fieldinit[j].csx = CSX.this_ctor;
                    }
                }
            }
            else if (fd != sc.func)
            {
                if (var.type.isMutable())
                    result = false;
                else if (sc.func.fes)
                {
                    const(char)* p = var.isField() ? "field" : var.kind();
                    .error(loc, "%s %s `%s` initialization is not allowed in foreach loop",
                        MODtoChars(var.type.mod), p, var.toChars());
                }
                else
                {
                    const(char)* p = var.isField() ? "field" : var.kind();
                    .error(loc, "%s %s `%s` initialization is not allowed in nested function `%s`",
                        MODtoChars(var.type.mod), p, var.toChars(), sc.func.toChars());
                }
            }
            else if (fd.isStaticCtorDeclaration() && !fd.isSharedStaticCtorDeclaration() &&
                     var.type.isImmutable())
            {
                .error(loc, "%s %s `%s` initialization is not allowed in `static this`",
                    MODtoChars(var.type.mod), var.kind(), var.toChars());
                errorSupplemental(loc, "Use `shared static this` instead.");
            }
            return result;
        }
        else
        {
            if (s)
            {
                s = s.toParentP(var.toParent2());
                continue;
            }
        }
        break;
    }
    return false;
}

/******************************************
 */
extern (C++) void ObjectNotFound(Identifier id)
{
    error(Loc.initial, "`%s` not found. object.d may be incorrectly installed or corrupt.", id.toChars());
    fatal();
}

/* Accumulator for successive matches.
 */
struct MatchAccumulator
{
    int count;              // number of matches found so far
    MATCH last = MATCH.nomatch; // match level of lastf
    FuncDeclaration lastf;  // last matching function we found
    FuncDeclaration nextf;  // if ambiguous match, this is the "other" function
}

/***********************************************************
 */
extern (C++) abstract class Declaration : Dsymbol
{
    Type type;
    Type originalType;  // before semantic analysis
    StorageClass storage_class = STC.undefined_;
    Visibility visibility;
    LINK _linkage = LINK.default_; // may be `LINK.system`; use `resolvedLinkage()` to resolve it
    short inuse;          // used to detect cycles

    ubyte adFlags;         // control re-assignment of AliasDeclaration (put here for packing reasons)
      enum wasRead    = 1; // set if AliasDeclaration was read
      enum ignoreRead = 2; // ignore any reads of AliasDeclaration
      enum nounderscore = 4; // don't prepend _ to mangled name

    Symbol* isym;           // import version of csym

    // overridden symbol with pragma(mangle, "...")
    const(char)[] mangleOverride;

    final extern (D) this(Identifier ident)
    {
        super(ident);
        visibility = Visibility(Visibility.Kind.undefined);
    }

    final extern (D) this(const ref Loc loc, Identifier ident)
    {
        super(loc, ident);
        visibility = Visibility(Visibility.Kind.undefined);
    }

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

    override final uinteger_t size(const ref Loc loc)
    {
        assert(type);
        const sz = type.size();
        if (sz == SIZE_INVALID)
            errors = true;
        return sz;
    }

    /**
     * Issue an error if an attempt to call a disabled method is made
     *
     * If the declaration is disabled but inside a disabled function,
     * returns `true` but do not issue an error message.
     *
     * Params:
     *   loc = Location information of the call
     *   sc  = Scope in which the call occurs
     *   isAliasedDeclaration = if `true` searches overload set
     *
     * Returns:
     *   `true` if this `Declaration` is `@disable`d, `false` otherwise.
     */
    extern (D) final bool checkDisabled(Loc loc, Scope* sc, bool isAliasedDeclaration = false)
    {
        if (!(storage_class & STC.disable))
            return false;

        if (sc.func && sc.func.storage_class & STC.disable)
            return true;

        if (auto p = toParent())
        {
            if (auto postblit = isPostBlitDeclaration())
            {
                /* https://issues.dlang.org/show_bug.cgi?id=21885
                 *
                 * If the generated postblit is disabled, it
                 * means that one of the fields has a disabled
                 * postblit. Print the first field that has
                 * a disabled postblit.
                 */
                if (postblit.isGenerated())
                {
                    auto sd = p.isStructDeclaration();
                    assert(sd);
                    for (size_t i = 0; i < sd.fields.length; i++)
                    {
                        auto structField = sd.fields[i];
                        if (structField.overlapped)
                            continue;
                        Type tv = structField.type.baseElemOf();
                        if (tv.ty != Tstruct)
                            continue;
                        auto sdv = (cast(TypeStruct)tv).sym;
                        if (!sdv.postblit)
                            continue;
                        if (sdv.postblit.isDisabled())
                        {
                            p.error(loc, "is not copyable because field `%s` is not copyable", structField.toChars());
                            return true;
                        }
                    }
                }
                p.error(loc, "is not copyable because it has a disabled postblit");
                return true;
            }
        }

        // if the function is @disabled, maybe there
        // is an overload in the overload set that isn't
        if (isAliasedDeclaration)
        {
            FuncDeclaration fd = isFuncDeclaration();
            if (fd)
            {
                for (FuncDeclaration ovl = fd; ovl; ovl = cast(FuncDeclaration)ovl.overnext)
                    if (!(ovl.storage_class & STC.disable))
                        return false;
            }
        }

        if (auto ctor = isCtorDeclaration())
        {
            if (ctor.isCpCtor && ctor.isGenerated())
            {
                .error(loc, "generating an `inout` copy constructor for `struct %s` failed, therefore instances of it are uncopyable", parent.toPrettyChars());
                return true;
            }
        }
        error(loc, "cannot be used because it is annotated with `@disable`");
        return true;
    }

    /*************************************
     * Check to see if declaration can be modified in this context (sc).
     * Issue error if not.
     * Params:
     *  loc  = location for error messages
     *  e1   = `null` or `this` expression when this declaration is a field
     *  sc   = context
     *  flag = if the first bit is set it means do not issue error message for
     *         invalid modification; if the second bit is set, it means that
               this declaration is a field and a subfield of it is modified.
     * Returns:
     *  Modifiable.yes or Modifiable.initialization
     */
    extern (D) final Modifiable checkModify(Loc loc, Scope* sc, Expression e1, ModifyFlags flag)
    {
        VarDeclaration v = isVarDeclaration();
        if (v && v.canassign)
            return Modifiable.initialization;

        if (isParameter() || isResult())
        {
            for (Scope* scx = sc; scx; scx = scx.enclosing)
            {
                if (scx.func == parent && (scx.flags & SCOPE.contract))
                {
                    const(char)* s = isParameter() && parent.ident != Id.ensure ? "parameter" : "result";
                    if (!(flag & ModifyFlags.noError))
                        error(loc, "cannot modify %s `%s` in contract", s, toChars());
                    return Modifiable.initialization; // do not report type related errors
                }
            }
        }

        if (e1 && e1.op == EXP.this_ && isField())
        {
            VarDeclaration vthis = e1.isThisExp().var;
            for (Scope* scx = sc; scx; scx = scx.enclosing)
            {
                if (scx.func == vthis.parent && (scx.flags & SCOPE.contract))
                {
                    if (!(flag & ModifyFlags.noError))
                        error(loc, "cannot modify parameter `this` in contract");
                    return Modifiable.initialization; // do not report type related errors
                }
            }
        }

        if (v && (v.isCtorinit() || isField()))
        {
            // It's only modifiable if inside the right constructor
            if ((storage_class & (STC.foreach_ | STC.ref_)) == (STC.foreach_ | STC.ref_))
                return Modifiable.initialization;
            if (flag & ModifyFlags.fieldAssign)
                return Modifiable.yes;
            return modifyFieldVar(loc, sc, v, e1) ? Modifiable.initialization : Modifiable.yes;
        }
        return Modifiable.yes;
    }

    override final Dsymbol search(const ref Loc loc, Identifier ident, int flags = SearchLocalsOnly)
    {
        Dsymbol s = Dsymbol.search(loc, ident, flags);
        if (!s && type)
        {
            s = type.toDsymbol(_scope);
            if (s)
                s = s.search(loc, ident, flags);
        }
        return s;
    }

    final bool isStatic() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.static_) != 0;
    }

    /// Returns the linkage, resolving the target-specific `System` one.
    final LINK resolvedLinkage() const
    {
        return _linkage == LINK.system ? target.systemLinkage() : _linkage;
    }

    bool isDelete()
    {
        return false;
    }

    bool isDataseg()
    {
        return false;
    }

    bool isThreadlocal()
    {
        return false;
    }

    bool isCodeseg() const pure nothrow @nogc @safe
    {
        return false;
    }

    final bool isFinal() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.final_) != 0;
    }

    bool isAbstract()
    {
        return (storage_class & STC.abstract_) != 0;
    }

    final bool isConst() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.const_) != 0;
    }

    final bool isImmutable() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.immutable_) != 0;
    }

    final bool isWild() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.wild) != 0;
    }

    final bool isAuto() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.auto_) != 0;
    }

    final bool isScope() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.scope_) != 0;
    }

    final bool isReturn() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.return_) != 0;
    }

    final bool isSynchronized() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.synchronized_) != 0;
    }

    final bool isParameter() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.parameter) != 0;
    }

    override final bool isDeprecated() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.deprecated_) != 0;
    }

    final bool isDisabled() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.disable) != 0;
    }

    final bool isOverride() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.override_) != 0;
    }

    final bool isResult() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.result) != 0;
    }

    final bool isField() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.field) != 0;
    }

    final bool isIn() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.in_) != 0;
    }

    final bool isOut() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.out_) != 0;
    }

    final bool isRef() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.ref_) != 0;
    }

    /// Returns: Whether the variable is a reference, annotated with `out` or `ref`
    final bool isReference() const pure nothrow @nogc @safe
    {
        return (storage_class & (STC.ref_ | STC.out_)) != 0;
    }

    final bool isFuture() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.future) != 0;
    }

    final extern(D) bool isSystem() const pure nothrow @nogc @safe
    {
        return (storage_class & STC.system) != 0;
    }

    override final Visibility visible() pure nothrow @nogc @safe
    {
        return visibility;
    }

    override final inout(Declaration) isDeclaration() inout pure nothrow @nogc @safe
    {
        return this;
    }

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

/***********************************************************
 */
extern (C++) final class TupleDeclaration : Declaration
{
    Objects* objects;
    TypeTuple tupletype;    // !=null if this is a type tuple
    bool isexp;             // true: expression tuple
    bool building;          // it's growing in AliasAssign semantic

    extern (D) this(const ref Loc loc, Identifier ident, Objects* objects)
    {
        super(loc, ident);
        this.objects = objects;
    }

    override TupleDeclaration syntaxCopy(Dsymbol s)
    {
        assert(0);
    }

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

    override Type getType()
    {
        /* If this tuple represents a type, return that type
         */

        //printf("TupleDeclaration::getType() %s\n", toChars());
        if (isexp || building)
            return null;
        if (!tupletype)
        {
            /* It's only a type tuple if all the Object's are types
             */
            for (size_t i = 0; i < objects.length; i++)
            {
                RootObject o = (*objects)[i];
                if (o.dyncast() != DYNCAST.type)
                {
                    //printf("\tnot[%d], %p, %d\n", i, o, o.dyncast());
                    return null;
                }
            }

            /* We know it's a type tuple, so build the TypeTuple
             */
            Types* types = cast(Types*)objects;
            auto args = new Parameters(objects.length);
            OutBuffer buf;
            int hasdeco = 1;
            for (size_t i = 0; i < types.length; i++)
            {
                Type t = (*types)[i];
                //printf("type = %s\n", t.toChars());
                version (none)
                {
                    buf.printf("_%s_%d", ident.toChars(), i);
                    const len = buf.offset;
                    const name = buf.extractSlice().ptr;
                    auto id = Identifier.idPool(name, len);
                    auto arg = new Parameter(STC.in_, t, id, null);
                }
                else
                {
                    auto arg = new Parameter(0, t, null, null, null);
                }
                (*args)[i] = arg;
                if (!t.deco)
                    hasdeco = 0;
            }

            tupletype = new TypeTuple(args);
            if (hasdeco)
                return tupletype.typeSemantic(Loc.initial, null);
        }
        return tupletype;
    }

    override Dsymbol toAlias2()
    {
        //printf("TupleDeclaration::toAlias2() '%s' objects = %s\n", toChars(), objects.toChars());
        for (size_t i = 0; i < objects.length; i++)
        {
            RootObject o = (*objects)[i];
            if (Dsymbol s = isDsymbol(o))
            {
                s = s.toAlias2();
                (*objects)[i] = s;
            }
        }
        return this;
    }

    override bool needThis()
    {
        //printf("TupleDeclaration::needThis(%s)\n", toChars());
        return isexp ? foreachVar((s) { return s.needThis(); }) != 0 : false;
    }

    /***********************************************************
     * Calls dg(Dsymbol) for each Dsymbol, which should be a VarDeclaration
     * inside VarExp (isexp == true).
     * Params:
     *    dg = delegate to call for each Dsymbol
     */
    extern (D) void foreachVar(scope void delegate(Dsymbol) dg)
    {
        assert(isexp);
        foreach (o; *objects)
        {
            if (auto e = o.isExpression())
                if (auto ve = e.isVarExp())
                    dg(ve.var);
        }
    }

    /***********************************************************
     * Calls dg(Dsymbol) for each Dsymbol, which should be a VarDeclaration
     * inside VarExp (isexp == true).
     * If dg returns !=0, stops and returns that value else returns 0.
     * Params:
     *    dg = delegate to call for each Dsymbol
     * Returns:
     *    last value returned by dg()
     */
    extern (D) int foreachVar(scope int delegate(Dsymbol) dg)
    {
        assert(isexp);
        foreach (o; *objects)
        {
            if (auto e = o.isExpression())
                if (auto ve = e.isVarExp())
                    if(auto ret = dg(ve.var))
                        return ret;
        }
        return 0;
    }

    override inout(TupleDeclaration) isTupleDeclaration() inout
    {
        return this;
    }

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

/***********************************************************
 * https://dlang.org/spec/declaration.html#AliasDeclaration
 */
extern (C++) final class AliasDeclaration : Declaration
{
    Dsymbol aliassym;   // alias ident = aliassym;

    Dsymbol overnext;   // next in overload list
    Dsymbol _import;    // !=null if unresolved internal alias for selective import

    extern (D) this(const ref Loc loc, Identifier ident, Type type)
    {
        super(loc, ident);
        //printf("AliasDeclaration(id = '%s', type = %p)\n", ident.toChars(), type);
        //printf("type = '%s'\n", type.toChars());
        this.type = type;
        assert(type);
    }

    extern (D) this(const ref Loc loc, Identifier ident, Dsymbol s)
    {
        super(loc, ident);
        //printf("AliasDeclaration(id = '%s', s = %p)\n", ident.toChars(), s);
        assert(s != this);
        this.aliassym = s;
        assert(s);
    }

    static AliasDeclaration create(const ref Loc loc, Identifier id, Type type)
    {
        return new AliasDeclaration(loc, id, type);
    }

    override AliasDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("AliasDeclaration::syntaxCopy()\n");
        assert(!s);
        AliasDeclaration sa = type ? new AliasDeclaration(loc, ident, type.syntaxCopy()) : new AliasDeclaration(loc, ident, aliassym.syntaxCopy(null));
        sa.comment = comment;
        sa.storage_class = storage_class;
        return sa;
    }

    override bool overloadInsert(Dsymbol s)
    {
        //printf("[%s] AliasDeclaration::overloadInsert('%s') s = %s %s @ [%s]\n",
        //       loc.toChars(), toChars(), s.kind(), s.toChars(), s.loc.toChars());

        /** Aliases aren't overloadable themselves, but if their Aliasee is
         *  overloadable they are converted to an overloadable Alias (either
         *  FuncAliasDeclaration or OverDeclaration).
         *
         *  This is done by moving the Aliasee into such an overloadable alias
         *  which is then used to replace the existing Aliasee. The original
         *  Alias (_this_) remains a useless shell.
         *
         *  This is a horrible mess. It was probably done to avoid replacing
         *  existing AST nodes and references, but it needs a major
         *  simplification b/c it's too complex to maintain.
         *
         *  A simpler approach might be to merge any colliding symbols into a
         *  simple Overload class (an array) and then later have that resolve
         *  all collisions.
         */
        if (semanticRun >= PASS.semanticdone)
        {
            /* Semantic analysis is already finished, and the aliased entity
             * is not overloadable.
             */
            if (type)
            {
                /*
                    If type has been resolved already we could
                    still be inserting an alias from an import.

                    If we are handling an alias then pretend
                    it was inserting and return true, if not then
                    false since we didn't even pretend to insert something.
                */
                return this._import && this.equals(s);
            }

            /* When s is added in member scope by static if, mixin("code") or others,
             * aliassym is determined already. See the case in: test/compilable/test61.d
             */
            auto sa = aliassym.toAlias();

            if (auto td = s.toAlias().isTemplateDeclaration())
                s = td.funcroot ? td.funcroot : td;

            if (auto fd = sa.isFuncDeclaration())
            {
                auto fa = new FuncAliasDeclaration(ident, fd);
                fa.visibility = visibility;
                fa.parent = parent;
                aliassym = fa;
                return aliassym.overloadInsert(s);
            }
            if (auto td = sa.isTemplateDeclaration())
            {
                auto od = new OverDeclaration(ident, td.funcroot ? td.funcroot : td);
                od.visibility = visibility;
                od.parent = parent;
                aliassym = od;
                return aliassym.overloadInsert(s);
            }
            if (auto od = sa.isOverDeclaration())
            {
                if (sa.ident != ident || sa.parent != parent)
                {
                    od = new OverDeclaration(ident, od);
                    od.visibility = visibility;
                    od.parent = parent;
                    aliassym = od;
                }
                return od.overloadInsert(s);
            }
            if (auto os = sa.isOverloadSet())
            {
                if (sa.ident != ident || sa.parent != parent)
                {
                    os = new OverloadSet(ident, os);
                    // TODO: visibility is lost here b/c OverloadSets have no visibility attribute
                    // Might no be a practical issue, b/c the code below fails to resolve the overload anyhow.
                    // ----
                    // module os1;
                    // import a, b;
                    // private alias merged = foo; // private alias to overload set of a.foo and b.foo
                    // ----
                    // module os2;
                    // import a, b;
                    // public alias merged = bar; // public alias to overload set of a.bar and b.bar
                    // ----
                    // module bug;
                    // import os1, os2;
                    // void test() { merged(123); } // should only look at os2.merged
                    //
                    // os.visibility = visibility;
                    os.parent = parent;
                    aliassym = os;
                }
                os.push(s);
                return true;
            }
            return false;
        }

        /* Don't know yet what the aliased symbol is, so assume it can
         * be overloaded and check later for correctness.
         */
        if (overnext)
            return overnext.overloadInsert(s);
        if (s is this)
            return true;
        overnext = s;
        return true;
    }

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

    override Type getType()
    {
        if (type)
            return type;
        return toAlias().getType();
    }

    override Dsymbol toAlias()
    {
        //printf("[%s] AliasDeclaration::toAlias('%s', this = %p, aliassym = %p, kind = '%s', inuse = %d)\n",
        //    loc.toChars(), toChars(), this, aliassym, aliassym ? aliassym.kind() : "", inuse);
        assert(this != aliassym);
        //static int count; if (++count == 10) *(char*)0=0;

        // Reading the AliasDeclaration
        if (!(adFlags & ignoreRead))
            adFlags |= wasRead;                 // can never assign to this AliasDeclaration again

        if (inuse == 1 && type && _scope)
        {
            inuse = 2;
            uint olderrors = global.errors;
            Dsymbol s = type.toDsymbol(_scope);
            //printf("[%s] type = %s, s = %p, this = %p\n", loc.toChars(), type.toChars(), s, this);
            if (global.errors != olderrors)
                goto Lerr;
            if (s)
            {
                s = s.toAlias();
                if (global.errors != olderrors)
                    goto Lerr;
                aliassym = s;
                inuse = 0;
            }
            else
            {
                Type t = type.typeSemantic(loc, _scope);
                if (t.ty == Terror)
                    goto Lerr;
                if (global.errors != olderrors)
                    goto Lerr;
                //printf("t = %s\n", t.toChars());
                inuse = 0;
            }
        }
        if (inuse)
        {
            error("recursive alias declaration");

        Lerr:
            // Avoid breaking "recursive alias" state during errors gagged
            if (global.gag)
                return this;
            aliassym = new AliasDeclaration(loc, ident, Type.terror);
            type = Type.terror;
            return aliassym;
        }

        if (semanticRun >= PASS.semanticdone)
        {
            // semantic is already done.

            // Do not see aliassym !is null, because of lambda aliases.

            // Do not see type.deco !is null, even so "alias T = const int;` needs
            // semantic analysis to take the storage class `const` as type qualifier.
        }
        else
        {
            // stop AliasAssign tuple building
            if (aliassym)
            {
                if (auto td = aliassym.isTupleDeclaration())
                {
                    if (td.building)
                    {
                        td.building = false;
                        semanticRun = PASS.semanticdone;
                        return td;
                    }
                }
            }
            if (_import && _import._scope)
            {
                /* If this is an internal alias for selective/renamed import,
                 * load the module first.
                 */
                _import.dsymbolSemantic(null);
            }
            if (_scope)
            {
                aliasSemantic(this, _scope);
            }
        }

        inuse = 1;
        Dsymbol s = aliassym ? aliassym.toAlias() : this;
        inuse = 0;
        return s;
    }

    override Dsymbol toAlias2()
    {
        if (inuse)
        {
            error("recursive alias declaration");
            return this;
        }
        inuse = 1;
        Dsymbol s = aliassym ? aliassym.toAlias2() : this;
        inuse = 0;
        return s;
    }

    override bool isOverloadable() const
    {
        // assume overloadable until alias is resolved
        return semanticRun < PASS.semanticdone ||
            aliassym && aliassym.isOverloadable();
    }

    override inout(AliasDeclaration) isAliasDeclaration() inout
    {
        return this;
    }

    /** Returns: `true` if this instance was created to make a template parameter
    visible in the scope of a template body, `false` otherwise */
    extern (D) bool isAliasedTemplateParameter() const
    {
        return !!(storage_class & STC.templateparameter);
    }

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

/***********************************************************
 */
extern (C++) final class OverDeclaration : Declaration
{
    Dsymbol overnext;   // next in overload list
    Dsymbol aliassym;

    extern (D) this(Identifier ident, Dsymbol s)
    {
        super(ident);
        this.aliassym = s;
    }

    override const(char)* kind() const
    {
        return "overload alias"; // todo
    }

    override bool equals(const RootObject o) const
    {
        if (this == o)
            return true;

        auto s = isDsymbol(o);
        if (!s)
            return false;

        if (auto od2 = s.isOverDeclaration())
            return this.aliassym.equals(od2.aliassym);
        return this.aliassym == s;
    }

    override bool overloadInsert(Dsymbol s)
    {
        //printf("OverDeclaration::overloadInsert('%s') aliassym = %p, overnext = %p\n", s.toChars(), aliassym, overnext);
        if (overnext)
            return overnext.overloadInsert(s);
        if (s == this)
            return true;
        overnext = s;
        return true;
    }

    override bool isOverloadable() const
    {
        return true;
    }

    Dsymbol isUnique()
    {
        Dsymbol result = null;
        overloadApply(aliassym, (Dsymbol s)
        {
            if (result)
            {
                result = null;
                return 1; // ambiguous, done
            }
            else
            {
                result = s;
                return 0;
            }
        });
        return result;
    }

    override inout(OverDeclaration) isOverDeclaration() inout
    {
        return this;
    }

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

/***********************************************************
 */
extern (C++) class VarDeclaration : Declaration
{
    Initializer _init;
    FuncDeclarations nestedrefs;    // referenced by these lexically nested functions
    TupleDeclaration aliasTuple;    // when `this` is really a tuple of declarations
    VarDeclaration lastVar;         // Linked list of variables for goto-skips-init detection
    Expression edtor;               // if !=null, does the destruction of the variable
    IntRange* range;                // if !=null, the variable is known to be within the range
    VarDeclarations* maybes;        // maybeScope variables that are assigned to this maybeScope variable

    uint endlinnum;                 // line number of end of scope that this var lives in
    uint offset;
    uint sequenceNumber;            // order the variables are declared
    structalign_t alignment;

    // When interpreting, these point to the value (NULL if value not determinable)
    // The index of this variable on the CTFE stack, AdrOnStackNone if not allocated
    enum AdrOnStackNone = ~0u;
    uint ctfeAdrOnStack;

    // `bool` fields that are compacted into bit fields in a string mixin
    private extern (D) static struct BitFields
    {
        bool isargptr;          /// if parameter that _argptr points to
        bool ctorinit;          /// it has been initialized in a ctor
        bool iscatchvar;        /// this is the exception object variable in catch() clause
        bool isowner;           /// this is an Owner, despite it being `scope`
        bool setInCtorOnly;     /// field can only be set in a constructor, as it is const or immutable

        /// It is a class that was allocated on the stack
        ///
        /// This means the var is not rebindable once assigned,
        /// and the destructor gets run when it goes out of scope
        bool onstack;

        bool overlapped;        /// if it is a field and has overlapping
        bool overlapUnsafe;     /// if it is an overlapping field and the overlaps are unsafe
        bool maybeScope;        /// allow inferring 'scope' for this variable
        bool doNotInferReturn;  /// do not infer 'return' for this variable

        bool isArgDtorVar;      /// temporary created to handle scope destruction of a function argument
        bool isCmacro;          /// it is a C macro turned into a C declaration
        bool dllImport;         /// __declspec(dllimport)
        bool dllExport;         /// __declspec(dllexport)
        version (MARS)
        {
            bool inClosure;         /// is inserted into a GC allocated closure
            bool inAlignSection;    /// is inserted into an aligned section on stack
        }
    }

    import dmd.common.bitfields : generateBitFields;
    mixin(generateBitFields!(BitFields, ushort));

    byte canassign;                 // it can be assigned to
    ubyte isdataseg;                // private data for isDataseg 0 unset, 1 true, 2 false

    final extern (D) this(const ref Loc loc, Type type, Identifier ident, Initializer _init, StorageClass storage_class = STC.undefined_)
    in
    {
        assert(ident);
    }
    do
    {
        //printf("VarDeclaration('%s')\n", ident.toChars());
        super(loc, ident);
        debug
        {
            if (!type && !_init)
            {
                //printf("VarDeclaration('%s')\n", ident.toChars());
                //*(char*)0=0;
            }
        }

        assert(type || _init);
        this.type = type;
        this._init = _init;
        ctfeAdrOnStack = AdrOnStackNone;
        this.storage_class = storage_class;
    }

    static VarDeclaration create(const ref Loc loc, Type type, Identifier ident, Initializer _init, StorageClass storage_class = STC.undefined_)
    {
        return new VarDeclaration(loc, type, ident, _init, storage_class);
    }

    override VarDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("VarDeclaration::syntaxCopy(%s)\n", toChars());
        assert(!s);
        auto v = new VarDeclaration(loc, type ? type.syntaxCopy() : null, ident, _init ? _init.syntaxCopy() : null, storage_class);
        v.comment = comment;
        return v;
    }

    override void setFieldOffset(AggregateDeclaration ad, ref FieldState fieldState, bool isunion)
    {
        //printf("VarDeclaration::setFieldOffset(ad = %s) %s\n", ad.toChars(), toChars());

        if (aliasTuple)
        {
            // If this variable was really a tuple, set the offsets for the tuple fields
            aliasTuple.foreachVar((s) { s.setFieldOffset(ad, fieldState, isunion); });
            return;
        }

        if (!isField())
            return;
        assert(!(storage_class & (STC.static_ | STC.extern_ | STC.parameter)));

        //printf("+VarDeclaration::setFieldOffset(ad = %s) %s\n", ad.toChars(), toChars());

        /* Fields that are tuples appear both as part of TupleDeclarations and
         * as members. That means ignore them if they are already a field.
         */
        if (offset)
        {
            // already a field
            fieldState.offset = ad.structsize; // https://issues.dlang.org/show_bug.cgi?id=13613
            return;
        }
        for (size_t i = 0; i < ad.fields.length; i++)
        {
            if (ad.fields[i] == this)
            {
                // already a field
                fieldState.offset = ad.structsize; // https://issues.dlang.org/show_bug.cgi?id=13613
                return;
            }
        }

        // Check for forward referenced types which will fail the size() call
        Type t = type.toBasetype();
        if (storage_class & STC.ref_)
        {
            // References are the size of a pointer
            t = Type.tvoidptr;
        }
        Type tv = t.baseElemOf();
        if (tv.ty == Tstruct)
        {
            auto ts = cast(TypeStruct)tv;
            assert(ts.sym != ad);   // already checked in ad.determineFields()
            if (!ts.sym.determineSize(loc))
            {
                type = Type.terror;
                errors = true;
                return;
            }
        }

        // List in ad.fields. Even if the type is error, it's necessary to avoid
        // pointless error diagnostic "more initializers than fields" on struct literal.
        ad.fields.push(this);

        if (t.ty == Terror)
            return;

        /* If coming after a bit field in progress,
         * advance past the field
         */
        fieldState.inFlight = false;

        const sz = t.size(loc);
        assert(sz != SIZE_INVALID && sz < uint.max);
        uint memsize = cast(uint)sz;                // size of member
        uint memalignsize = target.fieldalign(t);   // size of member for alignment purposes
        offset = AggregateDeclaration.placeField(
            &fieldState.offset,
            memsize, memalignsize, alignment,
            &ad.structsize, &ad.alignsize,
            isunion);

        //printf("\t%s: memalignsize = %d\n", toChars(), memalignsize);
        //printf(" addField '%s' to '%s' at offset %d, size = %d\n", toChars(), ad.toChars(), offset, memsize);
    }

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

    override final inout(AggregateDeclaration) isThis() inout
    {
        if (!(storage_class & (STC.static_ | STC.extern_ | STC.manifest | STC.templateparameter | STC.gshared | STC.ctfe)))
        {
            /* The casting is necessary because `s = s.parent` is otherwise rejected
             */
            for (auto s = cast(Dsymbol)this; s; s = s.parent)
            {
                auto ad = (cast(inout)s).isMember();
                if (ad)
                    return ad;
                if (!s.parent || !s.parent.isTemplateMixin())
                    break;
            }
        }
        return null;
    }

    override final bool needThis()
    {
        //printf("VarDeclaration::needThis(%s, x%x)\n", toChars(), storage_class);
        return isField();
    }

    override final bool isExport() const
    {
        return visibility.kind == Visibility.Kind.export_ || dllExport;
    }

    override final bool isImportedSymbol() const
    {
        /* If global variable has `export` and `extern` then it is imported
         *   export int sym1;            // definition:  exported
         *   export extern int sym2;     // declaration: imported
         *   export extern int sym3 = 0; // error, extern cannot have initializer
         */
        bool result =
            dllImport ||
            visibility.kind == Visibility.Kind.export_ &&
            storage_class & STC.extern_ &&
            (storage_class & STC.static_ || parent.isModule());
        //printf("isImportedSymbol() %s %d\n", toChars(), result);
        return result;
    }

    final bool isCtorinit() const pure nothrow @nogc @safe
    {
        return setInCtorOnly;
    }

    /*******************************
     * Does symbol go into data segment?
     * Includes extern variables.
     */
    override final bool isDataseg()
    {
        version (none)
        {
            printf("VarDeclaration::isDataseg(%p, '%s')\n", this, toChars());
            printf("%llx, isModule: %p, isTemplateInstance: %p, isNspace: %p\n",
                   storage_class & (STC.static_ | STC.const_), parent.isModule(), parent.isTemplateInstance(), parent.isNspace());
            printf("parent = '%s'\n", parent.toChars());
        }

        if (isdataseg == 0) // the value is not cached
        {
            isdataseg = 2; // The Variables does not go into the datasegment

            if (!canTakeAddressOf())
            {
                return false;
            }

            Dsymbol parent = toParent();
            if (!parent && !(storage_class & STC.static_))
            {
                error("forward referenced");
                type = Type.terror;
            }
            else if (storage_class & (STC.static_ | STC.extern_ | STC.gshared) ||
                parent.isModule() || parent.isTemplateInstance() || parent.isNspace())
            {
                assert(!isParameter() && !isResult());
                isdataseg = 1; // It is in the DataSegment
            }
        }

        return (isdataseg == 1);
    }
    /************************************
     * Does symbol go into thread local storage?
     */
    override final bool isThreadlocal()
    {
        //printf("VarDeclaration::isThreadlocal(%p, '%s')\n", this, toChars());
        /* Data defaults to being thread-local. It is not thread-local
         * if it is immutable, const or shared.
         */
        bool i = isDataseg() && !(storage_class & (STC.immutable_ | STC.const_ | STC.shared_ | STC.gshared));
        //printf("\treturn %d\n", i);
        return i;
    }

    /********************************************
     * Can variable be read and written by CTFE?
     */
    final bool isCTFE()
    {
        return (storage_class & STC.ctfe) != 0; // || !isDataseg();
    }

    final bool isOverlappedWith(VarDeclaration v)
    {
        const vsz = v.type.size();
        const tsz = type.size();
        assert(vsz != SIZE_INVALID && tsz != SIZE_INVALID);

        // Overlap is checked by comparing bit offsets
        auto bitoffset  =   offset * 8;
        auto vbitoffset = v.offset * 8;

        // Bitsize of types are overridden by any bit-field widths.
        ulong tbitsize = void;
        if (auto bf = isBitFieldDeclaration())
        {
            bitoffset += bf.bitOffset;
            tbitsize = bf.fieldWidth;
        }
        else
            tbitsize = tsz * 8;

        ulong vbitsize = void;
        if (auto vbf = v.isBitFieldDeclaration())
        {
            vbitoffset += vbf.bitOffset;
            vbitsize = vbf.fieldWidth;
        }
        else
            vbitsize = vsz * 8;

        return   bitoffset < vbitoffset + vbitsize &&
                vbitoffset <  bitoffset + tbitsize;
    }

    override final bool hasPointers()
    {
        //printf("VarDeclaration::hasPointers() %s, ty = %d\n", toChars(), type.ty);
        return (!isDataseg() && type.hasPointers());
    }

    /*************************************
     * Return true if we can take the address of this variable.
     */
    final bool canTakeAddressOf()
    {
        return !(storage_class & STC.manifest);
    }

    /******************************************
     * Return true if variable needs to call the destructor.
     */
    final bool needsScopeDtor()
    {
        //printf("VarDeclaration::needsScopeDtor() %s\n", toChars());
        return edtor && !(storage_class & STC.nodtor);
    }

    /******************************************
     * If a variable has a scope destructor call, return call for it.
     * Otherwise, return NULL.
     */
    extern (D) final Expression callScopeDtor(Scope* sc)
    {
        //printf("VarDeclaration::callScopeDtor() %s\n", toChars());

        // Destruction of STC.field's is handled by buildDtor()
        if (storage_class & (STC.nodtor | STC.ref_ | STC.out_ | STC.field))
        {
            return null;
        }

        if (iscatchvar)
            return null;    // destructor is built by `void semantic(Catch c, Scope* sc)`, not here

        Expression e = null;
        // Destructors for structs and arrays of structs
        Type tv = type.baseElemOf();
        if (tv.ty == Tstruct)
        {
            StructDeclaration sd = (cast(TypeStruct)tv).sym;
            if (!sd.dtor || sd.errors)
                return null;

            const sz = type.size();
            assert(sz != SIZE_INVALID);
            if (!sz)
                return null;

            if (type.toBasetype().ty == Tstruct)
            {
                // v.__xdtor()
                e = new VarExp(loc, this);

                /* This is a hack so we can call destructors on const/immutable objects.
                 * Need to add things like "const ~this()" and "immutable ~this()" to
                 * fix properly.
                 */
                e.type = e.type.mutableOf();

                // Enable calling destructors on shared objects.
                // The destructor is always a single, non-overloaded function,
                // and must serve both shared and non-shared objects.
                e.type = e.type.unSharedOf;

                e = new DotVarExp(loc, e, sd.dtor, false);
                e = new CallExp(loc, e);
            }
            else
            {
                // __ArrayDtor(v[0 .. n])
                e = new VarExp(loc, this);

                const sdsz = sd.type.size();
                assert(sdsz != SIZE_INVALID && sdsz != 0);
                const n = sz / sdsz;
                SliceExp se = new SliceExp(loc, e, new IntegerExp(loc, 0, Type.tsize_t),
                    new IntegerExp(loc, n, Type.tsize_t));

                // Prevent redundant bounds check
                se.upperIsInBounds = true;
                se.lowerIsLessThanUpper = true;

                // This is a hack so we can call destructors on const/immutable objects.
                se.type = sd.type.arrayOf();

                e = new CallExp(loc, new IdentifierExp(loc, Id.__ArrayDtor), se);
            }
            return e;
        }
        // Destructors for classes
        if (storage_class & (STC.auto_ | STC.scope_) && !(storage_class & STC.parameter))
        {
            for (ClassDeclaration cd = type.isClassHandle(); cd; cd = cd.baseClass)
            {
                /* We can do better if there's a way with onstack
                 * classes to determine if there's no way the monitor
                 * could be set.
                 */
                //if (cd.isInterfaceDeclaration())
                //    error("interface `%s` cannot be scope", cd.toChars());

                if (onstack) // if any destructors
                {
                    // delete'ing C++ classes crashes (and delete is deprecated anyway)
                    if (cd.classKind == ClassKind.cpp)
                    {
                        // Don't call non-existant dtor
                        if (!cd.dtor)
                            break;

                        e = new VarExp(loc, this);
                        e.type = e.type.mutableOf().unSharedOf(); // Hack for mutable ctor on immutable instances
                        e = new DotVarExp(loc, e, cd.dtor, false);
                        e = new CallExp(loc, e);
                        break;
                    }

                    // delete this;
                    Expression ec;
                    ec = new VarExp(loc, this);
                    e = new DeleteExp(loc, ec, true);
                    e.type = Type.tvoid;
                    break;
                }
            }
        }
        return e;
    }

    /*******************************************
     * If variable has a constant expression initializer, get it.
     * Otherwise, return null.
     */
    extern (D) final Expression getConstInitializer(bool needFullType = true)
    {
        assert(type && _init);

        // Ungag errors when not speculative
        uint oldgag = global.gag;
        if (global.gag)
        {
            Dsymbol sym = isMember();
            if (sym && !sym.isSpeculative())
                global.gag = 0;
        }

        if (_scope)
        {
            inuse++;
            _init = _init.initializerSemantic(_scope, type, INITinterpret);
            _scope = null;
            inuse--;
        }

        Expression e = _init.initializerToExpression(needFullType ? type : null);
        global.gag = oldgag;
        return e;
    }

    /*******************************************
     * Helper function for the expansion of manifest constant.
     */
    extern (D) final Expression expandInitializer(Loc loc)
    {
        assert((storage_class & STC.manifest) && _init);

        auto e = getConstInitializer();
        if (!e)
        {
            .error(loc, "cannot make expression out of initializer for `%s`", toChars());
            return ErrorExp.get();
        }

        e = e.copy();
        e.loc = loc;    // for better error message
        return e;
    }

    override final void checkCtorConstInit()
    {
        version (none)
        {
            /* doesn't work if more than one static ctor */
            if (ctorinit == 0 && isCtorinit() && !isField())
                error("missing initializer in static constructor for const variable");
        }
    }

    /************************************
     * Check to see if this variable is actually in an enclosing function
     * rather than the current one.
     * Update nestedrefs[], closureVars[] and outerVars[].
     * Returns: true if error occurs.
     */
    extern (D) final bool checkNestedReference(Scope* sc, Loc loc)
    {
        //printf("VarDeclaration::checkNestedReference() %s\n", toChars());
        if (sc.intypeof == 1 || (sc.flags & SCOPE.ctfe))
            return false;
        if (!parent || parent == sc.parent)
            return false;
        if (isDataseg() || (storage_class & STC.manifest))
            return false;

        // The current function
        FuncDeclaration fdthis = sc.parent.isFuncDeclaration();
        if (!fdthis)
            return false; // out of function scope

        Dsymbol p = toParent2();

        // Function literals from fdthis to p must be delegates
        ensureStaticLinkTo(fdthis, p);

        // The function that this variable is in
        FuncDeclaration fdv = p.isFuncDeclaration();
        if (!fdv || fdv == fdthis)
            return false;

        // Add fdthis to nestedrefs[] if not already there
        if (!nestedrefs.contains(fdthis))
            nestedrefs.push(fdthis);

        //printf("\tfdv = %s\n", fdv.toChars());
        //printf("\tfdthis = %s\n", fdthis.toChars());
        if (loc.isValid())
        {
            if (fdthis.getLevelAndCheck(loc, sc, fdv, this) == fdthis.LevelError)
                return true;
        }

        // Add this VarDeclaration to fdv.closureVars[] if not already there
        if (!sc.intypeof && !(sc.flags & SCOPE.compile) &&
            // https://issues.dlang.org/show_bug.cgi?id=17605
            (fdv.skipCodegen || !fdthis.skipCodegen))
        {
            if (!fdv.closureVars.contains(this))
                fdv.closureVars.push(this);
        }

        if (!fdthis.outerVars.contains(this))
            fdthis.outerVars.push(this);

        //printf("fdthis is %s\n", fdthis.toChars());
        //printf("var %s in function %s is nested ref\n", toChars(), fdv.toChars());
        // __dollar creates problems because it isn't a real variable
        // https://issues.dlang.org/show_bug.cgi?id=3326
        if (ident == Id.dollar)
        {
            .error(loc, "cannnot use `$` inside a function literal");
            return true;
        }
        if (ident == Id.withSym) // https://issues.dlang.org/show_bug.cgi?id=1759
        {
            ExpInitializer ez = _init.isExpInitializer();
            assert(ez);
            Expression e = ez.exp;
            if (e.op == EXP.construct || e.op == EXP.blit)
                e = (cast(AssignExp)e).e2;
            return lambdaCheckForNestedRef(e, sc);
        }

        return false;
    }

    override final Dsymbol toAlias()
    {
        //printf("VarDeclaration::toAlias('%s', this = %p, aliassym = %p)\n", toChars(), this, aliassym);
        if ((!type || !type.deco) && _scope)
            dsymbolSemantic(this, _scope);

        assert(this != aliasTuple);
        Dsymbol s = aliasTuple ? aliasTuple.toAlias() : this;
        return s;
    }

    // Eliminate need for dynamic_cast
    override final inout(VarDeclaration) isVarDeclaration() inout
    {
        return this;
    }

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

/*******************************************************
 * C11 6.7.2.1-4 bit fields
 */
extern (C++) class BitFieldDeclaration : VarDeclaration
{
    Expression width;

    uint fieldWidth;
    uint bitOffset;

    final extern (D) this(const ref Loc loc, Type type, Identifier ident, Expression width)
    {
        super(loc, type, ident, null);

        this.width = width;
        this.storage_class |= STC.field;
    }

    override BitFieldDeclaration syntaxCopy(Dsymbol s)
    {
        //printf("BitFieldDeclaration::syntaxCopy(%s)\n", toChars());
        assert(!s);
        auto bf = new BitFieldDeclaration(loc, type ? type.syntaxCopy() : null, ident, width.syntaxCopy());
        bf.comment = comment;
        return bf;
    }

    override final inout(BitFieldDeclaration) isBitFieldDeclaration() inout
    {
        return this;
    }

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

    /***********************************
     * Retrieve the .min or .max values.
     * Only valid after semantic analysis.
     * Params:
     *  id = Id.min or Id.max
     * Returns:
     *  the min or max value
     */
    final ulong getMinMax(Identifier id)
    {
        const width = fieldWidth;
        const uns = type.isunsigned();
        const min = id == Id.min;
        ulong v;
        assert(width != 0);  // should have been rejected in semantic pass
        if (width == ulong.sizeof * 8)
            v = uns ? (min ? ulong.min : ulong.max)
                    : (min ?  long.min :  long.max);
        else
            v = uns ? (min ? 0
                           : (1L << width) - 1)
                    : (min ? -(1L << (width - 1))
                           :  (1L << (width - 1)) - 1);
        return v;
    }

    override final void setFieldOffset(AggregateDeclaration ad, ref FieldState fieldState, bool isunion)
    {
        enum log = false;
        static if (log)
        {
            printf("BitFieldDeclaration::setFieldOffset(ad: %s, field: %s)\n", ad.toChars(), toChars());
            void print(const ref FieldState fieldState)
            {
                printf("FieldState.offset      = %d bytes\n",   fieldState.offset);
                printf("          .fieldOffset = %d bytes\n",   fieldState.fieldOffset);
                printf("          .bitOffset   = %d bits\n",    fieldState.bitOffset);
                printf("          .fieldSize   = %d bytes\n",   fieldState.fieldSize);
                printf("          .inFlight    = %d\n",         fieldState.inFlight);
                printf("          fieldWidth   = %d bits\n",    fieldWidth);
            }
            print(fieldState);
        }

        Type t = type.toBasetype();
        const bool anon = isAnonymous();

        // List in ad.fields. Even if the type is error, it's necessary to avoid
        // pointless error diagnostic "more initializers than fields" on struct literal.
        if (!anon)
            ad.fields.push(this);

        if (t.ty == Terror)
            return;

        const sz = t.size(loc);
        assert(sz != SIZE_INVALID && sz < uint.max);
        uint memsize = cast(uint)sz;                // size of member
        uint memalignsize = target.fieldalign(t);   // size of member for alignment purposes
        if (log) printf("          memsize: %u memalignsize: %u\n", memsize, memalignsize);

        if (fieldWidth == 0 && !anon)
            error(loc, "named bit fields cannot have 0 width");
        if (fieldWidth > memsize * 8)
            error(loc, "bit field width %d is larger than type", fieldWidth);

        const style = target.c.bitFieldStyle;

        void startNewField()
        {
            if (log) printf("startNewField()\n");
            uint alignsize;
            if (style == TargetC.BitFieldStyle.Gcc_Clang)
            {
                if (fieldWidth > 32)
                    alignsize = memalignsize;
                else if (fieldWidth > 16)
                    alignsize = 4;
                else if (fieldWidth > 8)
                    alignsize = 2;
                else
                    alignsize = 1;
            }
            else
                alignsize = memsize; // not memalignsize

            uint dummy;
            offset = AggregateDeclaration.placeField(
                &fieldState.offset,
                memsize, alignsize, alignment,
                &ad.structsize,
                (anon && style == TargetC.BitFieldStyle.Gcc_Clang) ? &dummy : &ad.alignsize,
                isunion);

            fieldState.inFlight = true;
            fieldState.fieldOffset = offset;
            fieldState.bitOffset = 0;
            fieldState.fieldSize = memsize;
        }

        if (style == TargetC.BitFieldStyle.Gcc_Clang)
        {
            if (fieldWidth == 0)
            {
                if (!isunion)
                {
                    // Use type of zero width field to align to next field
                    fieldState.offset = (fieldState.offset + memalignsize - 1) & ~(memalignsize - 1);
                    ad.structsize = fieldState.offset;
                }

                fieldState.inFlight = false;
                return;
            }

            if (ad.alignsize == 0)
                ad.alignsize = 1;
            if (!anon &&
                  ad.alignsize < memalignsize)
                ad.alignsize = memalignsize;
        }
        else if (style == TargetC.BitFieldStyle.MS)
        {
            if (ad.alignsize == 0)
                ad.alignsize = 1;
            if (fieldWidth == 0)
            {
                if (fieldState.inFlight && !isunion)
                {
                    // documentation says align to next int
                    //const alsz = cast(uint)Type.tint32.size();
                    const alsz = memsize; // but it really does this
                    fieldState.offset = (fieldState.offset + alsz - 1) & ~(alsz - 1);
                    ad.structsize = fieldState.offset;
                }

                fieldState.inFlight = false;
                return;
            }
        }
        else if (style == TargetC.BitFieldStyle.DM)
        {
            if (anon && fieldWidth && (!fieldState.inFlight || fieldState.bitOffset == 0))
                return;  // this probably should be a bug in DMC
            if (ad.alignsize == 0)
                ad.alignsize = 1;
            if (fieldWidth == 0)
            {
                if (fieldState.inFlight && !isunion)
                {
                    const alsz = memsize;
                    fieldState.offset = (fieldState.offset + alsz - 1) & ~(alsz - 1);
                    ad.structsize = fieldState.offset;
                }

                fieldState.inFlight = false;
                return;
            }
        }

        if (!fieldState.inFlight)
        {
            //printf("not in flight\n");
            startNewField();
        }
        else if (style == TargetC.BitFieldStyle.Gcc_Clang)
        {
            // If the bit-field spans more units of alignment than its type,
            // start a new field at the next alignment boundary.
            if (fieldState.bitOffset == fieldState.fieldSize * 8 &&
                fieldState.bitOffset + fieldWidth > memalignsize * 8)
            {
                if (log) printf("more units of alignment than its type\n");
                startNewField();        // the bit field is full
            }
            else
            {
                // if alignment boundary is crossed
                uint start = fieldState.fieldOffset * 8 + fieldState.bitOffset;
                uint end   = start + fieldWidth;
                //printf("%s start: %d end: %d memalignsize: %d\n", ad.toChars(), start, end, memalignsize);
                if (start / (memalignsize * 8) != (end - 1) / (memalignsize * 8))
                {
                    if (log) printf("alignment is crossed\n");
                    startNewField();
                }
            }
        }
        else if (style == TargetC.BitFieldStyle.DM ||
                 style == TargetC.BitFieldStyle.MS)
        {
            if (memsize != fieldState.fieldSize ||
                fieldState.bitOffset + fieldWidth > fieldState.fieldSize * 8)
            {
                //printf("new field\n");
                startNewField();
            }
        }
        else
            assert(0);

        offset = fieldState.fieldOffset;
        bitOffset = fieldState.bitOffset;

        const pastField = bitOffset + fieldWidth;
        if (style == TargetC.BitFieldStyle.Gcc_Clang)
        {
            auto size = (pastField + 7) / 8;
            fieldState.fieldSize = size;
            //printf(" offset: %d, size: %d\n", offset, size);
            ad.structsize = offset + size;
        }
        else
            fieldState.fieldSize = memsize;
        //printf("at end: ad.structsize = %d\n", cast(int)ad.structsize);
        //print(fieldState);

        if (!isunion)
        {
            fieldState.offset = offset + fieldState.fieldSize;
            fieldState.bitOffset = pastField;
        }

        //printf("\t%s: offset = %d bitOffset = %d fieldWidth = %d memsize = %d\n", toChars(), offset, bitOffset, fieldWidth, memsize);
        //printf("\t%s: memalignsize = %d\n", toChars(), memalignsize);
        //printf(" addField '%s' to '%s' at offset %d, size = %d\n", toChars(), ad.toChars(), offset, memsize);
    }
}

/***********************************************************
 * This is a shell around a back end symbol
 */
extern (C++) final class SymbolDeclaration : Declaration
{
    AggregateDeclaration dsym;

    extern (D) this(const ref Loc loc, AggregateDeclaration dsym)
    {
        super(loc, dsym.ident);
        this.dsym = dsym;
        storage_class |= STC.const_;
    }

    // Eliminate need for dynamic_cast
    override inout(SymbolDeclaration) isSymbolDeclaration() inout
    {
        return this;
    }

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

/***********************************************************
 */
extern (C++) class TypeInfoDeclaration : VarDeclaration
{
    Type tinfo;

    final extern (D) this(Type tinfo)
    {
        super(Loc.initial, Type.dtypeinfo.type, tinfo.getTypeInfoIdent(), null);
        this.tinfo = tinfo;
        storage_class = STC.static_ | STC.gshared;
        visibility = Visibility(Visibility.Kind.public_);
        _linkage = LINK.c;
        alignment.set(target.ptrsize);
    }

    static TypeInfoDeclaration create(Type tinfo)
    {
        return new TypeInfoDeclaration(tinfo);
    }

    override final TypeInfoDeclaration syntaxCopy(Dsymbol s)
    {
        assert(0); // should never be produced by syntax
    }

    override final const(char)* toChars() const
    {
        //printf("TypeInfoDeclaration::toChars() tinfo = %s\n", tinfo.toChars());
        OutBuffer buf;
        buf.writestring("typeid(");
        buf.writestring(tinfo.toChars());
        buf.writeByte(')');
        return buf.extractChars();
    }

    override final inout(TypeInfoDeclaration) isTypeInfoDeclaration() inout @nogc nothrow pure @safe
    {
        return this;
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoStructDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfostruct)
        {
            ObjectNotFound(Id.TypeInfo_Struct);
        }
        type = Type.typeinfostruct.type;
    }

    static TypeInfoStructDeclaration create(Type tinfo)
    {
        return new TypeInfoStructDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoClassDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfoclass)
        {
            ObjectNotFound(Id.TypeInfo_Class);
        }
        type = Type.typeinfoclass.type;
    }

    static TypeInfoClassDeclaration create(Type tinfo)
    {
        return new TypeInfoClassDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoInterfaceDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfointerface)
        {
            ObjectNotFound(Id.TypeInfo_Interface);
        }
        type = Type.typeinfointerface.type;
    }

    static TypeInfoInterfaceDeclaration create(Type tinfo)
    {
        return new TypeInfoInterfaceDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoPointerDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfopointer)
        {
            ObjectNotFound(Id.TypeInfo_Pointer);
        }
        type = Type.typeinfopointer.type;
    }

    static TypeInfoPointerDeclaration create(Type tinfo)
    {
        return new TypeInfoPointerDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoArrayDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfoarray)
        {
            ObjectNotFound(Id.TypeInfo_Array);
        }
        type = Type.typeinfoarray.type;
    }

    static TypeInfoArrayDeclaration create(Type tinfo)
    {
        return new TypeInfoArrayDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoStaticArrayDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfostaticarray)
        {
            ObjectNotFound(Id.TypeInfo_StaticArray);
        }
        type = Type.typeinfostaticarray.type;
    }

    static TypeInfoStaticArrayDeclaration create(Type tinfo)
    {
        return new TypeInfoStaticArrayDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoAssociativeArrayDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfoassociativearray)
        {
            ObjectNotFound(Id.TypeInfo_AssociativeArray);
        }
        type = Type.typeinfoassociativearray.type;
    }

    static TypeInfoAssociativeArrayDeclaration create(Type tinfo)
    {
        return new TypeInfoAssociativeArrayDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoEnumDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfoenum)
        {
            ObjectNotFound(Id.TypeInfo_Enum);
        }
        type = Type.typeinfoenum.type;
    }

    static TypeInfoEnumDeclaration create(Type tinfo)
    {
        return new TypeInfoEnumDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoFunctionDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfofunction)
        {
            ObjectNotFound(Id.TypeInfo_Function);
        }
        type = Type.typeinfofunction.type;
    }

    static TypeInfoFunctionDeclaration create(Type tinfo)
    {
        return new TypeInfoFunctionDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoDelegateDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfodelegate)
        {
            ObjectNotFound(Id.TypeInfo_Delegate);
        }
        type = Type.typeinfodelegate.type;
    }

    static TypeInfoDelegateDeclaration create(Type tinfo)
    {
        return new TypeInfoDelegateDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoTupleDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfotypelist)
        {
            ObjectNotFound(Id.TypeInfo_Tuple);
        }
        type = Type.typeinfotypelist.type;
    }

    static TypeInfoTupleDeclaration create(Type tinfo)
    {
        return new TypeInfoTupleDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoConstDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfoconst)
        {
            ObjectNotFound(Id.TypeInfo_Const);
        }
        type = Type.typeinfoconst.type;
    }

    static TypeInfoConstDeclaration create(Type tinfo)
    {
        return new TypeInfoConstDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoInvariantDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfoinvariant)
        {
            ObjectNotFound(Id.TypeInfo_Invariant);
        }
        type = Type.typeinfoinvariant.type;
    }

    static TypeInfoInvariantDeclaration create(Type tinfo)
    {
        return new TypeInfoInvariantDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoSharedDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfoshared)
        {
            ObjectNotFound(Id.TypeInfo_Shared);
        }
        type = Type.typeinfoshared.type;
    }

    static TypeInfoSharedDeclaration create(Type tinfo)
    {
        return new TypeInfoSharedDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoWildDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfowild)
        {
            ObjectNotFound(Id.TypeInfo_Wild);
        }
        type = Type.typeinfowild.type;
    }

    static TypeInfoWildDeclaration create(Type tinfo)
    {
        return new TypeInfoWildDeclaration(tinfo);
    }

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

/***********************************************************
 */
extern (C++) final class TypeInfoVectorDeclaration : TypeInfoDeclaration
{
    extern (D) this(Type tinfo)
    {
        super(tinfo);
        if (!Type.typeinfovector)
        {
            ObjectNotFound(Id.TypeInfo_Vector);
        }
        type = Type.typeinfovector.type;
    }

    static TypeInfoVectorDeclaration create(Type tinfo)
    {
        return new TypeInfoVectorDeclaration(tinfo);
    }

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

/***********************************************************
 * For the "this" parameter to member functions
 */
extern (C++) final class ThisDeclaration : VarDeclaration
{
    extern (D) this(const ref Loc loc, Type t)
    {
        super(loc, t, Id.This, null);
        storage_class |= STC.nodtor;
    }

    override ThisDeclaration syntaxCopy(Dsymbol s)
    {
        assert(0); // should never be produced by syntax
    }

    override inout(ThisDeclaration) isThisDeclaration() inout
    {
        return this;
    }

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