/** * Implements conversion from expressions to delegates for lazy parameters. * * Specification: $(LINK2 https://dlang.org/spec/function.html#lazy-params, Lazy Parameters) * * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/delegatize.d, _delegatize.d) * Documentation: https://dlang.org/phobos/dmd_delegatize.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/delegatize.d */ module dmd.delegatize; import core.stdc.stdio; import dmd.apply; import dmd.astenums; import dmd.declaration; import dmd.dscope; import dmd.dsymbol; import dmd.expression; import dmd.expressionsem; import dmd.func; import dmd.globals; import dmd.init; import dmd.initsem; import dmd.mtype; import dmd.statement; import dmd.tokens; import dmd.visitor; /********************************* * Convert expression into a delegate. * * Used to convert the argument to a lazy parameter. * * Params: * e = argument to convert to a delegate * t = the type to be returned by the delegate * sc = context * Returns: * A delegate literal */ Expression toDelegate(Expression e, Type t, Scope* sc) { //printf("Expression::toDelegate(t = %s) %s\n", t.toChars(), e.toChars()); Loc loc = e.loc; auto tf = new TypeFunction(ParameterList(), t, LINK.d); if (t.hasWild()) tf.mod = MODFlags.wild; auto fld = new FuncLiteralDeclaration(loc, loc, tf, TOK.delegate_, null); lambdaSetParent(e, fld); sc = sc.push(); sc.parent = fld; // set current function to be the delegate bool r = lambdaCheckForNestedRef(e, sc); sc = sc.pop(); if (r) return ErrorExp.get(); Statement s; if (t.ty == Tvoid) s = new ExpStatement(loc, e); else s = new ReturnStatement(loc, e); fld.fbody = s; e = new FuncExp(loc, fld); e = e.expressionSemantic(sc); return e; } /****************************************** * Patch the parent of declarations to be the new function literal. * * Since the expression is going to be moved into a function literal, * the parent for declarations in the expression needs to be * reset to that function literal. * Params: * e = expression to check * fd = function literal symbol (the new parent) */ private void lambdaSetParent(Expression e, FuncDeclaration fd) { extern (C++) final class LambdaSetParent : StoppableVisitor { alias visit = typeof(super).visit; FuncDeclaration fd; private void setParent(Dsymbol s) { VarDeclaration vd = s.isVarDeclaration(); FuncDeclaration pfd = s.parent ? s.parent.isFuncDeclaration() : null; s.parent = fd; if (!vd || !pfd) return; // move to fd's closure when applicable foreach (i; 0 .. pfd.closureVars.dim) { if (vd == pfd.closureVars[i]) { pfd.closureVars.remove(i); fd.closureVars.push(vd); break; } } } public: extern (D) this(FuncDeclaration fd) { this.fd = fd; } override void visit(Expression) { } override void visit(DeclarationExp e) { setParent(e.declaration); e.declaration.accept(this); } override void visit(IndexExp e) { if (e.lengthVar) { //printf("lengthVar\n"); setParent(e.lengthVar); e.lengthVar.accept(this); } } override void visit(SliceExp e) { if (e.lengthVar) { //printf("lengthVar\n"); setParent(e.lengthVar); e.lengthVar.accept(this); } } override void visit(Dsymbol) { } override void visit(VarDeclaration v) { if (v._init) v._init.accept(this); } override void visit(Initializer) { } override void visit(ExpInitializer ei) { walkPostorder(ei.exp ,this); } override void visit(StructInitializer si) { foreach (i, const id; si.field) if (Initializer iz = si.value[i]) iz.accept(this); } override void visit(ArrayInitializer ai) { foreach (i, ex; ai.index) { if (ex) walkPostorder(ex, this); if (Initializer iz = ai.value[i]) iz.accept(this); } } } scope LambdaSetParent lsp = new LambdaSetParent(fd); walkPostorder(e, lsp); } /******************************************* * Look for references to variables in a scope enclosing the new function literal. * * Essentially just calls `checkNestedReference() for each variable reference in `e`. * Params: * sc = context * e = expression to check * Returns: * true if error occurs. */ bool lambdaCheckForNestedRef(Expression e, Scope* sc) { extern (C++) final class LambdaCheckForNestedRef : StoppableVisitor { alias visit = typeof(super).visit; public: Scope* sc; bool result; extern (D) this(Scope* sc) { this.sc = sc; } override void visit(Expression) { } override void visit(SymOffExp e) { VarDeclaration v = e.var.isVarDeclaration(); if (v) result = v.checkNestedReference(sc, Loc.initial); } override void visit(VarExp e) { VarDeclaration v = e.var.isVarDeclaration(); if (v) result = v.checkNestedReference(sc, Loc.initial); } override void visit(ThisExp e) { if (e.var) result = e.var.checkNestedReference(sc, Loc.initial); } override void visit(DeclarationExp e) { VarDeclaration v = e.declaration.isVarDeclaration(); if (v) { result = v.checkNestedReference(sc, Loc.initial); if (result) return; /* Some expressions cause the frontend to create a temporary. * For example, structs with cpctors replace the original * expression e with: * __cpcttmp = __cpcttmp.cpctor(e); * * In this instance, we need to ensure that the original * expression e does not have any nested references by * checking the declaration initializer too. */ if (v._init && v._init.isExpInitializer()) { Expression ie = v._init.initializerToExpression(); result = lambdaCheckForNestedRef(ie, sc); } } } } scope LambdaCheckForNestedRef v = new LambdaCheckForNestedRef(sc); walkPostorder(e, v); return v.result; } /***************************************** * See if context `s` is nested within context `p`, meaning * it `p` is reachable at runtime by walking the static links. * If any of the intervening contexts are function literals, * make sure they are delegates. * Params: * s = inner context * p = outer context * Returns: * true means it is accessible by walking the context pointers at runtime * References: * for static links see https://en.wikipedia.org/wiki/Call_stack#Functions_of_the_call_stack */ bool ensureStaticLinkTo(Dsymbol s, Dsymbol p) { while (s) { if (s == p) // hit! return true; if (auto fd = s.isFuncDeclaration()) { if (!fd.isThis() && !fd.isNested()) break; // https://issues.dlang.org/show_bug.cgi?id=15332 // change to delegate if fd is actually nested. if (auto fld = fd.isFuncLiteralDeclaration()) fld.tok = TOK.delegate_; } if (auto ad = s.isAggregateDeclaration()) { if (ad.storage_class & STC.static_) break; } s = s.toParentP(p); } return false; }