blob: fd9569105b59c56ce18cc418e22958e9a5b86aaf [file] [log] [blame]
/**
* 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-2023 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.location;
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.length)
{
if (vd == pfd.closureVars[i])
{
pfd.closureVars.remove(i);
fd.closureVars.push(vd);
break;
}
}
}
public:
extern (D) this(FuncDeclaration fd) scope
{
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) scope
{
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;
}