/**
 * Compiler implementation of the
 * $(LINK2 http://www.dlang.org, D programming language).
 *
 * Copyright:   Copyright (c) 1999-2017 by Digital Mars, All Rights Reserved
 * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/ddmd/declaration.d, _declaration.d)
 */

module ddmd.declaration;

// Online documentation: https://dlang.org/phobos/ddmd_declaration.html

import core.stdc.stdio;
import ddmd.aggregate;
import ddmd.arraytypes;
import ddmd.dcast;
import ddmd.dclass;
import ddmd.delegatize;
import ddmd.dscope;
import ddmd.dstruct;
import ddmd.dsymbol;
import ddmd.dtemplate;
import ddmd.errors;
import ddmd.expression;
import ddmd.func;
import ddmd.globals;
import ddmd.hdrgen;
import ddmd.id;
import ddmd.identifier;
import ddmd.init;
import ddmd.initsem;
import ddmd.intrange;
import ddmd.mtype;
import ddmd.root.outbuffer;
import ddmd.root.rootobject;
import ddmd.sideeffect;
import ddmd.target;
import ddmd.tokens;
import ddmd.visitor;

/************************************
 * Check to see the aggregate type is nested and its context pointer is
 * accessible from the current scope.
 * Returns true if error occurs.
 */
extern (C++) bool checkFrameAccess(Loc loc, Scope* sc, AggregateDeclaration ad, size_t iStart = 0)
{
    Dsymbol sparent = 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());
        if (!ensureStaticLinkTo(s, sparent))
        {
            error(loc, "cannot access frame pointer of %s", ad.toPrettyChars());
            return true;
        }
    }

    bool result = false;
    for (size_t i = iStart; i < ad.fields.dim; 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;
}

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

enum STCundefined           = 0L;
enum STCstatic              = (1L << 0);
enum STCextern              = (1L << 1);
enum STCconst               = (1L << 2);
enum STCfinal               = (1L << 3);
enum STCabstract            = (1L << 4);
enum STCparameter           = (1L << 5);
enum STCfield               = (1L << 6);
enum STCoverride            = (1L << 7);
enum STCauto                = (1L << 8);
enum STCsynchronized        = (1L << 9);
enum STCdeprecated          = (1L << 10);
enum STCin                  = (1L << 11);   // in parameter
enum STCout                 = (1L << 12);   // out parameter
enum STClazy                = (1L << 13);   // lazy parameter
enum STCforeach             = (1L << 14);   // variable for foreach loop
//                            (1L << 15)
enum STCvariadic            = (1L << 16);   // the 'variadic' parameter in: T foo(T a, U b, V variadic...)
enum STCctorinit            = (1L << 17);   // can only be set inside constructor
enum STCtemplateparameter   = (1L << 18);   // template parameter
enum STCscope               = (1L << 19);
enum STCimmutable           = (1L << 20);
enum STCref                 = (1L << 21);
enum STCinit                = (1L << 22);   // has explicit initializer
enum STCmanifest            = (1L << 23);   // manifest constant
enum STCnodtor              = (1L << 24);   // don't run destructor
enum STCnothrow             = (1L << 25);   // never throws exceptions
enum STCpure                = (1L << 26);   // pure function
enum STCtls                 = (1L << 27);   // thread local
enum STCalias               = (1L << 28);   // alias parameter
enum STCshared              = (1L << 29);   // accessible from multiple threads
enum STCgshared             = (1L << 30);   // accessible from multiple threads, but not typed as "shared"
enum STCwild                = (1L << 31);   // for "wild" type constructor
enum STCproperty            = (1L << 32);
enum STCsafe                = (1L << 33);
enum STCtrusted             = (1L << 34);
enum STCsystem              = (1L << 35);
enum STCctfe                = (1L << 36);   // can be used in CTFE, even if it is static
enum STCdisable             = (1L << 37);   // for functions that are not callable
enum STCresult              = (1L << 38);   // for result variables passed to out contracts
enum STCnodefaultctor       = (1L << 39);   // must be set inside constructor
enum STCtemp                = (1L << 40);   // temporary variable
enum STCrvalue              = (1L << 41);   // force rvalue for variables
enum STCnogc                = (1L << 42);   // @nogc
enum STCvolatile            = (1L << 43);   // destined for volatile in the back end
enum STCreturn              = (1L << 44);   // 'return ref' or 'return scope' for function parameters
enum STCautoref             = (1L << 45);   // Mark for the already deduced 'auto ref' parameter
enum STCinference           = (1L << 46);   // do attribute inference
enum STCexptemp             = (1L << 47);   // temporary variable that has lifetime restricted to an expression
enum STCmaybescope          = (1L << 48);   // parameter might be 'scope'
enum STCscopeinferred       = (1L << 49);   // 'scope' has been inferred and should not be part of mangling
enum STCfuture              = (1L << 50);   // introducing new base class function
enum STClocal               = (1L << 51);   // do not forward (see ddmd.dsymbol.ForwardingScopeDsymbol).

enum STC_TYPECTOR = (STCconst | STCimmutable | STCshared | STCwild);
enum STC_FUNCATTR = (STCref | STCnothrow | STCnogc | STCpure | STCproperty | STCsafe | STCtrusted | STCsystem);

extern (C++) __gshared const(StorageClass) STCStorageClass =
    (STCauto | STCscope | STCstatic | STCextern | STCconst | STCfinal | STCabstract | STCsynchronized |
     STCdeprecated | STCfuture | STCoverride | STClazy | STCalias | STCout | STCin | STCmanifest |
     STCimmutable | STCshared | STCwild | STCnothrow | STCnogc | STCpure | STCref | STCtls | STCgshared |
     STCproperty | STCsafe | STCtrusted | STCsystem | STCdisable | STClocal);

struct Match
{
    int count;              // number of matches found
    MATCH last;             // match level of lastf
    FuncDeclaration lastf;  // last matching function we found
    FuncDeclaration nextf;  // current matching function
    FuncDeclaration anyf;   // pick a func, any func, to use for error recovery
}

/***********************************************************
 */
