Skip to content

Commit ae00eb3

Browse files
committed
Restart inference of foreach nested functions when lowering returns
1 parent 94b695a commit ae00eb3

9 files changed

Lines changed: 322 additions & 22 deletions

File tree

compiler/src/dmd/frontend.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5024,6 +5024,8 @@ class ForeachStatement final : public Statement
50245024
FuncDeclaration* func;
50255025
Array<Statement* >* cases;
50265026
Array<ScopeStatement* >* gotos;
5027+
bool hasReturnExp;
5028+
Statement* lowering;
50275029
ForeachStatement* syntaxCopy() override;
50285030
bool hasBreak() const override;
50295031
bool hasContinue() const override;

compiler/src/dmd/funcsem.d

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ public:
370370
auto catches = new Catches(ctch);
371371

372372
Statement s2 = new TryCatchStatement(Loc.initial, s._body, catches);
373-
fd.hasNoEH = false;
373+
fd.hasCatches = true;
374374
replaceCurrent(s2);
375375
s2.accept(this);
376376
}
@@ -1687,6 +1687,15 @@ extern (D) void declareThis(FuncDeclaration fd, Scope* sc)
16871687
fd.vthis.parent = fd;
16881688
if (ad)
16891689
fd.objc.selectorParameter = .objc.createSelectorParameter(fd, sc);
1690+
1691+
if (fd.fes && !fd.vthis._scope)
1692+
{
1693+
/* If foreach nested function, save the scope to allow outer semantic
1694+
* to access internal context pointer.
1695+
*/
1696+
fd.vthis._scope = sc.copy();
1697+
fd.vthis._scope.setNoFree();
1698+
}
16901699
}
16911700

16921701
/****************************************************

compiler/src/dmd/semantic3.d

Lines changed: 170 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import dmd.opover;
6060
import dmd.optimize;
6161
import dmd.parse;
6262
import dmd.root.filename;
63+
import dmd.root.aav;
6364
import dmd.root.array;
6465
import dmd.common.outbuffer;
6566
import dmd.root.rmem;
@@ -755,25 +756,10 @@ private extern(C++) final class Semantic3Visitor : Visitor
755756
*/
756757
funcdecl.buildEnsureRequire();
757758

758-
// Check for errors related to 'nothrow'.
759-
const blockexit = funcdecl.fbody.blockExit(funcdecl, f.isNothrow ? global.errorSink : null);
760-
if (f.isNothrow && blockexit & BE.throw_)
761-
error(funcdecl.loc, "%s `%s` may throw but is marked as `nothrow`", funcdecl.kind(), funcdecl.toPrettyChars());
762-
763-
if (!(blockexit & (BE.throw_ | BE.halt) || funcdecl.hasCatches))
764-
{
765-
/* Don't generate unwind tables for this function
766-
* https://issues.dlang.org/show_bug.cgi?id=17997
767-
*/
768-
funcdecl.hasNoEH = true;
769-
}
770-
771-
if (funcdecl.nothrowInprocess)
772-
{
773-
if (funcdecl.type == f)
774-
f = cast(TypeFunction)f.copy();
775-
f.isNothrow = !(blockexit & BE.throw_);
776-
}
759+
// Tentatively check the flow, ignoring errors.
760+
const oldErrors2 = global.startGagging();
761+
auto blockexit = funcdecl.fbody.blockExit(funcdecl, null);
762+
global.endGagging(oldErrors2);
777763

778764
if (funcdecl.fbody.isErrorStatement())
779765
{
@@ -834,6 +820,8 @@ private extern(C++) final class Semantic3Visitor : Visitor
834820
if (funcdecl.vresult || funcdecl.returnLabel)
835821
funcdecl.buildResultVar(scout ? scout : sc2, tret);
836822

823+
scope flr = new ForeachLambdaRewriter(funcdecl);
824+
837825
/* Cannot move this loop into NrvoWalker, because
838826
* returns[i] may be in the nested delegate for foreach-body.
839827
*/
@@ -975,7 +963,16 @@ private extern(C++) final class Semantic3Visitor : Visitor
975963

976964
if (funcdecl.vresult)
977965
{
978-
Scope* scret = rs.fesFunc ? rs.fesFunc._scope : sc2;
966+
Scope* scret = sc2;
967+
968+
if (rs.fesFunc)
969+
{
970+
flr.restartInference(rs.fesFunc);
971+
972+
// Borrow context pointer's scope for vresult.
973+
assert(rs.fesFunc.vthis);
974+
scret = rs.fesFunc.vthis._scope;
975+
}
979976

980977
// Create: return (vresult = exp, vresult);
981978
exp = new ConstructExp(rs.loc, funcdecl.vresult, exp);
@@ -991,6 +988,8 @@ private extern(C++) final class Semantic3Visitor : Visitor
991988
}
992989
rs.exp = exp;
993990
}
991+
992+
flr.finish();
994993
}
995994
if (funcdecl.nrvo_var || funcdecl.returnLabel)
996995
{
@@ -1000,6 +999,27 @@ private extern(C++) final class Semantic3Visitor : Visitor
1000999
nw.visitStmt(funcdecl.fbody);
10011000
}
10021001

