diff options
Diffstat (limited to 'gcc/d/dmd/statementsem.d')
-rw-r--r-- | gcc/d/dmd/statementsem.d | 4995 |
1 files changed, 4995 insertions, 0 deletions
diff --git a/gcc/d/dmd/statementsem.d b/gcc/d/dmd/statementsem.d new file mode 100644 index 0000000..f067c91 --- /dev/null +++ b/gcc/d/dmd/statementsem.d @@ -0,0 +1,4995 @@ +/** + * Does semantic analysis for statements. + * + * Specification: $(LINK2 https://dlang.org/spec/statement.html, Statements) + * + * Copyright: Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved + * Authors: $(LINK2 http://www.digitalmars.com, Walter Bright) + * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) + * 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.init; +import dmd.intrange; +import dmd.mtype; +import dmd.nogc; +import dmd.opover; +import dmd.parse; +import dmd.printast; +import dmd.root.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) +{ + auto ec = lastComma(e); + if (ec.op == TOK.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; +} + +private 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 (discardValue(s.exp)) + s.exp = ErrorExp.get(); + + s.exp = s.exp.optimize(WANTvalue); + s.exp = checkGC(sc, s.exp); + if (s.exp.op == TOK.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 == TOK.dotIdentifier) + (cast(DotIdExp)ds.condition).noderef = true; + + // check in syntax level + ds.condition = checkAssignmentAsCondition(ds.condition); + + 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 == TOK.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 == TOK.dotIdentifier) + (cast(DotIdExp)fs.condition).noderef = true; + + // check in syntax level + fs.condition = checkAssignmentAsCondition(fs.condition); + + 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 == TOK.error || + fs.increment && fs.increment.op == TOK.error || + fs._body && fs._body.isErrorStatement()) + return setError(); + result = fs; + } + + /******************* + * Determines the return type of makeTupleForeach. + */ + private static template MakeTupleForeachRet(bool isDecl) + { + static if(isDecl) + { + alias MakeTupleForeachRet = Dsymbols*; + } + else + { + alias MakeTupleForeachRet = void; + } + } + + /******************* + * 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. + */ + MakeTupleForeachRet!isDecl makeTupleForeach(bool isStatic, bool isDecl)(ForeachStatement fs, TupleForeachArgs!(isStatic, isDecl) args) + { + auto returnEarly() + { + static if (isDecl) + { + return null; + } + else + { + result = new ErrorStatement(); + return; + } + } + static if(isDecl) + { + static assert(isStatic); + auto dbody = args[0]; + } + static if(isStatic) + { + auto needExpansion = args[$-1]; + assert(sc); + } + + auto loc = fs.loc; + size_t dim = fs.parameters.dim; + static if(isStatic) bool skipCheck = needExpansion; + else enum skipCheck = false; + if (!skipCheck && (dim < 1 || dim > 2)) + { + fs.error("only one (value) or two (key,value) arguments for tuple `foreach`"); + setError(); + return returnEarly(); + } + + Type paramtype = (*fs.parameters)[dim - 1].type; + if (paramtype) + { + paramtype = paramtype.typeSemantic(loc, sc); + if (paramtype.ty == Terror) + { + setError(); + return returnEarly(); + } + } + + Type tab = fs.aggr.type.toBasetype(); + TypeTuple tuple = cast(TypeTuple)tab; + static if(!isDecl) + { + auto statements = new Statements(); + } + else + { + auto declarations = new Dsymbols(); + } + //printf("aggr: op = %d, %s\n", fs.aggr.op, fs.aggr.toChars()); + size_t n; + TupleExp te = null; + if (fs.aggr.op == TOK.tuple) // expression tuple + { + te = cast(TupleExp)fs.aggr; + n = te.exps.dim; + } + else if (fs.aggr.op == TOK.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]; + static if(!isDecl) + { + auto st = new Statements(); + } + else + { + auto st = new Dsymbols(); + } + + static if(isStatic) bool skip = needExpansion; + else enum skip = false; + 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()); + setError(); + return returnEarly(); + } + static 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()); + setError(); + 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); + setError(); + 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.manifest; + static if(isStatic) var.storage_class |= STC.local; + static if(!isDecl) + { + st.push(new ExpStatement(loc, var)); + } + else + { + st.push(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()); + setError(); + 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 == TOK.variable) + ds = (cast(VarExp)e).var; + else if (e.op == TOK.template_) + ds = (cast(TemplateExp)e).td; + else if (e.op == TOK.scope_) + ds = (cast(ScopeExp)e).sds; + else if (e.op == TOK.function_) + { + auto fe = cast(FuncExp)e; + ds = fe.td ? cast(Dsymbol)fe.td : fe.fd; + } + else if (e.op == TOK.overloadSet) + ds = (cast(OverExp)e).vars; + } + else if (storageClass & STC.alias_) + { + fs.error("`foreach` loop variable cannot be both `enum` and `alias`"); + setError(); + return false; + } + + if (ds) + { + var = new AliasDeclaration(loc, ident, ds); + if (storageClass & STC.ref_) + { + fs.error("symbol `%s` cannot be `ref`", ds.toChars()); + setError(); + return false; + } + if (paramtype) + { + fs.error("cannot specify element type for symbol `%s`", ds.toChars()); + setError(); + return false; + } + } + else if (e.op == TOK.type) + { + var = new AliasDeclaration(loc, ident, e.type); + if (paramtype) + { + fs.error("cannot specify element type for type `%s`", e.type.toChars()); + setError(); + return false; + } + } + else + { + e = resolveProperties(sc, e); + Initializer ie = new ExpInitializer(Loc.initial, e); + auto v = new VarDeclaration(loc, type, ident, ie, storageClass); + if (storageClass & STC.ref_) + v.storage_class |= STC.ref_ | STC.foreach_; + if (isStatic || storageClass&STC.manifest || e.isConst() || + e.op == TOK.string_ || + e.op == TOK.structLiteral || + e.op == TOK.arrayLiteral) + { + if (v.storage_class & STC.ref_) + { + static 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()); + } + } + setError(); + 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()); + setError(); + return false; + } + } + static if (isStatic) + { + var.storage_class |= STC.local; + } + static if (!isDecl) + { + st.push(new ExpStatement(loc, var)); + } + else + { + st.push(var); + } + return true; + } + static 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); + } + } + } + + static if (!isDecl) + { + if (fs._body) // https://issues.dlang.org/show_bug.cgi?id=17646 + st.push(fs._body.syntaxCopy()); + Statement res = new CompoundStatement(loc, st); + } + else + { + st.append(Dsymbol.arraySyntaxCopy(dbody)); + } + static if (!isStatic) + { + res = new ScopeStatement(loc, res, fs.endloc); + } + else static if (!isDecl) + { + auto fwd = new ForwardingStatement(loc, res); + res = fwd; + } + else + { + import dmd.attrib: ForwardingAttribDeclaration; + auto res = new ForwardingAttribDeclaration(st); + } + static if (!isDecl) + { + statements.push(res); + } + else + { + declarations.push(res); + } + } + + static 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); + } + else static if (!isDecl) + { + Statement res = new CompoundStatement(loc, statements); + } + else + { + auto res = declarations; + } + static if (!isDecl) + { + result = res; + } + else + { + return res; + } + } + + 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 == TOK.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.op != TOK.type && !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)) + { + const(char)* msg = ""; + if (fs.aggr.type && isAggregate(fs.aggr.type)) + { + msg = ", define `opApply()`, range primitives, or use `.tupleof`"; + } + fs.error("invalid `foreach` aggregate `%s`%s", oaggr.toChars(), msg); + 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 + { + makeTupleForeach!(false,false)(fs); + if (vinit) + result = new CompoundStatement(loc, new ExpStatement(loc, vinit), result); + result = result.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(); + } + //printf("tfld = %s\n", tfld.toChars()); + } + } + } + + 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 == TOK.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]; + auto var = new VarDeclaration(loc, p.type.mutableOf(), Identifier.generateId("__key"), null); + var.storage_class |= STC.temp | STC.foreach_; + if (var.storage_class & (STC.ref_ | STC.out_)) + var.storage_class |= STC.nodtor; + + fs.key = var; + if (p.storageClass & STC.ref_) + { + if (var.type.constConv(p.type) == MATCH.nomatch) + { + fs.error("key type mismatch, `%s` to `ref %s`", + var.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(var.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]; + auto var = new VarDeclaration(loc, p.type, p.ident, null); + var.storage_class |= STC.foreach_; + var.storage_class |= p.storageClass & (STC.scope_ | STC.IOR | STC.TYPECTOR); + if (var.isReference()) + var.storage_class |= STC.nodtor; + + fs.value = var; + if (var.storage_class & STC.ref_) + { + if (fs.aggr.checkModifiable(sc2, ModifyFlags.noError) == Modifiable.initialization) + var.storage_class |= STC.ctorinit; + + 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 = cast(bool) ((*fs.parameters)[dim - 1].storageClass & STC.ref_); + VarDeclaration tmp; + if (fs.aggr.op == TOK.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 == TOK.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(TOK.minusMinus, loc, new VarExp(loc, fs.key)); + } + else + { + // key < tmp.length + cond = new CmpExp(TOK.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 == TOK.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 == TOK.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 == TOK.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 == TOK.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) + */ + __gshared const(char)** 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], 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, 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_; + 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_; + id = p.ident; // argument copy is not need. + if ((p.storageClass & STC.ref_) != stc) + { + if (!stc) + { + fs.error("`foreach`: cannot make `%s` `ref`", p.ident.toChars()); + return null; + } + goto LcopyArg; + } + } + 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; + 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 == TOK.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 == TOK.error || fs.upr.op == TOK.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(TOK.minusMinus, loc, new VarExp(loc, fs.key)); + if (fs.prm.type.isscalar()) + { + // key-- > tmp + cond = new CmpExp(TOK.greaterThan, loc, cond, new VarExp(loc, tmp)); + } + else + { + // key-- != tmp + cond = new EqualExp(TOK.notEqual, loc, cond, new VarExp(loc, tmp)); + } + } + else + { + if (fs.prm.type.isscalar()) + { + // key < tmp + cond = new CmpExp(TOK.lessThan, loc, new VarExp(loc, fs.key), new VarExp(loc, tmp)); + } + else + { + // key != tmp + cond = new EqualExp(TOK.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(TOK.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); + + 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 == TOK.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 == TOK.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 == TOK.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(); + } + + if (e.isBool(true)) + inlining = PINLINE.always; + else if (e.isBool(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.op != TOK.error) + { + // 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.op != TOK.error && 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.op != TOK.error) + { + 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 == TOK.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())) + 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 (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 (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; + + 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 == TOK.cast_) + e = (cast(CastExp)e).e1; + + /* This is where variables are allowed as case expressions. + */ + if (e.op == TOK.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.op != TOK.int64 && cs.exp.op != TOK.error) + { + cs.error("`case` must be a `string` or an integral constant, not `%s`", cs.exp.toChars()); + errors = true; + } + + L1: + 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 == TOK.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 == TOK.error || crs.last.op == TOK.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 256 cases in case range", 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 == TOK.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 == TOK.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); + // 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 == TOK.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 == TOK.call) + rs.exp = valueNoDtor(rs.exp); + + if (e0) + e0 = e0.optimize(WANTvalue); + + /* Void-return function can have void typed expression + * on return statement. + */ + if (tbret && tbret.ty == Tvoid || rs.exp.type.ty == Tvoid) + { + if (rs.exp.type.ty != Tvoid) + { + 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 = 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.op != TOK.error) + { + 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 + { + // infer return type + if (fd.inferRetType) + { + if (tf.next && tf.next.ty != Tvoid) + { + 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; + } + else + tf.next = Type.tvoid; + + tret = tf.next; + tbret = tret.toBasetype(); + } + + if (inferRef) // deduce 'auto ref' + tf.isref = false; + + if (tbret.ty != Tvoid) // if non-void return + { + if (tbret.ty != Terror) + 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 == TOK.declaration || e0.op == TOK.comma) + { + rs.exp = Expression.combine(e0, rs.exp); + } + else + { + result = new CompoundStatement(rs.loc, new ExpStatement(rs.loc, e0), 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 == TOK.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 == TOK.error) + return setError(); + if (ws.exp.op == TOK.scope_) + { + sym = new WithScopeSymbol(ws); + sym.parent = sc.scopesym; + sym.endlinnum = ws.endloc.linnum; + } + else if (ws.exp.op == TOK.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); + assert(tcs._body); + + /* 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(); + + 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())) + { + // 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 (!global.params.useExceptions) + { + ts.error("Cannot use `throw` statements with -betterC"); + return setError(); + } + + if (!ClassDeclaration.throwable) + { + ts.error("Cannot use `throw` statements because `object.Throwable` was not declared"); + return setError(); + } + + FuncDeclaration fd = sc.parent.isFuncDeclaration(); + fd.hasReturnExp |= 2; + + if (ts.exp.op == TOK.new_) + { + NewExp ne = cast(NewExp)ts.exp; + ne.thrownew = true; + } + + ts.exp = ts.exp.expressionSemantic(sc); + ts.exp = resolveProperties(sc, ts.exp); + ts.exp = checkGC(sc, ts.exp); + if (ts.exp.op == TOK.error) + return setError(); + + checkThrowEscape(sc, ts.exp, false); + + ClassDeclaration cd = ts.exp.type.toBasetype().isClassHandle(); + if (!cd || ((cd != ClassDeclaration.throwable) && !ClassDeclaration.throwable.isBaseOf(cd, null))) + { + ts.error("can only throw class objects derived from `Throwable`, not type `%s`", ts.exp.type.toChars()); + return setError(); + } + + result = ts; + } + + 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 (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); + // use setImpure/setGC when the deprecation cycle is over + PURE purity; + if (!(cas.stc & STC.pure_) && (purity = sc.func.isPureBypassingInference()) != PURE.impure && purity != PURE.fwdref) + cas.deprecation("`asm` statement is assumed to be impure - mark it with `pure` if it is not"); + if (!(cas.stc & STC.nogc) && sc.func.isNogcBypassingInference()) + cas.deprecation("`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 == TOK.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; +} + + +/******************* + * Determines additional argument types for makeTupleForeach. + */ +static template TupleForeachArgs(bool isStatic, bool isDecl) +{ + alias Seq(T...)=T; + static if(isStatic) alias T = Seq!(bool); + else alias T = Seq!(); + static if(!isDecl) alias TupleForeachArgs = T; + else alias TupleForeachArgs = Seq!(Dsymbols*,T); +} + +/******************* + * Determines the return type of makeTupleForeach. + */ +static template TupleForeachRet(bool isStatic, bool isDecl) +{ + alias Seq(T...)=T; + static if(!isDecl) alias TupleForeachRet = Statement; + else alias TupleForeachRet = Dsymbols*; +} + + +/******************* + * See StatementSemanticVisitor.makeTupleForeach. This is a simple + * wrapper that returns the generated statements/declarations. + */ +TupleForeachRet!(isStatic, isDecl) makeTupleForeach(bool isStatic, bool isDecl)(Scope* sc, ForeachStatement fs, TupleForeachArgs!(isStatic, isDecl) args) +{ + scope v = new StatementSemanticVisitor(sc); + static if(!isDecl) + { + v.makeTupleForeach!(isStatic, isDecl)(fs, args); + return v.result; + } + else + { + return v.makeTupleForeach!(isStatic, isDecl)(fs, args); + } +} + +/********************************* + * 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.op != TOK.declaration) + return null; + + Dsymbol d = (cast(DeclarationExp)es.exp).declaration; + auto tm = d.isTemplateMixin(); + if (!tm) + return null; + + Expression e = es.exp.expressionSemantic(sc); + if (e.op == TOK.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()) + { + auto s = makeTupleForeach!(true, false)(sc, sfs.sfe.aggrfe, sfs.sfe.needExpansion); + 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 (== Dsymbols) to Statements. + */ +private Statement toStatement(Dsymbol s) +{ + extern (C++) final class ToStmt : Visitor + { + alias visit = Visitor.visit; + public: + Statement result; + + Statement visitMembers(Loc loc, Dsymbols* a) + { + if (!a) + return null; + + auto statements = new Statements(); + foreach (s; *a) + { + statements.push(toStatement(s)); + } + return new CompoundStatement(loc, statements); + } + + override void visit(Dsymbol s) + { + .error(Loc.initial, "Internal Compiler Error: cannot mixin %s `%s`\n", s.kind(), s.toChars()); + result = new ErrorStatement(); + } + + override void visit(TemplateMixin tm) + { + auto a = new Statements(); + foreach (m; *tm.members) + { + Statement s = toStatement(m); + if (s) + a.push(s); + } + result = new CompoundStatement(tm.loc, a); + } + + /* An actual declaration symbol will be converted to DeclarationExp + * with ExpStatement. + */ + Statement declStmt(Dsymbol s) + { + auto de = new DeclarationExp(s.loc, s); + de.type = Type.tvoid; // avoid repeated semantic + return new ExpStatement(s.loc, de); + } + + override void visit(VarDeclaration d) + { + result = declStmt(d); + } + + override void visit(AggregateDeclaration d) + { + result = declStmt(d); + } + + override void visit(FuncDeclaration d) + { + result = declStmt(d); + } + + override void visit(EnumDeclaration d) + { + result = declStmt(d); + } + + override void visit(AliasDeclaration d) + { + result = declStmt(d); + } + + override void visit(TemplateDeclaration d) + { + result = declStmt(d); + } + + /* 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. + */ + override void visit(StorageClassDeclaration d) + { + result = visitMembers(d.loc, d.decl); + } + + override void visit(DeprecatedDeclaration d) + { + result = visitMembers(d.loc, d.decl); + } + + override void visit(LinkDeclaration d) + { + result = visitMembers(d.loc, d.decl); + } + + override void visit(VisibilityDeclaration d) + { + result = visitMembers(d.loc, d.decl); + } + + override void visit(AlignDeclaration d) + { + result = visitMembers(d.loc, d.decl); + } + + override void visit(UserAttributeDeclaration d) + { + result = visitMembers(d.loc, d.decl); + } + + override void visit(ForwardingAttribDeclaration d) + { + result = visitMembers(d.loc, d.decl); + } + + override void visit(StaticAssert s) + { + } + + override void visit(Import s) + { + } + + override void visit(PragmaDeclaration d) + { + } + + override void visit(ConditionalDeclaration d) + { + result = visitMembers(d.loc, d.include(null)); + } + + override void visit(StaticForeachDeclaration d) + { + assert(d.sfe && !!d.sfe.aggrfe ^ !!d.sfe.rangefe); + result = visitMembers(d.loc, d.include(null)); + } + + override void visit(CompileDeclaration d) + { + result = visitMembers(d.loc, d.include(null)); + } + } + + if (!s) + return null; + + scope ToStmt v = new ToStmt(); + s.accept(v); + return v.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); +} |