| /** |
| * Does semantic analysis for statements. |
| * |
| * Specification: $(LINK2 https://dlang.org/spec/statement.html, Statements) |
| * |
| * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved |
| * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) |
| * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) |
| * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/statementsem.d, _statementsem.d) |
| * Documentation: https://dlang.org/phobos/dmd_statementsem.html |
| * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/statementsem.d |
| */ |
| |
| module dmd.statementsem; |
| |
| import core.stdc.stdio; |
| |
| import dmd.aggregate; |
| import dmd.aliasthis; |
| import dmd.arrayop; |
| import dmd.arraytypes; |
| import dmd.astcodegen; |
| import dmd.astenums; |
| import dmd.ast_node; |
| import dmd.attrib; |
| import dmd.blockexit; |
| import dmd.clone; |
| import dmd.cond; |
| import dmd.ctorflow; |
| import dmd.dcast; |
| import dmd.dclass; |
| import dmd.declaration; |
| import dmd.denum; |
| import dmd.dimport; |
| import dmd.dinterpret; |
| import dmd.dmodule; |
| import dmd.dscope; |
| import dmd.dsymbol; |
| import dmd.dsymbolsem; |
| import dmd.dtemplate; |
| import dmd.errors; |
| import dmd.escape; |
| import dmd.expression; |
| import dmd.expressionsem; |
| import dmd.func; |
| import dmd.globals; |
| import dmd.gluelayer; |
| import dmd.id; |
| import dmd.identifier; |
| import dmd.importc; |
| import dmd.init; |
| import dmd.intrange; |
| import dmd.mtype; |
| import dmd.mustuse; |
| import dmd.nogc; |
| import dmd.opover; |
| import dmd.parse; |
| import dmd.printast; |
| import dmd.common.outbuffer; |
| import dmd.root.string; |
| import dmd.semantic2; |
| import dmd.sideeffect; |
| import dmd.statement; |
| import dmd.staticassert; |
| import dmd.target; |
| import dmd.tokens; |
| import dmd.typesem; |
| import dmd.visitor; |
| import dmd.compiler; |
| |
| version (DMDLIB) |
| { |
| version = CallbackAPI; |
| } |
| |
| /***************************************** |
| * CTFE requires FuncDeclaration::labtab for the interpretation. |
| * So fixing the label name inside in/out contracts is necessary |
| * for the uniqueness in labtab. |
| * Params: |
| * sc = context |
| * ident = statement label name to be adjusted |
| * Returns: |
| * adjusted label name |
| */ |
| private Identifier fixupLabelName(Scope* sc, Identifier ident) |
| { |
| uint flags = (sc.flags & SCOPE.contract); |
| const id = ident.toString(); |
| if (flags && flags != SCOPE.invariant_ && |
| !(id.length >= 2 && id[0] == '_' && id[1] == '_')) // does not start with "__" |
| { |
| OutBuffer buf; |
| buf.writestring(flags == SCOPE.require ? "__in_" : "__out_"); |
| buf.writestring(ident.toString()); |
| |
| ident = Identifier.idPool(buf[]); |
| } |
| return ident; |
| } |
| |
| /******************************************* |
| * Check to see if statement is the innermost labeled statement. |
| * Params: |
| * sc = context |
| * statement = Statement to check |
| * Returns: |
| * if `true`, then the `LabelStatement`, otherwise `null` |
| */ |
| private LabelStatement checkLabeledLoop(Scope* sc, Statement statement) |
| { |
| if (sc.slabel && sc.slabel.statement == statement) |
| { |
| return sc.slabel; |
| } |
| return null; |
| } |
| |
| /*********************************************************** |
| * Check an assignment is used as a condition. |
| * Intended to be use before the `semantic` call on `e`. |
| * Params: |
| * e = condition expression which is not yet run semantic analysis. |
| * Returns: |
| * `e` or ErrorExp. |
| */ |
| private Expression checkAssignmentAsCondition(Expression e, Scope* sc) |
| { |
| if (sc.flags & SCOPE.Cfile) |
| return e; |
| auto ec = lastComma(e); |
| if (ec.op == EXP.assign) |
| { |
| ec.error("assignment cannot be used as a condition, perhaps `==` was meant?"); |
| return ErrorExp.get(); |
| } |
| return e; |
| } |
| |
| // Performs semantic analysis in Statement AST nodes |
| extern(C++) Statement statementSemantic(Statement s, Scope* sc) |
| { |
| version (CallbackAPI) |
| Compiler.onStatementSemanticStart(s, sc); |
| |
| scope v = new StatementSemanticVisitor(sc); |
| s.accept(v); |
| |
| version (CallbackAPI) |
| Compiler.onStatementSemanticDone(s, sc); |
| |
| return v.result; |
| } |
| |
| package (dmd) extern (C++) final class StatementSemanticVisitor : Visitor |
| { |
| alias visit = Visitor.visit; |
| |
| Statement result; |
| Scope* sc; |
| |
| this(Scope* sc) |
| { |
| this.sc = sc; |
| } |
| |
| private void setError() |
| { |
| result = new ErrorStatement(); |
| } |
| |
| override void visit(Statement s) |
| { |
| result = s; |
| } |
| |
| override void visit(ErrorStatement s) |
| { |
| result = s; |
| } |
| |
| override void visit(PeelStatement s) |
| { |
| /* "peel" off this wrapper, and don't run semantic() |
| * on the result. |
| */ |
| result = s.s; |
| } |
| |
| override void visit(ExpStatement s) |
| { |
| /* https://dlang.org/spec/statement.html#expression-statement |
| */ |
| |
| if (!s.exp) |
| { |
| result = s; |
| return; |
| } |
| //printf("ExpStatement::semantic() %s\n", exp.toChars()); |
| |
| // Allow CommaExp in ExpStatement because return isn't used |
| CommaExp.allow(s.exp); |
| |
| s.exp = s.exp.expressionSemantic(sc); |
| s.exp = resolveProperties(sc, s.exp); |
| s.exp = s.exp.addDtorHook(sc); |
| if (checkNonAssignmentArrayOp(s.exp)) |
| s.exp = ErrorExp.get(); |
| if (auto f = isFuncAddress(s.exp)) |
| { |
| if (f.checkForwardRef(s.exp.loc)) |
| s.exp = ErrorExp.get(); |
| } |
| if (checkMustUse(s.exp, sc)) |
| s.exp = ErrorExp.get(); |
| if (!(sc.flags & SCOPE.Cfile) && discardValue(s.exp)) |
| s.exp = ErrorExp.get(); |
| |
| s.exp = s.exp.optimize(WANTvalue); |
| s.exp = checkGC(sc, s.exp); |
| if (s.exp.op == EXP.error) |
| return setError(); |
| result = s; |
| } |
| |
| override void visit(CompileStatement cs) |
| { |
| /* https://dlang.org/spec/statement.html#mixin-statement |
| */ |
| |
| //printf("CompileStatement::semantic() %s\n", exp.toChars()); |
| Statements* a = cs.flatten(sc); |
| if (!a) |
| return; |
| Statement s = new CompoundStatement(cs.loc, a); |
| result = s.statementSemantic(sc); |
| } |
| |
| override void visit(CompoundStatement cs) |
| { |
| //printf("CompoundStatement::semantic(this = %p, sc = %p)\n", cs, sc); |
| version (none) |
| { |
| foreach (i, s; cs.statements) |
| { |
| if (s) |
| printf("[%d]: %s", i, s.toChars()); |
| } |
| } |
| |
| for (size_t i = 0; i < cs.statements.dim;) |
| { |
| Statement s = (*cs.statements)[i]; |
| if (!s) |
| { |
| ++i; |
| continue; |
| } |
| |
| Statements* flt = s.flatten(sc); |
| if (flt) |
| { |
| cs.statements.remove(i); |
| cs.statements.insert(i, flt); |
| continue; |
| } |
| s = s.statementSemantic(sc); |
| (*cs.statements)[i] = s; |
| if (!s) |
| { |
| /* Remove NULL statements from the list. |
| */ |
| cs.statements.remove(i); |
| continue; |
| } |
| if (s.isErrorStatement()) |
| { |
| result = s; // propagate error up the AST |
| ++i; |
| continue; // look for errors in rest of statements |
| } |
| Statement sentry; |
| Statement sexception; |
| Statement sfinally; |
| |
| (*cs.statements)[i] = s.scopeCode(sc, sentry, sexception, sfinally); |
| if (sentry) |
| { |
| sentry = sentry.statementSemantic(sc); |
| cs.statements.insert(i, sentry); |
| i++; |
| } |
| if (sexception) |
| sexception = sexception.statementSemantic(sc); |
| if (sexception) |
| { |
| /* Returns: true if statements[] are empty statements |
| */ |
| static bool isEmpty(const Statement[] statements) |
| { |
| foreach (s; statements) |
| { |
| if (const cs = s.isCompoundStatement()) |
| { |
| if (!isEmpty((*cs.statements)[])) |
| return false; |
| } |
| else |
| return false; |
| } |
| return true; |
| } |
| |
| if (!sfinally && isEmpty((*cs.statements)[i + 1 .. cs.statements.dim])) |
| { |
| } |
| else |
| { |
| /* Rewrite: |
| * s; s1; s2; |
| * As: |
| * s; |
| * try { s1; s2; } |
| * catch (Throwable __o) |
| * { sexception; throw __o; } |
| */ |
| auto a = new Statements(); |
| a.pushSlice((*cs.statements)[i + 1 .. cs.statements.length]); |
| cs.statements.setDim(i + 1); |
| |
| Statement _body = new CompoundStatement(Loc.initial, a); |
| _body = new ScopeStatement(Loc.initial, _body, Loc.initial); |
| |
| Identifier id = Identifier.generateId("__o"); |
| |
| Statement handler = new PeelStatement(sexception); |
| if (sexception.blockExit(sc.func, false) & BE.fallthru) |
| { |
| auto ts = new ThrowStatement(Loc.initial, new IdentifierExp(Loc.initial, id)); |
| ts.internalThrow = true; |
| handler = new CompoundStatement(Loc.initial, handler, ts); |
| } |
| |
| auto catches = new Catches(); |
| auto ctch = new Catch(Loc.initial, getThrowable(), id, handler); |
| ctch.internalCatch = true; |
| catches.push(ctch); |
| |
| Statement st = new TryCatchStatement(Loc.initial, _body, catches); |
| if (sfinally) |
| st = new TryFinallyStatement(Loc.initial, st, sfinally); |
| st = st.statementSemantic(sc); |
| |
| cs.statements.push(st); |
| break; |
| } |
| } |
| else if (sfinally) |
| { |
| if (0 && i + 1 == cs.statements.dim) |
| { |
| cs.statements.push(sfinally); |
| } |
| else |
| { |
| /* Rewrite: |
| * s; s1; s2; |
| * As: |
| * s; try { s1; s2; } finally { sfinally; } |
| */ |
| auto a = new Statements(); |
| a.pushSlice((*cs.statements)[i + 1 .. cs.statements.length]); |
| cs.statements.setDim(i + 1); |
| |
| auto _body = new CompoundStatement(Loc.initial, a); |
| Statement stf = new TryFinallyStatement(Loc.initial, _body, sfinally); |
| stf = stf.statementSemantic(sc); |
| cs.statements.push(stf); |
| break; |
| } |
| } |
| i++; |
| } |
| |
| /* Flatten them in place |
| */ |
| void flatten(Statements* statements) |
| { |
| for (size_t i = 0; i < statements.length;) |
| { |
| Statement s = (*statements)[i]; |
| if (s) |
| { |
| if (auto flt = s.flatten(sc)) |
| { |
| statements.remove(i); |
| statements.insert(i, flt); |
| continue; |
| } |
| } |
| ++i; |
| } |
| } |
| |
| /* https://issues.dlang.org/show_bug.cgi?id=11653 |
| * 'semantic' may return another CompoundStatement |
| * (eg. CaseRangeStatement), so flatten it here. |
| */ |
| flatten(cs.statements); |
| |
| foreach (s; *cs.statements) |
| { |
| if (!s) |
| continue; |
| |
| if (auto se = s.isErrorStatement()) |
| { |
| result = se; |
| return; |
| } |
| } |
| |
| if (cs.statements.length == 1) |
| { |
| result = (*cs.statements)[0]; |
| return; |
| } |
| result = cs; |
| } |
| |
| override void visit(UnrolledLoopStatement uls) |
| { |
| //printf("UnrolledLoopStatement::semantic(this = %p, sc = %p)\n", uls, sc); |
| Scope* scd = sc.push(); |
| scd.sbreak = uls; |
| scd.scontinue = uls; |
| |
| Statement serror = null; |
| foreach (i, ref s; *uls.statements) |
| { |
| if (s) |
| { |
| //printf("[%d]: %s\n", i, s.toChars()); |
| s = s.statementSemantic(scd); |
| if (s && !serror) |
| serror = s.isErrorStatement(); |
| } |
| } |
| |
| scd.pop(); |
| result = serror ? serror : uls; |
| } |
| |
| override void visit(ScopeStatement ss) |
| { |
| //printf("ScopeStatement::semantic(sc = %p)\n", sc); |
| if (!ss.statement) |
| { |
| result = ss; |
| return; |
| } |
| |
| ScopeDsymbol sym = new ScopeDsymbol(); |
| sym.parent = sc.scopesym; |
| sym.endlinnum = ss.endloc.linnum; |
| sc = sc.push(sym); |
| |
| Statements* a = ss.statement.flatten(sc); |
| if (a) |
| { |
| ss.statement = new CompoundStatement(ss.loc, a); |
| } |
| |
| ss.statement = ss.statement.statementSemantic(sc); |
| if (ss.statement) |
| { |
| if (ss.statement.isErrorStatement()) |
| { |
| sc.pop(); |
| result = ss.statement; |
| return; |
| } |
| |
| Statement sentry; |
| Statement sexception; |
| Statement sfinally; |
| ss.statement = ss.statement.scopeCode(sc, sentry, sexception, sfinally); |
| assert(!sentry); |
| assert(!sexception); |
| if (sfinally) |
| { |
| //printf("adding sfinally\n"); |
| sfinally = sfinally.statementSemantic(sc); |
| ss.statement = new CompoundStatement(ss.loc, ss.statement, sfinally); |
| } |
| } |
| sc.pop(); |
| result = ss; |
| } |
| |
| override void visit(ForwardingStatement ss) |
| { |
| assert(ss.sym); |
| for (Scope* csc = sc; !ss.sym.parent; csc = csc.enclosing) |
| { |
| assert(csc); |
| ss.sym.parent = csc.scopesym; |
| } |
| sc = sc.push(ss.sym); |
| sc.sbreak = ss; |
| sc.scontinue = ss; |
| ss.statement = ss.statement.statementSemantic(sc); |
| sc = sc.pop(); |
| result = ss.statement; |
| } |
| |
| override void visit(WhileStatement ws) |
| { |
| /* Rewrite as a for(;condition;) loop |
| * https://dlang.org/spec/statement.html#while-statement |
| */ |
| Expression cond = ws.condition; |
| Statement _body = ws._body; |
| if (ws.param) |
| { |
| /** |
| * If the while loop is of form `while(auto a = exp) { loop_body }`, |
| * rewrite to: |
| * |
| * while(true) |
| * if (auto a = exp) |
| * { loop_body } |
| * else |
| * { break; } |
| */ |
| _body = new IfStatement(ws.loc, ws.param, ws.condition, ws._body, new BreakStatement(ws.loc, null), ws.endloc); |
| cond = IntegerExp.createBool(true); |
| } |
| Statement s = new ForStatement(ws.loc, null, cond, null, _body, ws.endloc); |
| s = s.statementSemantic(sc); |
| result = s; |
| } |
| |
| override void visit(DoStatement ds) |
| { |
| /* https://dlang.org/spec/statement.html#do-statement |
| */ |
| const inLoopSave = sc.inLoop; |
| sc.inLoop = true; |
| if (ds._body) |
| ds._body = ds._body.semanticScope(sc, ds, ds, null); |
| sc.inLoop = inLoopSave; |
| |
| if (ds.condition.op == EXP.dotIdentifier) |
| (cast(DotIdExp)ds.condition).noderef = true; |
| |
| // check in syntax level |
| ds.condition = checkAssignmentAsCondition(ds.condition, sc); |
| |
| ds.condition = ds.condition.expressionSemantic(sc); |
| ds.condition = resolveProperties(sc, ds.condition); |
| if (checkNonAssignmentArrayOp(ds.condition)) |
| ds.condition = ErrorExp.get(); |
| ds.condition = ds.condition.optimize(WANTvalue); |
| ds.condition = checkGC(sc, ds.condition); |
| |
| ds.condition = ds.condition.toBoolean(sc); |
| |
| if (ds.condition.op == EXP.error) |
| return setError(); |
| if (ds._body && ds._body.isErrorStatement()) |
| { |
| result = ds._body; |
| return; |
| } |
| |
| result = ds; |
| } |
| |
| override void visit(ForStatement fs) |
| { |
| /* https://dlang.org/spec/statement.html#for-statement |
| */ |
| //printf("ForStatement::semantic %s\n", fs.toChars()); |
| |
| if (fs._init) |
| { |
| /* Rewrite: |
| * for (auto v1 = i1, v2 = i2; condition; increment) { ... } |
| * to: |
| * { auto v1 = i1, v2 = i2; for (; condition; increment) { ... } } |
| * then lowered to: |
| * auto v1 = i1; |
| * try { |
| * auto v2 = i2; |
| * try { |
| * for (; condition; increment) { ... } |
| * } finally { v2.~this(); } |
| * } finally { v1.~this(); } |
| */ |
| auto ainit = new Statements(); |
| ainit.push(fs._init); |
| fs._init = null; |
| ainit.push(fs); |
| Statement s = new CompoundStatement(fs.loc, ainit); |
| s = new ScopeStatement(fs.loc, s, fs.endloc); |
| s = s.statementSemantic(sc); |
| if (!s.isErrorStatement()) |
| { |
| if (LabelStatement ls = checkLabeledLoop(sc, fs)) |
| ls.gotoTarget = fs; |
| fs.relatedLabeled = s; |
| } |
| result = s; |
| return; |
| } |
| assert(fs._init is null); |
| |
| auto sym = new ScopeDsymbol(); |
| sym.parent = sc.scopesym; |
| sym.endlinnum = fs.endloc.linnum; |
| sc = sc.push(sym); |
| sc.inLoop = true; |
| |
| if (fs.condition) |
| { |
| if (fs.condition.op == EXP.dotIdentifier) |
| (cast(DotIdExp)fs.condition).noderef = true; |
| |
| // check in syntax level |
| fs.condition = checkAssignmentAsCondition(fs.condition, sc); |
| |
| fs.condition = fs.condition.expressionSemantic(sc); |
| fs.condition = resolveProperties(sc, fs.condition); |
| if (checkNonAssignmentArrayOp(fs.condition)) |
| fs.condition = ErrorExp.get(); |
| fs.condition = fs.condition.optimize(WANTvalue); |
| fs.condition = checkGC(sc, fs.condition); |
| |
| fs.condition = fs.condition.toBoolean(sc); |
| } |
| if (fs.increment) |
| { |
| CommaExp.allow(fs.increment); |
| fs.increment = fs.increment.expressionSemantic(sc); |
| fs.increment = resolveProperties(sc, fs.increment); |
| if (checkNonAssignmentArrayOp(fs.increment)) |
| fs.increment = ErrorExp.get(); |
| fs.increment = fs.increment.optimize(WANTvalue); |
| fs.increment = checkGC(sc, fs.increment); |
| } |
| |
| sc.sbreak = fs; |
| sc.scontinue = fs; |
| if (fs._body) |
| fs._body = fs._body.semanticNoScope(sc); |
| |
| sc.pop(); |
| |
| if (fs.condition && fs.condition.op == EXP.error || |
| fs.increment && fs.increment.op == EXP.error || |
| fs._body && fs._body.isErrorStatement()) |
| return setError(); |
| result = fs; |
| } |
| |
| override void visit(ForeachStatement fs) |
| { |
| /* https://dlang.org/spec/statement.html#foreach-statement |
| */ |
| |
| //printf("ForeachStatement::semantic() %p\n", fs); |
| |
| /****** |
| * Issue error if any of the ForeachTypes were not supplied and could not be inferred. |
| * Returns: |
| * true if error issued |
| */ |
| static bool checkForArgTypes(ForeachStatement fs) |
| { |
| bool result = false; |
| foreach (p; *fs.parameters) |
| { |
| if (!p.type) |
| { |
| fs.error("cannot infer type for `foreach` variable `%s`, perhaps set it explicitly", p.ident.toChars()); |
| p.type = Type.terror; |
| result = true; |
| } |
| } |
| return result; |
| } |
| |
| const loc = fs.loc; |
| const dim = fs.parameters.dim; |
| |
| fs.func = sc.func; |
| if (fs.func.fes) |
| fs.func = fs.func.fes.func; |
| |
| VarDeclaration vinit = null; |
| fs.aggr = fs.aggr.expressionSemantic(sc); |
| fs.aggr = resolveProperties(sc, fs.aggr); |
| fs.aggr = fs.aggr.optimize(WANTvalue); |
| if (fs.aggr.op == EXP.error) |
| return setError(); |
| Expression oaggr = fs.aggr; // remember original for error messages |
| if (fs.aggr.type && fs.aggr.type.toBasetype().ty == Tstruct && |
| (cast(TypeStruct)(fs.aggr.type.toBasetype())).sym.dtor && |
| !fs.aggr.isTypeExp() && !fs.aggr.isLvalue()) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=14653 |
| // Extend the life of rvalue aggregate till the end of foreach. |
| vinit = copyToTemp(STC.rvalue, "__aggr", fs.aggr); |
| vinit.endlinnum = fs.endloc.linnum; |
| vinit.dsymbolSemantic(sc); |
| fs.aggr = new VarExp(fs.aggr.loc, vinit); |
| } |
| |
| /* If aggregate is a vector type, add the .array to make it a static array |
| */ |
| if (fs.aggr.type) |
| if (auto tv = fs.aggr.type.toBasetype().isTypeVector()) |
| { |
| auto vae = new VectorArrayExp(fs.aggr.loc, fs.aggr); |
| vae.type = tv.basetype; |
| fs.aggr = vae; |
| } |
| |
| Dsymbol sapply = null; // the inferred opApply() or front() function |
| if (!inferForeachAggregate(sc, fs.op == TOK.foreach_, fs.aggr, sapply)) |
| { |
| assert(oaggr.type); |
| |
| fs.error("invalid `%s` aggregate `%s` of type `%s`", |
| Token.toChars(fs.op), oaggr.toChars(), oaggr.type.toPrettyChars()); |
| |
| if (auto ad = isAggregate(fs.aggr.type)) |
| { |
| if (fs.op == TOK.foreach_reverse_) |
| { |
| fs.loc.errorSupplemental("`foreach_reverse` works with bidirectional ranges"~ |
| " (implementing `back` and `popBack`), aggregates implementing" ~ |
| " `opApplyReverse`, or the result of an aggregate's `.tupleof` property"); |
| fs.loc.errorSupplemental("https://dlang.org/phobos/std_range_primitives.html#isBidirectionalRange"); |
| } |
| else |
| { |
| fs.loc.errorSupplemental("`foreach` works with input ranges"~ |
| " (implementing `front` and `popFront`), aggregates implementing" ~ |
| " `opApply`, or the result of an aggregate's `.tupleof` property"); |
| fs.loc.errorSupplemental("https://dlang.org/phobos/std_range_primitives.html#isInputRange"); |
| } |
| } |
| |
| return setError(); |
| } |
| |
| Dsymbol sapplyOld = sapply; // 'sapply' will be NULL if and after 'inferApplyArgTypes' errors |
| |
| /* Check for inference errors |
| */ |
| if (!inferApplyArgTypes(fs, sc, sapply)) |
| { |
| /** |
| Try and extract the parameter count of the opApply callback function, e.g.: |
| int opApply(int delegate(int, float)) => 2 args |
| */ |
| bool foundMismatch = false; |
| size_t foreachParamCount = 0; |
| if (sapplyOld) |
| { |
| if (FuncDeclaration fd = sapplyOld.isFuncDeclaration()) |
| { |
| auto fparameters = fd.getParameterList(); |
| |
| if (fparameters.length == 1) |
| { |
| // first param should be the callback function |
| Parameter fparam = fparameters[0]; |
| if ((fparam.type.ty == Tpointer || |
| fparam.type.ty == Tdelegate) && |
| fparam.type.nextOf().ty == Tfunction) |
| { |
| TypeFunction tf = cast(TypeFunction)fparam.type.nextOf(); |
| foreachParamCount = tf.parameterList.length; |
| foundMismatch = true; |
| } |
| } |
| } |
| } |
| |
| //printf("dim = %d, parameters.dim = %d\n", dim, parameters.dim); |
| if (foundMismatch && dim != foreachParamCount) |
| { |
| const(char)* plural = foreachParamCount > 1 ? "s" : ""; |
| fs.error("cannot infer argument types, expected %llu argument%s, not %llu", |
| cast(ulong) foreachParamCount, plural, cast(ulong) dim); |
| } |
| else |
| fs.error("cannot uniquely infer `foreach` argument types"); |
| |
| return setError(); |
| } |
| |
| Type tab = fs.aggr.type.toBasetype(); |
| |
| if (tab.ty == Ttuple) // don't generate new scope for tuple loops |
| { |
| Statement s = makeTupleForeach(sc, false, false, fs, null, false).statement; |
| if (vinit) |
| s = new CompoundStatement(loc, new ExpStatement(loc, vinit), s); |
| result = s.statementSemantic(sc); |
| return; |
| } |
| |
| auto sym = new ScopeDsymbol(); |
| sym.parent = sc.scopesym; |
| sym.endlinnum = fs.endloc.linnum; |
| auto sc2 = sc.push(sym); |
| sc2.inLoop = true; |
| |
| foreach (Parameter p; *fs.parameters) |
| { |
| if (p.storageClass & STC.manifest) |
| { |
| fs.error("cannot declare `enum` loop variables for non-unrolled foreach"); |
| } |
| if (p.storageClass & STC.alias_) |
| { |
| fs.error("cannot declare `alias` loop variables for non-unrolled foreach"); |
| } |
| } |
| |
| void retError() |
| { |
| sc2.pop(); |
| result = new ErrorStatement(); |
| } |
| |
| void rangeError() |
| { |
| fs.error("cannot infer argument types"); |
| return retError(); |
| } |
| |
| void retStmt(Statement s) |
| { |
| if (!s) |
| return retError(); |
| s = s.statementSemantic(sc2); |
| sc2.pop(); |
| result = s; |
| } |
| |
| TypeAArray taa = null; |
| Type tn = null; |
| Type tnv = null; |
| Statement apply() |
| { |
| if (checkForArgTypes(fs)) |
| return null; |
| |
| TypeFunction tfld = null; |
| if (sapply) |
| { |
| FuncDeclaration fdapply = sapply.isFuncDeclaration(); |
| if (fdapply) |
| { |
| assert(fdapply.type && fdapply.type.ty == Tfunction); |
| tfld = cast(TypeFunction)fdapply.type.typeSemantic(loc, sc2); |
| goto Lget; |
| } |
| else if (tab.ty == Tdelegate) |
| { |
| tfld = cast(TypeFunction)tab.nextOf(); |
| Lget: |
| //printf("tfld = %s\n", tfld.toChars()); |
| if (tfld.parameterList.parameters.dim == 1) |
| { |
| Parameter p = tfld.parameterList[0]; |
| if (p.type && p.type.ty == Tdelegate) |
| { |
| auto t = p.type.typeSemantic(loc, sc2); |
| assert(t.ty == Tdelegate); |
| tfld = cast(TypeFunction)t.nextOf(); |
| } |
| } |
| } |
| } |
| |
| FuncExp flde = foreachBodyToFunction(sc2, fs, tfld); |
| if (!flde) |
| return null; |
| |
| // Resolve any forward referenced goto's |
| foreach (ScopeStatement ss; *fs.gotos) |
| { |
| GotoStatement gs = ss.statement.isGotoStatement(); |
| if (!gs.label.statement) |
| { |
| // 'Promote' it to this scope, and replace with a return |
| fs.cases.push(gs); |
| ss.statement = new ReturnStatement(Loc.initial, new IntegerExp(fs.cases.dim + 1)); |
| } |
| } |
| |
| Expression e = null; |
| Expression ec; |
| if (vinit) |
| { |
| e = new DeclarationExp(loc, vinit); |
| e = e.expressionSemantic(sc2); |
| if (e.op == EXP.error) |
| return null; |
| } |
| |
| if (taa) |
| ec = applyAssocArray(fs, flde, taa); |
| else if (tab.ty == Tarray || tab.ty == Tsarray) |
| ec = applyArray(fs, flde, sc2, tn, tnv, tab.ty); |
| else if (tab.ty == Tdelegate) |
| ec = applyDelegate(fs, flde, sc2, tab); |
| else |
| ec = applyOpApply(fs, tab, sapply, sc2, flde); |
| if (!ec) |
| return null; |
| e = Expression.combine(e, ec); |
| return loopReturn(e, fs.cases, loc); |
| } |
| switch (tab.ty) |
| { |
| case Tarray: |
| case Tsarray: |
| { |
| if (checkForArgTypes(fs)) |
| return retError(); |
| |
| if (dim < 1 || dim > 2) |
| { |
| fs.error("only one or two arguments for array `foreach`"); |
| return retError(); |
| } |
| |
| // Finish semantic on all foreach parameter types. |
| foreach (i; 0 .. dim) |
| { |
| Parameter p = (*fs.parameters)[i]; |
| p.type = p.type.typeSemantic(loc, sc2); |
| p.type = p.type.addStorageClass(p.storageClass); |
| } |
| |
| tn = tab.nextOf().toBasetype(); |
| |
| if (dim == 2) |
| { |
| Type tindex = (*fs.parameters)[0].type; |
| if (!tindex.isintegral()) |
| { |
| fs.error("foreach: key cannot be of non-integral type `%s`", tindex.toChars()); |
| return retError(); |
| } |
| /* What cases to deprecate implicit conversions for: |
| * 1. foreach aggregate is a dynamic array |
| * 2. foreach body is lowered to _aApply (see special case below). |
| */ |
| Type tv = (*fs.parameters)[1].type.toBasetype(); |
| if ((tab.ty == Tarray || |
| (tn.ty != tv.ty && tn.ty.isSomeChar && tv.ty.isSomeChar)) && |
| !Type.tsize_t.implicitConvTo(tindex)) |
| { |
| fs.deprecation("foreach: loop index implicitly converted from `size_t` to `%s`", |
| tindex.toChars()); |
| } |
| } |
| |
| /* Look for special case of parsing char types out of char type |
| * array. |
| */ |
| if (tn.ty.isSomeChar) |
| { |
| int i = (dim == 1) ? 0 : 1; // index of value |
| Parameter p = (*fs.parameters)[i]; |
| tnv = p.type.toBasetype(); |
| if (tnv.ty != tn.ty && tnv.ty.isSomeChar) |
| { |
| if (p.storageClass & STC.ref_) |
| { |
| fs.error("`foreach`: value of UTF conversion cannot be `ref`"); |
| return retError(); |
| } |
| if (dim == 2) |
| { |
| p = (*fs.parameters)[0]; |
| if (p.storageClass & STC.ref_) |
| { |
| fs.error("`foreach`: key cannot be `ref`"); |
| return retError(); |
| } |
| } |
| return retStmt(apply()); |
| } |
| } |
| |
| // Declare the key |
| if (dim == 2) |
| { |
| Parameter p = (*fs.parameters)[0]; |
| fs.key = new VarDeclaration(loc, p.type.mutableOf(), Identifier.generateId("__key"), null); |
| fs.key.storage_class |= STC.temp | STC.foreach_; |
| if (fs.key.isReference()) |
| fs.key.storage_class |= STC.nodtor; |
| |
| if (p.storageClass & STC.ref_) |
| { |
| if (fs.key.type.constConv(p.type) == MATCH.nomatch) |
| { |
| fs.error("key type mismatch, `%s` to `ref %s`", |
| fs.key.type.toChars(), p.type.toChars()); |
| return retError(); |
| } |
| } |
| if (tab.ty == Tsarray) |
| { |
| TypeSArray ta = cast(TypeSArray)tab; |
| IntRange dimrange = getIntRange(ta.dim); |
| // https://issues.dlang.org/show_bug.cgi?id=12504 |
| dimrange.imax = SignExtendedNumber(dimrange.imax.value-1); |
| if (!IntRange.fromType(fs.key.type).contains(dimrange)) |
| { |
| fs.error("index type `%s` cannot cover index range 0..%llu", |
| p.type.toChars(), ta.dim.toInteger()); |
| return retError(); |
| } |
| fs.key.range = new IntRange(SignExtendedNumber(0), dimrange.imax); |
| } |
| } |
| // Now declare the value |
| { |
| Parameter p = (*fs.parameters)[dim - 1]; |
| fs.value = new VarDeclaration(loc, p.type, p.ident, null); |
| fs.value.storage_class |= STC.foreach_; |
| fs.value.storage_class |= p.storageClass & (STC.scope_ | STC.IOR | STC.TYPECTOR); |
| if (fs.value.isReference()) |
| { |
| fs.value.storage_class |= STC.nodtor; |
| |
| if (fs.aggr.checkModifiable(sc2, ModifyFlags.noError) == Modifiable.initialization) |
| fs.value.setInCtorOnly = true; |
| |
| Type t = tab.nextOf(); |
| if (t.constConv(p.type) == MATCH.nomatch) |
| { |
| fs.error("argument type mismatch, `%s` to `ref %s`", |
| t.toChars(), p.type.toChars()); |
| return retError(); |
| } |
| } |
| } |
| |
| /* Convert to a ForStatement |
| * foreach (key, value; a) body => |
| * for (T[] tmp = a[], size_t key; key < tmp.length; ++key) |
| * { T value = tmp[k]; body } |
| * |
| * foreach_reverse (key, value; a) body => |
| * for (T[] tmp = a[], size_t key = tmp.length; key--; ) |
| * { T value = tmp[k]; body } |
| */ |
| auto id = Identifier.generateId("__r"); |
| auto ie = new ExpInitializer(loc, new SliceExp(loc, fs.aggr, null, null)); |
| const valueIsRef = (*fs.parameters)[$ - 1].isReference(); |
| VarDeclaration tmp; |
| if (fs.aggr.op == EXP.arrayLiteral && !valueIsRef) |
| { |
| auto ale = cast(ArrayLiteralExp)fs.aggr; |
| size_t edim = ale.elements ? ale.elements.dim : 0; |
| auto telem = (*fs.parameters)[dim - 1].type; |
| |
| // https://issues.dlang.org/show_bug.cgi?id=12936 |
| // if telem has been specified explicitly, |
| // converting array literal elements to telem might make it @nogc. |
| fs.aggr = fs.aggr.implicitCastTo(sc, telem.sarrayOf(edim)); |
| if (fs.aggr.op == EXP.error) |
| return retError(); |
| |
| // for (T[edim] tmp = a, ...) |
| tmp = new VarDeclaration(loc, fs.aggr.type, id, ie); |
| } |
| else |
| { |
| tmp = new VarDeclaration(loc, tab.nextOf().arrayOf(), id, ie); |
| if (!valueIsRef) |
| tmp.storage_class |= STC.scope_; |
| } |
| tmp.storage_class |= STC.temp; |
| |
| Expression tmp_length = new DotIdExp(loc, new VarExp(loc, tmp), Id.length); |
| |
| if (!fs.key) |
| { |
| Identifier idkey = Identifier.generateId("__key"); |
| fs.key = new VarDeclaration(loc, Type.tsize_t, idkey, null); |
| fs.key.storage_class |= STC.temp; |
| } |
| else if (fs.key.type.ty != Type.tsize_t.ty) |
| { |
| tmp_length = new CastExp(loc, tmp_length, fs.key.type); |
| } |
| if (fs.op == TOK.foreach_reverse_) |
| fs.key._init = new ExpInitializer(loc, tmp_length); |
| else |
| fs.key._init = new ExpInitializer(loc, new IntegerExp(loc, 0, fs.key.type)); |
| |
| auto cs = new Statements(); |
| if (vinit) |
| cs.push(new ExpStatement(loc, vinit)); |
| cs.push(new ExpStatement(loc, tmp)); |
| cs.push(new ExpStatement(loc, fs.key)); |
| Statement forinit = new CompoundDeclarationStatement(loc, cs); |
| |
| Expression cond; |
| if (fs.op == TOK.foreach_reverse_) |
| { |
| // key-- |
| cond = new PostExp(EXP.minusMinus, loc, new VarExp(loc, fs.key)); |
| } |
| else |
| { |
| // key < tmp.length |
| cond = new CmpExp(EXP.lessThan, loc, new VarExp(loc, fs.key), tmp_length); |
| } |
| |
| Expression increment = null; |
| if (fs.op == TOK.foreach_) |
| { |
| // key += 1 |
| increment = new AddAssignExp(loc, new VarExp(loc, fs.key), new IntegerExp(loc, 1, fs.key.type)); |
| } |
| |
| // T value = tmp[key]; |
| IndexExp indexExp = new IndexExp(loc, new VarExp(loc, tmp), new VarExp(loc, fs.key)); |
| indexExp.indexIsInBounds = true; // disabling bounds checking in foreach statements. |
| fs.value._init = new ExpInitializer(loc, indexExp); |
| Statement ds = new ExpStatement(loc, fs.value); |
| |
| if (dim == 2) |
| { |
| Parameter p = (*fs.parameters)[0]; |
| if ((p.storageClass & STC.ref_) && p.type.equals(fs.key.type)) |
| { |
| fs.key.range = null; |
| auto v = new AliasDeclaration(loc, p.ident, fs.key); |
| fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body); |
| } |
| else |
| { |
| auto ei = new ExpInitializer(loc, new IdentifierExp(loc, fs.key.ident)); |
| auto v = new VarDeclaration(loc, p.type, p.ident, ei); |
| v.storage_class |= STC.foreach_ | (p.storageClass & STC.ref_); |
| fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body); |
| if (fs.key.range && !p.type.isMutable()) |
| { |
| /* Limit the range of the key to the specified range |
| */ |
| v.range = new IntRange(fs.key.range.imin, fs.key.range.imax - SignExtendedNumber(1)); |
| } |
| } |
| } |
| fs._body = new CompoundStatement(loc, ds, fs._body); |
| |
| Statement s = new ForStatement(loc, forinit, cond, increment, fs._body, fs.endloc); |
| if (auto ls = checkLabeledLoop(sc, fs)) // https://issues.dlang.org/show_bug.cgi?id=15450 |
| // don't use sc2 |
| ls.gotoTarget = s; |
| return retStmt(s); |
| } |
| case Taarray: |
| if (fs.op == TOK.foreach_reverse_) |
| fs.warning("cannot use `foreach_reverse` with an associative array"); |
| if (checkForArgTypes(fs)) |
| return retError(); |
| |
| taa = cast(TypeAArray)tab; |
| if (dim < 1 || dim > 2) |
| { |
| fs.error("only one or two arguments for associative array `foreach`"); |
| return retError(); |
| } |
| return retStmt(apply()); |
| |
| case Tclass: |
| case Tstruct: |
| /* Prefer using opApply, if it exists |
| */ |
| if (sapply) |
| return retStmt(apply()); |
| { |
| /* Look for range iteration, i.e. the properties |
| * .empty, .popFront, .popBack, .front and .back |
| * foreach (e; aggr) { ... } |
| * translates to: |
| * for (auto __r = aggr[]; !__r.empty; __r.popFront()) { |
| * auto e = __r.front; |
| * ... |
| * } |
| */ |
| auto ad = (tab.ty == Tclass) ? |
| cast(AggregateDeclaration)(cast(TypeClass)tab).sym : |
| cast(AggregateDeclaration)(cast(TypeStruct)tab).sym; |
| Identifier idfront; |
| Identifier idpopFront; |
| if (fs.op == TOK.foreach_) |
| { |
| idfront = Id.Ffront; |
| idpopFront = Id.FpopFront; |
| } |
| else |
| { |
| idfront = Id.Fback; |
| idpopFront = Id.FpopBack; |
| } |
| auto sfront = ad.search(Loc.initial, idfront); |
| if (!sfront) |
| return retStmt(apply()); |
| |
| /* Generate a temporary __r and initialize it with the aggregate. |
| */ |
| VarDeclaration r; |
| Statement _init; |
| if (vinit && fs.aggr.op == EXP.variable && (cast(VarExp)fs.aggr).var == vinit) |
| { |
| r = vinit; |
| _init = new ExpStatement(loc, vinit); |
| } |
| else |
| { |
| r = copyToTemp(0, "__r", fs.aggr); |
| r.dsymbolSemantic(sc); |
| _init = new ExpStatement(loc, r); |
| if (vinit) |
| _init = new CompoundStatement(loc, new ExpStatement(loc, vinit), _init); |
| } |
| |
| // !__r.empty |
| Expression e = new VarExp(loc, r); |
| e = new DotIdExp(loc, e, Id.Fempty); |
| Expression condition = new NotExp(loc, e); |
| |
| // __r.idpopFront() |
| e = new VarExp(loc, r); |
| Expression increment = new CallExp(loc, new DotIdExp(loc, e, idpopFront)); |
| |
| /* Declaration statement for e: |
| * auto e = __r.idfront; |
| */ |
| e = new VarExp(loc, r); |
| Expression einit = new DotIdExp(loc, e, idfront); |
| Statement makeargs, forbody; |
| bool ignoreRef = false; // If a range returns a non-ref front we ignore ref on foreach |
| |
| Type tfront; |
| if (auto fd = sfront.isFuncDeclaration()) |
| { |
| if (!fd.functionSemantic()) |
| return rangeError(); |
| tfront = fd.type; |
| } |
| else if (auto td = sfront.isTemplateDeclaration()) |
| { |
| Expressions a; |
| if (auto f = resolveFuncCall(loc, sc, td, null, tab, &a, FuncResolveFlag.quiet)) |
| tfront = f.type; |
| } |
| else if (auto d = sfront.toAlias().isDeclaration()) |
| { |
| tfront = d.type; |
| } |
| if (!tfront || tfront.ty == Terror) |
| return rangeError(); |
| if (tfront.toBasetype().ty == Tfunction) |
| { |
| auto ftt = cast(TypeFunction)tfront.toBasetype(); |
| tfront = tfront.toBasetype().nextOf(); |
| if (!ftt.isref) |
| { |
| // .front() does not return a ref. We ignore ref on foreach arg. |
| // see https://issues.dlang.org/show_bug.cgi?id=11934 |
| if (tfront.needsDestruction()) ignoreRef = true; |
| } |
| } |
| if (tfront.ty == Tvoid) |
| { |
| fs.error("`%s.front` is `void` and has no value", oaggr.toChars()); |
| return retError(); |
| } |
| |
| if (dim == 1) |
| { |
| auto p = (*fs.parameters)[0]; |
| auto ve = new VarDeclaration(loc, p.type, p.ident, new ExpInitializer(loc, einit)); |
| ve.storage_class |= STC.foreach_; |
| ve.storage_class |= p.storageClass & (STC.scope_ | STC.IOR | STC.TYPECTOR); |
| |
| if (ignoreRef) |
| ve.storage_class &= ~STC.ref_; |
| |
| makeargs = new ExpStatement(loc, ve); |
| } |
| else |
| { |
| auto vd = copyToTemp(STC.ref_, "__front", einit); |
| vd.dsymbolSemantic(sc); |
| makeargs = new ExpStatement(loc, vd); |
| |
| // Resolve inout qualifier of front type |
| tfront = tfront.substWildTo(tab.mod); |
| |
| Expression ve = new VarExp(loc, vd); |
| ve.type = tfront; |
| |
| auto exps = new Expressions(); |
| exps.push(ve); |
| int pos = 0; |
| while (exps.dim < dim) |
| { |
| pos = expandAliasThisTuples(exps, pos); |
| if (pos == -1) |
| break; |
| } |
| if (exps.dim != dim) |
| { |
| const(char)* plural = exps.dim > 1 ? "s" : ""; |
| fs.error("cannot infer argument types, expected %llu argument%s, not %llu", |
| cast(ulong) exps.dim, plural, cast(ulong) dim); |
| return retError(); |
| } |
| |
| foreach (i; 0 .. dim) |
| { |
| auto p = (*fs.parameters)[i]; |
| auto exp = (*exps)[i]; |
| version (none) |
| { |
| printf("[%d] p = %s %s, exp = %s %s\n", i, |
| p.type ? p.type.toChars() : "?", p.ident.toChars(), |
| exp.type.toChars(), exp.toChars()); |
| } |
| if (!p.type) |
| p.type = exp.type; |
| |
| auto sc = p.storageClass; |
| if (ignoreRef) sc &= ~STC.ref_; |
| p.type = p.type.addStorageClass(sc).typeSemantic(loc, sc2); |
| if (!exp.implicitConvTo(p.type)) |
| return rangeError(); |
| |
| auto var = new VarDeclaration(loc, p.type, p.ident, new ExpInitializer(loc, exp)); |
| var.storage_class |= STC.ctfe | STC.ref_ | STC.foreach_; |
| makeargs = new CompoundStatement(loc, makeargs, new ExpStatement(loc, var)); |
| } |
| } |
| |
| forbody = new CompoundStatement(loc, makeargs, fs._body); |
| |
| Statement s = new ForStatement(loc, _init, condition, increment, forbody, fs.endloc); |
| if (auto ls = checkLabeledLoop(sc, fs)) |
| ls.gotoTarget = s; |
| |
| version (none) |
| { |
| printf("init: %s\n", _init.toChars()); |
| printf("condition: %s\n", condition.toChars()); |
| printf("increment: %s\n", increment.toChars()); |
| printf("body: %s\n", forbody.toChars()); |
| } |
| return retStmt(s); |
| } |
| case Tdelegate: |
| if (fs.op == TOK.foreach_reverse_) |
| fs.deprecation("cannot use `foreach_reverse` with a delegate"); |
| return retStmt(apply()); |
| case Terror: |
| return retError(); |
| default: |
| fs.error("`foreach`: `%s` is not an aggregate type", fs.aggr.type.toChars()); |
| return retError(); |
| } |
| } |
| |
| private static extern(D) Expression applyOpApply(ForeachStatement fs, Type tab, Dsymbol sapply, |
| Scope* sc2, Expression flde) |
| { |
| version (none) |
| { |
| if (global.params.useDIP1000 == FeatureState.enabled) |
| { |
| message(loc, "To enforce `@safe`, the compiler allocates a closure unless `opApply()` uses `scope`"); |
| } |
| (cast(FuncExp)flde).fd.tookAddressOf = 1; |
| } |
| else |
| { |
| if (global.params.useDIP1000 == FeatureState.enabled) |
| ++(cast(FuncExp)flde).fd.tookAddressOf; // allocate a closure unless the opApply() uses 'scope' |
| } |
| assert(tab.ty == Tstruct || tab.ty == Tclass); |
| assert(sapply); |
| /* Call: |
| * aggr.apply(flde) |
| */ |
| Expression ec; |
| ec = new DotIdExp(fs.loc, fs.aggr, sapply.ident); |
| ec = new CallExp(fs.loc, ec, flde); |
| ec = ec.expressionSemantic(sc2); |
| if (ec.op == EXP.error) |
| return null; |
| if (ec.type != Type.tint32) |
| { |
| fs.error("`opApply()` function for `%s` must return an `int`", tab.toChars()); |
| return null; |
| } |
| return ec; |
| } |
| |
| private static extern(D) Expression applyDelegate(ForeachStatement fs, Expression flde, |
| Scope* sc2, Type tab) |
| { |
| Expression ec; |
| /* Call: |
| * aggr(flde) |
| */ |
| if (fs.aggr.op == EXP.delegate_ && (cast(DelegateExp)fs.aggr).func.isNested() && |
| !(cast(DelegateExp)fs.aggr).func.needThis()) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=3560 |
| fs.aggr = (cast(DelegateExp)fs.aggr).e1; |
| } |
| ec = new CallExp(fs.loc, fs.aggr, flde); |
| ec = ec.expressionSemantic(sc2); |
| if (ec.op == EXP.error) |
| return null; |
| if (ec.type != Type.tint32) |
| { |
| fs.error("`opApply()` function for `%s` must return an `int`", tab.toChars()); |
| return null; |
| } |
| return ec; |
| } |
| |
| private static extern(D) Expression applyArray(ForeachStatement fs, Expression flde, |
| Scope* sc2, Type tn, Type tnv, TY tabty) |
| { |
| Expression ec; |
| const dim = fs.parameters.dim; |
| const loc = fs.loc; |
| /* Call: |
| * _aApply(aggr, flde) |
| */ |
| static immutable fntab = |
| [ |
| "cc", "cw", "cd", |
| "wc", "cc", "wd", |
| "dc", "dw", "dd" |
| ]; |
| |
| const(size_t) BUFFER_LEN = 7 + 1 + 2 + dim.sizeof * 3 + 1; |
| char[BUFFER_LEN] fdname; |
| int flag; |
| |
| switch (tn.ty) |
| { |
| case Tchar: flag = 0; break; |
| case Twchar: flag = 3; break; |
| case Tdchar: flag = 6; break; |
| default: |
| assert(0); |
| } |
| switch (tnv.ty) |
| { |
| case Tchar: flag += 0; break; |
| case Twchar: flag += 1; break; |
| case Tdchar: flag += 2; break; |
| default: |
| assert(0); |
| } |
| const(char)* r = (fs.op == TOK.foreach_reverse_) ? "R" : ""; |
| int j = sprintf(fdname.ptr, "_aApply%s%.*s%llu", r, 2, fntab[flag].ptr, cast(ulong)dim); |
| assert(j < BUFFER_LEN); |
| |
| FuncDeclaration fdapply; |
| TypeDelegate dgty; |
| auto params = new Parameters(); |
| params.push(new Parameter(STC.in_, tn.arrayOf(), null, null, null)); |
| auto dgparams = new Parameters(); |
| dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null)); |
| if (dim == 2) |
| dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null)); |
| dgty = new TypeDelegate(new TypeFunction(ParameterList(dgparams), Type.tint32, LINK.d)); |
| params.push(new Parameter(0, dgty, null, null, null)); |
| fdapply = FuncDeclaration.genCfunc(params, Type.tint32, fdname.ptr); |
| |
| if (tabty == Tsarray) |
| fs.aggr = fs.aggr.castTo(sc2, tn.arrayOf()); |
| // paint delegate argument to the type runtime expects |
| Expression fexp = flde; |
| if (!dgty.equals(flde.type)) |
| { |
| fexp = new CastExp(loc, flde, flde.type); |
| fexp.type = dgty; |
| } |
| ec = new VarExp(Loc.initial, fdapply, false); |
| ec = new CallExp(loc, ec, fs.aggr, fexp); |
| ec.type = Type.tint32; // don't run semantic() on ec |
| return ec; |
| } |
| |
| private static extern(D) Expression applyAssocArray(ForeachStatement fs, Expression flde, TypeAArray taa) |
| { |
| Expression ec; |
| const dim = fs.parameters.dim; |
| // Check types |
| Parameter p = (*fs.parameters)[0]; |
| bool isRef = (p.storageClass & STC.ref_) != 0; |
| Type ta = p.type; |
| if (dim == 2) |
| { |
| Type ti = (isRef ? taa.index.addMod(MODFlags.const_) : taa.index); |
| if (isRef ? !ti.constConv(ta) : !ti.implicitConvTo(ta)) |
| { |
| fs.error("`foreach`: index must be type `%s`, not `%s`", |
| ti.toChars(), ta.toChars()); |
| return null; |
| } |
| p = (*fs.parameters)[1]; |
| isRef = (p.storageClass & STC.ref_) != 0; |
| ta = p.type; |
| } |
| Type taav = taa.nextOf(); |
| if (isRef ? !taav.constConv(ta) : !taav.implicitConvTo(ta)) |
| { |
| fs.error("`foreach`: value must be type `%s`, not `%s`", |
| taav.toChars(), ta.toChars()); |
| return null; |
| } |
| |
| /* Call: |
| * extern(C) int _aaApply(void*, in size_t, int delegate(void*)) |
| * _aaApply(aggr, keysize, flde) |
| * |
| * extern(C) int _aaApply2(void*, in size_t, int delegate(void*, void*)) |
| * _aaApply2(aggr, keysize, flde) |
| */ |
| __gshared FuncDeclaration* fdapply = [null, null]; |
| __gshared TypeDelegate* fldeTy = [null, null]; |
| ubyte i = (dim == 2 ? 1 : 0); |
| if (!fdapply[i]) |
| { |
| auto params = new Parameters(); |
| params.push(new Parameter(0, Type.tvoid.pointerTo(), null, null, null)); |
| params.push(new Parameter(STC.const_, Type.tsize_t, null, null, null)); |
| auto dgparams = new Parameters(); |
| dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null)); |
| if (dim == 2) |
| dgparams.push(new Parameter(0, Type.tvoidptr, null, null, null)); |
| fldeTy[i] = new TypeDelegate(new TypeFunction(ParameterList(dgparams), Type.tint32, LINK.d)); |
| params.push(new Parameter(0, fldeTy[i], null, null, null)); |
| fdapply[i] = FuncDeclaration.genCfunc(params, Type.tint32, i ? Id._aaApply2 : Id._aaApply); |
| } |
| |
| auto exps = new Expressions(); |
| exps.push(fs.aggr); |
| auto keysize = taa.index.size(); |
| if (keysize == SIZE_INVALID) |
| return null; |
| assert(keysize < keysize.max - target.ptrsize); |
| keysize = (keysize + (target.ptrsize - 1)) & ~(target.ptrsize - 1); |
| // paint delegate argument to the type runtime expects |
| Expression fexp = flde; |
| if (!fldeTy[i].equals(flde.type)) |
| { |
| fexp = new CastExp(fs.loc, flde, flde.type); |
| fexp.type = fldeTy[i]; |
| } |
| exps.push(new IntegerExp(Loc.initial, keysize, Type.tsize_t)); |
| exps.push(fexp); |
| ec = new VarExp(Loc.initial, fdapply[i], false); |
| ec = new CallExp(fs.loc, ec, exps); |
| ec.type = Type.tint32; // don't run semantic() on ec |
| return ec; |
| } |
| |
| private static extern(D) Statement loopReturn(Expression e, Statements* cases, const ref Loc loc) |
| { |
| if (!cases.dim) |
| { |
| // Easy case, a clean exit from the loop |
| e = new CastExp(loc, e, Type.tvoid); // https://issues.dlang.org/show_bug.cgi?id=13899 |
| return new ExpStatement(loc, e); |
| } |
| // Construct a switch statement around the return value |
| // of the apply function. |
| Statement s; |
| auto a = new Statements(); |
| |
| // default: break; takes care of cases 0 and 1 |
| s = new BreakStatement(Loc.initial, null); |
| s = new DefaultStatement(Loc.initial, s); |
| a.push(s); |
| |
| // cases 2... |
| foreach (i, c; *cases) |
| { |
| s = new CaseStatement(Loc.initial, new IntegerExp(i + 2), c); |
| a.push(s); |
| } |
| |
| s = new CompoundStatement(loc, a); |
| return new SwitchStatement(loc, e, s, false); |
| } |
| /************************************* |
| * Turn foreach body into the function literal: |
| * int delegate(ref T param) { body } |
| * Params: |
| * sc = context |
| * fs = ForeachStatement |
| * tfld = type of function literal to be created (type of opApply() function if any), can be null |
| * Returns: |
| * Function literal created, as an expression |
| * null if error. |
| */ |
| static FuncExp foreachBodyToFunction(Scope* sc, ForeachStatement fs, TypeFunction tfld) |
| { |
| auto params = new Parameters(); |
| foreach (i, p; *fs.parameters) |
| { |
| StorageClass stc = STC.ref_ | (p.storageClass & STC.scope_); |
| Identifier id; |
| |
| p.type = p.type.typeSemantic(fs.loc, sc); |
| p.type = p.type.addStorageClass(p.storageClass); |
| if (tfld) |
| { |
| Parameter prm = tfld.parameterList[i]; |
| //printf("\tprm = %s%s\n", (prm.storageClass&STC.ref_?"ref ":"").ptr, prm.ident.toChars()); |
| stc = (prm.storageClass & STC.ref_) | (p.storageClass & STC.scope_); |
| if ((p.storageClass & STC.ref_) != (prm.storageClass & STC.ref_)) |
| { |
| if (!(prm.storageClass & STC.ref_)) |
| { |
| fs.error("`foreach`: cannot make `%s` `ref`", p.ident.toChars()); |
| return null; |
| } |
| goto LcopyArg; |
| } |
| id = p.ident; // argument copy is not need. |
| } |
| else if (p.storageClass & STC.ref_) |
| { |
| // default delegate parameters are marked as ref, then |
| // argument copy is not need. |
| id = p.ident; |
| } |
| else |
| { |
| // Make a copy of the ref argument so it isn't |
| // a reference. |
| LcopyArg: |
| id = Identifier.generateId("__applyArg", cast(int)i); |
| |
| Initializer ie = new ExpInitializer(fs.loc, new IdentifierExp(fs.loc, id)); |
| auto v = new VarDeclaration(fs.loc, p.type, p.ident, ie); |
| v.storage_class |= STC.temp | (stc & STC.scope_); |
| Statement s = new ExpStatement(fs.loc, v); |
| fs._body = new CompoundStatement(fs.loc, s, fs._body); |
| } |
| params.push(new Parameter(stc, p.type, id, null, null)); |
| } |
| // https://issues.dlang.org/show_bug.cgi?id=13840 |
| // Throwable nested function inside nothrow function is acceptable. |
| StorageClass stc = mergeFuncAttrs(STC.safe | STC.pure_ | STC.nogc, fs.func); |
| auto tf = new TypeFunction(ParameterList(params), Type.tint32, LINK.d, stc); |
| fs.cases = new Statements(); |
| fs.gotos = new ScopeStatements(); |
| auto fld = new FuncLiteralDeclaration(fs.loc, fs.endloc, tf, TOK.delegate_, fs); |
| fld.fbody = fs._body; |
| Expression flde = new FuncExp(fs.loc, fld); |
| flde = flde.expressionSemantic(sc); |
| fld.tookAddressOf = 0; |
| if (flde.op == EXP.error) |
| return null; |
| return cast(FuncExp)flde; |
| } |
| |
| override void visit(ForeachRangeStatement fs) |
| { |
| /* https://dlang.org/spec/statement.html#foreach-range-statement |
| */ |
| |
| //printf("ForeachRangeStatement::semantic() %p\n", fs); |
| auto loc = fs.loc; |
| fs.lwr = fs.lwr.expressionSemantic(sc); |
| fs.lwr = resolveProperties(sc, fs.lwr); |
| fs.lwr = fs.lwr.optimize(WANTvalue); |
| if (!fs.lwr.type) |
| { |
| fs.error("invalid range lower bound `%s`", fs.lwr.toChars()); |
| return setError(); |
| } |
| |
| fs.upr = fs.upr.expressionSemantic(sc); |
| fs.upr = resolveProperties(sc, fs.upr); |
| fs.upr = fs.upr.optimize(WANTvalue); |
| if (!fs.upr.type) |
| { |
| fs.error("invalid range upper bound `%s`", fs.upr.toChars()); |
| return setError(); |
| } |
| |
| if (fs.prm.type) |
| { |
| fs.prm.type = fs.prm.type.typeSemantic(loc, sc); |
| fs.prm.type = fs.prm.type.addStorageClass(fs.prm.storageClass); |
| fs.lwr = fs.lwr.implicitCastTo(sc, fs.prm.type); |
| |
| if (fs.upr.implicitConvTo(fs.prm.type) || (fs.prm.storageClass & STC.ref_)) |
| { |
| fs.upr = fs.upr.implicitCastTo(sc, fs.prm.type); |
| } |
| else |
| { |
| // See if upr-1 fits in prm.type |
| Expression limit = new MinExp(loc, fs.upr, IntegerExp.literal!1); |
| limit = limit.expressionSemantic(sc); |
| limit = limit.optimize(WANTvalue); |
| if (!limit.implicitConvTo(fs.prm.type)) |
| { |
| fs.upr = fs.upr.implicitCastTo(sc, fs.prm.type); |
| } |
| } |
| } |
| else |
| { |
| /* Must infer types from lwr and upr |
| */ |
| Type tlwr = fs.lwr.type.toBasetype(); |
| if (tlwr.ty == Tstruct || tlwr.ty == Tclass) |
| { |
| /* Just picking the first really isn't good enough. |
| */ |
| fs.prm.type = fs.lwr.type; |
| } |
| else if (fs.lwr.type == fs.upr.type) |
| { |
| /* Same logic as CondExp ?lwr:upr |
| */ |
| fs.prm.type = fs.lwr.type; |
| } |
| else |
| { |
| scope AddExp ea = new AddExp(loc, fs.lwr, fs.upr); |
| if (typeCombine(ea, sc)) |
| return setError(); |
| fs.prm.type = ea.type; |
| fs.lwr = ea.e1; |
| fs.upr = ea.e2; |
| } |
| fs.prm.type = fs.prm.type.addStorageClass(fs.prm.storageClass); |
| } |
| if (fs.prm.type.ty == Terror || fs.lwr.op == EXP.error || fs.upr.op == EXP.error) |
| { |
| return setError(); |
| } |
| |
| /* Convert to a for loop: |
| * foreach (key; lwr .. upr) => |
| * for (auto key = lwr, auto tmp = upr; key < tmp; ++key) |
| * |
| * foreach_reverse (key; lwr .. upr) => |
| * for (auto tmp = lwr, auto key = upr; key-- > tmp;) |
| */ |
| auto ie = new ExpInitializer(loc, (fs.op == TOK.foreach_) ? fs.lwr : fs.upr); |
| fs.key = new VarDeclaration(loc, fs.upr.type.mutableOf(), Identifier.generateId("__key"), ie); |
| fs.key.storage_class |= STC.temp; |
| SignExtendedNumber lower = getIntRange(fs.lwr).imin; |
| SignExtendedNumber upper = getIntRange(fs.upr).imax; |
| if (lower <= upper) |
| { |
| fs.key.range = new IntRange(lower, upper); |
| } |
| |
| Identifier id = Identifier.generateId("__limit"); |
| ie = new ExpInitializer(loc, (fs.op == TOK.foreach_) ? fs.upr : fs.lwr); |
| auto tmp = new VarDeclaration(loc, fs.upr.type, id, ie); |
| tmp.storage_class |= STC.temp; |
| |
| auto cs = new Statements(); |
| // Keep order of evaluation as lwr, then upr |
| if (fs.op == TOK.foreach_) |
| { |
| cs.push(new ExpStatement(loc, fs.key)); |
| cs.push(new ExpStatement(loc, tmp)); |
| } |
| else |
| { |
| cs.push(new ExpStatement(loc, tmp)); |
| cs.push(new ExpStatement(loc, fs.key)); |
| } |
| Statement forinit = new CompoundDeclarationStatement(loc, cs); |
| |
| Expression cond; |
| if (fs.op == TOK.foreach_reverse_) |
| { |
| cond = new PostExp(EXP.minusMinus, loc, new VarExp(loc, fs.key)); |
| if (fs.prm.type.isscalar()) |
| { |
| // key-- > tmp |
| cond = new CmpExp(EXP.greaterThan, loc, cond, new VarExp(loc, tmp)); |
| } |
| else |
| { |
| // key-- != tmp |
| cond = new EqualExp(EXP.notEqual, loc, cond, new VarExp(loc, tmp)); |
| } |
| } |
| else |
| { |
| if (fs.prm.type.isscalar()) |
| { |
| // key < tmp |
| cond = new CmpExp(EXP.lessThan, loc, new VarExp(loc, fs.key), new VarExp(loc, tmp)); |
| } |
| else |
| { |
| // key != tmp |
| cond = new EqualExp(EXP.notEqual, loc, new VarExp(loc, fs.key), new VarExp(loc, tmp)); |
| } |
| } |
| |
| Expression increment = null; |
| if (fs.op == TOK.foreach_) |
| { |
| // key += 1 |
| //increment = new AddAssignExp(loc, new VarExp(loc, fs.key), IntegerExp.literal!1); |
| increment = new PreExp(EXP.prePlusPlus, loc, new VarExp(loc, fs.key)); |
| } |
| if ((fs.prm.storageClass & STC.ref_) && fs.prm.type.equals(fs.key.type)) |
| { |
| fs.key.range = null; |
| auto v = new AliasDeclaration(loc, fs.prm.ident, fs.key); |
| fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body); |
| } |
| else |
| { |
| ie = new ExpInitializer(loc, new CastExp(loc, new VarExp(loc, fs.key), fs.prm.type)); |
| auto v = new VarDeclaration(loc, fs.prm.type, fs.prm.ident, ie); |
| v.storage_class |= STC.temp | STC.foreach_ | (fs.prm.storageClass & STC.ref_); |
| fs._body = new CompoundStatement(loc, new ExpStatement(loc, v), fs._body); |
| if (fs.key.range && !fs.prm.type.isMutable()) |
| { |
| /* Limit the range of the key to the specified range |
| */ |
| v.range = new IntRange(fs.key.range.imin, fs.key.range.imax - SignExtendedNumber(1)); |
| } |
| } |
| if (fs.prm.storageClass & STC.ref_) |
| { |
| if (fs.key.type.constConv(fs.prm.type) == MATCH.nomatch) |
| { |
| fs.error("argument type mismatch, `%s` to `ref %s`", fs.key.type.toChars(), fs.prm.type.toChars()); |
| return setError(); |
| } |
| } |
| |
| auto s = new ForStatement(loc, forinit, cond, increment, fs._body, fs.endloc); |
| if (LabelStatement ls = checkLabeledLoop(sc, fs)) |
| ls.gotoTarget = s; |
| result = s.statementSemantic(sc); |
| } |
| |
| override void visit(IfStatement ifs) |
| { |
| /* https://dlang.org/spec/statement.html#IfStatement |
| */ |
| |
| // check in syntax level |
| ifs.condition = checkAssignmentAsCondition(ifs.condition, sc); |
| |
| auto sym = new ScopeDsymbol(); |
| sym.parent = sc.scopesym; |
| sym.endlinnum = ifs.endloc.linnum; |
| Scope* scd = sc.push(sym); |
| if (ifs.prm) |
| { |
| /* Declare prm, which we will set to be the |
| * result of condition. |
| */ |
| auto ei = new ExpInitializer(ifs.loc, ifs.condition); |
| ifs.match = new VarDeclaration(ifs.loc, ifs.prm.type, ifs.prm.ident, ei); |
| ifs.match.parent = scd.func; |
| ifs.match.storage_class |= ifs.prm.storageClass; |
| ifs.match.dsymbolSemantic(scd); |
| |
| auto de = new DeclarationExp(ifs.loc, ifs.match); |
| auto ve = new VarExp(ifs.loc, ifs.match); |
| ifs.condition = new CommaExp(ifs.loc, de, ve); |
| ifs.condition = ifs.condition.expressionSemantic(scd); |
| |
| if (ifs.match.edtor) |
| { |
| Statement sdtor = new DtorExpStatement(ifs.loc, ifs.match.edtor, ifs.match); |
| sdtor = new ScopeGuardStatement(ifs.loc, TOK.onScopeExit, sdtor); |
| ifs.ifbody = new CompoundStatement(ifs.loc, sdtor, ifs.ifbody); |
| ifs.match.storage_class |= STC.nodtor; |
| |
| // the destructor is always called |
| // whether the 'ifbody' is executed or not |
| Statement sdtor2 = new DtorExpStatement(ifs.loc, ifs.match.edtor, ifs.match); |
| if (ifs.elsebody) |
| ifs.elsebody = new CompoundStatement(ifs.loc, sdtor2, ifs.elsebody); |
| else |
| ifs.elsebody = sdtor2; |
| } |
| } |
| else |
| { |
| if (ifs.condition.op == EXP.dotIdentifier) |
| (cast(DotIdExp)ifs.condition).noderef = true; |
| |
| ifs.condition = ifs.condition.expressionSemantic(scd); |
| ifs.condition = resolveProperties(scd, ifs.condition); |
| ifs.condition = ifs.condition.addDtorHook(scd); |
| } |
| if (checkNonAssignmentArrayOp(ifs.condition)) |
| ifs.condition = ErrorExp.get(); |
| ifs.condition = checkGC(scd, ifs.condition); |
| |
| // Convert to boolean after declaring prm so this works: |
| // if (S prm = S()) {} |
| // where S is a struct that defines opCast!bool. |
| ifs.condition = ifs.condition.toBoolean(scd); |
| |
| // If we can short-circuit evaluate the if statement, don't do the |
| // semantic analysis of the skipped code. |
| // This feature allows a limited form of conditional compilation. |
| ifs.condition = ifs.condition.optimize(WANTvalue); |
| |
| // Save 'root' of two branches (then and else) at the point where it forks |
| CtorFlow ctorflow_root = scd.ctorflow.clone(); |
| |
| ifs.ifbody = ifs.ifbody.semanticNoScope(scd); |
| scd.pop(); |
| |
| CtorFlow ctorflow_then = sc.ctorflow; // move flow results |
| sc.ctorflow = ctorflow_root; // reset flow analysis back to root |
| if (ifs.elsebody) |
| ifs.elsebody = ifs.elsebody.semanticScope(sc, null, null, null); |
| |
| // Merge 'then' results into 'else' results |
| sc.merge(ifs.loc, ctorflow_then); |
| |
| ctorflow_then.freeFieldinit(); // free extra copy of the data |
| |
| if (ifs.condition.op == EXP.error || |
| (ifs.ifbody && ifs.ifbody.isErrorStatement()) || |
| (ifs.elsebody && ifs.elsebody.isErrorStatement())) |
| { |
| return setError(); |
| } |
| result = ifs; |
| } |
| |
| override void visit(ConditionalStatement cs) |
| { |
| //printf("ConditionalStatement::semantic()\n"); |
| |
| // If we can short-circuit evaluate the if statement, don't do the |
| // semantic analysis of the skipped code. |
| // This feature allows a limited form of conditional compilation. |
| if (cs.condition.include(sc)) |
| { |
| DebugCondition dc = cs.condition.isDebugCondition(); |
| if (dc) |
| { |
| sc = sc.push(); |
| sc.flags |= SCOPE.debug_; |
| cs.ifbody = cs.ifbody.statementSemantic(sc); |
| sc.pop(); |
| } |
| else |
| cs.ifbody = cs.ifbody.statementSemantic(sc); |
| result = cs.ifbody; |
| } |
| else |
| { |
| if (cs.elsebody) |
| cs.elsebody = cs.elsebody.statementSemantic(sc); |
| result = cs.elsebody; |
| } |
| } |
| |
| override void visit(PragmaStatement ps) |
| { |
| /* https://dlang.org/spec/statement.html#pragma-statement |
| */ |
| // Should be merged with PragmaDeclaration |
| |
| //printf("PragmaStatement::semantic() %s\n", ps.toChars()); |
| //printf("body = %p\n", ps._body); |
| if (ps.ident == Id.msg) |
| { |
| if (!pragmaMsgSemantic(ps.loc, sc, ps.args)) |
| return setError(); |
| } |
| 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 (!pragmaStartAddressSemantic(ps.loc, sc, ps.args)) |
| return setError(); |
| } |
| else if (ps.ident == Id.Pinline) |
| { |
| if (auto fd = sc.func) |
| { |
| fd.inlining = evalPragmaInline(ps.loc, sc, ps.args); |
| } |
| else |
| { |
| ps.error("`pragma(inline)` is not inside a function"); |
| return setError(); |
| } |
| } |
| else if (!global.params.ignoreUnsupportedPragmas) |
| { |
| ps.error("unrecognized `pragma(%s)`", ps.ident.toChars()); |
| return setError(); |
| } |
| |
| if (ps._body) |
| { |
| if (ps.ident == Id.msg || ps.ident == Id.startaddress) |
| { |
| ps.error("`pragma(%s)` is missing a terminating `;`", ps.ident.toChars()); |
| return setError(); |
| } |
| ps._body = ps._body.statementSemantic(sc); |
| } |
| result = ps._body; |
| } |
| |
| override void visit(StaticAssertStatement s) |
| { |
| s.sa.semantic2(sc); |
| if (s.sa.errors) |
| return setError(); |
| } |
| |
| override void visit(SwitchStatement ss) |
| { |
| /* https://dlang.org/spec/statement.html#switch-statement |
| */ |
| |
| //printf("SwitchStatement::semantic(%p)\n", ss); |
| ss.tryBody = sc.tryBody; |
| ss.tf = sc.tf; |
| if (ss.cases) |
| { |
| result = ss; // already run |
| return; |
| } |
| |
| bool conditionError = false; |
| ss.condition = ss.condition.expressionSemantic(sc); |
| ss.condition = resolveProperties(sc, ss.condition); |
| |
| Type att = null; |
| TypeEnum te = null; |
| while (!ss.condition.isErrorExp()) |
| { |
| // preserve enum type for final switches |
| if (ss.condition.type.ty == Tenum) |
| te = cast(TypeEnum)ss.condition.type; |
| if (ss.condition.type.isString()) |
| { |
| // If it's not an array, cast it to one |
| if (ss.condition.type.ty != Tarray) |
| { |
| ss.condition = ss.condition.implicitCastTo(sc, ss.condition.type.nextOf().arrayOf()); |
| } |
| ss.condition.type = ss.condition.type.constOf(); |
| break; |
| } |
| ss.condition = integralPromotions(ss.condition, sc); |
| if (!ss.condition.isErrorExp() && ss.condition.type.isintegral()) |
| break; |
| |
| auto ad = isAggregate(ss.condition.type); |
| if (ad && ad.aliasthis && !isRecursiveAliasThis(att, ss.condition.type)) |
| { |
| if (auto e = resolveAliasThis(sc, ss.condition, true)) |
| { |
| ss.condition = e; |
| continue; |
| } |
| } |
| |
| if (!ss.condition.isErrorExp()) |
| { |
| ss.error("`%s` must be of integral or string type, it is a `%s`", |
| ss.condition.toChars(), ss.condition.type.toChars()); |
| conditionError = true; |
| break; |
| } |
| } |
| if (checkNonAssignmentArrayOp(ss.condition)) |
| ss.condition = ErrorExp.get(); |
| ss.condition = ss.condition.optimize(WANTvalue); |
| ss.condition = checkGC(sc, ss.condition); |
| if (ss.condition.op == EXP.error) |
| conditionError = true; |
| |
| bool needswitcherror = false; |
| |
| ss.lastVar = sc.lastVar; |
| |
| sc = sc.push(); |
| sc.sbreak = ss; |
| sc.sw = ss; |
| |
| ss.cases = new CaseStatements(); |
| const inLoopSave = sc.inLoop; |
| sc.inLoop = true; // BUG: should use Scope::mergeCallSuper() for each case instead |
| ss._body = ss._body.statementSemantic(sc); |
| sc.inLoop = inLoopSave; |
| |
| if (conditionError || (ss._body && ss._body.isErrorStatement())) |
| { |
| sc.pop(); |
| return setError(); |
| } |
| |
| // Resolve any goto case's with exp |
| Lgotocase: |
| foreach (gcs; ss.gotoCases) |
| { |
| if (!gcs.exp) |
| { |
| gcs.error("no `case` statement following `goto case;`"); |
| sc.pop(); |
| return setError(); |
| } |
| |
| for (Scope* scx = sc; scx; scx = scx.enclosing) |
| { |
| if (!scx.sw) |
| continue; |
| foreach (cs; *scx.sw.cases) |
| { |
| if (cs.exp.equals(gcs.exp)) |
| { |
| gcs.cs = cs; |
| continue Lgotocase; |
| } |
| } |
| } |
| gcs.error("`case %s` not found", gcs.exp.toChars()); |
| sc.pop(); |
| return setError(); |
| } |
| |
| if (ss.isFinal) |
| { |
| Type t = ss.condition.type; |
| Dsymbol ds; |
| EnumDeclaration ed = null; |
| if (t && ((ds = t.toDsymbol(sc)) !is null)) |
| ed = ds.isEnumDeclaration(); // typedef'ed enum |
| if (!ed && te && ((ds = te.toDsymbol(sc)) !is null)) |
| ed = ds.isEnumDeclaration(); |
| if (ed && ss.cases.length < ed.members.length) |
| { |
| int missingMembers = 0; |
| const maxShown = !global.params.verbose ? 6 : int.max; |
| Lmembers: |
| foreach (es; *ed.members) |
| { |
| EnumMember em = es.isEnumMember(); |
| if (em) |
| { |
| foreach (cs; *ss.cases) |
| { |
| if (cs.exp.equals(em.value) || (!cs.exp.type.isString() && |
| !em.value.type.isString() && cs.exp.toInteger() == em.value.toInteger())) |
| continue Lmembers; |
| } |
| if (missingMembers == 0) |
| ss.error("missing cases for `enum` members in `final switch`:"); |
| |
| if (missingMembers < maxShown) |
| errorSupplemental(ss.loc, "`%s`", em.toChars()); |
| missingMembers++; |
| } |
| } |
| if (missingMembers > 0) |
| { |
| if (missingMembers > maxShown) |
| errorSupplemental(ss.loc, "... (%d more, -v to show) ...", missingMembers - maxShown); |
| sc.pop(); |
| return setError(); |
| } |
| } |
| else |
| needswitcherror = true; |
| } |
| |
| if (!sc.sw.sdefault && |
| (!ss.isFinal || needswitcherror || global.params.useAssert == CHECKENABLE.on)) |
| { |
| ss.hasNoDefault = 1; |
| |
| if (!ss.isFinal && (!ss._body || !ss._body.isErrorStatement()) && !(sc.flags & SCOPE.Cfile)) |
| ss.error("`switch` statement without a `default`; use `final switch` or add `default: assert(0);` or add `default: break;`"); |
| |
| // Generate runtime error if the default is hit |
| auto a = new Statements(); |
| CompoundStatement cs; |
| Statement s; |
| |
| if (sc.flags & SCOPE.Cfile) |
| { |
| s = new BreakStatement(ss.loc, null); // default for C is `default: break;` |
| } |
| else if (global.params.useSwitchError == CHECKENABLE.on && |
| global.params.checkAction != CHECKACTION.halt) |
| { |
| if (global.params.checkAction == CHECKACTION.C) |
| { |
| /* Rewrite as an assert(0) and let e2ir generate |
| * the call to the C assert failure function |
| */ |
| s = new ExpStatement(ss.loc, new AssertExp(ss.loc, IntegerExp.literal!0)); |
| } |
| else |
| { |
| if (!verifyHookExist(ss.loc, *sc, Id.__switch_error, "generating assert messages")) |
| return setError(); |
| |
| Expression sl = new IdentifierExp(ss.loc, Id.empty); |
| sl = new DotIdExp(ss.loc, sl, Id.object); |
| sl = new DotIdExp(ss.loc, sl, Id.__switch_error); |
| |
| Expressions* args = new Expressions(2); |
| (*args)[0] = new StringExp(ss.loc, ss.loc.filename.toDString()); |
| (*args)[1] = new IntegerExp(ss.loc.linnum); |
| |
| sl = new CallExp(ss.loc, sl, args); |
| sl = sl.expressionSemantic(sc); |
| |
| s = new SwitchErrorStatement(ss.loc, sl); |
| } |
| } |
| else |
| s = new ExpStatement(ss.loc, new HaltExp(ss.loc)); |
| |
| a.reserve(2); |
| sc.sw.sdefault = new DefaultStatement(ss.loc, s); |
| a.push(ss._body); |
| if (ss._body.blockExit(sc.func, false) & BE.fallthru) |
| a.push(new BreakStatement(Loc.initial, null)); |
| a.push(sc.sw.sdefault); |
| cs = new CompoundStatement(ss.loc, a); |
| ss._body = cs; |
| } |
| |
| if (!(sc.flags & SCOPE.Cfile) && ss.checkLabel()) |
| { |
| sc.pop(); |
| return setError(); |
| } |
| |
| |
| if (!ss.condition.type.isString()) |
| { |
| sc.pop(); |
| result = ss; |
| return; |
| } |
| |
| // Transform a switch with string labels into a switch with integer labels. |
| |
| // The integer value of each case corresponds to the index of each label |
| // string in the sorted array of label strings. |
| |
| // The value of the integer condition is obtained by calling the druntime template |
| // switch(object.__switch(cond, options...)) {0: {...}, 1: {...}, ...} |
| |
| // We sort a copy of the array of labels because we want to do a binary search in object.__switch, |
| // without modifying the order of the case blocks here in the compiler. |
| |
| if (!verifyHookExist(ss.loc, *sc, Id.__switch, "switch cases on strings")) |
| return setError(); |
| |
| size_t numcases = 0; |
| if (ss.cases) |
| numcases = ss.cases.dim; |
| |
| for (size_t i = 0; i < numcases; i++) |
| { |
| CaseStatement cs = (*ss.cases)[i]; |
| cs.index = cast(int)i; |
| } |
| |
| // Make a copy of all the cases so that qsort doesn't scramble the actual |
| // data we pass to codegen (the order of the cases in the switch). |
| CaseStatements *csCopy = (*ss.cases).copy(); |
| |
| if (numcases) |
| { |
| static int sort_compare(in CaseStatement* x, in CaseStatement* y) @trusted |
| { |
| auto se1 = x.exp.isStringExp(); |
| auto se2 = y.exp.isStringExp(); |
| return (se1 && se2) ? se1.compare(se2) : 0; |
| } |
| // Sort cases for efficient lookup |
| csCopy.sort!sort_compare; |
| } |
| |
| // The actual lowering |
| auto arguments = new Expressions(); |
| arguments.push(ss.condition); |
| |
| auto compileTimeArgs = new Objects(); |
| |
| // The type & label no. |
| compileTimeArgs.push(new TypeExp(ss.loc, ss.condition.type.nextOf())); |
| |
| // The switch labels |
| foreach (caseString; *csCopy) |
| { |
| compileTimeArgs.push(caseString.exp); |
| } |
| |
| Expression sl = new IdentifierExp(ss.loc, Id.empty); |
| sl = new DotIdExp(ss.loc, sl, Id.object); |
| sl = new DotTemplateInstanceExp(ss.loc, sl, Id.__switch, compileTimeArgs); |
| |
| sl = new CallExp(ss.loc, sl, arguments); |
| sl = sl.expressionSemantic(sc); |
| ss.condition = sl; |
| |
| auto i = 0; |
| foreach (c; *csCopy) |
| { |
| (*ss.cases)[c.index].exp = new IntegerExp(i++); |
| } |
| |
| //printf("%s\n", ss._body.toChars()); |
| ss.statementSemantic(sc); |
| |
| sc.pop(); |
| result = ss; |
| } |
| |
| override void visit(CaseStatement cs) |
| { |
| SwitchStatement sw = sc.sw; |
| bool errors = false; |
| |
| //printf("CaseStatement::semantic() %s\n", toChars()); |
| sc = sc.startCTFE(); |
| cs.exp = cs.exp.expressionSemantic(sc); |
| cs.exp = resolveProperties(sc, cs.exp); |
| sc = sc.endCTFE(); |
| |
| if (sw) |
| { |
| Expression initialExp = cs.exp; |
| |
| // The switch'ed value has errors and doesn't provide the actual type |
| // Omit the cast to enable further semantic (exluding the check for matching types) |
| if (sw.condition.type && !sw.condition.type.isTypeError()) |
| cs.exp = cs.exp.implicitCastTo(sc, sw.condition.type); |
| cs.exp = cs.exp.optimize(WANTvalue | WANTexpand); |
| |
| Expression e = cs.exp; |
| // Remove all the casts the user and/or implicitCastTo may introduce |
| // otherwise we'd sometimes fail the check below. |
| while (e.op == EXP.cast_) |
| e = (cast(CastExp)e).e1; |
| |
| /* This is where variables are allowed as case expressions. |
| */ |
| if (e.op == EXP.variable) |
| { |
| VarExp ve = cast(VarExp)e; |
| VarDeclaration v = ve.var.isVarDeclaration(); |
| Type t = cs.exp.type.toBasetype(); |
| if (v && (t.isintegral() || t.ty == Tclass)) |
| { |
| /* Flag that we need to do special code generation |
| * for this, i.e. generate a sequence of if-then-else |
| */ |
| sw.hasVars = 1; |
| |
| /* TODO check if v can be uninitialized at that point. |
| */ |
| if (!v.isConst() && !v.isImmutable()) |
| { |
| cs.error("`case` variables have to be `const` or `immutable`"); |
| } |
| |
| if (sw.isFinal) |
| { |
| cs.error("`case` variables not allowed in `final switch` statements"); |
| errors = true; |
| } |
| |
| /* Find the outermost scope `scx` that set `sw`. |
| * Then search scope `scx` for a declaration of `v`. |
| */ |
| for (Scope* scx = sc; scx; scx = scx.enclosing) |
| { |
| if (scx.enclosing && scx.enclosing.sw == sw) |
| continue; |
| assert(scx.sw == sw); |
| |
| if (!scx.search(cs.exp.loc, v.ident, null)) |
| { |
| cs.error("`case` variable `%s` declared at %s cannot be declared in `switch` body", |
| v.toChars(), v.loc.toChars()); |
| errors = true; |
| } |
| break; |
| } |
| goto L1; |
| } |
| } |
| else |
| cs.exp = cs.exp.ctfeInterpret(); |
| |
| if (StringExp se = cs.exp.toStringExp()) |
| cs.exp = se; |
| else if (!cs.exp.isIntegerExp() && !cs.exp.isErrorExp()) |
| { |
| cs.error("`case` must be a `string` or an integral constant, not `%s`", cs.exp.toChars()); |
| errors = true; |
| } |
| |
| L1: |
| // // Don't check other cases if this has errors |
| if (!cs.exp.isErrorExp()) |
| foreach (cs2; *sw.cases) |
| { |
| //printf("comparing '%s' with '%s'\n", exp.toChars(), cs.exp.toChars()); |
| if (cs2.exp.equals(cs.exp)) |
| { |
| // https://issues.dlang.org/show_bug.cgi?id=15909 |
| cs.error("duplicate `case %s` in `switch` statement", initialExp.toChars()); |
| errors = true; |
| break; |
| } |
| } |
| |
| sw.cases.push(cs); |
| |
| // Resolve any goto case's with no exp to this case statement |
| for (size_t i = 0; i < sw.gotoCases.dim;) |
| { |
| GotoCaseStatement gcs = sw.gotoCases[i]; |
| if (!gcs.exp) |
| { |
| gcs.cs = cs; |
| sw.gotoCases.remove(i); // remove from array |
| continue; |
| } |
| i++; |
| } |
| |
| if (sc.sw.tf != sc.tf) |
| { |
| cs.error("`switch` and `case` are in different `finally` blocks"); |
| errors = true; |
| } |
| if (sc.sw.tryBody != sc.tryBody) |
| { |
| cs.error("case cannot be in different `try` block level from `switch`"); |
| errors = true; |
| } |
| } |
| else |
| { |
| cs.error("`case` not in `switch` statement"); |
| errors = true; |
| } |
| |
| sc.ctorflow.orCSX(CSX.label); |
| cs.statement = cs.statement.statementSemantic(sc); |
| if (cs.statement.isErrorStatement()) |
| { |
| result = cs.statement; |
| return; |
| } |
| if (errors || cs.exp.op == EXP.error) |
| return setError(); |
| |
| cs.lastVar = sc.lastVar; |
| result = cs; |
| } |
| |
| override void visit(CaseRangeStatement crs) |
| { |
| SwitchStatement sw = sc.sw; |
| if (sw is null) |
| { |
| crs.error("case range not in `switch` statement"); |
| return setError(); |
| } |
| |
| //printf("CaseRangeStatement::semantic() %s\n", toChars()); |
| bool errors = false; |
| if (sw.isFinal) |
| { |
| crs.error("case ranges not allowed in `final switch`"); |
| errors = true; |
| } |
| |
| sc = sc.startCTFE(); |
| crs.first = crs.first.expressionSemantic(sc); |
| crs.first = resolveProperties(sc, crs.first); |
| sc = sc.endCTFE(); |
| crs.first = crs.first.implicitCastTo(sc, sw.condition.type); |
| crs.first = crs.first.ctfeInterpret(); |
| |
| sc = sc.startCTFE(); |
| crs.last = crs.last.expressionSemantic(sc); |
| crs.last = resolveProperties(sc, crs.last); |
| sc = sc.endCTFE(); |
| crs.last = crs.last.implicitCastTo(sc, sw.condition.type); |
| crs.last = crs.last.ctfeInterpret(); |
| |
| if (crs.first.op == EXP.error || crs.last.op == EXP.error || errors) |
| { |
| if (crs.statement) |
| crs.statement.statementSemantic(sc); |
| return setError(); |
| } |
| |
| uinteger_t fval = crs.first.toInteger(); |
| uinteger_t lval = crs.last.toInteger(); |
| if ((crs.first.type.isunsigned() && fval > lval) || (!crs.first.type.isunsigned() && cast(sinteger_t)fval > cast(sinteger_t)lval)) |
| { |
| crs.error("first `case %s` is greater than last `case %s`", crs.first.toChars(), crs.last.toChars()); |
| errors = true; |
| lval = fval; |
| } |
| |
| if (lval - fval > 256) |
| { |
| crs.error("had %llu cases which is more than 257 cases in case range", 1 + lval - fval); |
| errors = true; |
| lval = fval + 256; |
| } |
| |
| if (errors) |
| return setError(); |
| |
| /* This works by replacing the CaseRange with an array of Case's. |
| * |
| * case a: .. case b: s; |
| * => |
| * case a: |
| * [...] |
| * case b: |
| * s; |
| */ |
| |
| auto statements = new Statements(); |
| for (uinteger_t i = fval; i != lval + 1; i++) |
| { |
| Statement s = crs.statement; |
| if (i != lval) // if not last case |
| s = new ExpStatement(crs.loc, cast(Expression)null); |
| Expression e = new IntegerExp(crs.loc, i, crs.first.type); |
| Statement cs = new CaseStatement(crs.loc, e, s); |
| statements.push(cs); |
| } |
| Statement s = new CompoundStatement(crs.loc, statements); |
| sc.ctorflow.orCSX(CSX.label); |
| s = s.statementSemantic(sc); |
| result = s; |
| } |
| |
| override void visit(DefaultStatement ds) |
| { |
| //printf("DefaultStatement::semantic()\n"); |
| bool errors = false; |
| if (sc.sw) |
| { |
| if (sc.sw.sdefault) |
| { |
| ds.error("`switch` statement already has a default"); |
| errors = true; |
| } |
| sc.sw.sdefault = ds; |
| |
| if (sc.sw.tf != sc.tf) |
| { |
| ds.error("`switch` and `default` are in different `finally` blocks"); |
| errors = true; |
| } |
| if (sc.sw.tryBody != sc.tryBody) |
| { |
| ds.error("default cannot be in different `try` block level from `switch`"); |
| errors = true; |
| } |
| if (sc.sw.isFinal) |
| { |
| ds.error("`default` statement not allowed in `final switch` statement"); |
| errors = true; |
| } |
| } |
| else |
| { |
| ds.error("`default` not in `switch` statement"); |
| errors = true; |
| } |
| |
| sc.ctorflow.orCSX(CSX.label); |
| ds.statement = ds.statement.statementSemantic(sc); |
| if (errors || ds.statement.isErrorStatement()) |
| return setError(); |
| |
| ds.lastVar = sc.lastVar; |
| result = ds; |
| } |
| |
| override void visit(GotoDefaultStatement gds) |
| { |
| /* https://dlang.org/spec/statement.html#goto-statement |
| */ |
| |
| gds.sw = sc.sw; |
| if (!gds.sw) |
| { |
| gds.error("`goto default` not in `switch` statement"); |
| return setError(); |
| } |
| if (gds.sw.isFinal) |
| { |
| gds.error("`goto default` not allowed in `final switch` statement"); |
| return setError(); |
| } |
| result = gds; |
| } |
| |
| override void visit(GotoCaseStatement gcs) |
| { |
| /* https://dlang.org/spec/statement.html#goto-statement |
| */ |
| |
| if (!sc.sw) |
| { |
| gcs.error("`goto case` not in `switch` statement"); |
| return setError(); |
| } |
| |
| if (gcs.exp) |
| { |
| gcs.exp = gcs.exp.expressionSemantic(sc); |
| gcs.exp = gcs.exp.implicitCastTo(sc, sc.sw.condition.type); |
| gcs.exp = gcs.exp.optimize(WANTvalue); |
| if (gcs.exp.op == EXP.error) |
| return setError(); |
| } |
| |
| sc.sw.gotoCases.push(gcs); |
| result = gcs; |
| } |
| |
| override void visit(ReturnStatement rs) |
| { |
| /* https://dlang.org/spec/statement.html#return-statement |
| */ |
| |
| //printf("ReturnStatement.dsymbolSemantic() %p, %s\n", rs, rs.toChars()); |
| |
|