mirror of
https://github.com/dlang/dmd.git
synced 2025-04-25 20:50:41 +03:00
3781 lines
132 KiB
D
3781 lines
132 KiB
D
/**
|
|
* Does semantic analysis for functions.
|
|
*
|
|
* Specification: $(LINK2 https://dlang.org/spec/function.html, Functions)
|
|
*
|
|
* Copyright: Copyright (C) 1999-2025 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/compiler/src/dmd/funcsem.d, _funcsem.d)
|
|
* Documentation: https://dlang.org/phobos/dmd_funcsem.html
|
|
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/funcsem.d
|
|
*/
|
|
|
|
module dmd.funcsem;
|
|
|
|
import core.stdc.stdio;
|
|
|
|
import dmd.aggregate;
|
|
import dmd.arraytypes;
|
|
import dmd.astenums;
|
|
import dmd.attrib;
|
|
import dmd.blockexit;
|
|
import dmd.gluelayer;
|
|
import dmd.dcast;
|
|
import dmd.dclass;
|
|
import dmd.declaration;
|
|
import dmd.delegatize;
|
|
import dmd.dinterpret;
|
|
import dmd.dmodule;
|
|
import dmd.dscope;
|
|
import dmd.dstruct;
|
|
import dmd.dsymbol;
|
|
import dmd.dsymbolsem;
|
|
import dmd.dtemplate;
|
|
import dmd.errors;
|
|
import dmd.escape;
|
|
import dmd.expression;
|
|
import dmd.func;
|
|
import dmd.globals;
|
|
import dmd.hdrgen;
|
|
import dmd.id;
|
|
import dmd.identifier;
|
|
import dmd.importc;
|
|
import dmd.init;
|
|
import dmd.location;
|
|
import dmd.mtype;
|
|
import dmd.mustuse;
|
|
import dmd.objc;
|
|
import dmd.opover;
|
|
import dmd.pragmasem;
|
|
import dmd.root.aav;
|
|
import dmd.common.outbuffer;
|
|
import dmd.rootobject;
|
|
import dmd.root.filename;
|
|
import dmd.root.string;
|
|
import dmd.root.stringtable;
|
|
import dmd.safe;
|
|
import dmd.semantic2;
|
|
import dmd.semantic3;
|
|
import dmd.statement;
|
|
import dmd.statementsem;
|
|
import dmd.target;
|
|
import dmd.templatesem;
|
|
import dmd.tokens;
|
|
import dmd.typesem;
|
|
import dmd.visitor;
|
|
import dmd.visitor.statement_rewrite_walker;
|
|
|
|
version (IN_GCC) {}
|
|
else version (IN_LLVM) {}
|
|
else version = MARS;
|
|
|
|
/* Tweak all return statements and dtor call for nrvo_var, for correct NRVO.
|
|
*/
|
|
extern (C++) final class NrvoWalker : StatementRewriteWalker
|
|
{
|
|
alias visit = typeof(super).visit;
|
|
public:
|
|
FuncDeclaration fd;
|
|
Scope* sc;
|
|
|
|
override void visit(ReturnStatement s)
|
|
{
|
|
// See if all returns are instead to be replaced with a goto returnLabel;
|
|
if (fd.returnLabel)
|
|
{
|
|
/* Rewrite:
|
|
* return exp;
|
|
* as:
|
|
* vresult = exp; goto Lresult;
|
|
*/
|
|
auto gs = new GotoStatement(s.loc, Id.returnLabel);
|
|
gs.label = fd.returnLabel;
|
|
|
|
Statement s1 = gs;
|
|
if (s.exp)
|
|
s1 = new CompoundStatement(s.loc, new ExpStatement(s.loc, s.exp), gs);
|
|
|
|
replaceCurrent(s1);
|
|
}
|
|
}
|
|
|
|
override void visit(TryFinallyStatement s)
|
|
{
|
|
DtorExpStatement des;
|
|
if (fd.isNRVO() && s.finalbody && (des = s.finalbody.isDtorExpStatement()) !is null &&
|
|
fd.nrvo_var == des.var)
|
|
{
|
|
if (!(global.params.useExceptions && ClassDeclaration.throwable))
|
|
{
|
|
/* Don't need to call destructor at all, since it is nrvo
|
|
*/
|
|
replaceCurrent(s._body);
|
|
s._body.accept(this);
|
|
return;
|
|
}
|
|
|
|
/* Normally local variable dtors are called regardless exceptions.
|
|
* But for nrvo_var, its dtor should be called only when exception is thrown.
|
|
*
|
|
* Rewrite:
|
|
* try { s.body; } finally { nrvo_var.edtor; }
|
|
* // equivalent with:
|
|
* // s.body; scope(exit) nrvo_var.edtor;
|
|
* as:
|
|
* try { s.body; } catch(Throwable __o) { nrvo_var.edtor; throw __o; }
|
|
* // equivalent with:
|
|
* // s.body; scope(failure) nrvo_var.edtor;
|
|
*/
|
|
Statement sexception = new DtorExpStatement(Loc.initial, fd.nrvo_var.edtor, fd.nrvo_var);
|
|
Identifier id = Identifier.generateId("__o");
|
|
|
|
Statement handler = new PeelStatement(sexception);
|
|
if (sexception.blockExit(fd, null) & BE.fallthru)
|
|
{
|
|
auto ts = new ThrowStatement(Loc.initial, new IdentifierExp(Loc.initial, id));
|
|
ts.internalThrow = true;
|
|
handler = new CompoundStatement(Loc.initial, handler, ts);
|
|
}
|
|
|
|
auto catches = new Catches();
|
|
auto ctch = new Catch(Loc.initial, getThrowable(), id, handler);
|
|
ctch.internalCatch = true;
|
|
ctch.catchSemantic(sc); // Run semantic to resolve identifier '__o'
|
|
catches.push(ctch);
|
|
|
|
Statement s2 = new TryCatchStatement(Loc.initial, s._body, catches);
|
|
fd.hasNoEH = false;
|
|
replaceCurrent(s2);
|
|
s2.accept(this);
|
|
}
|
|
else
|
|
StatementRewriteWalker.visit(s);
|
|
}
|
|
}
|
|
|
|
/****************************************
|
|
* Only one entry point function is allowed. Print error if more than one.
|
|
* Params:
|
|
* fd = a "main" function
|
|
* Returns:
|
|
* true if haven't seen "main" before
|
|
*/
|
|
extern (C++) bool onlyOneMain(FuncDeclaration fd)
|
|
{
|
|
if (auto lastMain = FuncDeclaration.lastMain)
|
|
{
|
|
const format = (target.os == Target.OS.Windows)
|
|
? "only one entry point `main`, `WinMain` or `DllMain` is allowed"
|
|
: "only one entry point `main` is allowed";
|
|
error(fd.loc, format.ptr);
|
|
errorSupplemental(lastMain.loc, "previously found `%s` here", lastMain.toFullSignature());
|
|
return false;
|
|
}
|
|
FuncDeclaration.lastMain = fd;
|
|
return true;
|
|
}
|
|
|
|
/**********************************
|
|
* Main semantic routine for functions.
|
|
*/
|
|
void funcDeclarationSemantic(Scope* sc, FuncDeclaration funcdecl)
|
|
{
|
|
version (none)
|
|
{
|
|
printf("FuncDeclaration::semantic(sc = %p, this = %p, '%s', linkage = %d)\n", sc, funcdecl, funcdecl.toPrettyChars(), sc.linkage);
|
|
if (funcdecl.isFuncLiteralDeclaration())
|
|
printf("\tFuncLiteralDeclaration()\n");
|
|
printf("sc.parent = %s, parent = %s\n", sc.parent.toChars(), funcdecl.parent ? funcdecl.parent.toChars() : "");
|
|
printf("type: %p, %s\n", funcdecl.type, funcdecl.type.toChars());
|
|
}
|
|
|
|
if (funcdecl.semanticRun != PASS.initial && funcdecl.isFuncLiteralDeclaration())
|
|
{
|
|
/* Member functions that have return types that are
|
|
* forward references can have semantic() run more than
|
|
* once on them.
|
|
* See test\interface2.d, test20
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (funcdecl.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
assert(funcdecl.semanticRun <= PASS.semantic);
|
|
funcdecl.semanticRun = PASS.semantic;
|
|
|
|
if (funcdecl._scope)
|
|
{
|
|
sc = funcdecl._scope;
|
|
funcdecl._scope = null;
|
|
}
|
|
|
|
if (!sc || funcdecl.errors)
|
|
return;
|
|
|
|
funcdecl.cppnamespace = sc.namespace;
|
|
funcdecl.parent = sc.parent;
|
|
Dsymbol parent = funcdecl.toParent();
|
|
|
|
funcdecl.foverrides.setDim(0); // reset in case semantic() is being retried for this function
|
|
|
|
funcdecl.storage_class |= sc.stc & ~STC.ref_;
|
|
AggregateDeclaration ad = funcdecl.isThis();
|
|
// Don't nest structs b/c of generated methods which should not access the outer scopes.
|
|
// https://issues.dlang.org/show_bug.cgi?id=16627
|
|
if (ad && !funcdecl.isGenerated())
|
|
{
|
|
funcdecl.storage_class |= ad.storage_class & (STC.TYPECTOR | STC.synchronized_);
|
|
ad.makeNested();
|
|
}
|
|
if (sc.func)
|
|
funcdecl.storage_class |= sc.func.storage_class & STC.disable;
|
|
// Remove prefix storage classes silently.
|
|
if ((funcdecl.storage_class & STC.TYPECTOR) && !(ad || funcdecl.isNested()))
|
|
funcdecl.storage_class &= ~STC.TYPECTOR;
|
|
|
|
auto tf = funcdecl.type.isTypeFunction();
|
|
if ((funcdecl.storage_class & STC.auto_) && tf.isRef && !funcdecl.inferRetType)
|
|
{
|
|
if (!(funcdecl.storage_class & STC.autoref))
|
|
{
|
|
// @@@DEPRECATED_2.122@@@
|
|
// Deprecated in 2.112, turn into an error in 2.122
|
|
deprecation(funcdecl.loc, "`auto ref` return type must have `auto` and `ref` adjacent");
|
|
funcdecl.storage_class |= STC.autoref;
|
|
}
|
|
}
|
|
//printf("function storage_class = x%llx, sc.stc = x%llx, %x\n", storage_class, sc.stc, Declaration.isFinal());
|
|
|
|
if (sc.traitsCompiles)
|
|
funcdecl.skipCodegen = true;
|
|
|
|
funcdecl._linkage = sc.linkage;
|
|
if (sc.inCfile && funcdecl.isFuncLiteralDeclaration())
|
|
funcdecl._linkage = LINK.d; // so they are uniquely mangled
|
|
|
|
if (auto fld = funcdecl.isFuncLiteralDeclaration())
|
|
{
|
|
if (fld.treq)
|
|
{
|
|
Type treq = fld.treq;
|
|
assert(treq.nextOf().ty == Tfunction);
|
|
if (treq.ty == Tdelegate)
|
|
fld.tok = TOK.delegate_;
|
|
else if (treq.isPtrToFunction())
|
|
fld.tok = TOK.function_;
|
|
else
|
|
assert(0);
|
|
funcdecl._linkage = treq.nextOf().toTypeFunction().linkage;
|
|
}
|
|
}
|
|
|
|
// evaluate pragma(inline)
|
|
if (auto pragmadecl = sc.inlining)
|
|
funcdecl.inlining = evalPragmaInline(pragmadecl.loc, sc, pragmadecl.args);
|
|
|
|
funcdecl.visibility = sc.visibility;
|
|
funcdecl.userAttribDecl = sc.userAttribDecl;
|
|
checkGNUABITag(funcdecl, funcdecl._linkage);
|
|
checkMustUseReserved(funcdecl);
|
|
|
|
if (!funcdecl.originalType)
|
|
funcdecl.originalType = funcdecl.type.syntaxCopy();
|
|
|
|
if (sc.inCfile)
|
|
{
|
|
/* C11 allows a function to be declared with a typedef, D does not.
|
|
*/
|
|
if (auto ti = funcdecl.type.isTypeIdentifier())
|
|
{
|
|
auto tj = ti.typeSemantic(funcdecl.loc, sc);
|
|
if (auto tjf = tj.isTypeFunction())
|
|
{
|
|
/* Copy the type instead of just pointing to it,
|
|
* as we don't merge function types
|
|
*/
|
|
auto tjf2 = new TypeFunction(tjf.parameterList, tjf.next, tjf.linkage);
|
|
funcdecl.type = tjf2;
|
|
funcdecl.originalType = tjf2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!getFunctionType(funcdecl))
|
|
return;
|
|
|
|
if (!funcdecl.type.deco)
|
|
{
|
|
sc = sc.push();
|
|
sc.stc |= funcdecl.storage_class & (STC.disable | STC.deprecated_); // forward to function type
|
|
|
|
if (sc.func)
|
|
{
|
|
/* If the nesting parent is pure without inference,
|
|
* then this function defaults to pure too.
|
|
*
|
|
* auto foo() pure {
|
|
* auto bar() {} // become a weak purity function
|
|
* class C { // nested class
|
|
* auto baz() {} // become a weak purity function
|
|
* }
|
|
*
|
|
* static auto boo() {} // typed as impure
|
|
* // Even though, boo cannot call any impure functions.
|
|
* // See also Expression::checkPurity().
|
|
* }
|
|
*/
|
|
if (tf.purity == PURE.impure && (funcdecl.isNested() || funcdecl.isThis()))
|
|
{
|
|
FuncDeclaration fd = null;
|
|
for (Dsymbol p = funcdecl.toParent2(); p; p = p.toParent2())
|
|
{
|
|
if (AggregateDeclaration adx = p.isAggregateDeclaration())
|
|
{
|
|
if (adx.isNested())
|
|
continue;
|
|
break;
|
|
}
|
|
if ((fd = p.isFuncDeclaration()) !is null)
|
|
break;
|
|
}
|
|
|
|
/* If the parent's purity is inferred, then this function's purity needs
|
|
* to be inferred first.
|
|
*/
|
|
if (fd && fd.isPureBypassingInference() >= PURE.weak && !funcdecl.isInstantiated())
|
|
{
|
|
tf.purity = PURE.fwdref; // default to pure
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tf.isRef)
|
|
sc.stc |= STC.ref_;
|
|
if (tf.isScopeQual)
|
|
sc.stc |= STC.scope_;
|
|
if (tf.isNothrow)
|
|
sc.stc |= STC.nothrow_;
|
|
if (tf.isNogc)
|
|
sc.stc |= STC.nogc;
|
|
if (tf.isProperty)
|
|
sc.stc |= STC.property;
|
|
if (tf.purity == PURE.fwdref)
|
|
sc.stc |= STC.pure_;
|
|
|
|
if (tf.trust != TRUST.default_)
|
|
{
|
|
sc.stc &= ~STC.safeGroup;
|
|
if (tf.trust == TRUST.safe)
|
|
sc.stc |= STC.safe;
|
|
else if (tf.trust == TRUST.system)
|
|
sc.stc |= STC.system;
|
|
else if (tf.trust == TRUST.trusted)
|
|
sc.stc |= STC.trusted;
|
|
}
|
|
|
|
if (funcdecl.isCtorDeclaration())
|
|
{
|
|
tf.isCtor = true;
|
|
Type tret = ad.handleType();
|
|
assert(tret);
|
|
tret = tret.addStorageClass(funcdecl.storage_class | sc.stc);
|
|
tret = tret.addMod(funcdecl.type.mod);
|
|
tf.next = tret;
|
|
if (ad.isStructDeclaration())
|
|
sc.stc |= STC.ref_;
|
|
}
|
|
|
|
// 'return' on a non-static class member function implies 'scope' as well
|
|
if (ad && ad.isClassDeclaration() && (tf.isReturn || sc.stc & STC.return_) && !(sc.stc & STC.static_))
|
|
sc.stc |= STC.scope_;
|
|
|
|
// If 'this' has no pointers, remove 'scope' as it has no meaning
|
|
// Note: this is already covered by semantic of `VarDeclaration` and `TypeFunction`,
|
|
// but existing code relies on `hasPointers()` being called here to resolve forward references:
|
|
// https://github.com/dlang/dmd/pull/14232#issuecomment-1162906573
|
|
if (sc.stc & STC.scope_ && ad && ad.isStructDeclaration() && !ad.type.hasPointers())
|
|
{
|
|
sc.stc &= ~STC.scope_;
|
|
tf.isScopeQual = false;
|
|
if (tf.isReturnScope)
|
|
{
|
|
sc.stc &= ~(STC.return_ | STC.returnScope);
|
|
tf.isReturn = false;
|
|
tf.isReturnScope = false;
|
|
}
|
|
}
|
|
|
|
sc.linkage = funcdecl._linkage;
|
|
|
|
if (!tf.isNaked() && !(funcdecl.isThis() || funcdecl.isNested()))
|
|
{
|
|
import core.bitop : popcnt;
|
|
auto mods = MODtoChars(tf.mod);
|
|
.error(funcdecl.loc, "%s `%s` without `this` cannot be `%s`", funcdecl.kind, funcdecl.toPrettyChars, mods);
|
|
if (tf.next && tf.next.ty != Tvoid && popcnt(tf.mod) == 1)
|
|
.errorSupplemental(funcdecl.loc,
|
|
"did you mean to use `%s(%s)` as the return type?", mods, tf.next.toChars());
|
|
|
|
tf.mod = 0; // remove qualifiers
|
|
}
|
|
|
|
/* Apply const, immutable, wild and shared storage class
|
|
* to the function type. Do this before type semantic.
|
|
*/
|
|
auto stc = funcdecl.storage_class;
|
|
if (funcdecl.type.isImmutable())
|
|
stc |= STC.immutable_;
|
|
if (funcdecl.type.isConst())
|
|
stc |= STC.const_;
|
|
if (funcdecl.type.isShared() || funcdecl.storage_class & STC.synchronized_)
|
|
stc |= STC.shared_;
|
|
if (funcdecl.type.isWild())
|
|
stc |= STC.wild;
|
|
funcdecl.type = funcdecl.type.addSTC(stc);
|
|
|
|
funcdecl.type = funcdecl.type.typeSemantic(funcdecl.loc, sc);
|
|
sc = sc.pop();
|
|
}
|
|
|
|
auto f = getFunctionType(funcdecl);
|
|
if (!f)
|
|
return; // funcdecl's type is not a function
|
|
|
|
{
|
|
// Merge back function attributes into 'originalType'.
|
|
// It's used for mangling, ddoc, and json output.
|
|
TypeFunction tfo = funcdecl.originalType.toTypeFunction();
|
|
tfo.mod = f.mod;
|
|
tfo.isScopeQual = f.isScopeQual;
|
|
tfo.isReturnInferred = f.isReturnInferred;
|
|
tfo.isScopeInferred = f.isScopeInferred;
|
|
tfo.isRef = f.isRef;
|
|
tfo.isNothrow = f.isNothrow;
|
|
tfo.isNogc = f.isNogc;
|
|
tfo.isProperty = f.isProperty;
|
|
tfo.purity = f.purity;
|
|
tfo.trust = f.trust;
|
|
|
|
funcdecl.storage_class &= ~(STC.TYPECTOR | STC.FUNCATTR);
|
|
}
|
|
|
|
// check pragma(crt_constructor) signature
|
|
if (funcdecl.isCrtCtor || funcdecl.isCrtDtor)
|
|
{
|
|
const idStr = funcdecl.isCrtCtor ? "crt_constructor" : "crt_destructor";
|
|
if (f.nextOf().ty != Tvoid)
|
|
.error(funcdecl.loc, "%s `%s` must return `void` for `pragma(%s)`", funcdecl.kind, funcdecl.toPrettyChars, idStr.ptr);
|
|
if (funcdecl._linkage != LINK.c && f.parameterList.length != 0)
|
|
.error(funcdecl.loc, "%s `%s` must be `extern(C)` for `pragma(%s)` when taking parameters", funcdecl.kind, funcdecl.toPrettyChars, idStr.ptr);
|
|
if (funcdecl.isThis())
|
|
.error(funcdecl.loc, "%s `%s` cannot be a non-static member function for `pragma(%s)`", funcdecl.kind, funcdecl.toPrettyChars, idStr.ptr);
|
|
}
|
|
|
|
if (funcdecl.overnext && funcdecl.isCsymbol())
|
|
{
|
|
/* C does not allow function overloading, but it does allow
|
|
* redeclarations of the same function. If .overnext points
|
|
* to a redeclaration, ok. Error if it is an overload.
|
|
*/
|
|
auto fnext = funcdecl.overnext.isFuncDeclaration();
|
|
funcDeclarationSemantic(sc, fnext);
|
|
auto fn = fnext.type.isTypeFunction();
|
|
if (!fn || !cFuncEquivalence(f, fn))
|
|
{
|
|
.error(funcdecl.loc, "%s `%s` redeclaration with different type", funcdecl.kind, funcdecl.toPrettyChars);
|
|
//printf("t1: %s\n", f.toChars());
|
|
//printf("t2: %s\n", fn.toChars());
|
|
}
|
|
funcdecl.overnext = null; // don't overload the redeclarations
|
|
}
|
|
|
|
if ((funcdecl.storage_class & STC.auto_) && !f.isRef && !funcdecl.inferRetType)
|
|
.error(funcdecl.loc, "%s `%s` storage class `auto` has no effect if return type is not inferred", funcdecl.kind, funcdecl.toPrettyChars);
|
|
|
|
if (f.isReturn && !funcdecl.needThis() && !funcdecl.isNested())
|
|
{
|
|
/* Non-static nested functions have a hidden 'this' pointer to which
|
|
* the 'return' applies
|
|
*/
|
|
if (sc.scopesym && sc.scopesym.isAggregateDeclaration())
|
|
.error(funcdecl.loc, "%s `%s` `static` member has no `this` to which `return` can apply", funcdecl.kind, funcdecl.toPrettyChars);
|
|
else
|
|
error(funcdecl.loc, "top-level function `%s` has no `this` to which `return` can apply", funcdecl.toChars());
|
|
}
|
|
|
|
if (funcdecl.isAbstract() && !funcdecl.isVirtual())
|
|
{
|
|
const(char)* sfunc;
|
|
if (funcdecl.isStatic())
|
|
sfunc = "static";
|
|
else if (funcdecl.visibility.kind == Visibility.Kind.private_ || funcdecl.visibility.kind == Visibility.Kind.package_)
|
|
sfunc = visibilityToChars(funcdecl.visibility.kind);
|
|
else
|
|
sfunc = "final";
|
|
.error(funcdecl.loc, "%s `%s` `%s` functions cannot be `abstract`", funcdecl.kind, funcdecl.toPrettyChars, sfunc);
|
|
}
|
|
|
|
if (funcdecl.isOverride() && !funcdecl.isVirtual() && !funcdecl.isFuncLiteralDeclaration())
|
|
{
|
|
Visibility.Kind kind = funcdecl.visible().kind;
|
|
if ((kind == Visibility.Kind.private_ || kind == Visibility.Kind.package_) && funcdecl.isMember())
|
|
.error(funcdecl.loc, "%s `%s` `%s` method is not virtual and cannot override", funcdecl.kind, funcdecl.toPrettyChars, visibilityToChars(kind));
|
|
else
|
|
.error(funcdecl.loc, "%s `%s` cannot override a non-virtual function", funcdecl.kind, funcdecl.toPrettyChars);
|
|
}
|
|
|
|
if (funcdecl.isAbstract() && funcdecl.isFinalFunc())
|
|
.error(funcdecl.loc, "%s `%s` cannot be both `final` and `abstract`", funcdecl.kind, funcdecl.toPrettyChars);
|
|
|
|
if (funcdecl.printf || funcdecl.scanf)
|
|
{
|
|
checkPrintfScanfSignature(funcdecl, f, sc);
|
|
}
|
|
|
|
if (auto id = parent.isInterfaceDeclaration())
|
|
{
|
|
funcdecl.storage_class |= STC.abstract_;
|
|
if (funcdecl.isCtorDeclaration() || funcdecl.isPostBlitDeclaration() || funcdecl.isDtorDeclaration() || funcdecl.isInvariantDeclaration() || funcdecl.isNewDeclaration() || funcdecl.isDelete())
|
|
.error(funcdecl.loc, "%s `%s` constructors, destructors, postblits, invariants, new and delete functions are not allowed in interface `%s`", funcdecl.kind, funcdecl.toPrettyChars, id.toChars());
|
|
if (funcdecl.fbody && funcdecl.isVirtual())
|
|
.error(funcdecl.loc, "%s `%s` function body only allowed in `final` functions in interface `%s`", funcdecl.kind, funcdecl.toPrettyChars, id.toChars());
|
|
}
|
|
|
|
if (UnionDeclaration ud = parent.isUnionDeclaration())
|
|
{
|
|
if (funcdecl.isPostBlitDeclaration() || funcdecl.isDtorDeclaration() || funcdecl.isInvariantDeclaration())
|
|
.error(funcdecl.loc, "%s `%s` destructors, postblits and invariants are not allowed in union `%s`", funcdecl.kind, funcdecl.toPrettyChars, ud.toChars());
|
|
}
|
|
|
|
if (StructDeclaration sd = parent.isStructDeclaration())
|
|
{
|
|
if (funcdecl.isCtorDeclaration())
|
|
{
|
|
goto Ldone;
|
|
}
|
|
}
|
|
|
|
if (ClassDeclaration cd = parent.isClassDeclaration())
|
|
{
|
|
switch (classFuncSemantic(cd, funcdecl, parent, sc, f))
|
|
{
|
|
case 0: break;
|
|
case 1: goto Ldone;
|
|
case 2: return;
|
|
default: assert(0);
|
|
}
|
|
}
|
|
else if (funcdecl.isOverride() && !parent.isTemplateInstance())
|
|
.error(funcdecl.loc, "%s `%s` `override` only applies to class member functions", funcdecl.kind, funcdecl.toPrettyChars);
|
|
|
|
if (auto ti = parent.isTemplateInstance)
|
|
{
|
|
objc.setSelector(funcdecl, sc);
|
|
objc.setAsOptional(funcdecl, sc);
|
|
}
|
|
|
|
objc.validateSelector(funcdecl);
|
|
objc.validateOptional(funcdecl);
|
|
// Reflect this.type to f because it could be changed by findVtblIndex
|
|
f = funcdecl.type.toTypeFunction();
|
|
|
|
Ldone:
|
|
if (!funcdecl.fbody && !funcdecl.allowsContractWithoutBody())
|
|
.error(funcdecl.loc, "%s `%s` `in` and `out` contracts can only appear without a body when they are virtual interface functions or abstract", funcdecl.kind, funcdecl.toPrettyChars);
|
|
|
|
/* Do not allow template instances to add virtual functions
|
|
* to a class.
|
|
*/
|
|
if (funcdecl.isVirtual())
|
|
{
|
|
if (auto ti = parent.isTemplateInstance())
|
|
{
|
|
// Take care of nested templates
|
|
while (1)
|
|
{
|
|
TemplateInstance ti2 = ti.tempdecl.parent.isTemplateInstance();
|
|
if (!ti2)
|
|
break;
|
|
ti = ti2;
|
|
}
|
|
|
|
// If it's a member template
|
|
if (ClassDeclaration cd = ti.tempdecl.isClassMember())
|
|
{
|
|
.error(funcdecl.loc, "%s `%s` cannot use template to add virtual function to class `%s`", funcdecl.kind, funcdecl.toPrettyChars, cd.toChars());
|
|
}
|
|
}
|
|
}
|
|
|
|
funcdecl.checkMain(); // Check main() parameters and return type
|
|
|
|
/* Purity and safety can be inferred for some functions by examining
|
|
* the function body.
|
|
*/
|
|
if (funcdecl.canInferAttributes(sc))
|
|
funcdecl.initInferAttributes();
|
|
|
|
funcdecl.semanticRun = PASS.semanticdone;
|
|
|
|
/* Save scope for possible later use (if we need the
|
|
* function internals)
|
|
*/
|
|
funcdecl._scope = sc.copy();
|
|
funcdecl._scope.setNoFree();
|
|
|
|
__gshared bool printedMain = false; // semantic might run more than once
|
|
if (global.params.v.verbose && !printedMain)
|
|
{
|
|
const(char)* type = funcdecl.isMain() ? "main" : funcdecl.isWinMain() ? "winmain" : funcdecl.isDllMain() ? "dllmain" : cast(const(char)*)null;
|
|
Module mod = sc._module;
|
|
|
|
if (type && mod)
|
|
{
|
|
printedMain = true;
|
|
auto name = mod.srcfile.toChars();
|
|
auto path = FileName.searchPath(global.importPaths, name, true);
|
|
message("entry %-10s\t%s", type, path ? path : name);
|
|
}
|
|
}
|
|
|
|
if (funcdecl.fbody && sc._module.isRoot() &&
|
|
(funcdecl.isMain() || funcdecl.isWinMain() || funcdecl.isDllMain() || funcdecl.isCMain()))
|
|
global.hasMainFunction = true;
|
|
|
|
if (funcdecl.fbody && funcdecl.isMain() && sc._module.isRoot())
|
|
{
|
|
// check if `_d_cmain` is defined
|
|
bool cmainTemplateExists()
|
|
{
|
|
Dsymbol pscopesym;
|
|
auto rootSymbol = sc.search(funcdecl.loc, Id.empty, pscopesym);
|
|
if (auto moduleSymbol = rootSymbol.search(funcdecl.loc, Id.object))
|
|
if (moduleSymbol.search(funcdecl.loc, Id.CMain))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Only mixin `_d_cmain` if it is defined
|
|
if (cmainTemplateExists())
|
|
{
|
|
// add `mixin _d_cmain!();` to the declaring module
|
|
auto tqual = new TypeIdentifier(funcdecl.loc, Id.CMain);
|
|
auto tm = new TemplateMixin(funcdecl.loc, null, tqual, null);
|
|
sc._module.members.push(tm);
|
|
}
|
|
}
|
|
|
|
assert(funcdecl.type.ty != Terror || funcdecl.errors);
|
|
|
|
// semantic for parameters' UDAs
|
|
foreach (i, param; f.parameterList)
|
|
{
|
|
if (param && param.userAttribDecl)
|
|
param.userAttribDecl.dsymbolSemantic(sc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
Returns:
|
|
0 if semantic analysis in `funcDeclarationSemantic` should continue as normal
|
|
1 if it should skip over some analysis and `goto Ldone;`
|
|
2 if `funcDeclarationSemantic` should return early because of forward refernce error or
|
|
the derived class cd doesn't have its vtbl[] allocated yet
|
|
*/
|
|
private int classFuncSemantic(ClassDeclaration cd, FuncDeclaration funcdecl,
|
|
ref Dsymbol parent, Scope* sc, TypeFunction f)
|
|
{
|
|
parent = cd = objc.getParent(funcdecl, cd);
|
|
|
|
if (funcdecl.isCtorDeclaration())
|
|
{
|
|
return 1;
|
|
}
|
|
|
|
if (funcdecl.storage_class & STC.abstract_)
|
|
cd.isabstract = ThreeState.yes;
|
|
|
|
// if static function, do not put in vtbl[]
|
|
if (!funcdecl.isVirtual())
|
|
{
|
|
//printf("\tnot virtual\n");
|
|
return 1;
|
|
}
|
|
// Suppress further errors if the return type is an error
|
|
if (funcdecl.type.nextOf() == Type.terror)
|
|
return 1;
|
|
|
|
bool may_override = false;
|
|
for (size_t i = 0; i < cd.baseclasses.length; i++)
|
|
{
|
|
BaseClass* b = (*cd.baseclasses)[i];
|
|
ClassDeclaration cbd = b.type.toBasetype().isClassHandle();
|
|
if (!cbd)
|
|
continue;
|
|
for (size_t j = 0; j < cbd.vtbl.length; j++)
|
|
{
|
|
FuncDeclaration f2 = cbd.vtbl[j].isFuncDeclaration();
|
|
if (!f2 || f2.ident != funcdecl.ident)
|
|
continue;
|
|
if (cbd.parent && cbd.parent.isTemplateInstance())
|
|
{
|
|
if (!functionSemantic(f2))
|
|
return 1;
|
|
}
|
|
may_override = true;
|
|
}
|
|
}
|
|
if (may_override && funcdecl.type.nextOf() is null)
|
|
{
|
|
/* If same name function exists in base class but 'this' is auto return,
|
|
* cannot find index of base class's vtbl[] to override.
|
|
*/
|
|
.error(funcdecl.loc, "%s `%s` return type inference is not supported if may override base class function", funcdecl.kind, funcdecl.toPrettyChars);
|
|
}
|
|
|
|
/* Find index of existing function in base class's vtbl[] to override
|
|
* (the index will be the same as in cd's current vtbl[])
|
|
*/
|
|
int vi = cd.baseClass ? findVtblIndex(funcdecl, cd.baseClass.vtbl[]) : -1;
|
|
|
|
bool doesoverride = false;
|
|
switch (vi)
|
|
{
|
|
case -1:
|
|
Lintro:
|
|
/* Didn't find one, so
|
|
* This is an 'introducing' function which gets a new
|
|
* slot in the vtbl[].
|
|
*/
|
|
|
|
// Verify this doesn't override previous final function
|
|
if (cd.baseClass)
|
|
{
|
|
Dsymbol s = cd.baseClass.search(funcdecl.loc, funcdecl.ident);
|
|
if (s)
|
|
{
|
|
if (auto f2 = s.isFuncDeclaration())
|
|
{
|
|
f2 = f2.overloadExactMatch(funcdecl.type);
|
|
if (f2 && f2.isFinalFunc() && f2.visible().kind != Visibility.Kind.private_)
|
|
.error(funcdecl.loc, "%s `%s` cannot override `final` function `%s`", funcdecl.kind, funcdecl.toPrettyChars, f2.toPrettyChars());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* These quirky conditions mimic what happens when virtual
|
|
inheritance is implemented by producing a virtual base table
|
|
with offsets to each of the virtual bases.
|
|
*/
|
|
if (target.cpp.splitVBasetable && cd.classKind == ClassKind.cpp &&
|
|
cd.baseClass && cd.baseClass.vtbl.length)
|
|
{
|
|
/* if overriding an interface function, then this is not
|
|
* introducing and don't put it in the class vtbl[]
|
|
*/
|
|
funcdecl.interfaceVirtual = overrideInterface(funcdecl);
|
|
if (funcdecl.interfaceVirtual)
|
|
{
|
|
//printf("\tinterface function %s\n", toChars());
|
|
cd.vtblFinal.push(funcdecl);
|
|
goto Linterfaces;
|
|
}
|
|
}
|
|
|
|
if (funcdecl.isFinalFunc())
|
|
{
|
|
// Don't check here, as it may override an interface function
|
|
//if (isOverride())
|
|
// error("is marked as override, but does not override any function");
|
|
cd.vtblFinal.push(funcdecl);
|
|
}
|
|
else
|
|
{
|
|
//printf("\tintroducing function %s\n", funcdecl.toChars());
|
|
funcdecl.isIntroducing = true;
|
|
if (cd.classKind == ClassKind.cpp && target.cpp.reverseOverloads)
|
|
{
|
|
/* Overloaded functions with same name are grouped and in reverse order.
|
|
* Search for first function of overload group, and insert
|
|
* funcdecl into vtbl[] immediately before it.
|
|
*/
|
|
funcdecl.vtblIndex = cast(int)cd.vtbl.length;
|
|
bool found;
|
|
foreach (const i, s; cd.vtbl)
|
|
{
|
|
if (found)
|
|
// the rest get shifted forward
|
|
++s.isFuncDeclaration().vtblIndex;
|
|
else if (s.ident == funcdecl.ident && s.parent == parent)
|
|
{
|
|
// found first function of overload group
|
|
funcdecl.vtblIndex = cast(int)i;
|
|
found = true;
|
|
++s.isFuncDeclaration().vtblIndex;
|
|
}
|
|
}
|
|
cd.vtbl.insert(funcdecl.vtblIndex, funcdecl);
|
|
|
|
debug foreach (const i, s; cd.vtbl)
|
|
{
|
|
// a C++ dtor gets its vtblIndex later (and might even be added twice to the vtbl),
|
|
// e.g. when compiling druntime with a debug compiler, namely with core.stdcpp.exception.
|
|
if (auto fd = s.isFuncDeclaration())
|
|
assert(fd.vtblIndex == i ||
|
|
(cd.classKind == ClassKind.cpp && fd.isDtorDeclaration) ||
|
|
funcdecl.parent.isInterfaceDeclaration); // interface functions can be in multiple vtbls
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Append to end of vtbl[]
|
|
vi = cast(int)cd.vtbl.length;
|
|
cd.vtbl.push(funcdecl);
|
|
funcdecl.vtblIndex = vi;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case -2:
|
|
// can't determine because of forward references
|
|
funcdecl.errors = true;
|
|
return 2;
|
|
|
|
default:
|
|
{
|
|
if (vi >= cd.vtbl.length)
|
|
{
|
|
/* the derived class cd doesn't have its vtbl[] allocated yet.
|
|
* https://issues.dlang.org/show_bug.cgi?id=21008
|
|
*/
|
|
.error(funcdecl.loc, "%s `%s` circular reference to class `%s`", funcdecl.kind, funcdecl.toPrettyChars, cd.toChars());
|
|
funcdecl.errors = true;
|
|
return 2;
|
|
}
|
|
FuncDeclaration fdv = cd.baseClass.vtbl[vi].isFuncDeclaration();
|
|
FuncDeclaration fdc = cd.vtbl[vi].isFuncDeclaration();
|
|
// This function is covariant with fdv
|
|
|
|
if (fdc == funcdecl)
|
|
{
|
|
doesoverride = true;
|
|
break;
|
|
}
|
|
|
|
auto vtf = getFunctionType(fdv);
|
|
if (vtf.trust > TRUST.system && f.trust == TRUST.system)
|
|
.error(funcdecl.loc, "%s `%s` cannot override `@safe` method `%s` with a `@system` attribute", funcdecl.kind, funcdecl.toPrettyChars,
|
|
fdv.toPrettyChars);
|
|
|
|
if (fdc.toParent() == parent)
|
|
{
|
|
//printf("vi = %d,\tthis = %p %s %s @ [%s]\n\tfdc = %p %s %s @ [%s]\n\tfdv = %p %s %s @ [%s]\n",
|
|
// vi, this, this.toChars(), this.type.toChars(), this.loc.toChars(),
|
|
// fdc, fdc .toChars(), fdc .type.toChars(), fdc .loc.toChars(),
|
|
// fdv, fdv .toChars(), fdv .type.toChars(), fdv .loc.toChars());
|
|
|
|
// fdc overrides fdv exactly, then this introduces new function.
|
|
if (fdc.type.mod == fdv.type.mod && funcdecl.type.mod != fdv.type.mod)
|
|
goto Lintro;
|
|
}
|
|
|
|
if (fdv.isDeprecated && !funcdecl.isDeprecated)
|
|
deprecation(funcdecl.loc, "`%s` is overriding the deprecated method `%s`",
|
|
funcdecl.toPrettyChars, fdv.toPrettyChars);
|
|
|
|
// This function overrides fdv
|
|
if (fdv.isFinalFunc())
|
|
.error(funcdecl.loc, "%s `%s` cannot override `final` function `%s`", funcdecl.kind, funcdecl.toPrettyChars, fdv.toPrettyChars());
|
|
|
|
if (!funcdecl.isOverride())
|
|
{
|
|
if (fdv.isFuture())
|
|
{
|
|
deprecation(funcdecl.loc, "method `%s` implicitly overrides `@__future` base class method; rename the former",
|
|
funcdecl.toPrettyChars());
|
|
deprecationSupplemental(fdv.loc, "base method `%s` defined here",
|
|
fdv.toPrettyChars());
|
|
// Treat 'this' as an introducing function, giving it a separate hierarchy in the vtbl[]
|
|
goto Lintro;
|
|
}
|
|
else
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=17349
|
|
error(funcdecl.loc, "cannot implicitly override base class method `%s` with `%s`; add `override` attribute",
|
|
fdv.toPrettyChars(), funcdecl.toPrettyChars());
|
|
}
|
|
}
|
|
doesoverride = true;
|
|
if (fdc.toParent() == parent)
|
|
{
|
|
// If both are mixins, or both are not, then error.
|
|
// If either is not, the one that is not overrides the other.
|
|
bool thismixin = funcdecl.parent.isClassDeclaration() !is null;
|
|
bool fdcmixin = fdc.parent.isClassDeclaration() !is null;
|
|
if (thismixin == fdcmixin)
|
|
{
|
|
.error(funcdecl.loc, "%s `%s` multiple overrides of same function", funcdecl.kind, funcdecl.toPrettyChars);
|
|
}
|
|
/*
|
|
* https://issues.dlang.org/show_bug.cgi?id=711
|
|
*
|
|
* If an overriding method is introduced through a mixin,
|
|
* we need to update the vtbl so that both methods are
|
|
* present.
|
|
*/
|
|
else if (thismixin)
|
|
{
|
|
/* if the mixin introduced the overriding method, then reintroduce it
|
|
* in the vtbl. The initial entry for the mixined method
|
|
* will be updated at the end of the enclosing `if` block
|
|
* to point to the current (non-mixined) function.
|
|
*/
|
|
auto vitmp = cast(int)cd.vtbl.length;
|
|
cd.vtbl.push(fdc);
|
|
fdc.vtblIndex = vitmp;
|
|
}
|
|
else if (fdcmixin)
|
|
{
|
|
/* if the current overriding function is coming from a
|
|
* mixined block, then push the current function in the
|
|
* vtbl, but keep the previous (non-mixined) function as
|
|
* the overriding one.
|
|
*/
|
|
auto vitmp = cast(int)cd.vtbl.length;
|
|
cd.vtbl.push(funcdecl);
|
|
funcdecl.vtblIndex = vitmp;
|
|
break;
|
|
}
|
|
else // fdc overrides fdv
|
|
{
|
|
// this doesn't override any function
|
|
break;
|
|
}
|
|
}
|
|
cd.vtbl[vi] = funcdecl;
|
|
funcdecl.vtblIndex = vi;
|
|
|
|
/* Remember which functions this overrides
|
|
*/
|
|
funcdecl.foverrides.push(fdv);
|
|
|
|
/* This works by whenever this function is called,
|
|
* it actually returns tintro, which gets dynamically
|
|
* cast to type. But we know that tintro is a base
|
|
* of type, so we could optimize it by not doing a
|
|
* dynamic cast, but just subtracting the isBaseOf()
|
|
* offset if the value is != null.
|
|
*/
|
|
|
|
if (fdv.tintro)
|
|
funcdecl.tintro = fdv.tintro;
|
|
else if (!funcdecl.type.equals(fdv.type))
|
|
{
|
|
auto tnext = funcdecl.type.nextOf();
|
|
if (auto handle = tnext.isClassHandle())
|
|
{
|
|
if (handle.semanticRun < PASS.semanticdone && !handle.isBaseInfoComplete())
|
|
handle.dsymbolSemantic(null);
|
|
}
|
|
/* Only need to have a tintro if the vptr
|
|
* offsets differ
|
|
*/
|
|
int offset;
|
|
if (fdv.type.nextOf().isBaseOf(tnext, &offset))
|
|
{
|
|
funcdecl.tintro = fdv.type;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Go through all the interface bases.
|
|
* If this function is covariant with any members of those interface
|
|
* functions, set the tintro.
|
|
*/
|
|
Linterfaces:
|
|
bool foundVtblMatch = false;
|
|
|
|
for (ClassDeclaration bcd = cd; !foundVtblMatch && bcd; bcd = bcd.baseClass)
|
|
{
|
|
foreach (b; bcd.interfaces)
|
|
{
|
|
vi = findVtblIndex(funcdecl, b.sym.vtbl[]);
|
|
switch (vi)
|
|
{
|
|
case -1:
|
|
break;
|
|
|
|
case -2:
|
|
// can't determine because of forward references
|
|
funcdecl.errors = true;
|
|
return 2;
|
|
|
|
default:
|
|
{
|
|
auto fdv = cast(FuncDeclaration)b.sym.vtbl[vi];
|
|
Type ti = null;
|
|
|
|
foundVtblMatch = true;
|
|
|
|
/* Remember which functions this overrides
|
|
*/
|
|
funcdecl.foverrides.push(fdv);
|
|
|
|
if (fdv.tintro)
|
|
ti = fdv.tintro;
|
|
else if (!funcdecl.type.equals(fdv.type))
|
|
{
|
|
/* Only need to have a tintro if the vptr
|
|
* offsets differ
|
|
*/
|
|
int offset;
|
|
if (fdv.type.nextOf().isBaseOf(funcdecl.type.nextOf(), &offset))
|
|
{
|
|
ti = fdv.type;
|
|
}
|
|
}
|
|
if (ti)
|
|
{
|
|
if (funcdecl.tintro)
|
|
{
|
|
if (!funcdecl.tintro.nextOf().equals(ti.nextOf()) && !funcdecl.tintro.nextOf().isBaseOf(ti.nextOf(), null) && !ti.nextOf().isBaseOf(funcdecl.tintro.nextOf(), null))
|
|
{
|
|
.error(funcdecl.loc, "%s `%s` incompatible covariant types `%s` and `%s`", funcdecl.kind, funcdecl.toPrettyChars, funcdecl.tintro.toChars(), ti.toChars());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
funcdecl.tintro = ti;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (foundVtblMatch)
|
|
{
|
|
goto L2;
|
|
}
|
|
|
|
if (!doesoverride && funcdecl.isOverride() && (funcdecl.type.nextOf() || !may_override))
|
|
{
|
|
BaseClass* bc = null;
|
|
Dsymbol s = null;
|
|
for (size_t i = 0; i < cd.baseclasses.length; i++)
|
|
{
|
|
bc = (*cd.baseclasses)[i];
|
|
s = bc.sym.search_correct(funcdecl.ident);
|
|
if (s)
|
|
break;
|
|
}
|
|
|
|
if (s)
|
|
{
|
|
HdrGenState hgs;
|
|
OutBuffer buf;
|
|
|
|
auto fd = s.isFuncDeclaration();
|
|
functionToBufferFull(cast(TypeFunction)(funcdecl.type), buf,
|
|
new Identifier(funcdecl.toPrettyChars()), hgs, null);
|
|
const(char)* funcdeclToChars = buf.peekChars();
|
|
|
|
if (fd)
|
|
{
|
|
OutBuffer buf1;
|
|
|
|
if (fd.ident == funcdecl.ident)
|
|
hgs.fullQual = true;
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=23745
|
|
// If the potentially overridden function contains errors,
|
|
// inform the user to fix that one first
|
|
if (fd.errors)
|
|
{
|
|
error(funcdecl.loc, "function `%s` does not override any function, did you mean to override `%s`?",
|
|
funcdecl.toChars(), fd.toPrettyChars());
|
|
errorSupplemental(fd.loc, "Function `%s` contains errors in its declaration, therefore it cannot be correctly overridden",
|
|
fd.toPrettyChars());
|
|
}
|
|
else if (fd.isFinalFunc())
|
|
{
|
|
// When trying to override a final method, don't suggest it as a candidate(Issue #19613)
|
|
.error(funcdecl.loc, "%s `%s` does not override any function", funcdecl.kind, funcdecl.toPrettyChars);
|
|
|
|
// Look for a non-final method with the same name to suggest as an alternative
|
|
auto cdparent = fd.parent ? fd.parent.isClassDeclaration() : null;
|
|
if (cdparent)
|
|
{
|
|
Dsymbol nonFinalAlt = null;
|
|
|
|
auto overloadableSyms = cdparent.symtab.lookup(fd.ident);
|
|
if (overloadableSyms)
|
|
{
|
|
// Check each overload to find one that's not final
|
|
overloadApply(overloadableSyms, (Dsymbol s)
|
|
{
|
|
if (auto funcAlt = s.isFuncDeclaration())
|
|
{
|
|
if (funcAlt != fd && !funcAlt.isFinalFunc())
|
|
{
|
|
nonFinalAlt = funcAlt;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
});
|
|
|
|
// Provide a helpful suggestion if we found a viable alternative
|
|
if (nonFinalAlt)
|
|
{
|
|
auto funcAlt = nonFinalAlt.isFuncDeclaration();
|
|
OutBuffer buf2;
|
|
functionToBufferFull(cast(TypeFunction)(funcAlt.type), buf2,
|
|
new Identifier(funcAlt.toPrettyChars()), hgs, null);
|
|
errorSupplemental(funcdecl.loc, "Did you mean to override `%s`?", buf2.peekChars());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
functionToBufferFull(cast(TypeFunction)(fd.type), buf1,
|
|
new Identifier(fd.toPrettyChars()), hgs, null);
|
|
|
|
error(funcdecl.loc, "function `%s` does not override any function, did you mean to override `%s`?",
|
|
funcdeclToChars, buf1.peekChars());
|
|
|
|
// Supplemental error for parameter scope differences
|
|
auto tf1 = cast(TypeFunction)funcdecl.type;
|
|
auto tf2 = cast(TypeFunction)fd.type;
|
|
|
|
if (tf1 && tf2)
|
|
{
|
|
auto params1 = tf1.parameterList;
|
|
auto params2 = tf2.parameterList;
|
|
|
|
if (params1.length == params2.length)
|
|
{
|
|
bool hasScopeDifference = false;
|
|
|
|
for (size_t i = 0; i < params1.length; i++)
|
|
{
|
|
auto p1 = params1[i];
|
|
auto p2 = params2[i];
|
|
|
|
if ((p1.storageClass & STC.scope_) == (p2.storageClass & STC.scope_))
|
|
continue;
|
|
|
|
if (!(p2.storageClass & STC.scope_))
|
|
continue;
|
|
|
|
if (!hasScopeDifference)
|
|
{
|
|
// Intended signature
|
|
errorSupplemental(funcdecl.loc, "Did you intend to override:");
|
|
errorSupplemental(funcdecl.loc, "`%s`", buf1.peekChars());
|
|
hasScopeDifference = true;
|
|
}
|
|
errorSupplemental(funcdecl.loc, "Parameter %d is missing `scope`",
|
|
cast(int)(i + 1));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
error(funcdecl.loc, "function `%s` does not override any function, did you mean to override %s `%s`?",
|
|
funcdeclToChars, s.kind, s.toPrettyChars());
|
|
errorSupplemental(funcdecl.loc, "Functions are the only declarations that may be overridden");
|
|
}
|
|
}
|
|
else
|
|
.error(funcdecl.loc, "%s `%s` does not override any function", funcdecl.kind, funcdecl.toPrettyChars);
|
|
}
|
|
|
|
L2:
|
|
objc.setSelector(funcdecl, sc);
|
|
objc.checkLinkage(funcdecl);
|
|
objc.addToClassMethodList(funcdecl, cd);
|
|
objc.setAsOptional(funcdecl, sc);
|
|
|
|
/* Go through all the interface bases.
|
|
* Disallow overriding any final functions in the interface(s).
|
|
*/
|
|
foreach (b; cd.interfaces)
|
|
{
|
|
if (b.sym)
|
|
{
|
|
if (auto s = search_function(b.sym, funcdecl.ident))
|
|
{
|
|
if (auto f2 = s.isFuncDeclaration())
|
|
{
|
|
f2 = f2.overloadExactMatch(funcdecl.type);
|
|
if (f2 && f2.isFinalFunc() && f2.visible().kind != Visibility.Kind.private_)
|
|
.error(funcdecl.loc, "%s `%s` cannot override `final` function `%s.%s`", funcdecl.kind, funcdecl.toPrettyChars, b.sym.toChars(), f2.toPrettyChars());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (funcdecl.isOverride)
|
|
{
|
|
if (funcdecl.storage_class & STC.disable)
|
|
deprecation(funcdecl.loc,
|
|
"`%s` cannot be annotated with `@disable` because it is overriding a function in the base class",
|
|
funcdecl.toPrettyChars);
|
|
|
|
if (funcdecl.isDeprecated && !(funcdecl.foverrides.length && funcdecl.foverrides[0].isDeprecated))
|
|
deprecation(funcdecl.loc,
|
|
"`%s` cannot be marked as `deprecated` because it is overriding a function in the base class",
|
|
funcdecl.toPrettyChars);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private TypeFunction getFunctionType(FuncDeclaration fd)
|
|
{
|
|
if (auto tf = fd.type.isTypeFunction())
|
|
return tf;
|
|
|
|
if (!fd.type.isTypeError())
|
|
{
|
|
.error(fd.loc, "%s `%s` `%s` must be a function instead of `%s`", fd.kind, fd.toPrettyChars, fd.toChars(), fd.type.toChars());
|
|
fd.type = Type.terror;
|
|
}
|
|
fd.errors = true;
|
|
return null;
|
|
}
|
|
|
|
/*****************************************
|
|
* Initialize for inferring the attributes of this function.
|
|
*/
|
|
private void initInferAttributes(FuncDeclaration fd)
|
|
{
|
|
//printf("initInferAttributes() for %s (%s)\n", toPrettyChars(), ident.toChars());
|
|
TypeFunction tf = fd.type.toTypeFunction();
|
|
if (tf.purity == PURE.impure) // purity not specified
|
|
fd.purityInprocess = true;
|
|
|
|
if (tf.trust == TRUST.default_)
|
|
fd.safetyInprocess = true;
|
|
|
|
if (!tf.isNothrow)
|
|
fd.nothrowInprocess = true;
|
|
|
|
if (!tf.isNogc)
|
|
fd.nogcInprocess = true;
|
|
|
|
// Initialize for inferring STC.scope_
|
|
fd.scopeInprocess = true;
|
|
}
|
|
|
|
/****************************************************
|
|
* Resolve forward reference of function signature -
|
|
* parameter types, return type, and attributes.
|
|
* Params:
|
|
* fd = function declaration
|
|
* Returns:
|
|
* false if any errors exist in the signature.
|
|
*/
|
|
public
|
|
bool functionSemantic(FuncDeclaration fd)
|
|
{
|
|
//printf("functionSemantic() %p %s\n", this, toChars());
|
|
if (!fd._scope)
|
|
return !fd.errors;
|
|
|
|
fd.cppnamespace = fd._scope.namespace;
|
|
|
|
if (!fd.originalType) // semantic not yet run
|
|
{
|
|
TemplateInstance spec = fd.isSpeculative();
|
|
const olderrs = global.errors;
|
|
const oldgag = global.gag;
|
|
if (global.gag && !spec)
|
|
global.gag = 0;
|
|
dsymbolSemantic(fd, fd._scope);
|
|
global.gag = oldgag;
|
|
if (spec && global.errors != olderrs)
|
|
spec.errors = (global.errors - olderrs != 0);
|
|
if (olderrs != global.errors) // if errors compiling this function
|
|
return false;
|
|
}
|
|
|
|
// if inferring return type, sematic3 needs to be run
|
|
// - When the function body contains any errors, we cannot assume
|
|
// the inferred return type is valid.
|
|
// So, the body errors should become the function signature error.
|
|
if (fd.inferRetType && fd.type && !fd.type.nextOf())
|
|
return fd.functionSemantic3();
|
|
|
|
TemplateInstance ti;
|
|
if (fd.isInstantiated() && !fd.isVirtualMethod() &&
|
|
((ti = fd.parent.isTemplateInstance()) is null || ti.isTemplateMixin() || ti.tempdecl.ident == fd.ident))
|
|
{
|
|
AggregateDeclaration ad = fd.isMemberLocal();
|
|
if (ad && ad.sizeok != Sizeok.done)
|
|
{
|
|
/* Currently dmd cannot resolve forward references per methods,
|
|
* then setting SIZOKfwd is too conservative and would break existing code.
|
|
* So, just stop method attributes inference until ad.dsymbolSemantic() done.
|
|
*/
|
|
//ad.sizeok = Sizeok.fwd;
|
|
}
|
|
else
|
|
return fd.functionSemantic3() || !fd.errors;
|
|
}
|
|
|
|
if (fd.storage_class & STC.inference)
|
|
return fd.functionSemantic3() || !fd.errors;
|
|
|
|
return !fd.errors;
|
|
}
|
|
|
|
/****************************************************
|
|
* Resolve forward reference of function body.
|
|
* Returns false if any errors exist in the body.
|
|
*/
|
|
public
|
|
bool functionSemantic3(FuncDeclaration fd)
|
|
{
|
|
if (fd.semanticRun < PASS.semantic3 && fd._scope)
|
|
{
|
|
/* Forward reference - we need to run semantic3 on this function.
|
|
* If errors are gagged, and it's not part of a template instance,
|
|
* we need to temporarily ungag errors.
|
|
*/
|
|
TemplateInstance spec = fd.isSpeculative();
|
|
const olderrs = global.errors;
|
|
const oldgag = global.gag;
|
|
if (global.gag && !spec)
|
|
global.gag = 0;
|
|
semantic3(fd, fd._scope);
|
|
global.gag = oldgag;
|
|
|
|
// If it is a speculatively-instantiated template, and errors occur,
|
|
// we need to mark the template as having errors.
|
|
if (spec && global.errors != olderrs)
|
|
spec.errors = (global.errors - olderrs != 0);
|
|
if (olderrs != global.errors) // if errors compiling this function
|
|
return false;
|
|
}
|
|
|
|
return !fd.errors && !fd.hasSemantic3Errors();
|
|
}
|
|
|
|
// called from semantic3
|
|
/**
|
|
* Creates and returns the hidden parameters for this function declaration.
|
|
*
|
|
* Hidden parameters include the `this` parameter of a class, struct or
|
|
* nested function and the selector parameter for Objective-C methods.
|
|
*/
|
|
extern (D) void declareThis(FuncDeclaration fd, Scope* sc)
|
|
{
|
|
const bool dualCtx = (fd.toParent2() != fd.toParentLocal());
|
|
if (dualCtx)
|
|
fd.hasDualContext = true;
|
|
|
|
auto ad = fd.isThis();
|
|
if (!dualCtx && !ad && !fd.isNested())
|
|
{
|
|
fd.vthis = null;
|
|
fd.objc.selectorParameter = null;
|
|
return;
|
|
}
|
|
|
|
Type addModStc(Type t)
|
|
{
|
|
return t.addMod(fd.type.mod).addStorageClass(fd.storage_class);
|
|
}
|
|
|
|
if (dualCtx || fd.isNested())
|
|
{
|
|
/* The 'this' for a nested function is the link to the
|
|
* enclosing function's stack frame.
|
|
* Note that nested functions and member functions are disjoint.
|
|
*/
|
|
Type tthis = addModStc(dualCtx ?
|
|
Type.tvoidptr.sarrayOf(2).pointerTo() :
|
|
Type.tvoid.pointerTo());
|
|
fd.vthis = new VarDeclaration(fd.loc, tthis, dualCtx ? Id.this2 : Id.capture, null);
|
|
fd.vthis.storage_class |= STC.parameter | STC.nodtor;
|
|
}
|
|
else if (ad)
|
|
{
|
|
Type thandle = addModStc(ad.handleType());
|
|
fd.vthis = new ThisDeclaration(fd.loc, thandle);
|
|
fd.vthis.storage_class |= STC.parameter;
|
|
if (thandle.ty == Tstruct)
|
|
{
|
|
fd.vthis.storage_class |= STC.ref_;
|
|
}
|
|
}
|
|
|
|
if (auto tf = fd.type.isTypeFunction())
|
|
{
|
|
if (tf.isReturn)
|
|
fd.vthis.storage_class |= STC.return_;
|
|
if (tf.isScopeQual)
|
|
fd.vthis.storage_class |= STC.scope_;
|
|
if (tf.isReturnScope)
|
|
fd.vthis.storage_class |= STC.returnScope;
|
|
}
|
|
|
|
fd.vthis.dsymbolSemantic(sc);
|
|
if (!sc.insert(fd.vthis))
|
|
assert(0);
|
|
fd.vthis.parent = fd;
|
|
if (ad)
|
|
fd.objc.selectorParameter = .objc.createSelectorParameter(fd, sc);
|
|
}
|
|
|
|
/****************************************************
|
|
* Check that this function type is properly resolved.
|
|
* If not, report "forward reference error" and return true.
|
|
*/
|
|
extern (D) bool checkForwardRef(FuncDeclaration fd, Loc loc)
|
|
{
|
|
if (!functionSemantic(fd))
|
|
return true;
|
|
|
|
/* No deco means the functionSemantic() call could not resolve
|
|
* forward referenes in the type of this function.
|
|
*/
|
|
if (!fd.type.deco)
|
|
{
|
|
bool inSemantic3 = (fd.inferRetType && fd.semanticRun >= PASS.semantic3);
|
|
.error(loc, "forward reference to %s`%s`",
|
|
(inSemantic3 ? "inferred return type of function " : "").ptr,
|
|
fd.toChars());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*************************************************
|
|
* Find index of function in vtbl[0..length] that
|
|
* this function overrides.
|
|
* Prefer an exact match to a covariant one.
|
|
* Params:
|
|
* fd = function
|
|
* vtbl = vtable to use
|
|
* Returns:
|
|
* -1 didn't find one
|
|
* -2 can't determine because of forward references
|
|
*/
|
|
int findVtblIndex(FuncDeclaration fd, Dsymbol[] vtbl)
|
|
{
|
|
//printf("findVtblIndex() %s\n", toChars());
|
|
import dmd.typesem : covariant;
|
|
|
|
FuncDeclaration mismatch = null;
|
|
STC mismatchstc = STC.none;
|
|
int mismatchvi = -1;
|
|
int exactvi = -1;
|
|
int bestvi = -1;
|
|
for (int vi = 0; vi < cast(int)vtbl.length; vi++)
|
|
{
|
|
FuncDeclaration fdv = vtbl[vi].isFuncDeclaration();
|
|
if (!fdv || fdv.ident != fd.ident)
|
|
continue;
|
|
|
|
if (fd.type.equals(fdv.type)) // if exact match
|
|
{
|
|
if (fdv.parent.isClassDeclaration())
|
|
{
|
|
if (fdv.isFuture())
|
|
{
|
|
bestvi = vi;
|
|
continue; // keep looking
|
|
}
|
|
return vi; // no need to look further
|
|
}
|
|
|
|
if (exactvi >= 0)
|
|
{
|
|
.error(fd.loc, "%s `%s` cannot determine overridden function", fd.kind, fd.toPrettyChars);
|
|
return exactvi;
|
|
}
|
|
exactvi = vi;
|
|
bestvi = vi;
|
|
continue;
|
|
}
|
|
|
|
STC stc = STC.none;
|
|
const cov = fd.type.covariant(fdv.type, &stc);
|
|
//printf("\tbaseclass cov = %d\n", cov);
|
|
final switch (cov)
|
|
{
|
|
case Covariant.distinct:
|
|
// types are distinct
|
|
break;
|
|
|
|
case Covariant.yes:
|
|
bestvi = vi; // covariant, but not identical
|
|
break;
|
|
// keep looking for an exact match
|
|
|
|
case Covariant.no:
|
|
mismatchvi = vi;
|
|
mismatchstc = stc;
|
|
mismatch = fdv; // overrides, but is not covariant
|
|
break;
|
|
// keep looking for an exact match
|
|
|
|
case Covariant.fwdref:
|
|
return -2; // forward references
|
|
}
|
|
}
|
|
if (fd._linkage == LINK.cpp && bestvi != -1)
|
|
{
|
|
STC stc = STC.none;
|
|
FuncDeclaration fdv = vtbl[bestvi].isFuncDeclaration();
|
|
assert(fdv && fdv.ident == fd.ident);
|
|
if (fd.type.covariant(fdv.type, &stc, /*cppCovariant=*/true) == Covariant.no)
|
|
{
|
|
/* https://issues.dlang.org/show_bug.cgi?id=22351
|
|
* Under D rules, `type` and `fdv.type` are covariant, but under C++ rules, they are not.
|
|
* For now, continue to allow D covariant rules to apply when `override` has been used,
|
|
* but issue a deprecation warning that this behaviour will change in the future.
|
|
* Otherwise, follow the C++ covariant rules, which will create a new vtable entry.
|
|
*/
|
|
if (fd.isOverride())
|
|
{
|
|
/* @@@DEPRECATED_2.110@@@
|
|
* After deprecation period has ended, be sure to remove this entire `LINK.cpp` branch,
|
|
* but also the `cppCovariant` parameter from Type.covariant, and update the function
|
|
* so that both `LINK.cpp` covariant conditions within are always checked.
|
|
*/
|
|
.deprecation(fd.loc, "overriding `extern(C++)` function `%s%s` with `const` qualified function `%s%s%s` is deprecated",
|
|
fdv.toPrettyChars(), fdv.type.toTypeFunction().parameterList.parametersTypeToChars(),
|
|
fd.toPrettyChars(), fd.type.toTypeFunction().parameterList.parametersTypeToChars(), fd.type.modToChars());
|
|
|
|
const char* where = fd.type.isNaked() ? "parameters" : "type";
|
|
deprecationSupplemental(fd.loc, "Either remove `override`, or adjust the `const` qualifiers of the "
|
|
~ "overriding function %s", where);
|
|
}
|
|
else
|
|
{
|
|
// Treat as if Covariant.no
|
|
mismatchvi = bestvi;
|
|
mismatchstc = stc;
|
|
mismatch = fdv;
|
|
bestvi = -1;
|
|
}
|
|
}
|
|
}
|
|
if (bestvi == -1 && mismatch)
|
|
{
|
|
//type.print();
|
|
//mismatch.type.print();
|
|
//printf("%s %s\n", type.deco, mismatch.type.deco);
|
|
//printf("stc = %llx\n", mismatchstc);
|
|
if (mismatchstc)
|
|
{
|
|
// Fix it by modifying the type to add the storage classes
|
|
fd.type = fd.type.addStorageClass(mismatchstc);
|
|
bestvi = mismatchvi;
|
|
}
|
|
}
|
|
return bestvi;
|
|
}
|
|
|
|
/*********************************
|
|
* If function is a function in a base class,
|
|
* return that base class.
|
|
* Params:
|
|
* fd = function
|
|
* Returns:
|
|
* base class if overriding, null if not
|
|
*/
|
|
BaseClass* overrideInterface(FuncDeclaration fd)
|
|
{
|
|
for (ClassDeclaration cd = fd.toParent2().isClassDeclaration(); cd; cd = cd.baseClass)
|
|
{
|
|
foreach (b; cd.interfaces)
|
|
{
|
|
auto v = findVtblIndex(fd, b.sym.vtbl[]);
|
|
if (v >= 0)
|
|
return b;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Flag used by $(LREF resolveFuncCall).
|
|
enum FuncResolveFlag : ubyte
|
|
{
|
|
standard = 0, /// issue error messages, solve the call.
|
|
quiet = 1, /// do not issue error message on no match, just return `null`.
|
|
overloadOnly = 2, /// only resolve overloads, i.e. do not issue error on ambiguous
|
|
/// matches and need explicit this.
|
|
ufcs = 4, /// trying to resolve UFCS call
|
|
}
|
|
|
|
/*******************************************
|
|
* Given a symbol that could be either a FuncDeclaration or
|
|
* a function template, resolve it to a function symbol.
|
|
* Params:
|
|
* loc = instantiation location
|
|
* sc = instantiation scope
|
|
* s = instantiation symbol
|
|
* tiargs = initial list of template arguments
|
|
* tthis = if !NULL, the `this` argument type
|
|
* argumentList = arguments to function
|
|
* flags = see $(LREF FuncResolveFlag).
|
|
* Returns:
|
|
* if match is found, then function symbol, else null
|
|
*/
|
|
FuncDeclaration resolveFuncCall(Loc loc, Scope* sc, Dsymbol s,
|
|
Objects* tiargs, Type tthis, ArgumentList argumentList, FuncResolveFlag flags)
|
|
{
|
|
//printf("resolveFuncCall() %s\n", s.toChars());
|
|
auto fargs = argumentList.arguments;
|
|
if (!s)
|
|
return null; // no match
|
|
|
|
version (none)
|
|
{
|
|
printf("resolveFuncCall() %s)\n", s.toChars());
|
|
if (tthis)
|
|
printf("\tthis: %s\n", tthis.toChars());
|
|
if (fargs)
|
|
{
|
|
for (size_t i = 0; i < fargs.length; i++)
|
|
{
|
|
Expression arg = (*fargs)[i];
|
|
assert(arg.type);
|
|
printf("\t%s: %s\n", arg.toChars(), arg.type.toChars());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tiargs && arrayObjectIsError(*tiargs))
|
|
return null;
|
|
if (fargs !is null)
|
|
foreach (arg; *fargs)
|
|
if (isError(arg))
|
|
return null;
|
|
|
|
MatchAccumulator m;
|
|
functionResolve(m, s, loc, sc, tiargs, tthis, argumentList);
|
|
auto orig_s = s;
|
|
|
|
if (m.last > MATCH.nomatch && m.lastf)
|
|
{
|
|
if (m.count == 1) // exactly one match
|
|
{
|
|
if (!(flags & FuncResolveFlag.quiet))
|
|
functionSemantic(m.lastf);
|
|
return m.lastf;
|
|
}
|
|
if ((flags & FuncResolveFlag.overloadOnly) && !tthis && m.lastf.needThis())
|
|
{
|
|
return m.lastf;
|
|
}
|
|
}
|
|
|
|
/* Failed to find a best match.
|
|
* Do nothing or print error.
|
|
*/
|
|
if (m.last == MATCH.nomatch)
|
|
{
|
|
// error was caused on matched function, not on the matching itself,
|
|
// so return the function to produce a better diagnostic
|
|
if (m.count == 1)
|
|
return m.lastf;
|
|
}
|
|
|
|
// We are done at this point, as the rest of this function generate
|
|
// a diagnostic on invalid match
|
|
if (flags & FuncResolveFlag.quiet)
|
|
return null;
|
|
|
|
auto fd = s.isFuncDeclaration();
|
|
auto od = s.isOverDeclaration();
|
|
auto td = s.isTemplateDeclaration();
|
|
if (td && td.funcroot)
|
|
s = fd = td.funcroot;
|
|
|
|
OutBuffer tiargsBuf;
|
|
arrayObjectsToBuffer(tiargsBuf, tiargs);
|
|
|
|
OutBuffer fargsBuf;
|
|
fargsBuf.writeByte('(');
|
|
argExpTypesToCBuffer(fargsBuf, fargs);
|
|
fargsBuf.writeByte(')');
|
|
if (tthis)
|
|
tthis.modToBuffer(fargsBuf);
|
|
|
|
// The call is ambiguous
|
|
if (m.lastf && m.nextf)
|
|
{
|
|
TypeFunction tf1 = m.lastf.type.toTypeFunction();
|
|
TypeFunction tf2 = m.nextf.type.toTypeFunction();
|
|
const(char)* lastprms = parametersTypeToChars(tf1.parameterList);
|
|
const(char)* nextprms = parametersTypeToChars(tf2.parameterList);
|
|
|
|
string match = "";
|
|
final switch (m.last)
|
|
{
|
|
case MATCH.convert:
|
|
match = "after implicit conversions";
|
|
break;
|
|
case MATCH.constant:
|
|
match = "after qualifier conversion";
|
|
break;
|
|
case MATCH.exact:
|
|
match = "exactly";
|
|
break;
|
|
case MATCH.nomatch:
|
|
assert(0);
|
|
}
|
|
|
|
.error(loc, "`%s.%s` called with argument types `%s` matches multiple overloads %.*s:\n%s: `%s%s%s`\nand:\n%s: `%s%s%s`",
|
|
s.parent.toPrettyChars(), s.ident.toChars(),
|
|
fargsBuf.peekChars(),
|
|
match.fTuple.expand,
|
|
m.lastf.loc.toChars(), m.lastf.toPrettyChars(), lastprms, tf1.modToChars(),
|
|
m.nextf.loc.toChars(), m.nextf.toPrettyChars(), nextprms, tf2.modToChars());
|
|
return null;
|
|
}
|
|
|
|
// no match, generate an error messages
|
|
if (flags & FuncResolveFlag.ufcs)
|
|
{
|
|
auto arg = (*fargs)[0];
|
|
.error(loc, "no property `%s` for `%s` of type `%s`", s.ident.toChars(), arg.toChars(), arg.type.toChars());
|
|
.errorSupplemental(loc, "the following error occured while looking for a UFCS match");
|
|
}
|
|
|
|
if (!fd)
|
|
{
|
|
// all of overloads are templates
|
|
if (td)
|
|
{
|
|
if (!od && !td.overnext)
|
|
{
|
|
.error(loc, "%s `%s` is not callable using argument types `!(%s)%s`",
|
|
td.kind(), td.ident.toChars(), tiargsBuf.peekChars(), fargsBuf.peekChars());
|
|
}
|
|
else
|
|
{
|
|
.error(loc, "none of the overloads of %s `%s.%s` are callable using argument types `!(%s)%s`",
|
|
td.kind(), td.parent.toPrettyChars(), td.ident.toChars(),
|
|
tiargsBuf.peekChars(), fargsBuf.peekChars());
|
|
}
|
|
|
|
|
|
if (!global.gag || global.params.v.showGaggedErrors)
|
|
printCandidates(loc, td, sc.isDeprecated());
|
|
return null;
|
|
}
|
|
/* This case used to happen when several ctors are mixed in an agregate.
|
|
A (bad) error message is already generated in overloadApply().
|
|
see https://issues.dlang.org/show_bug.cgi?id=19729
|
|
and https://issues.dlang.org/show_bug.cgi?id=17259
|
|
*/
|
|
if (!od)
|
|
return null;
|
|
}
|
|
|
|
if (od)
|
|
{
|
|
.error(loc, "none of the overloads of `%s` are callable using argument types `!(%s)%s`",
|
|
od.ident.toChars(), tiargsBuf.peekChars(), fargsBuf.peekChars());
|
|
if (!global.gag || global.params.v.showGaggedErrors)
|
|
printCandidates(loc, od, sc.isDeprecated());
|
|
return null;
|
|
}
|
|
|
|
import dmd.expressionsem : checkDisabled;
|
|
// remove when deprecation period of class allocators and deallocators is over
|
|
if (fd.isNewDeclaration() && fd.checkDisabled(loc, sc))
|
|
return null;
|
|
|
|
bool hasOverloads = fd.overnext !is null;
|
|
auto tf = fd.type.isTypeFunction();
|
|
// if type is an error, the original type should be there for better diagnostics
|
|
if (!tf)
|
|
tf = fd.originalType.toTypeFunction();
|
|
|
|
// modifier mismatch
|
|
if (tthis && (fd.isCtorDeclaration() ?
|
|
!MODimplicitConv(tf.mod, tthis.mod) :
|
|
!MODimplicitConv(tthis.mod, tf.mod)))
|
|
{
|
|
OutBuffer thisBuf, funcBuf;
|
|
MODMatchToBuffer(&thisBuf, tthis.mod, tf.mod);
|
|
auto mismatches = MODMatchToBuffer(&funcBuf, tf.mod, tthis.mod);
|
|
if (hasOverloads)
|
|
{
|
|
OutBuffer buf;
|
|
buf.argExpTypesToCBuffer(fargs);
|
|
if (fd.isCtorDeclaration())
|
|
{
|
|
if (tthis.mod & MODFlags.immutable_)
|
|
.error(loc, "none of the overloads of `%s` can construct an immutable object with argument types `(%s)`. Expected `immutable(%s)`",
|
|
fd.toChars(), buf.peekChars(), buf.peekChars());
|
|
else
|
|
.error(loc, "none of the overloads of `%s` can construct a %sobject with argument types `(%s)`",
|
|
fd.toChars(), thisBuf.peekChars(), buf.peekChars());
|
|
}
|
|
else
|
|
.error(loc, "none of the overloads of `%s` are callable using a %sobject with argument types `(%s)`",
|
|
fd.toChars(), thisBuf.peekChars(), buf.peekChars());
|
|
|
|
if (!global.gag || global.params.v.showGaggedErrors)
|
|
printCandidates(loc, fd, sc.isDeprecated());
|
|
return null;
|
|
}
|
|
|
|
bool calledHelper;
|
|
void errorHelper(const(char)* failMessage) scope
|
|
{
|
|
.error(loc, "%s `%s%s%s` is not callable using argument types `%s`",
|
|
fd.kind(), fd.toPrettyChars(), parametersTypeToChars(tf.parameterList),
|
|
tf.modToChars(), fargsBuf.peekChars());
|
|
errorSupplemental(loc, failMessage);
|
|
calledHelper = true;
|
|
}
|
|
|
|
functionResolve(m, orig_s, loc, sc, tiargs, tthis, argumentList, &errorHelper);
|
|
if (calledHelper)
|
|
return null;
|
|
|
|
if (fd.isCtorDeclaration())
|
|
.error(loc, "%s%s `%s` cannot construct a %sobject",
|
|
funcBuf.peekChars(), fd.kind(), fd.toPrettyChars(), thisBuf.peekChars());
|
|
else
|
|
.error(loc, "%smethod `%s` is not callable using a %sobject",
|
|
funcBuf.peekChars(), fd.toPrettyChars(), thisBuf.peekChars());
|
|
|
|
if (mismatches.isNotShared)
|
|
.errorSupplemental(fd.loc, "Consider adding `shared` here");
|
|
else if (mismatches.isMutable)
|
|
.errorSupplemental(fd.loc, "Consider adding `const` or `inout` here");
|
|
return null;
|
|
}
|
|
|
|
//printf("tf = %s, args = %s\n", tf.deco, (*fargs)[0].type.deco);
|
|
if (hasOverloads)
|
|
{
|
|
.error(loc, "none of the overloads of `%s` are callable using argument types `%s`",
|
|
fd.toChars(), fargsBuf.peekChars());
|
|
if (!global.gag || global.params.v.showGaggedErrors)
|
|
printCandidates(loc, fd, sc.isDeprecated());
|
|
return null;
|
|
}
|
|
|
|
.error(loc, "%s `%s%s%s` is not callable using argument types `%s`",
|
|
fd.kind(), fd.toPrettyChars(), parametersTypeToChars(tf.parameterList),
|
|
tf.modToChars(), fargsBuf.peekChars());
|
|
|
|
if (global.gag && !global.params.v.showGaggedErrors)
|
|
return null;
|
|
|
|
// re-resolve to check for supplemental message
|
|
if (tthis)
|
|
{
|
|
if (auto classType = tthis.isTypeClass())
|
|
{
|
|
if (auto baseClass = classType.sym.baseClass)
|
|
{
|
|
if (auto baseFunction = baseClass.search(baseClass.loc, fd.ident))
|
|
{
|
|
MatchAccumulator mErr;
|
|
functionResolve(mErr, baseFunction, loc, sc, tiargs, baseClass.type, argumentList);
|
|
if (mErr.last > MATCH.nomatch && mErr.lastf)
|
|
{
|
|
errorSupplemental(loc, "Note: %s `%s` hides base class %s `%s`",
|
|
fd.kind, fd.toPrettyChars(),
|
|
mErr.lastf.kind, mErr.lastf.toPrettyChars());
|
|
|
|
if (!fd.isCtorDeclaration)
|
|
{
|
|
errorSupplemental(loc, "Add `alias %s = %s;` to `%s`'s body to merge the overload sets",
|
|
fd.toChars(), mErr.lastf.toPrettyChars(), tthis.toChars());
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void errorHelper2(const(char)* failMessage) scope
|
|
{
|
|
errorSupplemental(loc, failMessage);
|
|
}
|
|
|
|
functionResolve(m, orig_s, loc, sc, tiargs, tthis, argumentList, &errorHelper2);
|
|
|
|
return null;
|
|
}
|
|
|
|
/*******************************************
|
|
* Prints template and function overload candidates as supplemental errors.
|
|
* Params:
|
|
* loc = instantiation location
|
|
* declaration = the declaration to print overload candidates for
|
|
* showDeprecated = If `false`, `deprecated` function won't be shown
|
|
*/
|
|
private void printCandidates(Decl)(Loc loc, Decl declaration, bool showDeprecated)
|
|
{
|
|
// max num of overloads to print (-v or -verror-supplements overrides this).
|
|
const uint DisplayLimit = global.params.v.errorSupplementCount();
|
|
const(char)* constraintsTip;
|
|
|
|
int printed = 0; // number of candidates printed
|
|
int count = 0; // total candidates
|
|
bool child; // true if inside an eponymous template
|
|
const(char)* errorPrefix() @safe
|
|
{
|
|
if (child)
|
|
return " - Containing: ";
|
|
|
|
// align with blank spaces after first message
|
|
enum plural = "Candidates are: ";
|
|
enum spaces = " ";
|
|
if (printed)
|
|
return spaces;
|
|
|
|
return (count == 1) ? "Candidate is: " : plural;
|
|
}
|
|
bool matchSymbol(Dsymbol s, bool print)
|
|
{
|
|
if (auto fd = s.isFuncDeclaration())
|
|
{
|
|
// Don't print overloads which have errors.
|
|
// Not that if the whole overload set has errors, we'll never reach
|
|
// this point so there's no risk of printing no candidate
|
|
if (fd.errors || fd.type.ty == Terror)
|
|
return false;
|
|
// Don't print disabled functions, or `deprecated` outside of deprecated scope
|
|
if (fd.storage_class & STC.disable || (fd.isDeprecated() && !showDeprecated))
|
|
return false;
|
|
if (!print)
|
|
return true;
|
|
auto tf = cast(TypeFunction) fd.type;
|
|
OutBuffer buf;
|
|
buf.writestring(child ? fd.toChars() : fd.toPrettyChars());
|
|
buf.writestring(parametersTypeToChars(tf.parameterList));
|
|
if (tf.mod)
|
|
{
|
|
buf.writeByte(' ');
|
|
buf.MODtoBuffer(tf.mod);
|
|
}
|
|
.errorSupplemental(fd.loc, "%s`%s`", errorPrefix(), buf.peekChars());
|
|
}
|
|
else if (auto td = s.isTemplateDeclaration())
|
|
{
|
|
import dmd.staticcond;
|
|
|
|
if (!print)
|
|
return true;
|
|
|
|
// if td.onemember is a function, toCharsMaybeConstraints can print it
|
|
// without us recursing, otherwise we have to handle it.
|
|
// td.onemember may not have overloads set
|
|
// (see fail_compilation/onemember_overloads.d)
|
|
// assume if more than one member it is overloaded internally
|
|
bool recurse = td.onemember && (!td.onemember.isFuncDeclaration ||
|
|
td.members.length > 1);
|
|
OutBuffer buf;
|
|
HdrGenState hgs;
|
|
hgs.skipConstraints = true; // failing constraint should get printed below
|
|
hgs.showOneMember = !recurse;
|
|
toCharsMaybeConstraints(td, buf, hgs);
|
|
const tmsg = buf.peekChars();
|
|
const cmsg = child ? null : td.getConstraintEvalError(constraintsTip);
|
|
|
|
if (cmsg)
|
|
.errorSupplemental(td.loc, "%s`%s`\n%s", errorPrefix(), tmsg, cmsg);
|
|
else
|
|
.errorSupplemental(td.loc, "%s`%s`", errorPrefix(), tmsg);
|
|
|
|
if (recurse)
|
|
{
|
|
child = true;
|
|
foreach (d; *td.members)
|
|
{
|
|
if (d.ident != td.ident)
|
|
continue;
|
|
|
|
if (auto fd2 = d.isFuncDeclaration())
|
|
matchSymbol(fd2, print);
|
|
else if (auto td2 = d.isTemplateDeclaration())
|
|
matchSymbol(td2, print);
|
|
}
|
|
child = false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
// determine if there's > 1 candidate
|
|
overloadApply(declaration, (s) {
|
|
if (matchSymbol(s, false))
|
|
count++;
|
|
return count > 1;
|
|
});
|
|
int skipped = 0;
|
|
overloadApply(declaration, (s) {
|
|
if (global.params.v.verbose || printed < DisplayLimit)
|
|
{
|
|
if (matchSymbol(s, true))
|
|
printed++;
|
|
}
|
|
else
|
|
{
|
|
// Too many overloads to sensibly display.
|
|
// Just show count of remaining overloads.
|
|
if (matchSymbol(s, false))
|
|
skipped++;
|
|
}
|
|
return 0;
|
|
});
|
|
if (skipped > 0)
|
|
.errorSupplemental(loc, "... (%d more, -v to show) ...", skipped);
|
|
|
|
// Nothing was displayed, all overloads are either disabled or deprecated
|
|
if (!printed)
|
|
.errorSupplemental(loc, "All possible candidates are marked as `deprecated` or `@disable`");
|
|
// should be only in verbose mode
|
|
if (constraintsTip)
|
|
.tip(constraintsTip);
|
|
}
|
|
|
|
/********************************************************
|
|
* Generate Expression to call the invariant.
|
|
* Input:
|
|
* ad aggregate with the invariant
|
|
* vthis variable with 'this'
|
|
* Returns:
|
|
* void expression that calls the invariant
|
|
*/
|
|
Expression addInvariant(AggregateDeclaration ad, VarDeclaration vthis)
|
|
{
|
|
Expression e = null;
|
|
// Call invariant directly only if it exists
|
|
FuncDeclaration inv = ad.inv;
|
|
ClassDeclaration cd = ad.isClassDeclaration();
|
|
|
|
while (!inv && cd)
|
|
{
|
|
cd = cd.baseClass;
|
|
if (!cd)
|
|
break;
|
|
inv = cd.inv;
|
|
}
|
|
if (!inv)
|
|
return e;
|
|
|
|
version (all)
|
|
{
|
|
// Workaround for https://issues.dlang.org/show_bug.cgi?id=13394
|
|
// For the correct mangling,
|
|
// run attribute inference on inv if needed.
|
|
functionSemantic(inv);
|
|
}
|
|
|
|
//e = new DsymbolExp(Loc.initial, inv);
|
|
//e = new CallExp(Loc.initial, e);
|
|
//e = e.semantic(sc2);
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=13113
|
|
* Currently virtual invariant calls completely
|
|
* bypass attribute enforcement.
|
|
* Change the behavior of pre-invariant call by following it.
|
|
*/
|
|
e = new ThisExp(Loc.initial);
|
|
e.type = ad.type.addMod(vthis.type.mod);
|
|
e = new DotVarExp(Loc.initial, e, inv, false);
|
|
e.type = inv.type;
|
|
e = new CallExp(Loc.initial, e);
|
|
e.type = Type.tvoid;
|
|
return e;
|
|
}
|
|
|
|
/********************************************
|
|
* Find function in overload list that exactly matches t.
|
|
*/
|
|
FuncDeclaration overloadExactMatch(FuncDeclaration thisfd, Type t)
|
|
{
|
|
FuncDeclaration fd;
|
|
overloadApply(thisfd, (Dsymbol s)
|
|
{
|
|
auto f = s.isFuncDeclaration();
|
|
if (!f)
|
|
return 0;
|
|
if (f.storage_class & STC.disable)
|
|
return 0;
|
|
if (t.equals(f.type))
|
|
{
|
|
fd = f;
|
|
return 1;
|
|
}
|
|
/* Allow covariant matches, as long as the return type
|
|
* is just a const conversion.
|
|
* This allows things like pure functions to match with an impure function type.
|
|
*/
|
|
if (t.ty == Tfunction)
|
|
{
|
|
auto tf = cast(TypeFunction)f.type;
|
|
if (tf.covariant(t) == Covariant.yes &&
|
|
tf.nextOf().implicitConvTo(t.nextOf()) >= MATCH.constant)
|
|
{
|
|
fd = f;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
});
|
|
return fd;
|
|
}
|
|
|
|
/****************************************************
|
|
* Determine if fd1 overrides fd2.
|
|
* Return !=0 if it does.
|
|
*/
|
|
int overrides(FuncDeclaration fd1, FuncDeclaration fd2)
|
|
{
|
|
if (fd1.ident != fd2.ident)
|
|
return 0;
|
|
|
|
const cov = fd1.type.covariant(fd2.type);
|
|
if (cov == Covariant.distinct)
|
|
return 0;
|
|
|
|
ClassDeclaration cd1 = fd1.toParent().isClassDeclaration();
|
|
ClassDeclaration cd2 = fd2.toParent().isClassDeclaration();
|
|
|
|
if (cd1 && cd2 && cd2.isBaseOf(cd1, null))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/*************************************
|
|
* Determine partial specialization order of functions `f` vs `g`.
|
|
* This is very similar to TemplateDeclaration::leastAsSpecialized().
|
|
* Params:
|
|
* f = first function
|
|
* g = second function
|
|
* names = names of parameters
|
|
* Returns:
|
|
* match 'this' is at least as specialized as g
|
|
* 0 g is more specialized than 'this'
|
|
*/
|
|
MATCH leastAsSpecialized(FuncDeclaration f, FuncDeclaration g, Identifiers* names)
|
|
{
|
|
enum LOG_LEASTAS = 0;
|
|
static if (LOG_LEASTAS)
|
|
{
|
|
import core.stdc.stdio : printf;
|
|
printf("leastAsSpecialized(%s, %s, %s)\n", f.toChars(), g.toChars(), names ? names.toChars() : "null");
|
|
printf("%s, %s\n", f.type.toChars(), g.type.toChars());
|
|
}
|
|
|
|
/* This works by calling g() with f()'s parameters, and
|
|
* if that is possible, then f() is at least as specialized
|
|
* as g() is.
|
|
*/
|
|
|
|
TypeFunction tf = f.type.toTypeFunction();
|
|
TypeFunction tg = g.type.toTypeFunction();
|
|
|
|
/* If both functions have a 'this' pointer, and the mods are not
|
|
* the same and g's is not const, then this is less specialized.
|
|
*/
|
|
if (f.needThis() && g.needThis() && tf.mod != tg.mod)
|
|
{
|
|
if (f.isCtorDeclaration())
|
|
{
|
|
if (!MODimplicitConv(tg.mod, tf.mod))
|
|
return MATCH.nomatch;
|
|
}
|
|
else
|
|
{
|
|
if (!MODimplicitConv(tf.mod, tg.mod))
|
|
return MATCH.nomatch;
|
|
}
|
|
}
|
|
|
|
/* Create a dummy array of arguments out of the parameters to f()
|
|
*/
|
|
Expressions args;
|
|
foreach (u, p; tf.parameterList)
|
|
{
|
|
Expression e;
|
|
if (p.isReference())
|
|
{
|
|
e = new IdentifierExp(Loc.initial, p.ident);
|
|
e.type = p.type;
|
|
}
|
|
else
|
|
e = p.type.defaultInitLiteral(Loc.initial);
|
|
args.push(e);
|
|
}
|
|
|
|
MATCH m = callMatch(g, tg, null, ArgumentList(&args, names), 1);
|
|
if (m > MATCH.nomatch)
|
|
{
|
|
/* A variadic parameter list is less specialized than a
|
|
* non-variadic one.
|
|
*/
|
|
if (tf.parameterList.varargs && !tg.parameterList.varargs)
|
|
goto L1; // less specialized
|
|
|
|
static if (LOG_LEASTAS)
|
|
{
|
|
printf(" matches %d, so is least as specialized\n", m);
|
|
}
|
|
return m;
|
|
}
|
|
L1:
|
|
static if (LOG_LEASTAS)
|
|
{
|
|
printf(" doesn't match, so is not as specialized\n");
|
|
}
|
|
return MATCH.nomatch;
|
|
}
|
|
|
|
/********************************************
|
|
* Find function in overload list that matches to the 'this' modifier.
|
|
* There's four result types.
|
|
*
|
|
* 1. If the 'tthis' matches only one candidate, it's an "exact match".
|
|
* Returns the function and 'hasOverloads' is set to false.
|
|
* eg. If 'tthis" is mutable and there's only one mutable method.
|
|
* 2. If there's two or more match candidates, but a candidate function will be
|
|
* a "better match".
|
|
* Returns the better match function but 'hasOverloads' is set to true.
|
|
* eg. If 'tthis' is mutable, and there's both mutable and const methods,
|
|
* the mutable method will be a better match.
|
|
* 3. If there's two or more match candidates, but there's no better match,
|
|
* Returns null and 'hasOverloads' is set to true to represent "ambiguous match".
|
|
* eg. If 'tthis' is mutable, and there's two or more mutable methods.
|
|
* 4. If there's no candidates, it's "no match" and returns null with error report.
|
|
* e.g. If 'tthis' is const but there's no const methods.
|
|
*/
|
|
FuncDeclaration overloadModMatch(FuncDeclaration thisfd, Loc loc, Type tthis, ref bool hasOverloads)
|
|
{
|
|
//printf("FuncDeclaration::overloadModMatch('%s')\n", toChars());
|
|
MatchAccumulator m;
|
|
overloadApply(thisfd, (Dsymbol s)
|
|
{
|
|
auto f = s.isFuncDeclaration();
|
|
if (!f || f == m.lastf) // skip duplicates
|
|
return 0;
|
|
auto tf = f.type.toTypeFunction();
|
|
//printf("tf = %s\n", tf.toChars());
|
|
MATCH match;
|
|
int lastIsBetter()
|
|
{
|
|
//printf("\tlastbetter\n");
|
|
m.count++; // count up
|
|
return 0;
|
|
}
|
|
int currIsBetter()
|
|
{
|
|
//printf("\tisbetter\n");
|
|
if (m.last <= MATCH.convert)
|
|
{
|
|
// clear last secondary matching
|
|
m.nextf = null;
|
|
m.count = 0;
|
|
}
|
|
m.last = match;
|
|
m.lastf = f;
|
|
m.count++; // count up
|
|
return 0;
|
|
}
|
|
if (tthis) // non-static functions are preferred than static ones
|
|
{
|
|
if (f.needThis())
|
|
match = f.isCtorDeclaration() ? MATCH.exact : MODmethodConv(tthis.mod, tf.mod);
|
|
else
|
|
match = MATCH.constant; // keep static function in overload candidates
|
|
}
|
|
else // static functions are preferred than non-static ones
|
|
{
|
|
if (f.needThis())
|
|
match = MATCH.convert;
|
|
else
|
|
match = MATCH.exact;
|
|
}
|
|
if (match == MATCH.nomatch)
|
|
return 0;
|
|
if (match > m.last) return currIsBetter();
|
|
if (match < m.last) return lastIsBetter();
|
|
// See if one of the matches overrides the other.
|
|
if (m.lastf.overrides(f)) return lastIsBetter();
|
|
if (f.overrides(m.lastf)) return currIsBetter();
|
|
//printf("\tambiguous\n");
|
|
m.nextf = f;
|
|
m.count++;
|
|
return 0;
|
|
});
|
|
if (m.count == 1) // exact match
|
|
{
|
|
hasOverloads = false;
|
|
}
|
|
else if (m.count > 1) // better or ambiguous match
|
|
{
|
|
hasOverloads = true;
|
|
}
|
|
else // no match
|
|
{
|
|
hasOverloads = true;
|
|
auto tf = thisfd.type.toTypeFunction();
|
|
assert(tthis);
|
|
assert(!MODimplicitConv(tthis.mod, tf.mod)); // modifier mismatch
|
|
{
|
|
OutBuffer thisBuf, funcBuf;
|
|
MODMatchToBuffer(&thisBuf, tthis.mod, tf.mod);
|
|
MODMatchToBuffer(&funcBuf, tf.mod, tthis.mod);
|
|
.error(loc, "%smethod %s is not callable using a %sobject", thisfd.kind, thisfd.toPrettyChars,
|
|
funcBuf.peekChars(), thisfd.toPrettyChars(), thisBuf.peekChars());
|
|
}
|
|
}
|
|
return m.lastf;
|
|
}
|
|
|
|
/***********************************
|
|
* Determine lexical level difference from `fd` to nested function `target`.
|
|
* Issue error if `fd` cannot call `target`.
|
|
*
|
|
* Params:
|
|
* fd = function
|
|
* loc = location for error messages
|
|
* sc = context
|
|
* target = target of call
|
|
* decl = The `Declaration` that triggered this check.
|
|
* Used to provide a better error message only.
|
|
* Returns:
|
|
* 0 same level
|
|
* >0 decrease nesting by number
|
|
* -1 increase nesting by 1 (`target` is nested within 'fd')
|
|
* LevelError error
|
|
*/
|
|
int getLevelAndCheck(FuncDeclaration fd, Loc loc, Scope* sc, FuncDeclaration target,
|
|
Declaration decl)
|
|
{
|
|
int level = fd.getLevel(target, sc.intypeof);
|
|
if (level != fd.LevelError)
|
|
return level;
|
|
// Don't give error if in template constraint
|
|
if (!sc.inTemplateConstraint)
|
|
{
|
|
const(char)* xstatic = fd.isStatic() ? "`static` " : "";
|
|
// better diagnostics for static functions
|
|
.error(loc, "%s%s `%s` cannot access %s `%s` in frame of function `%s`",
|
|
xstatic, fd.kind(), fd.toPrettyChars(), decl.kind(), decl.toChars(),
|
|
target.toPrettyChars());
|
|
.errorSupplemental(decl.loc, "`%s` declared here", decl.toChars());
|
|
return fd.LevelError;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/**********************************
|
|
* Decide if attributes for this function can be inferred from examining
|
|
* the function body.
|
|
* Params:
|
|
* fd = function to infer attributes for
|
|
* sc = context
|
|
* Returns:
|
|
* true if can
|
|
*/
|
|
bool canInferAttributes(FuncDeclaration fd, Scope* sc)
|
|
{
|
|
if (!fd.fbody)
|
|
return false;
|
|
if (fd.isVirtualMethod() &&
|
|
/*
|
|
* https://issues.dlang.org/show_bug.cgi?id=21719
|
|
*
|
|
* If we have an auto virtual function we can infer
|
|
* the attributes.
|
|
*/
|
|
!(fd.inferRetType && !fd.isCtorDeclaration()))
|
|
return false; // since they may be overridden
|
|
if (sc.func &&
|
|
/********** this is for backwards compatibility for the moment ********/
|
|
(!fd.isMember() || sc.func.isSafeBypassingInference() && !fd.isInstantiated()))
|
|
return true;
|
|
if (fd.isFuncLiteralDeclaration() || // externs are not possible with literals
|
|
(fd.storage_class & STC.inference) || // do attribute inference
|
|
fd.isGenerated || // compiler generated function
|
|
(fd.inferRetType && !fd.isCtorDeclaration()))
|
|
return true;
|
|
if (fd.isInstantiated())
|
|
{
|
|
auto ti = fd.parent.isTemplateInstance();
|
|
if (ti is null || ti.isTemplateMixin() || ti.tempdecl.ident == fd.ident)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*********************************************
|
|
* In the current function 'sc.func', we are calling 'fd'.
|
|
* 1. Check to see if the current function can call 'fd' , issue error if not.
|
|
* 2. If the current function is not the parent of 'fd' , then add
|
|
* the current function to the list of siblings of 'fd' .
|
|
* 3. If the current function is a literal, and it's accessing an uplevel scope,
|
|
* then mark it as a delegate.
|
|
* Returns true if error occurs.
|
|
*/
|
|
bool checkNestedFuncReference(FuncDeclaration fd, Scope* sc, Loc loc)
|
|
{
|
|
//printf("FuncDeclaration::checkNestedFuncReference() %s\n", toPrettyChars());
|
|
if (auto fld = fd.isFuncLiteralDeclaration())
|
|
{
|
|
if (fld.tok == TOK.reserved)
|
|
{
|
|
fld.tok = TOK.function_;
|
|
fld.vthis = null;
|
|
}
|
|
}
|
|
if (!fd.parent || fd.parent == sc.parent)
|
|
return false;
|
|
if (fd.ident == Id.require || fd.ident == Id.ensure)
|
|
return false;
|
|
if (!fd.isThis() && !fd.isNested())
|
|
return false;
|
|
// The current function
|
|
FuncDeclaration fdthis = sc.parent.isFuncDeclaration();
|
|
if (!fdthis)
|
|
return false; // out of function scope
|
|
Dsymbol p = fd.toParentLocal();
|
|
Dsymbol p2 = fd.toParent2();
|
|
// Function literals from fdthis to p must be delegates
|
|
ensureStaticLinkTo(fdthis, p);
|
|
if (p != p2)
|
|
ensureStaticLinkTo(fdthis, p2);
|
|
if (!fd.isNested())
|
|
return false;
|
|
|
|
// The function that this function is in
|
|
bool checkEnclosing(FuncDeclaration fdv)
|
|
{
|
|
if (!fdv)
|
|
return false;
|
|
if (fdv == fdthis)
|
|
return false;
|
|
//printf("this = %s in [%s]\n", this.toChars(), this.loc.toChars());
|
|
//printf("fdv = %s in [%s]\n", fdv .toChars(), fdv .loc.toChars());
|
|
//printf("fdthis = %s in [%s]\n", fdthis.toChars(), fdthis.loc.toChars());
|
|
// Add this function to the list of those which called us
|
|
if (fdthis != fd)
|
|
{
|
|
bool found = false;
|
|
for (size_t i = 0; i < fd.siblingCallers.length; ++i)
|
|
{
|
|
if (fd.siblingCallers[i] == fdthis)
|
|
found = true;
|
|
}
|
|
if (!found)
|
|
{
|
|
//printf("\tadding sibling %s to %s\n", fdthis.toPrettyChars(), toPrettyChars());
|
|
if (!sc.intypeof && !sc.traitsCompiles)
|
|
{
|
|
fd.siblingCallers.push(fdthis);
|
|
fd.computedEscapingSiblings = false;
|
|
}
|
|
}
|
|
}
|
|
const lv = fdthis.getLevelAndCheck(loc, sc, fdv, fd);
|
|
if (lv == fd.LevelError)
|
|
return true; // error
|
|
if (lv == -1)
|
|
return false; // downlevel call
|
|
if (lv == 0)
|
|
return false; // same level call
|
|
return false; // Uplevel call
|
|
}
|
|
if (checkEnclosing(p.isFuncDeclaration()))
|
|
return true;
|
|
if (checkEnclosing(p == p2 ? null : p2.isFuncDeclaration()))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
/****************************************************
|
|
* Check whether result variable can be built.
|
|
* Returns:
|
|
* `true` if the function has a return type that
|
|
* is different from `void`.
|
|
*/
|
|
private bool canBuildResultVar(FuncDeclaration fd)
|
|
{
|
|
auto f = cast(TypeFunction)fd.type;
|
|
return f && f.nextOf() && f.nextOf().toBasetype().ty != Tvoid;
|
|
}
|
|
|
|
/****************************************************
|
|
* Declare result variable lazily.
|
|
*/
|
|
void buildResultVar(FuncDeclaration fd, Scope* sc, Type tret)
|
|
{
|
|
if (!fd.vresult)
|
|
{
|
|
Loc loc = fd.fensure ? fd.fensure.loc : fd.loc;
|
|
/* If inferRetType is true, tret may not be a correct return type yet.
|
|
* So, in here it may be a temporary type for vresult, and after
|
|
* fbody.dsymbolSemantic() running, vresult.type might be modified.
|
|
*/
|
|
fd.vresult = new VarDeclaration(loc, tret, Id.result, null);
|
|
fd.vresult.storage_class |= STC.nodtor | STC.temp;
|
|
if (!fd.isVirtual())
|
|
fd.vresult.storage_class |= STC.const_;
|
|
fd.vresult.storage_class |= STC.result;
|
|
// set before the semantic() for checkNestedReference()
|
|
fd.vresult.parent = fd;
|
|
}
|
|
if (sc && fd.vresult.semanticRun == PASS.initial)
|
|
{
|
|
TypeFunction tf = fd.type.toTypeFunction();
|
|
if (tf.isRef)
|
|
fd.vresult.storage_class |= STC.ref_;
|
|
fd.vresult.type = tret;
|
|
fd.vresult.dsymbolSemantic(sc);
|
|
if (!sc.insert(fd.vresult))
|
|
.error(fd.loc, "%s `%s` out result %s is already defined", fd.kind, fd.toPrettyChars, fd.vresult.toChars());
|
|
assert(fd.vresult.parent == fd);
|
|
}
|
|
}
|
|
|
|
/****************************************************
|
|
* Merge into this function the 'in' contracts of all it overrides.
|
|
* 'in's are OR'd together, i.e. only one of them needs to pass.
|
|
*/
|
|
Statement mergeFrequire(FuncDeclaration fd, Statement sf, Expressions* params)
|
|
{
|
|
/* If a base function and its override both have an IN contract, then
|
|
* only one of them needs to succeed. This is done by generating:
|
|
*
|
|
* void derived.in() {
|
|
* try {
|
|
* base.in();
|
|
* }
|
|
* catch () {
|
|
* ... body of derived.in() ...
|
|
* }
|
|
* }
|
|
*
|
|
* So if base.in() doesn't throw, derived.in() need not be executed, and the contract is valid.
|
|
* If base.in() throws, then derived.in()'s body is executed.
|
|
*/
|
|
foreach (fdv; fd.foverrides)
|
|
{
|
|
/* The semantic pass on the contracts of the overridden functions must
|
|
* be completed before code generation occurs.
|
|
* https://issues.dlang.org/show_bug.cgi?id=3602
|
|
*/
|
|
if (fdv.frequires && fdv.semanticRun != PASS.semantic3done)
|
|
{
|
|
assert(fdv._scope);
|
|
Scope* sc = fdv._scope.push();
|
|
sc.stc &= ~STC.override_;
|
|
fdv.semantic3(sc);
|
|
sc.pop();
|
|
}
|
|
sf = fdv.mergeFrequire(sf, params);
|
|
if (!sf || !fdv.fdrequire)
|
|
return null;
|
|
//printf("fdv.frequire: %s\n", fdv.frequire.toChars());
|
|
/* Make the call:
|
|
* try { __require(params); }
|
|
* catch (Throwable) { frequire; }
|
|
*/
|
|
params = Expression.arraySyntaxCopy(params);
|
|
Expression e = new CallExp(fd.loc, new VarExp(fd.loc, fdv.fdrequire, false), params);
|
|
Statement s2 = new ExpStatement(fd.loc, e);
|
|
auto c = new Catch(fd.loc, getThrowable(), null, sf);
|
|
c.internalCatch = true;
|
|
auto catches = new Catches();
|
|
catches.push(c);
|
|
sf = new TryCatchStatement(fd.loc, s2, catches);
|
|
}
|
|
return sf;
|
|
}
|
|
|
|
/****************************************************
|
|
* Merge into this function the 'in' contracts of all it overrides.
|
|
*/
|
|
Statement mergeFrequireInclusivePreview(FuncDeclaration fd, Statement sf, Expressions* params)
|
|
{
|
|
/* If a base function and its override both have an IN contract, then
|
|
* the override in contract must widen the guarantee of the base contract.
|
|
* This is checked by generating:
|
|
*
|
|
* void derived.in() {
|
|
* try {
|
|
* ... body of derived.in() ...
|
|
* }
|
|
* catch () {
|
|
* // derived in rejected this argument. so parent must also reject it, or we've tightened the contract.
|
|
* base.in();
|
|
* assert(false, "Logic error: " ~ thr.msg);
|
|
* }
|
|
* }
|
|
*/
|
|
foreach (fdv; fd.foverrides)
|
|
{
|
|
/* The semantic pass on the contracts of the overridden functions must
|
|
* be completed before code generation occurs.
|
|
* https://issues.dlang.org/show_bug.cgi?id=3602
|
|
*/
|
|
if (fdv.frequires && fdv.semanticRun != PASS.semantic3done)
|
|
{
|
|
assert(fdv._scope);
|
|
Scope* sc = fdv._scope.push();
|
|
sc.stc &= ~STC.override_;
|
|
fdv.semantic3(sc);
|
|
sc.pop();
|
|
}
|
|
sf = fdv.mergeFrequireInclusivePreview(sf, params);
|
|
if (!sf || !fdv.fdrequire)
|
|
return null;
|
|
|
|
const loc = fd.fdrequire.loc;
|
|
//printf("fdv.frequire: %s\n", fdv.frequire.toChars());
|
|
/* Make the call:
|
|
* try { frequire; }
|
|
* catch (Throwable thr) { __require(params); assert(false, "Logic error: " ~ thr.msg); }
|
|
*/
|
|
Identifier id = Identifier.generateId("thr");
|
|
params = Expression.arraySyntaxCopy(params);
|
|
Expression e = new CallExp(loc, new VarExp(loc, fdv.fdrequire, false), params);
|
|
Statement s2 = new ExpStatement(loc, e);
|
|
// assert(false, ...)
|
|
// TODO make this a runtime helper to allow:
|
|
// - chaining the original expression
|
|
// - nogc concatenation
|
|
Expression msg = new StringExp(loc, "Logic error: in-contract was tighter than parent in-contract");
|
|
Statement fail = new ExpStatement(loc, new AssertExp(loc, IntegerExp.literal!0, msg));
|
|
Statement s3 = new CompoundStatement(loc, s2, fail);
|
|
auto c = new Catch(loc, getThrowable(), id, s3);
|
|
c.internalCatch = true;
|
|
auto catches = new Catches();
|
|
catches.push(c);
|
|
sf = new TryCatchStatement(loc, sf, catches);
|
|
}
|
|
return sf;
|
|
}
|
|
|
|
/****************************************************
|
|
* Rewrite contracts as statements.
|
|
*/
|
|
void buildEnsureRequire(FuncDeclaration thisfd)
|
|
{
|
|
if (thisfd.frequires)
|
|
{
|
|
/* in { statements1... }
|
|
* in { statements2... }
|
|
* ...
|
|
* becomes:
|
|
* in { { statements1... } { statements2... } ... }
|
|
*/
|
|
assert(thisfd.frequires.length);
|
|
auto loc = (*thisfd.frequires)[0].loc;
|
|
auto s = new Statements;
|
|
foreach (r; *thisfd.frequires)
|
|
{
|
|
s.push(new ScopeStatement(r.loc, r, r.loc));
|
|
}
|
|
thisfd.frequire = new CompoundStatement(loc, s);
|
|
}
|
|
if (thisfd.fensures)
|
|
{
|
|
/* out(id1) { statements1... }
|
|
* out(id2) { statements2... }
|
|
* ...
|
|
* becomes:
|
|
* out(__result) { { ref id1 = __result; { statements1... } }
|
|
* { ref id2 = __result; { statements2... } } ... }
|
|
*/
|
|
assert(thisfd.fensures.length);
|
|
auto loc = (*thisfd.fensures)[0].ensure.loc;
|
|
auto s = new Statements;
|
|
foreach (r; *thisfd.fensures)
|
|
{
|
|
if (r.id && thisfd.canBuildResultVar())
|
|
{
|
|
auto rloc = r.ensure.loc;
|
|
auto resultId = new IdentifierExp(rloc, Id.result);
|
|
auto init = new ExpInitializer(rloc, resultId);
|
|
auto stc = STC.ref_ | STC.temp | STC.result;
|
|
auto decl = new VarDeclaration(rloc, null, r.id, init, stc);
|
|
auto sdecl = new ExpStatement(rloc, decl);
|
|
s.push(new ScopeStatement(rloc, new CompoundStatement(rloc, sdecl, r.ensure), rloc));
|
|
}
|
|
else
|
|
{
|
|
s.push(r.ensure);
|
|
}
|
|
}
|
|
thisfd.fensure = new CompoundStatement(loc, s);
|
|
}
|
|
if (!thisfd.isVirtual())
|
|
return;
|
|
/* Rewrite contracts as nested functions, then call them. Doing it as nested
|
|
* functions means that overriding functions can call them.
|
|
*/
|
|
auto f = cast(TypeFunction) thisfd.type;
|
|
/* Make a copy of the parameters and make them all ref */
|
|
static Parameters* toRefCopy(ParameterList parameterList)
|
|
{
|
|
auto result = new Parameters();
|
|
foreach (n, p; parameterList)
|
|
{
|
|
p = p.syntaxCopy();
|
|
if (!p.isLazy())
|
|
p.storageClass = (p.storageClass | STC.ref_) & ~STC.out_;
|
|
p.defaultArg = null; // won't be the same with ref
|
|
result.push(p);
|
|
}
|
|
return result;
|
|
}
|
|
if (thisfd.frequire)
|
|
{
|
|
/* in { ... }
|
|
* becomes:
|
|
* void __require(ref params) { ... }
|
|
* __require(params);
|
|
*/
|
|
Loc loc = thisfd.frequire.loc;
|
|
thisfd.fdrequireParams = new Expressions();
|
|
if (thisfd.parameters)
|
|
{
|
|
foreach (vd; *thisfd.parameters)
|
|
thisfd.fdrequireParams.push(new VarExp(loc, vd));
|
|
}
|
|
auto fo = cast(TypeFunction)(thisfd.originalType ? thisfd.originalType : f);
|
|
auto fparams = toRefCopy(fo.parameterList);
|
|
auto tf = new TypeFunction(ParameterList(fparams), Type.tvoid, LINK.d);
|
|
tf.isNothrow = f.isNothrow;
|
|
tf.isNogc = f.isNogc;
|
|
tf.purity = f.purity;
|
|
tf.trust = f.trust;
|
|
auto fd = new FuncDeclaration(loc, loc, Id.require, STC.none, tf);
|
|
fd.fbody = thisfd.frequire;
|
|
Statement s1 = new ExpStatement(loc, fd);
|
|
Expression e = new CallExp(loc, new VarExp(loc, fd, false), thisfd.fdrequireParams);
|
|
Statement s2 = new ExpStatement(loc, e);
|
|
thisfd.frequire = new CompoundStatement(loc, s1, s2);
|
|
thisfd.fdrequire = fd;
|
|
}
|
|
/* We need to set fdensureParams here and not in the block below to
|
|
* have the parameters available when calling a base class ensure(),
|
|
* even if this function doesn't have an out contract.
|
|
*/
|
|
thisfd.fdensureParams = new Expressions();
|
|
if (thisfd.canBuildResultVar())
|
|
thisfd.fdensureParams.push(new IdentifierExp(thisfd.loc, Id.result));
|
|
if (thisfd.parameters)
|
|
{
|
|
foreach (vd; *thisfd.parameters)
|
|
thisfd.fdensureParams.push(new VarExp(thisfd.loc, vd));
|
|
}
|
|
if (thisfd.fensure)
|
|
{
|
|
/* out (result) { ... }
|
|
* becomes:
|
|
* void __ensure(ref tret result, ref params) { ... }
|
|
* __ensure(result, params);
|
|
*/
|
|
Loc loc = thisfd.fensure.loc;
|
|
auto fparams = new Parameters();
|
|
if (thisfd.canBuildResultVar())
|
|
{
|
|
Parameter p = new Parameter(loc, STC.ref_ | STC.const_, f.nextOf(), Id.result, null, null);
|
|
fparams.push(p);
|
|
}
|
|
auto fo = cast(TypeFunction)(thisfd.originalType ? thisfd.originalType : f);
|
|
fparams.pushSlice((*toRefCopy(fo.parameterList))[]);
|
|
auto tf = new TypeFunction(ParameterList(fparams), Type.tvoid, LINK.d);
|
|
tf.isNothrow = f.isNothrow;
|
|
tf.isNogc = f.isNogc;
|
|
tf.purity = f.purity;
|
|
tf.trust = f.trust;
|
|
auto fd = new FuncDeclaration(loc, loc, Id.ensure, STC.none, tf);
|
|
fd.fbody = thisfd.fensure;
|
|
Statement s1 = new ExpStatement(loc, fd);
|
|
Expression e = new CallExp(loc, new VarExp(loc, fd, false), thisfd.fdensureParams);
|
|
Statement s2 = new ExpStatement(loc, e);
|
|
thisfd.fensure = new CompoundStatement(loc, s1, s2);
|
|
thisfd.fdensure = fd;
|
|
}
|
|
}
|
|
|
|
/****************************************************
|
|
* Determine whether an 'out' contract is declared inside
|
|
* the given function or any of its overrides.
|
|
* Params:
|
|
* fd = the function to search
|
|
* Returns:
|
|
* true found an 'out' contract
|
|
*/
|
|
bool needsFensure(FuncDeclaration fd) @safe
|
|
{
|
|
if (fd.fensures)
|
|
return true;
|
|
|
|
foreach (fdv; fd.foverrides)
|
|
{
|
|
if (needsFensure(fdv))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/****************************************************
|
|
* Merge into this function the 'out' contracts of all it overrides.
|
|
* 'out's are AND'd together, i.e. all of them need to pass.
|
|
*/
|
|
Statement mergeFensure(FuncDeclaration fd, Statement sf, Identifier oid, Expressions* params)
|
|
{
|
|
/* Same comments as for mergeFrequire(), except that we take care
|
|
* of generating a consistent reference to the 'result' local by
|
|
* explicitly passing 'result' to the nested function as a reference
|
|
* argument.
|
|
* This won't work for the 'this' parameter as it would require changing
|
|
* the semantic code for the nested function so that it looks on the parameter
|
|
* list for the 'this' pointer, something that would need an unknown amount
|
|
* of tweaking of various parts of the compiler that I'd rather leave alone.
|
|
*/
|
|
foreach (fdv; fd.foverrides)
|
|
{
|
|
/* The semantic pass on the contracts of the overridden functions must
|
|
* be completed before code generation occurs.
|
|
* https://issues.dlang.org/show_bug.cgi?id=3602 and
|
|
* https://issues.dlang.org/show_bug.cgi?id=5230
|
|
*/
|
|
if (needsFensure(fdv) && fdv.semanticRun != PASS.semantic3done)
|
|
{
|
|
assert(fdv._scope);
|
|
Scope* sc = fdv._scope.push();
|
|
sc.stc &= ~STC.override_;
|
|
fdv.semantic3(sc);
|
|
sc.pop();
|
|
}
|
|
sf = fdv.mergeFensure(sf, oid, params);
|
|
if (!fdv.fdensure)
|
|
continue;
|
|
|
|
//printf("fdv.fensure: %s\n", fdv.fensure.toChars());
|
|
// Make the call: __ensure(result, params)
|
|
params = Expression.arraySyntaxCopy(params);
|
|
if (fd.canBuildResultVar())
|
|
{
|
|
Type t1 = fdv.type.nextOf().toBasetype();
|
|
Type t2 = fd.type.nextOf().toBasetype();
|
|
if (t1.isBaseOf(t2, null))
|
|
{
|
|
/* Making temporary reference variable is necessary
|
|
* in covariant return.
|
|
* https://issues.dlang.org/show_bug.cgi?id=5204
|
|
* https://issues.dlang.org/show_bug.cgi?id=10479
|
|
*/
|
|
Expression* eresult = &(*params)[0];
|
|
auto ei = new ExpInitializer(Loc.initial, *eresult);
|
|
auto v = new VarDeclaration(Loc.initial, t1, Identifier.generateId("__covres"), ei);
|
|
v.storage_class |= STC.temp;
|
|
auto de = new DeclarationExp(Loc.initial, v);
|
|
auto ve = new VarExp(Loc.initial, v);
|
|
*eresult = new CommaExp(Loc.initial, de, ve);
|
|
}
|
|
}
|
|
Expression e = new CallExp(fd.loc, new VarExp(fd.loc, fdv.fdensure, false), params);
|
|
Statement s2 = new ExpStatement(fd.loc, e);
|
|
if (sf)
|
|
{
|
|
sf = new CompoundStatement(sf.loc, s2, sf);
|
|
}
|
|
else
|
|
sf = s2;
|
|
}
|
|
return sf;
|
|
}
|
|
|
|
/*******************************
|
|
* Modify all expression type of return statements to tret.
|
|
*
|
|
* On function literals, return type may be modified based on the context type
|
|
* after its semantic3 is done, in FuncExp::implicitCastTo.
|
|
*
|
|
* A function() dg = (){ return new B(); } // OK if is(B : A) == true
|
|
*
|
|
* If B to A conversion is convariant that requires offseet adjusting,
|
|
* all return statements should be adjusted to return expressions typed A.
|
|
*/
|
|
void modifyReturns(FuncLiteralDeclaration fld, Scope* sc, Type tret)
|
|
{
|
|
extern (C++) final class RetWalker : StatementRewriteWalker
|
|
{
|
|
alias visit = typeof(super).visit;
|
|
public:
|
|
Scope* sc;
|
|
Type tret;
|
|
FuncLiteralDeclaration fld;
|
|
override void visit(ReturnStatement s)
|
|
{
|
|
Expression exp = s.exp;
|
|
if (exp && !exp.type.equals(tret))
|
|
s.exp = exp.implicitCastTo(sc, tret);
|
|
}
|
|
}
|
|
if (fld.semanticRun < PASS.semantic3done)
|
|
return;
|
|
if (fld.fes)
|
|
return;
|
|
scope RetWalker w = new RetWalker();
|
|
w.sc = sc;
|
|
w.tret = tret;
|
|
w.fld = fld;
|
|
fld.fbody.accept(w);
|
|
// Also update the inferred function type to match the new return type.
|
|
// This is required so the code generator does not try to cast the
|
|
// modified returns back to the original type.
|
|
if (fld.inferRetType && fld.type.nextOf() != tret)
|
|
fld.type.toTypeFunction().next = tret;
|
|
}
|
|
|
|
/**************************************
|
|
* When a traits(compiles) is used on a function literal call
|
|
* we need to take into account if the body of the function
|
|
* violates any attributes, however, we must not affect the
|
|
* attribute inference on the outer function. The attributes
|
|
* of the function literal still need to be inferred, therefore
|
|
* we need a way to check for the scope that the traits compiles
|
|
* introduces.
|
|
*
|
|
* Params:
|
|
* sc = scope to be checked for
|
|
*
|
|
* Returns: `true` if the provided scope is the root
|
|
* of the traits compiles list of scopes.
|
|
*/
|
|
bool isRootTraitsCompilesScope(Scope* sc) @safe
|
|
{
|
|
return (sc.traitsCompiles) && !sc.func.skipCodegen;
|
|
}
|
|
|
|
/+
|
|
+ Checks the parameter and return types iff this is a `main` function.
|
|
+
|
|
+ The following signatures are allowed for a `D main`:
|
|
+ - Either no or a single parameter of type `string[]`
|
|
+ - Return type is either `void`, `int` or `noreturn`
|
|
+
|
|
+ The following signatures are standard C:
|
|
+ - `int main()`
|
|
+ - `int main(int, char**)`
|
|
+
|
|
+ This function accepts the following non-standard extensions:
|
|
+ - `char** envp` as a third parameter
|
|
+ - `void` / `noreturn` as return type
|
|
+
|
|
+ This function will issue errors for unexpected arguments / return types.
|
|
+/
|
|
extern (D) void checkMain(FuncDeclaration fd)
|
|
{
|
|
if (fd.ident != Id.main || fd.isMember() || fd.isNested())
|
|
return; // Not a main function
|
|
|
|
TypeFunction tf = fd.type.toTypeFunction();
|
|
|
|
Type retType = tf.nextOf();
|
|
if (!retType)
|
|
{
|
|
// auto main(), check after semantic
|
|
assert(fd.inferRetType);
|
|
return;
|
|
}
|
|
|
|
/// Checks whether `t` is equivalent to `char**`
|
|
/// Ignores qualifiers and treats enums according to their base type
|
|
static bool isCharPtrPtr(Type t)
|
|
{
|
|
auto tp = t.toBasetype().isTypePointer();
|
|
if (!tp)
|
|
return false;
|
|
|
|
tp = tp.next.toBasetype().isTypePointer();
|
|
if (!tp)
|
|
return false;
|
|
|
|
return tp.next.toBasetype().ty == Tchar;
|
|
}
|
|
|
|
// Neither of these qualifiers is allowed because they affect the ABI
|
|
enum invalidSTC = STC.out_ | STC.ref_ | STC.lazy_;
|
|
|
|
const nparams = tf.parameterList.length;
|
|
bool argerr;
|
|
|
|
const linkage = fd.resolvedLinkage();
|
|
if (linkage == LINK.d)
|
|
{
|
|
if (nparams == 1)
|
|
{
|
|
auto fparam0 = tf.parameterList[0];
|
|
auto t = fparam0.type.toBasetype();
|
|
if (t.ty != Tarray ||
|
|
t.nextOf().ty != Tarray ||
|
|
t.nextOf().nextOf().ty != Tchar ||
|
|
fparam0.storageClass & invalidSTC)
|
|
{
|
|
argerr = true;
|
|
}
|
|
}
|
|
|
|
if (tf.parameterList.varargs || nparams >= 2 || argerr)
|
|
.error(fd.loc, "%s `%s` parameter list must be empty or accept one parameter of type `string[]`", fd.kind, fd.toPrettyChars);
|
|
}
|
|
|
|
else if (linkage == LINK.c)
|
|
{
|
|
if (nparams == 2 || nparams == 3)
|
|
{
|
|
// Argument count must be int
|
|
auto argCount = tf.parameterList[0];
|
|
argerr |= !!(argCount.storageClass & invalidSTC);
|
|
argerr |= argCount.type.toBasetype().ty != Tint32;
|
|
|
|
// Argument pointer must be char**
|
|
auto argPtr = tf.parameterList[1];
|
|
argerr |= !!(argPtr.storageClass & invalidSTC);
|
|
argerr |= !isCharPtrPtr(argPtr.type);
|
|
|
|
// `char** environ` is a common extension, see J.5.1 of the C standard
|
|
if (nparams == 3)
|
|
{
|
|
auto envPtr = tf.parameterList[2];
|
|
argerr |= !!(envPtr.storageClass & invalidSTC);
|
|
argerr |= !isCharPtrPtr(envPtr.type);
|
|
}
|
|
}
|
|
else
|
|
argerr = nparams != 0;
|
|
|
|
// Disallow variadic main() - except for K&R declarations in C files.
|
|
// E.g. int main(), int main(argc, argv) int argc, char** argc { ... }
|
|
if (tf.parameterList.varargs && (!fd.isCsymbol() || (!tf.parameterList.hasIdentifierList && nparams)))
|
|
argerr |= true;
|
|
|
|
if (argerr)
|
|
{
|
|
.error(fd.loc, "%s `%s` parameters must match one of the following signatures", fd.kind, fd.toPrettyChars);
|
|
fd.loc.errorSupplemental("`main()`");
|
|
fd.loc.errorSupplemental("`main(int argc, char** argv)`");
|
|
fd.loc.errorSupplemental("`main(int argc, char** argv, char** environ)` [POSIX extension]");
|
|
}
|
|
}
|
|
else
|
|
return; // Neither C nor D main, ignore (should probably be an error)
|
|
|
|
// Allow enums with appropriate base types (same ABI)
|
|
retType = retType.toBasetype();
|
|
|
|
if (retType.ty != Tint32 && retType.ty != Tvoid && retType.ty != Tnoreturn)
|
|
.error(fd.loc, "%s `%s` must return `int`, `void` or `noreturn`, not `%s`", fd.kind, fd.toPrettyChars, tf.nextOf().toChars());
|
|
}
|
|
|
|
/***********************************************
|
|
* Check all return statements for a function to verify that returning
|
|
* using NRVO is possible.
|
|
*
|
|
* Returns:
|
|
* `false` if the result cannot be returned by hidden reference.
|
|
*/
|
|
extern (D) bool checkNRVO(FuncDeclaration fd)
|
|
{
|
|
//printf("checkNRVO*() %s\n", fd.ident.toChars());
|
|
if (!fd.isNRVO() || fd.returns is null)
|
|
return false;
|
|
|
|
auto tf = fd.type.toTypeFunction();
|
|
if (tf.isRef)
|
|
return false;
|
|
|
|
foreach (rs; *fd.returns)
|
|
{
|
|
if (auto ve = rs.exp.isVarExp())
|
|
{
|
|
auto v = ve.var.isVarDeclaration();
|
|
if (!v || v.isReference())
|
|
return false;
|
|
if (fd.nrvo_var is null)
|
|
{
|
|
// Variables in the data segment (e.g. globals, TLS or not),
|
|
// parameters and closure variables cannot be NRVOed.
|
|
if (v.isDataseg() || v.isParameter() || v.toParent2() != fd)
|
|
return false;
|
|
if (v.nestedrefs.length && fd.needsClosure())
|
|
return false;
|
|
// don't know if the return storage is aligned
|
|
version (MARS)
|
|
{
|
|
if (fd.alignSectionVars && (*fd.alignSectionVars).contains(v))
|
|
return false;
|
|
}
|
|
// The variable type needs to be equivalent to the return type.
|
|
if (!v.type.equivalent(tf.next))
|
|
return false;
|
|
//printf("Setting nrvo to %s\n", v.toChars());
|
|
fd.nrvo_var = v;
|
|
}
|
|
else if (fd.nrvo_var != v)
|
|
return false;
|
|
}
|
|
else //if (!exp.isLvalue()) // keep NRVO-ability
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**************************************
|
|
* The function is doing something impure, so mark it as impure.
|
|
*
|
|
* Params:
|
|
* fd = function declaration to mark
|
|
* loc = location of impure action
|
|
* fmt = format string for error message
|
|
* args = argument to format string
|
|
*
|
|
* Returns: `true` if there's a purity error
|
|
*/
|
|
extern (D) bool setImpure(FuncDeclaration fd, Loc loc, const(char)* fmt, RootObject[] args...)
|
|
{
|
|
if (fd.purityInprocess)
|
|
{
|
|
fd.purityInprocess = false;
|
|
if (fmt)
|
|
fd.pureViolation = new AttributeViolation(loc, fmt, args); // impure action
|
|
else if (args.length > 0)
|
|
{
|
|
if (auto sa = args[0].isDsymbol())
|
|
{
|
|
if (FuncDeclaration fd2 = sa.isFuncDeclaration())
|
|
{
|
|
fd.pureViolation = new AttributeViolation(loc, fd2); // call to impure function
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fd.fes)
|
|
fd.fes.func.setImpure(loc, fmt, args);
|
|
}
|
|
else if (fd.isPure())
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
PURE isPure(FuncDeclaration fd)
|
|
{
|
|
//printf("FuncDeclaration::isPure() '%s'\n", toChars());
|
|
|
|
|
|
TypeFunction tf = fd.type.toTypeFunction();
|
|
if (fd.purityInprocess)
|
|
fd.setImpure(Loc.initial, null);
|
|
if (tf.purity == PURE.fwdref)
|
|
tf.purityLevel();
|
|
PURE purity = tf.purity;
|
|
if (purity > PURE.weak && fd.isNested())
|
|
purity = PURE.weak;
|
|
if (purity > PURE.weak && fd.needThis())
|
|
{
|
|
// The attribute of the 'this' reference affects purity strength
|
|
if (fd.type.mod & MODFlags.immutable_)
|
|
{
|
|
}
|
|
else if (fd.type.mod & (MODFlags.const_ | MODFlags.wild) && purity >= PURE.const_)
|
|
purity = PURE.const_;
|
|
else
|
|
purity = PURE.weak;
|
|
}
|
|
tf.purity = purity;
|
|
// ^ This rely on the current situation that every FuncDeclaration has a
|
|
// unique TypeFunction.
|
|
return purity;
|
|
}
|
|
|
|
extern (D) PURE isPureBypassingInference(FuncDeclaration fd)
|
|
{
|
|
if (fd.purityInprocess)
|
|
return PURE.fwdref;
|
|
else
|
|
return fd.isPure();
|
|
}
|
|
|
|
/**************************************
|
|
* Performs type-based alias analysis between a newly created value and a pre-
|
|
* existing memory reference:
|
|
*
|
|
* Assuming that a reference A to a value of type `ta` was available to the code
|
|
* that created a reference B to a value of type `tb`, it returns whether B
|
|
* might alias memory reachable from A based on the types involved (either
|
|
* directly or via any number of indirections in either A or B).
|
|
*
|
|
* This relation is not symmetric in the two arguments. For example, a
|
|
* a `const(int)` reference can point to a pre-existing `int`, but not the other
|
|
* way round.
|
|
*
|
|
* Examples:
|
|
*
|
|
* ta, tb, result
|
|
* `const(int)`, `int`, `false`
|
|
* `int`, `const(int)`, `true`
|
|
* `int`, `immutable(int)`, `false`
|
|
* const(immutable(int)*), immutable(int)*, false // BUG: returns true
|
|
*
|
|
* Params:
|
|
* ta = value type being referred to
|
|
* tb = referred to value type that could be constructed from ta
|
|
*
|
|
* Returns:
|
|
* true if reference to `tb` is isolated from reference to `ta`
|
|
*/
|
|
bool traverseIndirections(Type ta, Type tb)
|
|
{
|
|
//printf("traverseIndirections(%s, %s)\n", ta.toChars(), tb.toChars());
|
|
|
|
static bool traverse(Type ta, Type tb, ref scope AssocArray!(const(char)*, bool) table, bool reversePass)
|
|
{
|
|
//printf("traverse(%s, %s)\n", ta.toChars(), tb.toChars());
|
|
ta = ta.baseElemOf();
|
|
tb = tb.baseElemOf();
|
|
|
|
// First, check if the pointed-to types are convertible to each other such
|
|
// that they might alias directly.
|
|
static bool mayAliasDirect(Type source, Type target)
|
|
{
|
|
return
|
|
// if source is the same as target or can be const-converted to target
|
|
source.constConv(target) != MATCH.nomatch ||
|
|
// if target is void and source can be const-converted to target
|
|
(target.ty == Tvoid && MODimplicitConv(source.mod, target.mod));
|
|
}
|
|
|
|
if (mayAliasDirect(reversePass ? tb : ta, reversePass ? ta : tb))
|
|
{
|
|
//printf(" true mayalias %s %s %d\n", ta.toChars(), tb.toChars(), reversePass);
|
|
return false;
|
|
}
|
|
if (ta.nextOf() && ta.nextOf() == tb.nextOf())
|
|
{
|
|
//printf(" next==next %s %s %d\n", ta.toChars(), tb.toChars(), reversePass);
|
|
return true;
|
|
}
|
|
|
|
if (tb.ty == Tclass || tb.ty == Tstruct)
|
|
{
|
|
/* Traverse the type of each field of the aggregate
|
|
*/
|
|
bool* found = table.getLvalue(tb.deco);
|
|
if (*found == true)
|
|
return true; // We have already seen this symbol, break the cycle
|
|
else
|
|
*found = true;
|
|
|
|
AggregateDeclaration sym = tb.toDsymbol(null).isAggregateDeclaration();
|
|
foreach (v; sym.fields)
|
|
{
|
|
Type tprmi = v.type.addMod(tb.mod);
|
|
//printf("\ttb = %s, tprmi = %s\n", tb.toChars(), tprmi.toChars());
|
|
if (!traverse(ta, tprmi, table, reversePass))
|
|
return false;
|
|
}
|
|
}
|
|
else if (tb.ty == Tarray || tb.ty == Taarray || tb.ty == Tpointer)
|
|
{
|
|
Type tind = tb.nextOf();
|
|
if (!traverse(ta, tind, table, reversePass))
|
|
return false;
|
|
}
|
|
else if (tb.hasPointers())
|
|
{
|
|
// BUG: consider the context pointer of delegate types
|
|
return false;
|
|
}
|
|
|
|
// Still no match, so try breaking up ta if we have not done so yet.
|
|
if (!reversePass)
|
|
{
|
|
scope newTable = AssocArray!(const(char)*, bool)();
|
|
return traverse(tb, ta, newTable, true);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// To handle arbitrary levels of indirections in both parameters, we
|
|
// recursively descend into aggregate members/levels of indirection in both
|
|
// `ta` and `tb` while avoiding cycles. Start with the original types.
|
|
scope table = AssocArray!(const(char)*, bool)();
|
|
const result = traverse(ta, tb, table, false);
|
|
//printf(" returns %d\n", result);
|
|
return result;
|
|
}
|
|
|
|
/********************************************
|
|
* Params:
|
|
* fd = function declaration to check
|
|
* t = type of object to test one level of indirection down
|
|
* Returns:
|
|
* true if an object typed `t` has no indirections
|
|
* which could have come from the function's parameters, mutable
|
|
* globals, or uplevel functions.
|
|
*/
|
|
bool isTypeIsolatedIndirect(FuncDeclaration fd, Type t)
|
|
{
|
|
//printf("isTypeIsolatedIndirect(t: %s)\n", t.toChars());
|
|
assert(t);
|
|
|
|
/* Since `t` is one level down from an indirection, it could pick
|
|
* up a reference to a mutable global or an outer function, so
|
|
* return false.
|
|
*/
|
|
if (!fd.isPureBypassingInference() || fd.isNested())
|
|
return false;
|
|
|
|
TypeFunction tf = fd.type.toTypeFunction();
|
|
|
|
//printf("isTypeIsolatedIndirect(%s) t = %s\n", tf.toChars(), t.toChars());
|
|
|
|
foreach (i, fparam; tf.parameterList)
|
|
{
|
|
Type tp = fparam.type;
|
|
if (!tp)
|
|
continue;
|
|
|
|
if (fparam.isLazy() || fparam.isReference())
|
|
{
|
|
if (!traverseIndirections(tp, t))
|
|
return false;
|
|
continue;
|
|
}
|
|
|
|
/* Goes down one level of indirection, then calls traverseIndirection() on
|
|
* the result.
|
|
* Returns:
|
|
* true if t is isolated from tp
|
|
*/
|
|
static bool traverse(Type tp, Type t)
|
|
{
|
|
tp = tp.baseElemOf();
|
|
switch (tp.ty)
|
|
{
|
|
case Tarray:
|
|
case Tpointer:
|
|
return traverseIndirections(tp.nextOf(), t);
|
|
|
|
case Taarray:
|
|
case Tclass:
|
|
return traverseIndirections(tp, t);
|
|
|
|
case Tstruct:
|
|
/* Drill down and check the struct's fields
|
|
*/
|
|
auto sym = tp.toDsymbol(null).isStructDeclaration();
|
|
foreach (v; sym.fields)
|
|
{
|
|
Type tprmi = v.type.addMod(tp.mod);
|
|
//printf("\ttp = %s, tprmi = %s\n", tp.toChars(), tprmi.toChars());
|
|
if (!traverse(tprmi, t))
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (!traverse(tp, t))
|
|
return false;
|
|
}
|
|
// The 'this' reference is a parameter, too
|
|
if (AggregateDeclaration ad = fd.isCtorDeclaration() ? null : fd.isThis())
|
|
{
|
|
Type tthis = ad.getType().addMod(tf.mod);
|
|
//printf("\ttthis = %s\n", tthis.toChars());
|
|
if (!traverseIndirections(tthis, t))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/********************************************
|
|
* See if pointers from function parameters, mutable globals, or uplevel functions
|
|
* could leak into return value.
|
|
* Returns:
|
|
* true if the function return value is isolated from
|
|
* any inputs to the function
|
|
*/
|
|
extern (D) bool isReturnIsolated(FuncDeclaration fd)
|
|
{
|
|
//printf("isReturnIsolated(this: %s)\n", this.toChars);
|
|
TypeFunction tf = fd.type.toTypeFunction();
|
|
assert(tf.next);
|
|
|
|
Type treti = tf.next;
|
|
if (tf.isRef)
|
|
return fd.isTypeIsolatedIndirect(treti); // check influence from parameters
|
|
|
|
return fd.isTypeIsolated(treti);
|
|
}
|
|
|
|
/********************
|
|
* See if pointers from function parameters, mutable globals, or uplevel functions
|
|
* could leak into type `t`.
|
|
* Params:
|
|
* t = type to check if it is isolated
|
|
* Returns:
|
|
* true if `t` is isolated from
|
|
* any inputs to the function
|
|
*/
|
|
extern (D) bool isTypeIsolated(FuncDeclaration fd, Type t)
|
|
{
|
|
StringTable!Type parentTypes;
|
|
const uniqueTypeID = t.getUniqueID();
|
|
if (uniqueTypeID)
|
|
{
|
|
const cacheResultPtr = uniqueTypeID in fd.isTypeIsolatedCache;
|
|
if (cacheResultPtr !is null)
|
|
return *cacheResultPtr;
|
|
|
|
parentTypes._init();
|
|
const isIsolated = fd.isTypeIsolated(t, parentTypes);
|
|
fd.isTypeIsolatedCache[uniqueTypeID] = isIsolated;
|
|
return isIsolated;
|
|
}
|
|
else
|
|
{
|
|
parentTypes._init();
|
|
return fd.isTypeIsolated(t, parentTypes);
|
|
}
|
|
}
|
|
|
|
///ditto
|
|
extern (D) bool isTypeIsolated(FuncDeclaration fd, Type t, ref StringTable!Type parentTypes)
|
|
{
|
|
//printf("this: %s, isTypeIsolated(t: %s)\n", this.toChars(), t.toChars());
|
|
|
|
t = t.baseElemOf();
|
|
switch (t.ty)
|
|
{
|
|
case Tarray:
|
|
case Tpointer:
|
|
return fd.isTypeIsolatedIndirect(t.nextOf()); // go down one level
|
|
|
|
case Taarray:
|
|
case Tclass:
|
|
return fd.isTypeIsolatedIndirect(t);
|
|
|
|
case Tstruct:
|
|
/* Drill down and check the struct's fields
|
|
*/
|
|
auto sym = t.toDsymbol(null).isStructDeclaration();
|
|
const tName = t.toChars.toDString;
|
|
const entry = parentTypes.insert(tName, t);
|
|
if (entry == null)
|
|
{
|
|
//we've already seen this type in a parent, not isolated
|
|
return false;
|
|
}
|
|
foreach (v; sym.fields)
|
|
{
|
|
Type tmi = v.type.addMod(t.mod);
|
|
//printf("\tt = %s, v: %s, vtype: %s, tmi = %s\n",
|
|
// t.toChars(), v.toChars(), v.type.toChars(), tmi.toChars());
|
|
if (!fd.isTypeIsolated(tmi, parentTypes))
|
|
return false;
|
|
}
|
|
return true;
|
|
|
|
default:
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check signature of `pragma(printf)` function, print error if invalid.
|
|
*
|
|
* printf/scanf-like functions must be of the form:
|
|
* extern (C/C++) T printf([parameters...], const(char)* format, ...);
|
|
* or:
|
|
* extern (C/C++) T vprintf([parameters...], const(char)* format, va_list);
|
|
*
|
|
* Params:
|
|
* funcdecl = function to check
|
|
* f = function type
|
|
* sc = scope
|
|
*/
|
|
private void checkPrintfScanfSignature(FuncDeclaration funcdecl, TypeFunction f, Scope* sc)
|
|
{
|
|
static bool isPointerToChar(Parameter p)
|
|
{
|
|
if (auto tptr = p.type.isTypePointer())
|
|
{
|
|
return tptr.next.ty == Tchar;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isVa_list(Parameter p)
|
|
{
|
|
return p.type.equals(target.va_listType(funcdecl.loc, sc));
|
|
}
|
|
|
|
const nparams = f.parameterList.length;
|
|
const p = (funcdecl.printf ? Id.printf : Id.scanf).toChars();
|
|
if (!(f.linkage == LINK.c || f.linkage == LINK.cpp))
|
|
{
|
|
.error(funcdecl.loc, "`pragma(%s)` function `%s` must have `extern(C)` or `extern(C++)` linkage,"
|
|
~" not `extern(%s)`",
|
|
p, funcdecl.toChars(), f.linkage.linkageToChars());
|
|
}
|
|
if (f.parameterList.varargs == VarArg.variadic)
|
|
{
|
|
if (!(nparams >= 1 && isPointerToChar(f.parameterList[nparams - 1])))
|
|
{
|
|
.error(funcdecl.loc, "`pragma(%s)` function `%s` must have"
|
|
~ " signature `%s %s([parameters...], const(char)*, ...)` not `%s`",
|
|
p, funcdecl.toChars(), f.next.toChars(), funcdecl.toChars(), funcdecl.type.toChars());
|
|
}
|
|
}
|
|
else if (f.parameterList.varargs == VarArg.none)
|
|
{
|
|
if(!(nparams >= 2 && isPointerToChar(f.parameterList[nparams - 2]) &&
|
|
isVa_list(f.parameterList[nparams - 1])))
|
|
.error(funcdecl.loc, "`pragma(%s)` function `%s` must have"~
|
|
" signature `%s %s([parameters...], const(char)*, va_list)`",
|
|
p, funcdecl.toChars(), f.next.toChars(), funcdecl.toChars());
|
|
}
|
|
else
|
|
{
|
|
.error(funcdecl.loc, "`pragma(%s)` function `%s` must have C-style variadic `...` or `va_list` parameter",
|
|
p, funcdecl.toChars());
|
|
}
|
|
}
|
|
|
|
/***************************************************
|
|
* Visit each overloaded function/template in turn, and call dg(s) on it.
|
|
* Exit when no more, or dg(s) returns nonzero.
|
|
*
|
|
* Params:
|
|
* fstart = symbol to start from
|
|
* dg = the delegate to be called on the overload
|
|
* sc = context used to check if symbol is accessible (and therefore visible),
|
|
* can be null
|
|
*
|
|
* Returns:
|
|
* ==0 continue
|
|
* !=0 done (and the return value from the last dg() call)
|
|
*/
|
|
extern (D) int overloadApply(Dsymbol fstart, scope int delegate(Dsymbol) dg, Scope* sc = null)
|
|
{
|
|
Dsymbols visited;
|
|
|
|
int overloadApplyRecurse(Dsymbol fstart, scope int delegate(Dsymbol) dg, Scope* sc)
|
|
{
|
|
// Detect cyclic calls.
|
|
if (visited.contains(fstart))
|
|
return 0;
|
|
visited.push(fstart);
|
|
|
|
Dsymbol next;
|
|
for (auto d = fstart; d; d = next)
|
|
{
|
|
import dmd.access : checkSymbolAccess;
|
|
if (auto od = d.isOverDeclaration())
|
|
{
|
|
/* The scope is needed here to check whether a function in
|
|
an overload set was added by means of a private alias (or a
|
|
selective import). If the scope where the alias is created
|
|
is imported somewhere, the overload set is visible, but the private
|
|
alias is not.
|
|
*/
|
|
if (sc)
|
|
{
|
|
if (checkSymbolAccess(sc, od))
|
|
{
|
|
if (int r = overloadApplyRecurse(od.aliassym, dg, sc))
|
|
return r;
|
|
}
|
|
}
|
|
else if (int r = overloadApplyRecurse(od.aliassym, dg, sc))
|
|
return r;
|
|
next = od.overnext;
|
|
}
|
|
else if (auto fa = d.isFuncAliasDeclaration())
|
|
{
|
|
if (fa.hasOverloads)
|
|
{
|
|
if (int r = overloadApplyRecurse(fa.funcalias, dg, sc))
|
|
return r;
|
|
}
|
|
else if (auto fd = fa.toAliasFunc())
|
|
{
|
|
if (int r = dg(fd))
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
.error(d.loc, "%s `%s` is aliased to a function", d.kind, d.toPrettyChars);
|
|
break;
|
|
}
|
|
next = fa.overnext;
|
|
}
|
|
else if (auto ad = d.isAliasDeclaration())
|
|
{
|
|
if (sc)
|
|
{
|
|
if (checkSymbolAccess(sc, ad))
|
|
next = ad.toAlias();
|
|
}
|
|
else
|
|
next = ad.toAlias();
|
|
if (next == ad)
|
|
break;
|
|
if (next == fstart)
|
|
break;
|
|
}
|
|
else if (auto td = d.isTemplateDeclaration())
|
|
{
|
|
if (int r = dg(td))
|
|
return r;
|
|
next = td.overnext;
|
|
}
|
|
else if (auto fd = d.isFuncDeclaration())
|
|
{
|
|
if (int r = dg(fd))
|
|
return r;
|
|
next = fd.overnext;
|
|
}
|
|
else if (auto os = d.isOverloadSet())
|
|
{
|
|
foreach (ds; os.a)
|
|
if (int r = dg(ds))
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
.error(d.loc, "%s `%s` is aliased to a function", d.kind, d.toPrettyChars);
|
|
break;
|
|
// BUG: should print error message?
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
return overloadApplyRecurse(fstart, dg, sc);
|
|
}
|
|
|
|
Dsymbol isUnique(OverDeclaration od)
|
|
{
|
|
Dsymbol result = null;
|
|
overloadApply(od.aliassym, (Dsymbol s)
|
|
{
|
|
if (result)
|
|
{
|
|
result = null;
|
|
return 1; // ambiguous, done
|
|
}
|
|
else
|
|
{
|
|
result = s;
|
|
return 0;
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/************************************
|
|
* Check to see if this variable is actually in an enclosing function
|
|
* rather than the current one.
|
|
* Update nestedrefs[], closureVars[] and outerVars[].
|
|
* Returns: true if error occurs.
|
|
*/
|
|
extern (D) bool checkNestedReference(VarDeclaration vd, Scope* sc, Loc loc)
|
|
{
|
|
//printf("VarDeclaration::checkNestedReference() %s\n", toChars());
|
|
if (sc.intypeof == 1 || sc.ctfe)
|
|
return false;
|
|
if (!vd.parent || vd.parent == sc.parent)
|
|
return false;
|
|
if (vd.isDataseg() || (vd.storage_class & STC.manifest))
|
|
return false;
|
|
|
|
// The current function
|
|
FuncDeclaration fdthis = sc.parent.isFuncDeclaration();
|
|
if (!fdthis)
|
|
return false; // out of function scope
|
|
|
|
Dsymbol p = vd.toParent2();
|
|
|
|
// Function literals from fdthis to p must be delegates
|
|
ensureStaticLinkTo(fdthis, p);
|
|
|
|
// The function that this variable is in
|
|
FuncDeclaration fdv = p.isFuncDeclaration();
|
|
if (!fdv || fdv == fdthis)
|
|
return false;
|
|
|
|
// Add fdthis to nestedrefs[] if not already there
|
|
if (!vd.nestedrefs.contains(fdthis))
|
|
vd.nestedrefs.push(fdthis);
|
|
|
|
//printf("\tfdv = %s\n", fdv.toChars());
|
|
//printf("\tfdthis = %s\n", fdthis.toChars());
|
|
if (loc.isValid())
|
|
{
|
|
if (fdthis.getLevelAndCheck(loc, sc, fdv, vd) == fdthis.LevelError)
|
|
return true;
|
|
}
|
|
|
|
// Add this VarDeclaration to fdv.closureVars[] if not already there
|
|
if (!sc.intypeof && !sc.traitsCompiles &&
|
|
// https://issues.dlang.org/show_bug.cgi?id=17605
|
|
(fdv.skipCodegen || !fdthis.skipCodegen))
|
|
{
|
|
if (!fdv.closureVars.contains(vd))
|
|
fdv.closureVars.push(vd);
|
|
}
|
|
|
|
if (!fdthis.outerVars.contains(vd))
|
|
fdthis.outerVars.push(vd);
|
|
|
|
//printf("fdthis is %s\n", fdthis.toChars());
|
|
//printf("var %s in function %s is nested ref\n", toChars(), fdv.toChars());
|
|
// __dollar creates problems because it isn't a real variable
|
|
// https://issues.dlang.org/show_bug.cgi?id=3326
|
|
if (vd.ident == Id.dollar)
|
|
{
|
|
.error(loc, "cannnot use `$` inside a function literal");
|
|
return true;
|
|
}
|
|
if (vd.ident == Id.withSym) // https://issues.dlang.org/show_bug.cgi?id=1759
|
|
{
|
|
ExpInitializer ez = vd._init.isExpInitializer();
|
|
assert(ez);
|
|
Expression e = ez.exp;
|
|
if (e.op == EXP.construct || e.op == EXP.blit)
|
|
e = (cast(AssignExp)e).e2;
|
|
return lambdaCheckForNestedRef(e, sc);
|
|
}
|
|
|
|
return false;
|
|
}
|