1002+
// Definitively check for errors related to 'nothrow'.
1003+
blockexit = funcdecl.fbody.blockExit(funcdecl, f.isNothrow ? global.errorSink : null);
1004+
1005+
if (f.isNothrow && blockexit & BE.throw_)
1006+
error(funcdecl.loc, "%s `%s` may throw but is marked as `nothrow`", funcdecl.kind(), funcdecl.toPrettyChars());
1007+
1008+
if (!(blockexit & (BE.throw_ | BE.halt) || funcdecl.hasCatches))
1009+
{
1010+
/* Don't generate unwind tables for this function
1011+
* https://issues.dlang.org/show_bug.cgi?id=17997
1012+
*/
1013+
funcdecl.hasNoEH = true;
1014+
}
1015+
1016+
if (funcdecl.nothrowInprocess)
1017+
{
1018+
if (funcdecl.type == f)
1019+
f = cast(TypeFunction)f.copy();
1020+
f.isNothrow = !(blockexit & BE.throw_);
1021+
}
1022+
10031023
sc2 = sc2.pop();
10041024
}
10051025

@@ -1945,3 +1965,133 @@ private void findClosureVars(FuncDeclaration fd, scope void delegate(FuncDeclara
19451965
}
19461966
}
19471967
}
1968+
1969+
/* Rewrites nested functions generated by foreach statements
1970+
* that contain returns. Fixes up attributes and runs semantic
1971+
* on lowered statements.
1972+
*/
1973+
private final class ForeachLambdaRewriter
1974+
{
1975+
// Parent function
1976+
FuncDeclaration parent;
1977+
1978+
// Set of nested functions pending rewrite
1979+
AssocArray!(FuncDeclaration, bool) lambdas;
1980+
1981+
public:
1982+
this(FuncDeclaration parent)
1983+
{
1984+
this.parent = parent;
1985+
}
1986+
1987+
void restartInference(FuncDeclaration fd)
1988+
{
1989+
auto tfp = parent.type.isTypeFunction();
1990+
1991+
for (; fd && fd.fes; fd = fd.toParent2().isFuncDeclaration())
1992+
{
1993+
auto tfl = fd.type.isTypeFunction();
1994+
1995+
if (tfp.purity == PURE.impure && tfl.purity > PURE.impure)
1996+
{
1997+
tfl.purity = PURE.impure;
1998+
fd.purityInprocess = true;
1999+
}
2000+
2001+
if (tfp.trust != TRUST.safe && tfl.trust == TRUST.safe)
2002+
fd.safetyInprocess = true;
2003+
2004+
if (!tfp.isNogc && tfl.isNogc)
2005+
fd.nogcInprocess = true;
2006+
2007+
/* Always restart inference of nothrow because new exceptions
2008+
* can be introduced that are caught by parent function.
2009+
* However, parent catches should not change whether
2010+
* a nested function is nothrow.
2011+
*/
2012+
if (tfl.isNothrow)
2013+
fd.nothrowInprocess = true;
2014+
2015+
lambdas.getLvalue(fd);
2016+
}
2017+
}
2018+
2019+
void finish()
2020+
{
2021+
Array!(FuncDeclarations*) nestLevels;
2022+
2023+
/* At this point, AST post-order no longer corresponds to function
2024+
* nesting depth. Do a counting sort of nesting levels.
2025+
*/
2026+
foreach (keyValue; lambdas.asRange())
2027+
{
2028+
size_t level = 0;
2029+
for (auto fd = keyValue.key; fd && fd.fes;
2030+
fd = fd.toParent2().isFuncDeclaration())
2031+
{
2032+
level++;
2033+
}
2034+
2035+
if (level > nestLevels.length)
2036+
{
2037+
size_t oldSize = nestLevels.length;
2038+
nestLevels.setDim(level);
2039+
foreach (size_t i; oldSize .. level)
2040+
nestLevels[i] = new FuncDeclarations();
2041+
}
2042+
2043+
nestLevels[level - 1].push(keyValue.key);
2044+
}
2045+
2046+
foreach_reverse (funcs; nestLevels[])
2047+
{
2048+
foreach (fd; (*funcs)[])
2049+
{
2050+
// Redo attribute inference, most nested first.
2051+
auto tf = fd.type.isTypeFunction();
2052+
2053+
if (fd.purityInprocess)
2054+
{
2055+
fd.purityInprocess = false;
2056+
tf.purity = PURE.fwdref;
2057+
}
2058+
2059+
if (fd.safetyInprocess)
2060+
fd.safetyInprocess = false;
2061+
2062+
if (fd.nogcInprocess)
2063+
fd.nogcInprocess = false;
2064+
2065+
if (fd.nothrowInprocess)
2066+
{
2067+
auto blockexit = fd.fbody.blockExit(fd, null);
2068+
2069+
if (blockexit & (BE.throw_ | BE.halt))
2070+
fd.hasNoEH = false;
2071+
2072+
fd.nothrowInprocess = false;
2073+
tf.isNothrow = !(blockexit & BE.throw_);
2074+
}
2075+
2076+
if (!fd.fes.lowering)
2077+
continue;
2078+
2079+
// Run semantic on the calling point.
2080+
Expression* ec;
2081+
if (auto ss = fd.fes.lowering.isSwitchStatement())
2082+
ec = &ss.condition;
2083+
else if (auto es = fd.fes.lowering.isExpStatement())
2084+
if (auto ce = es.exp.isCastExp())
2085+
ec = &ce.e1;
2086+
2087+
assert(ec, "Unexpected lowering of foreach statement");
2088+
2089+
ec.type = null;
2090+
*ec = expressionSemantic(*ec, fd._scope);
2091+
2092+
if (!ec.isErrorExp() && ec.type != Type.tint32)
2093+
.error(fd.fes.loc, "`opApply()` function for `%s` must return an `int`", fd.fes.aggr.type.toBasetype().toChars());
2094+
}
2095+
}
2096+
}
2097+
}

