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