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