compiler/src/dmd/statement.d

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -852,6 +852,9 @@ extern (C++) final class ForeachStatement : Statement
852852
Statements* cases; // put breaks, continues, gotos and returns here
853853
ScopeStatements* gotos; // forward referenced goto's go here
854854

855+
bool hasReturnExp; // has return exp; statement
856+
Statement lowering; // statement we lower to
857+
855858
extern (D) this(Loc loc, TOK op, Parameters* parameters, Expression aggr, Statement _body, Loc endloc) @safe
856859
{
857860
super(loc, STMT.Foreach);

compiler/src/dmd/statement.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,9 @@ class ForeachStatement final : public Statement
335335
Statements *cases; // put breaks, continues, gotos and returns here
336336
ScopeStatements *gotos; // forward referenced goto's go here
337337

338+
bool hasReturnExp; // has return exp; statement
339+
Statement *lowering; // statement we lower to
340+
338341
ForeachStatement *syntaxCopy() override;
339342
bool hasBreak() const override;
340343
bool hasContinue() const override;

compiler/src/dmd/statementsem.d

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,9 @@ Statement statementSemanticVisit(Statement s, Scope* sc)
958958
return null;
959959
}
960960

961+
if (fs.hasReturnExp && sc.fes)
962+
sc.fes.hasReturnExp = true;
963+
961964
Expression ec;
962965
switch (tab.ty)
963966
{
@@ -971,7 +974,15 @@ Statement statementSemanticVisit(Statement s, Scope* sc)
971974
return null;
972975

973976
e = Expression.combine(e, ec);
974-
return loopReturn(e, fs.cases, loc);
977+
Statement lrs = loopReturn(e, fs.cases, loc);
978+
979+
if (!e.type && fs.hasReturnExp)
980+
{
981+
e.type = Type.tint32;
982+
fs.lowering = lrs;
983+
}
984+
985+
return lrs;
975986
}
976987

977988
switch (tab.ty)
@@ -2888,6 +2899,7 @@ Statement statementSemanticVisit(Statement s, Scope* sc)
28882899
// return vresult;
28892900
Statement s = new ReturnStatement(Loc.initial, new VarExp(Loc.initial, fd.vresult));
28902901
sc.fes.cases.push(s);
2902+
sc.fes.hasReturnExp = true;
28912903

28922904
// Save receiver index for the later rewriting from:
28932905
// return exp;
@@ -3847,6 +3859,8 @@ private extern(D) Expression applyOpApply(ForeachStatement fs, Expression flde,
38473859
Expression ec;
38483860
ec = new DotIdExp(fs.loc, fs.aggr, sapply.ident);
38493861
ec = new CallExp(fs.loc, ec, flde);
3862+
if (fs.hasReturnExp) // would be rewritten in semantic3, run semantic later
3863+
return ec;
38503864
ec = ec.expressionSemantic(sc2);
38513865
if (ec.isErrorExp())
38523866
return null;
@@ -3873,6 +3887,8 @@ private extern(D) Expression applyDelegate(ForeachStatement fs, Expression flde,
38733887
fs.aggr = de.e1;
38743888
}
38753889
ec = new CallExp(fs.loc, fs.aggr, flde);
3890+
if (fs.hasReturnExp) // would be rewritten in semantic3, run semantic later
3891+
return ec;
38763892
ec = ec.expressionSemantic(sc2);
38773893
if (ec.op == EXP.error)
38783894
return null;

compiler/test/compilable/pr22672.d

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
struct S4
2+
{
3+
this(ref inout S4) @system {}
4+
}
5+
6+
struct V4
7+
{
8+
int opApply(int delegate(Object) @safe dg) @safe
9+
{
10+
return dg(null);
11+
}
12+
}
13+
14+
S4 s;
15+
16+
auto ref h4()
17+
{
18+
foreach (_; V4())
19+
{
20+
return s;
21+
}
22+
return s;
23+
}

0 commit comments

Comments
 (0)