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 }