]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - gcc/d/dmd/dinterpret.d
d: Merge upstream dmd, druntime 09faa4eacd, phobos 13ef27a56.
[thirdparty/gcc.git] / gcc / d / dmd / dinterpret.d
index a1f36c0c48f9259e9f7a8a8bde479d65ec1ec9a1..7920df799910e7bf8c1cc246ab8d31a591907e27 100644 (file)
@@ -3,9 +3,9 @@
  *
  * Specification: ($LINK2 https://dlang.org/spec/function.html#interpretation, Compile Time Function Execution (CTFE))
  *
- * Copyright:   Copyright (C) 1999-2021 by The D Language Foundation, 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)
+ * Copyright:   Copyright (C) 1999-2023 by The D Language Foundation, All Rights Reserved
+ * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
+ * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/dinterpret.d, _dinterpret.d)
  * Documentation:  https://dlang.org/phobos/dmd_dinterpret.html
  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/dinterpret.d
@@ -34,10 +34,12 @@ import dmd.expression;
 import dmd.expressionsem;
 import dmd.func;
 import dmd.globals;
+import dmd.hdrgen;
 import dmd.id;
 import dmd.identifier;
 import dmd.init;
 import dmd.initsem;
+import dmd.location;
 import dmd.mtype;
 import dmd.printast;
 import dmd.root.rmem;
@@ -45,9 +47,9 @@ import dmd.root.array;
 import dmd.root.ctfloat;
 import dmd.root.region;
 import dmd.root.rootobject;
+import dmd.root.utf;
 import dmd.statement;
 import dmd.tokens;
-import dmd.utf;
 import dmd.visitor;
 
 /*************************************
@@ -63,26 +65,26 @@ public Expression ctfeInterpret(Expression e)
 {
     switch (e.op)
     {
-        case TOK.int64:
-        case TOK.float64:
-        case TOK.complex80:
-        case TOK.null_:
-        case TOK.void_:
-        case TOK.string_:
-        case TOK.this_:
-        case TOK.super_:
-        case TOK.type:
-        case TOK.typeid_:
-        case TOK.template_:              // non-eponymous template/instance
-        case TOK.scope_:                 // ditto
-        case TOK.dotTemplateDeclaration: // ditto, e.e1 doesn't matter here
-        case TOK.dotTemplateInstance:    // ditto
-        case TOK.dot:                    // ditto
+        case EXP.int64:
+        case EXP.float64:
+        case EXP.complex80:
+        case EXP.null_:
+        case EXP.void_:
+        case EXP.string_:
+        case EXP.this_:
+        case EXP.super_:
+        case EXP.type:
+        case EXP.typeid_:
+        case EXP.template_:              // non-eponymous template/instance
+        case EXP.scope_:                 // ditto
+        case EXP.dotTemplateDeclaration: // ditto, e.e1 doesn't matter here
+        case EXP.dotTemplateInstance:    // ditto
+        case EXP.dot:                    // ditto
              if (e.type.ty == Terror)
                 return ErrorExp.get();
-            goto case TOK.error;
+            goto case EXP.error;
 
-        case TOK.error:
+        case EXP.error:
             return e;
 
         default:
@@ -98,7 +100,15 @@ public Expression ctfeInterpret(Expression e)
 
     Expression result = interpret(e, null);
 
-    result = copyRegionExp(result);
+    // Report an error if the expression contained a `ThrowException` and
+    // hence generated an uncaught exception
+    if (auto tee = result.isThrownExceptionExp())
+    {
+        tee.generateUncaughtError();
+        result = CTFEExp.cantexp;
+    }
+    else
+        result = copyRegionExp(result);
 
     if (!CTFEExp.isCantExp(result))
         result = scrubReturnValue(e.loc, result);
@@ -115,7 +125,7 @@ public Expression ctfeInterpret(Expression e)
  */
 public Expression ctfeInterpretForPragmaMsg(Expression e)
 {
-    if (e.op == TOK.error || e.op == TOK.type)
+    if (e.op == EXP.error || e.op == EXP.type)
         return e;
 
     // It's also OK for it to be a function declaration (happens only with
@@ -156,7 +166,7 @@ public Expression ctfeInterpretForPragmaMsg(Expression e)
     return e;
 }
 
-public extern (C++) Expression getValue(VarDeclaration vd)
+public Expression getValue(VarDeclaration vd)
 {
     return ctfeGlobals.stack.getValue(vd);
 }
@@ -271,25 +281,25 @@ private:
     Expression localThis;       // value of 'this', or NULL if none
 
 public:
-    extern (C++) size_t stackPointer()
+    size_t stackPointer()
     {
-        return values.dim;
+        return values.length;
     }
 
     // The current value of 'this', or NULL if none
-    extern (C++) Expression getThis()
+    Expression getThis()
     {
         return localThis;
     }
 
     // Largest number of stack positions we've used
-    extern (C++) size_t maxStackUsage()
+    size_t maxStackUsage()
     {
         return maxStackPointer;
     }
 
     // Start a new stack frame, using the provided 'this'.
-    extern (C++) void startFrame(Expression thisexp)
+    void startFrame(Expression thisexp)
     {
         frames.push(cast(void*)cast(size_t)framepointer);
         savedThis.push(localThis);
@@ -297,36 +307,36 @@ public:
         localThis = thisexp;
     }
 
-    extern (C++) void endFrame()
+    void endFrame()
     {
-        size_t oldframe = cast(size_t)frames[frames.dim - 1];
-        localThis = savedThis[savedThis.dim - 1];
+        size_t oldframe = cast(size_t)frames[frames.length - 1];
+        localThis = savedThis[savedThis.length - 1];
         popAll(framepointer);
         framepointer = oldframe;
-        frames.setDim(frames.dim - 1);
-        savedThis.setDim(savedThis.dim - 1);
+        frames.setDim(frames.length - 1);
+        savedThis.setDim(savedThis.length - 1);
     }
 
-    extern (C++) bool isInCurrentFrame(VarDeclaration v)
+    bool isInCurrentFrame(VarDeclaration v)
     {
         if (v.isDataseg() && !v.isCTFE())
             return false; // It's a global
         return v.ctfeAdrOnStack >= framepointer;
     }
 
-    extern (C++) Expression getValue(VarDeclaration v)
+    Expression getValue(VarDeclaration v)
     {
         //printf("getValue() %s\n", v.toChars());
         if ((v.isDataseg() || v.storage_class & STC.manifest) && !v.isCTFE())
         {
-            assert(v.ctfeAdrOnStack < globalValues.dim);
+            assert(v.ctfeAdrOnStack < globalValues.length);
             return globalValues[v.ctfeAdrOnStack];
         }
         assert(v.ctfeAdrOnStack < stackPointer());
         return values[v.ctfeAdrOnStack];
     }
 
-    extern (C++) void setValue(VarDeclaration v, Expression e)
+    void setValue(VarDeclaration v, Expression e)
     {
         //printf("setValue() %s : %s\n", v.toChars(), e.toChars());
         assert(!v.isDataseg() || v.isCTFE());
@@ -334,7 +344,7 @@ public:
         values[v.ctfeAdrOnStack] = e;
     }
 
-    extern (C++) void push(VarDeclaration v)
+    void push(VarDeclaration v)
     {
         //printf("push() %s\n", v.toChars());
         assert(!v.isDataseg() || v.isCTFE());
@@ -345,18 +355,18 @@ public:
             return;
         }
         savedId.push(cast(void*)cast(size_t)v.ctfeAdrOnStack);
-        v.ctfeAdrOnStack = cast(uint)values.dim;
+        v.ctfeAdrOnStack = cast(uint)values.length;
         vars.push(v);
         values.push(null);
     }
 
-    extern (C++) void pop(VarDeclaration v)
+    void pop(VarDeclaration v)
     {
         assert(!v.isDataseg() || v.isCTFE());
         assert(!v.isReference());
         const oldid = v.ctfeAdrOnStack;
         v.ctfeAdrOnStack = cast(uint)cast(size_t)savedId[oldid];
-        if (v.ctfeAdrOnStack == values.dim - 1)
+        if (v.ctfeAdrOnStack == values.length - 1)
         {
             values.pop();
             vars.pop();
@@ -364,12 +374,12 @@ public:
         }
     }
 
-    extern (C++) void popAll(size_t stackpointer)
+    void popAll(size_t stackpointer)
     {
         if (stackPointer() > maxStackPointer)
             maxStackPointer = stackPointer();
-        assert(values.dim >= stackpointer);
-        for (size_t i = stackpointer; i < values.dim; ++i)
+        assert(values.length >= stackpointer);
+        for (size_t i = stackpointer; i < values.length; ++i)
         {
             VarDeclaration v = vars[i];
             v.ctfeAdrOnStack = cast(uint)cast(size_t)savedId[i];
@@ -379,10 +389,10 @@ public:
         savedId.setDim(stackpointer);
     }
 
-    extern (C++) void saveGlobalConstant(VarDeclaration v, Expression e)
+    void saveGlobalConstant(VarDeclaration v, Expression e)
     {
         assert(v._init && (v.isConst() || v.isImmutable() || v.storage_class & STC.manifest) && !v.isCTFE());
-        v.ctfeAdrOnStack = cast(uint)globalValues.dim;
+        v.ctfeAdrOnStack = cast(uint)globalValues.length;
         globalValues.push(copyRegionExp(e));
     }
 }
@@ -410,7 +420,7 @@ private struct InterState
  *      thisarg   = 'this', if a needThis() function, NULL if not.
  *
  * Returns:
- * result expression if successful, TOK.cantExpression if not,
+ * result expression if successful, EXP.cantExpression if not,
  * or CTFEExp if function returned void.
  */
 private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterState* istate, Expressions* arguments, Expression thisarg)
@@ -435,7 +445,7 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
 
     auto tf = fd.type.toBasetype().isTypeFunction();
     if (tf.parameterList.varargs != VarArg.none && arguments &&
-        ((fd.parameters && arguments.dim != fd.parameters.dim) || (!fd.parameters && arguments.dim)))
+        ((fd.parameters && arguments.length != fd.parameters.length) || (!fd.parameters && arguments.length)))
     {
         fd.error("C-style variadic functions are not yet implemented in CTFE");
         return CTFEExp.cantexp;
@@ -458,8 +468,8 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
 
     // Place to hold all the arguments to the function while
     // we are evaluating them.
-    size_t dim = arguments ? arguments.dim : 0;
-    assert((fd.parameters ? fd.parameters.dim : 0) == dim);
+    size_t dim = arguments ? arguments.length : 0;
+    assert((fd.parameters ? fd.parameters.length : 0) == dim);
 
     /* Evaluate all the arguments to the function,
      * store the results in eargs[]
@@ -483,7 +493,7 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
             if (CTFEExp.isCantExp(earg))
                 return earg;
         }
-        else if (fparam.storageClass & STC.lazy_)
+        else if (fparam.isLazy())
         {
         }
         else
@@ -507,7 +517,7 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
             /* Struct literals are passed by value, but we don't need to
              * copy them if they are passed as const
              */
-            if (earg.op == TOK.structLiteral && !(fparam.storageClass & (STC.const_ | STC.immutable_)))
+            if (earg.op == EXP.structLiteral && !(fparam.storageClass & (STC.const_ | STC.immutable_)))
                 earg = copyLiteral(earg).copy();
         }
         if (auto tee = earg.isThrownExceptionExp())
@@ -526,7 +536,7 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
     istatex.caller = istate;
     istatex.fd = fd;
 
-    if (fd.isThis2)
+    if (fd.hasDualContext())
     {
         Expression arg0 = thisarg;
         if (arg0 && arg0.type.ty == Tstruct)
@@ -563,7 +573,7 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
         }
         ctfeGlobals.stack.push(v);
 
-        if (fparam.isReference() && earg.op == TOK.variable &&
+        if (fparam.isReference() && earg.op == EXP.variable &&
             earg.isVarExp().var.toParent2() == fd)
         {
             VarDeclaration vx = earg.isVarExp().var.isVarDeclaration();
@@ -660,16 +670,28 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
         }
         else
         {
-            assert(!e || (e.op != TOK.continue_ && e.op != TOK.break_));
+            assert(!e || (e.op != EXP.continue_ && e.op != EXP.break_));
             break;
         }
     }
     // If fell off the end of a void function, return void
-    if (!e && tf.next.ty == Tvoid)
-        e = CTFEExp.voidexp;
-    if (tf.isref && e.op == TOK.variable && e.isVarExp().var == fd.vthis)
+    if (!e)
+    {
+        if (tf.next.ty == Tvoid)
+            e = CTFEExp.voidexp;
+        else
+        {
+            /* missing a return statement can happen with C functions
+             * https://issues.dlang.org/show_bug.cgi?id=23056
+             */
+            fd.error("no return value from function");
+            e = CTFEExp.cantexp;
+        }
+    }
+
+    if (tf.isref && e.op == EXP.variable && e.isVarExp().var == fd.vthis)
         e = thisarg;
-    if (tf.isref && fd.isThis2 && e.op == TOK.index)
+    if (tf.isref && fd.hasDualContext() && e.op == EXP.index)
     {
         auto ie = e.isIndexExp();
         auto pe = ie.e1.isPtrExp();
@@ -686,7 +708,6 @@ private Expression interpretFunction(UnionExp* pue, FuncDeclaration fd, InterSta
             }
         }
     }
-    assert(e !is null);
 
     // Leave the function
     --ctfeGlobals.callDepth;
@@ -733,14 +754,14 @@ public:
         this.goal = goal;
     }
 
