blob: c2967d65d5f4a7d5b52bd4cec6481fd44b1153d2 [file] [log] [blame]
/**
* Does semantic analysis for statements.
*
* Specification: $(LINK2 https://dlang.org/spec/statement.html, Statements)
*
* 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/statementsem.d, _statementsem.d)
* Documentation: https://dlang.org/phobos/dmd_statementsem.html
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/statementsem.d
*/
module dmd.statementsem;
import core.stdc.stdio;
import dmd.aggregate;
import dmd.aliasthis;
import dmd.arrayop;
import dmd.arraytypes;
import dmd.astcodegen;
import dmd.astenums;
import dmd.ast_node;
import dmd.attrib;
import dmd.blockexit;
import dmd.clone;
import dmd.cond;
import dmd.ctorflow;
import dmd.dcast;
import dmd.dclass;
import dmd.declaration;
import dmd.denum;
import dmd.dimport;
import dmd.dinterpret;
import dmd.dmodule;
import dmd.dscope;
import dmd.dsymbol;
import dmd.dsymbolsem;
import dmd.dtemplate;
import dmd.errors;
import dmd.escape;
import dmd.expression;
import dmd.expressionsem;
import dmd.func;
import dmd.globals;
import dmd.gluelayer;
import dmd.id;
import dmd.identifier;
import dmd.importc;
import dmd.init;
import dmd.intrange;
import dmd.mtype;
import dmd.nogc;
import dmd.opover;
import dmd.parse;
import dmd.printast;
import dmd.common.outbuffer;
import dmd.root.string;
import dmd.semantic2;
import dmd.sideeffect;
import dmd.statement;
import dmd.staticassert;
import dmd.target;
import dmd.tokens;
import dmd.typesem;
import dmd.visitor;
import dmd.compiler;
version (DMDLIB)
{
version = CallbackAPI;
}
/*****************************************
* CTFE requires FuncDeclaration::labtab for the interpretation.
* So fixing the label name inside in/out contracts is necessary
* for the uniqueness in labtab.
* Params:
* sc = context
* ident = statement label name to be adjusted
* Returns:
* adjusted label name
*/
private Identifier fixupLabelName(Scope* sc, Identifier ident)
{
uint flags = (sc.flags & SCOPE.contract);
const id = ident.toString();
if (flags && flags != SCOPE.invariant_ &&
!(id.length >= 2 && id[0] == '_' && id[1] == '_')) // does not start with "__"
{
OutBuffer buf;
buf.writestring(flags == SCOPE.require ? "__in_" : "__out_");
buf.writestring(ident.toString());
ident = Identifier.idPool(buf[]);
}
return ident;
}
/*******************************************
* Check to see if statement is the innermost labeled statement.
* Params:
* sc = context
* statement = Statement to check
* Returns:
* if `true`, then the `LabelStatement`, otherwise `null`
*/
private LabelStatement checkLabeledLoop(Scope* sc, Statement statement)
{
if (sc.slabel && sc.slabel.statement == statement)
{
return sc.slabel;
}
return null;
}
/***********************************************************
* Check an assignment is used as a condition.
* Intended to be use before the `semantic` call on `e`.
* Params:
* e = condition expression which is not yet run semantic analysis.
* Returns:
* `e` or ErrorExp.
*/
private Expression checkAssignmentAsCondition(Expression e, Scope* sc)
{
if (sc.flags & SCOPE.Cfile)
return e;
auto ec = lastComma(e);
if (ec.op == EXP.assign)
{
ec.error("assignment cannot be used as a condition, perhaps `==` was meant?");
return ErrorExp.get();
}
return e;
}
// Performs semantic analysis in Statement AST nodes
extern(C++) Statement statementSemantic(Statement s, Scope* sc)
{
version (CallbackAPI)
Compiler.onStatementSemanticStart(s, sc);
scope v = new StatementSemanticVisitor(sc);
s.accept(v);
version (CallbackAPI)
Compiler.onStatementSemanticDone(s, sc);
return v.result;
}
package (dmd) extern (C++) final class StatementSemanticVisitor : Visitor
{
alias visit = Visitor.visit;
Statement result;
Scope* sc;
this(Scope* sc)
{
this.sc = sc;
}
private void setError()
{
result = new ErrorStatement();
}
override void visit(Statement s)
{
result = s;
}
override void visit(ErrorStatement s)
{
result = s;
}
override void visit(PeelStatement s)
{
/* "peel" off this wrapper, and don't run semantic()
* on the result.
*/
result = s.s;
}
override void visit(ExpStatement s)
{
/* https://dlang.org/spec/statement.html#expression-statement
*/
if (!s.exp)
{
result = s;
return;
}
//printf("ExpStatement::semantic() %s\n", exp.toChars());
// Allow CommaExp in ExpStatement because return isn't used
CommaExp.allow(s.exp);
s.exp = s.exp.expressionSemantic(sc);
s.exp = resolveProperties(sc, s.exp);
s.exp = s.exp.addDtorHook(sc);
if (checkNonAssignmentArrayOp(s.exp))
s.exp = ErrorExp.get();
if (auto f = isFuncAddress(s.exp))
{
if (f.checkForwardRef(s.exp.loc))
s.exp = ErrorExp.get();
}
if (!(sc.flags & SCOPE.Cfile) && discardValue(s.exp))
s.exp = ErrorExp.get();
s.exp = s.exp.optimize(WANTvalue);
s.exp = checkGC(sc, s.exp);
if (s.exp.op == EXP.error)
return setError();
result = s;
}
override void visit(CompileStatement cs)
{
/* https://dlang.org/spec/statement.html#mixin-statement
*/
//printf("CompileStatement::semantic() %s\n", exp.toChars());
Statements* a = cs.flatten(sc);
if (!a)
return;
Statement s = new CompoundStatement(cs.loc, a);
result = s.statementSemantic(sc);
}
override void visit(CompoundStatement cs)
{
//printf("CompoundStatement::semantic(this = %p, sc = %p)\n", cs, sc);
version (none)
{
foreach (i, s; cs.statements)
{
if (s)
printf("[%d]: %s", i, s.toChars());
}
}
for (size_t i = 0; i < cs.statements.dim;)
{
Statement s = (*cs.statements)[i];
if (!s)
{
++i;
continue;
}
Statements* flt = s.flatten(sc);
if (flt)
{
cs.statements.remove(i);
cs.statements.insert(i, flt);
continue;
}
s = s.statementSemantic(sc);
(*cs.statements)[i] = s;
if (!s)
{
/* Remove NULL statements from the list.
*/
cs.statements.remove(i);
continue;
}
if (s.isErrorStatement())
{
result = s; // propagate error up the AST
++i;
continue; // look for errors in rest of statements
}
Statement sentry;
Statement sexception;
Statement sfinally;
(*cs.statements)[i] = s.scopeCode(sc, sentry, sexception, sfinally);
if (sentry)
{
sentry = sentry.statementSemantic(sc);
cs.statements.insert(i, sentry);
i++;
}
if (sexception)
sexception = sexception.statementSemantic(sc);
if (sexception)
{
/* Returns: true if statements[] are empty statements
*/
static bool isEmpty(const Statement[] statements)
{
foreach (s; statements)
{
if (const cs = s.isCompoundStatement())
{
if (!isEmpty((*cs.statements)[]))
return false;
}
else
return false;
}
return true;
}
if (!sfinally && isEmpty((*cs.statements)[i + 1 .. cs.statements.dim]))
{
}
else
{
/* Rewrite:
* s; s1; s2;
* As:
* s;
* try { s1; s2; }
* catch (Throwable __o)
* { sexception; throw __o; }
*/
auto a = new Statements();
a.pushSlice((*cs.statements)[i + 1 .. cs.statements.length]);
cs.statements.setDim(i + 1);
Statement _body = new CompoundStatement(Loc.initial, a);
_body = new ScopeStatement(Loc.initial, _body, Loc.initial);
Identifier id = Identifier.generateId("__o");
Statement handler = new PeelStatement(sexception);
if (sexception.blockExit(sc.func, false) & BE.fallthru)
{
auto ts = new ThrowStatement(Loc.initial, new IdentifierExp(Loc.initial, id));
ts.internalThrow = true;
handler = new CompoundStatement(Loc.initial, handler, ts);
}
auto catches = new Catches();
auto ctch = new Catch(Loc.initial, getThrowable(), id, handler);
ctch.internalCatch = true;
catches.push(ctch);
Statement st = new TryCatchStatement(Loc.initial, _body, catches);
if (sfinally)
st = new TryFinallyStatement(Loc.initial, st, sfinally);
st = st.statementSemantic(sc);
cs.statements.push(st);
break;
}
}
else if (sfinally)
{
if (0 && i + 1 == cs.statements.dim)
{
cs.statements.push(sfinally);
}
else
{
/* Rewrite:
* s; s1; s2;
* As:
* s; try { s1; s2; } finally { sfinally; }
*/
auto a = new Statements();
a.pushSlice((*cs.statements)[i + 1 .. cs.statements.length]);
cs.statements.setDim(i + 1);
auto _body = new CompoundStatement(Loc.initial, a);
Statement stf = new TryFinallyStatement(Loc.initial, _body, sfinally);
stf = stf.statementSemantic(sc);
cs.statements.push(stf);
break;
}
}
i++;
}
/* Flatten them in place
*/
void flatten(Statements* statements)
{
for (size_t i = 0; i < statements.length;)
{
Statement s = (*statements)[i];
if (s)
{
if (auto flt = s.flatten(sc))
{
statements.remove(i);
statements.insert(i, flt);
continue;
}
}
++i;
}
}
/* https://issues.dlang.org/show_bug.cgi?id=11653
* 'semantic' may return another CompoundStatement
* (eg. CaseRangeStatement), so flatten it here.
*/
flatten(cs.statements);
foreach (s; *cs.statements)
{
if (!s)
continue;
if (auto se = s.isErrorStatement())
{
result = se;
return;
}
}
if (cs.statements.length == 1)
{
result = (*cs.statements)[0];
return;
}
result = cs;
}
override void visit(UnrolledLoopStatement uls)
{
//printf("UnrolledLoopStatement::semantic(this = %p, sc = %p)\n", uls, sc);
Scope* scd = sc.push();
scd.sbreak = uls;
scd.scontinue = uls;
Statement serror = null;
foreach (i, ref s; *uls.statements)
{
if (s)
{
//printf("[%d]: %s\n", i, s.toChars());
s = s.statementSemantic(scd);
if (s && !serror)
serror = s.isErrorStatement();
}
}
scd.pop();
result = serror ? serror : uls;
}
override void visit(ScopeStatement ss)
{
//printf("ScopeStatement::semantic(sc = %p)\n", sc);
if (!ss.statement)
{
result = ss;
return;
}
ScopeDsymbol sym = new ScopeDsymbol();
sym.parent = sc.scopesym;
sym.endlinnum = ss.endloc.linnum;
sc = sc.push(sym);
Statements* a = ss.statement.flatten(sc);
if (a)
{
ss.statement = new CompoundStatement(ss.loc, a);
}
ss.statement = ss.statement.statementSemantic(sc);
if (ss.statement)
{
if (ss.statement.isErrorStatement())
{
sc.pop();
result = ss.statement;
return;
}
Statement sentry;
Statement sexception;
Statement sfinally;
ss.statement = ss.statement.scopeCode(sc, sentry, sexception, sfinally);
assert(!sentry);
assert(!sexception);
if (sfinally)
{
//printf("adding sfinally\n");
sfinally = sfinally.statementSemantic(sc);
ss.statement = new CompoundStatement(ss.loc, ss.statement, sfinally);
}
}
sc.pop();
result = ss;
}
override void visit(ForwardingStatement ss)
{
assert(ss.sym);
for (Scope* csc = sc; !ss.sym.forward; csc = csc.enclosing)
{
assert(csc);
ss.sym.forward = csc.scopesym;
}
sc = sc.push(ss.sym);
sc.sbreak = ss;
sc.scontinue = ss;
ss.statement = ss.statement.statementSemantic(sc);
sc = sc.pop();
result = ss.statement;
}
override void visit(WhileStatement ws)
{
/* Rewrite as a for(;condition;) loop
* https://dlang.org/spec/statement.html#while-statement
*/
Expression cond = ws.condition;
Statement _body = ws._body;
if (ws.param)
{
/**
* If the while loop is of form `while(auto a = exp) { loop_body }`,
* rewrite to:
*
* while(true)
* if (auto a = exp)
* { loop_body }
* else
* { break; }
*/
_body = new IfStatement(ws.loc, ws.param, ws.condition, ws._body, new BreakStatement(ws.loc, null), ws.endloc);
cond = IntegerExp.createBool(true);
}
Statement s = new ForStatement(ws.loc, null, cond, null, _body, ws.endloc);
s = s.statementSemantic(sc);
result = s;
}
override void visit(DoStatement ds)
{
/* https://dlang.org/spec/statement.html#do-statement
*/
const inLoopSave = sc.inLoop;
sc.inLoop = true;
if (ds._body)
ds._body = ds._body.semanticScope(sc, ds, ds, null);
sc.inLoop = inLoopSave;
if (ds.condition.op == EXP.dotIdentifier)
(cast(DotIdExp)ds.condition).noderef = true;
// check in syntax level
ds.condition = checkAssignmentAsCondition(ds.condition, sc);
ds.condition = ds.condition.expressionSemantic(sc);
ds.condition = resolveProperties(sc, ds.condition);
if (checkNonAssignmentArrayOp(ds.condition))
ds.condition = ErrorExp.get();
ds.condition = ds.condition.optimize(WANTvalue);
ds.condition = checkGC(sc, ds.condition);
ds.condition = ds.condition.toBoolean(sc);
if (ds.condition.op == EXP.error)
return setError();
if (ds._body && ds._body.isErrorStatement())
{
result = ds._body;
return;
}
result = ds;
}
override void visit(ForStatement fs)
{
/* https://dlang.org/spec/statement.html#for-statement
*/
//printf("ForStatement::semantic %s\n", fs.toChars());
if (fs._init)
{
/* Rewrite:
* for (auto v1 = i1, v2 = i2; condition; increment) { ... }
* to:
* { auto v1 = i1, v2 = i2; for (; condition; increment) { ... } }
* then lowered to:
* auto v1 = i1;
* try {
* auto v2 = i2;
* try {
* for (; condition; increment) { ... }
* } finally { v2.~this(); }
* } finally { v1.~this(); }
*/
auto ainit = new Statements();
ainit.push(fs._init);
fs._init = null;
ainit.push(fs);
Statement s = new CompoundStatement(fs.loc, ainit);
s = new ScopeStatement(fs.loc, s, fs.endloc);
s = s.statementSemantic(sc);
if (!s.isErrorStatement())
{
if (LabelStatement ls = checkLabeledLoop(sc, fs))
ls.gotoTarget = fs;
fs.relatedLabeled = s;
}
result = s;
return;
}
assert(fs._init is null);
auto sym = new ScopeDsymbol();
sym.parent = sc.scopesym;
sym.endlinnum = fs.endloc.linnum;
sc = sc.push(sym);
sc.inLoop = true;
if (fs.condition)
{
if (fs.condition.op == EXP.dotIdentifier)
(cast(DotIdExp)fs.condition).noderef = true;
// check in syntax level
fs.condition = checkAssignmentAsCondition(fs.condition, sc);
fs.condition = fs.condition.expressionSemantic(sc);
fs.condition = resolveProperties(sc, fs.condition);
if (checkNonAssignmentArrayOp(fs.condition))
fs.condition = ErrorExp.get();
fs.condition = fs.condition.optimize(WANTvalue);
fs.condition = checkGC(sc, fs.condition);
fs.condition = fs.condition.toBoolean(sc);
}
if (fs.increment)
{
CommaExp.allow(fs.increment);
fs.increment = fs.increment.expressionSemantic(sc);
fs.increment = resolveProperties(sc, fs.increment);
if (checkNonAssignmentArrayOp(fs.increment))
fs.increment = ErrorExp.get();
fs.increment = fs.increment.optimize(WANTvalue);
fs.increment = checkGC(sc, fs.increment);
}
sc.sbreak = fs;
sc.scontinue = fs;
if (fs._body)
fs._body = fs._body.semanticNoScope(sc);
sc.pop();
if (fs.condition && fs.condition.op == EXP.error ||
fs.increment && fs.increment.op == EXP.error ||
fs._body && fs._body.isErrorStatement())
return setError();
result = fs;
}
override void visit(ForeachStatement fs)
{
/* https://dlang.org/spec/statement.html#foreach-statement
*/
//printf("ForeachStatement::semantic() %p\n", fs);
/******
* Issue error if any of the ForeachTypes were not supplied and could not be inferred.
* Returns:
* true if error issued
*/
static bool checkForArgTypes(ForeachStatement fs)
{
bool result = false;
foreach (p; *fs.parameters)
{
if (!p.type)
{
fs.error("cannot infer type for `foreach` variable `%s`, perhaps set it explicitly", p.ident.toChars());
p.type = Type.terror;
result = true;
}
}
return result;
}
const loc = fs.loc;
const dim = fs.parameters.dim;
fs.func = sc.func;
if (fs.func.fes)
fs.func = fs.func.fes.func;
VarDeclaration vinit = null;
fs.aggr = fs.aggr.expressionSemantic(sc);
fs.aggr = resolveProperties(sc, fs.aggr);
fs.aggr = fs.aggr.optimize(WANTvalue);
if (fs.aggr.op == EXP.error)
return setError();
Expression oaggr = fs.aggr; // remember original for error messages
if (fs.aggr.type && fs.aggr.type.toBasetype().ty == Tstruct &&
(cast(TypeStruct)(fs.aggr.type.toBasetype())).sym.dtor &&
!fs.aggr.isTypeExp() && !fs.aggr.isLvalue())
{
// https://issues.dlang.org/show_bug.cgi?id=14653
// Extend the life of rvalue aggregate till the end of foreach.
vinit = copyToTemp(STC.rvalue, "__aggr", fs.aggr);
vinit.endlinnum = fs.endloc.linnum;
vinit.dsymbolSemantic(sc);
fs.aggr = new VarExp(fs.aggr.loc, vinit);
}
/* If aggregate is a vector type, add the .array to make it a static array
*/
if (fs.aggr.type)
if (auto tv = fs.aggr.type.toBasetype().isTypeVector())
{
auto vae = new VectorArrayExp(fs.aggr.loc, fs.aggr);
vae.type = tv.basetype;
fs.aggr = vae;
}
Dsymbol sapply = null; // the inferred opApply() or front() function
if (!inferForeachAggregate(sc, fs.op == TOK.foreach_, fs.aggr, sapply))
{
assert(oaggr.type);
fs.error("invalid `foreach` aggregate `%s` of type `%s`", oaggr.toChars(), oaggr.type.toPrettyChars());
if (isAggregate(fs.aggr.type))
fs.loc.errorSupplemental("maybe define `opApply()`, range primitives, or use `.tupleof`");
return setError();
}
Dsymbol sapplyOld = sapply; // 'sapply' will be NULL if and after 'inferApplyArgTypes' errors
/* Check for inference errors
*/
if (!inferApplyArgTypes(fs, sc, sapply))
{
/**
Try and extract the parameter count of the opApply callback function, e.g.:
int opApply(int delegate(int, float)) => 2 args
*/
bool foundMismatch = false;
size_t foreachParamCount = 0;
if (sapplyOld)
{
if (FuncDeclaration fd = sapplyOld.isFuncDeclaration())
{
auto fparameters = fd.getParameterList();
if (fparameters.length == 1)
{
// first param should be the callback function
Parameter fparam = fparameters[0];
if ((fparam.type.ty == Tpointer ||
fparam.type.ty == Tdelegate) &&
fparam.type.nextOf().ty == Tfunction)
{
TypeFunction tf = cast(TypeFunction)fparam.type.nextOf();
foreachParamCount = tf.parameterList.length;
foundMismatch = true;
}
}
}
}
//printf("dim = %d, parameters.dim = %d\n", dim, parameters.dim);
if (foundMismatch && dim != foreachParamCount)
{
const(char)* plural = foreachParamCount > 1 ? "s" : "";
fs.error("cannot infer argument types, expected %llu argument%s, not %llu",
cast(ulong) foreachParamCount, plural, cast(ulong) dim);
}
else
fs.error("cannot uniquely infer `foreach` argument types");
return setError();
}
Type tab = fs.aggr.type.toBasetype();
if (tab.ty == Ttuple) // don't generate new scope for tuple loops
{
Statement s = makeTupleForeach(sc, false, false, fs, null, false).statement;
if (vinit)
s = new CompoundStatement(loc, new ExpStatement(loc, vinit), s);
result = s.statementSemantic(sc);
return;
}
auto sym = new ScopeDsymbol();
sym.parent = sc.scopesym;
sym.endlinnum = fs.endloc.linnum;
auto sc2 = sc.push(sym);
sc2.inLoop = true;
foreach (Parameter p; *fs.parameters)
{
if (p.storageClass & STC.manifest)
{
fs.error("cannot declare `enum` loop variables for non-unrolled foreach");
}
if (p.storageClass & STC.alias_)
{
fs.error("cannot declare `alias` loop variables for non-unrolled foreach");
}
}
void retError()
{
sc2.pop();
result = new ErrorStatement();
}
void rangeError()
{
fs.error("cannot infer argument types");
return retError();
}
void retStmt(Statement s)
{
if (!s)
return retError();
s = s.statementSemantic(sc2);
sc2.pop();
result = s;
}
TypeAArray taa = null;
Type tn = null;
Type tnv = null;
Statement apply()
{
if (checkForArgTypes(fs))
return null;
TypeFunction tfld = null;
if (sapply)
{
FuncDeclaration fdapply = sapply.isFuncDeclaration();
if (fdapply)
{
assert(fdapply.type && fdapply.type.ty == Tfunction);
tfld = cast(TypeFunction)fdapply.type.typeSemantic(loc, sc2);
goto Lget;
}
else if (tab.ty == Tdelegate)
{
tfld = cast(TypeFunction)tab.nextOf();
Lget:
//printf("tfld = %s\n", tfld.toChars());
if (tfld.parameterList.parameters.dim == 1)
{
Parameter p = tfld.parameterList[0];
if (p.type && p.type.ty == Tdelegate)
{
auto t = p.type.typeSemantic(loc, sc2);
assert(t.ty == Tdelegate);
tfld = cast(TypeFunction)t.nextOf();
}
}
}
}
FuncExp flde = foreachBodyToFunction(sc2, fs, tfld);
if (!flde)
return null;
// Resolve any forward referenced goto's
foreach (ScopeStatement ss; *fs.gotos)
{
GotoStatement gs = ss.statement.isGotoStatement();
if (!gs.label.statement)
{
// 'Promote' it to this scope, and replace with a return
fs.cases.push(gs);
ss.statement = new ReturnStatement(Loc.initial, new IntegerExp(fs.cases.dim + 1));
}
}
Expression e = null;
Expression ec;
if (vinit)
{
e = new DeclarationExp(loc, vinit);
e = e.expressionSemantic(sc2);
if (e.op == EXP.error)
return null;
}
if (taa)
ec = applyAssocArray(fs, flde, taa);
else if (tab.ty == Tarray || tab.ty == Tsarray)
ec = applyArray(fs, flde, sc2, tn, tnv, tab.ty);
else if (tab.ty == Tdelegate)
ec = applyDelegate(fs, flde, sc2, tab);
else
ec = applyOpApply(fs, tab, sapply, sc2, flde);
if (!ec)
return null;
e = Expression.combine(e, ec);
return loopReturn(e, fs.cases, loc);
}
switch (tab.ty)
{
case Tarray:
case Tsarray:
{
if (checkForArgTypes(fs))
return retError();
if (dim < 1 || dim > 2)
{
fs.error("only one or two arguments for array `foreach`");
return retError();
}
// Finish semantic on all foreach parameter types.
foreach (i; 0 .. dim)
{
Parameter p = (*fs.parameters)[i];
p.type = p.type.typeSemantic(loc, sc2);
p.type = p.type.addStorageClass(p.storageClass);
}
tn = tab.nextOf().toBasetype();
if (dim == 2)
{
Type tindex = (*fs.parameters)[0].type;
if (!tindex.isintegral())
{
fs.error("foreach: key cannot be of non-integral type `%s`", tindex.toChars());
return retError();
}
/* What cases to deprecate implicit conversions for:
* 1. foreach aggregate is a dynamic array
* 2. foreach body is lowered to _aApply (see special case below).
*/
Type tv = (*fs.parameters)[1].type.toBasetype();
if ((tab.ty == Tarray ||
(tn.ty != tv.ty && tn.ty.isSomeChar && tv.ty.isSomeChar)) &&
!Type.tsize_t.implicitConvTo(tindex))
{
fs.deprecation("foreach: loop index implicitly converted from `size_t` to `%s`",
tindex.toChars());
}
}
/* Look for special case of parsing char types out of char type
* array.
*/
if (tn.ty.isSomeChar)
{
int i = (dim == 1) ? 0 : 1; // index of value
Parameter p = (*fs.parameters)[i];
tnv = p.type.toBasetype();
if (tnv.ty != tn.ty && tnv.ty.isSomeChar)
{
if (p.storageClass & STC.ref_)
{
fs.error("`foreach`: value of UTF conversion cannot be `ref`");
return retError();
}
if (dim == 2)
{
p = (*fs.parameters)[0];
if (p.storageClass & STC.ref_)
{
fs.error("`foreach`: key cannot be `ref`");
return retError();
}
}
return retStmt(apply());
}
}
// Declare the key
if (dim == 2)
{
Parameter p = (*fs.parameters)[0];
fs.key = new VarDeclaration(loc, p.type.mutableOf(), Identifier.generateId("__key"), null);
fs.key.storage_class |= STC.temp | STC.foreach_;
if (fs.key.isReference())
fs.key.storage_class |= STC.nodtor;
if (p.storageClass & STC.ref_)
{
if (fs.key.type.constConv(p.type) == MATCH.nomatch)
{
fs.error("key type mismatch, `%s` to `ref %s`",
fs.key.type.toChars(), p.type.toChars());
return retError();
}
}
if (tab.ty == Tsarray)
{
TypeSArray ta = cast(TypeSArray)tab;
IntRange dimrange = getIntRange(ta.dim);
// https://issues.dlang.org/show_bug.cgi?id=12504
dimrange.imax = SignExtendedNumber(dimrange.imax.value-1);
if (!IntRange.fromType(fs.key.type).contains(dimrange))
{
fs.error("index type `%s` cannot cover index range 0..%llu",
p.type.toChars(), ta.dim.toInteger());
return retError();
}
fs.key.range = new IntRange(SignExtendedNumber(0), dimrange.imax);
}
}
// Now declare the value
{
Parameter p = (*fs.parameters)[dim - 1];
fs.value = new VarDeclaration(loc, p.type, p.ident, null);
fs.value.storage_class |= STC.foreach_;
fs.value.storage_class |= p.storageClass & (STC.scope_ | STC.IOR | STC.TYPECTOR);
if (fs.value.isReference())
{
fs.value.storage_class |= STC.nodtor;
if (fs.aggr.checkModifiable(sc2, ModifyFlags.noError) == Modifiable.initialization)
fs.value.setInCtorOnly = true;
Type t = tab.nextOf();
if (t.constConv(p.type) == MATCH.nomatch)
{
fs.error("argument type mismatch, `%s` to `ref %s`",
t.toChars(), p.type.toChars());
return retError();
}
}
}
/* Convert to a ForStatement
* foreach (key, value; a) body =>
* for (T[] tmp = a[], size_t key; key < tmp.length; ++key)
* { T value = tmp[k]; body }
*
* foreach_reverse (key, value; a) body =>
* for (T[] tmp = a[], size_t key = tmp.length; key--; )
* { T value = tmp[k]; body }
*/
auto id = Identifier.generateId("__r");
auto ie = new ExpInitializer(loc, new SliceExp(loc, fs.aggr, null, null));
const valueIsRef = (*fs.parameters)[$ - 1].isReference();
VarDeclaration tmp;
if (fs.aggr.op == EXP.arrayLiteral && !valueIsRef)
{
auto ale = cast(ArrayLiteralExp)fs.aggr;
size_t edim = ale.elements ? ale.elements.dim : 0;
auto telem = (*fs.parameters)[dim - 1].type;
// https://issues.dlang.org/show_bug.cgi?id=12936
// if telem has been specified explicitly,
// converting array literal elements to telem might make it @nogc.
fs.aggr = fs.aggr.implicitCastTo(sc, telem.sarrayOf(edim));
if (fs.aggr.op == EXP.error)
return retError();
// for (T[edim] tmp = a, ...)
tmp = new VarDeclaration(loc, fs.aggr.type, id, ie);
}
else
{
tmp = new VarDeclaration(loc, tab.nextOf().arrayOf(), id, ie);
if (!valueIsRef)
tmp.storage_class |= STC.scope_;
}
tmp.storage_class |= STC.temp;
Expression tmp_length = new DotIdExp(loc, new VarExp(loc, tmp), Id.length);
if (!fs.key)
{
Identifier idkey = Identifier.generateId("__key");
fs.key = new VarDeclaration(loc, Type.tsize_t, idkey, null);
fs.key.storage_class |= STC.temp;
}
else if (fs.key.type.ty != Type.tsize_t.ty)
{
tmp_length = new CastExp(loc, tmp_length, fs.key.type);
}
if (fs.op == TOK.foreach_reverse_)
fs.key._init = new ExpInitializer(loc, tmp_length);
else
fs.key._init = new ExpInitializer(loc, new IntegerExp(loc, 0, fs.key.type));
auto cs = new Statements();
if (vinit)
cs.push(new ExpStatement(loc, vinit));
cs.push(new ExpStatement(loc, tmp));
cs.push(new ExpStatement(loc, fs.key));
Statement forinit = new CompoundDeclarationStatement(loc, cs);
Expression cond;
if (fs.op == TOK.foreach_reverse_)
{
// key--
cond = new PostExp(EXP.minusMinus, loc, new VarExp(loc, fs.key));
}
else
{
// key < tmp.length
cond = new CmpExp(EXP.lessThan, loc, new VarExp(loc, fs.key), tmp_length);
}
Expression increment = null;
if (fs.op == TOK.foreach_)
{
// key += 1
increment = new AddAssignExp(loc, new VarExp(loc, fs.key), new IntegerExp(loc, 1, fs.key.type));
}
// T value = tmp[key];
IndexExp indexExp = new IndexExp(loc, new VarExp(loc, tmp), new VarExp(loc, fs.key));
indexExp.indexIsInBounds = true; // disabling bounds checking in foreach statements.
fs.value._init = new ExpInitializer(loc, indexExp);
Statement ds = new ExpStatement(loc, fs.value);
if (dim == 2)
{
Parameter p = (*fs.parameters)[0];
if ((p.storageClass & STC.ref_) && p.type.equals(fs.key.type))
{
fs.key.range = null;
auto v = new AliasDeclaration(loc, p.ident, fs.key);
fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body);
}
else
{
auto ei = new ExpInitializer(loc, new IdentifierExp(loc, fs.key.ident));
auto v = new VarDeclaration(loc, p.type, p.ident, ei);
v.storage_class |= STC.foreach_ | (p.storageClass & STC.ref_);
fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body);
if (fs.key.range && !p.type.isMutable())
{
/* Limit the range of the key to the specified range
*/
v.range = new IntRange(fs.key.range.imin, fs.key.range.imax - SignExtendedNumber(1));
}
}
}
fs._body = new CompoundStatement(loc, ds, fs._body);
Statement s = new ForStatement(loc, forinit, cond, increment, fs._body, fs.endloc);
if (auto ls = checkLabeledLoop(sc, fs)) // https://issues.dlang.org/show_bug.cgi?id=15450
// don't use sc2
ls.gotoTarget = s;
return retStmt(s);
}
case Taarray:
if (fs.op == TOK.foreach_reverse_)
fs.warning("cannot use `foreach_reverse` with an associative array");
if (checkForArgTypes(fs))
return retError();
taa = cast(TypeAArray)tab;
if (dim < 1 || dim > 2)
{
fs.error("only one or two arguments for associative array `foreach`");
return retError();
}
return retStmt(apply());
case Tclass:
case Tstruct:
/* Prefer using opApply, if it exists
*/
if (sapply)
return retStmt(apply());
{
/* Look for range iteration, i.e. the properties
* .empty, .popFront, .popBack, .front and .back
* foreach (e; aggr) { ... }
* translates to:
* for (auto __r = aggr[]; !__r.empty; __r.popFront()) {
* auto e = __r.front;
* ...
* }
*/
auto ad = (tab.ty == Tclass) ?
cast(AggregateDeclaration)(cast(TypeClass)tab).sym :
cast(AggregateDeclaration)(cast(TypeStruct)tab).sym;
Identifier idfront;
Identifier idpopFront;
if (fs.op == TOK.foreach_)
{
idfront = Id.Ffront;
idpopFront = Id.FpopFront;
}
else
{
idfront = Id.Fback;
idpopFront = Id.FpopBack;
}
auto sfront = ad.search(Loc.initial, idfront);
if (!sfront)
return retStmt(apply());
/* Generate a temporary __r and initialize it with the aggregate.
*/
VarDeclaration r;
Statement _init;
if (vinit && fs.aggr.op == EXP.variable && (cast(VarExp)fs.aggr).var == vinit)
{
r = vinit;
_init = new ExpStatement(loc, vinit);
}
else
{
r = copyToTemp(0, "__r", fs.aggr);
r.dsymbolSemantic(sc);
_init = new ExpStatement(loc, r);
if (vinit)
_init = new CompoundStatement(loc, new ExpStatement(loc, vinit), _init);
}
// !__r.empty
Expression e = new VarExp(loc, r);
e = new DotIdExp(loc, e, Id.Fempty);
Expression condition = new NotExp(loc, e);
// __r.idpopFront()
e = new VarExp(loc, r);
Expression increment = new CallExp(loc, new DotIdExp(loc, e, idpopFront));
/* Declaration statement for e:
* auto e = __r.idfront;
*/
e = new VarExp(loc, r);
Expression einit = new DotIdExp(loc, e, idfront);
Statement makeargs, forbody;
bool ignoreRef = false; // If a range returns a non-ref front we ignore ref on foreach
Type tfront;
if (auto fd = sfront.isFuncDeclaration())
{
if (!fd.functionSemantic())
return rangeError();
tfront = fd.type;
}
else if (auto td = sfront.isTemplateDeclaration())
{
Expressions a;
if (auto f = resolveFuncCall(loc, sc, td, null, tab, &a, FuncResolveFlag.quiet))
tfront = f.type;
}
else if (auto d = sfront.toAlias().isDeclaration())
{
tfront = d.type;
}
if (!tfront || tfront.ty == Terror)
return rangeError();
if (tfront.toBasetype().ty == Tfunction)
{
auto ftt = cast(TypeFunction)tfront.toBasetype();
tfront = tfront.toBasetype().nextOf();
if (!ftt.isref)
{
// .front() does not return a ref. We ignore ref on foreach arg.
// see https://issues.dlang.org/show_bug.cgi?id=11934
if (tfront.needsDestruction()) ignoreRef = true;
}
}
if (tfront.ty == Tvoid)
{
fs.error("`%s.front` is `void` and has no value", oaggr.toChars());
return retError();
}
if (dim == 1)
{
auto p = (*fs.parameters)[0];
auto ve = new VarDeclaration(loc, p.type, p.ident, new ExpInitializer(loc, einit));
ve.storage_class |= STC.foreach_;
ve.storage_class |= p.storageClass & (STC.scope_ | STC.IOR | STC.TYPECTOR);
if (ignoreRef)
ve.storage_class &= ~STC.ref_;
makeargs = new ExpStatement(loc, ve);
}
else
{
auto vd = copyToTemp(STC.ref_, "__front", einit);
vd.dsymbolSemantic(sc);
makeargs = new ExpStatement(loc, vd);
// Resolve inout qualifier of front type
tfront = tfront.substWildTo(tab.mod);
Expression ve = new VarExp(loc, vd);
ve.type = tfront;
auto exps = new Expressions();
exps.push(ve);
int pos = 0;
while (exps.dim < dim)
{
pos = expandAliasThisTuples(exps, pos);
if (pos == -1)
break;
}
if (exps.dim != dim)
{
const(char)* plural = exps.dim > 1 ? "s" : "";
fs.error("cannot infer argument types, expected %llu argument%s, not %llu",
cast(ulong) exps.dim, plural, cast(ulong) dim);
return retError();
}
foreach (i; 0 .. dim)
{
auto p = (*fs.parameters)[i];
auto exp = (*exps)[i];
version (none)
{
printf("[%d] p = %s %s, exp = %s %s\n", i,
p.type ? p.type.toChars() : "?", p.ident.toChars(),
exp.type.toChars(), exp.toChars());
}
if (!p.type)
p.type = exp.type;
auto sc = p.storageClass;
if (ignoreRef) sc &= ~STC.ref_;
p.type = p.type.addStorageClass(sc).typeSemantic(loc, sc2);
if (!exp.implicitConvTo(p.type))
return rangeError();
auto var = new VarDeclaration(loc, p.type, p.ident, new ExpInitializer(loc, exp));
var.storage_class |= STC.ctfe | STC.ref_ | STC.foreach_;
makeargs = new CompoundStatement(loc, makeargs, new ExpStatement(loc, var));
}
}
forbody = new CompoundStatement(loc, makeargs, fs._body);
Statement s = new ForStatement(loc, _init, condition, increment, forbody, fs.endloc);
if (auto ls = checkLabeledLoop(sc, fs))
ls.gotoTarget = s;
version (none)
{
printf("init: %s\n", _init.toChars());
printf("condition: %s\n", condition.toChars());
printf("increment: %s\n", increment.toChars());
printf("body: %s\n", forbody.toChars());
}
return retStmt(s);
}
case Tdelegate:
if (fs.op == TOK.foreach_reverse_)
fs.deprecation("cannot use `foreach_reverse` with a delegate");
return retStmt(apply());
case Terror:
return retError();
default:
fs.error("`foreach`: `%s` is not an aggregate type", fs.aggr.type.toChars());
return retError();
}
}
private static extern(D) Expression applyOpApply(ForeachStatement fs, Type tab, Dsymbol sapply,
Scope* sc2, Expression flde)
{
version (none)
{
if (global.params.useDIP1000 == FeatureState.enabled)
{
message(loc, "To enforce `@safe`, the compiler allocates a closure unless `opApply()` uses `scope`");
}
(cast(FuncExp)flde).fd.tookAddressOf = 1;
}
else
{
if (global.params.useDIP1000 == FeatureState.enabled)
++(cast(FuncExp)flde).fd.tookAddressOf; // allocate a closure unless the opApply() uses 'scope'
}
assert(tab.ty == Tstruct || tab.ty == Tclass);
assert(sapply);
/* Call:
* aggr.apply(flde)
*/
Expression ec;
ec = new DotIdExp(fs.loc, fs.aggr, sapply.ident);
ec = new CallExp(fs.loc, ec, flde);
ec = ec.expressionSemantic(sc2);
if (ec.op == EXP.error)
return null;
if (ec.type != Type.tint32)
{
fs.error("`opApply()` function for `%s` must return an `int`", tab.toChars());
return null;
}
return ec;
}
private static extern(D) Expression applyDelegate(ForeachStatement fs, Expression flde,
Scope* sc2, Type tab)
{
Expression ec;
/* Call:
* aggr(flde)
*/
if (fs.aggr.op == EXP.delegate_ && (cast(DelegateExp)fs.aggr).func.isNested() &&
!(cast(DelegateExp)fs.aggr).func.needThis())
{
// https://issues.dlang.org/show_bug.cgi?id=3560
fs.aggr = (cast(DelegateExp)fs.aggr).e1;
}
ec = new CallExp(fs.loc, fs.aggr, flde);
ec = ec.expressionSemantic(sc2);
if (ec.op == EXP.error)
return null;
if (ec.type != Type.tint32)
{
fs.error("`opApply()` function for `%s` must return an `int`", tab.toChars());
return null;
}
return ec;
}
private static extern(D) Expression applyArray(ForeachStatement fs, Expression flde,
Scope* sc2, Type tn, Type tnv, TY tabty)
{
Expression ec;
const dim = fs.parameters.dim;
const loc = fs.loc;
/* Call:
* _aApply(aggr, flde)
*/
static immutable fntab =
[
"cc", "cw", "cd",
"wc", "cc", "wd",
"dc", "dw", "dd"
];
const(size_t) BUFFER_LEN = 7 + 1 + 2 + dim.sizeof * 3 + 1;
char[BUFFER_LEN] fdname;
int flag;
switch (tn.ty)
{
case Tchar: flag = 0; break;
case Twchar: flag = 3; break;
case Tdchar: flag = 6; break;
default:
assert(0);
}
switch (tnv.ty)
{
case Tchar: flag += 0; break;
case Twchar: flag += 1; break;
case Tdchar: flag += 2; break;
default:
assert(0);
}
const(char)* r = (fs.op == TOK.foreach_reverse_) ? "R" : "";
int j = sprintf(fdname.ptr, "_aApply%s%.*s%llu", r, 2, fntab[flag].ptr, cast(ulong)dim);
assert(j < BUFFER_LEN);
FuncDeclaration fdapply;
TypeDelegate dgty;
auto params = new Parameters();
params.push(new Parameter(STC.in_, tn.arrayOf(), null, null, null));
auto dgparams = new Parameters();
dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null));
if (dim == 2)
dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null));
dgty = new TypeDelegate(new TypeFunction(ParameterList(dgparams), Type.tint32, LINK.d));
params.push(new Parameter(0, dgty, null, null, null));
fdapply = FuncDeclaration.genCfunc(params, Type.tint32, fdname.ptr);
if (tabty == Tsarray)
fs.aggr = fs.aggr.castTo(sc2, tn.arrayOf());
// paint delegate argument to the type runtime expects
Expression fexp = flde;
if (!dgty.equals(flde.type))
{
fexp = new CastExp(loc, flde, flde.type);
fexp.type = dgty;
}
ec = new VarExp(Loc.initial, fdapply, false);
ec = new CallExp(loc, ec, fs.aggr, fexp);
ec.type = Type.tint32; // don't run semantic() on ec
return ec;
}
private static extern(D) Expression applyAssocArray(ForeachStatement fs, Expression flde, TypeAArray taa)
{
Expression ec;
const dim = fs.parameters.dim;
// Check types
Parameter p = (*fs.parameters)[0];
bool isRef = (p.storageClass & STC.ref_) != 0;
Type ta = p.type;
if (dim == 2)
{
Type ti = (isRef ? taa.index.addMod(MODFlags.const_) : taa.index);
if (isRef ? !ti.constConv(ta) : !ti.implicitConvTo(ta))
{
fs.error("`foreach`: index must be type `%s`, not `%s`",
ti.toChars(), ta.toChars());
return null;
}
p = (*fs.parameters)[1];
isRef = (p.storageClass & STC.ref_) != 0;
ta = p.type;
}
Type taav = taa.nextOf();
if (isRef ? !taav.constConv(ta) : !taav.implicitConvTo(ta))
{
fs.error("`foreach`: value must be type `%s`, not `%s`",
taav.toChars(), ta.toChars());
return null;
}
/* Call:
* extern(C) int _aaApply(void*, in size_t, int delegate(void*))
* _aaApply(aggr, keysize, flde)
*
* extern(C) int _aaApply2(void*, in size_t, int delegate(void*, void*))
* _aaApply2(aggr, keysize, flde)
*/
__gshared FuncDeclaration* fdapply = [null, null];
__gshared TypeDelegate* fldeTy = [null, null];
ubyte i = (dim == 2 ? 1 : 0);
if (!fdapply[i])
{
auto params = new Parameters();
params.push(new Parameter(0, Type.tvoid.pointerTo(), null, null, null));
params.push(new Parameter(STC.const_, Type.tsize_t, null, null, null));
auto dgparams = new Parameters();
dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null));
if (dim == 2)
dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null));
fldeTy[i] = new TypeDelegate(new TypeFunction(ParameterList(dgparams), Type.tint32, LINK.d));
params.push(new Parameter(0, fldeTy[i], null, null, null));
fdapply[i] = FuncDeclaration.genCfunc(params, Type.tint32, i ? Id._aaApply2 : Id._aaApply);
}
auto exps = new Expressions();
exps.push(fs.aggr);
auto keysize = taa.index.size();
if (keysize == SIZE_INVALID)
return null;
assert(keysize < keysize.max - target.ptrsize);
keysize = (keysize + (target.ptrsize - 1)) & ~(target.ptrsize - 1);
// paint delegate argument to the type runtime expects
Expression fexp = flde;
if (!fldeTy[i].equals(flde.type))
{
fexp = new CastExp(fs.loc, flde, flde.type);
fexp.type = fldeTy[i];
}
exps.push(new IntegerExp(Loc.initial, keysize, Type.tsize_t));
exps.push(fexp);
ec = new VarExp(Loc.initial, fdapply[i], false);
ec = new CallExp(fs.loc, ec, exps);
ec.type = Type.tint32; // don't run semantic() on ec
return ec;
}
private static extern(D) Statement loopReturn(Expression e, Statements* cases, const ref Loc loc)
{
if (!cases.dim)
{
// Easy case, a clean exit from the loop
e = new CastExp(loc, e, Type.tvoid); // https://issues.dlang.org/show_bug.cgi?id=13899
return new ExpStatement(loc, e);
}
// Construct a switch statement around the return value
// of the apply function.
Statement s;
auto a = new Statements();
// default: break; takes care of cases 0 and 1
s = new BreakStatement(Loc.initial, null);
s = new DefaultStatement(Loc.initial, s);
a.push(s);
// cases 2...
foreach (i, c; *cases)
{
s = new CaseStatement(Loc.initial, new IntegerExp(i + 2), c);
a.push(s);
}
s = new CompoundStatement(loc, a);
return new SwitchStatement(loc, e, s, false);
}
/*************************************
* Turn foreach body into the function literal:
* int delegate(ref T param) { body }
* Params:
* sc = context
* fs = ForeachStatement
* tfld = type of function literal to be created (type of opApply() function if any), can be null
* Returns:
* Function literal created, as an expression
* null if error.
*/
static FuncExp foreachBodyToFunction(Scope* sc, ForeachStatement fs, TypeFunction tfld)
{
auto params = new Parameters();
foreach (i; 0 .. fs.parameters.dim)
{
Parameter p = (*fs.parameters)[i];
StorageClass stc = STC.ref_ | (p.storageClass & STC.scope_);
Identifier id;
p.type = p.type.typeSemantic(fs.loc, sc);
p.type = p.type.addStorageClass(p.storageClass);
if (tfld)
{
Parameter prm = tfld.parameterList[i];
//printf("\tprm = %s%s\n", (prm.storageClass&STC.ref_?"ref ":"").ptr, prm.ident.toChars());
stc = (prm.storageClass & STC.ref_) | (p.storageClass & STC.scope_);
if ((p.storageClass & STC.ref_) != (prm.storageClass & STC.ref_))
{
if (!(prm.storageClass & STC.ref_))
{
fs.error("`foreach`: cannot make `%s` `ref`", p.ident.toChars());
return null;
}
goto LcopyArg;
}
id = p.ident; // argument copy is not need.
}
else if (p.storageClass & STC.ref_)
{
// default delegate parameters are marked as ref, then
// argument copy is not need.
id = p.ident;
}
else
{
// Make a copy of the ref argument so it isn't
// a reference.
LcopyArg:
id = Identifier.generateId("__applyArg", cast(int)i);
Initializer ie = new ExpInitializer(fs.loc, new IdentifierExp(fs.loc, id));
auto v = new VarDeclaration(fs.loc, p.type, p.ident, ie);
v.storage_class |= STC.temp | (stc & STC.scope_);
Statement s = new ExpStatement(fs.loc, v);
fs._body = new CompoundStatement(fs.loc, s, fs._body);
}
params.push(new Parameter(stc, p.type, id, null, null));
}
// https://issues.dlang.org/show_bug.cgi?id=13840
// Throwable nested function inside nothrow function is acceptable.
StorageClass stc = mergeFuncAttrs(STC.safe | STC.pure_ | STC.nogc, fs.func);
auto tf = new TypeFunction(ParameterList(params), Type.tint32, LINK.d, stc);
fs.cases = new Statements();
fs.gotos = new ScopeStatements();
auto fld = new FuncLiteralDeclaration(fs.loc, fs.endloc, tf, TOK.delegate_, fs);
fld.fbody = fs._body;
Expression flde = new FuncExp(fs.loc, fld);
flde = flde.expressionSemantic(sc);
fld.tookAddressOf = 0;
if (flde.op == EXP.error)
return null;
return cast(FuncExp)flde;
}
override void visit(ForeachRangeStatement fs)
{
/* https://dlang.org/spec/statement.html#foreach-range-statement
*/
//printf("ForeachRangeStatement::semantic() %p\n", fs);
auto loc = fs.loc;
fs.lwr = fs.lwr.expressionSemantic(sc);
fs.lwr = resolveProperties(sc, fs.lwr);
fs.lwr = fs.lwr.optimize(WANTvalue);
if (!fs.lwr.type)
{
fs.error("invalid range lower bound `%s`", fs.lwr.toChars());
return setError();
}
fs.upr = fs.upr.expressionSemantic(sc);
fs.upr = resolveProperties(sc, fs.upr);
fs.upr = fs.upr.optimize(WANTvalue);
if (!fs.upr.type)
{
fs.error("invalid range upper bound `%s`", fs.upr.toChars());
return setError();
}
if (fs.prm.type)
{
fs.prm.type = fs.prm.type.typeSemantic(loc, sc);
fs.prm.type = fs.prm.type.addStorageClass(fs.prm.storageClass);
fs.lwr = fs.lwr.implicitCastTo(sc, fs.prm.type);
if (fs.upr.implicitConvTo(fs.prm.type) || (fs.prm.storageClass & STC.ref_))
{
fs.upr = fs.upr.implicitCastTo(sc, fs.prm.type);
}
else
{
// See if upr-1 fits in prm.type
Expression limit = new MinExp(loc, fs.upr, IntegerExp.literal!1);
limit = limit.expressionSemantic(sc);
limit = limit.optimize(WANTvalue);
if (!limit.implicitConvTo(fs.prm.type))
{
fs.upr = fs.upr.implicitCastTo(sc, fs.prm.type);
}
}
}
else
{
/* Must infer types from lwr and upr
*/
Type tlwr = fs.lwr.type.toBasetype();
if (tlwr.ty == Tstruct || tlwr.ty == Tclass)
{
/* Just picking the first really isn't good enough.
*/
fs.prm.type = fs.lwr.type;
}
else if (fs.lwr.type == fs.upr.type)
{
/* Same logic as CondExp ?lwr:upr
*/
fs.prm.type = fs.lwr.type;
}
else
{
scope AddExp ea = new AddExp(loc, fs.lwr, fs.upr);
if (typeCombine(ea, sc))
return setError();
fs.prm.type = ea.type;
fs.lwr = ea.e1;
fs.upr = ea.e2;
}
fs.prm.type = fs.prm.type.addStorageClass(fs.prm.storageClass);
}
if (fs.prm.type.ty == Terror || fs.lwr.op == EXP.error || fs.upr.op == EXP.error)
{
return setError();
}
/* Convert to a for loop:
* foreach (key; lwr .. upr) =>
* for (auto key = lwr, auto tmp = upr; key < tmp; ++key)
*
* foreach_reverse (key; lwr .. upr) =>
* for (auto tmp = lwr, auto key = upr; key-- > tmp;)
*/
auto ie = new ExpInitializer(loc, (fs.op == TOK.foreach_) ? fs.lwr : fs.upr);
fs.key = new VarDeclaration(loc, fs.upr.type.mutableOf(), Identifier.generateId("__key"), ie);
fs.key.storage_class |= STC.temp;
SignExtendedNumber lower = getIntRange(fs.lwr).imin;
SignExtendedNumber upper = getIntRange(fs.upr).imax;
if (lower <= upper)
{
fs.key.range = new IntRange(lower, upper);
}
Identifier id = Identifier.generateId("__limit");
ie = new ExpInitializer(loc, (fs.op == TOK.foreach_) ? fs.upr : fs.lwr);
auto tmp = new VarDeclaration(loc, fs.upr.type, id, ie);
tmp.storage_class |= STC.temp;
auto cs = new Statements();
// Keep order of evaluation as lwr, then upr
if (fs.op == TOK.foreach_)
{
cs.push(new ExpStatement(loc, fs.key));
cs.push(new ExpStatement(loc, tmp));
}
else
{
cs.push(new ExpStatement(loc, tmp));
cs.push(new ExpStatement(loc, fs.key));
}
Statement forinit = new CompoundDeclarationStatement(loc, cs);
Expression cond;
if (fs.op == TOK.foreach_reverse_)
{
cond = new PostExp(EXP.minusMinus, loc, new VarExp(loc, fs.key));
if (fs.prm.type.isscalar())
{
// key-- > tmp
cond = new CmpExp(EXP.greaterThan, loc, cond, new VarExp(loc, tmp));
}
else
{
// key-- != tmp
cond = new EqualExp(EXP.notEqual, loc, cond, new VarExp(loc, tmp));
}
}
else
{
if (fs.prm.type.isscalar())
{
// key < tmp
cond = new CmpExp(EXP.lessThan, loc, new VarExp(loc, fs.key), new VarExp(loc, tmp));
}
else
{
// key != tmp
cond = new EqualExp(EXP.notEqual, loc, new VarExp(loc, fs.key), new VarExp(loc, tmp));
}
}
Expression increment = null;
if (fs.op == TOK.foreach_)
{
// key += 1
//increment = new AddAssignExp(loc, new VarExp(loc, fs.key), IntegerExp.literal!1);
increment = new PreExp(EXP.prePlusPlus, loc, new VarExp(loc, fs.key));
}
if ((fs.prm.storageClass & STC.ref_) && fs.prm.type.equals(fs.key.type))
{
fs.key.range = null;
auto v = new AliasDeclaration(loc, fs.prm.ident, fs.key);
fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body);
}
else
{
ie = new ExpInitializer(loc, new CastExp(loc, new VarExp(loc, fs.key), fs.prm.type));
auto v = new VarDeclaration(loc, fs.prm.type, fs.prm.ident, ie);
v.storage_class |= STC.temp | STC.foreach_ | (fs.prm.storageClass & STC.ref_);
fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body);
if (fs.key.range && !fs.prm.type.isMutable())
{
/* Limit the range of the key to the specified range
*/
v.range = new IntRange(fs.key.range.imin, fs.key.range.imax - SignExtendedNumber(1));
}
}
if (fs.prm.storageClass & STC.ref_)
{
if (fs.key.type.constConv(fs.prm.type) == MATCH.nomatch)
{
fs.error("argument type mismatch, `%s` to `ref %s`", fs.key.type.toChars(), fs.prm.type.toChars());
return setError();
}
}
auto s = new ForStatement(loc, forinit, cond, increment, fs._body, fs.endloc);
if (LabelStatement ls = checkLabeledLoop(sc, fs))
ls.gotoTarget = s;
result = s.statementSemantic(sc);
}
override void visit(IfStatement ifs)
{
/* https://dlang.org/spec/statement.html#IfStatement
*/
// check in syntax level
ifs.condition = checkAssignmentAsCondition(ifs.condition, sc);
auto sym = new ScopeDsymbol();
sym.parent = sc.scopesym;
sym.endlinnum = ifs.endloc.linnum;
Scope* scd = sc.push(sym);
if (ifs.prm)
{
/* Declare prm, which we will set to be the
* result of condition.
*/
auto ei = new ExpInitializer(ifs.loc, ifs.condition);
ifs.match = new VarDeclaration(ifs.loc, ifs.prm.type, ifs.prm.ident, ei);
ifs.match.parent = scd.func;
ifs.match.storage_class |= ifs.prm.storageClass;
ifs.match.dsymbolSemantic(scd);
auto de = new DeclarationExp(ifs.loc, ifs.match);
auto ve = new VarExp(ifs.loc, ifs.match);
ifs.condition = new CommaExp(ifs.loc, de, ve);
ifs.condition = ifs.condition.expressionSemantic(scd);
if (ifs.match.edtor)
{
Statement sdtor = new DtorExpStatement(ifs.loc, ifs.match.edtor, ifs.match);
sdtor = new ScopeGuardStatement(ifs.loc, TOK.onScopeExit, sdtor);
ifs.ifbody = new CompoundStatement(ifs.loc, sdtor, ifs.ifbody);
ifs.match.storage_class |= STC.nodtor;
// the destructor is always called
// whether the 'ifbody' is executed or not
Statement sdtor2 = new DtorExpStatement(ifs.loc, ifs.match.edtor, ifs.match);
if (ifs.elsebody)
ifs.elsebody = new CompoundStatement(ifs.loc, sdtor2, ifs.elsebody);
else
ifs.elsebody = sdtor2;
}
}
else
{
if (ifs.condition.op == EXP.dotIdentifier)
(cast(DotIdExp)ifs.condition).noderef = true;
ifs.condition = ifs.condition.expressionSemantic(scd);
ifs.condition = resolveProperties(scd, ifs.condition);
ifs.condition = ifs.condition.addDtorHook(scd);
}
if (checkNonAssignmentArrayOp(ifs.condition))
ifs.condition = ErrorExp.get();
ifs.condition = checkGC(scd, ifs.condition);
// Convert to boolean after declaring prm so this works:
// if (S prm = S()) {}
// where S is a struct that defines opCast!bool.
ifs.condition = ifs.condition.toBoolean(scd);
// If we can short-circuit evaluate the if statement, don't do the
// semantic analysis of the skipped code.
// This feature allows a limited form of conditional compilation.
ifs.condition = ifs.condition.optimize(WANTvalue);
// Save 'root' of two branches (then and else) at the point where it forks
CtorFlow ctorflow_root = scd.ctorflow.clone();
ifs.ifbody = ifs.ifbody.semanticNoScope(scd);
scd.pop();
CtorFlow ctorflow_then = sc.ctorflow; // move flow results
sc.ctorflow = ctorflow_root; // reset flow analysis back to root
if (ifs.elsebody)
ifs.elsebody = ifs.elsebody.semanticScope(sc, null, null, null);
// Merge 'then' results into 'else' results
sc.merge(ifs.loc, ctorflow_then);
ctorflow_then.freeFieldinit(); // free extra copy of the data
if (ifs.condition.op == EXP.error ||
(ifs.ifbody && ifs.ifbody.isErrorStatement()) ||
(ifs.elsebody && ifs.elsebody.isErrorStatement()))
{
return setError();
}
result = ifs;
}
override void visit(ConditionalStatement cs)
{
//printf("ConditionalStatement::semantic()\n");
// If we can short-circuit evaluate the if statement, don't do the
// semantic analysis of the skipped code.
// This feature allows a limited form of conditional compilation.
if (cs.condition.include(sc))
{
DebugCondition dc = cs.condition.isDebugCondition();
if (dc)
{
sc = sc.push();
sc.flags |= SCOPE.debug_;
cs.ifbody = cs.ifbody.statementSemantic(sc);
sc.pop();
}
else
cs.ifbody = cs.ifbody.statementSemantic(sc);
result = cs.ifbody;
}
else
{
if (cs.elsebody)
cs.elsebody = cs.elsebody.statementSemantic(sc);
result = cs.elsebody;
}
}
override void visit(PragmaStatement ps)
{
/* https://dlang.org/spec/statement.html#pragma-statement
*/
// Should be merged with PragmaDeclaration
//printf("PragmaStatement::semantic() %s\n", ps.toChars());
//printf("body = %p\n", ps._body);
if (ps.ident == Id.msg)
{
if (ps.args)
{
foreach (arg; *ps.args)
{
sc = sc.startCTFE();
auto e = arg.expressionSemantic(sc);
e = resolveProperties(sc, e);
sc = sc.endCTFE();
// pragma(msg) is allowed to contain types as well as expressions
e = ctfeInterpretForPragmaMsg(e);
if (e.op == EXP.error)
{
errorSupplemental(ps.loc, "while evaluating `pragma(msg, %s)`", arg.toChars());
return setError();
}
if (auto se = e.toStringExp())
{
const slice = se.toUTF8(sc).peekString();
fprintf(stderr, "%.*s", cast(int)slice.length, slice.ptr);
}
else
fprintf(stderr, "%s", e.toChars());
}
fprintf(stderr, "\n");
}
}
else if (ps.ident == Id.lib)
{
version (all)
{
/* Should this be allowed?
*/
ps.error("`pragma(lib)` not allowed as statement");
return setError();
}
else
{
if (!ps.args || ps.args.dim != 1)
{
ps.error("`string` expected for library name");
return setError();
}
else
{
auto se = semanticString(sc, (*ps.args)[0], "library name");
if (!se)
return setError();
if (global.params.verbose)
{
message("library %.*s", cast(int)se.len, se.string);
}
}
}
}
else if (ps.ident == Id.linkerDirective)
{
/* Should this be allowed?
*/
ps.error("`pragma(linkerDirective)` not allowed as statement");
return setError();
}
else if (ps.ident == Id.startaddress)
{
if (!ps.args || ps.args.dim != 1)
ps.error("function name expected for start address");
else
{
Expression e = (*ps.args)[0];
sc = sc.startCTFE();
e = e.expressionSemantic(sc);
e = resolveProperties(sc, e);
sc = sc.endCTFE();
e = e.ctfeInterpret();
(*ps.args)[0] = e;
Dsymbol sa = getDsymbol(e);
if (!sa || !sa.isFuncDeclaration())
{
ps.error("function name expected for start address, not `%s`", e.toChars());
return setError();
}
if (ps._body)
{
ps._body = ps._body.statementSemantic(sc);
if (ps._body.isErrorStatement())
{
result = ps._body;
return;
}
}
result = ps;
return;
}
}
else if (ps.ident == Id.Pinline)
{
PINLINE inlining = PINLINE.default_;
if (!ps.args || ps.args.dim == 0)
inlining = PINLINE.default_;
else if (!ps.args || ps.args.dim != 1)
{
ps.error("boolean expression expected for `pragma(inline)`");
return setError();
}
else
{
Expression e = (*ps.args)[0];
sc = sc.startCTFE();
e = e.expressionSemantic(sc);
e = resolveProperties(sc, e);
sc = sc.endCTFE();
e = e.ctfeInterpret();
e = e.toBoolean(sc);
if (e.isErrorExp())
{
ps.error("pragma(`inline`, `true` or `false`) expected, not `%s`", (*ps.args)[0].toChars());
return setError();
}
const opt = e.toBool();
if (opt.hasValue(true))
inlining = PINLINE.always;
else if (opt.hasValue(false))
inlining = PINLINE.never;
FuncDeclaration fd = sc.func;
if (!fd)
{
ps.error("`pragma(inline)` is not inside a function");
return setError();
}
fd.inlining = inlining;
}
}
else if (!global.params.ignoreUnsupportedPragmas)
{
ps.error("unrecognized `pragma(%s)`", ps.ident.toChars());
return setError();
}
if (ps._body)
{
if (ps.ident == Id.msg || ps.ident == Id.startaddress)
{
ps.error("`pragma(%s)` is missing a terminating `;`", ps.ident.toChars());
return setError();
}
ps._body = ps._body.statementSemantic(sc);
}
result = ps._body;
}
override void visit(StaticAssertStatement s)
{
s.sa.semantic2(sc);
if (s.sa.errors)
return setError();
}
override void visit(SwitchStatement ss)
{
/* https://dlang.org/spec/statement.html#switch-statement
*/
//printf("SwitchStatement::semantic(%p)\n", ss);
ss.tryBody = sc.tryBody;
ss.tf = sc.tf;
if (ss.cases)
{
result = ss; // already run
return;
}
bool conditionError = false;
ss.condition = ss.condition.expressionSemantic(sc);
ss.condition = resolveProperties(sc, ss.condition);
Type att = null;
TypeEnum te = null;
while (!ss.condition.isErrorExp())
{
// preserve enum type for final switches
if (ss.condition.type.ty == Tenum)
te = cast(TypeEnum)ss.condition.type;
if (ss.condition.type.isString())
{
// If it's not an array, cast it to one
if (ss.condition.type.ty != Tarray)
{
ss.condition = ss.condition.implicitCastTo(sc, ss.condition.type.nextOf().arrayOf());
}
ss.condition.type = ss.condition.type.constOf();
break;
}
ss.condition = integralPromotions(ss.condition, sc);
if (!ss.condition.isErrorExp() && ss.condition.type.isintegral())
break;
auto ad = isAggregate(ss.condition.type);
if (ad && ad.aliasthis && !isRecursiveAliasThis(att, ss.condition.type))
{
if (auto e = resolveAliasThis(sc, ss.condition, true))
{
ss.condition = e;
continue;
}
}
if (!ss.condition.isErrorExp())
{
ss.error("`%s` must be of integral or string type, it is a `%s`",
ss.condition.toChars(), ss.condition.type.toChars());
conditionError = true;
break;
}
}
if (checkNonAssignmentArrayOp(ss.condition))
ss.condition = ErrorExp.get();
ss.condition = ss.condition.optimize(WANTvalue);
ss.condition = checkGC(sc, ss.condition);
if (ss.condition.op == EXP.error)
conditionError = true;
bool needswitcherror = false;
ss.lastVar = sc.lastVar;
sc = sc.push();
sc.sbreak = ss;
sc.sw = ss;
ss.cases = new CaseStatements();
const inLoopSave = sc.inLoop;
sc.inLoop = true; // BUG: should use Scope::mergeCallSuper() for each case instead
ss._body = ss._body.statementSemantic(sc);
sc.inLoop = inLoopSave;
if (conditionError || (ss._body && ss._body.isErrorStatement()))
{
sc.pop();
return setError();
}
// Resolve any goto case's with exp
Lgotocase:
foreach (gcs; ss.gotoCases)
{
if (!gcs.exp)
{
gcs.error("no `case` statement following `goto case;`");
sc.pop();
return setError();
}
for (Scope* scx = sc; scx; scx = scx.enclosing)
{
if (!scx.sw)
continue;
foreach (cs; *scx.sw.cases)
{
if (cs.exp.equals(gcs.exp))
{
gcs.cs = cs;
continue Lgotocase;
}
}
}
gcs.error("`case %s` not found", gcs.exp.toChars());
sc.pop();
return setError();
}
if (ss.isFinal)
{
Type t = ss.condition.type;
Dsymbol ds;
EnumDeclaration ed = null;
if (t && ((ds = t.toDsymbol(sc)) !is null))
ed = ds.isEnumDeclaration(); // typedef'ed enum
if (!ed && te && ((ds = te.toDsymbol(sc)) !is null))
ed = ds.isEnumDeclaration();
if (ed && ss.cases.length < ed.members.length)
{
int missingMembers = 0;
const maxShown = !global.params.verbose ? 6 : int.max;
Lmembers:
foreach (es; *ed.members)
{
EnumMember em = es.isEnumMember();
if (em)
{
foreach (cs; *ss.cases)
{
if (cs.exp.equals(em.value) || (!cs.exp.type.isString() &&
!em.value.type.isString() && cs.exp.toInteger() == em.value.toInteger()))
continue Lmembers;
}
if (missingMembers == 0)
ss.error("missing cases for `enum` members in `final switch`:");
if (missingMembers < maxShown)
errorSupplemental(ss.loc, "`%s`", em.toChars());
missingMembers++;
}
}
if (missingMembers > 0)
{
if (missingMembers > maxShown)
errorSupplemental(ss.loc, "... (%d more, -v to show) ...", missingMembers - maxShown);
sc.pop();
return setError();
}
}
else
needswitcherror = true;
}
if (!sc.sw.sdefault &&
(!ss.isFinal || needswitcherror || global.params.useAssert == CHECKENABLE.on))
{
ss.hasNoDefault = 1;
if (!ss.isFinal && (!ss._body || !ss._body.isErrorStatement()) && !(sc.flags & SCOPE.Cfile))
ss.error("`switch` statement without a `default`; use `final switch` or add `default: assert(0);` or add `default: break;`");
// Generate runtime error if the default is hit
auto a = new Statements();
CompoundStatement cs;
Statement s;
if (sc.flags & SCOPE.Cfile)
{
s = new BreakStatement(ss.loc, null); // default for C is `default: break;`
}
else if (global.params.useSwitchError == CHECKENABLE.on &&
global.params.checkAction != CHECKACTION.halt)
{
if (global.params.checkAction == CHECKACTION.C)
{
/* Rewrite as an assert(0) and let e2ir generate
* the call to the C assert failure function
*/
s = new ExpStatement(ss.loc, new AssertExp(ss.loc, IntegerExp.literal!0));
}
else
{
if (!verifyHookExist(ss.loc, *sc, Id.__switch_error, "generating assert messages"))
return setError();
Expression sl = new IdentifierExp(ss.loc, Id.empty);
sl = new DotIdExp(ss.loc, sl, Id.object);
sl = new DotIdExp(ss.loc, sl, Id.__switch_error);
Expressions* args = new Expressions(2);
(*args)[0] = new StringExp(ss.loc, ss.loc.filename.toDString());
(*args)[1] = new IntegerExp(ss.loc.linnum);
sl = new CallExp(ss.loc, sl, args);
sl = sl.expressionSemantic(sc);
s = new SwitchErrorStatement(ss.loc, sl);
}
}
else
s = new ExpStatement(ss.loc, new HaltExp(ss.loc));
a.reserve(2);
sc.sw.sdefault = new DefaultStatement(ss.loc, s);
a.push(ss._body);
if (ss._body.blockExit(sc.func, false) & BE.fallthru)
a.push(new BreakStatement(Loc.initial, null));
a.push(sc.sw.sdefault);
cs = new CompoundStatement(ss.loc, a);
ss._body = cs;
}
if (!(sc.flags & SCOPE.Cfile) && ss.checkLabel())
{
sc.pop();
return setError();
}
if (!ss.condition.type.isString())
{
sc.pop();
result = ss;
return;
}
// Transform a switch with string labels into a switch with integer labels.
// The integer value of each case corresponds to the index of each label
// string in the sorted array of label strings.
// The value of the integer condition is obtained by calling the druntime template
// switch(object.__switch(cond, options...)) {0: {...}, 1: {...}, ...}
// We sort a copy of the array of labels because we want to do a binary search in object.__switch,
// without modifying the order of the case blocks here in the compiler.
if (!verifyHookExist(ss.loc, *sc, Id.__switch, "switch cases on strings"))
return setError();
size_t numcases = 0;
if (ss.cases)
numcases = ss.cases.dim;
for (size_t i = 0; i < numcases; i++)
{
CaseStatement cs = (*ss.cases)[i];
cs.index = cast(int)i;
}
// Make a copy of all the cases so that qsort doesn't scramble the actual
// data we pass to codegen (the order of the cases in the switch).
CaseStatements *csCopy = (*ss.cases).copy();
if (numcases)
{
static int sort_compare(in CaseStatement* x, in CaseStatement* y) @trusted
{
auto se1 = x.exp.isStringExp();
auto se2 = y.exp.isStringExp();
return (se1 && se2) ? se1.compare(se2) : 0;
}
// Sort cases for efficient lookup
csCopy.sort!sort_compare;
}
// The actual lowering
auto arguments = new Expressions();
arguments.push(ss.condition);
auto compileTimeArgs = new Objects();
// The type & label no.
compileTimeArgs.push(new TypeExp(ss.loc, ss.condition.type.nextOf()));
// The switch labels
foreach (caseString; *csCopy)
{
compileTimeArgs.push(caseString.exp);
}
Expression sl = new IdentifierExp(ss.loc, Id.empty);
sl = new DotIdExp(ss.loc, sl, Id.object);
sl = new DotTemplateInstanceExp(ss.loc, sl, Id.__switch, compileTimeArgs);
sl = new CallExp(ss.loc, sl, arguments);
sl = sl.expressionSemantic(sc);
ss.condition = sl;
auto i = 0;
foreach (c; *csCopy)
{
(*ss.cases)[c.index].exp = new IntegerExp(i++);
}
//printf("%s\n", ss._body.toChars());
ss.statementSemantic(sc);
sc.pop();
result = ss;
}
override void visit(CaseStatement cs)
{
SwitchStatement sw = sc.sw;
bool errors = false;
//printf("CaseStatement::semantic() %s\n", toChars());
sc = sc.startCTFE();
cs.exp = cs.exp.expressionSemantic(sc);
cs.exp = resolveProperties(sc, cs.exp);
sc = sc.endCTFE();
if (sw)
{
Expression initialExp = cs.exp;
// The switch'ed value has errors and doesn't provide the actual type
// Omit the cast to enable further semantic (exluding the check for matching types)
if (sw.condition.type && !sw.condition.type.isTypeError())
cs.exp = cs.exp.implicitCastTo(sc, sw.condition.type);
cs.exp = cs.exp.optimize(WANTvalue | WANTexpand);
Expression e = cs.exp;
// Remove all the casts the user and/or implicitCastTo may introduce
// otherwise we'd sometimes fail the check below.
while (e.op == EXP.cast_)
e = (cast(CastExp)e).e1;
/* This is where variables are allowed as case expressions.
*/
if (e.op == EXP.variable)
{
VarExp ve = cast(VarExp)e;
VarDeclaration v = ve.var.isVarDeclaration();
Type t = cs.exp.type.toBasetype();
if (v && (t.isintegral() || t.ty == Tclass))
{
/* Flag that we need to do special code generation
* for this, i.e. generate a sequence of if-then-else
*/
sw.hasVars = 1;
/* TODO check if v can be uninitialized at that point.
*/
if (!v.isConst() && !v.isImmutable())
{
cs.error("`case` variables have to be `const` or `immutable`");
}
if (sw.isFinal)
{
cs.error("`case` variables not allowed in `final switch` statements");
errors = true;
}
/* Find the outermost scope `scx` that set `sw`.
* Then search scope `scx` for a declaration of `v`.
*/
for (Scope* scx = sc; scx; scx = scx.enclosing)
{
if (scx.enclosing && scx.enclosing.sw == sw)
continue;
assert(scx.sw == sw);
if (!scx.search(cs.exp.loc, v.ident, null))
{
cs.error("`case` variable `%s` declared at %s cannot be declared in `switch` body",
v.toChars(), v.loc.toChars());
errors = true;
}
break;
}
goto L1;
}
}
else
cs.exp = cs.exp.ctfeInterpret();
if (StringExp se = cs.exp.toStringExp())
cs.exp = se;
else if (!cs.exp.isIntegerExp() && !cs.exp.isErrorExp())
{
cs.error("`case` must be a `string` or an integral constant, not `%s`", cs.exp.toChars());
errors = true;
}
L1:
// // Don't check other cases if this has errors
if (!cs.exp.isErrorExp())
foreach (cs2; *sw.cases)
{
//printf("comparing '%s' with '%s'\n", exp.toChars(), cs.exp.toChars());
if (cs2.exp.equals(cs.exp))
{
// https://issues.dlang.org/show_bug.cgi?id=15909
cs.error("duplicate `case %s` in `switch` statement", initialExp.toChars());
errors = true;
break;
}
}
sw.cases.push(cs);
// Resolve any goto case's with no exp to this case statement
for (size_t i = 0; i < sw.gotoCases.dim;)
{
GotoCaseStatement gcs = sw.gotoCases[i];
if (!gcs.exp)
{
gcs.cs = cs;
sw.gotoCases.remove(i); // remove from array
continue;
}
i++;
}
if (sc.sw.tf != sc.tf)
{
cs.error("`switch` and `case` are in different `finally` blocks");
errors = true;
}
if (sc.sw.tryBody != sc.tryBody)
{
cs.error("case cannot be in different `try` block level from `switch`");
errors = true;
}
}
else
{
cs.error("`case` not in `switch` statement");
errors = true;
}
sc.ctorflow.orCSX(CSX.label);
cs.statement = cs.statement.statementSemantic(sc);
if (cs.statement.isErrorStatement())
{
result = cs.statement;
return;
}
if (errors || cs.exp.op == EXP.error)
return setError();
cs.lastVar = sc.lastVar;
result = cs;
}
override void visit(CaseRangeStatement crs)
{
SwitchStatement sw = sc.sw;
if (sw is null)
{
crs.error("case range not in `switch` statement");
return setError();
}
//printf("CaseRangeStatement::semantic() %s\n", toChars());
bool errors = false;
if (sw.isFinal)
{
crs.error("case ranges not allowed in `final switch`");
errors = true;
}
sc = sc.startCTFE();
crs.first = crs.first.expressionSemantic(sc);
crs.first = resolveProperties(sc, crs.first);
sc = sc.endCTFE();
crs.first = crs.first.implicitCastTo(sc, sw.condition.type);
crs.first = crs.first.ctfeInterpret();
sc = sc.startCTFE();
crs.last = crs.last.expressionSemantic(sc);
crs.last = resolveProperties(sc, crs.last);
sc = sc.endCTFE();
crs.last = crs.last.implicitCastTo(sc, sw.condition.type);
crs.last = crs.last.ctfeInterpret();
if (crs.first.op == EXP.error || crs.last.op == EXP.error || errors)
{
if (crs.statement)
crs.statement.statementSemantic(sc);
return setError();
}
uinteger_t fval = crs.first.toInteger();
uinteger_t lval = crs.last.toInteger();
if ((crs.first.type.isunsigned() && fval > lval) || (!crs.first.type.isunsigned() && cast(sinteger_t)fval > cast(sinteger_t)lval))
{
crs.error("first `case %s` is greater than last `case %s`", crs.first.toChars(), crs.last.toChars());
errors = true;
lval = fval;
}
if (lval - fval > 256)
{
crs.error("had %llu cases which is more than 257 cases in case range", 1 + lval - fval);
errors = true;
lval = fval + 256;
}
if (errors)
return setError();
/* This works by replacing the CaseRange with an array of Case's.
*
* case a: .. case b: s;
* =>
* case a:
* [...]
* case b:
* s;
*/
auto statements = new Statements();
for (uinteger_t i = fval; i != lval + 1; i++)
{
Statement s = crs.statement;
if (i != lval) // if not last case
s = new ExpStatement(crs.loc, cast(Expression)null);
Expression e = new IntegerExp(crs.loc, i, crs.first.type);
Statement cs = new CaseStatement(crs.loc, e, s);
statements.push(cs);
}
Statement s = new CompoundStatement(crs.loc, statements);
sc.ctorflow.orCSX(CSX.label);
s = s.statementSemantic(sc);
result = s;
}
override void visit(DefaultStatement ds)
{
//printf("DefaultStatement::semantic()\n");
bool errors = false;
if (sc.sw)
{
if (sc.sw.sdefault)
{
ds.error("`switch` statement already has a default");
errors = true;
}
sc.sw.sdefault = ds;
if (sc.sw.tf != sc.tf)
{
ds.error("`switch` and `default` are in different `finally` blocks");
errors = true;
}
if (sc.sw.tryBody != sc.tryBody)
{
ds.error("default cannot be in different `try` block level from `switch`");
errors = true;
}
if (sc.sw.isFinal)
{
ds.error("`default` statement not allowed in `final switch` statement");
errors = true;
}
}
else
{
ds.error("`default` not in `switch` statement");
errors = true;
}
sc.ctorflow.orCSX(CSX.label);
ds.statement = ds.statement.statementSemantic(sc);
if (errors || ds.statement.isErrorStatement())
return setError();
ds.lastVar = sc.lastVar;
result = ds;
}
override void visit(GotoDefaultStatement gds)
{
/* https://dlang.org/spec/statement.html#goto-statement
*/
gds.sw = sc.sw;
if (!gds.sw)
{
gds.error("`goto default` not in `switch` statement");
return setError();
}
if (gds.sw.isFinal)
{
gds.error("`goto default` not allowed in `final switch` statement");
return setError();
}
result = gds;
}
override void visit(GotoCaseStatement gcs)
{
/* https://dlang.org/spec/statement.html#goto-statement
*/
if (!sc.sw)
{
gcs.error("`goto case` not in `switch` statement");
return setError();
}
if (gcs.exp)
{
gcs.exp = gcs.exp.expressionSemantic(sc);
gcs.exp = gcs.exp.implicitCastTo(sc, sc.sw.condition.type);
gcs.exp = gcs.exp.optimize(WANTvalue);
if (gcs.exp.op == EXP.error)
return setError();
}
sc.sw.gotoCases.push(gcs);
result = gcs;
}
override void visit(ReturnStatement rs)
{
/* https://dlang.org/spec/statement.html#return-statement
*/
//printf("ReturnStatement.dsymbolSemantic() %p, %s\n", rs, rs.toChars());
FuncDeclaration fd = sc.parent.isFuncDeclaration();
if (fd.fes)
fd = fd.fes.func; // fd is now function enclosing foreach
TypeFunction tf = cast(TypeFunction)fd.type;
assert(tf.ty == Tfunction);
if (rs.exp && rs.exp.op == EXP.variable && (cast(VarExp)rs.exp).var == fd.vresult)
{
// return vresult;
if (sc.fes)
{
assert(rs.caseDim == 0);
sc.fes.cases.push(rs);
result = new ReturnStatement(Loc.initial, new IntegerExp(sc.fes.cases.dim + 1));
return;
}
if (fd.returnLabel)
{
auto gs = new GotoStatement(rs.loc, Id.returnLabel);
gs.label = fd.returnLabel;
result = gs;
return;
}
if (!fd.returns)
fd.returns = new ReturnStatements();
fd.returns.push(rs);
result = rs;
return;
}
Type tret = tf.next;
Type tbret = tret ? tret.toBasetype() : null;
bool inferRef = (tf.isref && (fd.storage_class & STC.auto_));
Expression e0 = null;
bool errors = false;
if (sc.flags & SCOPE.contract)
{
rs.error("`return` statements cannot be in contracts");
errors = true;
}
if (sc.os && sc.os.tok != TOK.onScopeFailure)
{
rs.error("`return` statements cannot be in `%s` bodies", Token.toChars(sc.os.tok));
errors = true;
}
if (sc.tf)
{
rs.error("`return` statements cannot be in `finally` bodies");
errors = true;
}
if (fd.isCtorDeclaration())
{
if (rs.exp)
{
rs.error("cannot return expression from constructor");
errors = true;
}
// Constructors implicitly do:
// return this;
rs.exp = new ThisExp(Loc.initial);
rs.exp.type = tret;
}
else if (rs.exp)
{
fd.hasReturnExp |= (fd.hasReturnExp & 1 ? 16 : 1);
FuncLiteralDeclaration fld = fd.isFuncLiteralDeclaration();
if (tret)
rs.exp = inferType(rs.exp, tret);
else if (fld && fld.treq)
rs.exp = inferType(rs.exp, fld.treq.nextOf().nextOf());
rs.exp = rs.exp.expressionSemantic(sc);
rs.exp = rs.exp.arrayFuncConv(sc);
// If we're returning by ref, allow the expression to be `shared`
const returnSharedRef = (tf.isref && (fd.inferRetType || tret.isShared()));
rs.exp.checkSharedAccess(sc, returnSharedRef);
// for static alias this: https://issues.dlang.org/show_bug.cgi?id=17684
if (rs.exp.op == EXP.type)
rs.exp = resolveAliasThis(sc, rs.exp);
rs.exp = resolveProperties(sc, rs.exp);
if (rs.exp.checkType())
rs.exp = ErrorExp.get();
if (auto f = isFuncAddress(rs.exp))
{
if (fd.inferRetType && f.checkForwardRef(rs.exp.loc))
rs.exp = ErrorExp.get();
}
if (checkNonAssignmentArrayOp(rs.exp))
rs.exp = ErrorExp.get();
// Extract side-effect part
rs.exp = Expression.extractLast(rs.exp, e0);
if (rs.exp.op == EXP.call)
rs.exp = valueNoDtor(rs.exp);
/* Void-return function can have void / noreturn typed expression
* on return statement.
*/
const convToVoid = rs.exp.type.ty == Tvoid || rs.exp.type.ty == Tnoreturn;
if (tbret && tbret.ty == Tvoid || convToVoid)
{
if (!convToVoid)
{
rs.error("cannot return non-void from `void` function");
errors = true;
rs.exp = new CastExp(rs.loc, rs.exp, Type.tvoid);
rs.exp = rs.exp.expressionSemantic(sc);
}
/* Replace:
* return exp;
* with:
* exp; return;
*/
e0 = Expression.combine(e0, rs.exp);
rs.exp = null;
}
if (e0)
{
e0 = e0.optimize(WANTvalue);
e0 = checkGC(sc, e0);
}
}
if (rs.exp)
{
if (fd.inferRetType) // infer return type
{
if (!tret)
{
tf.next = rs.exp.type;
}
else if (tret.ty != Terror && !rs.exp.type.equals(tret))
{
int m1 = rs.exp.type.implicitConvTo(tret);
int m2 = tret.implicitConvTo(rs.exp.type);
//printf("exp.type = %s m2<-->m1 tret %s\n", exp.type.toChars(), tret.toChars());
//printf("m1 = %d, m2 = %d\n", m1, m2);
if (m1 && m2)
{
}
else if (!m1 && m2)
tf.next = rs.exp.type;
else if (m1 && !m2)
{
}
else if (!rs.exp.isErrorExp())
{
rs.error("expected return type of `%s`, not `%s`:",
tret.toChars(),
rs.exp.type.toChars());
errorSupplemental((fd.returns) ? (*fd.returns)[0].loc : fd.loc,
"Return type of `%s` inferred here.",
tret.toChars());
errors = true;
tf.next = Type.terror;
}
}
tret = tf.next;
tbret = tret.toBasetype();
}
if (inferRef) // deduce 'auto ref'
{
/* Determine "refness" of function return:
* if it's an lvalue, return by ref, else return by value
* https://dlang.org/spec/function.html#auto-ref-functions
*/
void turnOffRef(scope void delegate() supplemental)
{
tf.isref = false; // return by value
tf.isreturn = false; // ignore 'return' attribute, whether explicit or inferred
fd.storage_class &= ~STC.return_;
// If we previously assumed the function could be ref when
// checking for `shared`, make sure we were right
if (global.params.noSharedAccess && rs.exp.type.isShared())
{
fd.error("function returns `shared` but cannot be inferred `ref`");
supplemental();
}
}
if (rs.exp.isLvalue())
{
/* May return by ref
*/
if (checkReturnEscapeRef(sc, rs.exp, true))
turnOffRef(() { checkReturnEscapeRef(sc, rs.exp, false); });
else if (!rs.exp.type.constConv(tf.next))
turnOffRef(
() => rs.loc.errorSupplemental("cannot implicitly convert `%s` of type `%s` to `%s`",
rs.exp.toChars(), rs.exp.type.toChars(), tf.next.toChars())
);
}
else
turnOffRef(
() => rs.loc.errorSupplemental("return value `%s` is not an lvalue", rs.exp.toChars())
);
/* The "refness" is determined by all of return statements.
* This means:
* return 3; return x; // ok, x can be a value
* return x; return 3; // ok, x can be a value
*/
}
}
else
{
// Type of the returned expression (if any), might've been moved to e0
auto resType = e0 ? e0.type : Type.tvoid;
// infer return type
if (fd.inferRetType)
{
// 1. First `return <noreturn exp>?`
// 2. Potentially found a returning branch, update accordingly
if (!tf.next || tf.next.toBasetype().isTypeNoreturn())
{
tf.next = resType; // infer void or noreturn
}
// Found an actual return value before
else if (tf.next.ty != Tvoid && !resType.toBasetype().isTypeNoreturn())
{
if (tf.next.ty != Terror)
{
rs.error("mismatched function return type inference of `void` and `%s`", tf.next.toChars());
}
errors = true;
tf.next = Type.terror;
}
tret = tf.next;
tbret = tret.toBasetype();
}
if (inferRef) // deduce 'auto ref'
tf.isref = false;
if (tbret.ty != Tvoid && !resType.isTypeNoreturn()) // if non-void return
{
if (tbret.ty != Terror)
{
if (e0)
rs.error("expected return type of `%s`, not `%s`", tret.toChars(), resType.toChars());
else
rs.error("`return` expression expected");
}
errors = true;
}
else if (fd.isMain())
{
// main() returns 0, even if it returns void
rs.exp = IntegerExp.literal!0;
}
}
// If any branches have called a ctor, but this branch hasn't, it's an error
if (sc.ctorflow.callSuper & CSX.any_ctor && !(sc.ctorflow.callSuper & (CSX.this_ctor | CSX.super_ctor)))
{
rs.error("`return` without calling constructor");
errors = true;
}
if (sc.ctorflow.fieldinit.length) // if aggregate fields are being constructed
{
auto ad = fd.isMemberLocal();
assert(ad);
foreach (i, v; ad.fields)
{
bool mustInit = (v.storage_class & STC.nodefaultctor || v.type.needsNested());
if (mustInit && !(sc.ctorflow.fieldinit[i].csx & CSX.this_ctor))
{
rs.error("an earlier `return` statement skips field `%s` initialization", v.toChars());
errors = true;
}
}
}
sc.ctorflow.orCSX(CSX.return_);
if (errors)
return setError();
if (sc.fes)
{
if (!rs.exp)
{
// Send out "case receiver" statement to the foreach.
// return exp;
Statement s = new ReturnStatement(Loc.initial, rs.exp);
sc.fes.cases.push(s);
// Immediately rewrite "this" return statement as:
// return cases.dim+1;
rs.exp = new IntegerExp(sc.fes.cases.dim + 1);
if (e0)
{
result = new CompoundStatement(rs.loc, new ExpStatement(rs.loc, e0), rs);
return;
}
result = rs;
return;
}
else
{
fd.buildResultVar(null, rs.exp.type);
bool r = fd.vresult.checkNestedReference(sc, Loc.initial);
assert(!r); // vresult should be always accessible
// Send out "case receiver" statement to the foreach.
// return vresult;
Statement s = new ReturnStatement(Loc.initial, new VarExp(Loc.initial, fd.vresult));
sc.fes.cases.push(s);
// Save receiver index for the later rewriting from:
// return exp;
// to:
// vresult = exp; retrun caseDim;
rs.caseDim = sc.fes.cases.dim + 1;
}
}
if (rs.exp)
{
if (!fd.returns)
fd.returns = new ReturnStatements();
fd.returns.push(rs);
}
if (e0)
{
if (e0.op == EXP.declaration || e0.op == EXP.comma)
{
rs.exp = Expression.combine(e0, rs.exp);
}
else
{
auto es = new ExpStatement(rs.loc, e0);
if (e0.type.isTypeNoreturn())
result = es; // Omit unreachable return;
else
result = new CompoundStatement(rs.loc, es, rs);
return;
}
}
result = rs;
}
override void visit(BreakStatement bs)
{
/* https://dlang.org/spec/statement.html#break-statement
*/
//printf("BreakStatement::semantic()\n");
// If:
// break Identifier;
if (bs.ident)
{
bs.ident = fixupLabelName(sc, bs.ident);
FuncDeclaration thisfunc = sc.func;
for (Scope* scx = sc; scx; scx = scx.enclosing)
{
if (scx.func != thisfunc) // if in enclosing function
{
if (sc.fes) // if this is the body of a foreach
{
/* Post this statement to the fes, and replace
* it with a return value that caller will put into
* a switch. Caller will figure out where the break
* label actually is.
* Case numbers start with 2, not 0, as 0 is continue
* and 1 is break.
*/
sc.fes.cases.push(bs);
result = new ReturnStatement(Loc.initial, new IntegerExp(sc.fes.cases.dim + 1));
return;
}
break; // can't break to it
}
LabelStatement ls = scx.slabel;
if (ls && ls.ident == bs.ident)
{
Statement s = ls.statement;
if (!s || !s.hasBreak())
bs.error("label `%s` has no `break`", bs.ident.toChars());
else if (ls.tf != sc.tf)
bs.error("cannot break out of `finally` block");
else
{
ls.breaks = true;
result = bs;
return;
}
return setError();
}
}
bs.error("enclosing label `%s` for `break` not found", bs.ident.toChars());
return setError();
}
else if (!sc.sbreak)
{
if (sc.os && sc.os.tok != TOK.onScopeFailure)
{
bs.error("`break` is not allowed inside `%s` bodies", Token.toChars(sc.os.tok));
}
else if (sc.fes)
{
// Replace break; with return 1;
result = new ReturnStatement(Loc.initial, IntegerExp.literal!1);
return;
}
else
bs.error("`break` is not inside a loop or `switch`");
return setError();
}
else if (sc.sbreak.isForwardingStatement())
{
bs.error("must use labeled `break` within `static foreach`");
}
result = bs;
}
override void visit(ContinueStatement cs)
{
/* https://dlang.org/spec/statement.html#continue-statement
*/
//printf("ContinueStatement::semantic() %p\n", cs);
if (cs.ident)
{
cs.ident = fixupLabelName(sc, cs.ident);
Scope* scx;
FuncDeclaration thisfunc = sc.func;
for (scx = sc; scx; scx = scx.enclosing)
{
LabelStatement ls;
if (scx.func != thisfunc) // if in enclosing function
{
if (sc.fes) // if this is the body of a foreach
{
for (; scx; scx = scx.enclosing)
{
ls = scx.slabel;
if (ls && ls.ident == cs.ident && ls.statement == sc.fes)
{
// Replace continue ident; with return 0;
result = new ReturnStatement(Loc.initial, IntegerExp.literal!0);
return;
}
}
/* Post this statement to the fes, and replace
* it with a return value that caller will put into
* a switch. Caller will figure out where the break
* label actually is.
* Case numbers start with 2, not 0, as 0 is continue
* and 1 is break.
*/
sc.fes.cases.push(cs);
result = new ReturnStatement(Loc.initial, new IntegerExp(sc.fes.cases.dim + 1));
return;
}
break; // can't continue to it
}
ls = scx.slabel;
if (ls && ls.ident == cs.ident)
{
Statement s = ls.statement;
if (!s || !s.hasContinue())
cs.error("label `%s` has no `continue`", cs.ident.toChars());
else if (ls.tf != sc.tf)
cs.error("cannot continue out of `finally` block");
else
{
result = cs;
return;
}
return setError();
}
}
cs.error("enclosing label `%s` for `continue` not found", cs.ident.toChars());
return setError();
}
else if (!sc.scontinue)
{
if (sc.os && sc.os.tok != TOK.onScopeFailure)
{
cs.error("`continue` is not allowed inside `%s` bodies", Token.toChars(sc.os.tok));
}
else if (sc.fes)
{
// Replace continue; with return 0;
result = new ReturnStatement(Loc.initial, IntegerExp.literal!0);
return;
}
else
cs.error("`continue` is not inside a loop");
return setError();
}
else if (sc.scontinue.isForwardingStatement())
{
cs.error("must use labeled `continue` within `static foreach`");
}
result = cs;
}
override void visit(SynchronizedStatement ss)
{
/* https://dlang.org/spec/statement.html#synchronized-statement
*/
if (ss.exp)
{
ss.exp = ss.exp.expressionSemantic(sc);
ss.exp = resolveProperties(sc, ss.exp);
ss.exp = ss.exp.optimize(WANTvalue);
ss.exp = checkGC(sc, ss.exp);
if (ss.exp.op == EXP.error)
{
if (ss._body)
ss._body = ss._body.statementSemantic(sc);
return setError();
}
ClassDeclaration cd = ss.exp.type.isClassHandle();
if (!cd)
{
ss.error("can only `synchronize` on class objects, not `%s`", ss.exp.type.toChars());
return setError();
}
else if (cd.isInterfaceDeclaration())
{
/* Cast the interface to an object, as the object has the monitor,
* not the interface.
*/
if (!ClassDeclaration.object)
{
ss.error("missing or corrupt object.d");
fatal();
}
Type t = ClassDeclaration.object.type;
t = t.typeSemantic(Loc.initial, sc).toBasetype();
assert(t.ty == Tclass);
ss.exp = new CastExp(ss.loc, ss.exp, t);
ss.exp = ss.exp.expressionSemantic(sc);
}
version (all)
{
/* Rewrite as:
* auto tmp = exp;
* _d_monitorenter(tmp);
* try { body } finally { _d_monitorexit(tmp); }
*/
auto tmp = copyToTemp(0, "__sync", ss.exp);
tmp.dsymbolSemantic(sc);
auto cs = new Statements();
cs.push(new ExpStatement(ss.loc, tmp));
auto args = new Parameters();
args.push(new Parameter(0, ClassDeclaration.object.type, null, null, null));
FuncDeclaration fdenter = FuncDeclaration.genCfunc(args, Type.tvoid, Id.monitorenter);
Expression e = new CallExp(ss.loc, fdenter, new VarExp(ss.loc, tmp));
e.type = Type.tvoid; // do not run semantic on e
cs.push(new ExpStatement(ss.loc, e));
FuncDeclaration fdexit = FuncDeclaration.genCfunc(args, Type.tvoid, Id.monitorexit);
e = new CallExp(ss.loc, fdexit, new VarExp(ss.loc, tmp));
e.type = Type.tvoid; // do not run semantic on e
Statement s = new ExpStatement(ss.loc, e);
s = new TryFinallyStatement(ss.loc, ss._body, s);
cs.push(s);
s = new CompoundStatement(ss.loc, cs);
result = s.statementSemantic(sc);
}
}
else
{
/* Generate our own critical section, then rewrite as:
* static shared void* __critsec;
* _d_criticalenter2(&__critsec);
* try { body } finally { _d_criticalexit(__critsec); }
*/
auto id = Identifier.generateId("__critsec");
auto t = Type.tvoidptr;
auto tmp = new VarDeclaration(ss.loc, t, id, null);
tmp.storage_class |= STC.temp | STC.shared_ | STC.static_;
Expression tmpExp = new VarExp(ss.loc, tmp);
auto cs = new Statements();
cs.push(new ExpStatement(ss.loc, tmp));
/* This is just a dummy variable for "goto skips declaration" error.
* Backend optimizer could remove this unused variable.
*/
auto v = new VarDeclaration(ss.loc, Type.tvoidptr, Identifier.generateId("__sync"), null);
v.dsymbolSemantic(sc);
cs.push(new ExpStatement(ss.loc, v));
auto enterArgs = new Parameters();
enterArgs.push(new Parameter(0, t.pointerTo(), null, null, null));
FuncDeclaration fdenter = FuncDeclaration.genCfunc(enterArgs, Type.tvoid, Id.criticalenter, STC.nothrow_);
Expression e = new AddrExp(ss.loc, tmpExp);
e = e.expressionSemantic(sc);
e = new CallExp(ss.loc, fdenter, e);
e.type = Type.tvoid; // do not run semantic on e
cs.push(new ExpStatement(ss.loc, e));
auto exitArgs = new Parameters();
exitArgs.push(new Parameter(0, t, null, null, null));
FuncDeclaration fdexit = FuncDeclaration.genCfunc(exitArgs, Type.tvoid, Id.criticalexit, STC.nothrow_);
e = new CallExp(ss.loc, fdexit, tmpExp);
e.type = Type.tvoid; // do not run semantic on e
Statement s = new ExpStatement(ss.loc, e);
s = new TryFinallyStatement(ss.loc, ss._body, s);
cs.push(s);
s = new CompoundStatement(ss.loc, cs);
result = s.statementSemantic(sc);
}
}
override void visit(WithStatement ws)
{
/* https://dlang.org/spec/statement.html#with-statement
*/
ScopeDsymbol sym;
Initializer _init;
//printf("WithStatement::semantic()\n");
ws.exp = ws.exp.expressionSemantic(sc);
ws.exp = resolveProperties(sc, ws.exp);
ws.exp = ws.exp.optimize(WANTvalue);
ws.exp = checkGC(sc, ws.exp);
if (ws.exp.op == EXP.error)
return setError();
if (ws.exp.op == EXP.scope_)
{
sym = new WithScopeSymbol(ws);
sym.parent = sc.scopesym;
sym.endlinnum = ws.endloc.linnum;
}
else if (ws.exp.op == EXP.type)
{
Dsymbol s = (cast(TypeExp)ws.exp).type.toDsymbol(sc);
if (!s || !s.isScopeDsymbol())
{
ws.error("`with` type `%s` has no members", ws.exp.toChars());
return setError();
}
sym = new WithScopeSymbol(ws);
sym.parent = sc.scopesym;
sym.endlinnum = ws.endloc.linnum;
}
else
{
Type t = ws.exp.type.toBasetype();
Expression olde = ws.exp;
if (t.ty == Tpointer)
{
ws.exp = new PtrExp(ws.loc, ws.exp);
ws.exp = ws.exp.expressionSemantic(sc);
t = ws.exp.type.toBasetype();
}
assert(t);
t = t.toBasetype();
if (t.isClassHandle())
{
_init = new ExpInitializer(ws.loc, ws.exp);
ws.wthis = new VarDeclaration(ws.loc, ws.exp.type, Id.withSym, _init);
ws.wthis.storage_class |= STC.temp;
ws.wthis.dsymbolSemantic(sc);
sym = new WithScopeSymbol(ws);
sym.parent = sc.scopesym;
sym.endlinnum = ws.endloc.linnum;
}
else if (t.ty == Tstruct)
{
if (!ws.exp.isLvalue())
{
/* Re-write to
* {
* auto __withtmp = exp
* with(__withtmp)
* {
* ...
* }
* }
*/
auto tmp = copyToTemp(0, "__withtmp", ws.exp);
tmp.dsymbolSemantic(sc);
auto es = new ExpStatement(ws.loc, tmp);
ws.exp = new VarExp(ws.loc, tmp);
Statement ss = new ScopeStatement(ws.loc, new CompoundStatement(ws.loc, es, ws), ws.endloc);
result = ss.statementSemantic(sc);
return;
}
Expression e = ws.exp.addressOf();
_init = new ExpInitializer(ws.loc, e);
ws.wthis = new VarDeclaration(ws.loc, e.type, Id.withSym, _init);
ws.wthis.storage_class |= STC.temp;
ws.wthis.dsymbolSemantic(sc);
sym = new WithScopeSymbol(ws);
// Need to set the scope to make use of resolveAliasThis
sym.setScope(sc);
sym.parent = sc.scopesym;
sym.endlinnum = ws.endloc.linnum;
}
else
{
ws.error("`with` expressions must be aggregate types or pointers to them, not `%s`", olde.type.toChars());
return setError();
}
}
if (ws._body)
{
sym._scope = sc;
sc = sc.push(sym);
sc.insert(sym);
ws._body = ws._body.statementSemantic(sc);
sc.pop();
if (ws._body && ws._body.isErrorStatement())
{
result = ws._body;
return;
}
}
result = ws;
}
// https://dlang.org/spec/statement.html#TryStatement
override void visit(TryCatchStatement tcs)
{
//printf("TryCatchStatement.semantic()\n");
if (!global.params.useExceptions)
{
tcs.error("Cannot use try-catch statements with -betterC");
return setError();
}
if (!ClassDeclaration.throwable)
{
tcs.error("Cannot use try-catch statements because `object.Throwable` was not declared");
return setError();
}
uint flags;
enum FLAGcpp = 1;
enum FLAGd = 2;
tcs.tryBody = sc.tryBody; // chain on the in-flight tryBody
tcs._body = tcs._body.semanticScope(sc, null, null, tcs);
/* Even if body is empty, still do semantic analysis on catches
*/
bool catchErrors = false;
foreach (i, c; *tcs.catches)
{
c.catchSemantic(sc);
if (c.errors)
{
catchErrors = true;
continue;
}
auto cd = c.type.toBasetype().isClassHandle();
flags |= cd.isCPPclass() ? FLAGcpp : FLAGd;
// Determine if current catch 'hides' any previous catches
foreach (j; 0 .. i)
{
Catch cj = (*tcs.catches)[j];
const si = c.loc.toChars();
const sj = cj.loc.toChars();
if (c.type.toBasetype().implicitConvTo(cj.type.toBasetype()))
{
tcs.error("`catch` at %s hides `catch` at %s", sj, si);
catchErrors = true;
}
}
}
if (sc.func)
{
sc.func.flags |= FUNCFLAG.hasCatches;
if (flags == (FLAGcpp | FLAGd))
{
tcs.error("cannot mix catching D and C++ exceptions in the same try-catch");
catchErrors = true;
}
}
if (catchErrors)
return setError();
// No actual code in the try (i.e. omitted any conditionally compiled code)
// Could also be extended to check for hasCode
if (!tcs._body)
return;
if (tcs._body.isErrorStatement())
{
result = tcs._body;
return;
}
/* If the try body never throws, we can eliminate any catches
* of recoverable exceptions.
*/
if (!(tcs._body.blockExit(sc.func, false) & BE.throw_) && ClassDeclaration.exception)
{
foreach_reverse (i; 0 .. tcs.catches.dim)
{
Catch c = (*tcs.catches)[i];
/* If catch exception type is derived from Exception
*/
if (c.type.toBasetype().implicitConvTo(ClassDeclaration.exception.type) &&
(!c.handler || !c.handler.comeFrom()) && !(sc.flags & SCOPE.debug_))
{
// Remove c from the array of catches
tcs.catches.remove(i);
}
}
}
if (tcs.catches.dim == 0)
{
result = tcs._body.hasCode() ? tcs._body : null;
return;
}
result = tcs;
}
override void visit(TryFinallyStatement tfs)
{
//printf("TryFinallyStatement::semantic()\n");
tfs.tryBody = sc.tryBody; // chain on in-flight tryBody
tfs._body = tfs._body.semanticScope(sc, null, null, tfs);
sc = sc.push();
sc.tf = tfs;
sc.sbreak = null;
sc.scontinue = null; // no break or continue out of finally block
tfs.finalbody = tfs.finalbody.semanticNoScope(sc);
sc.pop();
if (!tfs._body)
{
result = tfs.finalbody;
return;
}
if (!tfs.finalbody)
{
result = tfs._body;
return;
}
auto blockexit = tfs._body.blockExit(sc.func, false);
// if not worrying about exceptions
if (!(global.params.useExceptions && ClassDeclaration.throwable))
blockexit &= ~BE.throw_; // don't worry about paths that otherwise may throw
// Don't care about paths that halt, either
if ((blockexit & ~BE.halt) == BE.fallthru)
{
result = new CompoundStatement(tfs.loc, tfs._body, tfs.finalbody);
return;
}
tfs.bodyFallsThru = (blockexit & BE.fallthru) != 0;
result = tfs;
}
override void visit(ScopeGuardStatement oss)
{
/* https://dlang.org/spec/statement.html#scope-guard-statement
*/
if (oss.tok != TOK.onScopeExit)
{
// scope(success) and scope(failure) are rewritten to try-catch(-finally) statement,
// so the generated catch block cannot be placed in finally block.
// See also Catch::semantic.
if (sc.os && sc.os.tok != TOK.onScopeFailure)
{
// If enclosing is scope(success) or scope(exit), this will be placed in finally block.
oss.error("cannot put `%s` statement inside `%s`", Token.toChars(oss.tok), Token.toChars(sc.os.tok));
return setError();
}
if (sc.tf)
{
oss.error("cannot put `%s` statement inside `finally` block", Token.toChars(oss.tok));
return setError();
}
}
sc = sc.push();
sc.tf = null;
sc.os = oss;
if (oss.tok != TOK.onScopeFailure)
{
// Jump out from scope(failure) block is allowed.
sc.sbreak = null;
sc.scontinue = null;
}
oss.statement = oss.statement.semanticNoScope(sc);
sc.pop();
if (!oss.statement || oss.statement.isErrorStatement())
{
result = oss.statement;
return;
}
result = oss;
}
override void visit(ThrowStatement ts)
{
/* https://dlang.org/spec/statement.html#throw-statement
*/
//printf("ThrowStatement::semantic()\n");
if (throwSemantic(ts.loc, ts.exp, sc))
result = ts;
else
setError();
}
/**
* Run semantic on `throw <exp>`.
*
* Params:
* loc = location of the `throw`
* exp = value to be thrown
* sc = enclosing scope
*
* Returns: true if the `throw` is valid, or false if an error was found
*/
extern(D) static bool throwSemantic(const ref Loc loc, ref Expression exp, Scope* sc)
{
if (!global.params.useExceptions)
{
loc.error("Cannot use `throw` statements with -betterC");
return false;
}
if (!ClassDeclaration.throwable)
{
loc.error("Cannot use `throw` statements because `object.Throwable` was not declared");
return false;
}
if (FuncDeclaration fd = sc.parent.isFuncDeclaration())
fd.hasReturnExp |= 2;
if (exp.op == EXP.new_)
{
NewExp ne = cast(NewExp) exp;
ne.thrownew = true;
}
exp = exp.expressionSemantic(sc);
exp = resolveProperties(sc, exp);
exp = checkGC(sc, exp);
if (exp.op == EXP.error)
return false;
checkThrowEscape(sc, exp, false);
ClassDeclaration cd = exp.type.toBasetype().isClassHandle();
if (!cd || ((cd != ClassDeclaration.throwable) && !ClassDeclaration.throwable.isBaseOf(cd, null)))
{
loc.error("can only throw class objects derived from `Throwable`, not type `%s`", exp.type.toChars());
return false;
}
return true;
}
override void visit(DebugStatement ds)
{
if (ds.statement)
{
sc = sc.push();
sc.flags |= SCOPE.debug_;
ds.statement = ds.statement.statementSemantic(sc);
sc.pop();
}
result = ds.statement;
}
override void visit(GotoStatement gs)
{
/* https://dlang.org/spec/statement.html#goto-statement
*/
//printf("GotoStatement::semantic()\n");
FuncDeclaration fd = sc.func;
gs.ident = fixupLabelName(sc, gs.ident);
gs.label = fd.searchLabel(gs.ident, gs.loc);
gs.tryBody = sc.tryBody;
gs.tf = sc.tf;
gs.os = sc.os;
gs.lastVar = sc.lastVar;
if (!gs.label.statement && sc.fes)
{
/* Either the goto label is forward referenced or it
* is in the function that the enclosing foreach is in.
* Can't know yet, so wrap the goto in a scope statement
* so we can patch it later, and add it to a 'look at this later'
* list.
*/
gs.label.deleted = true;
auto ss = new ScopeStatement(gs.loc, gs, gs.loc);
sc.fes.gotos.push(ss); // 'look at this later' list
result = ss;
return;
}
// Add to fwdref list to check later
if (!gs.label.statement)
{
if (!fd.gotos)
fd.gotos = new GotoStatements();
fd.gotos.push(gs);
}
else if (!(sc.flags & SCOPE.Cfile) && gs.checkLabel())
return setError();
result = gs;
}
override void visit(LabelStatement ls)
{
//printf("LabelStatement::semantic()\n");
FuncDeclaration fd = sc.parent.isFuncDeclaration();
ls.ident = fixupLabelName(sc, ls.ident);
ls.tryBody = sc.tryBody;
ls.tf = sc.tf;
ls.os = sc.os;
ls.lastVar = sc.lastVar;
LabelDsymbol ls2 = fd.searchLabel(ls.ident, ls.loc);
if (ls2.statement)
{
ls.error("label `%s` already defined", ls2.toChars());
return setError();
}
else
ls2.statement = ls;
sc = sc.push();
sc.scopesym = sc.enclosing.scopesym;
sc.ctorflow.orCSX(CSX.label);
sc.slabel = ls;
if (ls.statement)
ls.statement = ls.statement.statementSemantic(sc);
sc.pop();
result = ls;
}
override void visit(AsmStatement s)
{
/* https://dlang.org/spec/statement.html#asm
*/
//printf("AsmStatement()::semantic()\n");
result = asmSemantic(s, sc);
}
override void visit(CompoundAsmStatement cas)
{
//printf("CompoundAsmStatement()::semantic()\n");
// Apply postfix attributes of the asm block to each statement.
sc = sc.push();
sc.stc |= cas.stc;
/* Go through the statements twice, first to declare any labels,
* second for anything else.
*/
foreach (ref s; *cas.statements)
{
if (s)
{
if (auto ls = s.isLabelStatement())
{
sc.func.searchLabel(ls.ident, ls.loc);
}
}
}
foreach (ref s; *cas.statements)
{
s = s ? s.statementSemantic(sc) : null;
}
assert(sc.func);
if (!(cas.stc & STC.pure_) && sc.func.setImpure())
cas.error("`asm` statement is assumed to be impure - mark it with `pure` if it is not");
if (!(cas.stc & STC.nogc) && sc.func.setGC())
cas.error("`asm` statement is assumed to use the GC - mark it with `@nogc` if it does not");
if (!(cas.stc & (STC.trusted | STC.safe)) && sc.func.setUnsafe())
cas.error("`asm` statement is assumed to be `@system` - mark it with `@trusted` if it is not");
sc.pop();
result = cas;
}
override void visit(ImportStatement imps)
{
/* https://dlang.org/spec/module.html#ImportDeclaration
*/
foreach (i; 0 .. imps.imports.dim)
{
Import s = (*imps.imports)[i].isImport();
assert(!s.aliasdecls.dim);
foreach (j, name; s.names)
{
Identifier _alias = s.aliases[j];
if (!_alias)
_alias = name;
auto tname = new TypeIdentifier(s.loc, name);
auto ad = new AliasDeclaration(s.loc, _alias, tname);
ad._import = s;
s.aliasdecls.push(ad);
}
s.dsymbolSemantic(sc);
// https://issues.dlang.org/show_bug.cgi?id=19942
// If the module that's being imported doesn't exist, don't add it to the symbol table
// for the current scope.
if (s.mod !is null)
{
Module.addDeferredSemantic2(s); // https://issues.dlang.org/show_bug.cgi?id=14666
sc.insert(s);
foreach (aliasdecl; s.aliasdecls)
{
sc.insert(aliasdecl);
}
}
}
result = imps;
}
}
void catchSemantic(Catch c, Scope* sc)
{
//printf("Catch::semantic(%s)\n", ident.toChars());
if (sc.os && sc.os.tok != TOK.onScopeFailure)
{
// If enclosing is scope(success) or scope(exit), this will be placed in finally block.
error(c.loc, "cannot put `catch` statement inside `%s`", Token.toChars(sc.os.tok));
c.errors = true;
}
if (sc.tf)
{
/* This is because the _d_local_unwind() gets the stack munged
* up on this. The workaround is to place any try-catches into
* a separate function, and call that.
* To fix, have the compiler automatically convert the finally
* body into a nested function.
*/
error(c.loc, "cannot put `catch` statement inside `finally` block");
c.errors = true;
}
auto sym = new ScopeDsymbol();
sym.parent = sc.scopesym;
sc = sc.push(sym);
if (!c.type)
{
error(c.loc, "`catch` statement without an exception specification is deprecated");
errorSupplemental(c.loc, "use `catch(Throwable)` for old behavior");
c.errors = true;
// reference .object.Throwable
c.type = getThrowable();
}
c.type = c.type.typeSemantic(c.loc, sc);
if (c.type == Type.terror)
{
c.errors = true;
sc.pop();
return;
}
StorageClass stc;
auto cd = c.type.toBasetype().isClassHandle();
if (!cd)
{
error(c.loc, "can only catch class objects, not `%s`", c.type.toChars());
c.errors = true;
}
else if (cd.isCPPclass())
{
if (!target.cpp.exceptions)
{
error(c.loc, "catching C++ class objects not supported for this target");
c.errors = true;
}
if (sc.func && !sc.intypeof && !c.internalCatch && sc.func.setUnsafe())
{
error(c.loc, "cannot catch C++ class objects in `@safe` code");
c.errors = true;
}
}
else if (cd != ClassDeclaration.throwable && !ClassDeclaration.throwable.isBaseOf(cd, null))
{
error(c.loc, "can only catch class objects derived from `Throwable`, not `%s`", c.type.toChars());
c.errors = true;
}
else if (sc.func && !sc.intypeof && !c.internalCatch && ClassDeclaration.exception &&
cd != ClassDeclaration.exception && !ClassDeclaration.exception.isBaseOf(cd, null) &&
sc.func.setUnsafe())
{
error(c.loc, "can only catch class objects derived from `Exception` in `@safe` code, not `%s`", c.type.toChars());
c.errors = true;
}
else if (global.params.ehnogc)
{
stc |= STC.scope_;
}
// DIP1008 requires destruction of the Throwable, even if the user didn't specify an identifier
auto ident = c.ident;
if (!ident && global.params.ehnogc)
ident = Identifier.generateAnonymousId("var");
if (ident)
{
c.var = new VarDeclaration(c.loc, c.type, ident, null, stc);
c.var.iscatchvar = true;
c.var.dsymbolSemantic(sc);
sc.insert(c.var);
if (global.params.ehnogc && stc & STC.scope_)
{
/* Add a destructor for c.var
* try { handler } finally { if (!__ctfe) _d_delThrowable(var); }
*/
assert(!c.var.edtor); // ensure we didn't create one in callScopeDtor()
Loc loc = c.loc;
Expression e = new VarExp(loc, c.var);
e = new CallExp(loc, new IdentifierExp(loc, Id._d_delThrowable), e);
Expression ec = new IdentifierExp(loc, Id.ctfe);
ec = new NotExp(loc, ec);
Statement s = new IfStatement(loc, null, ec, new ExpStatement(loc, e), null, loc);
c.handler = new TryFinallyStatement(loc, c.handler, s);
}
}
c.handler = c.handler.statementSemantic(sc);
if (c.handler && c.handler.isErrorStatement())
c.errors = true;
sc.pop();
}
Statement semanticNoScope(Statement s, Scope* sc)
{
//printf("Statement::semanticNoScope() %s\n", toChars());
if (!s.isCompoundStatement() && !s.isScopeStatement())
{
s = new CompoundStatement(s.loc, s); // so scopeCode() gets called
}
s = s.statementSemantic(sc);
return s;
}
// Same as semanticNoScope(), but do create a new scope
private Statement semanticScope(Statement s, Scope* sc, Statement sbreak, Statement scontinue, Statement tryBody)
{
auto sym = new ScopeDsymbol();
sym.parent = sc.scopesym;
Scope* scd = sc.push(sym);
if (sbreak)
scd.sbreak = sbreak;
if (scontinue)
scd.scontinue = scontinue;
if (tryBody)
scd.tryBody = tryBody;
s = s.semanticNoScope(scd);
scd.pop();
return s;
}
/****************************************
* If `statement` has code that needs to run in a finally clause
* at the end of the current scope, return that code in the form of
* a Statement.
* Params:
* statement = the statement
* sc = context
* sentry = set to code executed upon entry to the scope
* sexception = set to code executed upon exit from the scope via exception
* sfinally = set to code executed in finally block
* Returns:
* code to be run in the finally clause
*/
Statement scopeCode(Statement statement, Scope* sc, out Statement sentry, out Statement sexception, out Statement sfinally)
{
if (auto es = statement.isExpStatement())
{
if (es.exp && es.exp.op == EXP.declaration)
{
auto de = cast(DeclarationExp)es.exp;
auto v = de.declaration.isVarDeclaration();
if (v && !v.isDataseg())
{
if (v.needsScopeDtor())
{
sfinally = new DtorExpStatement(es.loc, v.edtor, v);
v.storage_class |= STC.nodtor; // don't add in dtor again
}
}
}
return es;
}
else if (auto sgs = statement.isScopeGuardStatement())
{
Statement s = new PeelStatement(sgs.statement);
switch (sgs.tok)
{
case TOK.onScopeExit:
sfinally = s;
break;
case TOK.onScopeFailure:
sexception = s;
break;
case TOK.onScopeSuccess:
{
/* Create:
* sentry: bool x = false;
* sexception: x = true;
* sfinally: if (!x) statement;
*/
auto v = copyToTemp(0, "__os", IntegerExp.createBool(false));
v.dsymbolSemantic(sc);
sentry = new ExpStatement(statement.loc, v);
Expression e = IntegerExp.createBool(true);
e = new AssignExp(Loc.initial, new VarExp(Loc.initial, v), e);
sexception = new ExpStatement(Loc.initial, e);
e = new VarExp(Loc.initial, v);
e = new NotExp(Loc.initial, e);
sfinally = new IfStatement(Loc.initial, null, e, s, null, Loc.initial);
break;
}
default:
assert(0);
}
return null;
}
else if (auto ls = statement.isLabelStatement())
{
if (ls.statement)
ls.statement = ls.statement.scopeCode(sc, sentry, sexception, sfinally);
return ls;
}
return statement;
}
/*******************
* Type check and unroll `foreach` over an expression tuple as well
* as `static foreach` statements and `static foreach`
* declarations. For `static foreach` statements and `static
* foreach` declarations, the visitor interface is used (and the
* result is written into the `result` field.) For `static
* foreach` declarations, the resulting Dsymbols* are returned
* directly.
*
* The unrolled body is wrapped into a
* - UnrolledLoopStatement, for `foreach` over an expression tuple.
* - ForwardingStatement, for `static foreach` statements.
* - ForwardingAttribDeclaration, for `static foreach` declarations.
*
* `static foreach` variables are declared as `STC.local`, such
* that they are inserted into the local symbol tables of the
* forwarding constructs instead of forwarded. For `static
* foreach` with multiple foreach loop variables whose aggregate
* has been lowered into a sequence of tuples, this function
* expands the tuples into multiple `STC.local` `static foreach`
* variables.
*/
public auto makeTupleForeach(Scope* sc, bool isStatic, bool isDecl, ForeachStatement fs, Dsymbols* dbody, bool needExpansion)
{
// Voldemort return type
union U
{
Statement statement;
Dsymbols* decl;
}
U result;
auto returnEarly()
{
if (isDecl)
result.decl = null;
else
result.statement = new ErrorStatement();
return result;
}
auto loc = fs.loc;
size_t dim = fs.parameters.dim;
const bool skipCheck = isStatic && needExpansion;
if (!skipCheck && (dim < 1 || dim > 2))
{
fs.error("only one (value) or two (key,value) arguments for tuple `foreach`");
return returnEarly();
}
Type paramtype = (*fs.parameters)[dim - 1].type;
if (paramtype)
{
paramtype = paramtype.typeSemantic(loc, sc);
if (paramtype.ty == Terror)
{
return returnEarly();
}
}
Type tab = fs.aggr.type.toBasetype();
TypeTuple tuple = cast(TypeTuple)tab;
Statements* statements;
Dsymbols* declarations;
if (isDecl)
declarations = new Dsymbols();
else
statements = new Statements();
//printf("aggr: op = %d, %s\n", fs.aggr.op, fs.aggr.toChars());
size_t n;
TupleExp te = null;
if (fs.aggr.op == EXP.tuple) // expression tuple
{
te = cast(TupleExp)fs.aggr;
n = te.exps.dim;
}
else if (fs.aggr.op == EXP.type) // type tuple
{
n = Parameter.dim(tuple.arguments);
}
else
assert(0);
foreach (j; 0 .. n)
{
size_t k = (fs.op == TOK.foreach_) ? j : n - 1 - j;
Expression e = null;
Type t = null;
if (te)
e = (*te.exps)[k];
else
t = Parameter.getNth(tuple.arguments, k).type;
Parameter p = (*fs.parameters)[0];
Statements* stmts;
Dsymbols* decls;
if (isDecl)
decls = new Dsymbols();
else
stmts = new Statements();
const bool skip = isStatic && needExpansion;
if (!skip && dim == 2)
{
// Declare key
if (p.storageClass & (STC.out_ | STC.ref_ | STC.lazy_))
{
fs.error("no storage class for key `%s`", p.ident.toChars());
return returnEarly();
}
if (isStatic)
{
if (!p.type)
{
p.type = Type.tsize_t;
}
}
p.type = p.type.typeSemantic(loc, sc);
if (!p.type.isintegral())
{
fs.error("foreach: key cannot be of non-integral type `%s`",
p.type.toChars());
return returnEarly();
}
const length = te ? te.exps.length : tuple.arguments.length;
IntRange dimrange = IntRange(SignExtendedNumber(length))._cast(Type.tsize_t);
// https://issues.dlang.org/show_bug.cgi?id=12504
dimrange.imax = SignExtendedNumber(dimrange.imax.value-1);
if (!IntRange.fromType(p.type).contains(dimrange))
{
fs.error("index type `%s` cannot cover index range 0..%llu",
p.type.toChars(), cast(ulong)length);
return returnEarly();
}
Initializer ie = new ExpInitializer(Loc.initial, new IntegerExp(k));
auto var = new VarDeclaration(loc, p.type, p.ident, ie);
var.storage_class |= STC.foreach_ | STC.manifest;
if (isStatic)
var.storage_class |= STC.local;
if (isDecl)
decls.push(var);
else
stmts.push(new ExpStatement(loc, var));
p = (*fs.parameters)[1]; // value
}
/***********************
* Declares a unrolled `foreach` loop variable or a `static foreach` variable.
*
* Params:
* storageClass = The storage class of the variable.
* type = The declared type of the variable.
* ident = The name of the variable.
* e = The initializer of the variable (i.e. the current element of the looped over aggregate).
* t = The type of the initializer.
* Returns:
* `true` iff the declaration was successful.
*/
bool declareVariable(StorageClass storageClass, Type type, Identifier ident, Expression e, Type t)
{
if (storageClass & (STC.out_ | STC.lazy_) ||
storageClass & STC.ref_ && !te)
{
fs.error("no storage class for value `%s`", ident.toChars());
return false;
}
Declaration var;
if (e)
{
Type tb = e.type.toBasetype();
Dsymbol ds = null;
if (!(storageClass & STC.manifest))
{
if ((isStatic || tb.ty == Tfunction || storageClass&STC.alias_) && e.op == EXP.variable)
ds = (cast(VarExp)e).var;
else if (e.op == EXP.template_)
ds = (cast(TemplateExp)e).td;
else if (e.op == EXP.scope_)
ds = (cast(ScopeExp)e).sds;
else if (e.op == EXP.function_)
{
auto fe = cast(FuncExp)e;
ds = fe.td ? cast(Dsymbol)fe.td : fe.fd;
}
else if (e.op == EXP.overloadSet)
ds = (cast(OverExp)e).vars;
}
else if (storageClass & STC.alias_)
{
fs.error("`foreach` loop variable cannot be both `enum` and `alias`");
return false;
}
if (ds)
{
var = new AliasDeclaration(loc, ident, ds);
if (storageClass & STC.ref_)
{
fs.error("symbol `%s` cannot be `ref`", ds.toChars());
return false;
}
if (paramtype)
{
fs.error("cannot specify element type for symbol `%s`", ds.toChars());
return false;
}
}
else if (e.op == EXP.type)
{
var = new AliasDeclaration(loc, ident, e.type);
if (paramtype)
{
fs.error("cannot specify element type for type `%s`", e.type.toChars());
return false;
}
}
else
{
e = resolveProperties(sc, e);
Initializer ie = new ExpInitializer(Loc.initial, e);
auto v = new VarDeclaration(loc, type, ident, ie, storageClass);
v.storage_class |= STC.foreach_;
if (storageClass & STC.ref_)
v.storage_class |= STC.ref_;
if (isStatic || storageClass&STC.manifest || e.isConst() ||
e.op == EXP.string_ ||
e.op == EXP.structLiteral ||
e.op == EXP.arrayLiteral)
{
if (v.storage_class & STC.ref_)
{
if (!isStatic)
{
fs.error("constant value `%s` cannot be `ref`", ie.toChars());
}
else
{
if (!needExpansion)
{
fs.error("constant value `%s` cannot be `ref`", ie.toChars());
}
else
{
fs.error("constant value `%s` cannot be `ref`", ident.toChars());
}
}
return false;
}
else
v.storage_class |= STC.manifest;
}
var = v;
}
}
else
{
var = new AliasDeclaration(loc, ident, t);
if (paramtype)
{
fs.error("cannot specify element type for symbol `%s`", fs.toChars());
return false;
}
}
if (isStatic)
{
var.storage_class |= STC.local;
}
if (isDecl)
decls.push(var);
else
stmts.push(new ExpStatement(loc, var));
return true;
}
if (!isStatic)
{
// Declare value
if (!declareVariable(p.storageClass, p.type, p.ident, e, t))
{
return returnEarly();
}
}
else
{
if (!needExpansion)
{
// Declare value
if (!declareVariable(p.storageClass, p.type, p.ident, e, t))
{
return returnEarly();
}
}
else
{ // expand tuples into multiple `static foreach` variables.
assert(e && !t);
auto ident = Identifier.generateId("__value");
declareVariable(0, e.type, ident, e, null);
import dmd.cond: StaticForeach;
auto field = Identifier.idPool(StaticForeach.tupleFieldName.ptr,StaticForeach.tupleFieldName.length);
Expression access = new DotIdExp(loc, e, field);
access = expressionSemantic(access, sc);
if (!tuple) return returnEarly();
//printf("%s\n",tuple.toChars());
foreach (l; 0 .. dim)
{
auto cp = (*fs.parameters)[l];
Expression init_ = new IndexExp(loc, access, new IntegerExp(loc, l, Type.tsize_t));
init_ = init_.expressionSemantic(sc);
assert(init_.type);
declareVariable(p.storageClass, init_.type, cp.ident, init_, null);
}
}
}
Statement s;
Dsymbol d;
if (isDecl)
decls.append(Dsymbol.arraySyntaxCopy(dbody));
else
{
if (fs._body) // https://issues.dlang.org/show_bug.cgi?id=17646
stmts.push(fs._body.syntaxCopy());
s = new CompoundStatement(loc, stmts);
}
if (!isStatic)
{
s = new ScopeStatement(loc, s, fs.endloc);
}
else if (isDecl)
{
import dmd.attrib: ForwardingAttribDeclaration;
d = new ForwardingAttribDeclaration(decls);
}
else
{
s = new ForwardingStatement(loc, s);
}
if (isDecl)
declarations.push(d);
else
statements.push(s);
}
if (!isStatic)
{
Statement res = new UnrolledLoopStatement(loc, statements);
if (LabelStatement ls = checkLabeledLoop(sc, fs))
ls.gotoTarget = res;
if (te && te.e0)
res = new CompoundStatement(loc, new ExpStatement(te.e0.loc, te.e0), res);
result.statement = res;
}
else if (isDecl)
result.decl = declarations;
else
result.statement = new CompoundStatement(loc, statements);
return result;
}
/*********************************
* Flatten out the scope by presenting `statement`
* as an array of statements.
* Params:
* statement = the statement to flatten
* sc = context
* Returns:
* The array of `Statements`, or `null` if no flattening necessary
*/
private Statements* flatten(Statement statement, Scope* sc)
{
static auto errorStatements()
{
auto a = new Statements();
a.push(new ErrorStatement());
return a;
}
/*compound and expression statements have classes that inherit from them with the same
*flattening behavior, so the isXXX methods won't work
*/
switch(statement.stmt)
{
case STMT.Compound:
case STMT.CompoundDeclaration:
return (cast(CompoundStatement)statement).statements;
case STMT.Exp:
case STMT.DtorExp:
auto es = cast(ExpStatement)statement;
/* https://issues.dlang.org/show_bug.cgi?id=14243
* expand template mixin in statement scope
* to handle variable destructors.
*/
if (!es.exp || !es.exp.isDeclarationExp())
return null;
Dsymbol d = es.exp.isDeclarationExp().declaration;
auto tm = d.isTemplateMixin();
if (!tm)
return null;
Expression e = es.exp.expressionSemantic(sc);
if (e.op == EXP.error || tm.errors)
return errorStatements();
assert(tm.members);
Statement s = toStatement(tm);
version (none)
{
OutBuffer buf;
buf.doindent = 1;
HdrGenState hgs;
hgs.hdrgen = true;
toCBuffer(s, &buf, &hgs);
printf("tm ==> s = %s\n", buf.peekChars());
}
auto a = new Statements();
a.push(s);
return a;
case STMT.Forwarding:
/***********************
* ForwardingStatements are distributed over the flattened
* sequence of statements. This prevents flattening to be
* "blocked" by a ForwardingStatement and is necessary, for
* example, to support generating scope guards with `static
* foreach`:
*
* static foreach(i; 0 .. 10) scope(exit) writeln(i);
* writeln("this is printed first");
* // then, it prints 10, 9, 8, 7, ...
*/
auto fs = statement.isForwardingStatement();
if (!fs.statement)
{
return null;
}
sc = sc.push(fs.sym);
auto a = fs.statement.flatten(sc);
sc = sc.pop();
if (!a)
{
return a;
}
auto b = new Statements(a.dim);
foreach (i, s; *a)
{
(*b)[i] = s ? new ForwardingStatement(s.loc, fs.sym, s) : null;
}
return b;
case STMT.Conditional:
auto cs = statement.isConditionalStatement();
Statement s;
//printf("ConditionalStatement::flatten()\n");
if (cs.condition.include(sc))
{
DebugCondition dc = cs.condition.isDebugCondition();
if (dc)
{
s = new DebugStatement(cs.loc, cs.ifbody);
debugThrowWalker(cs.ifbody);
}
else
s = cs.ifbody;
}
else
s = cs.elsebody;
auto a = new Statements();
a.push(s);
return a;
case STMT.StaticForeach:
auto sfs = statement.isStaticForeachStatement();
sfs.sfe.prepare(sc);
if (sfs.sfe.ready())
{
Statement s = makeTupleForeach(sc, true, false, sfs.sfe.aggrfe, null, sfs.sfe.needExpansion).statement;
auto result = s.flatten(sc);
if (result)
{
return result;
}
result = new Statements();
result.push(s);
return result;
}
else
return errorStatements();
case STMT.Debug:
auto ds = statement.isDebugStatement();
Statements* a = ds.statement ? ds.statement.flatten(sc) : null;
if (!a)
return null;
foreach (ref s; *a)
{
s = new DebugStatement(ds.loc, s);
}
return a;
case STMT.Label:
auto ls = statement.isLabelStatement();
if (!ls.statement)
return null;
Statements* a = null;
a = ls.statement.flatten(sc);
if (!a)
return null;
if (!a.dim)
{
a.push(new ExpStatement(ls.loc, cast(Expression)null));
}
// reuse 'this' LabelStatement
ls.statement = (*a)[0];
(*a)[0] = ls;
return a;
case STMT.Compile:
auto cs = statement.isCompileStatement();
OutBuffer buf;
if (expressionsToString(buf, sc, cs.exps))
return errorStatements();
const errors = global.errors;
const len = buf.length;
buf.writeByte(0);
const str = buf.extractSlice()[0 .. len];
scope p = new Parser!ASTCodegen(cs.loc, sc._module, str, false);
p.nextToken();
auto a = new Statements();
while (p.token.value != TOK.endOfFile)
{
Statement s = p.parseStatement(ParseStatementFlags.semi | ParseStatementFlags.curlyScope);
if (!s || global.errors != errors)
return errorStatements();
a.push(s);
}
return a;
default:
return null;
}
}
/***********************************************************
* Convert TemplateMixin members (which are Dsymbols) to Statements.
* Params:
* s = the symbol to convert to a Statement
* Returns:
* s redone as a Statement
*/
private Statement toStatement(Dsymbol s)
{
Statement result;
if (auto tm = s.isTemplateMixin())
{
auto a = new Statements();
foreach (m; *tm.members)
{
if (Statement sx = toStatement(m))
a.push(sx);
}
result = new CompoundStatement(tm.loc, a);
}
else if (s.isVarDeclaration() ||
s.isAggregateDeclaration() ||
s.isFuncDeclaration() ||
s.isEnumDeclaration() ||
s.isAliasDeclaration() ||
s.isTemplateDeclaration())
{
/* Perhaps replace the above with isScopeDsymbol() || isDeclaration()
*/
/* An actual declaration symbol will be converted to DeclarationExp
* with ExpStatement.
*/
auto de = new DeclarationExp(s.loc, s);
de.type = Type.tvoid; // avoid repeated semantic
result = new ExpStatement(s.loc, de);
}
else if (auto d = s.isAttribDeclaration())
{
/* All attributes have been already picked by the semantic analysis of
* 'bottom' declarations (function, struct, class, etc).
* So we don't have to copy them.
*/
if (Dsymbols* a = d.include(null))
{
auto statements = new Statements();
foreach (sx; *a)
{
statements.push(toStatement(sx));
}
result = new CompoundStatement(d.loc, statements);
}
}
else if (s.isStaticAssert() ||
s.isImport())
{
/* Ignore as they are not Statements
*/
}
else
{
.error(Loc.initial, "Internal Compiler Error: cannot mixin %s `%s`\n", s.kind(), s.toChars());
result = new ErrorStatement();
}
return result;
}
/**
Marks all occurring ThrowStatements as internalThrows.
This is intended to be called from a DebugStatement as it allows
to mark all its nodes as nothrow.
Params:
s = AST Node to traverse
*/
private void debugThrowWalker(Statement s)
{
extern(C++) final class DebugWalker : SemanticTimeTransitiveVisitor
{
alias visit = SemanticTimeTransitiveVisitor.visit;
public:
override void visit(ThrowStatement s)
{
s.internalThrow = true;
}
override void visit(CallExp s)
{
s.inDebugStatement = true;
}
}
scope walker = new DebugWalker();
s.accept(walker);
}