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 }