-    // If e is TOK.throw_exception or TOK.cantExpression,
+    // If e is EXP.throw_exception or EXP.cantExpression,
     // set it to 'result' and returns true.
     bool exceptionOrCant(Expression e)
     {
         if (exceptionOrCantInterpret(e))
         {
             // Make sure e is not pointing to a stack temporary
-            result = (e.op == TOK.cantExpression) ? CTFEExp.cantexp : e;
+            result = (e.op == EXP.cantExpression) ? CTFEExp.cantexp : e;
             return true;
         }
         return false;
@@ -807,7 +828,7 @@ public:
         if (istate.start == s)
             istate.start = null;
 
-        const dim = s.statements ? s.statements.dim : 0;
+        const dim = s.statements ? s.statements.length : 0;
         foreach (i; 0 .. dim)
         {
             Statement sx = (*s.statements)[i];
@@ -830,7 +851,7 @@ public:
         if (istate.start == s)
             istate.start = null;
 
-        const dim = s.statements ? s.statements.dim : 0;
+        const dim = s.statements ? s.statements.length : 0;
         foreach (i; 0 .. dim)
         {
             Statement sx = (*s.statements)[i];
@@ -839,7 +860,7 @@ public:
                 continue;
             if (exceptionOrCant(e))
                 return;
-            if (e.op == TOK.break_)
+            if (e.op == EXP.break_)
             {
                 if (istate.gotoTarget && istate.gotoTarget != s)
                 {
@@ -850,7 +871,7 @@ public:
                 result = null;
                 return;
             }
-            if (e.op == TOK.continue_)
+            if (e.op == EXP.continue_)
             {
                 if (istate.gotoTarget && istate.gotoTarget != s)
                 {
@@ -894,7 +915,7 @@ public:
 
         if (isTrueBool(e))
             result = interpret(pue, s.ifbody, istate);
-        else if (e.isBool(false))
+        else if (e.toBool().hasValue(false))
             result = interpret(pue, s.elsebody, istate);
         else
         {
@@ -935,7 +956,7 @@ public:
             if (auto eaddr = e.isAddrExp())
                 x = eaddr.e1;
             VarDeclaration v;
-            while (x.op == TOK.variable && (v = (cast(VarExp)x).var.isVarDeclaration()) !is null)
+            while (x.op == EXP.variable && (v = x.isVarExp().var.isVarDeclaration()) !is null)
             {
                 if (v.storage_class & STC.ref_)
                 {
@@ -952,7 +973,7 @@ public:
                 else
                     break;
             }
-            // TODO: If it is a TOK.dotVariable or TOK.index, we should check that it is not
+            // TODO: If it is a EXP.dotVariable or EXP.index, we should check that it is not
             // pointing to a local struct or static array.
         }
         if (auto se = e.isStructLiteralExp())
@@ -1014,7 +1035,7 @@ public:
             result = interpret(pue, s.exp, istate, CTFEGoal.LValue);
             return;
         }
-        if (tf.next && tf.next.ty == Tdelegate && istate.fd.closureVars.dim > 0)
+        if (tf.next && tf.next.ty == Tdelegate && istate.fd.closureVars.length > 0)
         {
             // To support this, we need to copy all the closure vars
             // into the delegate literal.
@@ -1023,12 +1044,27 @@ public:
             return;
         }
 
-        // We need to treat pointers specially, because TOK.symbolOffset can be used to
+        // We need to treat pointers specially, because EXP.symbolOffset can be used to
         // return a value OR a pointer
         Expression e = interpret(pue, s.exp, istate);
         if (exceptionOrCant(e))
             return;
 
+        /**
+         * Interpret `return a ~= b` (i.e. `return _d_arrayappendT{,Trace}(a, b)`) as:
+         *     a ~= b;
+         *     return a;
+         * This is needed because `a ~= b` has to be interpreted as an lvalue, in order to avoid
+         * assigning a larger array into a smaller one, such as:
+         *    `a = [1, 2], a ~= [3]` => `[1, 2] ~= [3]` => `[1, 2] = [1, 2, 3]`
+         */
+        if (isRuntimeHook(s.exp, Id._d_arrayappendT) || isRuntimeHook(s.exp, Id._d_arrayappendTTrace))
+        {
+            auto rs = new ReturnStatement(s.loc, e);
+            rs.accept(this);
+            return;
+        }
+
         // Disallow returning pointers to stack-allocated variables (bug 7876)
         if (!stopPointersEscaping(s.loc, e))
         {
@@ -1122,7 +1158,7 @@ public:
 
             if (exceptionOrCant(e))
                 return;
-            if (e && e.op == TOK.break_)
+            if (e && e.op == EXP.break_)
             {
                 if (istate.gotoTarget && istate.gotoTarget != s)
                 {
@@ -1132,7 +1168,7 @@ public:
                 istate.gotoTarget = null;
                 break;
             }
-            if (e && e.op == TOK.continue_)
+            if (e && e.op == EXP.continue_)
             {
                 if (istate.gotoTarget && istate.gotoTarget != s)
                 {
@@ -1158,7 +1194,7 @@ public:
                 result = CTFEExp.cantexp;
                 return;
             }
-            if (e.isBool(false))
+            if (e.toBool().hasValue(false))
                 break;
             assert(isTrueBool(e));
         }
@@ -1189,7 +1225,7 @@ public:
                 Expression e = interpret(&ue, s.condition, istate);
                 if (exceptionOrCant(e))
                     return;
-                if (e.isBool(false))
+                if (e.toBool().hasValue(false))
                     break;
                 assert(isTrueBool(e));
             }
@@ -1201,7 +1237,7 @@ public:
 
             if (exceptionOrCant(e))
                 return;
-            if (e && e.op == TOK.break_)
+            if (e && e.op == EXP.break_)
             {
                 if (istate.gotoTarget && istate.gotoTarget != s)
                 {
@@ -1211,7 +1247,7 @@ public:
                 istate.gotoTarget = null;
                 break;
             }
-            if (e && e.op == TOK.continue_)
+            if (e && e.op == EXP.continue_)
             {
                 if (istate.gotoTarget && istate.gotoTarget != s)
                 {
@@ -1263,7 +1299,7 @@ public:
                 return;
             if (exceptionOrCant(e))
                 return;
-            if (e && e.op == TOK.break_)
+            if (e && e.op == EXP.break_)
             {
                 if (istate.gotoTarget && istate.gotoTarget != s)
                 {
@@ -1290,7 +1326,7 @@ public:
                 Expression ecase = interpret(&uecase, cs.exp, istate);
                 if (exceptionOrCant(ecase))
                     return;
-                if (ctfeEqual(cs.exp.loc, TOK.equal, econdition, ecase))
+                if (ctfeEqual(cs.exp.loc, EXP.equal, econdition, ecase))
                 {
                     scase = cs;
                     break;
@@ -1310,7 +1346,7 @@ public:
         istate.start = scase;
         Expression e = interpret(pue, s._body, istate);
         assert(!istate.start); // jump must not fail
-        if (e && e.op == TOK.break_)
+        if (e && e.op == EXP.break_)
         {
             if (istate.gotoTarget && istate.gotoTarget != s)
             {
@@ -1486,11 +1522,6 @@ public:
         result = e;
     }
 
-    static bool isAnErrorException(ClassDeclaration cd)
-    {
-        return cd == ClassDeclaration.errorException || ClassDeclaration.errorException.isBaseOf(cd, null);
-    }
-
     static ThrownExceptionExp chainExceptions(ThrownExceptionExp oldest, ThrownExceptionExp newest)
     {
         debug (LOG)
@@ -1499,10 +1530,10 @@ public:
         }
         // Little sanity check to make sure it's really a Throwable
         ClassReferenceExp boss = oldest.thrown;
-        const next = 4;                         // index of Throwable.next
+        const next = 5;                         // index of Throwable.next
         assert((*boss.value.elements)[next].type.ty == Tclass); // Throwable.next
         ClassReferenceExp collateral = newest.thrown;
-        if (isAnErrorException(collateral.originalClass()) && !isAnErrorException(boss.originalClass()))
+        if (collateral.originalClass().isErrorException() && !boss.originalClass().isErrorException())
         {
             /* Find the index of the Error.bypassException field
              */
@@ -1515,9 +1546,9 @@ public:
             (*collateral.value.elements)[bypass] = boss;
             return newest;
         }
-        while ((*boss.value.elements)[next].op == TOK.classReference)
+        while ((*boss.value.elements)[next].op == EXP.classReference)
         {
-            boss = cast(ClassReferenceExp)(*boss.value.elements)[next];
+            boss = (*boss.value.elements)[next].isClassReferenceExp();
         }
         (*boss.value.elements)[next] = collateral;
         return oldest;
@@ -1600,14 +1631,27 @@ public:
             istate.start = null;
         }
 
-        incUsageCtfe(istate, s.loc);
+        interpretThrow(s.exp, s.loc);
+    }
+
+    /// Interpret `throw <exp>` found at the specified location `loc`
+    private void interpretThrow(Expression exp, const ref Loc loc)
+    {
+        incUsageCtfe(istate, loc);
 
-        Expression e = interpretRegion(s.exp, istate);
+        Expression e = interpretRegion(exp, istate);
         if (exceptionOrCant(e))
             return;
 
-        assert(e.op == TOK.classReference);
-        result = ctfeEmplaceExp!ThrownExceptionExp(s.loc, e.isClassReferenceExp());
+        if (e.op == EXP.classReference)
+        {
+            result = ctfeEmplaceExp!ThrownExceptionExp(loc, e.isClassReferenceExp());
+        }
+        else
+        {
+            exp.error("to be thrown `%s` must be non-null", exp.toChars());
+            result = ErrorExp.get();
+        }
     }
 
     override void visit(ScopeGuardStatement s)
@@ -1630,7 +1674,7 @@ public:
         }
 
         // If it is with(Enum) {...}, just execute the body.
-        if (s.exp.op == TOK.scope_ || s.exp.op == TOK.type)
+        if (s.exp.op == EXP.scope_ || s.exp.op == EXP.type)
         {
             result = interpret(pue, s._body, istate);
             return;
@@ -1707,7 +1751,7 @@ public:
     {
         debug (LOG)
         {
-            printf("%s Expression::interpret() '%s' %s\n", e.loc.toChars(), Token.toChars(e.op), e.toChars());
+            printf("%s Expression::interpret() '%s' %s\n", e.loc.toChars(), EXPtoString(e.op).ptr, e.toChars());
             printf("type = %s\n", e.type.toChars());
             showCtfeExpr(e);
         }
@@ -1737,7 +1781,7 @@ public:
             if (istate && istate.fd.vthis)
             {
                 result = ctfeEmplaceExp!VarExp(e.loc, istate.fd.vthis);
-                if (istate.fd.isThis2)
+                if (istate.fd.hasDualContext())
                 {
                     result = ctfeEmplaceExp!PtrExp(e.loc, result);
                     result.type = Type.tvoidptr.sarrayOf(2);
@@ -1753,19 +1797,19 @@ public:
         result = ctfeGlobals.stack.getThis();
         if (result)
         {
-            if (istate && istate.fd.isThis2)
+            if (istate && istate.fd.hasDualContext())
             {
-                assert(result.op == TOK.address);
-                result = (cast(AddrExp)result).e1;
-                assert(result.op == TOK.arrayLiteral);
-                result = (*(cast(ArrayLiteralExp)result).elements)[0];
+                assert(result.op == EXP.address);
+                result = result.isAddrExp().e1;
+                assert(result.op == EXP.arrayLiteral);
+                result = (*result.isArrayLiteralExp().elements)[0];
                 if (e.type.ty == Tstruct)
                 {
-                    result = (cast(AddrExp)result).e1;
+                    result = result.isAddrExp().e1;
                 }
                 return;
             }
-            assert(result.op == TOK.structLiteral || result.op == TOK.classReference || result.op == TOK.type);
+            assert(result.op == EXP.structLiteral || result.op == EXP.classReference || result.op == EXP.type);
             return;
         }
         e.error("value of `this` is not known at compile time");
@@ -1806,10 +1850,27 @@ public:
         {
             printf("%s StringExp::interpret() %s\n", e.loc.toChars(), e.toChars());
         }
-        /* Attempts to modify string literals are prevented
-         * in BinExp::interpretAssignCommon.
-         */
-        result = e;
+        if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted the string
+        {
+            result = e;
+            return;
+        }
+
+        if (e.type.ty != Tsarray ||
+            (cast(TypeNext)e.type).next.mod & (MODFlags.const_ | MODFlags.immutable_))
+        {
+            // If it's immutable, we don't need to dup it. Attempts to modify
+            // string literals are prevented in BinExp::interpretAssignCommon.
+            result = e;
+        }
+        else
+        {
+            // https://issues.dlang.org/show_bug.cgi?id=20811
+            // Create a copy of mutable string literals, so that any change in
+            // value via an index or slice will not survive CTFE.
+            *pue = copyLiteral(e);
+            result = pue.exp();
+        }
     }
 
     override void visit(FuncExp e)
@@ -1858,7 +1919,9 @@ public:
         {
             fromType = (cast(TypeArray)e.var.type).next;
         }
-        if (e.var.isDataseg() && ((e.offset == 0 && isSafePointerCast(e.var.type, pointee)) || (fromType && isSafePointerCast(fromType, pointee))))
+        if (e.var.isDataseg() && ((e.offset == 0 && isSafePointerCast(e.var.type, pointee)) ||
+                                  (fromType && isSafePointerCast(fromType, pointee)) ||
+                                  (e.var.isCsymbol() && e.offset + pointee.size() <= e.var.type.size())))
         {
             result = e;
             return;
@@ -1871,7 +1934,7 @@ public:
         {
             // Check for unsupported type painting operations
             Type elemtype = (cast(TypeArray)val.type).next;
-            d_uns64 elemsize = elemtype.size();
+            const elemsize = elemtype.size();
 
             // It's OK to cast from fixed length to fixed length array, eg &int[n] to int[d]*.
             if (val.type.ty == Tsarray && pointee.ty == Tsarray && elemsize == pointee.nextOf().size())
@@ -1909,7 +1972,7 @@ public:
             dinteger_t indx = e.offset / sz;
             assert(sz * indx == e.offset);
             Expression aggregate = null;
-            if (val.op == TOK.arrayLiteral || val.op == TOK.string_)
+            if (val.op == EXP.arrayLiteral || val.op == EXP.string_)
             {
                 aggregate = val;
             }
@@ -1965,7 +2028,7 @@ public:
             if (decl.isDataseg()) {
                 // Normally this is already done by optimize()
                 // Do it here in case optimize(WANTvalue) wasn't run before CTFE
-                emplaceExp!(SymOffExp)(pue, e.loc, (cast(VarExp)e.e1).var, 0);
+                emplaceExp!(SymOffExp)(pue, e.loc, e.e1.isVarExp().var, 0);
                 result = pue.exp();
                 result.type = e.type;
                 return;
@@ -2054,13 +2117,13 @@ public:
                     return CTFEExp.cantexp;
                 assert(e.type);
 
-                if (e.op == TOK.construct || e.op == TOK.blit)
+                if (e.op == EXP.construct || e.op == EXP.blit)
                 {
                     AssignExp ae = cast(AssignExp)e;
                     e = ae.e2;
                 }
 
-                if (e.op == TOK.error)
+                if (e.op == EXP.error)
                 {
                     // FIXME: Ultimately all errors should be detected in prior semantic analysis stage.
                 }
@@ -2142,12 +2205,20 @@ public:
         }
         else if (SymbolDeclaration s = d.isSymbolDeclaration())
         {
+            // exclude void[]-typed `__traits(initSymbol)`
+            if (auto ta = s.type.toBasetype().isTypeDArray())
+            {
+                assert(ta.next.ty == Tvoid);
+                error(loc, "cannot determine the address of the initializer symbol during CTFE");
+                return CTFEExp.cantexp;
+            }
+
             // Struct static initializers, for example
             e = s.dsym.type.defaultInitLiteral(loc);
-            if (e.op == TOK.error)
+            if (e.op == EXP.error)
                 error(loc, "CTFE failed because of previous errors in `%s.init`", s.toChars());
             e = e.expressionSemantic(null);
-            if (e.op == TOK.error)
+            if (e.op == EXP.error)
                 e = CTFEExp.cantexp;
             else // Convert NULL to CTFEExp
                 e = interpret(e, istate, goal);
@@ -2195,10 +2266,10 @@ public:
                 {
                     // Strip off the nest of ref variables
                     Expression ev = getValue(v);
-                    if (ev.op == TOK.variable ||
-                        ev.op == TOK.index ||
-                        (ev.op == TOK.slice && ev.type.toBasetype().ty == Tsarray) ||
-                        ev.op == TOK.dotVariable)
+                    if (ev.op == EXP.variable ||
+                        ev.op == EXP.index ||
+                        (ev.op == EXP.slice && ev.type.toBasetype().ty == Tsarray) ||
+                        ev.op == EXP.dotVariable)
                     {
                         result = interpret(pue, ev, istate, goal);
                         return;
@@ -2245,7 +2316,7 @@ public:
         while (s.isAttribDeclaration())
         {
             auto ad = cast(AttribDeclaration)s;
-            assert(ad.decl && ad.decl.dim == 1); // Currently, only one allowed when parsing
+            assert(ad.decl && ad.decl.length == 1); // Currently, only one allowed when parsing
             s = (*ad.decl)[0];
         }
         if (VarDeclaration v = s.isVarDeclaration())
@@ -2255,16 +2326,12 @@ public:
                 result = null;
 
                 // Reserve stack space for all tuple members
-                if (!td.objects)
-                    return;
-                foreach (o; *td.objects)
+                td.foreachVar((s)
                 {
-                    Expression ex = isExpression(o);
-                    DsymbolExp ds = ex ? ex.isDsymbolExp() : null;
-                    VarDeclaration v2 = ds ? ds.s.isVarDeclaration() : null;
+                    VarDeclaration v2 = s.isVarDeclaration();
                     assert(v2);
                     if (v2.isDataseg() && !v2.isCTFE())
-                        continue;
+                        return 0;
 
                     ctfeGlobals.stack.push(v2);
                     if (v2._init)
@@ -2274,7 +2341,7 @@ public:
                         {
                             einit = interpretRegion(ie.exp, istate, goal);
                             if (exceptionOrCant(einit))
-                                return;
+                                return 1;
                         }
                         else if (v2._init.isVoidInitializer())
                         {
@@ -2284,11 +2351,12 @@ public:
                         {
                             e.error("declaration `%s` is not yet implemented in CTFE", e.toChars());
                             result = CTFEExp.cantexp;
-                            return;
+                            return 1;
                         }
                         setValue(v2, einit);
                     }
-                }
+                    return 0;
+                });
                 return;
             }
             if (v.isStatic())
@@ -2304,6 +2372,7 @@ public:
                 if (ExpInitializer ie = v._init.isExpInitializer())
                 {
                     result = interpretRegion(ie.exp, istate, goal);
+                    return;
                 }
                 else if (v._init.isVoidInitializer())
                 {
@@ -2311,12 +2380,16 @@ public:
                     // There is no AssignExp for void initializers,
                     // so set it here.
                     setValue(v, result);
+                    return;
                 }
-                else
+                else if (v._init.isArrayInitializer())
                 {
-                    e.error("declaration `%s` is not yet implemented in CTFE", e.toChars());
-                    result = CTFEExp.cantexp;
+                    result = v._init.initializerToExpression(v.type);
+                    if (result !is null)
+                        return;
                 }
+                e.error("declaration `%s` is not yet implemented in CTFE", e.toChars());
+                result = CTFEExp.cantexp;
             }
             else if (v.type.size() == 0)
             {
@@ -2363,20 +2436,20 @@ public:
             if (exceptionOrCant(ex))
                 return;
 
-            if (result.op == TOK.null_)
+            if (result.op == EXP.null_)
             {
                 e.error("null pointer dereference evaluating typeid. `%s` is `null`", ex.toChars());
                 result = CTFEExp.cantexp;
                 return;
             }
-            if (result.op != TOK.classReference)
+            if (result.op != EXP.classReference)
             {
                 e.error("CTFE internal error: determining classinfo");
                 result = CTFEExp.cantexp;
                 return;
             }
 
-            ClassDeclaration cd = (cast(ClassReferenceExp)result).originalClass();
+            ClassDeclaration cd = result.isClassReferenceExp().originalClass();
             assert(cd);
 
             emplaceExp!(TypeidExp)(pue, e.loc, cd.type);
@@ -2406,7 +2479,7 @@ public:
             // A tuple of assignments can contain void (Bug 5676).
             if (goal == CTFEGoal.Nothing)
                 continue;
-            if (ex.op == TOK.voidExpression)
+            if (ex.op == EXP.voidExpression)
             {
                 e.error("CTFE internal error: void element `%s` in tuple", exp.toChars());
                 assert(0);
@@ -2452,7 +2525,7 @@ public:
             return;
 
         auto expsx = e.elements;
-        size_t dim = expsx ? expsx.dim : 0;
+        size_t dim = expsx ? expsx.length : 0;
         for (size_t i = 0; i < dim; i++)
         {
             Expression exp = (*expsx)[i];
@@ -2464,7 +2537,7 @@ public:
             else
             {
                 // segfault bug 6250
-                assert(exp.op != TOK.index || (cast(IndexExp)exp).e1 != e);
+                assert(exp.op != EXP.index || exp.isIndexExp().e1 != e);
 
                 ex = interpretRegion(exp, istate);
                 if (exceptionOrCant(ex))
@@ -2491,14 +2564,14 @@ public:
         {
             // todo: all tuple expansions should go in semantic phase.
             expandTuples(expsx);
-            if (expsx.dim != dim)
+            if (expsx.length != dim)
             {
                 e.error("CTFE internal error: invalid array literal");
                 result = CTFEExp.cantexp;
                 return;
             }
             emplaceExp!(ArrayLiteralExp)(pue, e.loc, e.type, basis, expsx);
-            auto ale = cast(ArrayLiteralExp)pue.exp();
+            auto ale = pue.exp().isArrayLiteralExp();
             ale.ownedByCtfe = OwnedBy.ctfe;
             result = ale;
         }
@@ -2554,7 +2627,7 @@ public:
             expandTuples(keysx);
         if (valuesx !is e.values)
             expandTuples(valuesx);
-        if (keysx.dim != valuesx.dim)
+        if (keysx.length != valuesx.length)
         {
             e.error("CTFE internal error: invalid AA");
             result = CTFEExp.cantexp;
@@ -2563,13 +2636,13 @@ public:
 
         /* Remove duplicate keys
          */
-        for (size_t i = 1; i < keysx.dim; i++)
+        for (size_t i = 1; i < keysx.length; i++)
         {
             auto ekey = (*keysx)[i - 1];
-            for (size_t j = i; j < keysx.dim; j++)
+            for (size_t j = i; j < keysx.length; j++)
             {
                 auto ekey2 = (*keysx)[j];
-                if (!ctfeEqual(e.loc, TOK.equal, ekey, ekey2))
+                if (!ctfeEqual(e.loc, EXP.equal, ekey, ekey2))
                     continue;
 
                 // Remove ekey
@@ -2612,14 +2685,14 @@ public:
             return;
         }
 
-        size_t dim = e.elements ? e.elements.dim : 0;
+        size_t dim = e.elements ? e.elements.length : 0;
         auto expsx = e.elements;
 
-        if (dim != e.sd.fields.dim)
+        if (dim != e.sd.fields.length)
         {
             // guaranteed by AggregateDeclaration.fill and TypeStruct.defaultInitLiteral
-            const nvthis = e.sd.fields.dim - e.sd.nonHiddenFields();
-            assert(e.sd.fields.dim - dim == nvthis);
+            const nvthis = e.sd.fields.length - e.sd.nonHiddenFields();
+            assert(e.sd.fields.length - dim == nvthis);
 
             /* If a nested struct has no initialized hidden pointer,
              * set it to null to match the runtime behaviour.
@@ -2635,7 +2708,7 @@ public:
                 ++dim;
             }
         }
-        assert(dim == e.sd.fields.dim);
+        assert(dim == e.sd.fields.length);
 
         foreach (i; 0 .. dim)
         {
@@ -2675,14 +2748,14 @@ public:
         if (expsx !is e.elements)
         {
             expandTuples(expsx);
-            if (expsx.dim != e.sd.fields.dim)
+            if (expsx.length != e.sd.fields.length)
             {
                 e.error("CTFE internal error: invalid struct literal");
                 result = CTFEExp.cantexp;
                 return;
             }
             emplaceExp!(StructLiteralExp)(pue, e.loc, e.sd, expsx);
-            auto sle = cast(StructLiteralExp)pue.exp();
+            auto sle = pue.exp().isStructLiteralExp();
             sle.type = e.type;
             sle.ownedByCtfe = OwnedBy.ctfe;
             sle.origin = e.origin;
@@ -2704,7 +2777,7 @@ public:
             return lenExpr;
         size_t len = cast(size_t)lenExpr.toInteger();
         Type elemType = (cast(TypeArray)newtype).next;
-        if (elemType.ty == Tarray && argnum < arguments.dim - 1)
+        if (elemType.ty == Tarray && argnum < arguments.length - 1)
         {
             Expression elem = recursivelyCreateArrayLiteral(pue, loc, elemType, istate, arguments, argnum + 1);
             if (exceptionOrCantInterpret(elem))
@@ -2714,11 +2787,11 @@ public:
             foreach (ref element; *elements)
                 element = copyLiteral(elem).copy();
             emplaceExp!(ArrayLiteralExp)(pue, loc, newtype, elements);
-            auto ae = cast(ArrayLiteralExp)pue.exp();
+            auto ae = pue.exp().isArrayLiteralExp();
             ae.ownedByCtfe = OwnedBy.ctfe;
             return ae;
         }
-        assert(argnum == arguments.dim - 1);
+        assert(argnum == arguments.length - 1);
         if (elemType.ty.isSomeChar)
         {
             const ch = cast(dchar)elemType.defaultInitLiteral(loc).toInteger();
@@ -2765,10 +2838,10 @@ public:
             {
                 StructDeclaration sd = ts.sym;
                 auto exps = new Expressions();
-                exps.reserve(sd.fields.dim);
+                exps.reserve(sd.fields.length);
                 if (e.arguments)
                 {
-                    exps.setDim(e.arguments.dim);
+                    exps.setDim(e.arguments.length);
                     foreach (i, ex; *e.arguments)
                     {
                         ex = interpretRegion(ex, istate);
@@ -2777,7 +2850,7 @@ public:
                         (*exps)[i] = ex;
                     }
                 }
-                sd.fill(e.loc, exps, false);
+                sd.fill(e.loc, *exps, false);
 
                 auto se = ctfeEmplaceExp!StructLiteralExp(e.loc, sd, exps, e.newtype);
                 se.origin = se;
@@ -2797,12 +2870,12 @@ public:
             ClassDeclaration cd = tc.sym;
             size_t totalFieldCount = 0;
             for (ClassDeclaration c = cd; c; c = c.baseClass)
-                totalFieldCount += c.fields.dim;
+                totalFieldCount += c.fields.length;
             auto elems = new Expressions(totalFieldCount);
             size_t fieldsSoFar = totalFieldCount;
             for (ClassDeclaration c = cd; c; c = c.baseClass)
             {
-                fieldsSoFar -= c.fields.dim;
+                fieldsSoFar -= c.fields.length;
                 foreach (i, v; c.fields)
                 {
                     if (v.inuse)
@@ -2819,6 +2892,12 @@ public:
                         else
                             m = v.getConstInitializer(true);
                     }
+                    else if (v.type.isTypeNoreturn())
+                    {
+                        // Noreturn field with default initializer
+                        (*elems)[fieldsSoFar + i] = null;
+                        continue;
+                    }
                     else
                         m = v.type.defaultInitLiteral(e.loc);
                     if (exceptionOrCant(m))
@@ -2868,7 +2947,7 @@ public:
         if (e.newtype.toBasetype().isscalar())
         {
             Expression newval;
-            if (e.arguments && e.arguments.dim)
+            if (e.arguments && e.arguments.length)
                 newval = (*e.arguments)[0];
             else
                 newval = e.newtype.defaultInitLiteral(e.loc);
@@ -2904,15 +2983,15 @@ public:
             return;
         switch (e.op)
         {
-        case TOK.negate:
+        case EXP.negate:
             *pue = Neg(e.type, e1);
             break;
 
-        case TOK.tilde:
+        case EXP.tilde:
             *pue = Com(e.type, e1);
             break;
 
-        case TOK.not:
+        case EXP.not:
             *pue = Not(e.type, e1);
             break;
 
@@ -2936,7 +3015,7 @@ public:
             result = e; // optimize: reuse this CTFE reference
         else
         {
-            auto edt = cast(DotTypeExp)e.copy();
+            auto edt = e.copy().isDotTypeExp();
             edt.e1 = (e1 == ue.exp()) ? e1.copy() : e1; // don't return pointer to ue
             result = edt;
         }
@@ -2948,7 +3027,7 @@ public:
         {
             printf("%s BinExp::interpretCommon() %s\n", e.loc.toChars(), e.toChars());
         }
-        if (e.e1.type.ty == Tpointer && e.e2.type.ty == Tpointer && e.op == TOK.min)
+        if (e.e1.type.ty == Tpointer && e.e2.type.ty == Tpointer && e.op == EXP.min)
         {
             UnionExp ue1 = void;
             Expression e1 = interpret(&ue1, e.e1, istate);
@@ -2958,8 +3037,7 @@ public:
             Expression e2 = interpret(&ue2, e.e2, istate);
             if (exceptionOrCant(e2))
                 return;
-            *pue = pointerDifference(e.loc, e.type, e1, e2);
-            result = (*pue).exp();
+            result = pointerDifference(pue, e.loc, e.type, e1, e2);
             return;
         }
         if (e.e1.type.ty == Tpointer && e.e2.type.isintegral())
@@ -2972,11 +3050,10 @@ public:
             Expression e2 = interpret(&ue2, e.e2, istate);
             if (exceptionOrCant(e2))
                 return;
-            *pue = pointerArithmetic(e.loc, e.op, e.type, e1, e2);
-            result = (*pue).exp();
+            result = pointerArithmetic(pue, e.loc, e.op, e.type, e1, e2);
             return;
         }
-        if (e.e2.type.ty == Tpointer && e.e1.type.isintegral() && e.op == TOK.add)
+        if (e.e2.type.ty == Tpointer && e.e1.type.isintegral() && e.op == EXP.add)
         {
             UnionExp ue1 = void;
             Expression e1 = interpret(&ue1, e.e1, istate);
@@ -2986,8 +3063,7 @@ public:
             Expression e2 = interpret(&ue2, e.e2, istate);
             if (exceptionOrCant(e2))
                 return;
-            *pue = pointerArithmetic(e.loc, e.op, e.type, e2, e1);
-            result = (*pue).exp();
+            result = pointerArithmetic(pue, e.loc, e.op, e.type, e2, e1);
             return;
         }
         if (e.e1.type.ty == Tpointer || e.e2.type.ty == Tpointer)
@@ -3015,10 +3091,10 @@ public:
         if (!evalOperand(&ue2, e.e2, e2))
             return;
 
-        if (e.op == TOK.rightShift || e.op == TOK.leftShift || e.op == TOK.unsignedRightShift)
+        if (e.op == EXP.rightShift || e.op == EXP.leftShift || e.op == EXP.unsignedRightShift)
         {
             const sinteger_t i2 = e2.toInteger();
-            const d_uns64 sz = e1.type.size() * 8;
+            const uinteger_t sz = e1.type.size() * 8;
             if (i2 < 0 || i2 >= sz)
             {
                 e.error("shift by %lld is outside the range 0..%llu", i2, cast(ulong)sz - 1);
@@ -3073,13 +3149,13 @@ public:
             {
                 // The following should really be an assert()
                 e1.error("CTFE internal error: non-constant value `%s`", e1.toChars());
-                emplaceExp!CTFEExp(&ue, TOK.cantExpression);
+                emplaceExp!CTFEExp(&ue, EXP.cantExpression);
                 return ue;
             }
             if (e2.isConst() != 1)
             {
                 e2.error("CTFE internal error: non-constant value `%s`", e2.toChars());
-                emplaceExp!CTFEExp(&ue, TOK.cantExpression);
+                emplaceExp!CTFEExp(&ue, EXP.cantExpression);
                 return ue;
             }
 
@@ -3116,7 +3192,7 @@ public:
             const cmp = comparePointers(e.op, agg1, ofs1, agg2, ofs2);
             if (cmp == -1)
             {
-                char dir = (e.op == TOK.greaterThan || e.op == TOK.greaterOrEqual) ? '<' : '>';
+                char dir = (e.op == EXP.greaterThan || e.op == EXP.greaterOrEqual) ? '<' : '>';
                 e.error("the ordering of pointers to unrelated memory blocks is indeterminate in CTFE. To check if they point to the same memory block, use both `>` and `<` inside `&&` or `||`, eg `%s && %s %c= %s + 1`", e.toChars(), e.e1.toChars(), dir, e.e2.toChars());
                 result = CTFEExp.cantexp;
                 return;
@@ -3162,73 +3238,73 @@ public:
     {
         switch (e.op)
         {
-        case TOK.add:
+        case EXP.add:
             interpretCommon(e, &Add);
             return;
 
-        case TOK.min:
+        case EXP.min:
             interpretCommon(e, &Min);
             return;
 
-        case TOK.mul:
+        case EXP.mul:
             interpretCommon(e, &Mul);
             return;
 
-        case TOK.div:
+        case EXP.div:
             interpretCommon(e, &Div);
             return;
 
-        case TOK.mod:
+        case EXP.mod:
             interpretCommon(e, &Mod);
             return;
 
-        case TOK.leftShift:
+        case EXP.leftShift:
             interpretCommon(e, &Shl);
             return;
 
-        case TOK.rightShift:
+        case EXP.rightShift:
             interpretCommon(e, &Shr);
             return;
 
-        case TOK.unsignedRightShift:
+        case EXP.unsignedRightShift:
             interpretCommon(e, &Ushr);
             return;
 
-        case TOK.and:
+        case EXP.and:
             interpretCommon(e, &And);
             return;
 
-        case TOK.or:
+        case EXP.or:
             interpretCommon(e, &Or);
             return;
 
-        case TOK.xor:
+        case EXP.xor:
             interpretCommon(e, &Xor);
             return;
 
-        case TOK.pow:
+        case EXP.pow:
             interpretCommon(e, &Pow);
             return;
 
-        case TOK.equal:
-        case TOK.notEqual:
+        case EXP.equal:
+        case EXP.notEqual:
             interpretCompareCommon(e, &ctfeEqual);
             return;
 
-        case TOK.identity:
-        case TOK.notIdentity:
+        case EXP.identity:
+        case EXP.notIdentity:
             interpretCompareCommon(e, &ctfeIdentity);
             return;
 
-        case TOK.lessThan:
-        case TOK.lessOrEqual:
-        case TOK.greaterThan:
-        case TOK.greaterOrEqual:
+        case EXP.lessThan:
+        case EXP.lessOrEqual:
+        case EXP.greaterThan:
+        case EXP.greaterOrEqual:
             interpretCompareCommon(e, &ctfeCmp);
             return;
 
         default:
-            printf("be = '%s' %s at [%s]\n", Token.toChars(e.op), e.toChars(), e.loc.toChars());
+            printf("be = '%s' %s at [%s]\n", EXPtoString(e.op).ptr, e.toChars(), e.loc.toChars());
             assert(0);
         }
     }
@@ -3286,7 +3362,7 @@ public:
          * So we need to recurse to determine if it is a block assignment.
          */
         bool isBlockAssignment = false;
-        if (e1.op == TOK.slice)
+        if (e1.op == EXP.slice)
         {
             // a[] = e can have const e. So we compare the naked types.
             Type tdst = e1.type.toBasetype();
@@ -3306,7 +3382,7 @@ public:
         //      Deal with reference assignment
         // ---------------------------------------
         // If it is a construction of a ref variable, it is a ref assignment
-        if ((e.op == TOK.construct || e.op == TOK.blit) &&
+        if ((e.op == EXP.construct || e.op == EXP.blit) &&
             ((cast(AssignExp)e).memset == MemorySet.referenceInit))
         {
             assert(!fp);
@@ -3315,7 +3391,7 @@ public:
             if (exceptionOrCant(newval))
                 return;
 
-            VarDeclaration v = (cast(VarExp)e1).var.isVarDeclaration();
+            VarDeclaration v = e1.isVarExp().var.isVarDeclaration();
             setValue(v, newval);
 
             // Get the value to return. Note that 'newval' is an Lvalue,
@@ -3329,9 +3405,9 @@ public:
 
         if (fp)
         {
-            while (e1.op == TOK.cast_)
+            while (e1.op == EXP.cast_)
             {
-                CastExp ce = cast(CastExp)e1;
+                CastExp ce = e1.isCastExp();
                 e1 = ce.e1;
             }
         }
@@ -3342,7 +3418,7 @@ public:
         AssocArrayLiteralExp existingAA = null;
         Expression lastIndex = null;
         Expression oldval = null;
-        if (e1.op == TOK.index && (cast(IndexExp)e1).e1.type.toBasetype().ty == Taarray)
+        if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
         {
             // ---------------------------------------
             //      Deal with AA index assignment
@@ -3354,12 +3430,12 @@ public:
              * (2) If the ultimate AA is null, no insertion happens at all. Instead,
              *     we create nested AA literals, and change it into a assignment.
              */
-            IndexExp ie = cast(IndexExp)e1;
+            IndexExp ie = e1.isIndexExp();
             int depth = 0; // how many nested AA indices are there?
-            while (ie.e1.op == TOK.index && (cast(IndexExp)ie.e1).e1.type.toBasetype().ty == Taarray)
+            while (ie.e1.op == EXP.index && ie.e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
             {
                 assert(ie.modifiable);
-                ie = cast(IndexExp)ie.e1;
+                ie = ie.e1.isIndexExp();
                 ++depth;
             }
 
@@ -3372,7 +3448,7 @@ public:
                 // Normal case, ultimate parent AA already exists
                 // We need to walk from the deepest index up, checking that an AA literal
                 // already exists on each level.
-                lastIndex = interpretRegion((cast(IndexExp)e1).e2, istate);
+                lastIndex = interpretRegion(e1.isIndexExp().e2, istate);
                 lastIndex = resolveSlice(lastIndex); // only happens with AA assignment
                 if (exceptionOrCant(lastIndex))
                     return;
@@ -3380,9 +3456,9 @@ public:
                 while (depth > 0)
                 {
                     // Walk the syntax tree to find the indexExp at this depth
-                    IndexExp xe = cast(IndexExp)e1;
+                    IndexExp xe = e1.isIndexExp();
                     foreach (d; 0 .. depth)
-                        xe = cast(IndexExp)xe.e1;
+                        xe = xe.e1.isIndexExp();
 
                     Expression ekey = interpretRegion(xe.e2, istate);
                     if (exceptionOrCant(ekey))
@@ -3430,9 +3506,9 @@ public:
                 oldval = copyLiteral(e.e1.type.defaultInitLiteral(e.loc)).copy();
 
                 Expression newaae = oldval;
-                while (e1.op == TOK.index && (cast(IndexExp)e1).e1.type.toBasetype().ty == Taarray)
+                while (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
                 {
-                    Expression ekey = interpretRegion((cast(IndexExp)e1).e2, istate);
+                    Expression ekey = interpretRegion(e1.isIndexExp().e2, istate);
                     if (exceptionOrCant(ekey))
                         return;
                     ekey = resolveSlice(ekey); // only happens with AA assignment
@@ -3443,7 +3519,7 @@ public:
                     valuesx.push(newaae);
 
                     auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx);
-                    aae.type = (cast(IndexExp)e1).e1.type;
+                    aae.type = e1.isIndexExp().e1.type;
                     aae.ownedByCtfe = OwnedBy.ctfe;
                     if (!existingAA)
                     {
@@ -3451,7 +3527,7 @@ public:
                         lastIndex = ekey;
                     }
                     newaae = aae;
-                    e1 = (cast(IndexExp)e1).e1;
+                    e1 = e1.isIndexExp().e1;
                 }
 
                 // We must set to aggregate with newaae
@@ -3465,13 +3541,13 @@ public:
             assert(existingAA && lastIndex);
             e1 = null; // stomp
         }
-        else if (e1.op == TOK.arrayLength)
+        else if (e1.op == EXP.arrayLength)
         {
             oldval = interpretRegion(e1, istate);
             if (exceptionOrCant(oldval))
                 return;
         }
-        else if (e.op == TOK.construct || e.op == TOK.blit)
+        else if (e.op == EXP.construct || e.op == EXP.blit)
         {
             // Unless we have a simple var assignment, we're
             // only modifying part of the variable. So we need to make sure
@@ -3501,11 +3577,11 @@ public:
             if (exceptionOrCant(e1))
                 return;
 
-            if (e1.op == TOK.index && (cast(IndexExp)e1).e1.type.toBasetype().ty == Taarray)
+            if (e1.op == EXP.index && e1.isIndexExp().e1.type.toBasetype().ty == Taarray)
             {
-                IndexExp ie = cast(IndexExp)e1;
-                assert(ie.e1.op == TOK.assocArrayLiteral);
-                existingAA = cast(AssocArrayLiteralExp)ie.e1;
+                IndexExp ie = e1.isIndexExp();
+                assert(ie.e1.op == EXP.assocArrayLiteral);
+                existingAA = ie.e1.isAssocArrayLiteralExp();
                 lastIndex = ie.e2;
             }
         }
@@ -3516,7 +3592,7 @@ public:
         Expression newval = interpretRegion(e.e2, istate);
         if (exceptionOrCant(newval))
             return;
-        if (e.op == TOK.blit && newval.op == TOK.int64)
+        if (e.op == EXP.blit && newval.op == EXP.int64)
         {
             Type tbn = e.type.baseElemOf();
             if (tbn.ty == Tstruct)
@@ -3524,7 +3600,7 @@ public:
                 /* Look for special case of struct being initialized with 0.
                  */
                 newval = e.type.defaultInitLiteral(e.loc);
-                if (newval.op == TOK.error)
+                if (newval.op == EXP.error)
                 {
                     result = CTFEExp.cantexp;
                     return;
@@ -3554,7 +3630,7 @@ public:
             if (e.e1.type.ty != Tpointer)
             {
                 // ~= can create new values (see bug 6052)
-                if (e.op == TOK.concatenateAssign || e.op == TOK.concatenateElemAssign || e.op == TOK.concatenateDcharAssign)
+                if (e.op == EXP.concatenateAssign || e.op == EXP.concatenateElemAssign || e.op == EXP.concatenateDcharAssign)
                 {
                     // We need to dup it and repaint the type. For a dynamic array
                     // we can skip duplication, because it gets copied later anyway.
@@ -3574,12 +3650,14 @@ public:
                 newval = (*fp)(e.loc, e.type, oldval, newval).copy();
             }
             else if (e.e2.type.isintegral() &&
-                     (e.op == TOK.addAssign ||
-                      e.op == TOK.minAssign ||
-                      e.op == TOK.plusPlus ||
-                      e.op == TOK.minusMinus))
+                     (e.op == EXP.addAssign ||
+                      e.op == EXP.minAssign ||
+                      e.op == EXP.plusPlus ||
+                      e.op == EXP.minusMinus))
             {
-                newval = pointerArithmetic(e.loc, e.op, e.type, oldval, newval).copy();
+                newval = pointerArithmetic(pue, e.loc, e.op, e.type, oldval, newval).copy();
+                if (newval == pue.exp())
+                    newval = pue.copy();
             }
             else
             {
@@ -3612,7 +3690,7 @@ public:
             result = ctfeCast(pue, e.loc, e.type, e.type, fp && post ? oldval : newval);
             return;
         }
-        if (e1.op == TOK.arrayLength)
+        if (e1.op == EXP.arrayLength)
         {
             /* Change the assignment from:
              *  arr.length = n;
@@ -3635,7 +3713,7 @@ public:
 
             // We have changed it into a reference assignment
             // Note that returnValue is still the new length.
-            e1 = (cast(ArrayLengthExp)e1).e1;
+            e1 = e1.isArrayLengthExp().e1;
             Type t = e1.type.toBasetype();
             if (t.ty != Tarray)
             {
@@ -3652,7 +3730,9 @@ public:
             UnionExp utmp = void;
             oldval = resolveSlice(oldval, &utmp);
 
-            newval = changeArrayLiteralLength(e.loc, cast(TypeArray)t, oldval, oldlen, newlen).copy();
+            newval = changeArrayLiteralLength(pue, e.loc, cast(TypeArray)t, oldval, oldlen, newlen);
+            if (newval == pue.exp())
+                newval = pue.copy();
 
             e1 = assignToLvalue(e, e1, newval);
             if (exceptionOrCant(e1))
@@ -3692,11 +3772,11 @@ public:
 
         /* Block assignment or element-wise assignment.
          */
-        if (e1.op == TOK.slice ||
-            e1.op == TOK.vector ||
-            e1.op == TOK.arrayLiteral ||
-            e1.op == TOK.string_ ||
-            e1.op == TOK.null_ && e1.type.toBasetype().ty == Tarray)
+        if (e1.op == EXP.slice ||
+            e1.op == EXP.vector ||
+            e1.op == EXP.arrayLiteral ||
+            e1.op == EXP.string_ ||
+            e1.op == EXP.null_ && e1.type.toBasetype().ty == Tarray)
         {
             // Note that slice assignments don't support things like ++, so
             // we don't need to remember 'returnValue'.
@@ -3709,8 +3789,8 @@ public:
                 if (auto dve = e1x.isDotVarExp())
                 {
                     auto ex = dve.e1;
-                    auto sle = ex.op == TOK.structLiteral ? (cast(StructLiteralExp)ex)
-                             : ex.op == TOK.classReference ? (cast(ClassReferenceExp)ex).value
+                    auto sle = ex.op == EXP.structLiteral ? ex.isStructLiteralExp()
+                             : ex.op == EXP.classReference ? ex.isClassReferenceExp().value
                              : null;
                     auto v = dve.var.isVarDeclaration();
                     if (!sle || !v)
@@ -3745,7 +3825,7 @@ public:
             if (v is v2 || !v.isOverlappedWith(v2))
                 continue;
             auto e = (*sle.elements)[i];
-            if (e.op != TOK.void_)
+            if (e.op != EXP.void_)
                 (*sle.elements)[i] = voidInitLiteral(e.type, v).copy();
         }
     }
@@ -3768,10 +3848,10 @@ public:
              *  e.v = newval
              */
             auto ex = dve.e1;
-            auto sle = ex.op == TOK.structLiteral ? (cast(StructLiteralExp)ex)
-                     : ex.op == TOK.classReference ? (cast(ClassReferenceExp)ex).value
+            auto sle = ex.op == EXP.structLiteral ? ex.isStructLiteralExp()
+                     : ex.op == EXP.classReference ? ex.isClassReferenceExp().value
                      : null;
-            auto v = (cast(DotVarExp)e1).var.isVarDeclaration();
+            auto v = e1.isDotVarExp().var.isVarDeclaration();
             if (!sle || !v)
             {
                 e.error("CTFE internal error: dotvar assignment");
@@ -3783,20 +3863,35 @@ public:
                 return CTFEExp.cantexp;
             }
 
-            int fieldi = ex.op == TOK.structLiteral ? findFieldIndexByName(sle.sd, v)
-                       : (cast(ClassReferenceExp)ex).findFieldIndexByName(v);
+            int fieldi = ex.op == EXP.structLiteral ? findFieldIndexByName(sle.sd, v)
+                       : ex.isClassReferenceExp().findFieldIndexByName(v);
             if (fieldi == -1)
             {
                 e.error("CTFE internal error: cannot find field `%s` in `%s`", v.toChars(), ex.toChars());
                 return CTFEExp.cantexp;
             }
-            assert(0 <= fieldi && fieldi < sle.elements.dim);
+            assert(0 <= fieldi && fieldi < sle.elements.length);
 
             // If it's a union, set all other members of this union to void
             stompOverlappedFields(sle, v);
 
             payload = &(*sle.elements)[fieldi];
             oldval = *payload;
+            if (auto ival = newval.isIntegerExp())
+            {
+                if (auto bf = v.isBitFieldDeclaration())
+                {
+                    sinteger_t value = ival.toInteger();
+                    if (bf.type.isunsigned())
+                        value &= (1L << bf.fieldWidth) - 1; // zero extra bits
+                    else
+                    {   // sign extend extra bits
+                        value = value << (64 - bf.fieldWidth);
+                        value = value >> (64 - bf.fieldWidth);
+                    }
+                    ival.setInteger(value);
+                }
+            }
         }
         else if (auto ie = e1.isIndexExp())
         {
@@ -3820,13 +3915,13 @@ public:
                 existingSE.setCodeUnit(index, cast(dchar)newval.toInteger());
                 return null;
             }
-            if (aggregate.op != TOK.arrayLiteral)
+            if (aggregate.op != EXP.arrayLiteral)
             {
                 e.error("index assignment `%s` is not yet supported in CTFE ", e.toChars());
                 return CTFEExp.cantexp;
             }
 
-            ArrayLiteralExp existingAE = cast(ArrayLiteralExp)aggregate;
+            ArrayLiteralExp existingAE = aggregate.isArrayLiteralExp();
             if (existingAE.ownedByCtfe != OwnedBy.ctfe)
             {
                 e.error("cannot modify read-only constant `%s`", existingAE.toChars());
@@ -3848,7 +3943,7 @@ public:
         if (auto ve = newval.isVectorExp())
         {
             // Ensure ve is an array literal, and not a broadcast
-            if (ve.e1.op == TOK.int64 || ve.e1.op == TOK.float64) // if broadcast
+            if (ve.e1.op == EXP.int64 || ve.e1.op == EXP.float64) // if broadcast
             {
                 UnionExp ue = void;
                 Expression ex = interpretVectorToArray(&ue, ve);
@@ -3856,13 +3951,13 @@ public:
             }
         }
 
-        if (newval.op == TOK.structLiteral && oldval)
+        if (newval.op == EXP.structLiteral && oldval)
         {
-            assert(oldval.op == TOK.structLiteral || oldval.op == TOK.arrayLiteral || oldval.op == TOK.string_);
+            assert(oldval.op == EXP.structLiteral || oldval.op == EXP.arrayLiteral || oldval.op == EXP.string_);
             newval = copyLiteral(newval).copy();
             assignInPlace(oldval, newval);
         }
-        else if (wantCopy && e.op == TOK.assign)
+        else if (wantCopy && e.op == EXP.assign)
         {
             // Currently postblit/destructor calls on static array are done
             // in the druntime internal functions so they don't appear in AST.
@@ -3878,12 +3973,12 @@ public:
                     return CTFEExp.cantexp;
                 }
             }
-            assert(oldval.op == TOK.arrayLiteral);
-            assert(newval.op == TOK.arrayLiteral);
+            assert(oldval.op == EXP.arrayLiteral);
+            assert(newval.op == EXP.arrayLiteral);
 
-            Expressions* oldelems = (cast(ArrayLiteralExp)oldval).elements;
-            Expressions* newelems = (cast(ArrayLiteralExp)newval).elements;
-            assert(oldelems.dim == newelems.dim);
+            Expressions* oldelems = oldval.isArrayLiteralExp().elements;
+            Expressions* newelems = newval.isArrayLiteralExp().elements;
+            assert(oldelems.length == newelems.length);
 
             Type elemtype = oldval.type.nextOf();
             foreach (i, ref oldelem; *oldelems)
@@ -3907,7 +4002,7 @@ public:
             if (wantCopy)
                 newval = copyLiteral(newval).copy();
 
-            if (t1b.ty == Tsarray && e.op == TOK.construct && e.e2.isLvalue())
+            if (t1b.ty == Tsarray && e.op == EXP.construct && e.e2.isLvalue())
             {
                 // https://issues.dlang.org/show_bug.cgi?id=9245
                 if (Expression ex = evaluatePostblit(istate, newval))
@@ -3923,7 +4018,7 @@ public:
             *payload = oldval;
 
         // Blit assignment should return the newly created value.
-        if (e.op == TOK.blit)
+        if (e.op == EXP.blit)
             return oldval;
 
         return null;
@@ -3938,7 +4033,7 @@ public:
      * This could be a slice assignment or a block assignment, and
      * dest could be either an array literal, or a string.
      *
-     * Returns TOK.cantExpression on failure. If there are no errors,
+     * Returns EXP.cantExpression on failure. If there are no errors,
      * it returns aggregate[low..upp], except that as an optimisation,
      * if goal == CTFEGoal.Nothing, it will return NULL
      */
@@ -3956,75 +4051,32 @@ public:
             //   aggregate[] = newval
             //   aggregate[low..upp] = newval
             // ------------------------------
-            version (all) // should be move in interpretAssignCommon as the evaluation of e1
-            {
-                Expression oldval = interpretRegion(se.e1, istate);
-
-                // Set the $ variable
-                uinteger_t dollar = resolveArrayLength(oldval);
-                if (se.lengthVar)
-                {
-                    Expression dollarExp = ctfeEmplaceExp!IntegerExp(e1.loc, dollar, Type.tsize_t);
-                    ctfeGlobals.stack.push(se.lengthVar);
-                    setValue(se.lengthVar, dollarExp);
-                }
-                Expression lwr = interpretRegion(se.lwr, istate);
-                if (exceptionOrCantInterpret(lwr))
-                {
-                    if (se.lengthVar)
-                        ctfeGlobals.stack.pop(se.lengthVar);
-                    return lwr;
-                }
-                Expression upr = interpretRegion(se.upr, istate);
-                if (exceptionOrCantInterpret(upr))
-                {
-                    if (se.lengthVar)
-                        ctfeGlobals.stack.pop(se.lengthVar);
-                    return upr;
-                }
-                if (se.lengthVar)
-                    ctfeGlobals.stack.pop(se.lengthVar); // $ is defined only in [L..U]
-
-                const dim = dollar;
-                lowerbound = lwr ? lwr.toInteger() : 0;
-                upperbound = upr ? upr.toInteger() : dim;
-
-                if (lowerbound < 0 || dim < upperbound)
-                {
-                    e.error("array bounds `[0..%llu]` exceeded in slice `[%llu..%llu]`",
-                        ulong(dim), ulong(lowerbound), ulong(upperbound));
-                    return CTFEExp.cantexp;
-                }
-            }
-            aggregate = oldval;
-            firstIndex = lowerbound;
+            aggregate = interpretRegion(se.e1, istate);
+            lowerbound = se.lwr ? se.lwr.toInteger() : 0;
+            upperbound = se.upr ? se.upr.toInteger() : resolveArrayLength(aggregate);
 
+            // Slice of a slice --> change the bounds
             if (auto oldse = aggregate.isSliceExp())
             {
-                // Slice of a slice --> change the bounds
-                if (oldse.upr.toInteger() < upperbound + oldse.lwr.toInteger())
-                {
-                    e.error("slice `[%llu..%llu]` exceeds array bounds `[0..%llu]`",
-                        ulong(lowerbound), ulong(upperbound), oldse.upr.toInteger() - oldse.lwr.toInteger());
-                    return CTFEExp.cantexp;
-                }
                 aggregate = oldse.e1;
                 firstIndex = lowerbound + oldse.lwr.toInteger();
             }
+            else
+                firstIndex = lowerbound;
         }
         else
         {
             if (auto ale = e1.isArrayLiteralExp())
             {
                 lowerbound = 0;
-                upperbound = ale.elements.dim;
+                upperbound = ale.elements.length;
             }
             else if (auto se = e1.isStringExp())
             {
                 lowerbound = 0;
                 upperbound = se.len;
             }
-            else if (e1.op == TOK.null_)
+            else if (e1.op == EXP.null_)
             {
                 lowerbound = 0;
                 upperbound = 0;
@@ -4088,7 +4140,7 @@ public:
                         return CTFEExp.cantexp;
                     }
                 }
-                assert(newval.op != TOK.slice);
+                assert(newval.op != EXP.slice);
             }
             if (auto se = newval.isStringExp())
             {
@@ -4126,9 +4178,9 @@ public:
                 return CTFEExp.cantexp;
             }
 
-            if (newval.op == TOK.slice && !isBlockAssignment)
+            if (newval.op == EXP.slice && !isBlockAssignment)
             {
-                auto se = cast(SliceExp)newval;
+                auto se = newval.isSliceExp();
                 auto aggr2 = se.e1;
                 const srclower = se.lwr.toInteger();
                 const srcupper = se.upr.toInteger();
@@ -4142,9 +4194,9 @@ public:
                     // Currently overlapping for struct array is allowed.
                     // The order of elements processing depends on the overlapping.
                     // https://issues.dlang.org/show_bug.cgi?id=14024
-                    assert(aggr2.op == TOK.arrayLiteral);
+                    assert(aggr2.op == EXP.arrayLiteral);
                     Expressions* oldelems = existingAE.elements;
-                    Expressions* newelems = (cast(ArrayLiteralExp)aggr2).elements;
+                    Expressions* newelems = aggr2.isArrayLiteralExp().elements;
 
                     Type elemtype = aggregate.type.nextOf();
                     bool needsPostblit = e.e2.isLvalue();
@@ -4210,22 +4262,22 @@ public:
                 }
                 // no overlapping
                 //length?
-                assert(newval.op != TOK.slice);
+                assert(newval.op != EXP.slice);
             }
-            if (newval.op == TOK.string_ && !isBlockAssignment)
+            if (newval.op == EXP.string_ && !isBlockAssignment)
             {
                 /* Mixed slice: it was initialized as an array literal of chars/integers.
                  * Now a slice of it is being set with a string.
                  */
-                sliceAssignArrayLiteralFromString(existingAE, cast(StringExp)newval, cast(size_t)firstIndex);
+                sliceAssignArrayLiteralFromString(existingAE, newval.isStringExp(), cast(size_t)firstIndex);
                 return newval;
             }
-            if (newval.op == TOK.arrayLiteral && !isBlockAssignment)
+            if (newval.op == EXP.arrayLiteral && !isBlockAssignment)
             {
                 Expressions* oldelems = existingAE.elements;
-                Expressions* newelems = (cast(ArrayLiteralExp)newval).elements;
+                Expressions* newelems = newval.isArrayLiteralExp().elements;
                 Type elemtype = existingAE.type.nextOf();
-                bool needsPostblit = e.op != TOK.blit && e.e2.isLvalue();
+                bool needsPostblit = e.op != EXP.blit && e.e2.isLvalue();
                 foreach (j, newelem; *newelems)
                 {
                     newelem = paintTypeOntoLiteral(elemtype, newelem);
@@ -4253,22 +4305,22 @@ public:
                 bool needsPostblit;
                 bool needsDtor;
 
-                extern (C++) Expression assignTo(ArrayLiteralExp ae)
+                Expression assignTo(ArrayLiteralExp ae)
                 {
-                    return assignTo(ae, 0, ae.elements.dim);
+                    return assignTo(ae, 0, ae.elements.length);
                 }
 
-                extern (C++) Expression assignTo(ArrayLiteralExp ae, size_t lwr, size_t upr)
+                Expression assignTo(ArrayLiteralExp ae, size_t lwr, size_t upr)
                 {
                     Expressions* w = ae.elements;
                     assert(ae.type.ty == Tsarray || ae.type.ty == Tarray);
                     bool directblk = (cast(TypeArray)ae.type).next.equivalent(newval.type);
                     for (size_t k = lwr; k < upr; k++)
                     {
-                        if (!directblk && (*w)[k].op == TOK.arrayLiteral)
+                        if (!directblk && (*w)[k].op == EXP.arrayLiteral)
                         {
                             // Multidimensional array block assign
-                            if (Expression ex = assignTo(cast(ArrayLiteralExp)(*w)[k]))
+                            if (Expression ex = assignTo((*w)[k].isArrayLiteralExp()))
                                 return ex;
                         }
                         else if (refCopy)
@@ -4303,7 +4355,7 @@ public:
 
             Type tn = newval.type.toBasetype();
             bool wantRef = (tn.ty == Tarray || isAssocArray(tn) || tn.ty == Tclass);
-            bool cow = newval.op != TOK.structLiteral && newval.op != TOK.arrayLiteral && newval.op != TOK.string_;
+            bool cow = newval.op != EXP.structLiteral && newval.op != EXP.arrayLiteral && newval.op != EXP.string_;
             Type tb = tn.baseElemOf();
             StructDeclaration sd = (tb.ty == Tstruct ? (cast(TypeStruct)tb).sym : null);
 
@@ -4311,8 +4363,8 @@ public:
             rb.istate = istate;
             rb.newval = newval;
             rb.refCopy = wantRef || cow;
-            rb.needsPostblit = sd && sd.postblit && e.op != TOK.blit && e.e2.isLvalue();
-            rb.needsDtor = sd && sd.dtor && e.op == TOK.assign;
+            rb.needsPostblit = sd && sd.postblit && e.op != EXP.blit && e.e2.isLvalue();
+            rb.needsDtor = sd && sd.dtor && e.op == EXP.assign;
             if (Expression ex = rb.assignTo(existingAE, cast(size_t)lowerbound, cast(size_t)upperbound))
                 return ex;
 
@@ -4338,57 +4390,57 @@ public:
     {
         switch (e.op)
         {
-        case TOK.addAssign:
+        case EXP.addAssign:
             interpretAssignCommon(e, &Add);
             return;
 
-        case TOK.minAssign:
+        case EXP.minAssign:
             interpretAssignCommon(e, &Min);
             return;
 
-        case TOK.concatenateAssign:
-        case TOK.concatenateElemAssign:
-        case TOK.concatenateDcharAssign:
+        case EXP.concatenateAssign:
+        case EXP.concatenateElemAssign:
+        case EXP.concatenateDcharAssign:
             interpretAssignCommon(e, &ctfeCat);
             return;
 
-        case TOK.mulAssign:
+        case EXP.mulAssign:
             interpretAssignCommon(e, &Mul);
             return;
 
-        case TOK.divAssign:
+        case EXP.divAssign:
             interpretAssignCommon(e, &Div);
             return;
 
-        case TOK.modAssign:
+        case EXP.modAssign:
             interpretAssignCommon(e, &Mod);
             return;
 
-        case TOK.leftShiftAssign:
+        case EXP.leftShiftAssign:
             interpretAssignCommon(e, &Shl);
             return;
 
-        case TOK.rightShiftAssign:
+        case EXP.rightShiftAssign:
             interpretAssignCommon(e, &Shr);
             return;
 
-        case TOK.unsignedRightShiftAssign:
+        case EXP.unsignedRightShiftAssign:
             interpretAssignCommon(e, &Ushr);
             return;
 
-        case TOK.andAssign:
+        case EXP.andAssign:
             interpretAssignCommon(e, &And);
             return;
 
-        case TOK.orAssign:
+        case EXP.orAssign:
             interpretAssignCommon(e, &Or);
             return;
 
-        case TOK.xorAssign:
+        case EXP.xorAssign:
             interpretAssignCommon(e, &Xor);
             return;
 
-        case TOK.powAssign:
+        case EXP.powAssign:
             interpretAssignCommon(e, &Pow);
             return;
 
@@ -4403,7 +4455,7 @@ public:
         {
             printf("%s PostExp::interpret() %s\n", e.loc.toChars(), e.toChars());
         }
-        if (e.op == TOK.plusPlus)
+        if (e.op == EXP.plusPlus)
             interpretAssignCommon(e, &Add, 1);
         else
             interpretAssignCommon(e, &Min, 1);
@@ -4421,21 +4473,21 @@ public:
     static int isPointerCmpExp(Expression e, Expression* p1, Expression* p2)
     {
         int ret = 1;
-        while (e.op == TOK.not)
+        while (e.op == EXP.not)
         {
             ret *= -1;
-            e = (cast(NotExp)e).e1;
+            e = e.isNotExp().e1;
         }
         switch (e.op)
         {
-        case TOK.lessThan:
-        case TOK.lessOrEqual:
+        case EXP.lessThan:
+        case EXP.lessOrEqual:
             ret *= -1;
             goto case; /+ fall through +/
-        case TOK.greaterThan:
-        case TOK.greaterOrEqual:
-            *p1 = (cast(BinExp)e).e1;
-            *p2 = (cast(BinExp)e).e2;
+        case EXP.greaterThan:
+        case EXP.greaterOrEqual:
+            *p1 = e.isBinExp().e1;
+            *p2 = e.isBinExp().e2;
             if (!(isPointer((*p1).type) && isPointer((*p2).type)))
                 ret = 0;
             break;
@@ -4465,7 +4517,7 @@ public:
      */
     private void interpretFourPointerRelation(UnionExp* pue, BinExp e)
     {
-        assert(e.op == TOK.andAnd || e.op == TOK.orOr);
+        assert(e.op == EXP.andAnd || e.op == EXP.orOr);
 
         /*  It can only be an isInside expression, if both e1 and e2 are
          *  directional pointer comparisons.
@@ -4506,7 +4558,7 @@ public:
         Expression agg1 = getAggregateFromPointer(p1, &ofs1);
         Expression agg2 = getAggregateFromPointer(p2, &ofs2);
 
-        if (!pointToSameMemoryBlock(agg1, agg2) && agg1.op != TOK.null_ && agg2.op != TOK.null_)
+        if (!pointToSameMemoryBlock(agg1, agg2) && agg1.op != EXP.null_ && agg2.op != EXP.null_)
         {
             // Here it is either CANT_INTERPRET,
             // or an IsInside comparison returning false.
@@ -4545,7 +4597,7 @@ public:
                 (dir1 != dir2 && pointToSameMemoryBlock(agg1, agg3) && pointToSameMemoryBlock(agg2, agg4)))
             {
                 // it's a legal two-sided comparison
-                emplaceExp!(IntegerExp)(pue, e.loc, (e.op == TOK.andAnd) ? 0 : 1, e.type);
+                emplaceExp!(IntegerExp)(pue, e.loc, (e.op == EXP.andAnd) ? 0 : 1, e.type);
                 result = pue.exp();
                 return;
             }
@@ -4582,29 +4634,29 @@ public:
          * Returns:
          *      negate operator
          */
-        static TOK negateRelation(TOK op) pure
+        static EXP negateRelation(EXP op) pure
         {
             switch (op)
             {
-                case TOK.greaterOrEqual:  op = TOK.lessThan;       break;
-                case TOK.greaterThan:     op = TOK.lessOrEqual;    break;
-                case TOK.lessOrEqual:     op = TOK.greaterThan;    break;
-                case TOK.lessThan:        op = TOK.greaterOrEqual; break;
+                case EXP.greaterOrEqual:  op = EXP.lessThan;       break;
+                case EXP.greaterThan:     op = EXP.lessOrEqual;    break;
+                case EXP.lessOrEqual:     op = EXP.greaterThan;    break;
+                case EXP.lessThan:        op = EXP.greaterOrEqual; break;
                 default:                  assert(0);
             }
             return op;
         }
 
-        const TOK cmpop = nott ? negateRelation(ex.op) : ex.op;
+        const EXP cmpop = nott ? negateRelation(ex.op) : ex.op;
         const cmp = comparePointers(cmpop, agg1, ofs1, agg2, ofs2);
         // We already know this is a valid comparison.
         assert(cmp >= 0);
-        if (e.op == TOK.andAnd && cmp == 1 || e.op == TOK.orOr && cmp == 0)
+        if (e.op == EXP.andAnd && cmp == 1 || e.op == EXP.orOr && cmp == 0)
         {
             result = interpret(pue, e.e2, istate);
             return;
         }
-        emplaceExp!(IntegerExp)(pue, e.loc, (e.op == TOK.andAnd) ? 0 : 1, e.type);
+        emplaceExp!(IntegerExp)(pue, e.loc, (e.op == EXP.andAnd) ? 0 : 1, e.type);
         result = pue.exp();
     }
 
@@ -4625,22 +4677,22 @@ public:
             return;
 
         bool res;
-        const andand = e.op == TOK.andAnd;
-        if (andand ? result.isBool(false) : isTrueBool(result))
+        const andand = e.op == EXP.andAnd;
+        if (andand ? result.toBool().hasValue(false) : isTrueBool(result))
             res = !andand;
-        else if (andand ? isTrueBool(result) : result.isBool(false))
+        else if (andand ? isTrueBool(result) : result.toBool().hasValue(false))
         {
             UnionExp ue2 = void;
             result = interpret(&ue2, e.e2, istate);
             if (exceptionOrCant(result))
                 return;
-            if (result.op == TOK.voidExpression)
+            if (result.op == EXP.voidExpression)
             {
                 assert(e.type.ty == Tvoid);
                 result = null;
                 return;
             }
-            if (result.isBool(false))
+            if (result.toBool().hasValue(false))
                 res = false;
             else if (isTrueBool(result))
                 res = true;
@@ -4754,7 +4806,7 @@ public:
 
             if (fd.ident == Id.__ArrayPostblit || fd.ident == Id.__ArrayDtor)
             {
-                assert(e.arguments.dim == 1);
+                assert(e.arguments.length == 1);
                 Expression ea = (*e.arguments)[0];
                 // printf("1 ea = %s %s\n", ea.type.toChars(), ea.toChars());
                 if (auto se = ea.isSliceExp())
@@ -4762,8 +4814,8 @@ public:
                 if (auto ce = ea.isCastExp())
                     ea = ce.e1;
 
-                // printf("2 ea = %s, %s %s\n", ea.type.toChars(), Token.toChars(ea.op), ea.toChars());
-                if (ea.op == TOK.variable || ea.op == TOK.symbolOffset)
+                // printf("2 ea = %s, %s %s\n", ea.type.toChars(), EXPtoString(ea.op).ptr, ea.toChars());
+                if (ea.op == EXP.variable || ea.op == EXP.symbolOffset)
                     result = getVarExp(e.loc, istate, (cast(SymbolExp)ea).var, CTFEGoal.RValue);
                 else if (auto ae = ea.isAddrExp())
                     result = interpretRegion(ae.e1, istate);
@@ -4790,7 +4842,7 @@ public:
             {
                 // In expressionsem.d `ea.length = eb;` got lowered to `_d_arraysetlengthT(ea, eb);`.
                 // The following code will rewrite it back to `ea.length = eb` and then interpret that expression.
-                assert(e.arguments.dim == 2);
+                assert(e.arguments.length == 2);
 
                 Expression ea = (*e.arguments)[0];
                 Expression eb = (*e.arguments)[1];
@@ -4805,6 +4857,65 @@ public:
                 result = interpretRegion(ae, istate);
                 return;
             }
+            else if (isArrayConstructionOrAssign(fd.ident))
+            {
+                // In expressionsem.d, the following lowerings were performed:
+                // * `T[x] ea = eb;` to `_d_array{,set}ctor(ea[], eb[]);`.
+                // * `ea = eb` to `_d_array{,setassign,assign_l,assign_r}(ea[], eb)`.
+                // The following code will rewrite them back to `ea = eb` and
+                // then interpret that expression.
+
+                if (fd.ident == Id._d_arrayctor)
+                    assert(e.arguments.length == 3);
+                else
+                    assert(e.arguments.length == 2);
+
+                Expression ea = (*e.arguments)[0];
+                if (ea.isCastExp)
+                    ea = ea.isCastExp.e1;
+
+                Expression eb = (*e.arguments)[1];
+                if (eb.isCastExp() && fd.ident != Id._d_arraysetctor)
+                    eb = eb.isCastExp.e1;
+
+                Expression rewrittenExp;
+                if (fd.ident == Id._d_arrayctor || fd.ident == Id._d_arraysetctor)
+                    rewrittenExp = new ConstructExp(e.loc, ea, eb);
+                else
+                    rewrittenExp = new AssignExp(e.loc, ea, eb);
+
+                rewrittenExp.type = ea.type;
+                result = interpret(rewrittenExp, istate);
+
+                return;
+            }
+            else if (fd.ident == Id._d_arrayappendT || fd.ident == Id._d_arrayappendTTrace)
+            {
+                // In expressionsem.d `ea ~= eb` was lowered to `_d_arrayappendT{,Trace}({file, line, funcname}, ea, eb);`.
+                // The following code will rewrite it back to `ea ~= eb` and then interpret that expression.
+                Expression lhs, rhs;
+
+                if (fd.ident == Id._d_arrayappendT)
+                {
+                    assert(e.arguments.length == 2);
+                    lhs = (*e.arguments)[0];
+                    rhs = (*e.arguments)[1];
+                }
+                else
+                {
+                    assert(e.arguments.length == 5);
+                    lhs = (*e.arguments)[3];
+                    rhs = (*e.arguments)[4];
+                }
+
+                auto cae = new CatAssignExp(e.loc, lhs, rhs);
+                cae.type = e.type;
+
+                result = interpretRegion(cae, istate, CTFEGoal.LValue);
+                return;
+            }
+            else if (fd.ident == Id._d_arrayappendcTX)
+                assert(0, "CTFE cannot interpret _d_arrayappendcTX!");
         }
         else if (auto soe = ecall.isSymOffExp())
         {
@@ -4848,7 +4959,7 @@ public:
             // Currently this is satisfied because closure is not yet supported.
             assert(!fd.isNested() || fd.needThis());
 
-            if (pthis.op == TOK.typeid_)
+            if (pthis.op == EXP.typeid_)
             {
                 pthis.error("static variable `%s` cannot be read at compile time", pthis.toChars());
                 result = CTFEExp.cantexp;
@@ -4856,7 +4967,7 @@ public:
             }
             assert(pthis);
 
-            if (pthis.op == TOK.null_)
+            if (pthis.op == EXP.null_)
             {
                 assert(pthis.type.toBasetype().ty == Tclass);
                 e.error("function call through null class reference `%s`", pthis.toChars());
@@ -4864,7 +4975,7 @@ public:
                 return;
             }
 
-            assert(pthis.op == TOK.structLiteral || pthis.op == TOK.classReference || pthis.op == TOK.type);
+            assert(pthis.op == EXP.structLiteral || pthis.op == EXP.classReference || pthis.op == EXP.type);
 
             if (fd.isVirtual() && !e.directcall)
             {
@@ -4879,7 +4990,7 @@ public:
             }
         }
 
-        if (fd && fd.semanticRun >= PASS.semantic3done && fd.semantic3Errors)
+        if (fd && fd.semanticRun >= PASS.semantic3done && fd.hasSemantic3Errors())
         {
             e.error("CTFE failed because of previous errors in `%s`", fd.toChars());
             result = CTFEExp.cantexp;
@@ -4899,7 +5010,7 @@ public:
         }
 
         result = interpretFunction(pue, fd, istate, e.arguments, pthis);
-        if (result.op == TOK.voidExpression)
+        if (result.op == EXP.voidExpression)
             return;
         if (!exceptionOrCantInterpret(result))
         {
@@ -4926,10 +5037,65 @@ public:
             printf("%s CommaExp::interpret() %s\n", e.loc.toChars(), e.toChars());
         }
 
+        bool isNewThrowableHook()
+        {
+            auto de = e.e1.isDeclarationExp();
+            if (de is null)
+                return false;
+
+            auto vd = de.declaration.isVarDeclaration();
+            if (vd is null)
+                return false;
+
+            auto ei = vd._init.isExpInitializer();
+            if (ei is null)
+                return false;
+
+            auto ce = ei.exp.isConstructExp();
+            if (ce is null)
+                return false;
+
+            return isRuntimeHook(ce.e2, Id._d_newThrowable) !is null;
+        }
+
+        if (auto ce = isRuntimeHook(e.e1, Id._d_arrayappendcTX))
+        {
+            // In expressionsem.d `arr ~= elem` was lowered to
+            // `_d_arrayappendcTX(arr, elem), arr[arr.length - 1] = elem, elem;`.
+            // The following code will rewrite it back to `arr ~= elem`
+            // and then interpret that expression.
+            assert(ce.arguments.length == 2);
+
+            auto arr = (*ce.arguments)[0];
+            auto elem = e.e2.isConstructExp().e2;
+            assert(elem);
+
+            auto cae = new CatAssignExp(e.loc, arr, elem);
+            cae.type = arr.type;
+
+            result = interpret(cae, istate);
+            return;
+        }
+        else if (isNewThrowableHook())
+        {
+            // In expressionsem.d `throw new Exception(args)` was lowered to
+            // `throw (tmp = _d_newThrowable!Exception(), tmp.ctor(args), tmp)`.
+            // The following code will rewrite it back to `throw new Exception(args)`
+            // and then interpret this expression instead.
+            auto ce = e.e2.isCallExp();
+            assert(ce);
+
+            auto ne = new NewExp(e.loc, null, e.type, ce.arguments);
+            ne.type = e.e1.type;
+
+            result = interpret(ne, istate);
+            return;
+        }
+
         // If it creates a variable, and there's no context for
         // the variable to be created in, we need to create one now.
         InterState istateComma;
-        if (!istate && firstComma(e.e1).op == TOK.declaration)
+        if (!istate && firstComma(e.e1).op == EXP.declaration)
         {
             ctfeGlobals.stack.startFrame(null);
             istate = &istateComma;
@@ -4946,8 +5112,8 @@ public:
 
         // If the comma returns a temporary variable, it needs to be an lvalue
         // (this is particularly important for struct constructors)
-        if (e.e1.op == TOK.declaration &&
-            e.e2.op == TOK.variable &&
+        if (e.e1.op == EXP.declaration &&
+            e.e2.op == EXP.variable &&
             e.e1.isDeclarationExp().declaration == e.e2.isVarExp().var &&
             e.e2.isVarExp().var.storage_class & STC.ctfe)
         {
@@ -4967,7 +5133,7 @@ public:
                 newval = interpretRegion(newval, istate);
                 if (exceptionOrCant(newval))
                     return endTempStackFrame();
-                if (newval.op != TOK.voidExpression)
+                if (newval.op != EXP.voidExpression)
                 {
                     // v isn't necessarily null.
                     setValueWithoutChecking(v, copyLiteral(newval).copy());
@@ -4999,7 +5165,7 @@ public:
 
         if (isPointer(e.econd.type))
         {
-            if (econd.op != TOK.null_)
+            if (econd.op != EXP.null_)
             {
                 econd = IntegerExp.createBool(true);
             }
@@ -5010,7 +5176,7 @@ public:
             result = interpret(pue, e.e1, istate, goal);
             incUsageCtfe(istate, e.e1.loc);
         }
-        else if (econd.isBool(false))
+        else if (econd.toBool().hasValue(false))
         {
             result = interpret(pue, e.e2, istate, goal);
             incUsageCtfe(istate, e.e2.loc);
@@ -5033,7 +5199,7 @@ public:
         assert(e1);
         if (exceptionOrCant(e1))
             return;
-        if (e1.op != TOK.string_ && e1.op != TOK.arrayLiteral && e1.op != TOK.slice && e1.op != TOK.null_)
+        if (e1.op != EXP.string_ && e1.op != EXP.arrayLiteral && e1.op != EXP.slice && e1.op != EXP.null_)
         {
             e.error("`%s` cannot be evaluated at compile time", e.toChars());
             result = CTFEExp.cantexp;
@@ -5055,7 +5221,7 @@ public:
     {
         if (auto ale = e.e1.isArrayLiteralExp())
             return ale;         // it's already an array literal
-        if (e.e1.op == TOK.int64 || e.e1.op == TOK.float64)
+        if (e.e1.op == EXP.int64 || e.e1.op == EXP.float64)
         {
             // Convert literal __vector(int) -> __vector([array])
             auto elements = new Expressions(e.dim);
@@ -5086,7 +5252,7 @@ public:
         assert(e1);
         if (exceptionOrCant(e1))
             return;
-        if (e1.op != TOK.arrayLiteral && e1.op != TOK.int64 && e1.op != TOK.float64)
+        if (e1.op != EXP.arrayLiteral && e1.op != EXP.int64 && e1.op != EXP.float64)
         {
             e.error("`%s` cannot be evaluated at compile time", e.toChars());
             result = CTFEExp.cantexp;
@@ -5115,7 +5281,7 @@ public:
         if (auto ve = e1.isVectorExp())
         {
             result = interpretVectorToArray(pue, ve);
-            if (result.op != TOK.vector)
+            if (result.op != EXP.vector)
                 return;
         }
         e.error("`%s` cannot be evaluated at compile time", e.toChars());
@@ -5169,24 +5335,24 @@ public:
             dinteger_t ofs;
             Expression agg = getAggregateFromPointer(e1, &ofs);
 
-            if (agg.op == TOK.null_)
+            if (agg.op == EXP.null_)
             {
                 e.error("cannot index through null pointer `%s`", e.e1.toChars());
                 return false;
             }
-            if (agg.op == TOK.int64)
+            if (agg.op == EXP.int64)
             {
                 e.error("cannot index through invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars());
                 return false;
             }
             // Pointer to a non-array variable
-            if (agg.op == TOK.symbolOffset)
+            if (agg.op == EXP.symbolOffset)
             {
-                e.error("mutable variable `%s` cannot be %s at compile time, even through a pointer", cast(char*)(modify ? "modified" : "read"), (cast(SymOffExp)agg).var.toChars());
+                e.error("mutable variable `%s` cannot be %s at compile time, even through a pointer", cast(char*)(modify ? "modified" : "read"), agg.isSymOffExp().var.toChars());
                 return false;
             }
 
-            if (agg.op == TOK.arrayLiteral || agg.op == TOK.string_)
+            if (agg.op == EXP.arrayLiteral || agg.op == EXP.string_)
             {
                 dinteger_t len = resolveArrayLength(agg);
                 if (ofs + indx >= len)
@@ -5211,7 +5377,7 @@ public:
         Expression e1 = interpretRegion(e.e1, istate);
         if (exceptionOrCantInterpret(e1))
             return false;
-        if (e1.op == TOK.null_)
+        if (e1.op == EXP.null_)
         {
             e.error("cannot index null array `%s`", e.e1.toChars());
             return false;
@@ -5225,11 +5391,11 @@ public:
 
         // Set the $ variable, and find the array literal to modify
         dinteger_t len;
-        if (e1.op == TOK.variable && e1.type.toBasetype().ty == Tsarray)
+        if (e1.op == EXP.variable && e1.type.toBasetype().ty == Tsarray)
             len = e1.type.toBasetype().isTypeSArray().dim.toInteger();
         else
         {
-            if (e1.op != TOK.arrayLiteral && e1.op != TOK.string_ && e1.op != TOK.slice && e1.op != TOK.vector)
+            if (e1.op != EXP.arrayLiteral && e1.op != EXP.string_ && e1.op != EXP.slice && e1.op != EXP.vector)
             {
                 e.error("cannot determine length of `%s` at compile time", e.e1.toChars());
                 return false;
@@ -5248,7 +5414,7 @@ public:
             ctfeGlobals.stack.pop(e.lengthVar); // $ is defined only inside []
         if (exceptionOrCantInterpret(e2))
             return false;
-        if (e2.op != TOK.int64)
+        if (e2.op != EXP.int64)
         {
             e.error("CTFE internal error: non-integral index `[%s]`", e.e2.toChars());
             return false;
@@ -5266,7 +5432,7 @@ public:
                 e.error("index %llu exceeds array length %llu", index, iupr - ilwr);
                 return false;
             }
-            *pagg = (cast(SliceExp)e1).e1;
+            *pagg = e1.isSliceExp().e1;
             *pidx = index + ilwr;
         }
         else
@@ -5297,7 +5463,7 @@ public:
                 result = CTFEExp.cantexp;
                 return;
             }
-            if (agg.op == TOK.arrayLiteral || agg.op == TOK.string_)
+            if (agg.op == EXP.arrayLiteral || agg.op == EXP.string_)
             {
                 if (goal == CTFEGoal.LValue)
                 {
@@ -5327,7 +5493,7 @@ public:
             Expression e1 = interpretRegion(e.e1, istate);
             if (exceptionOrCant(e1))
                 return;
-            if (e1.op == TOK.null_)
+            if (e1.op == EXP.null_)
             {
                 if (goal == CTFEGoal.LValue && e1.type.ty == Taarray && e.modifiable)
                 {
@@ -5355,10 +5521,10 @@ public:
                 return;
             }
 
-            assert(e1.op == TOK.assocArrayLiteral);
+            assert(e1.op == EXP.assocArrayLiteral);
             UnionExp e2tmp = void;
             e2 = resolveSlice(e2, &e2tmp);
-            result = findKeyInAA(e.loc, cast(AssocArrayLiteralExp)e1, e2);
+            result = findKeyInAA(e.loc, e1.isAssocArrayLiteralExp(), e2);
             if (!result)
             {
                 e.error("key `%s` not found in associative array `%s`", e2.toChars(), e.e1.toChars());
@@ -5387,7 +5553,7 @@ public:
         result = ctfeIndex(pue, e.loc, e.type, agg, indexToAccess);
         if (exceptionOrCant(result))
             return;
-        if (result.op == TOK.void_)
+        if (result.op == EXP.void_)
         {
             e.error("`%s` is used before initialized", e.toChars());
             errorSupplemental(result.loc, "originally uninitialized here");
@@ -5410,7 +5576,7 @@ public:
             Expression e1 = interpretRegion(e.e1, istate);
             if (exceptionOrCant(e1))
                 return;
-            if (e1.op == TOK.int64)
+            if (e1.op == EXP.int64)
             {
                 e.error("cannot slice invalid pointer `%s` of value `%s`", e.e1.toChars(), e1.toChars());
                 result = CTFEExp.cantexp;
@@ -5432,7 +5598,7 @@ public:
             Expression agg = getAggregateFromPointer(e1, &ofs);
             ilwr += ofs;
             iupr += ofs;
-            if (agg.op == TOK.null_)
+            if (agg.op == EXP.null_)
             {
                 if (iupr == ilwr)
                 {
@@ -5444,22 +5610,22 @@ public:
                 result = CTFEExp.cantexp;
                 return;
             }
-            if (agg.op == TOK.symbolOffset)
+            if (agg.op == EXP.symbolOffset)
             {
                 e.error("slicing pointers to static variables is not supported in CTFE");
                 result = CTFEExp.cantexp;
                 return;
             }
-            if (agg.op != TOK.arrayLiteral && agg.op != TOK.string_)
+            if (agg.op != EXP.arrayLiteral && agg.op != EXP.string_)
             {
                 e.error("pointer `%s` cannot be sliced at compile time (it does not point to an array)", e.e1.toChars());
                 result = CTFEExp.cantexp;
                 return;
             }
-            assert(agg.op == TOK.arrayLiteral || agg.op == TOK.string_);
+            assert(agg.op == EXP.arrayLiteral || agg.op == EXP.string_);
             dinteger_t len = ArrayLength(Type.tsize_t, agg).exp().toInteger();
             //Type *pointee = ((TypePointer *)agg.type)->next;
-            if (iupr > (len + 1) || iupr < ilwr)
+            if (sliceBoundsCheck(0, len, ilwr, iupr))
             {
                 e.error("pointer slice `[%lld..%lld]` exceeds allocated memory block `[0..%lld]`", ilwr, iupr, len);
                 result = CTFEExp.cantexp;
@@ -5503,11 +5669,11 @@ public:
         /* Set dollar to the length of the array
          */
         uinteger_t dollar;
-        if ((e1.op == TOK.variable || e1.op == TOK.dotVariable) && e1.type.toBasetype().ty == Tsarray)
+        if ((e1.op == EXP.variable || e1.op == EXP.dotVariable) && e1.type.toBasetype().ty == Tsarray)
             dollar = e1.type.toBasetype().isTypeSArray().dim.toInteger();
         else
         {
-            if (e1.op != TOK.arrayLiteral && e1.op != TOK.string_ && e1.op != TOK.null_ && e1.op != TOK.slice && e1.op != TOK.vector)
+            if (e1.op != EXP.arrayLiteral && e1.op != EXP.string_ && e1.op != EXP.null_ && e1.op != EXP.slice && e1.op != EXP.vector)
             {
                 e.error("cannot determine length of `%s` at compile time", e1.toChars());
                 result = CTFEExp.cantexp;
@@ -5546,7 +5712,7 @@ public:
 
         uinteger_t ilwr = lwr.toInteger();
         uinteger_t iupr = upr.toInteger();
-        if (e1.op == TOK.null_)
+        if (e1.op == EXP.null_)
         {
             if (ilwr == 0 && iupr == 0)
             {
@@ -5563,9 +5729,9 @@ public:
             //  aggregate[lo1..up1][lwr..upr] ---> aggregate[lwr'..upr']
             uinteger_t lo1 = se.lwr.toInteger();
             uinteger_t up1 = se.upr.toInteger();
-            if (ilwr > iupr || iupr > up1 - lo1)
+            if (sliceBoundsCheck(0, up1 - lo1, ilwr, iupr))
             {
-                e.error("slice `[%llu..%llu]` exceeds array bounds `[%llu..%llu]`", ilwr, iupr, lo1, up1);
+                e.error("slice `[%llu..%llu]` exceeds array bounds `[0..%llu]`", ilwr, iupr, up1 - lo1);
                 result = CTFEExp.cantexp;
                 return;
             }
@@ -5578,9 +5744,9 @@ public:
             result.type = e.type;
             return;
         }
-        if (e1.op == TOK.arrayLiteral || e1.op == TOK.string_)
+        if (e1.op == EXP.arrayLiteral || e1.op == EXP.string_)
         {
-            if (iupr < ilwr || dollar < iupr)
+            if (sliceBoundsCheck(0, dollar, ilwr, iupr))
             {
                 e.error("slice `[%lld..%lld]` exceeds array bounds `[0..%lld]`", ilwr, iupr, dollar);
                 result = CTFEExp.cantexp;
@@ -5604,13 +5770,13 @@ public:
         Expression e2 = interpretRegion(e.e2, istate);
         if (exceptionOrCant(e2))
             return;
-        if (e2.op == TOK.null_)
+        if (e2.op == EXP.null_)
         {
             emplaceExp!(NullExp)(pue, e.loc, e.type);
             result = pue.exp();
             return;
         }
-        if (e2.op != TOK.assocArrayLiteral)
+        if (e2.op != EXP.assocArrayLiteral)
         {
             e.error("`%s` cannot be interpreted at compile time", e.toChars());
             result = CTFEExp.cantexp;
@@ -5618,7 +5784,7 @@ public:
         }
 
         e1 = resolveSlice(e1);
-        result = findKeyInAA(e.loc, cast(AssocArrayLiteralExp)e2, e1);
+        result = findKeyInAA(e.loc, e2.isAssocArrayLiteralExp(), e1);
         if (exceptionOrCant(result))
             return;
         if (!result)
@@ -5663,7 +5829,7 @@ public:
          * result in [x,y] and then x or y is on the stack.
          * But if they are both strings, we can, because it isn't the x~[y] case.
          */
-        if (!(e1.op == TOK.string_ && e2.op == TOK.string_))
+        if (!(e1.op == EXP.string_ && e2.op == EXP.string_))
         {
             if (e1 == ue1.exp())
                 e1 = ue1.copy();
@@ -5706,7 +5872,7 @@ public:
         if (exceptionOrCant(result))
             return;
 
-        if (result.op == TOK.null_)
+        if (result.op == EXP.null_)
         {
             result = CTFEExp.voidexp;
             return;
@@ -5716,14 +5882,14 @@ public:
         switch (tb.ty)
         {
         case Tclass:
-            if (result.op != TOK.classReference)
+            if (result.op != EXP.classReference)
             {
                 e.error("`delete` on invalid class reference `%s`", result.toChars());
                 result = CTFEExp.cantexp;
                 return;
             }
 
-            auto cre = cast(ClassReferenceExp)result;
+            auto cre = result.isClassReferenceExp();
             auto cd = cre.originalClass();
 
             // Find dtor(s) in inheritance chain
@@ -5748,56 +5914,6 @@ public:
 
             break;
 
-        case Tpointer:
-            tb = (cast(TypePointer)tb).next.toBasetype();
-            if (tb.ty == Tstruct)
-            {
-                if (result.op != TOK.address ||
-                    (cast(AddrExp)result).e1.op != TOK.structLiteral)
-                {
-                    e.error("`delete` on invalid struct pointer `%s`", result.toChars());
-                    result = CTFEExp.cantexp;
-                    return;
-                }
-
-                auto sd = (cast(TypeStruct)tb).sym;
-                auto sle = cast(StructLiteralExp)(cast(AddrExp)result).e1;
-
-                if (sd.dtor)
-                {
-                    result = interpretFunction(pue, sd.dtor, istate, null, sle);
-                    if (exceptionOrCant(result))
-                        return;
-                }
-            }
-            break;
-
-        case Tarray:
-            auto tv = tb.nextOf().baseElemOf();
-            if (tv.ty == Tstruct)
-            {
-                if (result.op != TOK.arrayLiteral)
-                {
-                    e.error("`delete` on invalid struct array `%s`", result.toChars());
-                    result = CTFEExp.cantexp;
-                    return;
-                }
-
-                auto sd = (cast(TypeStruct)tv).sym;
-
-                if (sd.dtor)
-                {
-                    auto ale = cast(ArrayLiteralExp)result;
-                    foreach (el; *ale.elements)
-                    {
-                        result = interpretFunction(pue, sd.dtor, istate, null, el);
-                        if (exceptionOrCant(result))
-                            return;
-                    }
-                }
-            }
-            break;
-
         default:
             assert(0);
         }
@@ -5819,11 +5935,11 @@ public:
             result = CTFEExp.voidexp;
             return;
         }
-        if (e.to.ty == Tpointer && e1.op != TOK.null_)
+        if (e.to.ty == Tpointer && e1.op != EXP.null_)
         {
             Type pointee = (cast(TypePointer)e.type).next;
             // Implement special cases of normally-unsafe casts
-            if (e1.op == TOK.int64)
+            if (e1.op == EXP.int64)
             {
                 // Happens with Windows HANDLEs, for example.
                 result = paintTypeOntoLiteral(pue, e.to, e1);
@@ -5869,7 +5985,7 @@ public:
 
             if (auto se = e1.isSliceExp())
             {
-                if (se.e1.op == TOK.null_)
+                if (se.e1.op == EXP.null_)
                 {
                     result = paintTypeOntoLiteral(pue, e.type, se.e1);
                     return;
@@ -5881,7 +5997,7 @@ public:
                 result = pue.exp();
                 return;
             }
-            if (e1.op == TOK.arrayLiteral || e1.op == TOK.string_)
+            if (e1.op == EXP.arrayLiteral || e1.op == EXP.string_)
             {
                 // Create a CTFE pointer &[1,2,3][0] or &"abc"[0]
                 auto ei = ctfeEmplaceExp!IndexExp(e.loc, e1, ctfeEmplaceExp!IntegerExp(e.loc, 0, Type.tsize_t));
@@ -5890,20 +6006,20 @@ public:
                 result = pue.exp();
                 return;
             }
-            if (e1.op == TOK.index && !(cast(IndexExp)e1).e1.type.equals(e1.type))
+            if (e1.op == EXP.index && !e1.isIndexExp().e1.type.equals(e1.type))
             {
                 // type painting operation
-                IndexExp ie = cast(IndexExp)e1;
+                IndexExp ie = e1.isIndexExp();
                 if (castBackFromVoid)
                 {
                     // get the original type. For strings, it's just the type...
                     Type origType = ie.e1.type.nextOf();
                     // ..but for arrays of type void*, it's the type of the element
-                    if (ie.e1.op == TOK.arrayLiteral && ie.e2.op == TOK.int64)
+                    if (ie.e1.op == EXP.arrayLiteral && ie.e2.op == EXP.int64)
                     {
-                        ArrayLiteralExp ale = cast(ArrayLiteralExp)ie.e1;
+                        ArrayLiteralExp ale = ie.e1.isArrayLiteralExp();
                         const indx = cast(size_t)ie.e2.toInteger();
-                        if (indx < ale.elements.dim)
+                        if (indx < ale.elements.length)
                         {
                             if (Expression xx = (*ale.elements)[indx])
                             {
@@ -5939,11 +6055,11 @@ public:
                     return;
                 }
 
-                if (castToSarrayPointer && pointee.toBasetype().ty == Tsarray && ae.e1.op == TOK.index)
+                if (castToSarrayPointer && pointee.toBasetype().ty == Tsarray && ae.e1.op == EXP.index)
                 {
                     // &val[idx]
                     dinteger_t dim = (cast(TypeSArray)pointee.toBasetype()).dim.toInteger();
-                    IndexExp ie = cast(IndexExp)ae.e1;
+                    IndexExp ie = ae.e1.isIndexExp();
                     Expression lwr = ie.e2;
                     Expression upr = ctfeEmplaceExp!IntegerExp(ie.e2.loc, ie.e2.toInteger() + dim, Type.tsize_t);
 
@@ -5956,7 +6072,7 @@ public:
                 }
             }
 
-            if (e1.op == TOK.variable || e1.op == TOK.symbolOffset)
+            if (e1.op == EXP.variable || e1.op == EXP.symbolOffset)
             {
                 // type painting operation
                 Type origType = (cast(SymbolExp)e1).var.type;
@@ -5969,7 +6085,7 @@ public:
                 if (auto ve = e1.isVarExp())
                     emplaceExp!(VarExp)(pue, e.loc, ve.var);
                 else
-                    emplaceExp!(SymOffExp)(pue, e.loc, (cast(SymOffExp)e1).var, (cast(SymOffExp)e1).offset);
+                    emplaceExp!(SymOffExp)(pue, e.loc, e1.isSymOffExp().var, e1.isSymOffExp().offset);
                 result = pue.exp();
                 result.type = e.to;
                 return;
@@ -5977,7 +6093,7 @@ public:
 
             // Check if we have a null pointer (eg, inside a struct)
             e1 = interpretRegion(e1, istate);
-            if (e1.op != TOK.null_)
+            if (e1.op != EXP.null_)
             {
                 e.error("pointer cast from `%s` to `%s` is not supported at compile time", e1.type.toChars(), e.to.toChars());
                 result = CTFEExp.cantexp;
@@ -5990,14 +6106,14 @@ public:
             e1 = interpretRegion(e.e1, istate);
             if (exceptionOrCant(e1))
                 return;
-            assert(e1.op == TOK.vector);
+            assert(e1.op == EXP.vector);
             e1 = interpretVectorToArray(pue, e1.isVectorExp());
         }
-        if (e.to.ty == Tarray && e1.op == TOK.slice)
+        if (e.to.ty == Tarray && e1.op == EXP.slice)
         {
             // Note that the slice may be void[], so when checking for dangerous
             // casts, we need to use the original type, which is se.e1.
-            SliceExp se = cast(SliceExp)e1;
+            SliceExp se = e1.isSliceExp();
             if (!isSafePointerCast(se.e1.type.nextOf(), e.to.nextOf()))
             {
                 e.error("array cast from `%s` to `%s` is not supported at compile time", se.e1.type.toChars(), e.to.toChars());
@@ -6023,11 +6139,11 @@ public:
         auto tobt = e.to.toBasetype();
         if (tobt.ty == Tbool && e1.type.ty == Tpointer)
         {
-            emplaceExp!(IntegerExp)(pue, e.loc, e1.op != TOK.null_, e.to);
+            emplaceExp!(IntegerExp)(pue, e.loc, e1.op != EXP.null_, e.to);
             result = pue.exp();
             return;
         }
-        else if (tobt.isTypeBasic() && e1.op == TOK.null_)
+        else if (tobt.isTypeBasic() && e1.op == EXP.null_)
         {
             if (tobt.isintegral())
                 emplaceExp!(IntegerExp)(pue, e.loc, 0, e.to);
@@ -6036,7 +6152,7 @@ public:
             result = pue.exp();
             return;
         }
-        result = ctfeCast(pue, e.loc, e.type, e.to, e1);
+        result = ctfeCast(pue, e.loc, e.type, e.to, e1, true);
     }
 
     override void visit(AssertExp e)
@@ -6051,7 +6167,7 @@ public:
         if (isTrueBool(e1))
         {
         }
-        else if (e1.isBool(false))
+        else if (e1.toBool().hasValue(false))
         {
             if (e.msg)
             {
@@ -6076,6 +6192,15 @@ public:
         return;
     }
 
+    override void visit(ThrowExp te)
+    {
+        debug (LOG)
+        {
+            printf("%s ThrowExpression::interpret()\n", te.loc.toChars());
+        }
+        interpretThrow(te.e1, te.loc);
+    }
+
     override void visit(PtrExp e)
     {
         debug (LOG)
@@ -6106,9 +6231,9 @@ public:
         // Constant fold *(&structliteral + offset)
         if (auto ae = e.e1.isAddExp())
         {
-            if (ae.e1.op == TOK.address && ae.e2.op == TOK.int64)
+            if (ae.e1.op == EXP.address && ae.e2.op == EXP.int64)
             {
-                AddrExp ade = cast(AddrExp)ae.e1;
+                AddrExp ade = ae.e1.isAddrExp();
                 Expression ex = interpretRegion(ade.e1, istate);
                 if (exceptionOrCant(ex))
                     return;
@@ -6128,7 +6253,7 @@ public:
         if (exceptionOrCant(result))
             return;
 
-        if (result.op == TOK.function_)
+        if (result.op == EXP.function_)
             return;
         if (auto soe = result.isSymOffExp())
         {
@@ -6139,12 +6264,12 @@ public:
             return;
         }
 
-        if (result.isStringExp())
+        if (result.isStringExp() || result.isArrayLiteralExp())
             return;
 
-        if (result.op != TOK.address)
+        if (result.op != EXP.address)
         {
-            if (result.op == TOK.null_)
+            if (result.op == EXP.null_)
                 e.error("dereference of null pointer `%s`", e.e1.toChars());
             else
                 e.error("dereference of invalid pointer `%s`", result.toChars());
@@ -6153,9 +6278,9 @@ public:
         }
 
         // *(&x) ==> x
-        result = (cast(AddrExp)result).e1;
+        result = result.isAddrExp().e1;
 
-        if (result.op == TOK.slice && e.type.toBasetype().ty == Tsarray)
+        if (result.op == EXP.slice && e.type.toBasetype().ty == Tsarray)
         {
             /* aggr[lwr..upr]
              * upr may exceed the upper boundary of aggr, but the check is deferred
@@ -6212,7 +6337,7 @@ public:
             return;
         }
 
-        if (ex.op == TOK.null_)
+        if (ex.op == EXP.null_)
         {
             if (ex.type.toBasetype().ty == Tclass)
                 e.error("class `%s` is `null` and cannot be dereferenced", e.e1.toChars());
@@ -6225,18 +6350,18 @@ public:
         StructLiteralExp se;
         int i;
 
-        if (ex.op != TOK.structLiteral && ex.op != TOK.classReference && ex.op != TOK.typeid_)
+        if (ex.op != EXP.structLiteral && ex.op != EXP.classReference && ex.op != EXP.typeid_)
         {
             return notImplementedYet();
         }
 
         // We can't use getField, because it makes a copy
-        if (ex.op == TOK.classReference)
+        if (ex.op == EXP.classReference)
         {
-            se = (cast(ClassReferenceExp)ex).value;
-            i = (cast(ClassReferenceExp)ex).findFieldIndexByName(v);
+            se = ex.isClassReferenceExp().value;
+            i = ex.isClassReferenceExp().findFieldIndexByName(v);
         }
-        else if (ex.op == TOK.typeid_)
+        else if (ex.op == EXP.typeid_)
         {
             if (v.ident == Identifier.idPool("name"))
             {
@@ -6255,7 +6380,7 @@ public:
         }
         else
         {
-            se = cast(StructLiteralExp)ex;
+            se = ex.isStructLiteralExp();
             i = findFieldIndexByName(se.sd, v);
         }
         if (i == -1)
@@ -6288,7 +6413,7 @@ public:
         result = (*se.elements)[i];
         if (!result)
         {
-            e.error("Internal Compiler Error: null field `%s`", v.toChars());
+            e.error("internal compiler error: null field `%s`", v.toChars());
             result = CTFEExp.cantexp;
             return;
         }
@@ -6336,7 +6461,7 @@ public:
         Expression index = interpret(e.e2, istate);
         if (exceptionOrCant(index))
             return;
-        if (agg.op == TOK.null_)
+        if (agg.op == EXP.null_)
         {
             result = CTFEExp.voidexp;
             return;
@@ -6349,7 +6474,7 @@ public:
         foreach (j, evalue; *valuesx)
         {
             Expression ekey = (*keysx)[j];
-            int eq = ctfeEqual(e.loc, TOK.equal, ekey, index);
+            int eq = ctfeEqual(e.loc, EXP.equal, ekey, index);
             if (eq)
                 ++removed;
             else if (removed != 0)
@@ -6358,8 +6483,8 @@ public:
                 (*valuesx)[j - removed] = evalue;
             }
         }
-        valuesx.dim = valuesx.dim - removed;
-        keysx.dim = keysx.dim - removed;
+        valuesx.length = valuesx.length - removed;
+        keysx.length = keysx.length - removed;
         result = IntegerExp.createBool(removed != 0);
     }
 
@@ -6379,6 +6504,33 @@ public:
     {
         assert(0); // This should never be interpreted
     }
+
+    /*********************************************
+     * Checks if the given expresion is a call to the runtime hook `id`.
+     * Params:
+     *    e = the expression to check
+     *    id = the identifier of the runtime hook
+     * Returns:
+     *    `e` cast to `CallExp` if it's the hook, `null` otherwise
+     */
+    private CallExp isRuntimeHook(Expression e, Identifier id)
+    {
+        if (auto ce = e.isCallExp())
+        {
+            if (auto ve = ce.e1.isVarExp())
+            {
+                if (auto fd = ve.var.isFuncDeclaration())
+                {
+                    // If `_d_HookTraceImpl` is found, resolve the underlying
+                    // hook and replace `e` and `fd` with it.
+                    removeHookTraceImpl(ce, fd);
+                    return fd.ident == id ? ce : null;
+                }
+            }
+        }
+
+        return null;
+    }
 }
 
 /********************************************
@@ -6435,11 +6587,11 @@ Expression interpretRegion(Expression e, InterState* istate, CTFEGoal goal = CTF
     // mimicking UnionExp.copy, but with region allocation
     switch (uexp.op)
     {
-        case TOK.cantExpression: return CTFEExp.cantexp;
-        case TOK.voidExpression: return CTFEExp.voidexp;
-        case TOK.break_:         return CTFEExp.breakexp;
-        case TOK.continue_:      return CTFEExp.continueexp;
-        case TOK.goto_:          return CTFEExp.gotoexp;
+        case EXP.cantExpression: return CTFEExp.cantexp;
+        case EXP.voidExpression: return CTFEExp.voidexp;
+        case EXP.break_:         return CTFEExp.breakexp;
+        case EXP.continue_:      return CTFEExp.continueexp;
+        case EXP.goto_:          return CTFEExp.gotoexp;
         default:                 break;
     }
     auto p = ctfeGlobals.region.malloc(uexp.size);
@@ -6454,7 +6606,7 @@ Expression interpretRegion(Expression e, InterState* istate, CTFEGoal goal = CTF
  *    istate = context
  * Returns:
  *      NULL    continue to next statement
- *      TOK.cantExpression      cannot interpret statement at compile time
+ *      EXP.cantExpression      cannot interpret statement at compile time
  *      !NULL   expression from return statement, or thrown exception
  */
 Expression interpret(UnionExp* pue, Statement s, InterState* istate)
@@ -6490,7 +6642,7 @@ private Expression scrubReturnValue(const ref Loc loc, Expression e)
      */
     static bool isVoid(const Expression e, bool checkArrayType = false) pure
     {
-        if (e.op == TOK.void_)
+        if (e.op == EXP.void_)
             return true;
 
         static bool isEntirelyVoid(const Expressions* elems)
@@ -6539,7 +6691,7 @@ private Expression scrubReturnValue(const ref Loc loc, Expression e)
             else
             {
                 e = scrubReturnValue(loc, e);
-                if (CTFEExp.isCantExp(e) || e.op == TOK.error)
+                if (CTFEExp.isCantExp(e) || e.op == EXP.error)
                     return e;
             }
         }
@@ -6560,9 +6712,9 @@ private Expression scrubReturnValue(const ref Loc loc, Expression e)
         return null;
     }
 
-    if (e.op == TOK.classReference)
+    if (e.op == EXP.classReference)
     {
-        StructLiteralExp sle = (cast(ClassReferenceExp)e).value;
+        StructLiteralExp sle = e.isClassReferenceExp().value;
         if (auto ex = scrubSE(sle))
             return ex;
     }
@@ -6640,9 +6792,9 @@ private Expression scrubCacheValue(Expression e)
         return null;
     }
 
-    if (e.op == TOK.classReference)
+    if (e.op == EXP.classReference)
     {
-        if (auto ex = scrubSE((cast(ClassReferenceExp)e).value))
+        if (auto ex = scrubSE(e.isClassReferenceExp().value))
             return ex;
     }
     else if (auto sle = e.isStructLiteralExp())
@@ -6717,14 +6869,14 @@ private Expression copyRegionExp(Expression e)
 
     switch (e.op)
     {
-        case TOK.classReference:
+        case EXP.classReference:
         {
             auto cre = e.isClassReferenceExp();
             cre.value = copyRegionExp(cre.value).isStructLiteralExp();
             break;
         }
 
-        case TOK.structLiteral:
+        case EXP.structLiteral:
         {
             auto sle = e.isStructLiteralExp();
 
@@ -6754,7 +6906,7 @@ private Expression copyRegionExp(Expression e)
             return slec;
         }
 
-        case TOK.arrayLiteral:
+        case EXP.arrayLiteral:
         {
             auto ale = e.isArrayLiteralExp();
             ale.basis = copyRegionExp(ale.basis);
@@ -6762,12 +6914,12 @@ private Expression copyRegionExp(Expression e)
             break;
         }
 
-        case TOK.assocArrayLiteral:
+        case EXP.assocArrayLiteral:
             copyArray(e.isAssocArrayLiteralExp().keys);
             copyArray(e.isAssocArrayLiteralExp().values);
             break;
 
-        case TOK.slice:
+        case EXP.slice:
         {
             auto se = e.isSliceExp();
             se.e1  = copyRegionExp(se.e1);
@@ -6776,7 +6928,7 @@ private Expression copyRegionExp(Expression e)
             break;
         }
 
-        case TOK.tuple:
+        case EXP.tuple:
         {
             auto te = e.isTupleExp();
             te.e0 = copyRegionExp(te.e0);
@@ -6784,48 +6936,48 @@ private Expression copyRegionExp(Expression e)
             break;
         }
 
-        case TOK.address:
-        case TOK.delegate_:
-        case TOK.vector:
-        case TOK.dotVariable:
+        case EXP.address:
+        case EXP.delegate_:
+        case EXP.vector:
+        case EXP.dotVariable:
         {
-            UnaExp ue = cast(UnaExp)e;
+            UnaExp ue = e.isUnaExp();
             ue.e1 = copyRegionExp(ue.e1);
             break;
         }
 
-        case TOK.index:
+        case EXP.index:
         {
-            BinExp be = cast(BinExp)e;
+            BinExp be = e.isBinExp();
             be.e1 = copyRegionExp(be.e1);
             be.e2 = copyRegionExp(be.e2);
             break;
         }
 
-        case TOK.this_:
-        case TOK.super_:
-        case TOK.variable:
-        case TOK.type:
-        case TOK.function_:
-        case TOK.typeid_:
-        case TOK.string_:
-        case TOK.int64:
-        case TOK.error:
-        case TOK.float64:
-        case TOK.complex80:
-        case TOK.null_:
-        case TOK.void_:
-        case TOK.symbolOffset:
-        case TOK.char_:
+        case EXP.this_:
+        case EXP.super_:
+        case EXP.variable:
+        case EXP.type:
+        case EXP.function_:
+        case EXP.typeid_:
+        case EXP.string_:
+        case EXP.int64:
+        case EXP.error:
+        case EXP.float64:
+        case EXP.complex80:
+        case EXP.null_:
+        case EXP.void_:
+        case EXP.symbolOffset:
+        case EXP.char_:
             break;
 
-        case TOK.cantExpression:
-        case TOK.voidExpression:
-        case TOK.showCtfeContext:
+        case EXP.cantExpression:
+        case EXP.voidExpression:
+        case EXP.showCtfeContext:
             return e;
 
         default:
-            printf("e: %s, %s\n", Token.toChars(e.op), e.toChars());
+            printf("e: %s, %s\n", EXPtoString(e.op).ptr, e.toChars());
             assert(0);
     }
 
@@ -6846,9 +6998,9 @@ private Expression interpret_length(UnionExp* pue, InterState* istate, Expressio
         return earg;
     dinteger_t len = 0;
     if (auto aae = earg.isAssocArrayLiteralExp())
-        len = aae.keys.dim;
+        len = aae.keys.length;
     else
-        assert(earg.op == TOK.null_);
+        assert(earg.op == EXP.null_);
     emplaceExp!(IntegerExp)(pue, earg.loc, len, Type.tsize_t);
     return pue.exp();
 }
@@ -6862,12 +7014,12 @@ private Expression interpret_keys(UnionExp* pue, InterState* istate, Expression
     earg = interpret(pue, earg, istate);
     if (exceptionOrCantInterpret(earg))
         return earg;
-    if (earg.op == TOK.null_)
+    if (earg.op == EXP.null_)
     {
         emplaceExp!(NullExp)(pue, earg.loc, earg.type);
         return pue.exp();
     }
-    if (earg.op != TOK.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
+    if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
         return null;
     AssocArrayLiteralExp aae = earg.isAssocArrayLiteralExp();
     auto ae = ctfeEmplaceExp!ArrayLiteralExp(aae.loc, returnType, aae.keys);
@@ -6885,12 +7037,12 @@ private Expression interpret_values(UnionExp* pue, InterState* istate, Expressio
     earg = interpret(pue, earg, istate);
     if (exceptionOrCantInterpret(earg))
         return earg;
-    if (earg.op == TOK.null_)
+    if (earg.op == EXP.null_)
     {
         emplaceExp!(NullExp)(pue, earg.loc, earg.type);
         return pue.exp();
     }
-    if (earg.op != TOK.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
+    if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
         return null;
     auto aae = earg.isAssocArrayLiteralExp();
     auto ae = ctfeEmplaceExp!ArrayLiteralExp(aae.loc, returnType, aae.values);
@@ -6909,15 +7061,15 @@ private Expression interpret_dup(UnionExp* pue, InterState* istate, Expression e
     earg = interpret(pue, earg, istate);
     if (exceptionOrCantInterpret(earg))
         return earg;
-    if (earg.op == TOK.null_)
+    if (earg.op == EXP.null_)
     {
         emplaceExp!(NullExp)(pue, earg.loc, earg.type);
         return pue.exp();
     }
-    if (earg.op != TOK.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
+    if (earg.op != EXP.assocArrayLiteral && earg.type.toBasetype().ty != Taarray)
         return null;
     auto aae = copyLiteral(earg).copy().isAssocArrayLiteralExp();
-    for (size_t i = 0; i < aae.keys.dim; i++)
+    for (size_t i = 0; i < aae.keys.length; i++)
     {
         if (Expression e = evaluatePostblit(istate, (*aae.keys)[i]))
             return e;
@@ -6935,7 +7087,7 @@ private Expression interpret_aaApply(UnionExp* pue, InterState* istate, Expressi
     aa = interpret(aa, istate);
     if (exceptionOrCantInterpret(aa))
         return aa;
-    if (aa.op != TOK.assocArrayLiteral)
+    if (aa.op != EXP.assocArrayLiteral)
     {
         emplaceExp!(IntegerExp)(pue, deleg.loc, 0, Type.tsize_t);
         return pue.exp();
@@ -6953,7 +7105,7 @@ private Expression interpret_aaApply(UnionExp* pue, InterState* istate, Expressi
 
     assert(fd && fd.fbody);
     assert(fd.parameters);
-    size_t numParams = fd.parameters.dim;
+    size_t numParams = fd.parameters.length;
     assert(numParams == 1 || numParams == 2);
 
     Parameter fparam = fd.type.isTypeFunction().parameterList[numParams - 1];
@@ -6961,12 +7113,12 @@ private Expression interpret_aaApply(UnionExp* pue, InterState* istate, Expressi
 
     Expressions args = Expressions(numParams);
 
-    AssocArrayLiteralExp ae = cast(AssocArrayLiteralExp)aa;
-    if (!ae.keys || ae.keys.dim == 0)
+    AssocArrayLiteralExp ae = aa.isAssocArrayLiteralExp();
+    if (!ae.keys || ae.keys.length == 0)
         return ctfeEmplaceExp!IntegerExp(deleg.loc, 0, Type.tsize_t);
     Expression eresult;
 
-    for (size_t i = 0; i < ae.keys.dim; ++i)
+    for (size_t i = 0; i < ae.keys.length; ++i)
     {
         Expression ekey = (*ae.keys)[i];
         Expression evalue = (*ae.values)[i];
@@ -7014,7 +7166,7 @@ private Expression foreachApplyUtf(UnionExp* pue, InterState* istate, Expression
 
     assert(fd && fd.fbody);
     assert(fd.parameters);
-    size_t numParams = fd.parameters.dim;
+    size_t numParams = fd.parameters.length;
     assert(numParams == 1 || numParams == 2);
     Type charType = (*fd.parameters)[numParams - 1].type;
     Type indexType = numParams == 2 ? (*fd.parameters)[0].type : Type.tsize_t;
@@ -7257,7 +7409,7 @@ private Expression foreachApplyUtf(UnionExp* pue, InterState* istate, Expression
 private Expression evaluateIfBuiltin(UnionExp* pue, InterState* istate, const ref Loc loc, FuncDeclaration fd, Expressions* arguments, Expression pthis)
 {
     Expression e = null;
-    size_t nargs = arguments ? arguments.dim : 0;
+    size_t nargs = arguments ? arguments.length : 0;
     if (!pthis)
     {
         if (isBuiltin(fd) != BUILTIN.unimp)
@@ -7316,12 +7468,12 @@ private Expression evaluateIfBuiltin(UnionExp* pue, InterState* istate, const re
     }
     if (pthis && !fd.fbody && fd.isCtorDeclaration() && fd.parent && fd.parent.parent && fd.parent.parent.ident == Id.object)
     {
-        if (pthis.op == TOK.classReference && fd.parent.ident == Id.Throwable)
+        if (pthis.op == EXP.classReference && fd.parent.ident == Id.Throwable)
         {
             // At present, the constructors just copy their arguments into the struct.
             // But we might need some magic if stack tracing gets added to druntime.
-            StructLiteralExp se = (cast(ClassReferenceExp)pthis).value;
-            assert(arguments.dim <= se.elements.dim);
+            StructLiteralExp se = pthis.isClassReferenceExp().value;
+            assert(arguments.length <= se.elements.length);
             foreach (i, arg; *arguments)
             {
                 auto elem = interpret(arg, istate);
@@ -7383,7 +7535,7 @@ private Expression evaluatePostblit(InterState* istate, Expression e)
         }
         return null;
     }
-    if (e.op == TOK.structLiteral)
+    if (e.op == EXP.structLiteral)
     {
         // e.__postblit()
         UnionExp ue = void;
@@ -7412,7 +7564,7 @@ private Expression evaluateDtor(InterState* istate, Expression e)
         foreach_reverse (elem; *ale.elements)
             e = evaluateDtor(istate, elem);
     }
-    else if (e.op == TOK.structLiteral)
+    else if (e.op == EXP.structLiteral)
     {
         // e.__dtor()
         e = interpretFunction(&ue, sd.dtor, istate, null, e);
@@ -7483,7 +7635,7 @@ private void removeHookTraceImpl(ref CallExp ce, ref FuncDeclaration fd)
 
     // Remove the first three trace parameters
     auto arguments = new Expressions();
-    arguments.reserve(ce.arguments.dim - 3);
+    arguments.reserve(ce.arguments.length - 3);
     arguments.pushSlice((*ce.arguments)[3 .. $]);
 
     ce = ctfeEmplaceExp!CallExp(ce.loc, ctfeEmplaceExp!VarExp(ce.loc, fd, false), arguments);