extern (C++) abstract class Declaration : Dsymbol
{
    Type type;
    Type originalType;  // before semantic analysis
    StorageClass storage_class;
    Prot protection;
    LINK linkage;
    int inuse;          // used to detect cycles

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

    final extern (D) this(Identifier id)
    {
        super(id);
        storage_class = STCundefined;
        protection = Prot(PROTundefined);
        linkage = LINKdefault;
    }

    override void semantic(Scope* sc)
    {
    }

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

    override final d_uns64 size(Loc loc)
    {
        assert(type);
        return type.size();
    }

    /*************************************
     * Check to see if declaration can be modified in this context (sc).
     * Issue error if not.
     */
    final int checkModify(Loc loc, Scope* sc, Type t, Expression e1, int flag)
    {
        VarDeclaration v = isVarDeclaration();
        if (v && v.canassign)
            return 2;

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

        if (v && (isCtorinit() || isField()))
        {
            // It's only modifiable if inside the right constructor
            if ((storage_class & (STCforeach | STCref)) == (STCforeach | STCref))
                return 2;
            return modifyFieldVar(loc, sc, v, e1) ? 2 : 1;
        }
        return 1;
    }

    override final Dsymbol search(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()
    {
        return (storage_class & STCstatic) != 0;
    }

    bool isDelete()
    {
        return false;
    }

    bool isDataseg()
    {
        return false;
    }

    bool isThreadlocal()
    {
        return false;
    }

    bool isCodeseg()
    {
        return false;
    }

    final bool isCtorinit()
    {
        return (storage_class & STCctorinit) != 0;
    }

    final bool isFinal()
    {
        return (storage_class & STCfinal) != 0;
    }

    final bool isAbstract()
    {
        return (storage_class & STCabstract) != 0;
    }

    final bool isConst()
    {
        return (storage_class & STCconst) != 0;
    }

    final bool isImmutable()
    {
        return (storage_class & STCimmutable) != 0;
    }

    final bool isWild()
    {
        return (storage_class & STCwild) != 0;
    }

    final bool isAuto()
    {
        return (storage_class & STCauto) != 0;
    }

    final bool isScope()
    {
        return (storage_class & STCscope) != 0;
    }

    final bool isSynchronized()
    {
        return (storage_class & STCsynchronized) != 0;
    }

    final bool isParameter()
    {
        return (storage_class & STCparameter) != 0;
    }

    override final bool isDeprecated()
    {
        return (storage_class & STCdeprecated) != 0;
    }

    final bool isOverride()
    {
        return (storage_class & STCoverride) != 0;
    }

    final bool isResult()
    {
        return (storage_class & STCresult) != 0;
    }

    final bool isField()
    {
        return (storage_class & STCfield) != 0;
    }

    final bool isIn()
    {
        return (storage_class & STCin) != 0;
    }

    final bool isOut()
    {
        return (storage_class & STCout) != 0;
    }

    final bool isRef()
    {
        return (storage_class & STCref) != 0;
    }

    final bool isFuture()
    {
        return (storage_class & STCfuture) != 0;
    }

    override final Prot prot()
    {
        return protection;
    }

    override final inout(Declaration) isDeclaration() inout
    {
        return this;
    }

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

/***********************************************************
 */
extern (C++) final class TupleDeclaration : Declaration
{
    Objects* objects;
    bool isexp;             // true: expression tuple
    TypeTuple tupletype;    // !=null if this is a type tuple

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

    override Dsymbol 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)
            return null;
        if (!tupletype)
        {
            /* It's only a type tuple if all the Object's are types
             */
            for (size_t i = 0; i < objects.dim; 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();
            args.setDim(objects.dim);
            OutBuffer buf;
            int hasdeco = 1;
            for (size_t i = 0; i < types.dim; 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 = cast(const(char)*)buf.extractData();
                    auto id = Identifier.idPool(name, len);
                    auto arg = new Parameter(STCin, t, id, null);
                }
                else
                {
                    auto arg = new Parameter(0, t, null, null);
                }
                (*args)[i] = arg;
                if (!t.deco)
                    hasdeco = 0;
            }

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

    override Dsymbol toAlias2()
    {
        //printf("TupleDeclaration::toAlias2() '%s' objects = %s\n", toChars(), objects.toChars());
        for (size_t i = 0; i < objects.dim; 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());
        for (size_t i = 0; i < objects.dim; i++)
        {
            RootObject o = (*objects)[i];
            if (o.dyncast() == DYNCAST.expression)
            {
                Expression e = cast(Expression)o;
                if (e.op == TOKdsymbol)
                {
                    DsymbolExp ve = cast(DsymbolExp)e;
                    Declaration d = ve.s.isDeclaration();
                    if (d && d.needThis())
                    {
                        return true;
                    }
                }
            }
        }
        return false;
    }

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

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

/***********************************************************
 */
extern (C++) final class AliasDeclaration : Declaration
{
    Dsymbol aliassym;
    Dsymbol overnext;   // next in overload list
    Dsymbol _import;    // !=null if unresolved internal alias for selective import

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

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

    override Dsymbol 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.storage_class = storage_class;
        return sa;
    }

    override void semantic(Scope* sc)
    {
        if (semanticRun >= PASSsemanticdone)
            return;
        assert(semanticRun <= PASSsemantic);

        storage_class |= sc.stc & STCdeprecated;
        protection = sc.protection;
        userAttribDecl = sc.userAttribDecl;

        if (!sc.func && inNonRoot())
            return;

        aliasSemantic(sc);
    }

    final void aliasSemantic(Scope* sc)
    {
        //printf("AliasDeclaration::semantic() %s\n", toChars());
        if (aliassym)
        {
            auto fd = aliassym.isFuncLiteralDeclaration();
            auto td = aliassym.isTemplateDeclaration();
            if (fd || td && td.literal)
            {
                if (fd && fd.semanticRun >= PASSsemanticdone)
                    return;

                Expression e = new FuncExp(loc, aliassym);
                e = e.semantic(sc);
                if (e.op == TOKfunction)
                {
                    FuncExp fe = cast(FuncExp)e;
                    aliassym = fe.td ? cast(Dsymbol)fe.td : fe.fd;
                }
                else
                {
                    aliassym = null;
                    type = Type.terror;
                }
                return;
            }

            if (aliassym.isTemplateInstance())
                aliassym.semantic(sc);
            return;
        }
        inuse = 1;

        // Given:
        //  alias foo.bar.abc def;
        // it is not knowable from the syntax whether this is an alias
        // for a type or an alias for a symbol. It is up to the semantic()
        // pass to distinguish.
        // If it is a type, then type is set and getType() will return that
        // type. If it is a symbol, then aliassym is set and type is NULL -
        // toAlias() will return aliasssym.

        uint errors = global.errors;
        Type oldtype = type;

        // Ungag errors when not instantiated DeclDefs scope alias
        auto ungag = Ungag(global.gag);
        //printf("%s parent = %s, gag = %d, instantiated = %d\n", toChars(), parent, global.gag, isInstantiated());
        if (parent && global.gag && !isInstantiated() && !toParent2().isFuncDeclaration())
        {
            //printf("%s type = %s\n", toPrettyChars(), type.toChars());
            global.gag = 0;
        }

        /* This section is needed because Type.resolve() will:
         *   const x = 3;
         *   alias y = x;
         * try to convert identifier x to 3.
         */
        auto s = type.toDsymbol(sc);
        if (errors != global.errors)
        {
            s = null;
            type = Type.terror;
        }
        if (s && s == this)
        {
            error("cannot resolve");
            s = null;
            type = Type.terror;
        }
        if (!s || !s.isEnumMember())
        {
            Type t;
            Expression e;
            Scope* sc2 = sc;
            if (storage_class & (STCref | STCnothrow | STCnogc | STCpure | STCdisable))
            {
                // For 'ref' to be attached to function types, and picked
                // up by Type.resolve(), it has to go into sc.
                sc2 = sc.push();
                sc2.stc |= storage_class & (STCref | STCnothrow | STCnogc | STCpure | STCshared | STCdisable);
            }
            type = type.addSTC(storage_class);
            type.resolve(loc, sc2, &e, &t, &s);
            if (sc2 != sc)
                sc2.pop();

            if (e)  // Try to convert Expression to Dsymbol
            {
                s = getDsymbol(e);
                if (!s)
                {
                    if (e.op != TOKerror)
                        error("cannot alias an expression %s", e.toChars());
                    t = Type.terror;
                }
            }
            type = t;
        }
        if (s == this)
        {
            assert(global.errors);
            type = Type.terror;
            s = null;
        }
        if (!s) // it's a type alias
        {
            //printf("alias %s resolved to type %s\n", toChars(), type.toChars());
            type = type.semantic(loc, sc);
            aliassym = null;
        }
        else    // it's a symbolic alias
        {
            //printf("alias %s resolved to %s %s\n", toChars(), s.kind(), s.toChars());
            type = null;
            aliassym = s;
        }
        if (global.gag && errors != global.errors)
        {
            type = oldtype;
            aliassym = null;
        }
        inuse = 0;
        semanticRun = PASSsemanticdone;

        if (auto sx = overnext)
        {
            overnext = null;
            if (!overloadInsert(sx))
                ScopeDsymbol.multiplyDefined(Loc(), sx, this);
        }
    }

    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 >= PASSsemanticdone)
        {
            /* Semantic analysis is already finished, and the aliased entity
             * is not overloadable.
             */
            if (type)
                return false;

            /* 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 fd = sa.isFuncDeclaration())
            {
                auto fa = new FuncAliasDeclaration(ident, fd);
                fa.protection = protection;
                fa.parent = parent;
                aliassym = fa;
                return aliassym.overloadInsert(s);
            }
            if (auto td = sa.isTemplateDeclaration())
            {
                auto od = new OverDeclaration(ident, td);
                od.protection = protection;
                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.protection = protection;
                    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: protection is lost here b/c OverloadSets have no protection 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.protection = protection;
                    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;
        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.semantic(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 >= PASSsemanticdone)
        {
            // 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
        {
            if (_import && _import._scope)
            {
                /* If this is an internal alias for selective/renamed import,
                 * load the module first.
                 */
                _import.semantic(null);
            }
            if (_scope)
            {
                aliasSemantic(_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()
    {
        // assume overloadable until alias is resolved
        return semanticRun < PASSsemanticdone ||
            aliassym && aliassym.isOverloadable();
    }

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

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

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

    extern (D) this(Identifier ident, Dsymbol s, bool hasOverloads = true)
    {
        super(ident);
        this.aliassym = s;
        this.hasOverloads = hasOverloads;
        if (hasOverloads)
        {
            if (OverDeclaration od = aliassym.isOverDeclaration())
                this.hasOverloads = od.hasOverloads;
        }
        else
        {
            // for internal use
            assert(!aliassym.isOverDeclaration());
        }
    }

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

    override void semantic(Scope* sc)
    {
    }

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

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

        OverDeclaration od1 = this;
        if (OverDeclaration od2 = s.isOverDeclaration())
        {
            return od1.aliassym.equals(od2.aliassym) && od1.hasOverloads == od2.hasOverloads;
        }
        if (aliassym == s)
        {
            if (hasOverloads)
                return true;
            if (FuncDeclaration fd = s.isFuncDeclaration())
            {
                return fd.isUnique() !is null;
            }
            if (TemplateDeclaration td = s.isTemplateDeclaration())
            {
                return td.overnext is null;
            }
        }
        return false;
    }

    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()
    {
        return true;
    }

    Dsymbol isUnique()
    {
        if (!hasOverloads)
        {
            if (aliassym.isFuncDeclaration() ||
                aliassym.isTemplateDeclaration())
            {
                return aliassym;
            }
        }

        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;
    uint offset;
    uint sequenceNumber;            // order the variables are declared
    __gshared uint nextSequenceNumber;   // the counter for sequenceNumber
    FuncDeclarations nestedrefs;    // referenced by these lexically nested functions
    bool isargptr;                  // if parameter that _argptr points to
    structalign_t alignment;
    bool ctorinit;                  // it has been initialized in a ctor

    // Both these mean the var is not rebindable once assigned,
    // and the destructor gets run when it goes out of scope
    bool onstack;                   // it is a class that was allocated on the stack
    bool mynew;                     // it is a class new'd with custom operator new

    int canassign;                  // it can be assigned to
    bool overlapped;                // if it is a field and has overlapping
    bool overlapUnsafe;             // if it is an overlapping field and the overlaps are unsafe
    bool doNotInferScope;           // do not infer 'scope' for this variable
    ubyte isdataseg;                // private data for isDataseg 0 unset, 1 true, 2 false
    Dsymbol aliassym;               // if redone as alias to another symbol
    VarDeclaration lastVar;         // Linked list of variables for goto-skips-init detection
    uint endlinnum;                 // line number of end of scope that this var lives in

    // When interpreting, these point to the value (NULL if value not determinable)
    // The index of this variable on the CTFE stack, -1 if not allocated
    int ctfeAdrOnStack;

    Expression edtor;               // if !=null, does the destruction of the variable
    IntRange* range;                // if !=null, the variable is known to be within the range

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

        assert(type || _init);
        this.type = type;
        this._init = _init;
        this.loc = loc;
        ctfeAdrOnStack = -1;
        this.storage_class = storage_class;
        sequenceNumber = ++nextSequenceNumber;
    }

    override Dsymbol 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);
        return v;
    }

    override void semantic(Scope* sc)
    {
        version (none)
        {
            printf("VarDeclaration::semantic('%s', parent = '%s') sem = %d\n", toChars(), sc.parent ? sc.parent.toChars() : null, sem);
            printf(" type = %s\n", type ? type.toChars() : "null");
            printf(" stc = x%x\n", sc.stc);
            printf(" storage_class = x%llx\n", storage_class);
            printf("linkage = %d\n", sc.linkage);
            //if (strcmp(toChars(), "mul") == 0) assert(0);
        }
        //if (semanticRun > PASSinit)
        //    return;
        //semanticRun = PSSsemantic;

        if (semanticRun >= PASSsemanticdone)
            return;

        Scope* scx = null;
        if (_scope)
        {
            sc = _scope;
            scx = sc;
            _scope = null;
        }

        if (!sc)
            return;

        semanticRun = PASSsemantic;

        /* Pick up storage classes from context, but except synchronized,
         * override, abstract, and final.
         */
        storage_class |= (sc.stc & ~(STCsynchronized | STCoverride | STCabstract | STCfinal));
        if (storage_class & STCextern && _init)
            error("extern symbols cannot have initializers");

        userAttribDecl = sc.userAttribDecl;

        AggregateDeclaration ad = isThis();
        if (ad)
            storage_class |= ad.storage_class & STC_TYPECTOR;

        /* If auto type inference, do the inference
         */
        int inferred = 0;
        if (!type)
        {
            inuse++;

            // Infering the type requires running semantic,
            // so mark the scope as ctfe if required
            bool needctfe = (storage_class & (STCmanifest | STCstatic)) != 0;
            if (needctfe)
                sc = sc.startCTFE();

            //printf("inferring type for %s with init %s\n", toChars(), _init.toChars());
            _init = _init.inferType(sc);
            type = _init.initializerToExpression().type;
            if (needctfe)
                sc = sc.endCTFE();

            inuse--;
            inferred = 1;

            /* This is a kludge to support the existing syntax for RAII
             * declarations.
             */
            storage_class &= ~STCauto;
            originalType = type.syntaxCopy();
        }
        else
        {
            if (!originalType)
                originalType = type.syntaxCopy();

            /* Prefix function attributes of variable declaration can affect
             * its type:
             *      pure nothrow void function() fp;
             *      static assert(is(typeof(fp) == void function() pure nothrow));
             */
            Scope* sc2 = sc.push();
            sc2.stc |= (storage_class & STC_FUNCATTR);
            inuse++;
            type = type.semantic(loc, sc2);
            inuse--;
            sc2.pop();
        }
        //printf(" semantic type = %s\n", type ? type.toChars() : "null");
        if (type.ty == Terror)
            errors = true;

        type.checkDeprecated(loc, sc);
        linkage = sc.linkage;
        this.parent = sc.parent;
        //printf("this = %p, parent = %p, '%s'\n", this, parent, parent.toChars());
        protection = sc.protection;

        /* If scope's alignment is the default, use the type's alignment,
         * otherwise the scope overrrides.
         */
        alignment = sc.alignment();
        if (alignment == STRUCTALIGN_DEFAULT)
            alignment = type.alignment(); // use type's alignment

        //printf("sc.stc = %x\n", sc.stc);
        //printf("storage_class = x%x\n", storage_class);

        if (global.params.vcomplex)
            type.checkComplexTransition(loc);

        // Calculate type size + safety checks
        if (sc.func && !sc.intypeof)
        {
            if (storage_class & STCgshared && !isMember())
            {
                if (sc.func.setUnsafe())
                    error("__gshared not allowed in safe functions; use shared");
            }
        }

        Dsymbol parent = toParent();

        Type tb = type.toBasetype();
        Type tbn = tb.baseElemOf();
        if (tb.ty == Tvoid && !(storage_class & STClazy))
        {
            if (inferred)
            {
                error("type %s is inferred from initializer %s, and variables cannot be of type void", type.toChars(), _init.toChars());
            }
            else
                error("variables cannot be of type void");
            type = Type.terror;
            tb = type;
        }
        if (tb.ty == Tfunction)
        {
            error("cannot be declared to be a function");
            type = Type.terror;
            tb = type;
        }
        if (tb.ty == Tstruct)
        {
            TypeStruct ts = cast(TypeStruct)tb;
            if (!ts.sym.members)
            {
                error("no definition of struct %s", ts.toChars());
            }
        }
        if ((storage_class & STCauto) && !inferred)
            error("storage class 'auto' has no effect if type is not inferred, did you mean 'scope'?");

        if (tb.ty == Ttuple)
        {
            /* Instead, declare variables for each of the tuple elements
             * and add those.
             */
            TypeTuple tt = cast(TypeTuple)tb;
            size_t nelems = Parameter.dim(tt.arguments);
            Expression ie = (_init && !_init.isVoidInitializer()) ? _init.initializerToExpression() : null;
            if (ie)
                ie = ie.semantic(sc);
            if (nelems > 0 && ie)
            {
                auto iexps = new Expressions();
                iexps.push(ie);
                auto exps = new Expressions();
                for (size_t pos = 0; pos < iexps.dim; pos++)
                {
                Lexpand1:
                    Expression e = (*iexps)[pos];
                    Parameter arg = Parameter.getNth(tt.arguments, pos);
                    arg.type = arg.type.semantic(loc, sc);
                    //printf("[%d] iexps.dim = %d, ", pos, iexps.dim);
                    //printf("e = (%s %s, %s), ", Token::tochars[e.op], e.toChars(), e.type.toChars());
                    //printf("arg = (%s, %s)\n", arg.toChars(), arg.type.toChars());

                    if (e != ie)
                    {
                        if (iexps.dim > nelems)
                            goto Lnomatch;
                        if (e.type.implicitConvTo(arg.type))
                            continue;
                    }

                    if (e.op == TOKtuple)
                    {
                        TupleExp te = cast(TupleExp)e;
                        if (iexps.dim - 1 + te.exps.dim > nelems)
                            goto Lnomatch;

                        iexps.remove(pos);
                        iexps.insert(pos, te.exps);
                        (*iexps)[pos] = Expression.combine(te.e0, (*iexps)[pos]);
                        goto Lexpand1;
                    }
                    else if (isAliasThisTuple(e))
                    {
                        auto v = copyToTemp(0, "__tup", e);
                        v.semantic(sc);
                        auto ve = new VarExp(loc, v);
                        ve.type = e.type;

                        exps.setDim(1);
                        (*exps)[0] = ve;
                        expandAliasThisTuples(exps, 0);

                        for (size_t u = 0; u < exps.dim; u++)
                        {
                        Lexpand2:
                            Expression ee = (*exps)[u];
                            arg = Parameter.getNth(tt.arguments, pos + u);
                            arg.type = arg.type.semantic(loc, sc);
                            //printf("[%d+%d] exps.dim = %d, ", pos, u, exps.dim);
                            //printf("ee = (%s %s, %s), ", Token::tochars[ee.op], ee.toChars(), ee.type.toChars());
                            //printf("arg = (%s, %s)\n", arg.toChars(), arg.type.toChars());

                            size_t iexps_dim = iexps.dim - 1 + exps.dim;
                            if (iexps_dim > nelems)
                                goto Lnomatch;
                            if (ee.type.implicitConvTo(arg.type))
                                continue;

                            if (expandAliasThisTuples(exps, u) != -1)
                                goto Lexpand2;
                        }

                        if ((*exps)[0] != ve)
                        {
                            Expression e0 = (*exps)[0];
                            (*exps)[0] = new CommaExp(loc, new DeclarationExp(loc, v), e0);
                            (*exps)[0].type = e0.type;

                            iexps.remove(pos);
                            iexps.insert(pos, exps);
                            goto Lexpand1;
                        }
                    }
                }
                if (iexps.dim < nelems)
                    goto Lnomatch;

                ie = new TupleExp(_init.loc, iexps);
            }
        Lnomatch:

            if (ie && ie.op == TOKtuple)
            {
                TupleExp te = cast(TupleExp)ie;
                size_t tedim = te.exps.dim;
                if (tedim != nelems)
                {
                    .error(loc, "tuple of %d elements cannot be assigned to tuple of %d elements", cast(int)tedim, cast(int)nelems);
                    for (size_t u = tedim; u < nelems; u++) // fill dummy expression
                        te.exps.push(new ErrorExp());
                }
            }

            auto exps = new Objects();
            exps.setDim(nelems);
            for (size_t i = 0; i < nelems; i++)
            {
                Parameter arg = Parameter.getNth(tt.arguments, i);

                OutBuffer buf;
                buf.printf("__%s_field_%llu", ident.toChars(), cast(ulong)i);
                auto id = Identifier.idPool(buf.peekSlice());

                Initializer ti;
                if (ie)
                {
                    Expression einit = ie;
                    if (ie.op == TOKtuple)
                    {
                        TupleExp te = cast(TupleExp)ie;
                        einit = (*te.exps)[i];
                        if (i == 0)
                            einit = Expression.combine(te.e0, einit);
                    }
                    ti = new ExpInitializer(einit.loc, einit);
                }
                else
                    ti = _init ? _init.syntaxCopy() : null;

                StorageClass storage_class = STCtemp | storage_class;
                if (arg.storageClass & STCparameter)
                    storage_class |= arg.storageClass;
                auto v = new VarDeclaration(loc, arg.type, id, ti, storage_class);
                //printf("declaring field %s of type %s\n", v.toChars(), v.type.toChars());
                v.semantic(sc);

                if (sc.scopesym)
                {
                    //printf("adding %s to %s\n", v.toChars(), sc.scopesym.toChars());
                    if (sc.scopesym.members)
                        // Note this prevents using foreach() over members, because the limits can change
                        sc.scopesym.members.push(v);
                }

                Expression e = new DsymbolExp(loc, v);
                (*exps)[i] = e;
            }
            auto v2 = new TupleDeclaration(loc, ident, exps);
            v2.parent = this.parent;
            v2.isexp = true;
            aliassym = v2;
            semanticRun = PASSsemanticdone;
            return;
        }

        /* Storage class can modify the type
         */
        type = type.addStorageClass(storage_class);

        /* Adjust storage class to reflect type
         */
        if (type.isConst())
        {
            storage_class |= STCconst;
            if (type.isShared())
                storage_class |= STCshared;
        }
        else if (type.isImmutable())
            storage_class |= STCimmutable;
        else if (type.isShared())
            storage_class |= STCshared;
        else if (type.isWild())
            storage_class |= STCwild;

        if (StorageClass stc = storage_class & (STCsynchronized | STCoverride | STCabstract | STCfinal))
        {
            if (stc == STCfinal)
                error("cannot be final, perhaps you meant const?");
            else
            {
                OutBuffer buf;
                stcToBuffer(&buf, stc);
                error("cannot be %s", buf.peekString());
            }
            storage_class &= ~stc; // strip off
        }

        if (storage_class & STCscope)
        {
            StorageClass stc = storage_class & (STCstatic | STCextern | STCmanifest | STCtls | STCgshared);
            if (stc)
            {
                OutBuffer buf;
                stcToBuffer(&buf, stc);
                error("cannot be 'scope' and '%s'", buf.peekString());
            }
            else if (isMember())
            {
                error("field cannot be 'scope'");
            }
            else if (!type.hasPointers())
            {
                storage_class &= ~STCscope;     // silently ignore; may occur in generic code
            }
        }

        if (storage_class & (STCstatic | STCextern | STCmanifest | STCtemplateparameter | STCtls | STCgshared | STCctfe))
        {
        }
        else
        {
            AggregateDeclaration aad = parent.isAggregateDeclaration();
            if (aad)
            {
                if (global.params.vfield && storage_class & (STCconst | STCimmutable) && _init && !_init.isVoidInitializer())
                {
                    const(char)* p = loc.toChars();
                    const(char)* s = (storage_class & STCimmutable) ? "immutable" : "const";
                    fprintf(global.stdmsg, "%s: %s.%s is %s field\n", p ? p : "", ad.toPrettyChars(), toChars(), s);
                }
                storage_class |= STCfield;
                if (tbn.ty == Tstruct && (cast(TypeStruct)tbn).sym.noDefaultCtor)
                {
                    if (!isThisDeclaration() && !_init)
                        aad.noDefaultCtor = true;
                }
            }

            InterfaceDeclaration id = parent.isInterfaceDeclaration();
            if (id)
            {
                error("field not allowed in interface");
            }
            else if (aad && aad.sizeok == SIZEOKdone)
            {
                error("cannot be further field because it will change the determined %s size", aad.toChars());
            }

            /* Templates cannot add fields to aggregates
             */
            TemplateInstance ti = parent.isTemplateInstance();
            if (ti)
            {
                // Take care of nested templates
                while (1)
                {
                    TemplateInstance ti2 = ti.tempdecl.parent.isTemplateInstance();
                    if (!ti2)
                        break;
                    ti = ti2;
                }
                // If it's a member template
                AggregateDeclaration ad2 = ti.tempdecl.isMember();
                if (ad2 && storage_class != STCundefined)
                {
                    error("cannot use template to add field to aggregate '%s'", ad2.toChars());
                }
            }
        }

        if ((storage_class & (STCref | STCparameter | STCforeach | STCtemp | STCresult)) == STCref && ident != Id.This)
        {
            error("only parameters or foreach declarations can be ref");
        }

        if (type.hasWild())
        {
            if (storage_class & (STCstatic | STCextern | STCtls | STCgshared | STCmanifest | STCfield) || isDataseg())
            {
                error("only parameters or stack based variables can be inout");
            }
            FuncDeclaration func = sc.func;
            if (func)
            {
                if (func.fes)
                    func = func.fes.func;
                bool isWild = false;
                for (FuncDeclaration fd = func; fd; fd = fd.toParent2().isFuncDeclaration())
                {
                    if ((cast(TypeFunction)fd.type).iswild)
                    {
                        isWild = true;
                        break;
                    }
                }
                if (!isWild)
                {
                    error("inout variables can only be declared inside inout functions");
                }
            }
        }

        if (!(storage_class & (STCctfe | STCref | STCresult)) && tbn.ty == Tstruct && (cast(TypeStruct)tbn).sym.noDefaultCtor)
        {
            if (!_init)
            {
                if (isField())
                {
                    /* For fields, we'll check the constructor later to make sure it is initialized
                     */
                    storage_class |= STCnodefaultctor;
                }
                else if (storage_class & STCparameter)
                {
                }
                else
                    error("default construction is disabled for type %s", type.toChars());
            }
        }

        FuncDeclaration fd = parent.isFuncDeclaration();
        if (type.isscope() && !(storage_class & STCnodtor))
        {
            if (storage_class & (STCfield | STCout | STCref | STCstatic | STCmanifest | STCtls | STCgshared) || !fd)
            {
                error("globals, statics, fields, manifest constants, ref and out parameters cannot be scope");
            }
            if (!(storage_class & STCscope))
            {
                if (!(storage_class & STCparameter) && ident != Id.withSym)
                    error("reference to scope class must be scope");
            }
        }

        // Calculate type size + safety checks
        if (sc.func && !sc.intypeof)
        {
            if (_init && _init.isVoidInitializer() && type.hasPointers()) // get type size
            {
                if (sc.func.setUnsafe())
                    error("void initializers for pointers not allowed in safe functions");
            }
            else if (!_init &&
                     !(storage_class & (STCstatic | STCextern | STCtls | STCgshared | STCmanifest | STCfield | STCparameter)) &&
                     type.hasVoidInitPointers())
            {
                if (sc.func.setUnsafe())
                    error("void initializers for pointers not allowed in safe functions");
            }
        }

        if (!_init && !fd)
        {
            // If not mutable, initializable by constructor only
            storage_class |= STCctorinit;
        }

        if (_init)
            storage_class |= STCinit; // remember we had an explicit initializer
        else if (storage_class & STCmanifest)
            error("manifest constants must have initializers");

        bool isBlit = false;
        d_uns64 sz;
        if (!_init &&
            !sc.inunion &&
            !(storage_class & (STCstatic | STCgshared | STCextern)) &&
            fd &&
            (!(storage_class & (STCfield | STCin | STCforeach | STCparameter | STCresult)) ||
             (storage_class & STCout)) &&
            (sz = type.size()) != 0)
        {
            // Provide a default initializer

            //printf("Providing default initializer for '%s'\n", toChars());
            if (sz == SIZE_INVALID && type.ty != Terror)
                error("size of type %s is invalid", type.toChars());

            Type tv = type;
            while (tv.ty == Tsarray)    // Don't skip Tenum
                tv = tv.nextOf();
            if (tv.needsNested())
            {
                /* Nested struct requires valid enclosing frame pointer.
                 * In StructLiteralExp::toElem(), it's calculated.
                 */
                assert(tbn.ty == Tstruct);
                checkFrameAccess(loc, sc, (cast(TypeStruct)tbn).sym);

                Expression e = tv.defaultInitLiteral(loc);
                e = new BlitExp(loc, new VarExp(loc, this), e);
                e = e.semantic(sc);
                _init = new ExpInitializer(loc, e);
                goto Ldtor;
            }
            if (tv.ty == Tstruct && (cast(TypeStruct)tv).sym.zeroInit == 1)
            {
                /* If a struct is all zeros, as a special case
                 * set it's initializer to the integer 0.
                 * In AssignExp::toElem(), we check for this and issue
                 * a memset() to initialize the struct.
                 * Must do same check in interpreter.
                 */
                Expression e = new IntegerExp(loc, 0, Type.tint32);
                e = new BlitExp(loc, new VarExp(loc, this), e);
                e.type = type;      // don't type check this, it would fail
                _init = new ExpInitializer(loc, e);
                goto Ldtor;
            }
            if (type.baseElemOf().ty == Tvoid)
            {
                error("%s does not have a default initializer", type.toChars());
            }
            else if (auto e = type.defaultInit(loc))
            {
                _init = new ExpInitializer(loc, e);
            }

            // Default initializer is always a blit
            isBlit = true;
        }
        if (_init)
        {
            sc = sc.push();
            sc.stc &= ~(STC_TYPECTOR | STCpure | STCnothrow | STCnogc | STCref | STCdisable);

            ExpInitializer ei = _init.isExpInitializer();
            if (ei) // https://issues.dlang.org/show_bug.cgi?id=13424
                    // Preset the required type to fail in FuncLiteralDeclaration::semantic3
                ei.exp = inferType(ei.exp, type);

            // If inside function, there is no semantic3() call
            if (sc.func || sc.intypeof == 1)
            {
                // If local variable, use AssignExp to handle all the various
                // possibilities.
                if (fd && !(storage_class & (STCmanifest | STCstatic | STCtls | STCgshared | STCextern)) && !_init.isVoidInitializer())
                {
                    //printf("fd = '%s', var = '%s'\n", fd.toChars(), toChars());
                    if (!ei)
                    {
                        ArrayInitializer ai = _init.isArrayInitializer();
                        Expression e;
                        if (ai && tb.ty == Taarray)
                            e = ai.toAssocArrayLiteral();
                        else
                            e = _init.initializerToExpression();
                        if (!e)
                        {
                            // Run semantic, but don't need to interpret
                            _init = _init.semantic(sc, type, INITnointerpret);
                            e = _init.initializerToExpression();
                            if (!e)
                            {
                                error("is not a static and cannot have static initializer");
                                e = new ErrorExp();
                            }
                        }
                        ei = new ExpInitializer(_init.loc, e);
                        _init = ei;
                    }

                    Expression exp = ei.exp;
                    Expression e1 = new VarExp(loc, this);
                    if (isBlit)
                        exp = new BlitExp(loc, e1, exp);
                    else
                        exp = new ConstructExp(loc, e1, exp);
                    canassign++;
                    exp = exp.semantic(sc);
                    canassign--;
                    exp = exp.optimize(WANTvalue);
                    if (exp.op == TOKerror)
                    {
                        _init = new ErrorInitializer();
                        ei = null;
                    }
                    else
                        ei.exp = exp;

                    if (ei && isScope())
                    {
                        Expression ex = ei.exp;
                        while (ex.op == TOKcomma)
                            ex = (cast(CommaExp)ex).e2;
                        if (ex.op == TOKblit || ex.op == TOKconstruct)
                            ex = (cast(AssignExp)ex).e2;
                        if (ex.op == TOKnew)
                        {
                            // See if initializer is a NewExp that can be allocated on the stack
                            NewExp ne = cast(NewExp)ex;
                            if (type.toBasetype().ty == Tclass)
                            {
                                if (ne.newargs && ne.newargs.dim > 1)
                                {
                                    mynew = true;
                                }
                                else
                                {
                                    ne.onstack = 1;
                                    onstack = true;
                                }
                            }
                        }
                        else if (ex.op == TOKfunction)
                        {
                            // or a delegate that doesn't escape a reference to the function
                            FuncDeclaration f = (cast(FuncExp)ex).fd;
                            f.tookAddressOf--;
                        }
                    }
                }
                else
                {
                    // https://issues.dlang.org/show_bug.cgi?id=14166
                    // Don't run CTFE for the temporary variables inside typeof
                    _init = _init.semantic(sc, type, sc.intypeof == 1 ? INITnointerpret : INITinterpret);
                }
            }
            else if (parent.isAggregateDeclaration())
            {
                _scope = scx ? scx : sc.copy();
                _scope.setNoFree();
            }
            else if (storage_class & (STCconst | STCimmutable | STCmanifest) || type.isConst() || type.isImmutable())
            {
                /* Because we may need the results of a const declaration in a
                 * subsequent type, such as an array dimension, before semantic2()
                 * gets ordinarily run, try to run semantic2() now.
                 * Ignore failure.
                 */
                if (!inferred)
                {
                    uint errors = global.errors;
                    inuse++;
                    if (ei)
                    {
                        Expression exp = ei.exp.syntaxCopy();

                        bool needctfe = isDataseg() || (storage_class & STCmanifest);
                        if (needctfe)
                            sc = sc.startCTFE();
                        exp = exp.semantic(sc);
                        exp = resolveProperties(sc, exp);
                        if (needctfe)
                            sc = sc.endCTFE();

                        Type tb2 = type.toBasetype();
                        Type ti = exp.type.toBasetype();

                        /* The problem is the following code:
                         *  struct CopyTest {
                         *     double x;
                         *     this(double a) { x = a * 10.0;}
                         *     this(this) { x += 2.0; }
                         *  }
                         *  const CopyTest z = CopyTest(5.3);  // ok
                         *  const CopyTest w = z;              // not ok, postblit not run
                         *  static assert(w.x == 55.0);
                         * because the postblit doesn't get run on the initialization of w.
                         */
                        if (ti.ty == Tstruct)
                        {
                            StructDeclaration sd = (cast(TypeStruct)ti).sym;
                            /* Look to see if initializer involves a copy constructor
                             * (which implies a postblit)
                             */
                            // there is a copy constructor
                            // and exp is the same struct
                            if (sd.postblit && tb2.toDsymbol(null) == sd)
                            {
                                // The only allowable initializer is a (non-copy) constructor
                                if (exp.isLvalue())
                                    error("of type struct %s uses this(this), which is not allowed in static initialization", tb2.toChars());
                            }
                        }
                        ei.exp = exp;
                    }
                    _init = _init.semantic(sc, type, INITinterpret);
                    inuse--;
                    if (global.errors > errors)
                    {
                        _init = new ErrorInitializer();
                        type = Type.terror;
                    }
                }
                else
                {
                    _scope = scx ? scx : sc.copy();
                    _scope.setNoFree();
                }
            }
            sc = sc.pop();
        }

    Ldtor:
        /* Build code to execute destruction, if necessary
         */
        edtor = callScopeDtor(sc);
        if (edtor)
        {
            if (sc.func && storage_class & (STCstatic | STCgshared))
                edtor = edtor.semantic(sc._module._scope);
            else
                edtor = edtor.semantic(sc);

            version (none)
            {
                // currently disabled because of std.stdio.stdin, stdout and stderr
                if (isDataseg() && !(storage_class & STCextern))
                    error("static storage variables cannot have destructors");
            }
        }

        semanticRun = PASSsemanticdone;

        if (type.toBasetype().ty == Terror)
            errors = true;

        if(sc.scopesym && !sc.scopesym.isAggregateDeclaration())
        {
            for (ScopeDsymbol sym = sc.scopesym; sym && endlinnum == 0;
                 sym = sym.parent ? sym.parent.isScopeDsymbol() : null)
                endlinnum = sym.endlinnum;
        }
    }

    override final void setFieldOffset(AggregateDeclaration ad, uint* poffset, bool isunion)
    {
        //printf("VarDeclaration::setFieldOffset(ad = %s) %s\n", ad.toChars(), toChars());

        if (aliassym)
        {
            // If this variable was really a tuple, set the offsets for the tuple fields
            TupleDeclaration v2 = aliassym.isTupleDeclaration();
            assert(v2);
            for (size_t i = 0; i < v2.objects.dim; i++)
            {
                RootObject o = (*v2.objects)[i];
                assert(o.dyncast() == DYNCAST.expression);
                Expression e = cast(Expression)o;
                assert(e.op == TOKdsymbol);
                DsymbolExp se = cast(DsymbolExp)e;
                se.s.setFieldOffset(ad, poffset, isunion);
            }
            return;
        }

        if (!isField())
            return;
        assert(!(storage_class & (STCstatic | STCextern | STCparameter | STCtls)));

        //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
            *poffset = ad.structsize; // https://issues.dlang.org/show_bug.cgi?id=13613
            return;
        }
        for (size_t i = 0; i < ad.fields.dim; i++)
        {
            if (ad.fields[i] == this)
            {
                // already a field
                *poffset = 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 & STCref)
        {
            // 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;

        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(
            poffset,
            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 final void semantic2(Scope* sc)
    {
        if (semanticRun < PASSsemanticdone && inuse)
            return;

        //printf("VarDeclaration::semantic2('%s')\n", toChars());

        if (_init && !toParent().isFuncDeclaration())
        {
            inuse++;
            version (none)
            {
                ExpInitializer ei = _init.isExpInitializer();
                if (ei)
                {
                    ei.exp.print();
                    printf("type = %p\n", ei.exp.type);
                }
            }
            // https://issues.dlang.org/show_bug.cgi?id=14166
            // Don't run CTFE for the temporary variables inside typeof
            _init = _init.semantic(sc, type, sc.intypeof == 1 ? INITnointerpret : INITinterpret);
            inuse--;
        }
        if (_init && storage_class & STCmanifest)
        {
            /* Cannot initializer enums with CTFE classreferences and addresses of struct literals.
             * Scan initializer looking for them. Issue error if found.
             */
            if (ExpInitializer ei = _init.isExpInitializer())
            {
                static bool hasInvalidEnumInitializer(Expression e)
                {
                    static bool arrayHasInvalidEnumInitializer(Expressions* elems)
                    {
                        foreach (e; *elems)
                        {
                            if (e && hasInvalidEnumInitializer(e))
                                return true;
                        }
                        return false;
                    }

                    if (e.op == TOKclassreference)
                        return true;
                    if (e.op == TOKaddress && (cast(AddrExp)e).e1.op == TOKstructliteral)
                        return true;
                    if (e.op == TOKarrayliteral)
                        return arrayHasInvalidEnumInitializer((cast(ArrayLiteralExp)e).elements);
                    if (e.op == TOKstructliteral)
                        return arrayHasInvalidEnumInitializer((cast(StructLiteralExp)e).elements);
                    if (e.op == TOKassocarrayliteral)
                    {
                        AssocArrayLiteralExp ae = cast(AssocArrayLiteralExp)e;
                        return arrayHasInvalidEnumInitializer(ae.values) ||
                               arrayHasInvalidEnumInitializer(ae.keys);
                    }
                    return false;
                }

                if (hasInvalidEnumInitializer(ei.exp))
                    error(": Unable to initialize enum with class or pointer to struct. Use static const variable instead.");
            }
        }
        else if (_init && isThreadlocal())
        {
            if ((type.ty == Tclass) && type.isMutable() && !type.isShared())
            {
                ExpInitializer ei = _init.isExpInitializer();
                if (ei && ei.exp.op == TOKclassreference)
                    error("is mutable. Only const or immutable class thread local variable are allowed, not %s", type.toChars());
            }
            else if (type.ty == Tpointer && type.nextOf().ty == Tstruct && type.nextOf().isMutable() && !type.nextOf().isShared())
            {
                ExpInitializer ei = _init.isExpInitializer();
                if (ei && ei.exp.op == TOKaddress && (cast(AddrExp)ei.exp).e1.op == TOKstructliteral)
                {
                    error("is a pointer to mutable struct. Only pointers to const, immutable or shared struct thread local variable are allowed, not %s", type.toChars());
                }
            }
        }
        semanticRun = PASSsemantic2done;
    }

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

    override final AggregateDeclaration isThis()
    {
        AggregateDeclaration ad = null;
        if (!(storage_class & (STCstatic | STCextern | STCmanifest | STCtemplateparameter | STCtls | STCgshared | STCctfe)))
        {
            for (Dsymbol s = this; s; s = s.parent)
            {
                ad = s.isMember();
                if (ad)
                    break;
                if (!s.parent || !s.parent.isTemplateMixin())
                    break;
            }
        }
        return ad;
    }

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

    override final bool isExport()
    {
        return protection.kind == PROTexport;
    }

    override final bool isImportedSymbol()
    {
        if (protection.kind == PROTexport && !_init && (storage_class & STCstatic || parent.isModule()))
            return true;
        return false;
    }

    /*******************************
     * 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 & (STCstatic | STCconst), 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 & STCstatic))
            {
                error("forward referenced");
                type = Type.terror;
            }
            else if (storage_class & (STCstatic | STCextern | STCtls | STCgshared) ||
                parent.isModule() || parent.isTemplateInstance() || parent.isNspace())
            {
                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 & (STCimmutable | STCconst | STCshared | STCgshared));
        //printf("\treturn %d\n", i);
        return i;
    }

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

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

    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 & STCmanifest);
    }

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

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

        // Destruction of STCfield's is handled by buildDtor()
        if (storage_class & (STCnodtor | STCref | STCout | STCfield))
        {
            return null;
        }

        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;
                e = new SliceExp(loc, e, new IntegerExp(loc, 0, Type.tsize_t), new IntegerExp(loc, n, Type.tsize_t));

                // Prevent redundant bounds check
                (cast(SliceExp)e).upperIsInBounds = true;
                (cast(SliceExp)e).lowerIsLessThanUpper = true;

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

                e = new CallExp(loc, new IdentifierExp(loc, Id._ArrayDtor), e);
            }
            return e;
        }
        // Destructors for classes
        if (storage_class & (STCauto | STCscope) && !(storage_class & STCparameter))
        {
            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());

                // Destroying C++ scope classes crashes currently. Since C++ class dtors are not currently supported, simply do not run dtors for them.
                // See https://issues.dlang.org/show_bug.cgi?id=13182
                if (cd.cpp)
                {
                    break;
                }
                if (mynew || onstack) // if any destructors
                {
                    // 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.
     */
    final Expression getConstInitializer(bool needFullType = true)
    {
        assert(type && _init);

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

        if (_scope)
        {
            inuse++;
            _init = _init.semantic(_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.
     */
    final Expression expandInitializer(Loc loc)
    {
        assert((storage_class & STCmanifest) && _init);

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

        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.
     * Returns true if error occurs.
     */
    final bool checkNestedReference(Scope* sc, Loc loc)
    {
        //printf("VarDeclaration::checkNestedReference() %s\n", toChars());
        if (sc.intypeof == 1 || (sc.flags & SCOPEctfe))
            return false;
        if (!parent || parent == sc.parent)
            return false;
        if (isDataseg() || (storage_class & STCmanifest))
            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
        for (size_t i = 0; 1; i++)
        {
            if (i == nestedrefs.dim)
            {
                nestedrefs.push(fdthis);
                break;
            }
            if (nestedrefs[i] == fdthis)
                break;
        }

        /* __require and __ensure will always get called directly,
         * so they never make outer functions closure.
         */
        if (fdthis.ident == Id.require || fdthis.ident == Id.ensure)
            return false;

        //printf("\tfdv = %s\n", fdv.toChars());
        //printf("\tfdthis = %s\n", fdthis.toChars());
        if (loc.filename)
        {
            int lv = fdthis.getLevel(loc, sc, fdv);
            if (lv == -2) // error
                return true;
        }

        // Add this to fdv.closureVars[] if not already there
        for (size_t i = 0; 1; i++)
        {
            if (i == fdv.closureVars.dim)
            {
                if (!sc.intypeof && !(sc.flags & SCOPEcompile))
                    fdv.closureVars.push(this);
                break;
            }
            if (fdv.closureVars[i] == this)
                break;
        }

        //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 == TOKconstruct || e.op == TOKblit)
                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)
            semantic(_scope);

        assert(this != aliassym);
        Dsymbol s = aliassym ? aliassym.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);
    }

    /**********************************
     * Determine if `this` has a lifetime that lasts past
     * the destruction of `v`
     * Params:
     *  v = variable to test against
     * Returns:
     *  true if it does
     */
    final bool enclosesLifetimeOf(VarDeclaration v) const pure
    {
        return sequenceNumber < v.sequenceNumber;
    }
}

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

    extern (D) this(Loc loc, StructDeclaration dsym)
    {
        super(dsym.ident);
        this.loc = loc;
        this.dsym = dsym;
        storage_class |= STCconst;
    }

    // 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(), Type.dtypeinfo.type, tinfo.getTypeInfoIdent(), null);
        this.tinfo = tinfo;
        storage_class = STCstatic | STCgshared;
        protection = Prot(PROTpublic);
        linkage = LINKc;
        alignment = Target.ptrsize;
    }

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

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

    override final void semantic(Scope* sc)
    {
        assert(linkage == LINKc);
    }

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

    override final inout(TypeInfoDeclaration) isTypeInfoDeclaration() inout
    {
        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(Loc loc, Type t)
    {
        super(loc, t, Id.This, null);
        storage_class |= STCnodtor;
    }

    override Dsymbol 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);
    }
}
