1 /**
2  * Implements conversion from expressions to delegates for lazy parameters.
3  *
4  * Specification: $(LINK2 https://dlang.org/spec/function.html#lazy-params, Lazy Parameters)
5  *
6  * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
7  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
8  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
9  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/delegatize.d, _delegatize.d)
10  * Documentation:  https://dlang.org/phobos/dmd_delegatize.html
11  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/delegatize.d
12  */
13 
14 module dmd.delegatize;
15 
16 import core.stdc.stdio;
17 import dmd.apply;
18 import dmd.declaration;
19 import dmd.dscope;
20 import dmd.dsymbol;
21 import dmd.expression;
22 import dmd.expressionsem;
23 import dmd.func;
24 import dmd.globals;
25 import dmd.init;
26 import dmd.initsem;
27 import dmd.mtype;
28 import dmd.statement;
29 import dmd.tokens;
30 import dmd.visitor;
31 
32 
33 /*********************************
34  * Convert expression into a delegate.
35  *
36  * Used to convert the argument to a lazy parameter.
37  *
38  * Params:
39  *  e = argument to convert to a delegate
40  *  t = the type to be returned by the delegate
41  *  sc = context
42  * Returns:
43  *  A delegate literal
44  */
45 Expression toDelegate(Expression e, Type t, Scope* sc)
46 {
47     //printf("Expression::toDelegate(t = %s) %s\n", t.toChars(), e.toChars());
48     Loc loc = e.loc;
49     auto tf = new TypeFunction(ParameterList(), t, LINK.d);
50     if (t.hasWild())
51         tf.mod = MODFlags.wild;
52     auto fld = new FuncLiteralDeclaration(loc, loc, tf, TOK.delegate_, null);
53     lambdaSetParent(e, fld);
54 
55     sc = sc.push();
56     sc.parent = fld; // set current function to be the delegate
57     bool r = lambdaCheckForNestedRef(e, sc);
58     sc = sc.pop();
59     if (r)
60         return new ErrorExp();
61 
62     Statement s;
63     if (t.ty == Tvoid)
64         s = new ExpStatement(loc, e);
65     else
66         s = new ReturnStatement(loc, e);
67     fld.fbody = s;
68     e = new FuncExp(loc, fld);
69     e = e.expressionSemantic(sc);
70     return e;
71 }
72 
73 /******************************************
74  * Patch the parent of declarations to be the new function literal.
75  *
76  * Since the expression is going to be moved into a function literal,
77  * the parent for declarations in the expression needs to be
78  * reset to that function literal.
79  * Params:
80  *   e = expression to check
81  *   fd = function literal symbol (the new parent)
82  */
83 private void lambdaSetParent(Expression e, FuncDeclaration fd)
84 {
85     extern (C++) final class LambdaSetParent : StoppableVisitor
86     {
87         alias visit = typeof(super).visit;
88         FuncDeclaration fd;
89 
90         private void setParent(Dsymbol s)
91         {
92             VarDeclaration vd = s.isVarDeclaration();
93             FuncDeclaration pfd = s.parent ? s.parent.isFuncDeclaration() : null;
94             s.parent = fd;
95             if (!vd || !pfd)
96                 return;
97             // move to fd's closure when applicable
98             foreach (i; 0 .. pfd.closureVars.dim)
99             {
100                 if (vd == pfd.closureVars[i])
101                 {
102                     pfd.closureVars.remove(i);
103                     fd.closureVars.push(vd);
104                     break;
105                 }
106             }
107         }
108 
109     public:
110         extern (D) this(FuncDeclaration fd)
111         {
112             this.fd = fd;
113         }
114 
115         override void visit(Expression)
116         {
117         }
118 
119         override void visit(DeclarationExp e)
120         {
121             setParent(e.declaration);
122             e.declaration.accept(this);
123         }
124 
125         override void visit(IndexExp e)
126         {
127             if (e.lengthVar)
128             {
129                 //printf("lengthVar\n");
130                 setParent(e.lengthVar);
131                 e.lengthVar.accept(this);
132             }
133         }
134 
135         override void visit(SliceExp e)
136         {
137             if (e.lengthVar)
138             {
139                 //printf("lengthVar\n");
140                 setParent(e.lengthVar);
141                 e.lengthVar.accept(this);
142             }
143         }
144 
145         override void visit(Dsymbol)
146         {
147         }
148 
149         override void visit(VarDeclaration v)
150         {
151             if (v._init)
152                 v._init.accept(this);
153         }
154 
155         override void visit(Initializer)
156         {
157         }
158 
159         override void visit(ExpInitializer ei)
160         {
161             walkPostorder(ei.exp ,this);
162         }
163 
164         override void visit(StructInitializer si)
165         {
166             foreach (i, const id; si.field)
167                 if (Initializer iz = si.value[i])
168                     iz.accept(this);
169         }
170 
171         override void visit(ArrayInitializer ai)
172         {
173             foreach (i, ex; ai.index)
174             {
175                 if (ex)
176                     walkPostorder(ex, this);
177                 if (Initializer iz = ai.value[i])
178                     iz.accept(this);
179             }
180         }
181     }
182 
183     scope LambdaSetParent lsp = new LambdaSetParent(fd);
184     walkPostorder(e, lsp);
185 }
186 
187 /*******************************************
188  * Look for references to variables in a scope enclosing the new function literal.
189  *
190  * Essentially just calls `checkNestedReference() for each variable reference in `e`.
191  * Params:
192  *      sc = context
193  *      e = expression to check
194  * Returns:
195  *      true if error occurs.
196  */
197 bool lambdaCheckForNestedRef(Expression e, Scope* sc)
198 {
199     extern (C++) final class LambdaCheckForNestedRef : StoppableVisitor
200     {
201         alias visit = typeof(super).visit;
202     public:
203         Scope* sc;
204         bool result;
205 
206         extern (D) this(Scope* sc)
207         {
208             this.sc = sc;
209         }
210 
211         override void visit(Expression)
212         {
213         }
214 
215         override void visit(SymOffExp e)
216         {
217             VarDeclaration v = e.var.isVarDeclaration();
218             if (v)
219                 result = v.checkNestedReference(sc, Loc.initial);
220         }
221 
222         override void visit(VarExp e)
223         {
224             VarDeclaration v = e.var.isVarDeclaration();
225             if (v)
226                 result = v.checkNestedReference(sc, Loc.initial);
227         }
228 
229         override void visit(ThisExp e)
230         {
231             if (e.var)
232                 result = e.var.checkNestedReference(sc, Loc.initial);
233         }
234 
235         override void visit(DeclarationExp e)
236         {
237             VarDeclaration v = e.declaration.isVarDeclaration();
238             if (v)
239             {
240                 result = v.checkNestedReference(sc, Loc.initial);
241                 if (result)
242                     return;
243                 /* Some expressions cause the frontend to create a temporary.
244                  * For example, structs with cpctors replace the original
245                  * expression e with:
246                  *  __cpcttmp = __cpcttmp.cpctor(e);
247                  *
248                  * In this instance, we need to ensure that the original
249                  * expression e does not have any nested references by
250                  * checking the declaration initializer too.
251                  */
252                 if (v._init && v._init.isExpInitializer())
253                 {
254                     Expression ie = v._init.initializerToExpression();
255                     result = lambdaCheckForNestedRef(ie, sc);
256                 }
257             }
258         }
259     }
260 
261     scope LambdaCheckForNestedRef v = new LambdaCheckForNestedRef(sc);
262     walkPostorder(e, v);
263     return v.result;
264 }
265 
266 /*****************************************
267  * See if context `s` is nested within context `p`, meaning
268  * it `p` is reachable at runtime by walking the static links.
269  * If any of the intervening contexts are function literals,
270  * make sure they are delegates.
271  * Params:
272  *      s = inner context
273  *      p = outer context
274  * Returns:
275  *      true means it is accessible by walking the context pointers at runtime
276  * References:
277  *      for static links see https://en.wikipedia.org/wiki/Call_stack#Functions_of_the_call_stack
278  */
279 bool ensureStaticLinkTo(Dsymbol s, Dsymbol p)
280 {
281     while (s)
282     {
283         if (s == p) // hit!
284             return true;
285 
286         if (auto fd = s.isFuncDeclaration())
287         {
288             if (!fd.isThis() && !fd.isNested())
289                 break;
290 
291             // https://issues.dlang.org/show_bug.cgi?id=15332
292             // change to delegate if fd is actually nested.
293             if (auto fld = fd.isFuncLiteralDeclaration())
294                 fld.tok = TOK.delegate_;
295         }
296         if (auto ad = s.isAggregateDeclaration())
297         {
298             if (ad.storage_class & STC.static_)
299                 break;
300         }
301         s = s.toParentP(p);
302     }
303     return false;
304 }