blob: 63b700096e5e042e8c84497dedb91de67c131a4c [file] [log] [blame]
/**
* The entry point for CTFE.
*
* Specification: ($LINK2 https://dlang.org/spec/function.html#interpretation, Compile Time Function Execution (CTFE))
*
* Copyright: Copyright (C) 1999-2022 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
*/
module dmd.dinterpret;
import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.string;
import dmd.apply;
import dmd.arraytypes;
import dmd.astenums;
import dmd.attrib;
import dmd.builtin;
import dmd.constfold;
import dmd.ctfeexpr;
import dmd.dclass;
import dmd.declaration;
import dmd.dstruct;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.errors;
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.mtype;
import dmd.printast;
import dmd.root.rmem;
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.visitor;
/*************************************
* Entry point for CTFE.
* A compile-time result is required. Give an error if not possible.
*
* `e` must be semantically valid expression. In other words, it should not
* contain any `ErrorExp`s in it. But, CTFE interpretation will cross over
* functions and may invoke a function that contains `ErrorStatement` in its body.
* If that, the "CTFE failed because of previous errors" error is raised.
*/
public Expression ctfeInterpret(Expression e)
{
switch (e.op)
{
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 EXP.error;
case EXP.error:
return e;
default:
break;
}
assert(e.type); // https://issues.dlang.org/show_bug.cgi?id=14642
//assert(e.type.ty != Terror); // FIXME
if (e.type.ty == Terror)
return ErrorExp.get();
auto rgnpos = ctfeGlobals.region.savePos();
Expression result = interpret(e, null);
// 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);
if (CTFEExp.isCantExp(result))
result = ErrorExp.get();
ctfeGlobals.region.release(rgnpos);
return result;
}
/* Run CTFE on the expression, but allow the expression to be a TypeExp
* or a tuple containing a TypeExp. (This is required by pragma(msg)).
*/
public Expression ctfeInterpretForPragmaMsg(Expression e)
{
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
// __traits(getOverloads))
if (auto ve = e.isVarExp())
if (ve.var.isFuncDeclaration())
{
return e;
}
auto tup = e.isTupleExp();
if (!tup)
return e.ctfeInterpret();
// Tuples need to be treated separately, since they are
// allowed to contain a TypeExp in this case.
Expressions* expsx = null;
foreach (i, g; *tup.exps)
{
auto h = ctfeInterpretForPragmaMsg(g);
if (h != g)
{
if (!expsx)
{
expsx = tup.exps.copy();
}
(*expsx)[i] = h;
}
}
if (expsx)
{
auto te = new TupleExp(e.loc, expsx);
expandTuples(te.exps);
te.type = new TypeTuple(te.exps);
return te;
}
return e;
}
public extern (C++) Expression getValue(VarDeclaration vd)
{
return ctfeGlobals.stack.getValue(vd);
}
/*************************************************
* Allocate an Expression in the ctfe region.
* Params:
* T = type of Expression to allocate
* args = arguments to Expression's constructor
* Returns:
* allocated Expression
*/
T ctfeEmplaceExp(T : Expression, Args...)(Args args)
{
if (mem.isGCEnabled)
return new T(args);
auto p = ctfeGlobals.region.malloc(__traits(classInstanceSize, T));
emplaceExp!T(p, args);
return cast(T)p;
}
// CTFE diagnostic information
public extern (C++) void printCtfePerformanceStats()
{
debug (SHOWPERFORMANCE)
{
printf(" ---- CTFE Performance ----\n");
printf("max call depth = %d\tmax stack = %d\n", ctfeGlobals.maxCallDepth, ctfeGlobals.stack.maxStackUsage());
printf("array allocs = %d\tassignments = %d\n\n", ctfeGlobals.numArrayAllocs, ctfeGlobals.numAssignments);
}
}
/**************************
*/
void incArrayAllocs()
{
++ctfeGlobals.numArrayAllocs;
}
/* ================================================ Implementation ======================================= */
private:
/***************
* Collect together globals used by CTFE
*/
struct CtfeGlobals
{
Region region;
CtfeStack stack;
int callDepth = 0; // current number of recursive calls
// When printing a stack trace, suppress this number of calls
int stackTraceCallsToSuppress = 0;
int maxCallDepth = 0; // highest number of recursive calls
int numArrayAllocs = 0; // Number of allocated arrays
int numAssignments = 0; // total number of assignments executed
}
__gshared CtfeGlobals ctfeGlobals;
enum CTFEGoal : int
{
RValue, /// Must return an Rvalue (== CTFE value)
LValue, /// Must return an Lvalue (== CTFE reference)
Nothing, /// The return value is not required
}
//debug = LOG;
//debug = LOGASSIGN;
//debug = LOGCOMPILE;
//debug = SHOWPERFORMANCE;
// Maximum allowable recursive function calls in CTFE
enum CTFE_RECURSION_LIMIT = 1000;
/**
The values of all CTFE variables
*/
struct CtfeStack
{
private:
/* The stack. Every declaration we encounter is pushed here,
* together with the VarDeclaration, and the previous
* stack address of that variable, so that we can restore it
* when we leave the stack frame.
* Note that when a function is forward referenced, the interpreter must
* run semantic3, and that may start CTFE again with a NULL istate. Thus
* the stack might not be empty when CTFE begins.
*
* Ctfe Stack addresses are just 0-based integers, but we save
* them as 'void *' because Array can only do pointers.
*/
Expressions values; // values on the stack
VarDeclarations vars; // corresponding variables
Array!(void*) savedId; // id of the previous state of that var
Array!(void*) frames; // all previous frame pointers
Expressions savedThis; // all previous values of localThis
/* Global constants get saved here after evaluation, so we never
* have to redo them. This saves a lot of time and memory.
*/
Expressions globalValues; // values of global constants
size_t framepointer; // current frame pointer
size_t maxStackPointer; // most stack we've ever used
Expression localThis; // value of 'this', or NULL if none
public:
extern (C++) size_t stackPointer()
{
return values.dim;
}
// The current value of 'this', or NULL if none
extern (C++) Expression getThis()
{
return localThis;
}
// Largest number of stack positions we've used
extern (C++) size_t maxStackUsage()
{
return maxStackPointer;
}
// Start a new stack frame, using the provided 'this'.
extern (C++) void startFrame(Expression thisexp)
{
frames.push(cast(void*)cast(size_t)framepointer);
savedThis.push(localThis);
framepointer = stackPointer();
localThis = thisexp;
}
extern (C++) void endFrame()
{
size_t oldframe = cast(size_t)frames[frames.dim - 1];
localThis = savedThis[savedThis.dim - 1];
popAll(framepointer);
framepointer = oldframe;
frames.setDim(frames.dim - 1);
savedThis.setDim(savedThis.dim - 1);
}
extern (C++) 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)
{
//printf("getValue() %s\n", v.toChars());
if ((v.isDataseg() || v.storage_class & STC.manifest) && !v.isCTFE())
{
assert(v.ctfeAdrOnStack < globalValues.dim);
return globalValues[v.ctfeAdrOnStack];
}
assert(v.ctfeAdrOnStack < stackPointer());
return values[v.ctfeAdrOnStack];
}
extern (C++) void setValue(VarDeclaration v, Expression e)
{
//printf("setValue() %s : %s\n", v.toChars(), e.toChars());
assert(!v.isDataseg() || v.isCTFE());
assert(v.ctfeAdrOnStack < stackPointer());
values[v.ctfeAdrOnStack] = e;
}
extern (C++) void push(VarDeclaration v)
{
//printf("push() %s\n", v.toChars());
assert(!v.isDataseg() || v.isCTFE());
if (v.ctfeAdrOnStack != VarDeclaration.AdrOnStackNone && v.ctfeAdrOnStack >= framepointer)
{
// Already exists in this frame, reuse it.
values[v.ctfeAdrOnStack] = null;
return;
}
savedId.push(cast(void*)cast(size_t)v.ctfeAdrOnStack);
v.ctfeAdrOnStack = cast(uint)values.dim;
vars.push(v);
values.push(null);
}
extern (C++) 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)
{
values.pop();
vars.pop();
savedId.pop();
}
}
extern (C++) void popAll(size_t stackpointer)
{
if (stackPointer() > maxStackPointer)
maxStackPointer = stackPointer();
assert(values.dim >= stackpointer);
for (size_t i = stackpointer; i < values.dim; ++i)
{
VarDeclaration v = vars[i];
v.ctfeAdrOnStack = cast(uint)cast(size_t)savedId[i];
}
values.setDim(stackpointer);
vars.setDim(stackpointer);
savedId.setDim(stackpointer);
}
extern (C++) 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;
globalValues.push(copyRegionExp(e));
}
}
private struct InterState
{
InterState* caller; // calling function's InterState
FuncDeclaration fd; // function being interpreted
Statement start; // if !=NULL, start execution at this statement
/* target of CTFEExp result; also
* target of labelled CTFEExp or
* CTFEExp. (null if no label).
*/
Statement gotoTarget;
}
/*************************************
* Attempt to interpret a function given the arguments.
* Params:
* pue = storage for result
* fd = function being called
* istate = state for calling function (NULL if none)
* arguments = function arguments
* thisarg = 'this', if a needThis() function, NULL if not.
*
* Returns:
* 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)
{
debug (LOG)
{
printf("\n********\n%s FuncDeclaration::interpret(istate = %p) %s\n", fd.loc.toChars(), istate, fd.toChars());
}
assert(pue);
if (fd.semanticRun == PASS.semantic3)
{
fd.error("circular dependency. Functions cannot be interpreted while being compiled");
return CTFEExp.cantexp;
}
if (!fd.functionSemantic3())
return CTFEExp.cantexp;
if (fd.semanticRun < PASS.semantic3done)
{
fd.error("circular dependency. Functions cannot be interpreted while being compiled");
return CTFEExp.cantexp;
}
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.error("C-style variadic functions are not yet implemented in CTFE");
return CTFEExp.cantexp;
}
// Nested functions always inherit the 'this' pointer from the parent,
// except for delegates. (Note that the 'this' pointer may be null).
// Func literals report isNested() even if they are in global scope,
// so we need to check that the parent is a function.
if (fd.isNested() && fd.toParentLocal().isFuncDeclaration() && !thisarg && istate)
thisarg = ctfeGlobals.stack.getThis();
if (fd.needThis() && !thisarg)
{
// error, no this. Prevent segfault.
// Here should be unreachable by the strict 'this' check in front-end.
fd.error("need `this` to access member `%s`", fd.toChars());
return CTFEExp.cantexp;
}
// 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);
/* Evaluate all the arguments to the function,
* store the results in eargs[]
*/
Expressions eargs = Expressions(dim);
for (size_t i = 0; i < dim; i++)
{
Expression earg = (*arguments)[i];
Parameter fparam = tf.parameterList[i];
if (fparam.isReference())
{
if (!istate && (fparam.storageClass & STC.out_))
{
// initializing an out parameter involves writing to it.
earg.error("global `%s` cannot be passed as an `out` parameter at compile time", earg.toChars());
return CTFEExp.cantexp;
}
// Convert all reference arguments into lvalue references
earg = interpretRegion(earg, istate, CTFEGoal.LValue);
if (CTFEExp.isCantExp(earg))
return earg;
}
else if (fparam.isLazy())
{
}
else
{
/* Value parameters
*/
Type ta = fparam.type.toBasetype();
if (ta.ty == Tsarray)
if (auto eaddr = earg.isAddrExp())
{
/* Static arrays are passed by a simple pointer.
* Skip past this to get at the actual arg.
*/
earg = eaddr.e1;
}
earg = interpretRegion(earg, istate);
if (CTFEExp.isCantExp(earg))
return earg;
/* Struct literals are passed by value, but we don't need to
* copy them if they are passed as const
*/
if (earg.op == EXP.structLiteral && !(fparam.storageClass & (STC.const_ | STC.immutable_)))
earg = copyLiteral(earg).copy();
}
if (auto tee = earg.isThrownExceptionExp())
{
if (istate)
return tee;
tee.generateUncaughtError();
return CTFEExp.cantexp;
}
eargs[i] = earg;
}
// Now that we've evaluated all the arguments, we can start the frame
// (this is the moment when the 'call' actually takes place).
InterState istatex;
istatex.caller = istate;
istatex.fd = fd;
if (fd.hasDualContext())
{
Expression arg0 = thisarg;
if (arg0 && arg0.type.ty == Tstruct)
{
Type t = arg0.type.pointerTo();
arg0 = ctfeEmplaceExp!AddrExp(arg0.loc, arg0);
arg0.type = t;
}
auto elements = new Expressions(2);
(*elements)[0] = arg0;
(*elements)[1] = ctfeGlobals.stack.getThis();
Type t2 = Type.tvoidptr.sarrayOf(2);
const loc = thisarg ? thisarg.loc : fd.loc;
thisarg = ctfeEmplaceExp!ArrayLiteralExp(loc, t2, elements);
thisarg = ctfeEmplaceExp!AddrExp(loc, thisarg);
thisarg.type = t2.pointerTo();
}
ctfeGlobals.stack.startFrame(thisarg);
if (fd.vthis && thisarg)
{
ctfeGlobals.stack.push(fd.vthis);
setValue(fd.vthis, thisarg);
}
for (size_t i = 0; i < dim; i++)
{
Expression earg = eargs[i];
Parameter fparam = tf.parameterList[i];
VarDeclaration v = (*fd.parameters)[i];
debug (LOG)
{
printf("arg[%zu] = %s\n", i, earg.toChars());
}
ctfeGlobals.stack.push(v);
if (fparam.isReference() && earg.op == EXP.variable &&
earg.isVarExp().var.toParent2() == fd)
{
VarDeclaration vx = earg.isVarExp().var.isVarDeclaration();
if (!vx)
{
fd.error("cannot interpret `%s` as a `ref` parameter", earg.toChars());
return CTFEExp.cantexp;
}
/* vx is a variable that is declared in fd.
* It means that fd is recursively called. e.g.
*
* void fd(int n, ref int v = dummy) {
* int vx;
* if (n == 1) fd(2, vx);
* }
* fd(1);
*
* The old value of vx on the stack in fd(1)
* should be saved at the start of fd(2, vx) call.
*/
const oldadr = vx.ctfeAdrOnStack;
ctfeGlobals.stack.push(vx);
assert(!hasValue(vx)); // vx is made uninitialized
// https://issues.dlang.org/show_bug.cgi?id=14299
// v.ctfeAdrOnStack should be saved already
// in the stack before the overwrite.
v.ctfeAdrOnStack = oldadr;
assert(hasValue(v)); // ref parameter v should refer existing value.
}
else
{
// Value parameters and non-trivial references
setValueWithoutChecking(v, earg);
}
debug (LOG)
{
printf("interpreted arg[%zu] = %s\n", i, earg.toChars());
showCtfeExpr(earg);
}
debug (LOGASSIGN)
{
printf("interpreted arg[%zu] = %s\n", i, earg.toChars());
showCtfeExpr(earg);
}
}
if (fd.vresult)
ctfeGlobals.stack.push(fd.vresult);
// Enter the function
++ctfeGlobals.callDepth;
if (ctfeGlobals.callDepth > ctfeGlobals.maxCallDepth)
ctfeGlobals.maxCallDepth = ctfeGlobals.callDepth;
Expression e = null;
while (1)
{
if (ctfeGlobals.callDepth > CTFE_RECURSION_LIMIT)
{
// This is a compiler error. It must not be suppressed.
global.gag = 0;
fd.error("CTFE recursion limit exceeded");
e = CTFEExp.cantexp;
break;
}
e = interpret(pue, fd.fbody, &istatex);
if (CTFEExp.isCantExp(e))
{
debug (LOG)
{
printf("function body failed to interpret\n");
}
}
if (istatex.start)
{
fd.error("CTFE internal error: failed to resume at statement `%s`", istatex.start.toChars());
return CTFEExp.cantexp;
}
/* This is how we deal with a recursive statement AST
* that has arbitrary goto statements in it.
* Bubble up a 'result' which is the target of the goto
* statement, then go recursively down the AST looking
* for that statement, then execute starting there.
*/
if (CTFEExp.isGotoExp(e))
{
istatex.start = istatex.gotoTarget; // set starting statement
istatex.gotoTarget = null;
}
else
{
assert(!e || (e.op != EXP.continue_ && e.op != EXP.break_));
break;
}
}
// If fell off the end of a void function, return void
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.hasDualContext() && e.op == EXP.index)
{
auto ie = e.isIndexExp();
auto pe = ie.e1.isPtrExp();
auto ve = !pe ? null : pe.e1.isVarExp();
if (ve && ve.var == fd.vthis)
{
auto ne = ie.e2.isIntegerExp();
assert(ne);
auto ale = thisarg.isAddrExp().e1.isArrayLiteralExp();
e = (*ale.elements)[cast(size_t)ne.getInteger()];
if (auto ae = e.isAddrExp())
{
e = ae.e1;
}
}
}
// Leave the function
--ctfeGlobals.callDepth;
ctfeGlobals.stack.endFrame();
// If it generated an uncaught exception, report error.
if (!istate && e.isThrownExceptionExp())
{
if (e == pue.exp())
e = pue.copy();
e.isThrownExceptionExp().generateUncaughtError();
e = CTFEExp.cantexp;
}
return e;
}
/// used to collect coverage information in ctfe
void incUsageCtfe(InterState* istate, const ref Loc loc)
{
if (global.params.ctfe_cov && istate)
{
auto line = loc.linnum;
auto mod = istate.fd.getModule();
++mod.ctfe_cov[line];
}
}
private extern (C++) final class Interpreter : Visitor
{
alias visit = Visitor.visit;
public:
InterState* istate;
CTFEGoal goal;
Expression result;
UnionExp* pue; // storage for `result`
extern (D) this(UnionExp* pue, InterState* istate, CTFEGoal goal)
{
this.pue = pue;
this.istate = istate;
this.goal = goal;
}
// 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 == EXP.cantExpression) ? CTFEExp.cantexp : e;
return true;
}
return false;
}
static Expressions* copyArrayOnWrite(Expressions* exps, Expressions* original)
{
if (exps is original)
{
if (!original)
exps = new Expressions();
else
exps = original.copy();
++ctfeGlobals.numArrayAllocs;
}
return exps;
}
/******************************** Statement ***************************/
override void visit(Statement s)
{
debug (LOG)
{
printf("%s Statement::interpret()\n", s.loc.toChars());
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
s.error("statement `%s` cannot be interpreted at compile time", s.toChars());
result = CTFEExp.cantexp;
}
override void visit(ExpStatement s)
{
debug (LOG)
{
printf("%s ExpStatement::interpret(%s)\n", s.loc.toChars(), s.exp ? s.exp.toChars() : "");
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
if (s.exp && s.exp.hasCode)
incUsageCtfe(istate, s.loc);
Expression e = interpret(pue, s.exp, istate, CTFEGoal.Nothing);
if (exceptionOrCant(e))
return;
}
override void visit(CompoundStatement s)
{
debug (LOG)
{
printf("%s CompoundStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
const dim = s.statements ? s.statements.dim : 0;
foreach (i; 0 .. dim)
{
Statement sx = (*s.statements)[i];
result = interpret(pue, sx, istate);
if (result)
break;
}
debug (LOG)
{
printf("%s -CompoundStatement::interpret() %p\n", s.loc.toChars(), result);
}
}
override void visit(UnrolledLoopStatement s)
{
debug (LOG)
{
printf("%s UnrolledLoopStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
const dim = s.statements ? s.statements.dim : 0;
foreach (i; 0 .. dim)
{
Statement sx = (*s.statements)[i];
Expression e = interpret(pue, sx, istate);
if (!e) // succeeds to interpret, or goto target was not found
continue;
if (exceptionOrCant(e))
return;
if (e.op == EXP.break_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // break at a higher level
return;
}
istate.gotoTarget = null;
result = null;
return;
}
if (e.op == EXP.continue_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // continue at a higher level
return;
}
istate.gotoTarget = null;
continue;
}
// expression from return statement, or thrown exception
result = e;
break;
}
}
override void visit(IfStatement s)
{
debug (LOG)
{
printf("%s IfStatement::interpret(%s)\n", s.loc.toChars(), s.condition.toChars());
}
incUsageCtfe(istate, s.loc);
if (istate.start == s)
istate.start = null;
if (istate.start)
{
Expression e = null;
e = interpret(s.ifbody, istate);
if (!e && istate.start)
e = interpret(s.elsebody, istate);
result = e;
return;
}
UnionExp ue = void;
Expression e = interpret(&ue, s.condition, istate);
assert(e);
if (exceptionOrCant(e))
return;
if (isTrueBool(e))
result = interpret(pue, s.ifbody, istate);
else if (e.toBool().hasValue(false))
result = interpret(pue, s.elsebody, istate);
else
{
// no error, or assert(0)?
result = CTFEExp.cantexp;
}
}
override void visit(ScopeStatement s)
{
debug (LOG)
{
printf("%s ScopeStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
result = interpret(pue, s.statement, istate);
}
/**
Given an expression e which is about to be returned from the current
function, generate an error if it contains pointers to local variables.
Only checks expressions passed by value (pointers to local variables
may already be stored in members of classes, arrays, or AAs which
were passed as mutable function parameters).
Returns:
true if it is safe to return, false if an error was generated.
*/
static bool stopPointersEscaping(const ref Loc loc, Expression e)
{
if (!e.type.hasPointers())
return true;
if (isPointer(e.type))
{
Expression x = e;
if (auto eaddr = e.isAddrExp())
x = eaddr.e1;
VarDeclaration v;
while (x.op == EXP.variable && (v = x.isVarExp().var.isVarDeclaration()) !is null)
{
if (v.storage_class & STC.ref_)
{
x = getValue(v);
if (auto eaddr = e.isAddrExp())
eaddr.e1 = x;
continue;
}
if (ctfeGlobals.stack.isInCurrentFrame(v))
{
error(loc, "returning a pointer to a local stack variable");
return false;
}
else
break;
}
// 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())
{
return stopPointersEscapingFromArray(loc, se.elements);
}
if (auto ale = e.isArrayLiteralExp())
{
return stopPointersEscapingFromArray(loc, ale.elements);
}
if (auto aae = e.isAssocArrayLiteralExp())
{
if (!stopPointersEscapingFromArray(loc, aae.keys))
return false;
return stopPointersEscapingFromArray(loc, aae.values);
}
return true;
}
// Check all elements of an array for escaping local variables. Return false if error
static bool stopPointersEscapingFromArray(const ref Loc loc, Expressions* elems)
{
foreach (e; *elems)
{
if (e && !stopPointersEscaping(loc, e))
return false;
}
return true;
}
override void visit(ReturnStatement s)
{
debug (LOG)
{
printf("%s ReturnStatement::interpret(%s)\n", s.loc.toChars(), s.exp ? s.exp.toChars() : "");
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
if (!s.exp)
{
result = CTFEExp.voidexp;
return;
}
incUsageCtfe(istate, s.loc);
assert(istate && istate.fd && istate.fd.type && istate.fd.type.ty == Tfunction);
TypeFunction tf = cast(TypeFunction)istate.fd.type;
/* If the function returns a ref AND it's been called from an assignment,
* we need to return an lvalue. Otherwise, just do an (rvalue) interpret.
*/
if (tf.isref)
{
result = interpret(pue, s.exp, istate, CTFEGoal.LValue);
return;
}
if (tf.next && tf.next.ty == Tdelegate && istate.fd.closureVars.dim > 0)
{
// To support this, we need to copy all the closure vars
// into the delegate literal.
s.error("closures are not yet supported in CTFE");
result = CTFEExp.cantexp;
return;
}
// 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))
{
result = CTFEExp.cantexp;
return;
}
if (needToCopyLiteral(e))
e = copyLiteral(e).copy();
debug (LOGASSIGN)
{
printf("RETURN %s\n", s.loc.toChars());
showCtfeExpr(e);
}
result = e;
}
static Statement findGotoTarget(InterState* istate, Identifier ident)
{
Statement target = null;
if (ident)
{
LabelDsymbol label = istate.fd.searchLabel(ident);
assert(label && label.statement);
LabelStatement ls = label.statement;
target = ls.gotoTarget ? ls.gotoTarget : ls.statement;
}
return target;
}
override void visit(BreakStatement s)
{
debug (LOG)
{
printf("%s BreakStatement::interpret()\n", s.loc.toChars());
}
incUsageCtfe(istate, s.loc);
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
istate.gotoTarget = findGotoTarget(istate, s.ident);
result = CTFEExp.breakexp;
}
override void visit(ContinueStatement s)
{
debug (LOG)
{
printf("%s ContinueStatement::interpret()\n", s.loc.toChars());
}
incUsageCtfe(istate, s.loc);
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
istate.gotoTarget = findGotoTarget(istate, s.ident);
result = CTFEExp.continueexp;
}
override void visit(WhileStatement s)
{
debug (LOG)
{
printf("WhileStatement::interpret()\n");
}
assert(0); // rewritten to ForStatement
}
override void visit(DoStatement s)
{
debug (LOG)
{
printf("%s DoStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
while (1)
{
Expression e = interpret(s._body, istate);
if (!e && istate.start) // goto target was not found
return;
assert(!istate.start);
if (exceptionOrCant(e))
return;
if (e && e.op == EXP.break_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // break at a higher level
return;
}
istate.gotoTarget = null;
break;
}
if (e && e.op == EXP.continue_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // continue at a higher level
return;
}
istate.gotoTarget = null;
e = null;
}
if (e)
{
result = e; // bubbled up from ReturnStatement
return;
}
UnionExp ue = void;
incUsageCtfe(istate, s.condition.loc);
e = interpret(&ue, s.condition, istate);
if (exceptionOrCant(e))
return;
if (!e.isConst())
{
result = CTFEExp.cantexp;
return;
}
if (e.toBool().hasValue(false))
break;
assert(isTrueBool(e));
}
assert(result is null);
}
override void visit(ForStatement s)
{
debug (LOG)
{
printf("%s ForStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
UnionExp ueinit = void;
Expression ei = interpret(&ueinit, s._init, istate);
if (exceptionOrCant(ei))
return;
assert(!ei); // s.init never returns from function, or jumps out from it
while (1)
{
if (s.condition && !istate.start)
{
UnionExp ue = void;
incUsageCtfe(istate, s.condition.loc);
Expression e = interpret(&ue, s.condition, istate);
if (exceptionOrCant(e))
return;
if (e.toBool().hasValue(false))
break;
assert(isTrueBool(e));
}
Expression e = interpret(pue, s._body, istate);
if (!e && istate.start) // goto target was not found
return;
assert(!istate.start);
if (exceptionOrCant(e))
return;
if (e && e.op == EXP.break_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // break at a higher level
return;
}
istate.gotoTarget = null;
break;
}
if (e && e.op == EXP.continue_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // continue at a higher level
return;
}
istate.gotoTarget = null;
e = null;
}
if (e)
{
result = e; // bubbled up from ReturnStatement
return;
}
UnionExp uei = void;
if (s.increment)
incUsageCtfe(istate, s.increment.loc);
e = interpret(&uei, s.increment, istate, CTFEGoal.Nothing);
if (exceptionOrCant(e))
return;
}
assert(result is null);
}
override void visit(ForeachStatement s)
{
assert(0); // rewritten to ForStatement
}
override void visit(ForeachRangeStatement s)
{
assert(0); // rewritten to ForStatement
}
override void visit(SwitchStatement s)
{
debug (LOG)
{
printf("%s SwitchStatement::interpret()\n", s.loc.toChars());
}
incUsageCtfe(istate, s.loc);
if (istate.start == s)
istate.start = null;
if (istate.start)
{
Expression e = interpret(s._body, istate);
if (istate.start) // goto target was not found
return;
if (exceptionOrCant(e))
return;
if (e && e.op == EXP.break_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // break at a higher level
return;
}
istate.gotoTarget = null;
e = null;
}
result = e;
return;
}
UnionExp uecond = void;
Expression econdition = interpret(&uecond, s.condition, istate);
if (exceptionOrCant(econdition))
return;
Statement scase = null;
if (s.cases)
foreach (cs; *s.cases)
{
UnionExp uecase = void;
Expression ecase = interpret(&uecase, cs.exp, istate);
if (exceptionOrCant(ecase))
return;
if (ctfeEqual(cs.exp.loc, EXP.equal, econdition, ecase))
{
scase = cs;
break;
}
}
if (!scase)
{
if (s.hasNoDefault)
s.error("no `default` or `case` for `%s` in `switch` statement", econdition.toChars());
scase = s.sdefault;
}
assert(scase);
/* Jump to scase
*/
istate.start = scase;
Expression e = interpret(pue, s._body, istate);
assert(!istate.start); // jump must not fail
if (e && e.op == EXP.break_)
{
if (istate.gotoTarget && istate.gotoTarget != s)
{
result = e; // break at a higher level
return;
}
istate.gotoTarget = null;
e = null;
}
result = e;
}
override void visit(CaseStatement s)
{
debug (LOG)
{
printf("%s CaseStatement::interpret(%s) this = %p\n", s.loc.toChars(), s.exp.toChars(), s);
}
incUsageCtfe(istate, s.loc);
if (istate.start == s)
istate.start = null;
result = interpret(pue, s.statement, istate);
}
override void visit(DefaultStatement s)
{
debug (LOG)
{
printf("%s DefaultStatement::interpret()\n", s.loc.toChars());
}
incUsageCtfe(istate, s.loc);
if (istate.start == s)
istate.start = null;
result = interpret(pue, s.statement, istate);
}
override void visit(GotoStatement s)
{
debug (LOG)
{
printf("%s GotoStatement::interpret()\n", s.loc.toChars());
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
incUsageCtfe(istate, s.loc);
assert(s.label && s.label.statement);
istate.gotoTarget = s.label.statement;
result = CTFEExp.gotoexp;
}
override void visit(GotoCaseStatement s)
{
debug (LOG)
{
printf("%s GotoCaseStatement::interpret()\n", s.loc.toChars());
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
incUsageCtfe(istate, s.loc);
assert(s.cs);
istate.gotoTarget = s.cs;
result = CTFEExp.gotoexp;
}
override void visit(GotoDefaultStatement s)
{
debug (LOG)
{
printf("%s GotoDefaultStatement::interpret()\n", s.loc.toChars());
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
incUsageCtfe(istate, s.loc);
assert(s.sw && s.sw.sdefault);
istate.gotoTarget = s.sw.sdefault;
result = CTFEExp.gotoexp;
}
override void visit(LabelStatement s)
{
debug (LOG)
{
printf("%s LabelStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
result = interpret(pue, s.statement, istate);
}
override void visit(TryCatchStatement s)
{
debug (LOG)
{
printf("%s TryCatchStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
if (istate.start)
{
Expression e = null;
e = interpret(pue, s._body, istate);
foreach (ca; *s.catches)
{
if (e || !istate.start) // goto target was found
break;
e = interpret(pue, ca.handler, istate);
}
result = e;
return;
}
Expression e = interpret(s._body, istate);
// An exception was thrown
if (e && e.isThrownExceptionExp())
{
ThrownExceptionExp ex = e.isThrownExceptionExp();
Type extype = ex.thrown.originalClass().type;
// Search for an appropriate catch clause.
foreach (ca; *s.catches)
{
Type catype = ca.type;
if (!catype.equals(extype) && !catype.isBaseOf(extype, null))
continue;
// Execute the handler
if (ca.var)
{
ctfeGlobals.stack.push(ca.var);
setValue(ca.var, ex.thrown);
}
e = interpret(ca.handler, istate);
if (CTFEExp.isGotoExp(e))
{
/* This is an optimization that relies on the locality of the jump target.
* If the label is in the same catch handler, the following scan
* would find it quickly and can reduce jump cost.
* Otherwise, the catch block may be unnnecessary scanned again
* so it would make CTFE speed slower.
*/
InterState istatex = *istate;
istatex.start = istate.gotoTarget; // set starting statement
istatex.gotoTarget = null;
Expression eh = interpret(ca.handler, &istatex);
if (!istatex.start)
{
istate.gotoTarget = null;
e = eh;
}
}
break;
}
}
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)
{
printf("Collided exceptions %s %s\n", oldest.thrown.toChars(), newest.thrown.toChars());
}
// Little sanity check to make sure it's really a Throwable
ClassReferenceExp boss = oldest.thrown;
const next = 4; // 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()))
{
/* Find the index of the Error.bypassException field
*/
auto bypass = next + 1;
if ((*collateral.value.elements)[bypass].type.ty == Tuns32)
bypass += 1; // skip over _refcount field
assert((*collateral.value.elements)[bypass].type.ty == Tclass);
// The new exception bypass the existing chain
(*collateral.value.elements)[bypass] = boss;
return newest;
}
while ((*boss.value.elements)[next].op == EXP.classReference)
{
boss = (*boss.value.elements)[next].isClassReferenceExp();
}
(*boss.value.elements)[next] = collateral;
return oldest;
}
override void visit(TryFinallyStatement s)
{
debug (LOG)
{
printf("%s TryFinallyStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
if (istate.start)
{
Expression e = null;
e = interpret(pue, s._body, istate);
// Jump into/out from finalbody is disabled in semantic analysis.
// and jump inside will be handled by the ScopeStatement == finalbody.
result = e;
return;
}
Expression ex = interpret(s._body, istate);
if (CTFEExp.isCantExp(ex))
{
result = ex;
return;
}
while (CTFEExp.isGotoExp(ex))
{
// If the goto target is within the body, we must not interpret the finally statement,
// because that will call destructors for objects within the scope, which we should not do.
InterState istatex = *istate;
istatex.start = istate.gotoTarget; // set starting statement
istatex.gotoTarget = null;
Expression bex = interpret(s._body, &istatex);
if (istatex.start)
{
// The goto target is outside the current scope.
break;
}
// The goto target was within the body.
if (CTFEExp.isCantExp(bex))
{
result = bex;
return;
}
*istate = istatex;
ex = bex;
}
Expression ey = interpret(s.finalbody, istate);
if (CTFEExp.isCantExp(ey))
{
result = ey;
return;
}
if (ey && ey.isThrownExceptionExp())
{
// Check for collided exceptions
if (ex && ex.isThrownExceptionExp())
ex = chainExceptions(ex.isThrownExceptionExp(), ey.isThrownExceptionExp());
else
ex = ey;
}
result = ex;
}
override void visit(ThrowStatement s)
{
debug (LOG)
{
printf("%s ThrowStatement::interpret()\n", s.loc.toChars());
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
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(exp, istate);
if (exceptionOrCant(e))
return;
assert(e.op == EXP.classReference);
result = ctfeEmplaceExp!ThrownExceptionExp(loc, e.isClassReferenceExp());
}
override void visit(ScopeGuardStatement s)
{
assert(0);
}
override void visit(WithStatement s)
{
debug (LOG)
{
printf("%s WithStatement::interpret()\n", s.loc.toChars());
}
if (istate.start == s)
istate.start = null;
if (istate.start)
{
result = s._body ? interpret(s._body, istate) : null;
return;
}
// If it is with(Enum) {...}, just execute the body.
if (s.exp.op == EXP.scope_ || s.exp.op == EXP.type)
{
result = interpret(pue, s._body, istate);
return;
}
incUsageCtfe(istate, s.loc);
Expression e = interpret(s.exp, istate);
if (exceptionOrCant(e))
return;
if (s.wthis.type.ty == Tpointer && s.exp.type.ty != Tpointer)
{
e = ctfeEmplaceExp!AddrExp(s.loc, e, s.wthis.type);
}
ctfeGlobals.stack.push(s.wthis);
setValue(s.wthis, e);
e = interpret(s._body, istate);
if (CTFEExp.isGotoExp(e))
{
/* This is an optimization that relies on the locality of the jump target.
* If the label is in the same WithStatement, the following scan
* would find it quickly and can reduce jump cost.
* Otherwise, the statement body may be unnnecessary scanned again
* so it would make CTFE speed slower.
*/
InterState istatex = *istate;
istatex.start = istate.gotoTarget; // set starting statement
istatex.gotoTarget = null;
Expression ex = interpret(s._body, &istatex);
if (!istatex.start)
{
istate.gotoTarget = null;
e = ex;
}
}
ctfeGlobals.stack.pop(s.wthis);
result = e;
}
override void visit(AsmStatement s)
{
debug (LOG)
{
printf("%s AsmStatement::interpret()\n", s.loc.toChars());
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
s.error("`asm` statements cannot be interpreted at compile time");
result = CTFEExp.cantexp;
}
override void visit(ImportStatement s)
{
debug (LOG)
{
printf("ImportStatement::interpret()\n");
}
if (istate.start)
{
if (istate.start != s)
return;
istate.start = null;
}
}
/******************************** Expression ***************************/
override void visit(Expression e)
{
debug (LOG)
{
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);
}
e.error("cannot interpret `%s` at compile time", e.toChars());
result = CTFEExp.cantexp;
}
override void visit(TypeExp e)
{
debug (LOG)
{
printf("%s TypeExp.interpret() %s\n", e.loc.toChars(), e.toChars());
}
result = e;
}
override void visit(ThisExp e)
{
debug (LOG)
{
printf("%s ThisExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (goal == CTFEGoal.LValue)
{
// We might end up here with istate being zero
// https://issues.dlang.org/show_bug.cgi?id=16382
if (istate && istate.fd.vthis)
{
result = ctfeEmplaceExp!VarExp(e.loc, istate.fd.vthis);
if (istate.fd.hasDualContext())
{
result = ctfeEmplaceExp!PtrExp(e.loc, result);
result.type = Type.tvoidptr.sarrayOf(2);
result = ctfeEmplaceExp!IndexExp(e.loc, result, IntegerExp.literal!0);
}
result.type = e.type;
}
else
result = e;
return;
}
result = ctfeGlobals.stack.getThis();
if (result)
{
if (istate && istate.fd.hasDualContext())
{
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 = result.isAddrExp().e1;
}
return;
}
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");
result = CTFEExp.cantexp;
}
override void visit(NullExp e)
{
result = e;
}
override void visit(IntegerExp e)
{
debug (LOG)
{
printf("%s IntegerExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
result = e;
}
override void visit(RealExp e)
{
debug (LOG)
{
printf("%s RealExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
result = e;
}
override void visit(ComplexExp e)
{
result = e;
}
override void visit(StringExp e)
{
debug (LOG)
{
printf("%s StringExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
/* Attempts to modify string literals are prevented
* in BinExp::interpretAssignCommon.
*/
result = e;
}
override void visit(FuncExp e)
{
debug (LOG)
{
printf("%s FuncExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
result = e;
}
override void visit(SymOffExp e)
{
debug (LOG)
{
printf("%s SymOffExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (e.var.isFuncDeclaration() && e.offset == 0)
{
result = e;
return;
}
if (isTypeInfo_Class(e.type) && e.offset == 0)
{
result = e;
return;
}
if (e.type.ty != Tpointer)
{
// Probably impossible
e.error("cannot interpret `%s` at compile time", e.toChars());
result = CTFEExp.cantexp;
return;
}
Type pointee = (cast(TypePointer)e.type).next;
if (e.var.isThreadlocal())
{
e.error("cannot take address of thread-local variable %s at compile time", e.var.toChars());
result = CTFEExp.cantexp;
return;
}
// Check for taking an address of a shared variable.
// If the shared variable is an array, the offset might not be zero.
Type fromType = null;
if (e.var.type.ty == Tarray || e.var.type.ty == Tsarray)
{
fromType = (cast(TypeArray)e.var.type).next;
}
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;
}
Expression val = getVarExp(e.loc, istate, e.var, goal);
if (exceptionOrCant(val))
return;
if (val.type.ty == Tarray || val.type.ty == Tsarray)
{
// Check for unsupported type painting operations
Type elemtype = (cast(TypeArray)val.type).next;
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())
{
size_t d = cast(size_t)(cast(TypeSArray)pointee).dim.toInteger();
Expression elwr = ctfeEmplaceExp!IntegerExp(e.loc, e.offset / elemsize, Type.tsize_t);
Expression eupr = ctfeEmplaceExp!IntegerExp(e.loc, e.offset / elemsize + d, Type.tsize_t);
// Create a CTFE pointer &val[ofs..ofs+d]
auto se = ctfeEmplaceExp!SliceExp(e.loc, val, elwr, eupr);
se.type = pointee;
emplaceExp!(AddrExp)(pue, e.loc, se, e.type);
result = pue.exp();
return;
}
if (!isSafePointerCast(elemtype, pointee))
{
// It's also OK to cast from &string to string*.
if (e.offset == 0 && isSafePointerCast(e.var.type, pointee))
{
// Create a CTFE pointer &var
auto ve = ctfeEmplaceExp!VarExp(e.loc, e.var);
ve.type = elemtype;
emplaceExp!(AddrExp)(pue, e.loc, ve, e.type);
result = pue.exp();
return;
}
e.error("reinterpreting cast from `%s` to `%s` is not supported in CTFE", val.type.toChars(), e.type.toChars());
result = CTFEExp.cantexp;
return;
}
const dinteger_t sz = pointee.size();
dinteger_t indx = e.offset / sz;
assert(sz * indx == e.offset);
Expression aggregate = null;
if (val.op == EXP.arrayLiteral || val.op == EXP.string_)
{
aggregate = val;
}
else if (auto se = val.isSliceExp())
{
aggregate = se.e1;
UnionExp uelwr = void;
Expression lwr = interpret(&uelwr, se.lwr, istate);
indx += lwr.toInteger();
}
if (aggregate)
{
// Create a CTFE pointer &aggregate[ofs]
auto ofs = ctfeEmplaceExp!IntegerExp(e.loc, indx, Type.tsize_t);
auto ei = ctfeEmplaceExp!IndexExp(e.loc, aggregate, ofs);
ei.type = elemtype;
emplaceExp!(AddrExp)(pue, e.loc, ei, e.type);
result = pue.exp();
return;
}
}
else if (e.offset == 0 && isSafePointerCast(e.var.type, pointee))
{
// Create a CTFE pointer &var
auto ve = ctfeEmplaceExp!VarExp(e.loc, e.var);
ve.type = e.var.type;
emplaceExp!(AddrExp)(pue, e.loc, ve, e.type);
result = pue.exp();
return;
}
e.error("cannot convert `&%s` to `%s` at compile time", e.var.type.toChars(), e.type.toChars());
result = CTFEExp.cantexp;
}
override void visit(AddrExp e)
{
debug (LOG)
{
printf("%s AddrExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (auto ve = e.e1.isVarExp())
{
Declaration decl = ve.var;
// We cannot take the address of an imported symbol at compile time
if (decl.isImportedSymbol()) {
e.error("cannot take address of imported symbol `%s` at compile time", decl.toChars());
result = CTFEExp.cantexp;
return;
}
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, e.e1.isVarExp().var, 0);
result = pue.exp();
result.type = e.type;
return;
}
}
auto er = interpret(e.e1, istate, CTFEGoal.LValue);
if (auto ve = er.isVarExp())
if (ve.var == istate.fd.vthis)
er = interpret(er, istate);
if (exceptionOrCant(er))
return;
// Return a simplified address expression
emplaceExp!(AddrExp)(pue, e.loc, er, e.type);
result = pue.exp();
}
override void visit(DelegateExp e)
{
debug (LOG)
{
printf("%s DelegateExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
// TODO: Really we should create a CTFE-only delegate expression
// of a pointer and a funcptr.
// If it is &nestedfunc, just return it
// TODO: We should save the context pointer
if (auto ve1 = e.e1.isVarExp())
if (ve1.var == e.func)
{
result = e;
return;
}
auto er = interpret(pue, e.e1, istate);
if (exceptionOrCant(er))
return;
if (er == e.e1)
{
// If it has already been CTFE'd, just return it
result = e;
}
else
{
er = (er == pue.exp()) ? pue.copy() : er;
emplaceExp!(DelegateExp)(pue, e.loc, er, e.func, false);
result = pue.exp();
result.type = e.type;
}
}
static Expression getVarExp(const ref Loc loc, InterState* istate, Declaration d, CTFEGoal goal)
{
Expression e = CTFEExp.cantexp;
if (VarDeclaration v = d.isVarDeclaration())
{
/* Magic variable __ctfe always returns true when interpreting
*/
if (v.ident == Id.ctfe)
return IntegerExp.createBool(true);
if (!v.originalType && v.semanticRun < PASS.semanticdone) // semantic() not yet run
{
v.dsymbolSemantic(null);
if (v.type.ty == Terror)
return CTFEExp.cantexp;
}
if ((v.isConst() || v.isImmutable() || v.storage_class & STC.manifest) && !hasValue(v) && v._init && !v.isCTFE())
{
if (v.inuse)
{
error(loc, "circular initialization of %s `%s`", v.kind(), v.toPrettyChars());
return CTFEExp.cantexp;
}
if (v._scope)
{
v.inuse++;
v._init = v._init.initializerSemantic(v._scope, v.type, INITinterpret); // might not be run on aggregate members
v.inuse--;
}
e = v._init.initializerToExpression(v.type);
if (!e)
return CTFEExp.cantexp;
assert(e.type);
if (e.op == EXP.construct || e.op == EXP.blit)
{
AssignExp ae = cast(AssignExp)e;
e = ae.e2;
}
if (e.op == EXP.error)
{
// FIXME: Ultimately all errors should be detected in prior semantic analysis stage.
}
else if (v.isDataseg() || (v.storage_class & STC.manifest))
{
/* https://issues.dlang.org/show_bug.cgi?id=14304
* e is a value that is not yet owned by CTFE.
* Mark as "cached", and use it directly during interpretation.
*/
e = scrubCacheValue(e);
ctfeGlobals.stack.saveGlobalConstant(v, e);
}
else
{
v.inuse++;
e = interpret(e, istate);
v.inuse--;
if (CTFEExp.isCantExp(e) && !global.gag && !ctfeGlobals.stackTraceCallsToSuppress)
errorSupplemental(loc, "while evaluating %s.init", v.toChars());
if (exceptionOrCantInterpret(e))
return e;
}
}
else if (v.isCTFE() && !hasValue(v))
{
if (v._init && v.type.size() != 0)
{
if (v._init.isVoidInitializer())
{
// var should have been initialized when it was created
error(loc, "CTFE internal error: trying to access uninitialized var");
assert(0);
}
e = v._init.initializerToExpression();
}
else
// Zero-length arrays don't have an initializer
e = v.type.defaultInitLiteral(e.loc);
e = interpret(e, istate);
}
else if (!(v.isDataseg() || v.storage_class & STC.manifest) && !v.isCTFE() && !istate)
{
error(loc, "variable `%s` cannot be read at compile time", v.toChars());
return CTFEExp.cantexp;
}
else
{
e = hasValue(v) ? getValue(v) : null;
if (!e)
{
// Zero-length arrays don't have an initializer
if (v.type.size() == 0)
e = v.type.defaultInitLiteral(loc);
else if (!v.isCTFE() && v.isDataseg())
{
error(loc, "static variable `%s` cannot be read at compile time", v.toChars());
return CTFEExp.cantexp;
}
else
{
assert(!(v._init && v._init.isVoidInitializer()));
// CTFE initiated from inside a function
error(loc, "variable `%s` cannot be read at compile time", v.toChars());
return CTFEExp.cantexp;
}
}
if (auto vie = e.isVoidInitExp())
{
error(loc, "cannot read uninitialized variable `%s` in ctfe", v.toPrettyChars());
errorSupplemental(vie.var.loc, "`%s` was uninitialized and used before set", vie.var.toChars());
return CTFEExp.cantexp;
}
if (goal != CTFEGoal.LValue && v.isReference())
e = interpret(e, istate, goal);
}
if (!e)
e = CTFEExp.cantexp;
}
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 == EXP.error)
error(loc, "CTFE failed because of previous errors in `%s.init`", s.toChars());
e = e.expressionSemantic(null);
if (e.op == EXP.error)
e = CTFEExp.cantexp;
else // Convert NULL to CTFEExp
e = interpret(e, istate, goal);
}
else
error(loc, "cannot interpret declaration `%s` at compile time", d.toChars());
return e;
}
override void visit(VarExp e)
{
debug (LOG)
{
printf("%s VarExp::interpret() `%s`, goal = %d\n", e.loc.toChars(), e.toChars(), goal);
}
if (e.var.isFuncDeclaration())
{
result = e;
return;
}
if (goal == CTFEGoal.LValue)
{
if (auto v = e.var.isVarDeclaration())
{
if (!hasValue(v))
{
// Compile-time known non-CTFE variable from an outer context
// e.g. global or from a ref argument
if (v.isConst() || v.isImmutable())
{
result = getVarExp(e.loc, istate, v, goal);
return;
}
if (!v.isCTFE() && v.isDataseg())
e.error("static variable `%s` cannot be read at compile time", v.toChars());
else // CTFE initiated from inside a function
e.error("variable `%s` cannot be read at compile time", v.toChars());
result = CTFEExp.cantexp;
return;
}
if (v.storage_class & (STC.out_ | STC.ref_))
{
// Strip off the nest of ref variables
Expression ev = getValue(v);
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;
}
}
}
result = e;
return;
}
result = getVarExp(e.loc, istate, e.var, goal);
if (exceptionOrCant(result))
return;
// Visit the default initializer for noreturn variables
// (Custom initializers would abort the current function call and exit above)
if (result.type.ty == Tnoreturn)
{
result.accept(this);
return;
}
if ((e.var.storage_class & (STC.ref_ | STC.out_)) == 0 && e.type.baseElemOf().ty != Tstruct)
{
/* Ultimately, STC.ref_|STC.out_ check should be enough to see the
* necessity of type repainting. But currently front-end paints
* non-ref struct variables by the const type.
*
* auto foo(ref const S cs);
* S s;
* foo(s); // VarExp('s') will have const(S)
*/
// A VarExp may include an implicit cast. It must be done explicitly.
result = paintTypeOntoLiteral(pue, e.type, result);
}
}
override void visit(DeclarationExp e)
{
debug (LOG)
{
printf("%s DeclarationExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Dsymbol s = e.declaration;
while (s.isAttribDeclaration())
{
auto ad = cast(AttribDeclaration)s;
assert(ad.decl && ad.decl.dim == 1); // Currently, only one allowed when parsing
s = (*ad.decl)[0];
}
if (VarDeclaration v = s.isVarDeclaration())
{
if (TupleDeclaration td = v.toAlias().isTupleDeclaration())
{
result = null;
// Reserve stack space for all tuple members
td.foreachVar((s)
{
VarDeclaration v2 = s.isVarDeclaration();
assert(v2);
if (v2.isDataseg() && !v2.isCTFE())
return 0;
ctfeGlobals.stack.push(v2);
if (v2._init)
{
Expression einit;
if (ExpInitializer ie = v2._init.isExpInitializer())
{
einit = interpretRegion(ie.exp, istate, goal);
if (exceptionOrCant(einit))
return 1;
}
else if (v2._init.isVoidInitializer())
{
einit = voidInitLiteral(v2.type, v2).copy();
}
else
{
e.error("declaration `%s` is not yet implemented in CTFE", e.toChars());
result = CTFEExp.cantexp;
return 1;
}
setValue(v2, einit);
}
return 0;
});
return;
}
if (v.isStatic())
{
// Just ignore static variables which aren't read or written yet
result = null;
return;
}
if (!(v.isDataseg() || v.storage_class & STC.manifest) || v.isCTFE())
ctfeGlobals.stack.push(v);
if (v._init)
{
if (ExpInitializer ie = v._init.isExpInitializer())
{
result = interpretRegion(ie.exp, istate, goal);
return;
}
else if (v._init.isVoidInitializer())
{
result = voidInitLiteral(v.type, v).copy();
// There is no AssignExp for void initializers,
// so set it here.
setValue(v, result);
return;
}
else if (v._init.isArrayInitializer())
{
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)
{
// Zero-length arrays don't need an initializer
result = v.type.defaultInitLiteral(e.loc);
}
else
{
e.error("variable `%s` cannot be modified at compile time", v.toChars());
result = CTFEExp.cantexp;
}
return;
}
if (s.isTemplateMixin() || s.isTupleDeclaration())
{
// These can be made to work, too lazy now
e.error("declaration `%s` is not yet implemented in CTFE", e.toChars());
result = CTFEExp.cantexp;
return;
}
// Others should not contain executable code, so are trivial to evaluate
result = null;
debug (LOG)
{
printf("-DeclarationExp::interpret(%s): %p\n", e.toChars(), result);
}
}
override void visit(TypeidExp e)
{
debug (LOG)
{
printf("%s TypeidExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (Type t = isType(e.obj))
{
result = e;
return;
}
if (Expression ex = isExpression(e.obj))
{
result = interpret(pue, ex, istate);
if (exceptionOrCant(ex))
return;
if (result.op == EXP.null_)
{
e.error("null pointer dereference evaluating typeid. `%s` is `null`", ex.toChars());
result = CTFEExp.cantexp;
return;
}
if (result.op != EXP.classReference)
{
e.error("CTFE internal error: determining classinfo");
result = CTFEExp.cantexp;
return;
}
ClassDeclaration cd = result.isClassReferenceExp().originalClass();
assert(cd);
emplaceExp!(TypeidExp)(pue, e.loc, cd.type);
result = pue.exp();
result.type = e.type;
return;
}
visit(cast(Expression)e);
}
override void visit(TupleExp e)
{
debug (LOG)
{
printf("%s TupleExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (exceptionOrCant(interpretRegion(e.e0, istate, CTFEGoal.Nothing)))
return;
auto expsx = e.exps;
foreach (i, exp; *expsx)
{
Expression ex = interpretRegion(exp, istate);
if (exceptionOrCant(ex))
return;
// A tuple of assignments can contain void (Bug 5676).
if (goal == CTFEGoal.Nothing)
continue;
if (ex.op == EXP.voidExpression)
{
e.error("CTFE internal error: void element `%s` in tuple", exp.toChars());
assert(0);
}
/* If any changes, do Copy On Write
*/
if (ex !is exp)
{
expsx = copyArrayOnWrite(expsx, e.exps);
(*expsx)[i] = copyRegionExp(ex);
}
}
if (expsx !is e.exps)
{
expandTuples(expsx);
emplaceExp!(TupleExp)(pue, e.loc, expsx);
result = pue.exp();
result.type = new TypeTuple(expsx);
}
else
result = e;
}
override void visit(ArrayLiteralExp e)
{
debug (LOG)
{
printf("%s ArrayLiteralExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted all the elements
{
result = e;
return;
}
Type tn = e.type.toBasetype().nextOf().toBasetype();
bool wantCopy = (tn.ty == Tsarray || tn.ty == Tstruct);
auto basis = interpretRegion(e.basis, istate);
if (exceptionOrCant(basis))
return;
auto expsx = e.elements;
size_t dim = expsx ? expsx.dim : 0;
for (size_t i = 0; i < dim; i++)
{
Expression exp = (*expsx)[i];
Expression ex;
if (!exp)
{
ex = copyLiteral(basis).copy();
}
else
{
// segfault bug 6250
assert(exp.op != EXP.index || exp.isIndexExp().e1 != e);
ex = interpretRegion(exp, istate);
if (exceptionOrCant(ex))
return;
/* Each elements should have distinct CTFE memory.
* int[1] z = 7;
* int[1][] pieces = [z,z]; // here
*/
if (wantCopy)
ex = copyLiteral(ex).copy();
}
/* If any changes, do Copy On Write
*/
if (ex !is exp)
{
expsx = copyArrayOnWrite(expsx, e.elements);
(*expsx)[i] = ex;
}
}
if (expsx !is e.elements)
{
// todo: all tuple expansions should go in semantic phase.
expandTuples(expsx);
if (expsx.dim != dim)
{
e.error("CTFE internal error: invalid array literal");
result = CTFEExp.cantexp;
return;
}
emplaceExp!(ArrayLiteralExp)(pue, e.loc, e.type, basis, expsx);
auto ale = pue.exp().isArrayLiteralExp();
ale.ownedByCtfe = OwnedBy.ctfe;
result = ale;
}
else if ((cast(TypeNext)e.type).next.mod & (MODFlags.const_ | MODFlags.immutable_))
{
// If it's immutable, we don't need to dup it
result = e;
}
else
{
*pue = copyLiteral(e);
result = pue.exp();
}
}
override void visit(AssocArrayLiteralExp e)
{
debug (LOG)
{
printf("%s AssocArrayLiteralExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
if (e.ownedByCtfe >= OwnedBy.ctfe) // We've already interpreted all the elements
{
result = e;
return;
}
auto keysx = e.keys;
auto valuesx = e.values;
foreach (i, ekey; *keysx)
{
auto evalue = (*valuesx)[i];
auto ek = interpretRegion(ekey, istate);
if (exceptionOrCant(ek))
return;
auto ev = interpretRegion(evalue, istate);
if (exceptionOrCant(ev))
return;
/* If any changes, do Copy On Write
*/
if (ek !is ekey ||
ev !is evalue)
{
keysx = copyArrayOnWrite(keysx, e.keys);
valuesx = copyArrayOnWrite(valuesx, e.values);
(*keysx)[i] = ek;
(*valuesx)[i] = ev;
}
}
if (keysx !is e.keys)
expandTuples(keysx);
if (valuesx !is e.values)
expandTuples(valuesx);
if (keysx.dim != valuesx.dim)
{
e.error("CTFE internal error: invalid AA");
result = CTFEExp.cantexp;
return;
}
/* Remove duplicate keys
*/
for (size_t i = 1; i < keysx.dim; i++)
{
auto ekey = (*keysx)[i - 1];
for (size_t j = i; j < keysx.dim; j++)
{
auto ekey2 = (*keysx)[j];
if (!ctfeEqual(e.loc, EXP.equal, ekey, ekey2))
continue;
// Remove ekey
keysx = copyArrayOnWrite(keysx, e.keys);
valuesx = copyArrayOnWrite(valuesx, e.values);
keysx.remove(i - 1);
valuesx.remove(i - 1);
i -= 1; // redo the i'th iteration
break;
}
}
if (keysx !is e.keys ||
valuesx !is e.values)
{
assert(keysx !is e.keys &&
valuesx !is e.values);
auto aae = ctfeEmplaceExp!AssocArrayLiteralExp(e.loc, keysx, valuesx);
aae.type = e.type;
aae.ownedByCtfe = OwnedBy.ctfe;
result = aae;
}
else
{
*pue = copyLiteral(e);
result = pue.exp();
}
}
override void visit(StructLiteralExp e)
{
debug (LOG)
{
printf("%s StructLiteralExp::interpret() %s ownedByCtfe = %d\n", e.loc.toChars(), e.toChars(), e.ownedByCtfe);
}
if (e.ownedByCtfe >= OwnedBy.ctfe)
{
result = e;
return;
}
size_t dim = e.elements ? e.elements.dim : 0;
auto expsx = e.elements;
if (dim != e.sd.fields.dim)
{
// guaranteed by AggregateDeclaration.fill and TypeStruct.defaultInitLiteral
const nvthis = e.sd.fields.dim - e.sd.nonHiddenFields();
assert(e.sd.fields.dim - dim == nvthis);
/* If a nested struct has no initialized hidden pointer,
* set it to null to match the runtime behaviour.
*/
foreach (const i; 0 .. nvthis)
{
auto ne = ctfeEmplaceExp!NullExp(e.loc);
auto vthis = i == 0 ? e.sd.vthis : e.sd.vthis2;
ne.type = vthis.type;
expsx = copyArrayOnWrite(expsx, e.elements);
expsx.push(ne);
++dim;
}
}
assert(dim == e.sd.fields.dim);
foreach (i; 0 .. dim)
{
auto v = e.sd.fields[i];
Expression exp = (*expsx)[i];
Expression ex;
if (!exp)
{
ex = voidInitLiteral(v.type, v).copy();
}
else
{
ex = interpretRegion(exp, istate);
if (exceptionOrCant(ex))
return;
if ((v.type.ty != ex.type.ty) && v.type.ty == Tsarray)
{
// Block assignment from inside struct literals
auto tsa = cast(TypeSArray)v.type;
auto len = cast(size_t)tsa.dim.toInteger();
UnionExp ue = void;
ex = createBlockDuplicatedArrayLiteral(&ue, ex.loc, v.type, ex, len);
if (ex == ue.exp())
ex = ue.copy();
}
}
/* If any changes, do Copy On Write
*/
if (ex !is exp)
{
expsx = copyArrayOnWrite(expsx, e.elements);
(*expsx)[i] = ex;
}
}
if (expsx !is e.elements)
{
expandTuples(expsx);
if (expsx.dim != e.sd.fields.dim)
{
e.error("CTFE internal error: invalid struct literal");
result = CTFEExp.cantexp;
return;
}
emplaceExp!(StructLiteralExp)(pue, e.loc, e.sd, expsx);
auto sle = pue.exp().isStructLiteralExp();
sle.type = e.type;
sle.ownedByCtfe = OwnedBy.ctfe;
sle.origin = e.origin;
result = sle;
}
else
{
*pue = copyLiteral(e);
result = pue.exp();
}
}
// Create an array literal of type 'newtype' with dimensions given by
// 'arguments'[argnum..$]
static Expression recursivelyCreateArrayLiteral(UnionExp* pue, const ref Loc loc, Type newtype, InterState* istate, Expressions* arguments, int argnum)
{
Expression lenExpr = interpret(pue, (*arguments)[argnum], istate);
if (exceptionOrCantInterpret(lenExpr))
return lenExpr;
size_t len = cast(size_t)lenExpr.toInteger();
Type elemType = (cast(TypeArray)newtype).next;
if (elemType.ty == Tarray && argnum < arguments.dim - 1)
{
Expression elem = recursivelyCreateArrayLiteral(pue, loc, elemType, istate, arguments, argnum + 1);
if (exceptionOrCantInterpret(elem))
return elem;
auto elements = new Expressions(len);
foreach (ref element; *elements)
element = copyLiteral(elem).copy();
emplaceExp!(ArrayLiteralExp)(pue, loc, newtype, elements);
auto ae = pue.exp().isArrayLiteralExp();
ae.ownedByCtfe = OwnedBy.ctfe;
return ae;
}
assert(argnum == arguments.dim - 1);
if (elemType.ty.isSomeChar)
{
const ch = cast(dchar)elemType.defaultInitLiteral(loc).toInteger();
const sz = cast(ubyte)elemType.size();
return createBlockDuplicatedStringLiteral(pue, loc, newtype, ch, len, sz);
}
else
{
auto el = interpret(elemType.defaultInitLiteral(loc), istate);
return createBlockDuplicatedArrayLiteral(pue, loc, newtype, el, len);
}
}
override void visit(NewExp e)
{
debug (LOG)
{
printf("%s NewExp::interpret() %s\n", e.loc.toChars(), e.toChars());
}
Expression epre = interpret(pue, e.argprefix, istate, CTFEGoal.Nothing);
if (exceptionOrCant(epre))
return;
if (e.newtype.ty == Tarray && e.arguments)
{
result = recursivelyCreateArrayLiteral(pue, e.loc, e.newtype, istate, e.arguments, 0);
return;
}
if (auto ts = e.newtype.toBasetype().isTypeStruct())
{
if (e.member)
{
Expression se = e.newtype.defaultInitLiteral(e.loc);
se = interpret(se, istate);
if (exceptionOrCant(se))
return;
result = interpretFunction(pue, e.member, istate, e.arguments, se);
// Repaint as same as CallExp::interpret() does.
result.loc = e.loc;
}
else
{
StructDeclaration sd = ts.sym;
auto exps = new Expressions();
exps.reserve(sd.fields.dim);
if (e.arguments)
{
exps.setDim(e.arguments.dim);
foreach (i, ex; *e.arguments)
{
ex = interpretRegion(ex, istate);
if (exceptionOrCant(ex))
return;
(*exps)[i] = ex;
}
}
sd.fill(e.loc, *exps, false);
auto se = ctfeEmplaceExp!StructLiteralExp(e.loc, sd, exps, e.newtype);
se.origin = se;
se.type = e.newtype;
se.ownedByCtfe = OwnedBy.ctfe;
result = interpret(pue, se, istate);
}
if (exceptionOrCant(result))
return;
Expression ev = (result == pue.exp()) ? pue.copy() : result;
emplaceExp!(AddrExp)(pue, e.loc, ev, e.type);
result = pue.exp();
return;
}
if (auto tc = e.newtype.toBasetype().isTypeClass())
{
ClassDeclaration cd = tc.sym;
size_t totalFieldCount = 0;
for (ClassDeclaration c = cd; c; c = c.baseClass)
totalFieldCount += c.fields.dim;
auto elems = new Expressions(totalFieldCount);
size_t fieldsSoFar = totalFieldCount;
for (ClassDeclaration c = cd; c; c = c.baseClass)
{
fieldsSoFar -= c.fields.dim;
foreach (i, v; c.fields)
{
if (v.inuse)
{
e.error("circular reference to `%s`", v.toPrettyChars());
result = CTFEExp.cantexp;
return;
}
Expression m;
if (v._init)
{
if (v._init.isVoidInitializer())
m = voidInitLiteral(v.type, v).copy();
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))
return;
(*elems)[fieldsSoFar + i] = copyLiteral(m).copy();
}
}
// Hack: we store a ClassDeclaration instead of a StructDeclaration.
// We probably won't get away with this.
// auto se = new StructLiteralExp(e.loc, cast(StructDeclaration)cd, elems, e.newtype);
auto se = ctfeEmplaceExp!StructLiteralExp(e.loc, cast(StructDeclaration)cd, elems, e.newtype);
se.origin = se;
se.ownedByCtfe = OwnedBy.ctfe;
Expression eref = ctfeEmplaceExp!ClassReferenceExp(e.loc, se, e.type);
if (e.member)
{
// Call constructor
if (!e.member.fbody)
{
Expression ctorfail = evaluateIfBuiltin(pue, istate, e.loc, e.member, e.arguments, eref);
if (ctorfail)
{
if (exceptionOrCant(