mirror of
https://github.com/dlang/dmd.git
synced 2025-04-25 20:50:41 +03:00
9065 lines
316 KiB
D
9065 lines
316 KiB
D
/**
|
|
* Does the semantic 1 pass on the AST, which looks at symbol declarations but not initializers
|
|
* or function bodies.
|
|
*
|
|
* 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/dsymbolsem.d, _dsymbolsem.d)
|
|
* Documentation: https://dlang.org/phobos/dmd_dsymbolsem.html
|
|
* Coverage: https://codecov.io/gh/dlang/dmd/src/master/compiler/src/dmd/dsymbolsem.d
|
|
*/
|
|
|
|
module dmd.dsymbolsem;
|
|
|
|
import core.stdc.stdio;
|
|
import core.stdc.string;
|
|
|
|
import dmd.aggregate;
|
|
import dmd.aliasthis;
|
|
import dmd.arraytypes;
|
|
import dmd.astcodegen;
|
|
import dmd.astenums;
|
|
import dmd.attrib;
|
|
import dmd.attribsem;
|
|
import dmd.clone;
|
|
import dmd.cond;
|
|
import dmd.timetrace;
|
|
import dmd.dcast;
|
|
import dmd.dclass;
|
|
import dmd.declaration;
|
|
import dmd.denum;
|
|
import dmd.deps;
|
|
import dmd.dimport;
|
|
import dmd.dinterpret;
|
|
import dmd.dmodule;
|
|
import dmd.dscope;
|
|
import dmd.dstruct;
|
|
import dmd.dsymbol;
|
|
import dmd.dtemplate;
|
|
import dmd.dversion;
|
|
import dmd.enumsem;
|
|
import dmd.errors;
|
|
import dmd.escape;
|
|
import dmd.expression;
|
|
import dmd.expressionsem;
|
|
import dmd.func;
|
|
import dmd.funcsem;
|
|
import dmd.globals;
|
|
import dmd.id;
|
|
import dmd.identifier;
|
|
import dmd.importc;
|
|
import dmd.init;
|
|
import dmd.initsem;
|
|
import dmd.intrange;
|
|
import dmd.hdrgen;
|
|
import dmd.lexer;
|
|
import dmd.location;
|
|
import dmd.mtype;
|
|
import dmd.mustuse;
|
|
import dmd.nspace;
|
|
import dmd.objc;
|
|
import dmd.opover;
|
|
import dmd.optimize;
|
|
import dmd.parse;
|
|
debug import dmd.printast;
|
|
import dmd.root.array;
|
|
import dmd.root.filename;
|
|
import dmd.root.string;
|
|
import dmd.common.outbuffer;
|
|
import dmd.root.rmem;
|
|
import dmd.rootobject;
|
|
import dmd.safe;
|
|
import dmd.semantic2;
|
|
import dmd.semantic3;
|
|
import dmd.sideeffect;
|
|
import dmd.staticassert;
|
|
import dmd.tokens;
|
|
import dmd.utils;
|
|
import dmd.statement;
|
|
import dmd.target;
|
|
import dmd.templateparamsem;
|
|
import dmd.templatesem;
|
|
import dmd.typesem;
|
|
import dmd.visitor;
|
|
|
|
version (IN_GCC) {}
|
|
else version (IN_LLVM) {}
|
|
else version = MARS;
|
|
|
|
enum LOG = false;
|
|
|
|
/*************************************
|
|
* Does semantic analysis on the public face of declarations.
|
|
*/
|
|
void dsymbolSemantic(Dsymbol dsym, Scope* sc)
|
|
{
|
|
scope v = new DsymbolSemanticVisitor(sc);
|
|
dsym.accept(v);
|
|
}
|
|
|
|
/***************************************************
|
|
* Determine the numerical value of the AlignmentDeclaration
|
|
* Params:
|
|
* ad = AlignmentDeclaration
|
|
* sc = context
|
|
* Returns:
|
|
* ad with alignment value determined
|
|
*/
|
|
AlignDeclaration getAlignment(AlignDeclaration ad, Scope* sc)
|
|
{
|
|
if (!ad.salign.isUnknown()) // UNKNOWN is 0
|
|
return ad;
|
|
|
|
if (!ad.exps)
|
|
{
|
|
ad.salign.setDefault();
|
|
return ad;
|
|
}
|
|
|
|
dinteger_t strictest = 0; // strictest alignment
|
|
bool errors;
|
|
foreach (ref exp; (*ad.exps)[])
|
|
{
|
|
sc = sc.startCTFE();
|
|
auto e = exp.expressionSemantic(sc);
|
|
e = resolveProperties(sc, e);
|
|
sc = sc.endCTFE();
|
|
e = e.ctfeInterpret();
|
|
exp = e; // could be re-evaluated if exps are assigned to more than one AlignDeclaration by CParser.applySpecifier(),
|
|
// e.g. `_Alignas(8) int a, b;`
|
|
if (e.op == EXP.error)
|
|
errors = true;
|
|
else
|
|
{
|
|
auto n = e.toInteger();
|
|
if (sc.inCfile && n == 0) // C11 6.7.5-6 allows 0 for alignment
|
|
continue;
|
|
|
|
if (n < 1 || n & (n - 1) || ushort.max < n || !e.type.isIntegral())
|
|
{
|
|
error(ad.loc, "alignment must be an integer positive power of 2, not 0x%llx", cast(ulong)n);
|
|
errors = true;
|
|
}
|
|
if (n > strictest) // C11 6.7.5-6
|
|
strictest = n;
|
|
}
|
|
}
|
|
|
|
if (errors || strictest == 0) // C11 6.7.5-6 says alignment of 0 means no effect
|
|
ad.salign.setDefault();
|
|
else
|
|
ad.salign.set(cast(uint) strictest);
|
|
|
|
return ad;
|
|
}
|
|
|
|
const(char)* getMessage(DeprecatedDeclaration dd)
|
|
{
|
|
if (auto sc = dd._scope)
|
|
{
|
|
dd._scope = null;
|
|
|
|
sc = sc.startCTFE();
|
|
dd.msg = dd.msg.expressionSemantic(sc);
|
|
dd.msg = resolveProperties(sc, dd.msg);
|
|
sc = sc.endCTFE();
|
|
dd.msg = dd.msg.ctfeInterpret();
|
|
|
|
if (auto se = dd.msg.toStringExp())
|
|
dd.msgstr = se.toStringz().ptr;
|
|
else
|
|
error(dd.msg.loc, "compile time constant expected, not `%s`", dd.msg.toChars());
|
|
}
|
|
return dd.msgstr;
|
|
}
|
|
|
|
bool checkDeprecated(Dsymbol d, Loc loc, Scope* sc)
|
|
{
|
|
if (global.params.useDeprecated == DiagnosticReporting.off)
|
|
return false;
|
|
if (!d.isDeprecated())
|
|
return false;
|
|
// Don't complain if we're inside a deprecated symbol's scope
|
|
if (sc.isDeprecated())
|
|
return false;
|
|
// Don't complain if we're inside a template constraint
|
|
// https://issues.dlang.org/show_bug.cgi?id=21831
|
|
if (sc.inTemplateConstraint)
|
|
return false;
|
|
|
|
const(char)* message = null;
|
|
for (Dsymbol p = d; p; p = p.parent)
|
|
{
|
|
message = p.depdecl ? p.depdecl.getMessage() : null;
|
|
if (message)
|
|
break;
|
|
}
|
|
if (message)
|
|
deprecation(loc, "%s `%s` is deprecated - %s", d.kind, d.toPrettyChars, message);
|
|
else
|
|
deprecation(loc, "%s `%s` is deprecated", d.kind, d.toPrettyChars);
|
|
|
|
if (auto ti = sc.parent ? sc.parent.isInstantiated() : null)
|
|
ti.printInstantiationTrace(Classification.deprecation);
|
|
else if (auto ti = sc.parent ? sc.parent.isTemplateInstance() : null)
|
|
ti.printInstantiationTrace(Classification.deprecation);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*********************************
|
|
* Check type to see if it is based on a deprecated symbol.
|
|
*/
|
|
private void checkDeprecated(Type type, Loc loc, Scope* sc)
|
|
{
|
|
if (Dsymbol s = type.toDsymbol(sc))
|
|
{
|
|
s.checkDeprecated(loc, sc);
|
|
}
|
|
|
|
if (auto tn = type.nextOf())
|
|
tn.checkDeprecated(loc, sc);
|
|
}
|
|
|
|
// Returns true if a contract can appear without a function body.
|
|
package bool allowsContractWithoutBody(FuncDeclaration funcdecl)
|
|
{
|
|
assert(!funcdecl.fbody);
|
|
|
|
/* Contracts can only appear without a body when they are virtual
|
|
* interface functions or abstract.
|
|
*/
|
|
Dsymbol parent = funcdecl.toParent();
|
|
InterfaceDeclaration id = parent.isInterfaceDeclaration();
|
|
|
|
if (!funcdecl.isAbstract() &&
|
|
(funcdecl.fensures || funcdecl.frequires) &&
|
|
!(id && funcdecl.isVirtual()))
|
|
{
|
|
auto cd = parent.isClassDeclaration();
|
|
if (!(cd && cd.isAbstract()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
If sd has a copy constructor and ctor is an rvalue constructor,
|
|
issue an error.
|
|
|
|
Params:
|
|
sd = struct declaration that may contain both an rvalue and copy constructor
|
|
ctor = constructor that will be checked if it is an rvalue constructor
|
|
ti = template instance the ctor is part of
|
|
|
|
Return:
|
|
`true` if sd has a copy constructor and ctor is an rvalue constructor
|
|
*/
|
|
bool checkHasBothRvalueAndCpCtor(StructDeclaration sd, CtorDeclaration ctor, TemplateInstance ti)
|
|
{
|
|
//printf("checkHasBothRvalueAndCpCtor() sd: %s ctor: %s ti: %s\n", sd.toChars(), ctor.toChars(), ti.toChars());
|
|
/* cannot use ctor.isMoveCtor because semantic pass may not have been run yet,
|
|
* so use isRvalueConstructor()
|
|
*/
|
|
if (sd && sd.hasCopyCtor && isRvalueConstructor(sd, ctor))
|
|
{
|
|
.error(ctor.loc, "cannot define both an rvalue constructor and a copy constructor for `struct %s`", sd.toChars());
|
|
.errorSupplemental(ti.loc, "Template instance `%s` creates an rvalue constructor for `struct %s`",
|
|
ti.toPrettyChars(), sd.toChars());
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/************************************************
|
|
* Check if ctor is an rvalue constructor.
|
|
* A constructor that receives a single parameter of the same type as
|
|
* `Unqual!typeof(this)` is an rvalue constructor.
|
|
* Params:
|
|
* sd = struct that ctor is a member of
|
|
* ctor = constructor to test
|
|
* Returns:
|
|
* true if it is an rvalue constructor
|
|
*/
|
|
bool isRvalueConstructor(StructDeclaration sd, CtorDeclaration ctor)
|
|
{
|
|
// note commonality with setting isMoveCtor in the semantic code for CtorDeclaration
|
|
auto tf = ctor.type.isTypeFunction();
|
|
const dim = tf.parameterList.length;
|
|
if (dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg))
|
|
{
|
|
auto param = tf.parameterList[0];
|
|
if (!(param.storageClass & STC.ref_) && param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*************************************
|
|
* Find the `alias this` symbol of e's type.
|
|
* Params:
|
|
* sc = context
|
|
* e = expression forming the `this`
|
|
* gag = do not print errors, return `null` instead
|
|
* findOnly = don't do further processing like resolving properties,
|
|
* i.e. just return plain dotExp() result.
|
|
* Returns:
|
|
* Expression that is `e.aliasthis`
|
|
*/
|
|
Expression resolveAliasThis(Scope* sc, Expression e, bool gag = false, bool findOnly = false)
|
|
{
|
|
//printf("resolveAliasThis() %s\n", toChars(e));
|
|
import dmd.typesem : dotExp;
|
|
for (AggregateDeclaration ad = isAggregate(e.type); ad;)
|
|
{
|
|
if (ad.aliasthis)
|
|
{
|
|
Loc loc = e.loc;
|
|
Type tthis = (e.op == EXP.type ? e.type : null);
|
|
const flags = cast(DotExpFlag) (DotExpFlag.noAliasThis | (gag * DotExpFlag.gag));
|
|
const olderrors = gag ? global.startGagging() : 0;
|
|
e = dotExp(ad.type, sc, e, ad.aliasthis.ident, flags);
|
|
if (!e || findOnly)
|
|
return gag && global.endGagging(olderrors) ? null : e;
|
|
|
|
if (tthis && ad.aliasthis.sym.needThis())
|
|
{
|
|
if (auto ve = e.isVarExp())
|
|
{
|
|
if (auto fd = ve.var.isFuncDeclaration())
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=13009
|
|
// Support better match for the overloaded alias this.
|
|
bool hasOverloads;
|
|
if (auto f = fd.overloadModMatch(loc, tthis, hasOverloads))
|
|
{
|
|
if (!hasOverloads)
|
|
fd = f; // use exact match
|
|
e = new VarExp(loc, fd, hasOverloads);
|
|
e.type = f.type;
|
|
e = new CallExp(loc, e);
|
|
goto L1;
|
|
}
|
|
}
|
|
}
|
|
/* non-@property function is not called inside typeof(),
|
|
* so resolve it ahead.
|
|
*/
|
|
{
|
|
int save = sc.intypeof;
|
|
sc.intypeof = 1; // bypass "need this" error check
|
|
e = resolveProperties(sc, e);
|
|
sc.intypeof = save;
|
|
}
|
|
L1:
|
|
e = new TypeExp(loc, new TypeTypeof(loc, e));
|
|
e = e.expressionSemantic(sc);
|
|
}
|
|
e = resolveProperties(sc, e);
|
|
if (!gag)
|
|
ad.aliasthis.checkDeprecatedAliasThis(loc, sc);
|
|
else if (global.endGagging(olderrors))
|
|
e = null;
|
|
}
|
|
|
|
import dmd.dclass : ClassDeclaration;
|
|
auto cd = ad.isClassDeclaration();
|
|
if ((!e || !ad.aliasthis) && cd && cd.baseClass && cd.baseClass != ClassDeclaration.object)
|
|
{
|
|
ad = cd.baseClass;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
/**
|
|
* Check if an `alias this` is deprecated
|
|
*
|
|
* Usually one would use `expression.checkDeprecated(scope, aliasthis)` to
|
|
* check if `expression` uses a deprecated `aliasthis`, but this calls
|
|
* `toPrettyChars` which lead to the following message:
|
|
* "Deprecation: alias this `fullyqualified.aggregate.__anonymous` is deprecated"
|
|
*
|
|
* Params:
|
|
* at = The `AliasThis` object to check
|
|
* loc = `Loc` of the expression triggering the access to `at`
|
|
* sc = `Scope` of the expression
|
|
* (deprecations do not trigger in deprecated scopes)
|
|
*
|
|
* Returns:
|
|
* Whether the alias this was reported as deprecated.
|
|
*/
|
|
private bool checkDeprecatedAliasThis(AliasThis at, Loc loc, Scope* sc)
|
|
{
|
|
if (global.params.useDeprecated != DiagnosticReporting.off
|
|
&& at.isDeprecated() && !sc.isDeprecated())
|
|
{
|
|
const(char)* message = null;
|
|
for (Dsymbol p = at; p; p = p.parent)
|
|
{
|
|
message = p.depdecl ? p.depdecl.getMessage() : null;
|
|
if (message)
|
|
break;
|
|
}
|
|
if (message)
|
|
deprecation(loc, "`alias %s this` is deprecated - %s",
|
|
at.sym.toChars(), message);
|
|
else
|
|
deprecation(loc, "`alias %s this` is deprecated",
|
|
at.sym.toChars());
|
|
|
|
if (auto ti = sc.parent ? sc.parent.isInstantiated() : null)
|
|
ti.printInstantiationTrace(Classification.deprecation);
|
|
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Save the scope and defer semantic analysis on the Dsymbol.
|
|
void deferDsymbolSemantic(Scope* sc, Dsymbol s, Scope* scx)
|
|
{
|
|
s._scope = scx ? scx : sc.copy();
|
|
s._scope.setNoFree();
|
|
Module.addDeferredSemantic(s);
|
|
}
|
|
|
|
|
|
private extern(C++) final class DsymbolSemanticVisitor : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
Scope* sc;
|
|
this(Scope* sc) scope @safe
|
|
{
|
|
this.sc = sc;
|
|
}
|
|
|
|
override void visit(Dsymbol dsym)
|
|
{
|
|
.error(dsym.loc, "%s `%s` %p has no semantic routine", dsym.kind, dsym.toPrettyChars, dsym);
|
|
}
|
|
|
|
override void visit(ScopeDsymbol) { }
|
|
override void visit(Declaration) { }
|
|
|
|
override void visit(AliasThis dsym)
|
|
{
|
|
if (dsym.semanticRun != PASS.initial)
|
|
return;
|
|
|
|
if (dsym._scope)
|
|
{
|
|
sc = dsym._scope;
|
|
dsym._scope = null;
|
|
}
|
|
|
|
if (!sc)
|
|
return;
|
|
|
|
dsym.semanticRun = PASS.semantic;
|
|
dsym.isDeprecated_ = !!(sc.stc & STC.deprecated_);
|
|
|
|
Dsymbol p = sc.parent.pastMixin();
|
|
AggregateDeclaration ad = p.isAggregateDeclaration();
|
|
if (!ad)
|
|
{
|
|
error(dsym.loc, "alias this can only be a member of aggregate, not %s `%s`", p.kind(), p.toChars());
|
|
return;
|
|
}
|
|
|
|
assert(ad.members);
|
|
Dsymbol s = ad.search(dsym.loc, dsym.ident);
|
|
if (!s)
|
|
{
|
|
Dsymbol pscopesym;
|
|
s = sc.search(dsym.loc, dsym.ident, pscopesym);
|
|
if (s)
|
|
error(dsym.loc, "`%s` is not a member of `%s`", s.toChars(), ad.toChars());
|
|
else
|
|
error(dsym.loc, "undefined identifier `%s`", dsym.ident.toChars());
|
|
return;
|
|
}
|
|
if (ad.aliasthis && s != ad.aliasthis)
|
|
{
|
|
error(dsym.loc, "there can be only one alias this");
|
|
return;
|
|
}
|
|
|
|
/* disable the alias this conversion so the implicit conversion check
|
|
* doesn't use it.
|
|
*/
|
|
ad.aliasthis = null;
|
|
|
|
Dsymbol sx = s;
|
|
if (sx.isAliasDeclaration())
|
|
sx = sx.toAlias();
|
|
Declaration d = sx.isDeclaration();
|
|
if (d && !d.isTupleDeclaration())
|
|
{
|
|
/* https://issues.dlang.org/show_bug.cgi?id=18429
|
|
*
|
|
* If the identifier in the AliasThis declaration
|
|
* is defined later and is a voldemort type, we must
|
|
* perform semantic on the declaration to deduce the type.
|
|
*/
|
|
if (!d.type)
|
|
d.dsymbolSemantic(sc);
|
|
|
|
Type t = d.type;
|
|
assert(t);
|
|
if (ad.type.implicitConvTo(t) > MATCH.nomatch)
|
|
{
|
|
error(dsym.loc, "alias this is not reachable as `%s` already converts to `%s`", ad.toChars(), t.toChars());
|
|
}
|
|
}
|
|
|
|
dsym.sym = s;
|
|
// Restore alias this
|
|
ad.aliasthis = dsym;
|
|
dsym.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
override void visit(AliasDeclaration dsym)
|
|
{
|
|
if (dsym.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
assert(dsym.semanticRun <= PASS.semantic);
|
|
|
|
if (!sc)
|
|
return;
|
|
|
|
dsym.semanticRun = PASS.semantic;
|
|
|
|
dsym.storage_class |= sc.stc & STC.deprecated_;
|
|
dsym.visibility = sc.visibility;
|
|
dsym.userAttribDecl = sc.userAttribDecl;
|
|
|
|
if (!sc.func && dsym.inNonRoot())
|
|
return;
|
|
|
|
aliasSemantic(dsym, sc);
|
|
}
|
|
|
|
override void visit(AliasAssign dsym)
|
|
{
|
|
//printf("visit(AliasAssign)\n");
|
|
if (dsym.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
assert(dsym.semanticRun <= PASS.semantic);
|
|
|
|
if (!sc.func && dsym.inNonRoot())
|
|
return;
|
|
|
|
aliasAssignSemantic(dsym, sc);
|
|
}
|
|
|
|
override void visit(VarDeclaration dsym)
|
|
{
|
|
version (none)
|
|
{
|
|
printf("VarDeclaration::semantic('%s', parent = '%s') sem = %d\n",
|
|
dsym.toChars(), sc.parent ? sc.parent.toChars() : null, dsym.semanticRun);
|
|
printf(" type = %s\n", dsym.type ? dsym.type.toChars() : "null");
|
|
printf(" stc = x%llx\n", dsym.storage_class);
|
|
printf(" storage_class = x%llx\n", dsym.storage_class);
|
|
printf("linkage = %d\n", dsym._linkage);
|
|
//if (strcmp(toChars(), "mul") == 0) assert(0);
|
|
}
|
|
//if (semanticRun > PASS.initial)
|
|
// return;
|
|
//semanticRun = PSSsemantic;
|
|
|
|
if (dsym.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
|
|
if (sc && sc.inunion && sc.inunion.isAnonDeclaration())
|
|
dsym.overlapped = true;
|
|
|
|
dsym.sequenceNumber = global.varSequenceNumber++;
|
|
if (!dsym.isScope())
|
|
dsym.maybeScope = true;
|
|
|
|
Scope* scx = null;
|
|
if (dsym._scope)
|
|
{
|
|
sc = dsym._scope;
|
|
scx = sc;
|
|
dsym._scope = null;
|
|
}
|
|
|
|
if (!sc)
|
|
return;
|
|
|
|
dsym.semanticRun = PASS.semantic;
|
|
|
|
// 'static foreach' variables should not inherit scope properties
|
|
// https://issues.dlang.org/show_bug.cgi?id=19482
|
|
if ((dsym.storage_class & (STC.foreach_ | STC.local)) == (STC.foreach_ | STC.local))
|
|
{
|
|
dsym._linkage = LINK.d;
|
|
dsym.visibility = Visibility(Visibility.Kind.public_);
|
|
dsym.overlapped = false; // unset because it is modified early on this function
|
|
dsym.userAttribDecl = null; // unset because it is set by Dsymbol.setScope()
|
|
}
|
|
else
|
|
{
|
|
/* Pick up storage classes from context, but except synchronized,
|
|
* override, abstract, and final.
|
|
*/
|
|
dsym.storage_class |= (sc.stc & ~(STC.synchronized_ | STC.override_ | STC.abstract_ | STC.final_));
|
|
dsym.userAttribDecl = sc.userAttribDecl;
|
|
dsym.cppnamespace = sc.namespace;
|
|
dsym._linkage = sc.linkage;
|
|
dsym.visibility = sc.visibility;
|
|
dsym.alignment = sc.alignment();
|
|
}
|
|
|
|
if (dsym.storage_class & STC.extern_ && dsym._init)
|
|
{
|
|
if (sc.inCfile)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=24447
|
|
// extern int x = 3; is allowed in C
|
|
dsym.storage_class &= ~STC.extern_;
|
|
}
|
|
else
|
|
.error(dsym.loc, "%s `%s` extern symbols cannot have initializers", dsym.kind, dsym.toPrettyChars);
|
|
|
|
}
|
|
|
|
AggregateDeclaration ad = dsym.isThis();
|
|
if (ad)
|
|
dsym.storage_class |= ad.storage_class & STC.TYPECTOR;
|
|
|
|
if ((dsym.storage_class & STC.auto_) && (dsym.storage_class & STC.ref_))
|
|
{
|
|
if (!(dsym.storage_class & STC.autoref))
|
|
{
|
|
.error(dsym.loc, "%s `%s` - `auto ref` variable must have `auto` and `ref` adjacent", dsym.kind, dsym.toChars());
|
|
dsym.storage_class |= STC.autoref;
|
|
}
|
|
}
|
|
|
|
/* If auto type inference, do the inference
|
|
*/
|
|
int inferred = 0;
|
|
if (!dsym.type)
|
|
{
|
|
dsym.inuse++;
|
|
|
|
// Infering the type requires running semantic,
|
|
// so mark the scope as ctfe if required
|
|
bool needctfe = (dsym.storage_class & (STC.manifest | STC.static_)) != 0 || !sc.func;
|
|
if (needctfe)
|
|
{
|
|
sc.condition = true;
|
|
sc = sc.startCTFE();
|
|
}
|
|
//printf("inferring type for %s with init %s\n", dsym.toChars(), dsym._init.toChars());
|
|
dsym._init = dsym._init.inferType(sc);
|
|
dsym.type = dsym._init.initializerToExpression(null, sc.inCfile).type;
|
|
if (needctfe)
|
|
sc = sc.endCTFE();
|
|
|
|
dsym.inuse--;
|
|
inferred = 1;
|
|
|
|
/* This is a kludge to support the existing syntax for RAII
|
|
* declarations.
|
|
*/
|
|
dsym.storage_class &= ~STC.auto_;
|
|
dsym.originalType = dsym.type.syntaxCopy();
|
|
}
|
|
else
|
|
{
|
|
if (!dsym.originalType)
|
|
dsym.originalType = dsym.type.syntaxCopy();
|
|
|
|
/* Prefix function attributes of variable declaration can affect
|
|
* its type:
|
|
* pure nothrow void function() fp;
|
|
* static assert(is(typeof(fp) == void function() pure nothrow));
|
|
*/
|
|
Scope* sc2 = sc.push();
|
|
sc2.stc |= (dsym.storage_class & STC.FUNCATTR);
|
|
dsym.inuse++;
|
|
dsym.type = dsym.type.typeSemantic(dsym.loc, sc2);
|
|
dsym.inuse--;
|
|
sc2.pop();
|
|
}
|
|
//printf(" semantic type = %s\n", dsym.type ? dsym.type.toChars() : "null");
|
|
if (dsym.type.ty == Terror)
|
|
dsym.errors = true;
|
|
|
|
dsym.type.checkDeprecated(dsym.loc, sc);
|
|
dsym.parent = sc.parent;
|
|
//printf("this = %p, parent = %p, '%s'\n", dsym, dsym.parent, dsym.parent.toChars());
|
|
|
|
/* If scope's alignment is the default, use the type's alignment,
|
|
* otherwise the scope overrrides.
|
|
*/
|
|
if (dsym.alignment.isDefault())
|
|
dsym.alignment = dsym.type.alignment(); // use type's alignment
|
|
|
|
//printf("sc.stc = %x\n", sc.stc);
|
|
//printf("storage_class = x%x\n", storage_class);
|
|
|
|
dsym.type.checkComplexTransition(dsym.loc, sc);
|
|
|
|
// Calculate type size + safety checks
|
|
if (dsym.storage_class & STC.gshared && !dsym.isMember())
|
|
{
|
|
sc.setUnsafe(false, dsym.loc, "using `__gshared` instead of `shared`");
|
|
}
|
|
|
|
Dsymbol parent = dsym.toParent();
|
|
|
|
Type tb = dsym.type.toBasetype();
|
|
Type tbn = tb.baseElemOf();
|
|
if (tb.ty == Tvoid && !(dsym.storage_class & STC.lazy_))
|
|
{
|
|
if (inferred)
|
|
{
|
|
.error(dsym.loc, "%s `%s` - type `%s` is inferred from initializer `%s`, and variables cannot be of type `void`",
|
|
dsym.kind, dsym.toPrettyChars, dsym.type.toChars(), toChars(dsym._init));
|
|
}
|
|
else
|
|
.error(dsym.loc, "%s `%s` - variables cannot be of type `void`", dsym.kind, dsym.toPrettyChars);
|
|
dsym.type = Type.terror;
|
|
tb = dsym.type;
|
|
}
|
|
if (tb.ty == Tfunction)
|
|
{
|
|
.error(dsym.loc, "%s `%s` cannot be declared to be a function", dsym.kind, dsym.toPrettyChars);
|
|
dsym.type = Type.terror;
|
|
tb = dsym.type;
|
|
}
|
|
if (auto ts = tb.isTypeStruct())
|
|
{
|
|
// Require declarations, except when it's just a reference (as done for pointers)
|
|
// or when the variable is defined externally
|
|
if (!ts.sym.members && !(dsym.storage_class & (STC.ref_ | STC.extern_)))
|
|
{
|
|
.error(dsym.loc, "%s `%s` - no definition of struct `%s`", dsym.kind, dsym.toPrettyChars, ts.toChars());
|
|
|
|
// Explain why the definition is required when it's part of another type
|
|
if (!dsym.type.isTypeStruct())
|
|
{
|
|
// Prefer Loc of the dependant type
|
|
const s = dsym.type.toDsymbol(sc);
|
|
const loc = (s ? s : dsym).loc;
|
|
loc.errorSupplemental("required by type `%s`", dsym.type.toChars());
|
|
}
|
|
errorSupplemental(dsym.loc, "see https://dlang.org/spec/struct.html#opaque_struct_unions");
|
|
errorSupplemental(dsym.loc, "perhaps declare a variable with pointer type `%s*` instead", dsym.type.toChars());
|
|
|
|
// Flag variable as error to avoid invalid error messages due to unknown size
|
|
dsym.type = Type.terror;
|
|
}
|
|
}
|
|
if ((dsym.storage_class & STC.auto_) && !inferred && !(dsym.storage_class & STC.autoref))
|
|
.error(dsym.loc, "%s `%s` - storage class `auto` has no effect if type is not inferred, did you mean `scope`?", dsym.kind, dsym.toPrettyChars);
|
|
|
|
if (auto tt = tb.isTypeTuple())
|
|
{
|
|
/* Instead, declare variables for each of the tuple elements
|
|
* and add those.
|
|
*/
|
|
size_t nelems = Parameter.dim(tt.arguments);
|
|
Expression ie = (dsym._init && !dsym._init.isVoidInitializer()) ? dsym._init.initializerToExpression(null, sc.inCfile) : null;
|
|
if (ie)
|
|
ie = ie.expressionSemantic(sc);
|
|
if (nelems > 0 && ie)
|
|
{
|
|
auto iexps = new Expressions();
|
|
iexps.push(ie);
|
|
auto exps = new Expressions();
|
|
for (size_t pos = 0; pos < iexps.length; pos++)
|
|
{
|
|
Lexpand1:
|
|
Expression e = (*iexps)[pos];
|
|
Parameter arg = Parameter.getNth(tt.arguments, pos);
|
|
arg.type = arg.type.typeSemantic(dsym.loc, sc);
|
|
//printf("[%d] iexps.length = %d, ", pos, iexps.length);
|
|
//printf("e = (%s %s, %s), ", Token.tochars[e.op], e.toChars(), e.type.toChars());
|
|
//printf("arg = (%s, %s)\n", arg.toChars(), arg.type.toChars());
|
|
|
|
if (e != ie)
|
|
{
|
|
if (iexps.length > nelems)
|
|
goto Lnomatch;
|
|
if (e.type.implicitConvTo(arg.type))
|
|
continue;
|
|
}
|
|
|
|
if (auto te = e.isTupleExp())
|
|
{
|
|
if (iexps.length - 1 + te.exps.length > nelems)
|
|
goto Lnomatch;
|
|
|
|
iexps.remove(pos);
|
|
iexps.insert(pos, te.exps);
|
|
(*iexps)[pos] = Expression.combine(te.e0, (*iexps)[pos]);
|
|
goto Lexpand1;
|
|
}
|
|
else if (isAliasThisTuple(e))
|
|
{
|
|
auto v = copyToTemp(STC.none, "__tup", e);
|
|
v.dsymbolSemantic(sc);
|
|
auto ve = new VarExp(dsym.loc, v);
|
|
ve.type = e.type;
|
|
|
|
exps.setDim(1);
|
|
(*exps)[0] = ve;
|
|
expandAliasThisTuples(exps, 0);
|
|
|
|
for (size_t u = 0; u < exps.length; u++)
|
|
{
|
|
Lexpand2:
|
|
Expression ee = (*exps)[u];
|
|
arg = Parameter.getNth(tt.arguments, pos + u);
|
|
arg.type = arg.type.typeSemantic(dsym.loc, sc);
|
|
//printf("[%d+%d] exps.length = %d, ", pos, u, exps.length);
|
|
//printf("ee = (%s %s, %s), ", Token.tochars[ee.op], ee.toChars(), ee.type.toChars());
|
|
//printf("arg = (%s, %s)\n", arg.toChars(), arg.type.toChars());
|
|
|
|
size_t iexps_dim = iexps.length - 1 + exps.length;
|
|
if (iexps_dim > nelems)
|
|
goto Lnomatch;
|
|
if (ee.type.implicitConvTo(arg.type))
|
|
continue;
|
|
|
|
if (expandAliasThisTuples(exps, u) != -1)
|
|
goto Lexpand2;
|
|
}
|
|
|
|
if ((*exps)[0] != ve)
|
|
{
|
|
Expression e0 = (*exps)[0];
|
|
(*exps)[0] = new CommaExp(dsym.loc, new DeclarationExp(dsym.loc, v), e0);
|
|
(*exps)[0].type = e0.type;
|
|
|
|
iexps.remove(pos);
|
|
iexps.insert(pos, exps);
|
|
goto Lexpand1;
|
|
}
|
|
}
|
|
}
|
|
if (iexps.length < nelems)
|
|
goto Lnomatch;
|
|
|
|
ie = new TupleExp(dsym._init.loc, iexps);
|
|
}
|
|
Lnomatch:
|
|
|
|
if (ie && ie.op == EXP.tuple)
|
|
{
|
|
auto te = ie.isTupleExp();
|
|
size_t tedim = te.exps.length;
|
|
if (tedim != nelems)
|
|
{
|
|
error(dsym.loc, "sequence of %d elements cannot be assigned to sequence of %d elements", cast(int)tedim, cast(int)nelems);
|
|
for (size_t u = tedim; u < nelems; u++) // fill dummy expression
|
|
te.exps.push(ErrorExp.get());
|
|
}
|
|
}
|
|
|
|
auto exps = new Objects(nelems);
|
|
for (size_t i = 0; i < nelems; i++)
|
|
{
|
|
Parameter arg = Parameter.getNth(tt.arguments, i);
|
|
|
|
OutBuffer buf;
|
|
buf.printf("__%s_field_%llu", dsym.ident.toChars(), cast(ulong)i);
|
|
auto id = Identifier.idPool(buf[]);
|
|
|
|
Initializer ti;
|
|
if (ie)
|
|
{
|
|
Expression einit = ie;
|
|
if (auto te = ie.isTupleExp())
|
|
{
|
|
einit = (*te.exps)[i];
|
|
if (i == 0)
|
|
einit = Expression.combine(te.e0, einit);
|
|
}
|
|
ti = new ExpInitializer(einit.loc, einit);
|
|
}
|
|
else
|
|
ti = dsym._init ? dsym._init.syntaxCopy() : null;
|
|
|
|
STC storage_class = STC.temp | dsym.storage_class;
|
|
if ((dsym.storage_class & STC.parameter) && (arg.storageClass & STC.parameter))
|
|
storage_class |= arg.storageClass;
|
|
auto v = new VarDeclaration(dsym.loc, arg.type, id, ti, storage_class);
|
|
//printf("declaring field %s of type %s\n", v.toChars(), v.type.toChars());
|
|
v.overlapped = dsym.overlapped;
|
|
|
|
v.dsymbolSemantic(sc);
|
|
|
|
Expression e = new VarExp(dsym.loc, v);
|
|
(*exps)[i] = e;
|
|
}
|
|
auto v2 = new TupleDeclaration(dsym.loc, dsym.ident, exps);
|
|
v2.parent = dsym.parent;
|
|
v2.isexp = true;
|
|
dsym.aliasTuple = v2;
|
|
dsym.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
|
|
/* Storage class can modify the type
|
|
*/
|
|
dsym.type = dsym.type.addStorageClass(dsym.storage_class);
|
|
|
|
/* Adjust storage class to reflect type
|
|
*/
|
|
if (dsym.type.isConst())
|
|
{
|
|
dsym.storage_class |= STC.const_;
|
|
if (dsym.type.isShared())
|
|
dsym.storage_class |= STC.shared_;
|
|
}
|
|
else if (dsym.type.isImmutable())
|
|
dsym.storage_class |= STC.immutable_;
|
|
else if (dsym.type.isShared())
|
|
dsym.storage_class |= STC.shared_;
|
|
else if (dsym.type.isWild())
|
|
dsym.storage_class |= STC.wild;
|
|
|
|
if (STC stc = dsym.storage_class & (STC.synchronized_ | STC.override_ | STC.abstract_ | STC.final_))
|
|
{
|
|
if (stc == STC.final_)
|
|
.error(dsym.loc, "%s `%s` cannot be `final`, perhaps you meant `const`?", dsym.kind, dsym.toPrettyChars);
|
|
else
|
|
{
|
|
OutBuffer buf;
|
|
stcToBuffer(buf, stc);
|
|
.error(dsym.loc, "%s `%s` cannot be `%s`", dsym.kind, dsym.toPrettyChars, buf.peekChars());
|
|
}
|
|
dsym.storage_class &= ~stc; // strip off
|
|
}
|
|
|
|
// At this point we can add `scope` to the STC instead of `in`,
|
|
// because we are never going to use this variable's STC for user messages
|
|
if (dsym.storage_class & STC.constscoperef)
|
|
dsym.storage_class |= STC.scope_;
|
|
|
|
import dmd.typesem : hasPointers;
|
|
|
|
if (dsym.storage_class & STC.scope_)
|
|
{
|
|
STC stc = dsym.storage_class & (STC.static_ | STC.extern_ | STC.manifest | STC.gshared);
|
|
if (stc)
|
|
{
|
|
OutBuffer buf;
|
|
stcToBuffer(buf, stc);
|
|
.error(dsym.loc, "%s `%s` cannot be `scope` and `%s`", dsym.kind, dsym.toPrettyChars, buf.peekChars());
|
|
}
|
|
else if (dsym.isMember())
|
|
{
|
|
error(dsym.loc, "field `%s` cannot be `scope`", dsym.toChars());
|
|
}
|
|
else if (!dsym.type.hasPointers())
|
|
{
|
|
dsym.storage_class &= ~STC.scope_; // silently ignore; may occur in generic code
|
|
// https://issues.dlang.org/show_bug.cgi?id=23168
|
|
if (dsym.storage_class & STC.returnScope)
|
|
{
|
|
dsym.storage_class &= ~(STC.return_ | STC.returnScope);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dsym.storage_class & (STC.static_ | STC.extern_ | STC.manifest | STC.templateparameter | STC.gshared | STC.ctfe))
|
|
{
|
|
}
|
|
else
|
|
{
|
|
AggregateDeclaration aad = parent.isAggregateDeclaration();
|
|
if (aad)
|
|
{
|
|
if (global.params.v.field && dsym.storage_class & (STC.const_ | STC.immutable_) && dsym._init && !dsym._init.isVoidInitializer())
|
|
{
|
|
const(char)* s = (dsym.storage_class & STC.immutable_) ? "immutable" : "const";
|
|
message(dsym.loc, "`%s.%s` is `%s` field", ad.toPrettyChars(), dsym.toChars(), s);
|
|
}
|
|
dsym.storage_class |= STC.field;
|
|
if (auto ts = tbn.isTypeStruct())
|
|
if (ts.sym.noDefaultCtor)
|
|
{
|
|
if (!dsym.isThisDeclaration() && !dsym._init)
|
|
aad.noDefaultCtor = true;
|
|
}
|
|
}
|
|
|
|
InterfaceDeclaration id = parent.isInterfaceDeclaration();
|
|
if (id)
|
|
{
|
|
error(dsym.loc, "field `%s` not allowed in interface", dsym.toChars());
|
|
}
|
|
else if (aad && aad.sizeok == Sizeok.done)
|
|
{
|
|
error(dsym.loc, "cannot declare field `%s` because it will change the determined size of `%s`", dsym.toChars(), aad.toChars());
|
|
}
|
|
|
|
/* Templates cannot add fields to aggregates
|
|
*/
|
|
TemplateInstance ti = parent.isTemplateInstance();
|
|
if (ti)
|
|
{
|
|
// Take care of nested templates
|
|
while (1)
|
|
{
|
|
TemplateInstance ti2 = ti.tempdecl.parent.isTemplateInstance();
|
|
if (!ti2)
|
|
break;
|
|
ti = ti2;
|
|
}
|
|
// If it's a member template
|
|
AggregateDeclaration ad2 = ti.tempdecl.isMember();
|
|
if (ad2 && dsym.storage_class != STC.none)
|
|
{
|
|
.error(dsym.loc, "%s `%s` - cannot use template to add field to aggregate `%s`", dsym.kind, dsym.toPrettyChars, ad2.toChars());
|
|
}
|
|
}
|
|
}
|
|
|
|
/* If the alignment of a stack local is greater than the stack alignment,
|
|
* note it in the enclosing function's alignSectionVars
|
|
*/
|
|
version (MARS)
|
|
{
|
|
if (!dsym.alignment.isDefault() && sc.func &&
|
|
dsym.alignment.get() > target.stackAlign() &&
|
|
sc.func && !dsym.isDataseg() && !dsym.isParameter() && !dsym.isField())
|
|
{
|
|
auto fd = sc.func;
|
|
if (!fd.alignSectionVars)
|
|
fd.alignSectionVars = new VarDeclarations();
|
|
fd.alignSectionVars.push(dsym);
|
|
}
|
|
}
|
|
|
|
if ((dsym.storage_class & (STC.ref_ | STC.field)) == (STC.ref_ | STC.field) && dsym.ident != Id.This)
|
|
{
|
|
.error(dsym.loc, "%s `%s` - field declarations cannot be `ref`", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
|
|
if (dsym.type.hasWild())
|
|
{
|
|
if (dsym.storage_class & (STC.static_ | STC.extern_ | STC.gshared | STC.manifest | STC.field) || dsym.isDataseg())
|
|
{
|
|
.error(dsym.loc, "%s `%s` - only parameters or stack-based variables can be `inout`", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
FuncDeclaration func = sc.func;
|
|
if (func)
|
|
{
|
|
if (func.fes)
|
|
func = func.fes.func;
|
|
bool isWild = false;
|
|
for (FuncDeclaration fd = func; fd; fd = fd.toParentDecl().isFuncDeclaration())
|
|
{
|
|
if (fd.type.isTypeFunction().iswild)
|
|
{
|
|
isWild = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!isWild)
|
|
{
|
|
.error(dsym.loc, "%s `%s` - `inout` variables can only be declared inside `inout` functions", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!(dsym.storage_class & (STC.ctfe | STC.extern_ | STC.ref_ | STC.result)) &&
|
|
tbn.ty == Tstruct && tbn.isTypeStruct().sym.noDefaultCtor)
|
|
{
|
|
if (!dsym._init)
|
|
{
|
|
if (dsym.isField())
|
|
{
|
|
/* For fields, we'll check the constructor later to make sure it is initialized
|
|
*/
|
|
dsym.storage_class |= STC.nodefaultctor;
|
|
}
|
|
else if (dsym.storage_class & STC.parameter)
|
|
{
|
|
}
|
|
else
|
|
.error(dsym.loc, "%s `%s` - default construction is disabled for type `%s`", dsym.kind, dsym.toPrettyChars, dsym.type.toChars());
|
|
}
|
|
}
|
|
|
|
bool dsymIsRef = (dsym.storage_class & (STC.ref_ | STC.field | STC.parameter | STC.temp | STC.foreach_)) == STC.ref_;
|
|
if (dsymIsRef)
|
|
{
|
|
if (!dsym._init && dsym.ident != Id.This)
|
|
{
|
|
if (dsym.storage_class & STC.autoref)
|
|
{
|
|
dsymIsRef = false;
|
|
dsym.storage_class &= ~STC.ref_;
|
|
}
|
|
else
|
|
.error(dsym.loc, "%s `%s` - initializer is required for `ref` variable", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
else if (dsym._init.isVoidInitializer())
|
|
{
|
|
.error(dsym.loc, "%s `%s` - void initializer not allowed for `ref` variable", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
}
|
|
|
|
FuncDeclaration fd = parent.isFuncDeclaration();
|
|
if (dsym.type.isScopeClass() && !(dsym.storage_class & STC.nodtor))
|
|
{
|
|
if (dsym.storage_class & (STC.field | STC.out_ | STC.ref_ | STC.static_ | STC.manifest | STC.gshared) || !fd)
|
|
{
|
|
.error(dsym.loc, "%s `%s` globals, statics, fields, manifest constants, ref and out parameters cannot be `scope`", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
|
|
// @@@DEPRECATED_2.097@@@ https://dlang.org/deprecate.html#scope%20as%20a%20type%20constraint
|
|
// Deprecated in 2.087
|
|
// Remove this when the feature is removed from the language
|
|
if (!(dsym.storage_class & STC.scope_))
|
|
{
|
|
if (!(dsym.storage_class & STC.parameter) && dsym.ident != Id.withSym)
|
|
.error(dsym.loc, "%s `%s` reference to `scope class` must be `scope`", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
}
|
|
|
|
// Calculate type size + safety checks
|
|
if (sc && sc.func)
|
|
{
|
|
if (dsym._init && dsym._init.isVoidInitializer() && !(dsym.storage_class & STC.temp))
|
|
{
|
|
// Don't do these checks for STC.temp vars because the generated `opAssign`
|
|
// for a struct with postblit and destructor void initializes a temporary
|
|
// __swap variable, which can be trusted
|
|
|
|
if (dsym.type.hasPointers()) // also computes type size
|
|
sc.setUnsafe(false, dsym.loc,
|
|
"`void` initializing a pointer");
|
|
else if (dsym.type.hasInvariant())
|
|
sc.setUnsafe(false, dsym.loc,
|
|
"`void` initializing a struct with an invariant");
|
|
else if (dsym.type.toBasetype().ty == Tbool)
|
|
sc.setUnsafePreview(global.params.systemVariables, false, dsym.loc,
|
|
"void intializing a bool (which must always be 0 or 1)");
|
|
else if (dsym.type.hasUnsafeBitpatterns())
|
|
sc.setUnsafePreview(global.params.systemVariables, false, dsym.loc,
|
|
"`void` initializing a type with unsafe bit patterns");
|
|
}
|
|
else if (!dsym._init &&
|
|
!(dsym.storage_class & (STC.static_ | STC.extern_ | STC.gshared | STC.manifest | STC.field | STC.parameter)) &&
|
|
dsym.type.hasVoidInitPointers())
|
|
{
|
|
sc.setUnsafe(false, dsym.loc, "`void` initializers for pointers");
|
|
}
|
|
}
|
|
|
|
if ((!dsym._init || dsym._init.isVoidInitializer) && !fd)
|
|
{
|
|
// If not mutable, initializable by constructor only
|
|
dsym.setInCtorOnly = true;
|
|
}
|
|
|
|
if (dsym._init)
|
|
{ } // remember we had an explicit initializer
|
|
else if (dsym.storage_class & STC.manifest)
|
|
.error(dsym.loc, "%s `%s` - manifest constants must have initializers", dsym.kind, dsym.toPrettyChars);
|
|
|
|
// Don't allow non-extern, non-__gshared variables to be interfaced with C++
|
|
if (dsym._linkage == LINK.cpp && !(dsym.storage_class & (STC.ctfe | STC.extern_ | STC.gshared)) && dsym.isDataseg())
|
|
{
|
|
const char* p = (dsym.storage_class & STC.shared_) ? "shared" : "static";
|
|
.error(dsym.loc, "%s `%s` cannot have `extern(C++)` linkage because it is `%s`", dsym.kind, dsym.toPrettyChars, p);
|
|
errorSupplemental(dsym.loc, "perhaps declare it as `__gshared` instead");
|
|
dsym.errors = true;
|
|
}
|
|
|
|
bool isBlit = false;
|
|
uinteger_t sz;
|
|
if (sc.inCfile && !dsym._init)
|
|
{
|
|
addDefaultCInitializer(dsym);
|
|
}
|
|
if (!dsym._init &&
|
|
!(dsym.storage_class & (STC.static_ | STC.gshared | STC.extern_)) &&
|
|
fd &&
|
|
(!(dsym.storage_class & (STC.field | STC.in_ | STC.foreach_ | STC.parameter | STC.result)) ||
|
|
(dsym.storage_class & STC.out_)) &&
|
|
(sz = dsym.type.size()) != 0)
|
|
{
|
|
// Provide a default initializer
|
|
|
|
//printf("Providing default initializer for '%s'\n", dsym.toChars());
|
|
if (sz == SIZE_INVALID && dsym.type.ty != Terror)
|
|
.error(dsym.loc, "%s `%s` - size of type `%s` is invalid", dsym.kind, dsym.toPrettyChars, dsym.type.toChars());
|
|
|
|
Type tv = dsym.type;
|
|
while (tv.ty == Tsarray) // Don't skip Tenum
|
|
tv = tv.nextOf();
|
|
if (tv.needsNested())
|
|
{
|
|
/* Nested struct requires valid enclosing frame pointer.
|
|
* In StructLiteralExp::toElem(), it's calculated.
|
|
*/
|
|
assert(tbn.ty == Tstruct);
|
|
checkFrameAccess(dsym.loc, sc, tbn.isTypeStruct().sym);
|
|
|
|
Expression e = tv.defaultInitLiteral(dsym.loc);
|
|
e = new BlitExp(dsym.loc, new VarExp(dsym.loc, dsym), e);
|
|
e = e.expressionSemantic(sc);
|
|
dsym._init = new ExpInitializer(dsym.loc, e);
|
|
goto Ldtor;
|
|
}
|
|
if (tv.ty == Tstruct && tv.isTypeStruct().sym.zeroInit)
|
|
{
|
|
/* If a struct is all zeros, as a special case
|
|
* set its initializer to the integer 0.
|
|
* In AssignExp::toElem(), we check for this and issue
|
|
* a memset() to initialize the struct.
|
|
* Must do same check in interpreter.
|
|
*/
|
|
Expression e = IntegerExp.literal!0;
|
|
e = new BlitExp(dsym.loc, new VarExp(dsym.loc, dsym), e);
|
|
e.type = dsym.type; // don't type check this, it would fail
|
|
dsym._init = new ExpInitializer(dsym.loc, e);
|
|
goto Ldtor;
|
|
}
|
|
if (dsym.type.baseElemOf().ty == Tvoid)
|
|
{
|
|
.error(dsym.loc, "%s `%s` of type `%s` does not have a default initializer", dsym.kind, dsym.toPrettyChars, dsym.type.toChars());
|
|
}
|
|
else if (auto e = dsym.type.defaultInit(dsym.loc))
|
|
{
|
|
dsym._init = new ExpInitializer(dsym.loc, e);
|
|
}
|
|
|
|
// Default initializer is always a blit
|
|
isBlit = true;
|
|
}
|
|
if (dsym._init)
|
|
{
|
|
sc = sc.push();
|
|
sc.stc &= ~(STC.TYPECTOR | STC.pure_ | STC.nothrow_ | STC.nogc | STC.ref_ | STC.disable);
|
|
|
|
if (sc.inCfile &&
|
|
dsym.type.isTypeSArray() &&
|
|
dsym.type.isTypeSArray().isIncomplete() &&
|
|
dsym._init.isVoidInitializer() &&
|
|
!(dsym.storage_class & STC.field))
|
|
{
|
|
.error(dsym.loc, "%s `%s` - incomplete array type must have initializer", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
|
|
ExpInitializer ei = dsym._init.isExpInitializer();
|
|
|
|
if (ei) // https://issues.dlang.org/show_bug.cgi?id=13424
|
|
// Preset the required type to fail in FuncLiteralDeclaration::semantic3
|
|
ei.exp = inferType(ei.exp, dsym.type);
|
|
|
|
// If inside function, there is no semantic3() call
|
|
if (sc.func || sc.intypeof == 1)
|
|
{
|
|
// If local variable, use AssignExp to handle all the various
|
|
// possibilities.
|
|
if (fd && !(dsym.storage_class & (STC.manifest | STC.static_ | STC.gshared | STC.extern_)) && !dsym._init.isVoidInitializer())
|
|
{
|
|
//printf("fd = '%s', var = '%s'\n", fd.toChars(), dsym.toChars());
|
|
if (!ei)
|
|
{
|
|
ArrayInitializer ai = dsym._init.isArrayInitializer();
|
|
Expression e;
|
|
if (ai && tb.ty == Taarray)
|
|
e = ai.toAssocArrayLiteral();
|
|
else
|
|
e = dsym._init.initializerToExpression(null, sc.inCfile);
|
|
if (!e)
|
|
{
|
|
// Run semantic, but don't need to interpret
|
|
dsym._init = dsym._init.initializerSemantic(sc, dsym.type, INITnointerpret);
|
|
e = dsym._init.initializerToExpression(null, sc.inCfile);
|
|
if (!e)
|
|
{
|
|
.error(dsym.loc, "%s `%s` is not a static and cannot have static initializer", dsym.kind, dsym.toPrettyChars);
|
|
e = ErrorExp.get();
|
|
}
|
|
}
|
|
ei = new ExpInitializer(dsym._init.loc, e);
|
|
dsym._init = ei;
|
|
}
|
|
else if (sc.inCfile && dsym.type.isTypeSArray() &&
|
|
dsym.type.isTypeSArray().isIncomplete())
|
|
{
|
|
// C11 6.7.9-22 determine the size of the incomplete array,
|
|
// or issue an error that the initializer is invalid.
|
|
dsym._init = dsym._init.initializerSemantic(sc, dsym.type, INITinterpret);
|
|
}
|
|
|
|
if (ei && dsym.isScope())
|
|
{
|
|
Expression ex = ei.exp.lastComma();
|
|
if (ex.op == EXP.blit || ex.op == EXP.construct)
|
|
ex = (cast(AssignExp)ex).e2;
|
|
if (auto ne = ex.isNewExp())
|
|
{
|
|
if (ne.placement)
|
|
{
|
|
}
|
|
/* See if initializer is a NewExp that can be allocated on the stack.
|
|
*/
|
|
else if (dsym.type.toBasetype().ty == Tclass)
|
|
{
|
|
/* Unsafe to allocate on stack if constructor is not `scope` because the `this` can leak.
|
|
* https://issues.dlang.org/show_bug.cgi?id=23145
|
|
*/
|
|
if (ne.member && !(ne.member.storage_class & STC.scope_))
|
|
{
|
|
import dmd.escape : setUnsafeDIP1000;
|
|
const inSafeFunc = sc.func && sc.func.isSafeBypassingInference(); // isSafeBypassingInference may call setUnsafe().
|
|
if (setUnsafeDIP1000(*sc, false, dsym.loc, "`scope` allocation of `%s` with a non-`scope` constructor", dsym))
|
|
errorSupplemental(ne.member.loc, "is the location of the constructor");
|
|
}
|
|
ne.onstack = 1;
|
|
dsym.onstack = true;
|
|
}
|
|
}
|
|
else if (auto fe = ex.isFuncExp())
|
|
{
|
|
// or a delegate that doesn't escape a reference to the function
|
|
FuncDeclaration f = fe.fd;
|
|
if (f.tookAddressOf)
|
|
f.tookAddressOf--;
|
|
}
|
|
else if (auto ale = ex.isArrayLiteralExp())
|
|
{
|
|
// or an array literal assigned to a `scope` variable
|
|
if (sc.useDIP1000 == FeatureState.enabled
|
|
&& !dsym.type.nextOf().needsDestruction())
|
|
ale.onstack = true;
|
|
}
|
|
}
|
|
|
|
Expression exp = ei.exp;
|
|
Expression e1 = new VarExp(dsym.loc, dsym);
|
|
|
|
void constructInit(bool isBlit)
|
|
{
|
|
if (isBlit)
|
|
exp = new BlitExp(dsym.loc, e1, exp);
|
|
else
|
|
exp = new ConstructExp(dsym.loc, e1, exp);
|
|
dsym.canassign++;
|
|
exp = exp.expressionSemantic(sc);
|
|
dsym.canassign--;
|
|
}
|
|
|
|
if (dsymIsRef) // follow logic similar to typesem.argumentMatchParameter() and statementsem.visitForeach()
|
|
{
|
|
dsym.storage_class |= STC.nodtor;
|
|
exp = exp.expressionSemantic(sc);
|
|
Type tp = dsym.type;
|
|
Type ta = exp.type;
|
|
if (!exp.isLvalue())
|
|
{
|
|
if (dsym.storage_class & STC.autoref)
|
|
{
|
|
dsym.storage_class &= ~STC.ref_;
|
|
constructInit(isBlit);
|
|
}
|
|
else
|
|
{
|
|
.error(dsym.loc, "rvalue `%s` cannot be assigned to `ref %s`", exp.toChars(), dsym.toChars());
|
|
exp = ErrorExp.get();
|
|
}
|
|
}
|
|
else if (!ta.constConv(tp))
|
|
{
|
|
if (dsym.storage_class & STC.autoref)
|
|
{
|
|
dsym.storage_class &= ~STC.ref_;
|
|
constructInit(false);
|
|
}
|
|
else
|
|
{
|
|
.error(dsym.loc, "type `%s` cannot be assigned to `ref %s %s`", ta.toChars(), tp.toChars(), dsym.toChars());
|
|
exp = ErrorExp.get();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
constructInit(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
constructInit(isBlit);
|
|
}
|
|
|
|
if (exp.op == EXP.error)
|
|
{
|
|
dsym._init = new ErrorInitializer();
|
|
ei = null;
|
|
}
|
|
else
|
|
ei.exp = exp.optimize(WANTvalue);
|
|
}
|
|
else
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=14166
|
|
// Don't run CTFE for the temporary variables inside typeof
|
|
dsym._init = dsym._init.initializerSemantic(sc, dsym.type, sc.intypeof == 1 ? INITnointerpret : INITinterpret);
|
|
import dmd.semantic2 : lowerStaticAAs;
|
|
lowerStaticAAs(dsym, sc);
|
|
auto init_err = dsym._init.isExpInitializer();
|
|
if (init_err && init_err.exp.op == EXP.showCtfeContext)
|
|
{
|
|
init_err.exp = ErrorExp.get();
|
|
errorSupplemental(dsym.loc, "compile time context created here");
|
|
}
|
|
}
|
|
}
|
|
else if (parent.isAggregateDeclaration())
|
|
{
|
|
dsym._scope = scx ? scx : sc.copy();
|
|
dsym._scope.setNoFree();
|
|
}
|
|
else if (dsym.storage_class & (STC.const_ | STC.immutable_ | STC.manifest) ||
|
|
dsym.type.isConst() || dsym.type.isImmutable() ||
|
|
sc.inCfile)
|
|
{
|
|
/* Because we may need the results of a const declaration in a
|
|
* subsequent type, such as an array dimension, before semantic2()
|
|
* gets ordinarily run, try to run semantic2() now.
|
|
* If a C array is of unknown size, the initializer can provide the size. Do this
|
|
* eagerly because C does it eagerly.
|
|
* Ignore failure.
|
|
*/
|
|
if (!inferred)
|
|
{
|
|
const errors = global.errors;
|
|
dsym.inuse++;
|
|
// Bug 20549. Don't try this on modules or packages, syntaxCopy
|
|
// could crash (inf. recursion) on a mod/pkg referencing itself
|
|
if (ei && (ei.exp.op != EXP.scope_ ? true : !ei.exp.isScopeExp().sds.isPackage()))
|
|
{
|
|
if (ei.exp.type)
|
|
{
|
|
// If exp is already resolved we are done, our original init exp
|
|
// could have a type painting that we need to respect
|
|
// e.g. ['a'] typed as string, or [['z'], ""] as string[]
|
|
// See https://issues.dlang.org/show_bug.cgi?id=15711
|
|
}
|
|
else
|
|
{
|
|
Expression exp = ei.exp.syntaxCopy();
|
|
|
|
bool needctfe = dsym.isDataseg() || (dsym.storage_class & STC.manifest);
|
|
if (needctfe)
|
|
sc = sc.startCTFE();
|
|
sc = sc.push();
|
|
sc.varDecl = dsym; // https://issues.dlang.org/show_bug.cgi?id=24051
|
|
exp = exp.expressionSemantic(sc);
|
|
exp = resolveProperties(sc, exp);
|
|
sc = sc.pop();
|
|
if (needctfe)
|
|
sc = sc.endCTFE();
|
|
ei.exp = exp;
|
|
}
|
|
|
|
Type tb2 = dsym.type.toBasetype();
|
|
Type ti = ei.exp.type.toBasetype();
|
|
|
|
/* The problem is the following code:
|
|
* struct CopyTest {
|
|
* double x;
|
|
* this(double a) { x = a * 10.0;}
|
|
* this(this) { x += 2.0; }
|
|
* }
|
|
* const CopyTest z = CopyTest(5.3); // ok
|
|
* const CopyTest w = z; // not ok, postblit not run
|
|
* static assert(w.x == 55.0);
|
|
* because the postblit doesn't get run on the initialization of w.
|
|
*/
|
|
if (auto ts = ti.isTypeStruct())
|
|
{
|
|
StructDeclaration sd = ts.sym;
|
|
/* Look to see if initializer involves a copy constructor
|
|
* (which implies a postblit)
|
|
*/
|
|
// there is a copy constructor
|
|
// and exp is the same struct
|
|
if (sd.postblit && tb2.toDsymbol(null) == sd)
|
|
{
|
|
// The only allowable initializer is a (non-copy) constructor
|
|
if (ei.exp.isLvalue())
|
|
.error(dsym.loc, "%s `%s` of type struct `%s` uses `this(this)`, which is not allowed in static initialization", dsym.kind, dsym.toPrettyChars, tb2.toChars());
|
|
}
|
|
}
|
|
}
|
|
|
|
dsym._init = dsym._init.initializerSemantic(sc, dsym.type, INITinterpret);
|
|
dsym.inuse--;
|
|
if (global.errors > errors)
|
|
{
|
|
dsym._init = new ErrorInitializer();
|
|
dsym.type = Type.terror;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
dsym._scope = scx ? scx : sc.copy();
|
|
dsym._scope.setNoFree();
|
|
}
|
|
}
|
|
sc = sc.pop();
|
|
}
|
|
|
|
Ldtor:
|
|
/* Build code to execute destruction, if necessary
|
|
*/
|
|
dsym.edtor = dsym.callScopeDtor(sc);
|
|
if (dsym.edtor)
|
|
{
|
|
if (sc.func && dsym.storage_class & (STC.static_ | STC.gshared))
|
|
dsym.edtor = dsym.edtor.expressionSemantic(sc._module._scope);
|
|
else
|
|
dsym.edtor = dsym.edtor.expressionSemantic(sc);
|
|
|
|
version (none)
|
|
{
|
|
// currently disabled because of std.stdio.stdin, stdout and stderr
|
|
if (dsym.isDataseg() && !(dsym.storage_class & STC.extern_))
|
|
.error(dsym.loc, "%s `%s` static storage variables cannot have destructors", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
}
|
|
|
|
dsym.semanticRun = PASS.semanticdone;
|
|
|
|
if (dsym.type.toBasetype().ty == Terror)
|
|
dsym.errors = true;
|
|
|
|
if(sc.scopesym && !sc.scopesym.isAggregateDeclaration())
|
|
{
|
|
for (ScopeDsymbol sym = sc.scopesym; sym && dsym.endlinnum == 0;
|
|
sym = sym.parent ? sym.parent.isScopeDsymbol() : null)
|
|
dsym.endlinnum = sym.endlinnum;
|
|
}
|
|
}
|
|
|
|
override void visit(TypeInfoDeclaration dsym)
|
|
{
|
|
assert(dsym._linkage == LINK.c);
|
|
}
|
|
|
|
override void visit(CAsmDeclaration dsym)
|
|
{
|
|
if (dsym.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
import dmd.iasm : asmSemantic;
|
|
asmSemantic(dsym, sc);
|
|
dsym.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
override void visit(BitFieldDeclaration dsym)
|
|
{
|
|
//printf("BitField::semantic('%s')\n", dsym.toChars());
|
|
if (dsym.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
|
|
visit(cast(VarDeclaration)dsym);
|
|
if (dsym.errors)
|
|
return;
|
|
|
|
if (!(sc.previews.bitfields || sc.inCfile))
|
|
{
|
|
version (IN_GCC)
|
|
.error(dsym.loc, "%s `%s` use `-fpreview=bitfields` for bitfield support", dsym.kind, dsym.toPrettyChars);
|
|
else
|
|
.error(dsym.loc, "%s `%s` use -preview=bitfields for bitfield support", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
|
|
if (!dsym.parent.isStructDeclaration() && !dsym.parent.isClassDeclaration())
|
|
{
|
|
.error(dsym.loc, "%s `%s` - bit-field must be member of struct, union, or class", dsym.kind, dsym.toPrettyChars);
|
|
}
|
|
|
|
sc = sc.startCTFE();
|
|
auto width = dsym.width.expressionSemantic(sc);
|
|
sc = sc.endCTFE();
|
|
width = width.ctfeInterpret();
|
|
if (!dsym.type.isIntegral())
|
|
{
|
|
// C11 6.7.2.1-5
|
|
error(width.loc, "bit-field type `%s` is not an integer type", dsym.type.toChars());
|
|
dsym.errors = true;
|
|
}
|
|
if (!width.isIntegerExp())
|
|
{
|
|
error(width.loc, "bit-field width `%s` is not an integer constant", dsym.width.toChars());
|
|
dsym.errors = true;
|
|
}
|
|
const uwidth = width.toInteger(); // uwidth is unsigned
|
|
if (uwidth == 0 && !dsym.isAnonymous())
|
|
{
|
|
error(width.loc, "bit-field `%s` has zero width", dsym.toChars());
|
|
dsym.errors = true;
|
|
}
|
|
const sz = dsym.type.size();
|
|
if (sz == SIZE_INVALID)
|
|
dsym.errors = true;
|
|
const max_width = sz * 8;
|
|
if (uwidth > max_width)
|
|
{
|
|
error(width.loc, "width `%lld` of bit-field `%s` does not fit in type `%s`", cast(long)uwidth, dsym.toChars(), dsym.type.toChars());
|
|
dsym.errors = true;
|
|
}
|
|
dsym.fieldWidth = cast(uint)uwidth;
|
|
}
|
|
|
|
override void visit(Import imp)
|
|
{
|
|
timeTraceBeginEvent(TimeTraceEventType.sema1Import);
|
|
scope (exit) timeTraceEndEvent(TimeTraceEventType.sema1Import, imp);
|
|
static if (LOG)
|
|
{
|
|
printf("Import::semantic('%s') %s\n", imp.toPrettyChars(), imp.id.toChars());
|
|
scope(exit)
|
|
printf("-Import::semantic('%s'), pkg = %p\n", imp.toChars(), imp.pkg);
|
|
}
|
|
if (imp.semanticRun > PASS.initial)
|
|
return;
|
|
|
|
if (imp._scope)
|
|
{
|
|
sc = imp._scope;
|
|
imp._scope = null;
|
|
}
|
|
if (!sc)
|
|
return;
|
|
|
|
imp.parent = sc.parent;
|
|
|
|
imp.semanticRun = PASS.semantic;
|
|
|
|
// Load if not already done so
|
|
if (!imp.mod)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=22857
|
|
// if parser errors occur when loading a module
|
|
// we should just stop compilation
|
|
if (imp.load(sc))
|
|
{
|
|
for (size_t i = 0; i < imp.aliasdecls.length; i++)
|
|
imp.aliasdecls[i].type = Type.terror;
|
|
return;
|
|
}
|
|
|
|
if (imp.mod)
|
|
{
|
|
imp.mod.importAll(null);
|
|
imp.mod.checkImportDeprecation(imp.loc, sc);
|
|
}
|
|
}
|
|
if (!imp.mod)
|
|
{
|
|
imp.semanticRun = PASS.semanticdone;
|
|
addImportDep(global.params.moduleDeps, imp, sc._module);
|
|
}
|
|
|
|
// Modules need a list of each imported module
|
|
|
|
// if inside a template instantiation, the instantianting
|
|
// module gets the import.
|
|
// https://issues.dlang.org/show_bug.cgi?id=17181
|
|
Module importer = sc._module;
|
|
if (sc.minst && sc.tinst)
|
|
{
|
|
importer = sc.minst;
|
|
if (!sc.tinst.importedModules.contains(imp.mod))
|
|
sc.tinst.importedModules.push(imp.mod);
|
|
}
|
|
//printf("%s imports %s\n", importer.toChars(), imp.mod.toChars());
|
|
if (!importer.aimports.contains(imp.mod))
|
|
importer.aimports.push(imp.mod);
|
|
|
|
if (sc.explicitVisibility)
|
|
imp.visibility = sc.visibility;
|
|
|
|
if (!imp.aliasId && !imp.names.length) // neither a selective nor a renamed import
|
|
{
|
|
ScopeDsymbol scopesym = sc.getScopesym();
|
|
|
|
if (!imp.isstatic)
|
|
{
|
|
scopesym.importScope(imp.mod, imp.visibility);
|
|
}
|
|
|
|
|
|
imp.addPackageAccess(scopesym);
|
|
}
|
|
|
|
// if a module has errors it means that parsing has failed.
|
|
if (!imp.mod.errors)
|
|
imp.mod.dsymbolSemantic(null);
|
|
|
|
if (imp.mod.needmoduleinfo)
|
|
{
|
|
//printf("module4 %s because of %s\n", importer.toChars(), imp.mod.toChars());
|
|
importer.needmoduleinfo = 1;
|
|
}
|
|
|
|
sc = sc.push(imp.mod);
|
|
sc.visibility = imp.visibility;
|
|
for (size_t i = 0; i < imp.aliasdecls.length; i++)
|
|
{
|
|
AliasDeclaration ad = imp.aliasdecls[i];
|
|
//printf("\tImport %s alias %s = %s, scope = %p\n", toPrettyChars(), aliases[i].toChars(), names[i].toChars(), ad._scope);
|
|
Dsymbol sym = imp.mod.search(imp.loc, imp.names[i], SearchOpt.ignorePrivateImports);
|
|
if (sym)
|
|
{
|
|
import dmd.access : symbolIsVisible;
|
|
if (!symbolIsVisible(sc, sym) && !sym.errors)
|
|
{
|
|
.error(imp.loc, "%s `%s` member `%s` is not visible from module `%s`", imp.mod.kind, imp.mod.toPrettyChars,
|
|
imp.names[i].toChars(), sc._module.toChars());
|
|
sym.errors = true;
|
|
}
|
|
ad.dsymbolSemantic(sc);
|
|
// If the import declaration is in non-root module,
|
|
// analysis of the aliased symbol is deferred.
|
|
// Therefore, don't see the ad.aliassym or ad.type here.
|
|
}
|
|
else
|
|
{
|
|
Dsymbol s = imp.mod.search_correct(imp.names[i]);
|
|
// https://issues.dlang.org/show_bug.cgi?id=23908
|
|
// Don't suggest symbols from the importer's module
|
|
if (s && s.parent != importer)
|
|
.error(imp.loc, "%s `%s` import `%s` not found, did you mean %s `%s`?", imp.mod.kind, imp.mod.toPrettyChars, imp.names[i].toChars(), s.kind(), s.toPrettyChars());
|
|
else
|
|
.error(imp.loc, "%s `%s` import `%s` not found", imp.mod.kind, imp.mod.toPrettyChars, imp.names[i].toChars());
|
|
ad.type = Type.terror;
|
|
}
|
|
}
|
|
sc = sc.pop();
|
|
|
|
imp.semanticRun = PASS.semanticdone;
|
|
addImportDep(global.params.moduleDeps, imp, sc._module);
|
|
}
|
|
|
|
void attribSemantic(AttribDeclaration ad)
|
|
{
|
|
if (ad.semanticRun != PASS.initial)
|
|
return;
|
|
ad.semanticRun = PASS.semantic;
|
|
Dsymbols* d = ad.include(sc);
|
|
//printf("\tAttribDeclaration::semantic '%s', d = %p\n",toChars(), d);
|
|
if (!d)
|
|
{
|
|
ad.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
|
|
Scope* sc2 = ad.newScope(sc);
|
|
bool errors;
|
|
for (size_t i = 0; i < d.length; i++)
|
|
{
|
|
Dsymbol s = (*d)[i];
|
|
s.dsymbolSemantic(sc2);
|
|
errors |= s.errors;
|
|
}
|
|
if (errors)
|
|
ad.errors = true;
|
|
if (sc2 != sc)
|
|
sc2.pop();
|
|
|
|
ad.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
override void visit(AttribDeclaration atd)
|
|
{
|
|
attribSemantic(atd);
|
|
}
|
|
|
|
override void visit(AnonDeclaration scd)
|
|
{
|
|
//printf("\tAnonDeclaration::semantic isunion:%d ptr:%p\n", scd.isunion, scd);
|
|
assert(sc.parent);
|
|
auto p = sc.parent.pastMixin();
|
|
auto ad = p.isAggregateDeclaration();
|
|
if (!ad)
|
|
{
|
|
error(scd.loc, "%s can only be a part of an aggregate, not %s `%s`", scd.kind(), p.kind(), p.toChars());
|
|
scd.errors = true;
|
|
return;
|
|
}
|
|
|
|
if (!scd.decl)
|
|
return;
|
|
|
|
sc = sc.push();
|
|
sc.stc &= ~(STC.auto_ | STC.scope_ | STC.static_ | STC.gshared);
|
|
sc.inunion = scd.isunion ? scd : null;
|
|
sc.resetAllFlags();
|
|
for (size_t i = 0; i < scd.decl.length; i++)
|
|
{
|
|
Dsymbol s = (*scd.decl)[i];
|
|
if (auto var = s.isVarDeclaration)
|
|
{
|
|
if (scd.isunion)
|
|
var.overlapped = true;
|
|
}
|
|
s.dsymbolSemantic(sc);
|
|
}
|
|
sc = sc.pop();
|
|
}
|
|
|
|
override void visit(PragmaDeclaration pd)
|
|
{
|
|
import dmd.pragmasem : pragmaDeclSemantic;
|
|
pragmaDeclSemantic(pd, sc);
|
|
}
|
|
|
|
override void visit(StaticIfDeclaration sid)
|
|
{
|
|
attribSemantic(sid);
|
|
}
|
|
|
|
override void visit(StaticForeachDeclaration sfd)
|
|
{
|
|
attribSemantic(sfd);
|
|
}
|
|
|
|
private Dsymbols* compileIt(MixinDeclaration cd)
|
|
{
|
|
//printf("MixinDeclaration::compileIt(loc = %d) %s\n", cd.loc.linnum, cd.exp.toChars());
|
|
OutBuffer buf;
|
|
if (expressionsToString(buf, sc, cd.exps, cd.loc, null, true))
|
|
return null;
|
|
|
|
const errors = global.errors;
|
|
const len = buf.length;
|
|
buf.writeByte(0);
|
|
const str = buf.extractSlice()[0 .. len];
|
|
const bool doUnittests = global.params.parsingUnittestsRequired();
|
|
scope p = new Parser!ASTCodegen(sc._module, str, false, global.errorSink, &global.compileEnv, doUnittests);
|
|
adjustLocForMixin(str, cd.loc, *p.baseLoc, global.params.mixinOut);
|
|
p.linnum = p.baseLoc.startLine;
|
|
p.nextToken();
|
|
|
|
auto d = p.parseDeclDefs(0);
|
|
if (global.errors != errors)
|
|
return null;
|
|
|
|
if (p.token.value != TOK.endOfFile)
|
|
{
|
|
.error(cd.loc, "%s `%s` incomplete mixin declaration `%s`", cd.kind, cd.toPrettyChars, str.ptr);
|
|
return null;
|
|
}
|
|
return d;
|
|
}
|
|
|
|
/***********************************************************
|
|
* https://dlang.org/spec/module.html#mixin-declaration
|
|
*/
|
|
override void visit(MixinDeclaration cd)
|
|
{
|
|
//printf("MixinDeclaration::semantic()\n");
|
|
if (!cd.compiled)
|
|
{
|
|
cd.decl = compileIt(cd);
|
|
attribAddMember(cd, sc, cd.scopesym);
|
|
cd.compiled = true;
|
|
|
|
if (cd._scope && cd.decl)
|
|
{
|
|
for (size_t i = 0; i < cd.decl.length; i++)
|
|
{
|
|
Dsymbol s = (*cd.decl)[i];
|
|
s.setScope(cd._scope);
|
|
}
|
|
}
|
|
}
|
|
attribSemantic(cd);
|
|
}
|
|
|
|
override void visit(CPPNamespaceDeclaration ns)
|
|
{
|
|
Identifier identFromSE (StringExp se)
|
|
{
|
|
const sident = se.toStringz();
|
|
if (!sident.length || !Identifier.isValidIdentifier(sident))
|
|
{
|
|
error(ns.exp.loc, "expected valid identifier for C++ namespace but got `%s`", se.toErrMsg());
|
|
return null;
|
|
}
|
|
else
|
|
return Identifier.idPool(sident);
|
|
}
|
|
|
|
if (ns.ident !is null)
|
|
return attribSemantic(ns);
|
|
|
|
ns.cppnamespace = sc.namespace;
|
|
sc = sc.startCTFE();
|
|
ns.exp = ns.exp.expressionSemantic(sc);
|
|
ns.exp = resolveProperties(sc, ns.exp);
|
|
sc = sc.endCTFE();
|
|
ns.exp = ns.exp.ctfeInterpret();
|
|
// Can be either a tuple of strings or a string itself
|
|
if (auto te = ns.exp.isTupleExp())
|
|
{
|
|
expandTuples(te.exps);
|
|
CPPNamespaceDeclaration current = ns.cppnamespace;
|
|
for (size_t d = 0; d < te.exps.length; ++d)
|
|
{
|
|
auto exp = (*te.exps)[d];
|
|
auto prev = d ? current : ns.cppnamespace;
|
|
current = (d + 1) != te.exps.length
|
|
? new CPPNamespaceDeclaration(ns.loc, exp, null)
|
|
: ns;
|
|
current.exp = exp;
|
|
current.cppnamespace = prev;
|
|
if (auto se = exp.toStringExp())
|
|
{
|
|
current.ident = identFromSE(se);
|
|
if (current.ident is null)
|
|
return; // An error happened in `identFromSE`
|
|
}
|
|
else
|
|
error(ns.exp.loc, "`%s`: index %llu is not a string constant, it is a `%s`",
|
|
ns.exp.toChars(), cast(ulong) d, ns.exp.type.toChars());
|
|
}
|
|
}
|
|
else if (auto se = ns.exp.toStringExp())
|
|
ns.ident = identFromSE(se);
|
|
// Empty Tuple
|
|
else if (ns.exp.isTypeExp() && ns.exp.isTypeExp().type.toBasetype().isTypeTuple())
|
|
{
|
|
}
|
|
else if (!ns.exp.type.isTypeError())
|
|
error(ns.exp.loc, "compile time string constant (or sequence) expected, not `%s`",
|
|
ns.exp.toChars());
|
|
attribSemantic(ns);
|
|
}
|
|
|
|
override void visit(UserAttributeDeclaration uad)
|
|
{
|
|
//printf("UserAttributeDeclaration::semantic() %p\n", this);
|
|
if (uad.decl && !uad._scope)
|
|
uad.Dsymbol.setScope(sc); // for function local symbols
|
|
arrayExpressionSemantic(uad.atts.peekSlice(), sc, true);
|
|
return attribSemantic(uad);
|
|
}
|
|
|
|
override void visit(StaticAssert sa)
|
|
{
|
|
if (sa.semanticRun < PASS.semanticdone)
|
|
sa.semanticRun = PASS.semanticdone;
|
|
else
|
|
return;
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=24645
|
|
// This is a short-circuit. Usually, static assert conditions are evaluated
|
|
// in semantic2, but it's not uncommon to use this pattern:
|
|
// ---
|
|
// version(X)
|
|
// {}
|
|
// else
|
|
// static assert(false, "unsupported platform");
|
|
// ---
|
|
// However, without this short-circuit, the static assert error may get drowned
|
|
// out by subsequent semantic1 (import) errors. Only short-circuit at module scope though,
|
|
// inside mixin templates you want an instantiation trace (which you don't get here).
|
|
if (sc.parent && sc.parent.isModule())
|
|
if (auto i = sa.exp.isIntegerExp())
|
|
if (i.toInteger() == 0)
|
|
staticAssertFail(sa, sc);
|
|
}
|
|
|
|
override void visit(DebugSymbol ds)
|
|
{
|
|
//printf("DebugSymbol::semantic() %s\n", toChars());
|
|
if (ds.semanticRun < PASS.semanticdone)
|
|
ds.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
override void visit(VersionSymbol vs)
|
|
{
|
|
if (vs.semanticRun < PASS.semanticdone)
|
|
vs.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
override void visit(Package pkg)
|
|
{
|
|
if (pkg.semanticRun < PASS.semanticdone)
|
|
pkg.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
override void visit(Module m)
|
|
{
|
|
if (m.semanticRun != PASS.initial)
|
|
return;
|
|
|
|
timeTraceBeginEvent(TimeTraceEventType.sema1Module);
|
|
scope (exit) timeTraceEndEvent(TimeTraceEventType.sema1Module, m);
|
|
|
|
//printf("+Module::semantic(this = %p, '%s'): parent = %p\n", this, toChars(), parent);
|
|
m.semanticRun = PASS.semantic;
|
|
// Note that modules get their own scope, from scratch.
|
|
// This is so regardless of where in the syntax a module
|
|
// gets imported, it is unaffected by context.
|
|
Scope* sc = m._scope; // see if already got one from importAll()
|
|
if (!sc)
|
|
{
|
|
sc = Scope.createGlobal(m, global.errorSink); // create root scope
|
|
}
|
|
|
|
//printf("Module = %p, linkage = %d\n", sc.scopesym, sc.linkage);
|
|
// Pass 1 semantic routines: do public side of the definition
|
|
m.members.foreachDsymbol( (s)
|
|
{
|
|
//printf("\tModule('%s'): '%s'.dsymbolSemantic()\n", toChars(), s.toChars());
|
|
s.dsymbolSemantic(sc);
|
|
m.runDeferredSemantic();
|
|
});
|
|
|
|
if (m.userAttribDecl)
|
|
{
|
|
m.userAttribDecl.dsymbolSemantic(sc);
|
|
}
|
|
if (!m._scope)
|
|
{
|
|
sc = sc.pop();
|
|
sc.pop(); // 2 pops because Scope.createGlobal() created 2
|
|
}
|
|
m.semanticRun = PASS.semanticdone;
|
|
//printf("-Module::semantic(this = %p, '%s'): parent = %p\n", this, toChars(), parent);
|
|
}
|
|
|
|
override void visit(EnumDeclaration ed)
|
|
{
|
|
enumSemantic(sc, ed);
|
|
}
|
|
|
|
override void visit(EnumMember em)
|
|
{
|
|
enumMemberSemantic(sc, em);
|
|
}
|
|
|
|
override void visit(TemplateDeclaration tempdecl)
|
|
{
|
|
templateDeclarationSemantic(sc, tempdecl);
|
|
}
|
|
|
|
override void visit(TemplateInstance ti)
|
|
{
|
|
templateInstanceSemantic(ti, sc, ArgumentList());
|
|
}
|
|
|
|
override void visit(TemplateMixin tm)
|
|
{
|
|
static if (LOG)
|
|
{
|
|
printf("+TemplateMixin.dsymbolSemantic('%s', this=%p)\n", tm.toChars(), tm);
|
|
fflush(stdout);
|
|
}
|
|
if (tm.semanticRun != PASS.initial)
|
|
{
|
|
// When a class/struct contains mixin members, and is done over
|
|
// because of forward references, never reach here so semanticRun
|
|
// has been reset to PASS.initial.
|
|
static if (LOG)
|
|
{
|
|
printf("\tsemantic done\n");
|
|
}
|
|
return;
|
|
}
|
|
tm.semanticRun = PASS.semantic;
|
|
static if (LOG)
|
|
{
|
|
printf("\tdo semantic\n");
|
|
}
|
|
|
|
Scope* scx = null;
|
|
if (tm._scope)
|
|
{
|
|
sc = tm._scope;
|
|
scx = tm._scope; // save so we don't make redundant copies
|
|
tm._scope = null;
|
|
}
|
|
|
|
/* Run semantic on each argument, place results in tiargs[],
|
|
* then find best match template with tiargs
|
|
*/
|
|
if (!tm.findTempDecl(sc) || !tm.semanticTiargs(sc) || !tm.findBestMatch(sc, ArgumentList()))
|
|
{
|
|
if (tm.semanticRun == PASS.initial) // forward reference had occurred
|
|
{
|
|
//printf("forward reference - deferring\n");
|
|
return deferDsymbolSemantic(sc, tm, scx);
|
|
}
|
|
|
|
tm.inst = tm;
|
|
tm.errors = true;
|
|
return; // error recovery
|
|
}
|
|
|
|
auto tempdecl = tm.tempdecl.isTemplateDeclaration();
|
|
assert(tempdecl);
|
|
|
|
if (!tm.ident)
|
|
{
|
|
/* Assign scope local unique identifier, as same as lambdas.
|
|
*/
|
|
const(char)[] s = "__mixin";
|
|
|
|
if (FuncDeclaration func = sc.parent.isFuncDeclaration())
|
|
{
|
|
tm.symtab = func.localsymtab;
|
|
if (tm.symtab)
|
|
{
|
|
// Inside template constraint, symtab is not set yet.
|
|
goto L1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tm.symtab = sc.parent.isScopeDsymbol().symtab;
|
|
L1:
|
|
assert(tm.symtab);
|
|
tm.ident = Identifier.generateId(s, tm.symtab.length + 1);
|
|
tm.symtab.insert(tm);
|
|
}
|
|
}
|
|
|
|
tm.inst = tm;
|
|
tm.parent = sc.parent;
|
|
|
|
/* Detect recursive mixin instantiations.
|
|
*/
|
|
for (Dsymbol s = tm.parent; s; s = s.parent)
|
|
{
|
|
//printf("\ts = '%s'\n", s.toChars());
|
|
TemplateMixin tmix = s.isTemplateMixin();
|
|
if (!tmix || tempdecl != tmix.tempdecl)
|
|
continue;
|
|
|
|
/* Different argument list lengths happen with variadic args
|
|
*/
|
|
if (tm.tiargs.length != tmix.tiargs.length)
|
|
continue;
|
|
|
|
for (size_t i = 0; i < tm.tiargs.length; i++)
|
|
{
|
|
RootObject o = (*tm.tiargs)[i];
|
|
Type ta = isType(o);
|
|
Expression ea = isExpression(o);
|
|
Dsymbol sa = isDsymbol(o);
|
|
RootObject tmo = (*tmix.tiargs)[i];
|
|
if (ta)
|
|
{
|
|
Type tmta = isType(tmo);
|
|
if (!tmta)
|
|
goto Lcontinue;
|
|
if (!ta.equals(tmta))
|
|
goto Lcontinue;
|
|
}
|
|
else if (ea)
|
|
{
|
|
Expression tme = isExpression(tmo);
|
|
if (!tme || !ea.equals(tme))
|
|
goto Lcontinue;
|
|
}
|
|
else if (sa)
|
|
{
|
|
Dsymbol tmsa = isDsymbol(tmo);
|
|
if (sa != tmsa)
|
|
goto Lcontinue;
|
|
}
|
|
else
|
|
assert(0);
|
|
}
|
|
.error(tm.loc, "%s `%s` recursive mixin instantiation", tm.kind, tm.toPrettyChars);
|
|
return;
|
|
|
|
Lcontinue:
|
|
continue;
|
|
}
|
|
|
|
// Copy the syntax trees from the TemplateDeclaration
|
|
tm.members = Dsymbol.arraySyntaxCopy(tempdecl.members);
|
|
if (!tm.members)
|
|
return;
|
|
|
|
tm.symtab = new DsymbolTable();
|
|
|
|
sc.getScopesym().importScope(tm, Visibility(Visibility.Kind.public_));
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("\tcreate scope for template parameters '%s'\n", tm.toChars());
|
|
}
|
|
Scope* scy = sc.push(tm);
|
|
scy.parent = tm;
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=930
|
|
*
|
|
* If the template that is to be mixed in is in the scope of a template
|
|
* instance, we have to also declare the type aliases in the new mixin scope.
|
|
*/
|
|
auto parentInstance = tempdecl.parent ? tempdecl.parent.isTemplateInstance() : null;
|
|
if (parentInstance)
|
|
parentInstance.declareParameters(scy);
|
|
|
|
tm.argsym = new ScopeDsymbol();
|
|
tm.argsym.parent = scy.parent;
|
|
Scope* argscope = scy.push(tm.argsym);
|
|
|
|
const errorsave = global.errors;
|
|
|
|
// Declare each template parameter as an alias for the argument type
|
|
tm.declareParameters(argscope);
|
|
|
|
// Add members to enclosing scope, as well as this scope
|
|
tm.members.foreachDsymbol(s => s.addMember(argscope, tm));
|
|
|
|
// Do semantic() analysis on template instance members
|
|
static if (LOG)
|
|
{
|
|
printf("\tdo semantic() on template instance members '%s'\n", tm.toChars());
|
|
}
|
|
Scope* sc2 = argscope.push(tm);
|
|
//size_t deferred_dim = Module.deferred.length;
|
|
|
|
__gshared int nest;
|
|
//printf("%d\n", nest);
|
|
if (++nest > global.recursionLimit)
|
|
{
|
|
global.gag = 0; // ensure error message gets printed
|
|
.error(tm.loc, "%s `%s` recursive expansion", tm.kind, tm.toPrettyChars);
|
|
fatal();
|
|
}
|
|
|
|
tm.members.foreachDsymbol( s => s.setScope(sc2) );
|
|
|
|
tm.members.foreachDsymbol( s => s.importAll(sc2) );
|
|
|
|
tm.members.foreachDsymbol( s => s.dsymbolSemantic(sc2) );
|
|
|
|
nest--;
|
|
|
|
/* In DeclDefs scope, TemplateMixin does not have to handle deferred symbols.
|
|
* Because the members would already call Module.addDeferredSemantic() for themselves.
|
|
* See Struct, Class, Interface, and EnumDeclaration.dsymbolSemantic().
|
|
*/
|
|
//if (!sc.func && Module.deferred.length > deferred_dim) {}
|
|
|
|
AggregateDeclaration ad = tm.isMember();
|
|
if (sc.func && !ad)
|
|
{
|
|
tm.semantic2(sc2);
|
|
tm.semantic3(sc2);
|
|
}
|
|
|
|
// Give additional context info if error occurred during instantiation
|
|
if (global.errors != errorsave)
|
|
{
|
|
.error(tm.loc, "%s `%s` error instantiating", tm.kind, tm.toPrettyChars);
|
|
tm.errors = true;
|
|
}
|
|
|
|
sc2.pop();
|
|
argscope.pop();
|
|
scy.pop();
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("-TemplateMixin.dsymbolSemantic('%s', this=%p)\n", tm.toChars(), tm);
|
|
}
|
|
}
|
|
|
|
override void visit(Nspace ns)
|
|
{
|
|
if (ns.semanticRun != PASS.initial)
|
|
return;
|
|
static if (LOG)
|
|
{
|
|
printf("+Nspace::semantic('%s')\n", ns.toChars());
|
|
scope(exit) printf("-Nspace::semantic('%s')\n", ns.toChars());
|
|
}
|
|
if (ns._scope)
|
|
{
|
|
sc = ns._scope;
|
|
ns._scope = null;
|
|
}
|
|
if (!sc)
|
|
return;
|
|
|
|
bool repopulateMembers = false;
|
|
if (ns.identExp)
|
|
{
|
|
// resolve the namespace identifier
|
|
sc = sc.startCTFE();
|
|
Expression resolved = ns.identExp.expressionSemantic(sc);
|
|
resolved = resolveProperties(sc, resolved);
|
|
sc = sc.endCTFE();
|
|
resolved = resolved.ctfeInterpret();
|
|
StringExp name = resolved.toStringExp();
|
|
TupleExp tup = name ? null : resolved.isTupleExp();
|
|
if (!tup && !name)
|
|
{
|
|
error(ns.loc, "expected string expression for namespace name, got `%s`", ns.identExp.toChars());
|
|
return;
|
|
}
|
|
ns.identExp = resolved; // we don't need to keep the old AST around
|
|
if (name)
|
|
{
|
|
const(char)[] ident = name.toStringz();
|
|
if (ident.length == 0 || !Identifier.isValidIdentifier(ident))
|
|
{
|
|
error(ns.loc, "expected valid identifier for C++ namespace but got `%.*s`", cast(int)ident.length, ident.ptr);
|
|
return;
|
|
}
|
|
ns.ident = Identifier.idPool(ident);
|
|
}
|
|
else
|
|
{
|
|
// create namespace stack from the tuple
|
|
Nspace parentns = ns;
|
|
foreach (i, exp; *tup.exps)
|
|
{
|
|
name = exp.toStringExp();
|
|
if (!name)
|
|
{
|
|
error(ns.loc, "expected string expression for namespace name, got `%s`", exp.toChars());
|
|
return;
|
|
}
|
|
const(char)[] ident = name.toStringz();
|
|
if (ident.length == 0 || !Identifier.isValidIdentifier(ident))
|
|
{
|
|
error(ns.loc, "expected valid identifier for C++ namespace but got `%.*s`", cast(int)ident.length, ident.ptr);
|
|
return;
|
|
}
|
|
if (i == 0)
|
|
{
|
|
ns.ident = Identifier.idPool(ident);
|
|
}
|
|
else
|
|
{
|
|
// insert the new namespace
|
|
Nspace childns = new Nspace(ns.loc, Identifier.idPool(ident), null, parentns.members);
|
|
parentns.members = new Dsymbols;
|
|
parentns.members.push(childns);
|
|
parentns = childns;
|
|
repopulateMembers = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ns.semanticRun = PASS.semantic;
|
|
ns.parent = sc.parent;
|
|
// Link does not matter here, if the UDA is present it will error
|
|
checkGNUABITag(ns, LINK.cpp);
|
|
|
|
if (!ns.members)
|
|
{
|
|
ns.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
assert(sc);
|
|
sc = sc.push(ns);
|
|
sc.linkage = LINK.cpp; // note that namespaces imply C++ linkage
|
|
sc.parent = ns;
|
|
foreach (s; *ns.members)
|
|
{
|
|
if (repopulateMembers)
|
|
{
|
|
s.addMember(sc, sc.scopesym);
|
|
s.setScope(sc);
|
|
}
|
|
s.importAll(sc);
|
|
}
|
|
foreach (s; *ns.members)
|
|
{
|
|
static if (LOG)
|
|
{
|
|
printf("\tmember '%s', kind = '%s'\n", s.toChars(), s.kind());
|
|
}
|
|
s.dsymbolSemantic(sc);
|
|
}
|
|
sc.pop();
|
|
ns.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
/// Do the semantic analysis on the external interface to the function.
|
|
override void visit(FuncDeclaration funcdecl)
|
|
{
|
|
funcDeclarationSemantic(sc, funcdecl);
|
|
}
|
|
|
|
override void visit(CtorDeclaration ctd)
|
|
{
|
|
//printf("CtorDeclaration::semantic() %p %s\n", ctd, ctd.toChars());
|
|
if (ctd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
if (ctd._scope)
|
|
{
|
|
sc = ctd._scope;
|
|
ctd._scope = null;
|
|
}
|
|
|
|
ctd.parent = sc.parent;
|
|
Dsymbol p = ctd.toParentDecl();
|
|
AggregateDeclaration ad = p.isAggregateDeclaration();
|
|
if (!ad)
|
|
{
|
|
error(ctd.loc, "constructor can only be a member of aggregate, not %s `%s`", p.kind(), p.toChars());
|
|
ctd.type = Type.terror;
|
|
ctd.errors = true;
|
|
return;
|
|
}
|
|
|
|
sc = sc.push();
|
|
|
|
if (sc.stc & STC.static_)
|
|
{
|
|
if (sc.stc & STC.shared_)
|
|
error(ctd.loc, "`shared static` has no effect on a constructor inside a `shared static` block. Use `shared static this()`");
|
|
else
|
|
error(ctd.loc, "`static` has no effect on a constructor inside a `static` block. Use `static this()`");
|
|
}
|
|
|
|
sc.stc &= ~STC.static_; // not a static constructor
|
|
|
|
funcDeclarationSemantic(sc, ctd);
|
|
// Check short constructor: this() => expr;
|
|
if (ctd.fbody)
|
|
{
|
|
if (auto s = ctd.fbody.isExpStatement())
|
|
{
|
|
if (s.exp)
|
|
{
|
|
auto ce = s.exp.isCallExp();
|
|
// check this/super before semantic
|
|
if (!ce || (!ce.e1.isThisExp() && !ce.e1.isSuperExp()))
|
|
{
|
|
s.exp = s.exp.expressionSemantic(sc);
|
|
if (s.exp.type.ty != Tvoid)
|
|
error(s.loc, "can only return void expression, `this` call or `super` call from constructor");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sc.pop();
|
|
|
|
if (ctd.errors)
|
|
return;
|
|
|
|
TypeFunction tf = ctd.type.toTypeFunction();
|
|
immutable dim = tf.parameterList.length;
|
|
auto sd = ad.isStructDeclaration();
|
|
|
|
/* See if it's the default constructor
|
|
* But, template constructor should not become a default constructor.
|
|
*/
|
|
if (ad && (!ctd.parent.isTemplateInstance() || ctd.parent.isTemplateMixin()))
|
|
{
|
|
if (!sd)
|
|
{
|
|
if (dim == 0 && tf.parameterList.varargs == VarArg.none)
|
|
ad.defaultCtor = ctd;
|
|
return;
|
|
}
|
|
|
|
if (dim == 0 && tf.parameterList.varargs == VarArg.none) // empty default ctor w/o any varargs
|
|
{
|
|
if (ctd.fbody || !(ctd.storage_class & STC.disable))
|
|
{
|
|
.error(ctd.loc, "%s `%s` default constructor for structs only allowed " ~
|
|
"with `@disable`, no body, and no parameters", ctd.kind, ctd.toPrettyChars);
|
|
ctd.storage_class |= STC.disable;
|
|
ctd.fbody = null;
|
|
}
|
|
sd.noDefaultCtor = true;
|
|
}
|
|
else if (dim == 0 && tf.parameterList.varargs != VarArg.none) // allow varargs only ctor
|
|
{
|
|
}
|
|
else if (dim && !tf.parameterList.hasArgsWithoutDefault)
|
|
{
|
|
if (ctd.storage_class & STC.disable)
|
|
{
|
|
.error(ctd.loc, "%s `%s` is marked `@disable`, so it cannot have default "~
|
|
"arguments for all parameters.", ctd.kind, ctd.toPrettyChars);
|
|
errorSupplemental(ctd.loc, "Use `@disable this();` if you want to disable default initialization.");
|
|
}
|
|
else
|
|
.error(ctd.loc, "%s `%s` all parameters have default arguments, "~
|
|
"but structs cannot have default constructors.", ctd.kind, ctd.toPrettyChars);
|
|
}
|
|
else if ((dim == 1 || (dim > 1 && tf.parameterList[1].defaultArg)))
|
|
{
|
|
//printf("tf: %s\n", toChars(tf));
|
|
auto param = tf.parameterList[0];
|
|
if (param.type.mutableOf().unSharedOf() == sd.type.mutableOf().unSharedOf())
|
|
{
|
|
//printf("copy constructor %p\n", ctd);
|
|
assert(!ctd.isCpCtor && !ctd.isMoveCtor);
|
|
if (param.storageClass & STC.ref_)
|
|
ctd.isCpCtor = true; // copy constructor
|
|
else
|
|
ctd.isMoveCtor = true; // move constructor
|
|
assert(!(ctd.isCpCtor && ctd.isMoveCtor));
|
|
}
|
|
}
|
|
}
|
|
// https://issues.dlang.org/show_bug.cgi?id=22593
|
|
else if (auto ti = ctd.parent.isTemplateInstance())
|
|
{
|
|
checkHasBothRvalueAndCpCtor(sd, ctd, ti);
|
|
}
|
|
}
|
|
|
|
override void visit(PostBlitDeclaration pbd)
|
|
{
|
|
//printf("PostBlitDeclaration::semantic() %s\n", toChars());
|
|
//printf("ident: %s, %s, %p, %p\n", ident.toChars(), Id.dtor.toChars(), ident, Id.dtor);
|
|
//printf("stc = x%llx\n", sc.stc);
|
|
if (pbd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
if (pbd._scope)
|
|
{
|
|
sc = pbd._scope;
|
|
pbd._scope = null;
|
|
}
|
|
|
|
pbd.parent = sc.parent;
|
|
Dsymbol p = pbd.toParent2();
|
|
StructDeclaration ad = p.isStructDeclaration();
|
|
if (!ad)
|
|
{
|
|
error(pbd.loc, "postblit can only be a member of struct, not %s `%s`", p.kind(), p.toChars());
|
|
pbd.type = Type.terror;
|
|
pbd.errors = true;
|
|
return;
|
|
}
|
|
if (pbd.ident == Id.postblit && pbd.semanticRun < PASS.semantic)
|
|
ad.postblits.push(pbd);
|
|
if (!pbd.type)
|
|
pbd.type = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, pbd.storage_class);
|
|
|
|
sc = sc.push();
|
|
sc.stc &= ~STC.static_; // not static
|
|
sc.linkage = LINK.d;
|
|
|
|
funcDeclarationSemantic(sc, pbd);
|
|
|
|
sc.pop();
|
|
}
|
|
|
|
override void visit(DtorDeclaration dd)
|
|
{
|
|
//printf("DtorDeclaration::semantic() %s\n", dd.toChars());
|
|
//printf("ident: %s, %s, %p, %p\n", dd.ident.toChars(), Id.dtor.toChars(), dd.ident, Id.dtor);
|
|
if (dd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
if (dd._scope)
|
|
{
|
|
sc = dd._scope;
|
|
dd._scope = null;
|
|
}
|
|
|
|
dd.parent = sc.parent;
|
|
Dsymbol p = dd.toParent2();
|
|
AggregateDeclaration ad = p.isAggregateDeclaration();
|
|
if (!ad)
|
|
{
|
|
error(dd.loc, "destructor can only be a member of aggregate, not %s `%s`", p.kind(), p.toChars());
|
|
dd.type = Type.terror;
|
|
dd.errors = true;
|
|
return;
|
|
}
|
|
|
|
if (ad.isClassDeclaration() && ad.classKind == ClassKind.d)
|
|
{
|
|
// Class destructors are implicitly `scope`
|
|
dd.storage_class |= STC.scope_;
|
|
}
|
|
|
|
if (dd.ident == Id.dtor && dd.semanticRun < PASS.semantic)
|
|
ad.userDtors.push(dd);
|
|
if (!dd.type)
|
|
{
|
|
dd.type = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, dd.storage_class);
|
|
if (ad.classKind == ClassKind.cpp && dd.ident == Id.dtor)
|
|
{
|
|
if (auto cldec = ad.isClassDeclaration())
|
|
{
|
|
assert (cldec.cppDtorVtblIndex == -1); // double-call check already by dd.type
|
|
if (cldec.baseClass && cldec.baseClass.cppDtorVtblIndex != -1)
|
|
{
|
|
// override the base virtual
|
|
cldec.cppDtorVtblIndex = cldec.baseClass.cppDtorVtblIndex;
|
|
}
|
|
else if (!dd.isFinal())
|
|
{
|
|
// reserve the dtor slot for the destructor (which we'll create later)
|
|
cldec.cppDtorVtblIndex = cast(int)cldec.vtbl.length;
|
|
cldec.vtbl.push(dd);
|
|
if (target.cpp.twoDtorInVtable)
|
|
cldec.vtbl.push(dd); // deleting destructor uses a second slot
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sc = sc.push();
|
|
sc.stc &= ~STC.static_; // not a static destructor
|
|
if (sc.linkage != LINK.cpp)
|
|
sc.linkage = LINK.d;
|
|
|
|
funcDeclarationSemantic(sc, dd);
|
|
|
|
sc.pop();
|
|
}
|
|
|
|
void visitStaticCDtorDeclaration(FuncDeclaration sd, bool isDestructor)
|
|
{
|
|
if (sd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
if (sd._scope)
|
|
{
|
|
sc = sd._scope;
|
|
sd._scope = null;
|
|
}
|
|
sd.parent = sc.parent;
|
|
Dsymbol p = sd.parent.pastMixin();
|
|
const bool isShared = !!(sd.isSharedStaticDtorDeclaration() || sd.isSharedStaticCtorDeclaration());
|
|
const(char)* what = isDestructor ? "destructor" : "constructor";
|
|
if (!p.isScopeDsymbol())
|
|
{
|
|
const(char)* s = isShared ? "shared " : "";
|
|
error(sd.loc, "`%sstatic` %s can only be member of module/aggregate/template, not %s `%s`", s, what, p.kind(), p.toChars());
|
|
sd.type = Type.terror;
|
|
sd.errors = true;
|
|
return;
|
|
}
|
|
|
|
if (!sd.type)
|
|
sd.type = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, sd.storage_class);
|
|
|
|
/* If the static [dc]tor appears within a template instantiation,
|
|
* it could get called multiple times by the module constructors
|
|
* for different modules. Thus, protect it with a gate.
|
|
*/
|
|
if (sd.isInstantiated() && sd.semanticRun < PASS.semantic)
|
|
{
|
|
/* Add this prefix to the constructor:
|
|
* ```
|
|
* static int gate;
|
|
* if ([--|++]gate != [0|1]) return; // dependant on ctor/dtor
|
|
* ```
|
|
* or, for shared constructor:
|
|
* ```
|
|
* shared int gate;
|
|
* enum op = isDestructor ? "-=" : "+=";
|
|
* enum cmp = isDestructor ? 0 : 1;
|
|
* if (core.atomic.atomicOp!op(gate, 1) != cmp) return;
|
|
* ```
|
|
*/
|
|
|
|
auto v = new VarDeclaration(Loc.initial, Type.tint32, Id.gate, null);
|
|
v.storage_class = STC.temp | STC.static_ | (isShared ? STC.shared_ : STC.none);
|
|
|
|
auto sa = new Statements();
|
|
Statement s = new ExpStatement(Loc.initial, v);
|
|
sa.push(s);
|
|
|
|
Expression e;
|
|
if (isShared)
|
|
{
|
|
e = doAtomicOp(isDestructor ? "-=" : "+=", v.ident, IntegerExp.literal!(1));
|
|
if (e is null)
|
|
{
|
|
.error(sd.loc, "%s `%s` shared static %s within a template require `core.atomic : atomicOp` to be present", sd.kind, sd.toPrettyChars, what);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
IntegerExp one = isDestructor ? IntegerExp.literal!(-1) : IntegerExp.literal!(1);
|
|
e = new AddAssignExp(
|
|
Loc.initial, new IdentifierExp(Loc.initial, v.ident), one);
|
|
}
|
|
IntegerExp cmp = isDestructor ? IntegerExp.literal!0 : IntegerExp.literal!1;
|
|
e = new EqualExp(EXP.notEqual, Loc.initial, e, cmp);
|
|
s = new IfStatement(Loc.initial, null, e, new ReturnStatement(Loc.initial, null), null, Loc.initial);
|
|
|
|
sa.push(s);
|
|
if (sd.fbody)
|
|
sa.push(sd.fbody);
|
|
|
|
sd.fbody = new CompoundStatement(Loc.initial, sa);
|
|
if (isDestructor)
|
|
(cast(StaticDtorDeclaration)sd).vgate = v;
|
|
}
|
|
const LINK save = sc.linkage;
|
|
if (save != LINK.d)
|
|
{
|
|
const(char)* s = isShared ? "shared " : "";
|
|
deprecation(sd.loc, "`%sstatic` %s can only be of D linkage", s, what);
|
|
// Just correct it
|
|
sc.linkage = LINK.d;
|
|
}
|
|
funcDeclarationSemantic(sc, sd);
|
|
sc.linkage = save;
|
|
|
|
// We're going to need ModuleInfo
|
|
Module m = sd.getModule();
|
|
if (!m)
|
|
m = sc._module;
|
|
if (m)
|
|
{
|
|
m.needmoduleinfo = 1;
|
|
//printf("module2 %s needs moduleinfo\n", m.toChars());
|
|
}
|
|
}
|
|
override void visit(StaticCtorDeclaration scd)
|
|
{
|
|
//printf("StaticCtorDeclaration::semantic()\n");
|
|
visitStaticCDtorDeclaration(scd, false);
|
|
|
|
foreachUda(scd, sc, (Expression e) {
|
|
import dmd.attrib : isEnumAttribute;
|
|
if (!isEnumAttribute(e, Id.udaStandalone))
|
|
return 0;
|
|
|
|
if (auto sharedCtor = scd.isSharedStaticCtorDeclaration())
|
|
{
|
|
auto trust = sharedCtor.type.isTypeFunction().trust;
|
|
if (trust != TRUST.system && trust != TRUST.trusted)
|
|
error(e.loc, "a module constructor using `@%s` must be `@system` or `@trusted`", Id.udaStandalone.toChars());
|
|
sharedCtor.standalone = true;
|
|
}
|
|
else
|
|
.error(e.loc, "`@%s` can only be used on shared static constructors", Id.udaStandalone.toChars());
|
|
|
|
return 1;
|
|
});
|
|
}
|
|
|
|
override void visit(StaticDtorDeclaration sdd)
|
|
{
|
|
visitStaticCDtorDeclaration(sdd, true);
|
|
}
|
|
|
|
override void visit(InvariantDeclaration invd)
|
|
{
|
|
if (invd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
if (invd._scope)
|
|
{
|
|
sc = invd._scope;
|
|
invd._scope = null;
|
|
}
|
|
|
|
invd.parent = sc.parent;
|
|
Dsymbol p = invd.parent.pastMixin();
|
|
AggregateDeclaration ad = p.isAggregateDeclaration();
|
|
if (!ad)
|
|
{
|
|
error(invd.loc, "`invariant` can only be a member of aggregate, not %s `%s`", p.kind(), p.toChars());
|
|
invd.type = Type.terror;
|
|
invd.errors = true;
|
|
return;
|
|
}
|
|
if (invd.ident != Id.classInvariant &&
|
|
invd.semanticRun < PASS.semantic &&
|
|
!ad.isUnionDeclaration() // users are on their own with union fields
|
|
)
|
|
{
|
|
invd.fixupInvariantIdent(ad.invs.length);
|
|
ad.invs.push(invd);
|
|
}
|
|
if (!invd.type)
|
|
invd.type = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, invd.storage_class);
|
|
|
|
sc = sc.push();
|
|
sc.stc &= ~STC.static_; // not a static invariant
|
|
sc.stc |= STC.const_; // invariant() is always const
|
|
sc.contract = Contract.invariant_;
|
|
sc.linkage = LINK.d;
|
|
|
|
funcDeclarationSemantic(sc, invd);
|
|
|
|
sc.pop();
|
|
}
|
|
|
|
override void visit(UnitTestDeclaration utd)
|
|
{
|
|
if (utd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
if (utd._scope)
|
|
{
|
|
sc = utd._scope;
|
|
utd._scope = null;
|
|
}
|
|
|
|
utd.visibility = sc.visibility;
|
|
|
|
utd.parent = sc.parent;
|
|
Dsymbol p = utd.parent.pastMixin();
|
|
if (!p.isScopeDsymbol())
|
|
{
|
|
error(utd.loc, "`unittest` can only be a member of module/aggregate/template, not %s `%s`", p.kind(), p.toChars());
|
|
utd.type = Type.terror;
|
|
utd.errors = true;
|
|
return;
|
|
}
|
|
|
|
if (global.params.useUnitTests)
|
|
{
|
|
if (!utd.type)
|
|
utd.type = new TypeFunction(ParameterList(), Type.tvoid, LINK.d, utd.storage_class);
|
|
Scope* sc2 = sc.push();
|
|
sc2.linkage = LINK.d;
|
|
funcDeclarationSemantic(sc, utd);
|
|
sc2.pop();
|
|
}
|
|
|
|
version (none)
|
|
{
|
|
// We're going to need ModuleInfo even if the unit tests are not
|
|
// compiled in, because other modules may import this module and refer
|
|
// to this ModuleInfo.
|
|
// (This doesn't make sense to me?)
|
|
Module m = utd.getModule();
|
|
if (!m)
|
|
m = sc._module;
|
|
if (m)
|
|
{
|
|
//printf("module3 %s needs moduleinfo\n", m.toChars());
|
|
m.needmoduleinfo = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
override void visit(NewDeclaration nd)
|
|
{
|
|
//printf("NewDeclaration::semantic()\n");
|
|
if (nd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
if (!nd.type)
|
|
nd.type = new TypeFunction(ParameterList(), Type.tvoid.pointerTo(), LINK.d, nd.storage_class);
|
|
|
|
funcDeclarationSemantic(sc, nd);
|
|
}
|
|
|
|
override void visit(StructDeclaration sd)
|
|
{
|
|
enum log = false;
|
|
if (log) printf("+StructDeclaration::semantic(this=%p, '%s', sizeok = %d)\n", sd, sd.toPrettyChars(), sd.sizeok);
|
|
|
|
//static int count; if (++count == 20) assert(0);
|
|
|
|
if (sd.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
const errors = global.errors;
|
|
|
|
//printf("+StructDeclaration::semantic(this=%p, '%s', sizeok = %d)\n", sd, sd.toPrettyChars(), sd.sizeok);
|
|
Scope* scx = null;
|
|
if (sd._scope)
|
|
{
|
|
sc = sd._scope;
|
|
scx = sd._scope; // save so we don't make redundant copies
|
|
sd._scope = null;
|
|
}
|
|
|
|
if (!sd.parent)
|
|
{
|
|
assert(sc.parent && sc.func);
|
|
sd.parent = sc.parent;
|
|
}
|
|
assert(sd.parent && !sd.isAnonymous());
|
|
|
|
if (sd.errors)
|
|
sd.type = Type.terror;
|
|
if (sd.semanticRun == PASS.initial)
|
|
sd.type = sd.type.addSTC(sc.stc | sd.storage_class);
|
|
sd.type = sd.type.typeSemantic(sd.loc, sc);
|
|
auto ts = sd.type.isTypeStruct();
|
|
if (ts)
|
|
{
|
|
if (ts.sym != sd)
|
|
{
|
|
TemplateInstance ti = ts.sym.isInstantiated();
|
|
if (ti && isError(ti))
|
|
ts.sym = sd;
|
|
/* For C modules, if module A contains `struct S;` and
|
|
* module B contains `struct S { members...}` then replace
|
|
* the former with the latter
|
|
*/
|
|
else if (!ts.sym.members && sd.members)
|
|
ts.sym = sd;
|
|
}
|
|
}
|
|
|
|
// Ungag errors when not speculative
|
|
Ungag ungag = sd.ungagSpeculative();
|
|
|
|
if (sd.semanticRun == PASS.initial)
|
|
{
|
|
sd.visibility = sc.visibility;
|
|
|
|
if (sd.alignment.isUnknown()) // can be set already by `struct __declspec(align(N)) Tag { ... }`
|
|
sd.alignment = sc.alignment();
|
|
|
|
sd.storage_class |= sc.stc;
|
|
if (sd.storage_class & STC.abstract_)
|
|
.error(sd.loc, "%s `%s` structs, unions cannot be `abstract`", sd.kind, sd.toPrettyChars);
|
|
|
|
sd.userAttribDecl = sc.userAttribDecl;
|
|
|
|
if (sc.linkage == LINK.cpp)
|
|
sd.classKind = ClassKind.cpp;
|
|
else if (sc.linkage == LINK.c)
|
|
sd.classKind = ClassKind.c;
|
|
sd.cppnamespace = sc.namespace;
|
|
sd.cppmangle = sc.cppmangle;
|
|
}
|
|
else if (sd.symtab && !scx)
|
|
return;
|
|
|
|
sd.semanticRun = PASS.semantic;
|
|
checkGNUABITag(sd, sc.linkage);
|
|
|
|
if (!sd.members) // if opaque declaration
|
|
{
|
|
if (log) printf("\topaque declaration %s\n", sd.toChars());
|
|
sd.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
if (!sd.symtab)
|
|
{
|
|
sd.symtab = new DsymbolTable();
|
|
|
|
sd.members.foreachDsymbol( s => s.addMember(sc, sd) );
|
|
}
|
|
|
|
auto sc2 = sd.newScope(sc);
|
|
|
|
/* Set scope so if there are forward references, we still might be able to
|
|
* resolve individual members like enums.
|
|
*/
|
|
sd.members.foreachDsymbol( s => s.setScope(sc2) );
|
|
sd.members.foreachDsymbol( s => s.importAll(sc2) );
|
|
sd.members.foreachDsymbol( (s) { s.dsymbolSemantic(sc2); if (sd.errors) s.errors = true; } );
|
|
|
|
if (sd.errors)
|
|
sd.type = Type.terror;
|
|
|
|
if (!sd.determineFields())
|
|
{
|
|
if (sd.type.ty != Terror)
|
|
{
|
|
.error(sd.loc, "%s `%s` circular or forward reference", sd.kind, sd.toPrettyChars);
|
|
sd.errors = true;
|
|
sd.type = Type.terror;
|
|
}
|
|
|
|
sc2.pop();
|
|
sd.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
/* Following special member functions creation needs semantic analysis
|
|
* completion of sub-structs in each field types. For example, buildDtor
|
|
* needs to check existence of elaborate dtor in type of each fields.
|
|
* See the case in compilable/test14838.d
|
|
*/
|
|
foreach (v; sd.fields)
|
|
{
|
|
Type tb = v.type.baseElemOf();
|
|
if (tb.ty != Tstruct)
|
|
continue;
|
|
auto sdec = (cast(TypeStruct)tb).sym;
|
|
if (sdec.semanticRun >= PASS.semanticdone)
|
|
continue;
|
|
|
|
sc2.pop();
|
|
|
|
if (log) printf("\tdeferring %s\n", sd.toChars());
|
|
return deferDsymbolSemantic(sc, sd, scx);
|
|
}
|
|
|
|
/* Look for special member functions.
|
|
*/
|
|
sd.disableNew = sd.search(Loc.initial, Id.classNew) !is null;
|
|
|
|
// Look for the constructor
|
|
sd.ctor = sd.searchCtor();
|
|
|
|
buildDtors(sd, sc2);
|
|
|
|
bool hasCopyCtor;
|
|
bool hasMoveCtor;
|
|
bool needCopyCtor;
|
|
bool needMoveCtor;
|
|
needCopyOrMoveCtor(sd, hasCopyCtor, hasMoveCtor, needCopyCtor, needMoveCtor);
|
|
//printf("%s hasCopy %d hasMove %d needCopy %d needMove %d\n", sd.toChars(), hasCopyCtor, hasMoveCtor, needCopyCtor, needMoveCtor);
|
|
|
|
/* When generating a move ctor, generate a copy ctor too, otherwise
|
|
* https://github.com/s-ludwig/taggedalgebraic/issues/75
|
|
*/
|
|
if (0 && needMoveCtor && !hasCopyCtor)
|
|
{
|
|
needCopyCtor = true;
|
|
}
|
|
|
|
if (needCopyCtor)
|
|
{
|
|
assert(hasCopyCtor == false);
|
|
buildCopyOrMoveCtor(sd, sc2, false); // build copy constructor
|
|
hasCopyCtor = true;
|
|
}
|
|
if (needMoveCtor)
|
|
{
|
|
assert(hasMoveCtor == false);
|
|
buildCopyOrMoveCtor(sd, sc2, true); // build move constructor
|
|
hasMoveCtor = true;
|
|
}
|
|
sd.hasCopyCtor = hasCopyCtor;
|
|
sd.hasMoveCtor = hasMoveCtor;
|
|
|
|
sd.postblit = buildPostBlit(sd, sc2);
|
|
|
|
buildOpAssign(sd, sc2);
|
|
buildOpEquals(sd, sc2);
|
|
|
|
if (!sc2.inCfile &&
|
|
global.params.useTypeInfo && Type.dtypeinfo) // these functions are used for TypeInfo
|
|
{
|
|
sd.xeq = buildXopEquals(sd, sc2);
|
|
sd.xcmp = buildXopCmp(sd, sc2);
|
|
sd.xhash = buildXtoHash(sd, sc2);
|
|
}
|
|
|
|
sd.inv = buildInv(sd, sc2);
|
|
|
|
sd.semanticRun = PASS.semanticdone;
|
|
if (log) printf("-StructDeclaration::semantic(this=%p, '%s', sizeok = %d)\n", sd, sd.toPrettyChars(), sd.sizeok);
|
|
|
|
sc2.pop();
|
|
|
|
if (sd.ctor)
|
|
{
|
|
Dsymbol scall = sd.search(Loc.initial, Id.opCall);
|
|
if (scall)
|
|
{
|
|
const xerrors = global.startGagging();
|
|
sc = sc.push();
|
|
sc.tinst = null;
|
|
sc.minst = null;
|
|
auto fcall = resolveFuncCall(sd.loc, sc, scall, null, null, ArgumentList(), FuncResolveFlag.quiet);
|
|
sc = sc.pop();
|
|
global.endGagging(xerrors);
|
|
|
|
if (fcall && fcall.isStatic())
|
|
{
|
|
.error(fcall.loc, "%s `%s` `static opCall` is hidden by constructors and can never be called", sd.kind, sd.toPrettyChars);
|
|
errorSupplemental(fcall.loc, "Please use a factory method instead, or replace all constructors with `static opCall`.");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ts && ts.sym != sd)
|
|
{
|
|
StructDeclaration sym = ts.sym;
|
|
if (sd.isCsymbol() && sym.isCsymbol())
|
|
{
|
|
|
|
if (!isCCompatible(sd, sym))
|
|
{
|
|
// Already issued an error.
|
|
errorSupplemental(sd.loc, "C %ss with the same name from different imports are merged", sd.kind);
|
|
}
|
|
else {
|
|
/* This is two structs imported from different C files.
|
|
* Just ignore sd, the second one. The first one will always
|
|
* be found when going through the type.
|
|
*/
|
|
}
|
|
}
|
|
else
|
|
{
|
|
version (none)
|
|
{
|
|
printf("this = %p %s\n", sd, sd.toChars());
|
|
printf("type = %d sym = %p, %s\n", sd.type.ty, sym, sym.toPrettyChars());
|
|
}
|
|
// https://issues.dlang.org/show_bug.cgi?id=19024
|
|
.error(sd.loc, "%s `%s` already exists at %s. Perhaps in another function with the same name?", sd.kind, sd.toPrettyChars, sym.loc.toChars());
|
|
}
|
|
}
|
|
|
|
if (global.errors != errors)
|
|
{
|
|
// The type is no good.
|
|
sd.type = Type.terror;
|
|
sd.errors = true;
|
|
if (sd.deferred)
|
|
sd.deferred.errors = true;
|
|
}
|
|
|
|
if (sd.deferred && !global.gag)
|
|
{
|
|
sd.deferred.semantic2(sc);
|
|
sd.deferred.semantic3(sc);
|
|
}
|
|
|
|
version (none)
|
|
{
|
|
// @@@DEPRECATED_2.110@@@ https://dlang.org/deprecate.html#scope%20as%20a%20type%20constraint
|
|
// Deprecated in 2.100
|
|
// Make an error in 2.110
|
|
if (sd.storage_class & STC.scope_)
|
|
deprecation(sd.loc, "`scope` as a type constraint is deprecated. Use `scope` at the usage site.");
|
|
}
|
|
//printf("-StructDeclaration::semantic(this=%p, '%s', sizeok = %d)\n", sd, sd.toPrettyChars(), sd.sizeok);
|
|
}
|
|
|
|
//
|
|
// Checks if two structs are compatible
|
|
// Implements the rules according to C23 6.2.7
|
|
//
|
|
static bool isCCompatible(StructDeclaration a, StructDeclaration b)
|
|
{
|
|
// Get the name of a type, while avoiding exposing "__tagXXX" anonymous structs
|
|
static const(char)* typeName(Type t)
|
|
{
|
|
if (TypeStruct ts = t.isTypeStruct())
|
|
{
|
|
if (ts.sym.ident.toString().startsWith("__tag"))
|
|
return ts.sym.isUnionDeclaration() ? "(anonymous union)".ptr: "(anonymous struct)".ptr;
|
|
}
|
|
return t.toChars();
|
|
}
|
|
|
|
void incompatError()
|
|
{
|
|
.error(a.loc, "%s `%s` already exists with an incompatible definition.",
|
|
a.kind, typeName(a.type));
|
|
errorSupplemental(b.loc, "previously declared here");
|
|
}
|
|
|
|
|
|
// For recursive calls into unnamed structs (so Type.equals() doesn't work).
|
|
static bool isCCompatibleUnnamedStruct(Type a, Type b)
|
|
{
|
|
TypeStruct ats = a.isTypeStruct();
|
|
if (!ats) return false;
|
|
TypeStruct bts = b.isTypeStruct();
|
|
if (!bts) return false;
|
|
// Hack, anonymous structs within a struct are given
|
|
// an anonymous id starting with __tag.
|
|
if (!ats.sym.ident.toString().startsWith("__tag"))
|
|
return false;
|
|
if (!bts.sym.ident.toString().startsWith("__tag"))
|
|
return false;
|
|
return isCCompatible(ats.sym, bts.sym);
|
|
}
|
|
|
|
if (a.fields.length != b.fields.length)
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a.loc, "`%s` has %zu field(s) while `%s` has %zu field(s)",
|
|
a.toPrettyChars(), a.fields.length, b.toPrettyChars(), b.fields.length);
|
|
return false;
|
|
}
|
|
// both are structs or both are unions
|
|
if ((a.isUnionDeclaration() is null) != (b.isUnionDeclaration() is null))
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a.loc, "`%s` is a %s while `%s` is a %s",
|
|
a.toPrettyChars(), a.kind, b.toPrettyChars(), b.kind);
|
|
return false;
|
|
}
|
|
if (a.alignment != b.alignment)
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a.loc, "`%s` has different alignment or packing", a.toPrettyChars());
|
|
if (a.alignment.isDefault() && ! b.alignment.isDefault())
|
|
{
|
|
errorSupplemental(a.loc, "`%s` alignment: default", a.toPrettyChars());
|
|
errorSupplemental(b.loc, "`%s` alignment: %u",
|
|
b.toPrettyChars(), cast(uint)b.alignment.get());
|
|
}
|
|
else if (!a.alignment.isDefault() && b.alignment.isDefault())
|
|
{
|
|
errorSupplemental(a.loc, "`%s` alignment: %u",
|
|
a.toPrettyChars(), cast(uint)a.alignment.get());
|
|
errorSupplemental(b.loc, "`%s` alignment: default",
|
|
b.toPrettyChars());
|
|
}
|
|
else if (a.alignment.get() != b.alignment.get())
|
|
{
|
|
errorSupplemental(a.loc, "`%s` alignment: %u",
|
|
a.toPrettyChars(), cast(uint)a.alignment.get());
|
|
errorSupplemental(b.loc, "`%s` alignment: %u",
|
|
b.toPrettyChars(), cast(uint)b.alignment.get());
|
|
}
|
|
if (a.alignment.isPack() != b.alignment.isPack())
|
|
{
|
|
errorSupplemental(a.loc, "`%s` packed: %s",
|
|
a.toPrettyChars(), a.alignment.isPack()?"true".ptr:"false".ptr);
|
|
errorSupplemental(b.loc, "`%s` packed: %s",
|
|
b.toPrettyChars(), b.alignment.isPack()?"true".ptr:"false".ptr);
|
|
}
|
|
return false;
|
|
}
|
|
foreach (size_t i, VarDeclaration a_field; a.fields[])
|
|
{
|
|
VarDeclaration b_field = b.fields[i];
|
|
//
|
|
// — there shall be a one-to-one correspondence between
|
|
// their members such that each pair of corresponding
|
|
// members are declared with compatible types;
|
|
//
|
|
if (!a_field.type.equals(b_field.type) && !isCCompatibleUnnamedStruct(a_field.type, b_field.type))
|
|
{
|
|
// Already errored, just bail
|
|
incompatError();
|
|
if (a_field.type.isTypeError()) return false;
|
|
if (b_field.type.isTypeError()) return false;
|
|
|
|
errorSupplemental(a_field.loc, "Field %zu differs in type", i);
|
|
errorSupplemental(a_field.loc, "typeof(%s): %s",
|
|
a_field.toChars(), typeName(a_field.type));
|
|
errorSupplemental(b_field.loc, "typeof(%s): %s",
|
|
b_field.toChars(), typeName(b_field.type));
|
|
return false;
|
|
}
|
|
//
|
|
// — if one member of the pair is declared with an
|
|
// alignment specifier, the second is declared with an
|
|
// equivalent alignment specifier;
|
|
//
|
|
if (a_field.alignment != b_field.alignment)
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a_field.loc, "Field %zu differs in alignment or packing", i);
|
|
if (a_field.alignment.isDefault() && ! b_field.alignment.isDefault())
|
|
{
|
|
errorSupplemental(a_field.loc, "`%s.%s` alignment: default",
|
|
a.toPrettyChars(),a_field.toChars());
|
|
errorSupplemental(b_field.loc, "`%s.%s` alignment: %u",
|
|
b.toPrettyChars(), b_field.toChars(), cast(uint)b_field.alignment.get());
|
|
}
|
|
else if (!a_field.alignment.isDefault() && b_field.alignment.isDefault())
|
|
{
|
|
errorSupplemental(a_field.loc, "`%s.%s` alignment: %u",
|
|
a.toPrettyChars(), a_field.toChars(), cast(uint)a_field.alignment.get());
|
|
errorSupplemental(b_field.loc, "`%s.%s` alignment: default",
|
|
b.toPrettyChars(), b_field.toChars());
|
|
}
|
|
else if (a_field.alignment.get() != b_field.alignment.get())
|
|
{
|
|
errorSupplemental(a_field.loc, "`%s.%s` alignment: %u",
|
|
a.toPrettyChars(), a_field.toChars(),
|
|
cast(uint)a_field.alignment.get());
|
|
errorSupplemental(b_field.loc, "`%s.%s` alignment: %u",
|
|
b.toPrettyChars(), b_field.toChars(),
|
|
cast(uint)b_field.alignment.get());
|
|
}
|
|
if (a_field.alignment.isPack() != b_field.alignment.isPack())
|
|
{
|
|
errorSupplemental(a_field.loc, "`%s.%s` packed: %s",
|
|
a.toPrettyChars(), a_field.toChars(),
|
|
a_field.alignment.isPack()?"true".ptr:"false".ptr);
|
|
errorSupplemental(b_field.loc, "`%s.%s` packed: %s",
|
|
b.toPrettyChars(), b_field.toChars(),
|
|
b_field.alignment.isPack()?"true".ptr:"false".ptr);
|
|
}
|
|
return false;
|
|
}
|
|
//
|
|
// - and, if one member of the pair is declared with a
|
|
// name, the second is declared with the same name.
|
|
//
|
|
if (a_field.ident.isAnonymous())
|
|
{
|
|
if (!b_field.ident.isAnonymous())
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a_field.loc, "Field %zu differs in name", i);
|
|
errorSupplemental(a_field.loc, "(anonymous)", a_field.ident.toChars());
|
|
errorSupplemental(b_field.loc, "%s", b_field.ident.toChars());
|
|
return false;
|
|
}
|
|
}
|
|
else if (b_field.ident.isAnonymous())
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a_field.loc, "Field %zu differs in name", i);
|
|
errorSupplemental(a_field.loc, "%s", a_field.ident.toChars());
|
|
errorSupplemental(b_field.loc, "(anonymous)");
|
|
return false;
|
|
}
|
|
else if (a_field.ident != b_field.ident)
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a_field.loc, "Field %zu differs in name", i);
|
|
errorSupplemental(a_field.loc, "%s", a_field.ident.toChars());
|
|
errorSupplemental(b_field.loc, "%s", b_field.ident.toChars());
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// For two structures or unions, corresponding bit-fields shall have the same widths.
|
|
//
|
|
BitFieldDeclaration bfa = a_field.isBitFieldDeclaration();
|
|
BitFieldDeclaration bfb = b_field.isBitFieldDeclaration();
|
|
if ((bfa is null) != (bfb is null))
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a_field.loc, "Field %zu differs in being a bitfield", i);
|
|
if (bfa is null)
|
|
{
|
|
errorSupplemental(a_field.loc, "`%s.%s` is not a bitfield",
|
|
a.toPrettyChars(), a_field.toChars());
|
|
errorSupplemental(b_field.loc, "`%s.%s` is a bitfield",
|
|
b.toPrettyChars(), b_field.toChars());
|
|
}
|
|
else if (bfb is null)
|
|
{
|
|
errorSupplemental(a_field.loc, "`%s.%s` *is a bitfield",
|
|
a.toPrettyChars(), a_field.toChars());
|
|
errorSupplemental(b_field.loc, "`%s.%s` is not a bitfield",
|
|
b.toPrettyChars(), b_field.toChars());
|
|
}
|
|
return false;
|
|
}
|
|
if (bfa !is null && bfb !is null)
|
|
{
|
|
if (bfa.fieldWidth != bfb.fieldWidth)
|
|
{
|
|
incompatError();
|
|
errorSupplemental(a_field.loc, "Field %zu differs in bitfield width", i);
|
|
errorSupplemental(a_field.loc, "`%s.%s`: %u",
|
|
a.toPrettyChars(), a_field.toChars(), bfa.fieldWidth);
|
|
errorSupplemental(b_field.loc, "`%s.%s`: %u",
|
|
b.toPrettyChars(), b_field.toChars(), bfb.fieldWidth);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void interfaceSemantic(ClassDeclaration cd)
|
|
{
|
|
cd.vtblInterfaces = new BaseClasses();
|
|
cd.vtblInterfaces.reserve(cd.interfaces.length);
|
|
foreach (b; cd.interfaces)
|
|
{
|
|
cd.vtblInterfaces.push(b);
|
|
b.copyBaseInterfaces(cd.vtblInterfaces);
|
|
}
|
|
}
|
|
|
|
override void visit(ClassDeclaration cldec)
|
|
{
|
|
//printf("ClassDeclaration.dsymbolSemantic(%s), type = %p, sizeok = %d, this = %p\n", cldec.toChars(), cldec.type, cldec.sizeok, this);
|
|
//printf("\tparent = %p, '%s'\n", sc.parent, sc.parent ? sc.parent.toChars() : "");
|
|
//printf("sc.stc = %x\n", sc.stc);
|
|
|
|
//{ static int n; if (++n == 20) *(char*)0=0; }
|
|
|
|
if (cldec.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
const errors = global.errors;
|
|
|
|
//printf("+ClassDeclaration.dsymbolSemantic(%s), type = %p, sizeok = %d, this = %p\n", toChars(), type, sizeok, this);
|
|
|
|
Scope* scx = null;
|
|
if (cldec._scope)
|
|
{
|
|
sc = cldec._scope;
|
|
scx = cldec._scope; // save so we don't make redundant copies
|
|
cldec._scope = null;
|
|
}
|
|
|
|
if (!cldec.parent)
|
|
{
|
|
assert(sc.parent);
|
|
cldec.parent = sc.parent;
|
|
}
|
|
|
|
if (cldec.errors)
|
|
cldec.type = Type.terror;
|
|
if (cldec.semanticRun == PASS.initial)
|
|
cldec.type = cldec.type.addSTC(sc.stc | cldec.storage_class);
|
|
cldec.type = cldec.type.typeSemantic(cldec.loc, sc);
|
|
if (auto tc = cldec.type.isTypeClass())
|
|
if (tc.sym != cldec)
|
|
{
|
|
auto ti = tc.sym.isInstantiated();
|
|
if (ti && isError(ti))
|
|
tc.sym = cldec;
|
|
}
|
|
|
|
// Ungag errors when not speculative
|
|
Ungag ungag = cldec.ungagSpeculative();
|
|
|
|
if (cldec.semanticRun == PASS.initial)
|
|
{
|
|
cldec.visibility = sc.visibility;
|
|
|
|
cldec.storage_class |= sc.stc;
|
|
if (cldec.storage_class & STC.auto_)
|
|
.error(cldec.loc, "%s `%s` storage class `auto` is invalid when declaring a class, did you mean to use `scope`?", cldec.kind, cldec.toPrettyChars);
|
|
if (cldec.storage_class & STC.scope_)
|
|
cldec.stack = true;
|
|
if (cldec.storage_class & STC.abstract_)
|
|
cldec.isabstract = ThreeState.yes;
|
|
|
|
cldec.userAttribDecl = sc.userAttribDecl;
|
|
|
|
if (sc.linkage == LINK.cpp)
|
|
cldec.classKind = ClassKind.cpp;
|
|
cldec.cppnamespace = sc.namespace;
|
|
cldec.cppmangle = sc.cppmangle;
|
|
if (sc.linkage == LINK.objc)
|
|
objc.setObjc(cldec);
|
|
}
|
|
else if (cldec.symtab && !scx)
|
|
{
|
|
return;
|
|
}
|
|
cldec.semanticRun = PASS.semantic;
|
|
checkGNUABITag(cldec, sc.linkage);
|
|
checkMustUseReserved(cldec);
|
|
|
|
if (cldec.baseok < Baseok.done)
|
|
{
|
|
/* https://issues.dlang.org/show_bug.cgi?id=12078
|
|
* https://issues.dlang.org/show_bug.cgi?id=12143
|
|
* https://issues.dlang.org/show_bug.cgi?id=15733
|
|
* While resolving base classes and interfaces, a base may refer
|
|
* the member of this derived class. In that time, if all bases of
|
|
* this class can be determined, we can go forward the semantc process
|
|
* beyond the Lancestorsdone. To do the recursive semantic analysis,
|
|
* temporarily set and unset `_scope` around exp().
|
|
*/
|
|
T resolveBase(T)(lazy T exp)
|
|
{
|
|
if (!scx)
|
|
{
|
|
scx = sc.copy();
|
|
scx.setNoFree();
|
|
}
|
|
static if (!is(T == void))
|
|
{
|
|
cldec._scope = scx;
|
|
auto r = exp();
|
|
cldec._scope = null;
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
cldec._scope = scx;
|
|
exp();
|
|
cldec._scope = null;
|
|
}
|
|
}
|
|
|
|
cldec.baseok = Baseok.start;
|
|
|
|
// Expand any tuples in baseclasses[]
|
|
for (size_t i = 0; i < cldec.baseclasses.length;)
|
|
{
|
|
auto b = (*cldec.baseclasses)[i];
|
|
b.type = resolveBase(b.type.typeSemantic(cldec.loc, sc));
|
|
|
|
Type tb = b.type.toBasetype();
|
|
if (auto tup = tb.isTypeTuple())
|
|
{
|
|
cldec.baseclasses.remove(i);
|
|
size_t dim = Parameter.dim(tup.arguments);
|
|
for (size_t j = 0; j < dim; j++)
|
|
{
|
|
Parameter arg = Parameter.getNth(tup.arguments, j);
|
|
b = new BaseClass(arg.type);
|
|
cldec.baseclasses.insert(i + j, b);
|
|
}
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
|
|
if (cldec.baseok >= Baseok.done)
|
|
{
|
|
//printf("%s already semantic analyzed, semanticRun = %d\n", toChars(), semanticRun);
|
|
if (cldec.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
goto Lancestorsdone;
|
|
}
|
|
|
|
// See if there's a base class as first in baseclasses[]
|
|
if (cldec.baseclasses.length)
|
|
{
|
|
BaseClass* b = (*cldec.baseclasses)[0];
|
|
Type tb = b.type.toBasetype();
|
|
TypeClass tc = tb.isTypeClass();
|
|
if (!tc)
|
|
{
|
|
if (b.type != Type.terror)
|
|
.error(cldec.loc, "%s `%s` base type must be `class` or `interface`, not `%s`", cldec.kind, cldec.toPrettyChars, b.type.toChars());
|
|
cldec.baseclasses.remove(0);
|
|
goto L7;
|
|
}
|
|
if (tc.sym.isDeprecated())
|
|
{
|
|
if (!cldec.isDeprecated())
|
|
{
|
|
// Deriving from deprecated class makes this one deprecated too
|
|
cldec.setDeprecated();
|
|
tc.checkDeprecated(cldec.loc, sc);
|
|
}
|
|
}
|
|
if (tc.sym.isInterfaceDeclaration())
|
|
goto L7;
|
|
|
|
for (ClassDeclaration cdb = tc.sym; cdb; cdb = cdb.baseClass)
|
|
{
|
|
if (cdb == cldec)
|
|
{
|
|
.error(cldec.loc, "%s `%s` circular inheritance", cldec.kind, cldec.toPrettyChars);
|
|
cldec.baseclasses.remove(0);
|
|
goto L7;
|
|
}
|
|
}
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=11034
|
|
* Class inheritance hierarchy
|
|
* and instance size of each classes are orthogonal information.
|
|
* Therefore, even if tc.sym.sizeof == Sizeok.none,
|
|
* we need to set baseClass field for class covariance check.
|
|
*/
|
|
cldec.baseClass = tc.sym;
|
|
b.sym = cldec.baseClass;
|
|
|
|
if (tc.sym.baseok < Baseok.done)
|
|
resolveBase(tc.sym.dsymbolSemantic(null)); // Try to resolve forward reference
|
|
if (tc.sym.baseok < Baseok.done)
|
|
{
|
|
//printf("\ttry later, forward reference of base class %s\n", tc.sym.toChars());
|
|
if (tc.sym._scope)
|
|
Module.addDeferredSemantic(tc.sym);
|
|
cldec.baseok = Baseok.none;
|
|
}
|
|
L7:
|
|
}
|
|
|
|
// Treat the remaining entries in baseclasses as interfaces
|
|
// Check for errors, handle forward references
|
|
int multiClassError = cldec.baseClass is null ? 0 : 1;
|
|
|
|
BCLoop:
|
|
for (size_t i = (cldec.baseClass ? 1 : 0); i < cldec.baseclasses.length;)
|
|
{
|
|
BaseClass* b = (*cldec.baseclasses)[i];
|
|
Type tb = b.type.toBasetype();
|
|
TypeClass tc = tb.isTypeClass();
|
|
if (!tc || !tc.sym.isInterfaceDeclaration())
|
|
{
|
|
// It's a class
|
|
if (tc)
|
|
{
|
|
if (multiClassError == 0)
|
|
{
|
|
.error(cldec.loc,"`%s`: base class must be specified first, " ~
|
|
"before any interfaces.", cldec.toPrettyChars());
|
|
multiClassError += 1;
|
|
}
|
|
else if (multiClassError >= 1)
|
|
{
|
|
if(multiClassError == 1)
|
|
.error(cldec.loc, "`%s`: multiple class inheritance is not supported." ~
|
|
" Use multiple interface inheritance and/or composition.", cldec.toPrettyChars());
|
|
multiClassError += 1;
|
|
|
|
if (tc.sym.fields.length)
|
|
errorSupplemental(cldec.loc,"`%s` has fields, consider making it a member of `%s`",
|
|
b.type.toChars(), cldec.type.toChars());
|
|
else
|
|
errorSupplemental(cldec.loc,"`%s` has no fields, consider making it an `interface`",
|
|
b.type.toChars());
|
|
}
|
|
}
|
|
// It's something else: e.g. `int` in `class Foo : Bar, int { ... }`
|
|
else if (b.type != Type.terror)
|
|
{
|
|
error(cldec.loc,"`%s`: base type must be `interface`, not `%s`",
|
|
cldec.toPrettyChars(), b.type.toChars());
|
|
}
|
|
cldec.baseclasses.remove(i);
|
|
continue;
|
|
}
|
|
|
|
// Check for duplicate interfaces
|
|
for (size_t j = (cldec.baseClass ? 1 : 0); j < i; j++)
|
|
{
|
|
BaseClass* b2 = (*cldec.baseclasses)[j];
|
|
if (b2.sym == tc.sym)
|
|
{
|
|
.error(cldec.loc, "%s `%s` inherits from duplicate interface `%s`", cldec.kind, cldec.toPrettyChars, b2.sym.toChars());
|
|
cldec.baseclasses.remove(i);
|
|
continue BCLoop;
|
|
}
|
|
}
|
|
if (tc.sym.isDeprecated())
|
|
{
|
|
if (!cldec.isDeprecated())
|
|
{
|
|
// Deriving from deprecated class makes this one deprecated too
|
|
cldec.setDeprecated();
|
|
tc.checkDeprecated(cldec.loc, sc);
|
|
}
|
|
}
|
|
|
|
b.sym = tc.sym;
|
|
|
|
if (tc.sym.baseok < Baseok.done)
|
|
resolveBase(tc.sym.dsymbolSemantic(null)); // Try to resolve forward reference
|
|
if (tc.sym.baseok < Baseok.done)
|
|
{
|
|
//printf("\ttry later, forward reference of base %s\n", tc.sym.toChars());
|
|
if (tc.sym._scope)
|
|
Module.addDeferredSemantic(tc.sym);
|
|
cldec.baseok = Baseok.none;
|
|
}
|
|
i++;
|
|
}
|
|
if (cldec.baseok == Baseok.none)
|
|
{
|
|
// Forward referencee of one or more bases, try again later
|
|
//printf("\tL%d semantic('%s') failed due to forward references\n", __LINE__, toChars());
|
|
return deferDsymbolSemantic(sc, cldec, scx);
|
|
}
|
|
cldec.baseok = Baseok.done;
|
|
|
|
if (cldec.classKind == ClassKind.objc || (cldec.baseClass && cldec.baseClass.classKind == ClassKind.objc))
|
|
cldec.classKind = ClassKind.objc; // Objective-C classes do not inherit from Object
|
|
|
|
// If no base class, and this is not an Object, use Object as base class
|
|
if (!cldec.baseClass && cldec.ident != Id.Object && cldec.object && cldec.classKind == ClassKind.d)
|
|
{
|
|
void badObjectDotD()
|
|
{
|
|
ObjectNotFound(cldec.loc, cldec.ident);
|
|
}
|
|
|
|
if (!cldec.object || cldec.object.errors)
|
|
badObjectDotD();
|
|
|
|
Type t = cldec.object.type;
|
|
t = t.typeSemantic(cldec.loc, sc).toBasetype();
|
|
if (t.ty == Terror)
|
|
badObjectDotD();
|
|
TypeClass tc = t.isTypeClass();
|
|
assert(tc);
|
|
|
|
auto b = new BaseClass(tc);
|
|
cldec.baseclasses.shift(b);
|
|
|
|
cldec.baseClass = tc.sym;
|
|
assert(!cldec.baseClass.isInterfaceDeclaration());
|
|
b.sym = cldec.baseClass;
|
|
}
|
|
if (cldec.baseClass)
|
|
{
|
|
if (cldec.baseClass.storage_class & STC.final_)
|
|
.error(cldec.loc, "%s `%s` cannot inherit from class `%s` because it is `final`", cldec.kind, cldec.toPrettyChars, cldec.baseClass.toChars());
|
|
|
|
// Inherit properties from base class
|
|
if (cldec.baseClass.isCOMclass())
|
|
cldec.com = true;
|
|
if (cldec.baseClass.isCPPclass())
|
|
cldec.classKind = ClassKind.cpp;
|
|
if (cldec.classKind != cldec.baseClass.classKind)
|
|
.error(cldec.loc, "%s `%s` with %s linkage cannot inherit from class `%s` with %s linkage", cldec.kind, cldec.toPrettyChars,
|
|
ClassKindToChars(cldec.classKind), cldec.baseClass.toChars(), ClassKindToChars(cldec.baseClass.classKind));
|
|
|
|
if (cldec.baseClass.stack)
|
|
cldec.stack = true;
|
|
cldec.enclosing = cldec.baseClass.enclosing;
|
|
cldec.storage_class |= cldec.baseClass.storage_class & STC.TYPECTOR;
|
|
}
|
|
|
|
cldec.interfaces = cldec.baseclasses.tdata()[(cldec.baseClass ? 1 : 0) .. cldec.baseclasses.length];
|
|
foreach (b; cldec.interfaces)
|
|
{
|
|
// If this is an interface, and it derives from a COM interface,
|
|
// then this is a COM interface too.
|
|
if (b.sym.isCOMinterface())
|
|
cldec.com = true;
|
|
if (cldec.classKind == ClassKind.cpp && !b.sym.isCPPinterface())
|
|
{
|
|
.error(cldec.loc, "C++ class `%s` cannot implement D interface `%s`",
|
|
cldec.toPrettyChars(), b.sym.toPrettyChars());
|
|
}
|
|
}
|
|
interfaceSemantic(cldec);
|
|
}
|
|
Lancestorsdone:
|
|
//printf("\tClassDeclaration.dsymbolSemantic(%s) baseok = %d\n", toChars(), baseok);
|
|
|
|
if (!cldec.members) // if opaque declaration
|
|
{
|
|
cldec.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
if (!cldec.symtab)
|
|
{
|
|
cldec.symtab = new DsymbolTable();
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=12152
|
|
* The semantic analysis of base classes should be finished
|
|
* before the members semantic analysis of this class, in order to determine
|
|
* vtbl in this class. However if a base class refers the member of this class,
|
|
* it can be resolved as a normal forward reference.
|
|
* Call addMember() and setScope() to make this class members visible from the base classes.
|
|
*/
|
|
cldec.members.foreachDsymbol( s => s.addMember(sc, cldec) );
|
|
|
|
auto sc2 = cldec.newScope(sc);
|
|
|
|
/* Set scope so if there are forward references, we still might be able to
|
|
* resolve individual members like enums.
|
|
*/
|
|
cldec.members.foreachDsymbol( s => s.setScope(sc2) );
|
|
|
|
sc2.pop();
|
|
}
|
|
|
|
for (size_t i = 0; i < cldec.baseclasses.length; i++)
|
|
{
|
|
BaseClass* b = (*cldec.baseclasses)[i];
|
|
Type tb = b.type.toBasetype();
|
|
TypeClass tc = tb.isTypeClass();
|
|
if (tc.sym.semanticRun < PASS.semanticdone)
|
|
{
|
|
// Forward referencee of one or more bases, try again later
|
|
if (tc.sym._scope)
|
|
Module.addDeferredSemantic(tc.sym);
|
|
//printf("\tL%d semantic('%s') failed due to forward references\n", __LINE__, toChars());
|
|
return deferDsymbolSemantic(sc, cldec, scx);
|
|
}
|
|
}
|
|
|
|
if (cldec.baseok == Baseok.done)
|
|
{
|
|
cldec.baseok = Baseok.semanticdone;
|
|
objc.setMetaclass(cldec, sc);
|
|
|
|
// initialize vtbl
|
|
if (cldec.baseClass)
|
|
{
|
|
if (cldec.classKind == ClassKind.cpp && cldec.baseClass.vtbl.length == 0)
|
|
{
|
|
.error(cldec.loc, "%s `%s` C++ base class `%s` needs at least one virtual function", cldec.kind, cldec.toPrettyChars, cldec.baseClass.toChars());
|
|
}
|
|
|
|
// Copy vtbl[] from base class
|
|
assert(cldec.vtbl.length == 0);
|
|
cldec.vtbl.setDim(cldec.baseClass.vtbl.length);
|
|
memcpy(cldec.vtbl.tdata(), cldec.baseClass.vtbl.tdata(), (void*).sizeof * cldec.vtbl.length);
|
|
|
|
cldec.vthis = cldec.baseClass.vthis;
|
|
cldec.vthis2 = cldec.baseClass.vthis2;
|
|
}
|
|
else
|
|
{
|
|
// No base class, so this is the root of the class hierarchy
|
|
cldec.vtbl.setDim(0);
|
|
if (cldec.vtblOffset())
|
|
cldec.vtbl.push(cldec); // leave room for classinfo as first member
|
|
}
|
|
|
|
/* If this is a nested class, add the hidden 'this'
|
|
* member which is a pointer to the enclosing scope.
|
|
*/
|
|
if (cldec.vthis) // if inheriting from nested class
|
|
{
|
|
// Use the base class's 'this' member
|
|
if (cldec.storage_class & STC.static_)
|
|
.error(cldec.loc, "%s `%s` static class cannot inherit from nested class `%s`", cldec.kind, cldec.toPrettyChars, cldec.baseClass.toChars());
|
|
if (cldec.toParentLocal() != cldec.baseClass.toParentLocal() &&
|
|
(!cldec.toParentLocal() ||
|
|
!cldec.baseClass.toParentLocal().getType() ||
|
|
!cldec.baseClass.toParentLocal().getType().isBaseOf(cldec.toParentLocal().getType(), null)))
|
|
{
|
|
if (cldec.toParentLocal())
|
|
{
|
|
.error(cldec.loc, "%s `%s` is nested within `%s`, but super class `%s` is nested within `%s`", cldec.kind, cldec.toPrettyChars,
|
|
cldec.toParentLocal().toChars(),
|
|
cldec.baseClass.toChars(),
|
|
cldec.baseClass.toParentLocal().toChars());
|
|
}
|
|
else
|
|
{
|
|
.error(cldec.loc, "%s `%s` is not nested, but super class `%s` is nested within `%s`", cldec.kind, cldec.toPrettyChars,
|
|
cldec.baseClass.toChars(),
|
|
cldec.baseClass.toParentLocal().toChars());
|
|
}
|
|
}
|
|
if (cldec.vthis2)
|
|
{
|
|
if (cldec.toParent2() != cldec.baseClass.toParent2() &&
|
|
(!cldec.toParent2() ||
|
|
!cldec.baseClass.toParent2().getType() ||
|
|
!cldec.baseClass.toParent2().getType().isBaseOf(cldec.toParent2().getType(), null)))
|
|
{
|
|
if (cldec.toParent2() && cldec.toParent2() != cldec.toParentLocal())
|
|
{
|
|
.error(cldec.loc, "%s `%s` needs the frame pointer of `%s`, but super class `%s` needs the frame pointer of `%s`", cldec.kind, cldec.toPrettyChars,
|
|
cldec.toParent2().toChars(),
|
|
cldec.baseClass.toChars(),
|
|
cldec.baseClass.toParent2().toChars());
|
|
}
|
|
else
|
|
{
|
|
.error(cldec.loc, "%s `%s` doesn't need a frame pointer, but super class `%s` needs the frame pointer of `%s`", cldec.kind, cldec.toPrettyChars,
|
|
cldec.baseClass.toChars(),
|
|
cldec.baseClass.toParent2().toChars());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
cldec.makeNested2();
|
|
}
|
|
else
|
|
cldec.makeNested();
|
|
}
|
|
|
|
auto sc2 = cldec.newScope(sc);
|
|
|
|
cldec.members.foreachDsymbol( s => s.importAll(sc2) );
|
|
|
|
// Note that members.length can grow due to tuple expansion during semantic()
|
|
cldec.members.foreachDsymbol( s => s.dsymbolSemantic(sc2) );
|
|
|
|
if (!cldec.determineFields())
|
|
{
|
|
assert(cldec.type == Type.terror);
|
|
sc2.pop();
|
|
return;
|
|
}
|
|
/* Following special member functions creation needs semantic analysis
|
|
* completion of sub-structs in each field types.
|
|
*/
|
|
foreach (v; cldec.fields)
|
|
{
|
|
Type tb = v.type.baseElemOf();
|
|
if (tb.ty != Tstruct)
|
|
continue;
|
|
auto sd = (cast(TypeStruct)tb).sym;
|
|
if (sd.semanticRun >= PASS.semanticdone)
|
|
continue;
|
|
|
|
sc2.pop();
|
|
|
|
//printf("\tdeferring %s\n", toChars());
|
|
return deferDsymbolSemantic(sc, cldec, scx);
|
|
}
|
|
|
|
/* Look for special member functions.
|
|
* They must be in this class, not in a base class.
|
|
*/
|
|
// Can be in base class
|
|
cldec.disableNew = cldec.search(Loc.initial, Id.classNew) !is null;
|
|
|
|
// Look for the constructor
|
|
cldec.ctor = cldec.searchCtor();
|
|
|
|
if (!cldec.ctor && cldec.noDefaultCtor)
|
|
{
|
|
// A class object is always created by constructor, so this check is legitimate.
|
|
foreach (v; cldec.fields)
|
|
{
|
|
if (v.storage_class & STC.nodefaultctor)
|
|
error(v.loc, "field `%s` must be initialized in constructor", v.toChars());
|
|
}
|
|
}
|
|
|
|
// If this class has no constructor, but base class has a default
|
|
// ctor, create a constructor:
|
|
// this() { }
|
|
if (!cldec.ctor && cldec.baseClass && cldec.baseClass.ctor)
|
|
{
|
|
auto fd = resolveFuncCall(cldec.loc, sc2, cldec.baseClass.ctor, null, cldec.type, ArgumentList(), FuncResolveFlag.quiet);
|
|
if (!fd) // try shared base ctor instead
|
|
fd = resolveFuncCall(cldec.loc, sc2, cldec.baseClass.ctor, null, cldec.type.sharedOf, ArgumentList(), FuncResolveFlag.quiet);
|
|
if (fd && !fd.errors)
|
|
{
|
|
//printf("Creating default this(){} for class %s\n", toChars());
|
|
auto btf = fd.type.toTypeFunction();
|
|
auto tf = new TypeFunction(ParameterList(), null, LINK.d, fd.storage_class);
|
|
tf.mod = btf.mod;
|
|
// Don't copy @safe, ... from the base class constructor and let it be inferred instead
|
|
// This is required if other lowerings add code to the generated constructor which
|
|
// is less strict (e.g. `preview=dtorfields` might introduce a call to a less qualified dtor)
|
|
|
|
auto ctor = new CtorDeclaration(cldec.loc, Loc.initial, STC.none, tf);
|
|
ctor.storage_class |= STC.inference | (fd.storage_class & STC.scope_);
|
|
ctor.isGenerated = true;
|
|
ctor.fbody = new CompoundStatement(Loc.initial, new Statements());
|
|
|
|
cldec.members.push(ctor);
|
|
ctor.addMember(sc, cldec);
|
|
ctor.dsymbolSemantic(sc2);
|
|
|
|
cldec.ctor = ctor;
|
|
cldec.defaultCtor = ctor;
|
|
}
|
|
else
|
|
{
|
|
.error(cldec.loc, "%s `%s` cannot implicitly generate a default constructor when base class `%s` is missing a default constructor", cldec.kind, cldec.toPrettyChars,
|
|
cldec.baseClass.toPrettyChars());
|
|
}
|
|
}
|
|
|
|
buildDtors(cldec, sc2);
|
|
|
|
if (cldec.classKind == ClassKind.cpp && cldec.cppDtorVtblIndex != -1)
|
|
{
|
|
// now we've built the aggregate destructor, we'll make it virtual and assign it to the reserved vtable slot
|
|
cldec.dtor.vtblIndex = cldec.cppDtorVtblIndex;
|
|
cldec.vtbl[cldec.cppDtorVtblIndex] = cldec.dtor;
|
|
|
|
if (target.cpp.twoDtorInVtable)
|
|
{
|
|
// TODO: create a C++ compatible deleting destructor (call out to `operator delete`)
|
|
// for the moment, we'll call the non-deleting destructor and leak
|
|
cldec.vtbl[cldec.cppDtorVtblIndex + 1] = cldec.dtor;
|
|
}
|
|
}
|
|
|
|
if (auto f = hasIdentityOpAssign(cldec, sc2))
|
|
{
|
|
if (!(f.storage_class & STC.disable))
|
|
.error(f.loc, "%s `%s` identity assignment operator overload is illegal", cldec.kind, cldec.toPrettyChars);
|
|
}
|
|
|
|
cldec.inv = buildInv(cldec, sc2);
|
|
|
|
cldec.semanticRun = PASS.semanticdone;
|
|
//printf("-ClassDeclaration.dsymbolSemantic(%s), type = %p\n", toChars(), type);
|
|
|
|
sc2.pop();
|
|
|
|
/* isAbstract() is undecidable in some cases because of circular dependencies.
|
|
* Now that semantic is finished, get a definitive result, and error if it is not the same.
|
|
*/
|
|
if (cldec.isabstract != ThreeState.none) // if evaluated it before completion
|
|
{
|
|
const isabstractsave = cldec.isabstract;
|
|
cldec.isabstract = ThreeState.none;
|
|
cldec.isAbstract(); // recalculate
|
|
if (cldec.isabstract != isabstractsave)
|
|
{
|
|
.error(cldec.loc, "%s `%s` cannot infer `abstract` attribute due to circular dependencies", cldec.kind, cldec.toPrettyChars);
|
|
}
|
|
}
|
|
|
|
if (cldec.type.ty == Tclass && (cast(TypeClass)cldec.type).sym != cldec)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=17492
|
|
ClassDeclaration cd = (cast(TypeClass)cldec.type).sym;
|
|
version (none)
|
|
{
|
|
printf("this = %p %s\n", cldec, cldec.toPrettyChars());
|
|
printf("type = %d sym = %p, %s\n", cldec.type.ty, cd, cd.toPrettyChars());
|
|
}
|
|
.error(cldec.loc, "%s `%s` already exists at %s. Perhaps in another function with the same name?", cldec.kind, cldec.toPrettyChars, cd.loc.toChars());
|
|
}
|
|
|
|
if (global.errors != errors || (cldec.baseClass && cldec.baseClass.errors))
|
|
{
|
|
// The type is no good, but we should keep the
|
|
// the type so that we have more accurate error messages
|
|
// See: https://issues.dlang.org/show_bug.cgi?id=23552
|
|
cldec.errors = true;
|
|
if (cldec.deferred)
|
|
cldec.deferred.errors = true;
|
|
}
|
|
|
|
// Verify fields of a synchronized class are not public
|
|
if (cldec.storage_class & STC.synchronized_)
|
|
{
|
|
foreach (vd; cldec.fields)
|
|
{
|
|
if (!vd.isThisDeclaration() &&
|
|
vd.visible() >= Visibility(Visibility.Kind.public_))
|
|
{
|
|
.error(vd.loc, "%s `%s` Field members of a `synchronized` class cannot be `%s`", vd.kind, vd.toPrettyChars,
|
|
visibilityToChars(vd.visible().kind));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cldec.deferred && !global.gag)
|
|
{
|
|
cldec.deferred.semantic2(sc);
|
|
cldec.deferred.semantic3(sc);
|
|
}
|
|
//printf("-ClassDeclaration.dsymbolSemantic(%s), type = %p, sizeok = %d, this = %p\n", toChars(), type, sizeok, this);
|
|
|
|
version (none)
|
|
{
|
|
// @@@DEPRECATED_2.110@@@ https://dlang.org/deprecate.html#scope%20as%20a%20type%20constraint
|
|
// Deprecated in 2.100
|
|
// Make an error in 2.110
|
|
// Don't forget to remove code at https://github.com/dlang/dmd/blob/b2f8274ba76358607fc3297a1e9f361480f9bcf9/src/dmd/dsymbolsem.d#L1032-L1036
|
|
if (cldec.storage_class & STC.scope_)
|
|
deprecation(cldec.loc, "`scope` as a type constraint is deprecated. Use `scope` at the usage site.");
|
|
}
|
|
}
|
|
|
|
override void visit(InterfaceDeclaration idec)
|
|
{
|
|
/// Returns: `true` is this is an anonymous Objective-C metaclass
|
|
static bool isAnonymousMetaclass(InterfaceDeclaration idec)
|
|
{
|
|
return idec.classKind == ClassKind.objc &&
|
|
idec.objc.isMeta &&
|
|
idec.isAnonymous;
|
|
}
|
|
|
|
//printf("InterfaceDeclaration.dsymbolSemantic(%s), type = %p\n", toChars(), type);
|
|
if (idec.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
const errors = global.errors;
|
|
|
|
//printf("+InterfaceDeclaration.dsymbolSemantic(%s), type = %p\n", toChars(), type);
|
|
|
|
Scope* scx = null;
|
|
if (idec._scope)
|
|
{
|
|
sc = idec._scope;
|
|
scx = idec._scope; // save so we don't make redundant copies
|
|
idec._scope = null;
|
|
}
|
|
|
|
if (!idec.parent)
|
|
{
|
|
assert(sc.parent && sc.func);
|
|
idec.parent = sc.parent;
|
|
}
|
|
// Objective-C metaclasses are anonymous
|
|
assert(idec.parent && !idec.isAnonymous || isAnonymousMetaclass(idec));
|
|
|
|
if (idec.errors)
|
|
idec.type = Type.terror;
|
|
idec.type = idec.type.typeSemantic(idec.loc, sc);
|
|
if (idec.type.ty == Tclass && (cast(TypeClass)idec.type).sym != idec)
|
|
{
|
|
auto ti = (cast(TypeClass)idec.type).sym.isInstantiated();
|
|
if (ti && isError(ti))
|
|
(cast(TypeClass)idec.type).sym = idec;
|
|
}
|
|
|
|
// Ungag errors when not speculative
|
|
Ungag ungag = idec.ungagSpeculative();
|
|
|
|
if (idec.semanticRun == PASS.initial)
|
|
{
|
|
idec.visibility = sc.visibility;
|
|
|
|
idec.storage_class |= sc.stc;
|
|
idec.userAttribDecl = sc.userAttribDecl;
|
|
}
|
|
else if (idec.symtab)
|
|
{
|
|
if (idec.sizeok == Sizeok.done || !scx)
|
|
{
|
|
idec.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
}
|
|
idec.semanticRun = PASS.semantic;
|
|
|
|
if (idec.baseok < Baseok.done)
|
|
{
|
|
T resolveBase(T)(lazy T exp)
|
|
{
|
|
if (!scx)
|
|
{
|
|
scx = sc.copy();
|
|
scx.setNoFree();
|
|
}
|
|
static if (!is(T == void))
|
|
{
|
|
idec._scope = scx;
|
|
auto r = exp();
|
|
idec._scope = null;
|
|
return r;
|
|
}
|
|
else
|
|
{
|
|
idec._scope = scx;
|
|
exp();
|
|
idec._scope = null;
|
|
}
|
|
}
|
|
|
|
idec.baseok = Baseok.start;
|
|
|
|
// Expand any tuples in baseclasses[]
|
|
for (size_t i = 0; i < idec.baseclasses.length;)
|
|
{
|
|
auto b = (*idec.baseclasses)[i];
|
|
b.type = resolveBase(b.type.typeSemantic(idec.loc, sc));
|
|
|
|
Type tb = b.type.toBasetype();
|
|
if (auto tup = tb.isTypeTuple())
|
|
{
|
|
idec.baseclasses.remove(i);
|
|
size_t dim = Parameter.dim(tup.arguments);
|
|
for (size_t j = 0; j < dim; j++)
|
|
{
|
|
Parameter arg = Parameter.getNth(tup.arguments, j);
|
|
b = new BaseClass(arg.type);
|
|
idec.baseclasses.insert(i + j, b);
|
|
}
|
|
}
|
|
else
|
|
i++;
|
|
}
|
|
|
|
if (idec.baseok >= Baseok.done)
|
|
{
|
|
//printf("%s already semantic analyzed, semanticRun = %d\n", toChars(), semanticRun);
|
|
if (idec.semanticRun >= PASS.semanticdone)
|
|
return;
|
|
goto Lancestorsdone;
|
|
}
|
|
|
|
if (!idec.baseclasses.length && sc.linkage == LINK.cpp)
|
|
idec.classKind = ClassKind.cpp;
|
|
idec.cppnamespace = sc.namespace;
|
|
checkGNUABITag(idec, sc.linkage);
|
|
checkMustUseReserved(idec);
|
|
|
|
if (sc.linkage == LINK.objc)
|
|
objc.setObjc(idec);
|
|
|
|
// Check for errors, handle forward references
|
|
BCLoop:
|
|
for (size_t i = 0; i < idec.baseclasses.length;)
|
|
{
|
|
BaseClass* b = (*idec.baseclasses)[i];
|
|
Type tb = b.type.toBasetype();
|
|
TypeClass tc = (tb.ty == Tclass) ? cast(TypeClass)tb : null;
|
|
if (!tc || !tc.sym.isInterfaceDeclaration())
|
|
{
|
|
if (b.type != Type.terror)
|
|
.error(idec.loc, "%s `%s` base type must be `interface`, not `%s`", idec.kind, idec.toPrettyChars, b.type.toChars());
|
|
idec.baseclasses.remove(i);
|
|
continue;
|
|
}
|
|
|
|
// Check for duplicate interfaces
|
|
for (size_t j = 0; j < i; j++)
|
|
{
|
|
BaseClass* b2 = (*idec.baseclasses)[j];
|
|
if (b2.sym == tc.sym)
|
|
{
|
|
.error(idec.loc, "%s `%s` inherits from duplicate interface `%s`", idec.kind, idec.toPrettyChars, b2.sym.toChars());
|
|
idec.baseclasses.remove(i);
|
|
continue BCLoop;
|
|
}
|
|
}
|
|
if (tc.sym == idec || idec.isBaseOf2(tc.sym))
|
|
{
|
|
.error(idec.loc, "%s `%s` circular inheritance of interface", idec.kind, idec.toPrettyChars);
|
|
idec.baseclasses.remove(i);
|
|
continue;
|
|
}
|
|
if (tc.sym.isDeprecated())
|
|
{
|
|
if (!idec.isDeprecated())
|
|
{
|
|
// Deriving from deprecated interface makes this one deprecated too
|
|
idec.setDeprecated();
|
|
tc.checkDeprecated(idec.loc, sc);
|
|
}
|
|
}
|
|
|
|
b.sym = tc.sym;
|
|
|
|
if (tc.sym.baseok < Baseok.done)
|
|
resolveBase(tc.sym.dsymbolSemantic(null)); // Try to resolve forward reference
|
|
if (tc.sym.baseok < Baseok.done)
|
|
{
|
|
//printf("\ttry later, forward reference of base %s\n", tc.sym.toChars());
|
|
if (tc.sym._scope)
|
|
Module.addDeferredSemantic(tc.sym);
|
|
idec.baseok = Baseok.none;
|
|
}
|
|
i++;
|
|
}
|
|
if (idec.baseok == Baseok.none)
|
|
{
|
|
// Forward referencee of one or more bases, try again later
|
|
return deferDsymbolSemantic(sc, idec, scx);
|
|
}
|
|
idec.baseok = Baseok.done;
|
|
|
|
idec.interfaces = idec.baseclasses.tdata()[0 .. idec.baseclasses.length];
|
|
foreach (b; idec.interfaces)
|
|
{
|
|
// If this is an interface, and it derives from a COM interface,
|
|
// then this is a COM interface too.
|
|
if (b.sym.isCOMinterface())
|
|
idec.com = true;
|
|
if (b.sym.isCPPinterface())
|
|
idec.classKind = ClassKind.cpp;
|
|
}
|
|
|
|
interfaceSemantic(idec);
|
|
}
|
|
Lancestorsdone:
|
|
|
|
if (!idec.members) // if opaque declaration
|
|
{
|
|
idec.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
if (!idec.symtab)
|
|
idec.symtab = new DsymbolTable();
|
|
|
|
for (size_t i = 0; i < idec.baseclasses.length; i++)
|
|
{
|
|
BaseClass* b = (*idec.baseclasses)[i];
|
|
Type tb = b.type.toBasetype();
|
|
TypeClass tc = tb.isTypeClass();
|
|
if (tc.sym.semanticRun < PASS.semanticdone)
|
|
{
|
|
// Forward referencee of one or more bases, try again later
|
|
if (tc.sym._scope)
|
|
Module.addDeferredSemantic(tc.sym);
|
|
return deferDsymbolSemantic(sc, idec, scx);
|
|
}
|
|
}
|
|
|
|
if (idec.baseok == Baseok.done)
|
|
{
|
|
idec.baseok = Baseok.semanticdone;
|
|
objc.setMetaclass(idec, sc);
|
|
|
|
// initialize vtbl
|
|
if (idec.vtblOffset())
|
|
idec.vtbl.push(idec); // leave room at vtbl[0] for classinfo
|
|
|
|
// Cat together the vtbl[]'s from base interfaces
|
|
foreach (i, b; idec.interfaces)
|
|
{
|
|
// Skip if b has already appeared
|
|
for (size_t k = 0; k < i; k++)
|
|
{
|
|
if (b == idec.interfaces[k])
|
|
goto Lcontinue;
|
|
}
|
|
|
|
// Copy vtbl[] from base class
|
|
if (b.sym.vtblOffset())
|
|
{
|
|
size_t d = b.sym.vtbl.length;
|
|
if (d > 1)
|
|
{
|
|
idec.vtbl.pushSlice(b.sym.vtbl[1 .. d]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
idec.vtbl.append(&b.sym.vtbl);
|
|
}
|
|
|
|
Lcontinue:
|
|
}
|
|
}
|
|
|
|
idec.members.foreachDsymbol( s => s.addMember(sc, idec) );
|
|
|
|
auto sc2 = idec.newScope(sc);
|
|
|
|
/* Set scope so if there are forward references, we still might be able to
|
|
* resolve individual members like enums.
|
|
*/
|
|
idec.members.foreachDsymbol( s => s.setScope(sc2) );
|
|
|
|
idec.members.foreachDsymbol( s => s.importAll(sc2) );
|
|
|
|
idec.members.foreachDsymbol( s => s.dsymbolSemantic(sc2) );
|
|
|
|
idec.semanticRun = PASS.semanticdone;
|
|
//printf("-InterfaceDeclaration.dsymbolSemantic(%s), type = %p\n", toChars(), type);
|
|
|
|
sc2.pop();
|
|
|
|
if (global.errors != errors)
|
|
{
|
|
// The type is no good.
|
|
idec.type = Type.terror;
|
|
}
|
|
|
|
version (none)
|
|
{
|
|
if (type.ty == Tclass && (cast(TypeClass)idec.type).sym != idec)
|
|
{
|
|
printf("this = %p %s\n", idec, idec.toChars());
|
|
printf("type = %d sym = %p\n", idec.type.ty, (cast(TypeClass)idec.type).sym);
|
|
}
|
|
}
|
|
assert(idec.type.ty != Tclass || (cast(TypeClass)idec.type).sym == idec);
|
|
|
|
version (none)
|
|
{
|
|
// @@@DEPRECATED_2.120@@@ https://dlang.org/deprecate.html#scope%20as%20a%20type%20constraint
|
|
// Deprecated in 2.087
|
|
// Made an error in 2.100, but removal depends on `scope class` being removed too
|
|
// Don't forget to remove code at https://github.com/dlang/dmd/blob/b2f8274ba76358607fc3297a1e9f361480f9bcf9/src/dmd/dsymbolsem.d#L1032-L1036
|
|
if (idec.storage_class & STC.scope_)
|
|
error(idec.loc, "`scope` as a type constraint is obsolete. Use `scope` at the usage site.");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Adds dsym as a member of scope sds.
|
|
|
|
Params:
|
|
dsym = dsymbol to inserted
|
|
sc = scope where the dsymbol is declared
|
|
sds = ScopeDsymbol where dsym is inserted
|
|
*/
|
|
void addMember(Dsymbol dsym, Scope* sc, ScopeDsymbol sds)
|
|
{
|
|
auto addMemberVisitor = new AddMemberVisitor(sc, sds);
|
|
dsym.accept(addMemberVisitor);
|
|
}
|
|
|
|
private void attribAddMember(AttribDeclaration atb, Scope* sc, ScopeDsymbol sds)
|
|
{
|
|
Dsymbols* d = atb.include(sc);
|
|
if (d)
|
|
{
|
|
Scope* sc2 = atb.newScope(sc);
|
|
d.foreachDsymbol( s => s.addMember(sc2, sds) );
|
|
if (sc2 != sc)
|
|
sc2.pop();
|
|
}
|
|
}
|
|
|
|
private extern(C++) class AddMemberVisitor : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
Scope* sc;
|
|
ScopeDsymbol sds;
|
|
|
|
this(Scope* sc, ScopeDsymbol sds) @safe
|
|
{
|
|
this.sc = sc;
|
|
this.sds = sds;
|
|
}
|
|
|
|
override void visit(Dsymbol dsym)
|
|
{
|
|
//printf("Dsymbol::addMember('%s')\n", toChars());
|
|
//printf("Dsymbol::addMember(this = %p, '%s' scopesym = '%s')\n", this, toChars(), sds.toChars());
|
|
//printf("Dsymbol::addMember(this = %p, '%s' sds = %p, sds.symtab = %p)\n", this, toChars(), sds, sds.symtab);
|
|
dsym.parent = sds;
|
|
if (dsym.isAnonymous()) // no name, so can't add it to symbol table
|
|
return;
|
|
|
|
if (!sds.symtabInsert(dsym)) // if name is already defined
|
|
{
|
|
if (dsym.isAliasDeclaration() && !dsym._scope)
|
|
dsym.setScope(sc);
|
|
Dsymbol s2 = sds.symtabLookup(dsym, dsym.ident);
|
|
/* https://issues.dlang.org/show_bug.cgi?id=17434
|
|
*
|
|
* If we are trying to add an import to the symbol table
|
|
* that has already been introduced, then keep the one with
|
|
* larger visibility. This is fine for imports because if
|
|
* we have multiple imports of the same file, if a single one
|
|
* is public then the symbol is reachable.
|
|
*/
|
|
if (auto i1 = dsym.isImport())
|
|
{
|
|
if (auto i2 = s2.isImport())
|
|
{
|
|
if (sc.explicitVisibility && sc.visibility > i2.visibility)
|
|
sds.symtab.update(dsym);
|
|
}
|
|
}
|
|
|
|
// If using C tag/prototype/forward declaration rules
|
|
if (sc && sc.inCfile && !dsym.isImport())
|
|
// When merging master, replace with: if (sc && sc.inCfile && !dsym.isImport())
|
|
{
|
|
if (handleTagSymbols(*sc, dsym, s2, sds))
|
|
return;
|
|
if (handleSymbolRedeclarations(*sc, dsym, s2, sds))
|
|
return;
|
|
|
|
sds.multiplyDefined(Loc.initial, dsym, s2); // ImportC doesn't allow overloading
|
|
dsym.errors = true;
|
|
return;
|
|
}
|
|
|
|
if (!s2.overloadInsert(dsym))
|
|
{
|
|
sds.multiplyDefined(Loc.initial, dsym, s2);
|
|
dsym.errors = true;
|
|
}
|
|
}
|
|
if (sds.isAggregateDeclaration() || sds.isEnumDeclaration())
|
|
{
|
|
if (dsym.ident == Id.__sizeof ||
|
|
!(sc && sc.inCfile) && (dsym.ident == Id.__xalignof || dsym.ident == Id._mangleof))
|
|
{
|
|
.error(dsym.loc, "%s `%s` `.%s` property cannot be redefined", dsym.kind, dsym.toPrettyChars, dsym.ident.toChars());
|
|
dsym.errors = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
override void visit(StaticAssert _)
|
|
{
|
|
// we didn't add anything
|
|
}
|
|
|
|
/*****************************
|
|
* Add import to sd's symbol table.
|
|
*/
|
|
override void visit(Import imp)
|
|
{
|
|
//printf("Import.addMember(this=%s, sds=%s, sc=%p)\n", imp.toChars(), sds.toChars(), sc);
|
|
if (imp.names.length == 0)
|
|
return visit(cast(Dsymbol)imp);
|
|
if (imp.aliasId)
|
|
visit(cast(Dsymbol)imp);
|
|
|
|
/* Instead of adding the import to sds's symbol table,
|
|
* add each of the alias=name pairs
|
|
*/
|
|
for (size_t i = 0; i < imp.names.length; i++)
|
|
{
|
|
Identifier name = imp.names[i];
|
|
Identifier _alias = imp.aliases[i];
|
|
if (!_alias)
|
|
_alias = name;
|
|
auto tname = new TypeIdentifier(imp.loc, name);
|
|
auto ad = new AliasDeclaration(imp.loc, _alias, tname);
|
|
ad._import = imp;
|
|
addMember(ad, sc, sds);
|
|
imp.aliasdecls.push(ad);
|
|
}
|
|
}
|
|
|
|
override void visit(AttribDeclaration atb)
|
|
{
|
|
attribAddMember(atb, sc, sds);
|
|
}
|
|
|
|
override void visit(StorageClassDeclaration stcd)
|
|
{
|
|
Dsymbols* d = stcd.include(sc);
|
|
if (d)
|
|
{
|
|
Scope* sc2 = stcd.newScope(sc);
|
|
|
|
d.foreachDsymbol( (s)
|
|
{
|
|
//printf("\taddMember %s to %s\n", s.toChars(), sds.toChars());
|
|
// STC.local needs to be attached before the member is added to the scope (because it influences the parent symbol)
|
|
if (auto decl = s.isDeclaration())
|
|
{
|
|
decl.storage_class |= stcd.stc & STC.local;
|
|
if (auto sdecl = s.isStorageClassDeclaration()) // TODO: why is this not enough to deal with the nested case?
|
|
{
|
|
sdecl.stc |= stcd.stc & STC.local;
|
|
}
|
|
}
|
|
s.addMember(sc2, sds);
|
|
});
|
|
|
|
if (sc2 != sc)
|
|
sc2.pop();
|
|
}
|
|
}
|
|
|
|
override void visit(VisibilityDeclaration visd)
|
|
{
|
|
if (visd.pkg_identifiers)
|
|
{
|
|
Dsymbol tmp;
|
|
Package.resolve(visd.pkg_identifiers, &tmp, null);
|
|
visd.visibility.pkg = tmp ? tmp.isPackage() : null;
|
|
visd.pkg_identifiers = null;
|
|
}
|
|
if (visd.visibility.kind == Visibility.Kind.package_ && visd.visibility.pkg && sc._module)
|
|
{
|
|
Module m = sc._module;
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=17441
|
|
// While isAncestorPackageOf does an equality check, the fix for the issue adds a check to see if
|
|
// each package's .isModule() properites are equal.
|
|
//
|
|
// Properties generated from `package(foo)` i.e. visibility.pkg have .isModule() == null.
|
|
// This breaks package declarations of the package in question if they are declared in
|
|
// the same package.d file, which _do_ have a module associated with them, and hence a non-null
|
|
// isModule()
|
|
if (!m.isPackage() || !visd.visibility.pkg.ident.equals(m.isPackage().ident))
|
|
{
|
|
Package pkg = m.parent ? m.parent.isPackage() : null;
|
|
if (!pkg || !visd.visibility.pkg.isAncestorPackageOf(pkg))
|
|
.error(visd.loc, "%s `%s` does not bind to one of ancestor packages of module `%s`", visd.kind(), visd.toPrettyChars(false), m.toPrettyChars(true));
|
|
}
|
|
}
|
|
attribAddMember(visd, sc, sds);
|
|
}
|
|
|
|
override void visit(StaticIfDeclaration sid)
|
|
{
|
|
//printf("StaticIfDeclaration::addMember() '%s'\n", sid.toChars());
|
|
/* This is deferred until the condition evaluated later (by the include() call),
|
|
* so that expressions in the condition can refer to declarations
|
|
* in the same scope, such as:
|
|
*
|
|
* template Foo(int i)
|
|
* {
|
|
* const int j = i + 1;
|
|
* static if (j == 3)
|
|
* const int k;
|
|
* }
|
|
*/
|
|
sid.scopesym = sds;
|
|
}
|
|
|
|
|
|
override void visit(StaticForeachDeclaration sfd)
|
|
{
|
|
// used only for caching the enclosing symbol
|
|
sfd.scopesym = sds;
|
|
}
|
|
|
|
/***************************************
|
|
* Lazily initializes the scope to forward to.
|
|
*/
|
|
override void visit(ForwardingAttribDeclaration fad)
|
|
{
|
|
fad.sym.parent = sds;
|
|
sds = fad.sym;
|
|
attribAddMember(fad, sc, fad.sym);
|
|
}
|
|
|
|
override void visit(MixinDeclaration md)
|
|
{
|
|
//printf("MixinDeclaration::addMember(sc = %p, sds = %p, memnum = %d)\n", sc, sds, md.memnum);
|
|
md.scopesym = sds;
|
|
}
|
|
|
|
override void visit(DebugSymbol ds)
|
|
{
|
|
//printf("DebugSymbol::addMember('%s') %s\n", sds.toChars(), ds.toChars());
|
|
Module m = sds.isModule();
|
|
// Do not add the member to the symbol table,
|
|
// just make sure subsequent debug declarations work.
|
|
if (!m)
|
|
{
|
|
.error(ds.loc, "%s `%s` declaration must be at module level", ds.kind, ds.toPrettyChars);
|
|
ds.errors = true;
|
|
}
|
|
else
|
|
{
|
|
if (m.debugidsNot && findCondition(*m.debugidsNot, ds.ident))
|
|
{
|
|
.error(ds.loc, "%s `%s` defined after use", ds.kind, ds.toPrettyChars);
|
|
ds.errors = true;
|
|
}
|
|
if (!m.debugids)
|
|
m.debugids = new Identifiers();
|
|
m.debugids.push(ds.ident);
|
|
}
|
|
}
|
|
|
|
override void visit(VersionSymbol vs)
|
|
{
|
|
//printf("VersionSymbol::addMember('%s') %s\n", sds.toChars(), vs.toChars());
|
|
Module m = sds.isModule();
|
|
// Do not add the member to the symbol table,
|
|
// just make sure subsequent debug declarations work.
|
|
VersionCondition.checkReserved(vs.loc, vs.ident.toString());
|
|
if (!m)
|
|
{
|
|
.error(vs.loc, "%s `%s` declaration must be at module level", vs.kind, vs.toPrettyChars);
|
|
vs.errors = true;
|
|
}
|
|
else
|
|
{
|
|
if (m.versionidsNot && findCondition(*m.versionidsNot, vs.ident))
|
|
{
|
|
.error(vs.loc, "%s `%s` defined after use", vs.kind, vs.toPrettyChars);
|
|
vs.errors = true;
|
|
}
|
|
if (!m.versionids)
|
|
m.versionids = new Identifiers();
|
|
m.versionids.push(vs.ident);
|
|
}
|
|
|
|
}
|
|
|
|
override void visit(Nspace ns)
|
|
{
|
|
visit(cast(Dsymbol)ns);
|
|
|
|
if (ns.members)
|
|
{
|
|
if (!ns.symtab)
|
|
ns.symtab = new DsymbolTable();
|
|
// The namespace becomes 'imported' into the enclosing scope
|
|
for (Scope* sce = sc; 1; sce = sce.enclosing)
|
|
{
|
|
ScopeDsymbol sds2 = sce.scopesym;
|
|
if (sds2)
|
|
{
|
|
sds2.importScope(ns, Visibility(Visibility.Kind.public_));
|
|
break;
|
|
}
|
|
}
|
|
assert(sc);
|
|
sc = sc.push(ns);
|
|
sc.linkage = LINK.cpp; // namespaces default to C++ linkage
|
|
sc.parent = ns;
|
|
ns.members.foreachDsymbol(s => s.addMember(sc, ns));
|
|
sc.pop();
|
|
}
|
|
}
|
|
|
|
override void visit(EnumDeclaration ed)
|
|
{
|
|
version (none)
|
|
{
|
|
printf("EnumDeclaration::addMember() %s\n", ed.toChars());
|
|
for (size_t i = 0; i < ed.members.length; i++)
|
|
{
|
|
EnumMember em = (*ed.members)[i].isEnumMember();
|
|
printf(" member %s\n", em.toChars());
|
|
}
|
|
}
|
|
if (!ed.isAnonymous())
|
|
{
|
|
visit(cast(Dsymbol)ed);
|
|
}
|
|
|
|
addEnumMembersToSymtab(ed, sc, sds);
|
|
}
|
|
}
|
|
|
|
/*******************************************
|
|
* Add members of EnumDeclaration to the symbol table(s).
|
|
* Params:
|
|
* ed = EnumDeclaration
|
|
* sc = context of `ed`
|
|
* sds = symbol table that `ed` resides in
|
|
*/
|
|
void addEnumMembersToSymtab(EnumDeclaration ed, Scope* sc, ScopeDsymbol sds)
|
|
{
|
|
const bool isCEnum = sc.inCfile; // it's an ImportC enum
|
|
//printf("addEnumMembersToSymtab(ed: %s added: %d Cfile: %d)\n", ed.toChars(), ed.added, isCEnum);
|
|
if (ed.added)
|
|
return;
|
|
ed.added = true;
|
|
|
|
if (!ed.members)
|
|
return;
|
|
|
|
const bool isAnon = ed.isAnonymous();
|
|
|
|
if ((isCEnum || isAnon) && !sds.symtab)
|
|
sds.symtab = new DsymbolTable();
|
|
|
|
if ((isCEnum || !isAnon) && !ed.symtab)
|
|
ed.symtab = new DsymbolTable();
|
|
|
|
ed.members.foreachDsymbol( (s)
|
|
{
|
|
if (EnumMember em = s.isEnumMember())
|
|
{
|
|
//printf("adding EnumMember %s to %s %d\n", em.toChars(), ed.toChars(), isCEnum);
|
|
em.ed = ed;
|
|
if (isCEnum)
|
|
{
|
|
/* C doesn't add the enum member to the symbol table of the enum tag, it adds
|
|
* it to the symbol table that the tag is in. This is in contrast to D, where enum
|
|
* members become members of the enum tag. To accommodate this, we add
|
|
* the enum members to both symbol tables.
|
|
*/
|
|
em.addMember(sc, ed); // add em to ed's symbol table
|
|
if (em.errors)
|
|
return;
|
|
em.addMember(sc, sds); // add em to symbol table that ed is in
|
|
em.parent = ed; // restore it after previous addMember() changed it
|
|
}
|
|
else
|
|
{
|
|
em.addMember(sc, isAnon ? sds : ed);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/******************************************************
|
|
* Verifies if the given Identifier is a DRuntime hook. It uses the hooks
|
|
* defined in `id.d`.
|
|
*
|
|
* Params:
|
|
* id = Identifier to verify
|
|
* Returns:
|
|
* true if `id` is a DRuntime hook
|
|
* false otherwise
|
|
*/
|
|
private bool isDRuntimeHook(Identifier id)
|
|
{
|
|
return id == Id._d_HookTraceImpl ||
|
|
id == Id._d_newclassT || id == Id._d_newclassTTrace ||
|
|
id == Id._d_arraycatnTX || id == Id._d_arraycatnTXTrace ||
|
|
id == Id._d_newThrowable || id == Id._d_delThrowable ||
|
|
id == Id._d_arrayassign_l || id == Id._d_arrayassign_r ||
|
|
id == Id._d_arraysetassign || id == Id._d_arraysetctor ||
|
|
id == Id._d_arrayctor ||
|
|
id == Id._d_arraysetlengthTImpl || id == Id._d_arraysetlengthT ||
|
|
id == Id._d_arraysetlengthTTrace ||
|
|
id == Id._d_arrayappendT || id == Id._d_arrayappendTTrace ||
|
|
id == Id._d_arrayappendcTX;
|
|
}
|
|
|
|
void templateInstanceSemantic(TemplateInstance tempinst, Scope* sc, ArgumentList argumentList)
|
|
{
|
|
//printf("[%s] TemplateInstance.dsymbolSemantic('%s', this=%p, gag = %d, sc = %p)\n", tempinst.loc.toChars(), tempinst.toChars(), tempinst, global.gag, sc);
|
|
version (none)
|
|
{
|
|
for (Dsymbol s = tempinst; s; s = s.parent)
|
|
{
|
|
printf("\t%s\n", s.toChars());
|
|
}
|
|
printf("Scope\n");
|
|
for (Scope* scx = sc; scx; scx = scx.enclosing)
|
|
{
|
|
printf("\t%s parent %s\n", scx._module ? scx._module.toChars() : "null", scx.parent ? scx.parent.toChars() : "null");
|
|
}
|
|
}
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("\n+TemplateInstance.dsymbolSemantic('%s', this=%p)\n", tempinst.toChars(), tempinst);
|
|
}
|
|
if (tempinst.inst) // if semantic() was already run
|
|
{
|
|
static if (LOG)
|
|
{
|
|
printf("-TemplateInstance.dsymbolSemantic('%s', this=%p) already run\n",
|
|
tempinst.inst.toChars(), tempinst.inst);
|
|
}
|
|
return;
|
|
}
|
|
if (tempinst.semanticRun != PASS.initial)
|
|
{
|
|
static if (LOG)
|
|
{
|
|
printf("Recursive template expansion\n");
|
|
}
|
|
auto ungag = Ungag(global.gag);
|
|
if (!tempinst.gagged)
|
|
global.gag = 0;
|
|
.error(tempinst.loc, "%s `%s` recursive template expansion", tempinst.kind, tempinst.toPrettyChars);
|
|
if (tempinst.gagged)
|
|
tempinst.semanticRun = PASS.initial;
|
|
else
|
|
tempinst.inst = tempinst;
|
|
tempinst.errors = true;
|
|
return;
|
|
}
|
|
|
|
// Get the enclosing template instance from the scope tinst
|
|
tempinst.tinst = sc.tinst;
|
|
|
|
// Get the instantiating module from the scope minst
|
|
tempinst.minst = sc.minst;
|
|
// https://issues.dlang.org/show_bug.cgi?id=10920
|
|
// If the enclosing function is non-root symbol,
|
|
// this instance should be speculative.
|
|
if (!tempinst.tinst && sc.func && sc.func.inNonRoot())
|
|
{
|
|
tempinst.minst = null;
|
|
}
|
|
|
|
tempinst.gagged = (global.gag > 0);
|
|
|
|
tempinst.semanticRun = PASS.semantic;
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("\tdo semantic\n");
|
|
}
|
|
/* Find template declaration first,
|
|
* then run semantic on each argument (place results in tiargs[]),
|
|
* last find most specialized template from overload list/set.
|
|
*/
|
|
if (!tempinst.findTempDecl(sc, null) || !tempinst.semanticTiargs(sc) || !tempinst.findBestMatch(sc, argumentList))
|
|
{
|
|
Lerror:
|
|
if (tempinst.gagged)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=13220
|
|
// Roll back status for later semantic re-running
|
|
tempinst.semanticRun = PASS.initial;
|
|
}
|
|
else
|
|
tempinst.inst = tempinst;
|
|
tempinst.errors = true;
|
|
return;
|
|
}
|
|
TemplateDeclaration tempdecl = tempinst.tempdecl.isTemplateDeclaration();
|
|
assert(tempdecl);
|
|
|
|
if (global.params.v.templates)
|
|
TemplateStats.incInstance(tempdecl, tempinst, global.params.v.templatesListInstances);
|
|
|
|
tempdecl.checkDeprecated(tempinst.loc, sc);
|
|
|
|
// If tempdecl is a mixin, disallow it
|
|
if (tempdecl.ismixin)
|
|
{
|
|
.error(tempinst.loc, "%s `%s` mixin templates are not regular templates", tempinst.kind, tempinst.toPrettyChars);
|
|
goto Lerror;
|
|
}
|
|
|
|
tempinst.hasNestedArgs(tempinst.tiargs, tempdecl.isstatic);
|
|
if (tempinst.errors)
|
|
goto Lerror;
|
|
|
|
// Copy the tempdecl namespace (not the scope one)
|
|
tempinst.cppnamespace = tempdecl.cppnamespace;
|
|
if (tempinst.cppnamespace)
|
|
tempinst.cppnamespace.dsymbolSemantic(sc);
|
|
|
|
/* Greatly simplified semantic processing for AliasSeq templates
|
|
*/
|
|
if (tempdecl.isTrivialAliasSeq)
|
|
{
|
|
tempinst.inst = tempinst;
|
|
return aliasSeqInstanceSemantic(tempinst, sc, tempdecl);
|
|
}
|
|
|
|
/* Greatly simplified semantic processing for Alias templates
|
|
*/
|
|
else if (tempdecl.isTrivialAlias)
|
|
{
|
|
tempinst.inst = tempinst;
|
|
return aliasInstanceSemantic(tempinst, sc, tempdecl);
|
|
}
|
|
|
|
|
|
/* See if there is an existing TemplateInstantiation that already
|
|
* implements the typeargs. If so, just refer to that one instead.
|
|
*/
|
|
tempinst.inst = tempdecl.findExistingInstance(tempinst, argumentList);
|
|
TemplateInstance errinst = null;
|
|
if (!tempinst.inst)
|
|
{
|
|
// So, we need to implement 'this' instance.
|
|
}
|
|
else if (tempinst.inst.gagged && !tempinst.gagged && tempinst.inst.errors)
|
|
{
|
|
// If the first instantiation had failed, re-run semantic,
|
|
// so that error messages are shown.
|
|
errinst = tempinst.inst;
|
|
}
|
|
else
|
|
{
|
|
// It's a match
|
|
tempinst.parent = tempinst.inst.parent;
|
|
tempinst.errors = tempinst.inst.errors;
|
|
|
|
// If both this and the previous instantiation were gagged,
|
|
// use the number of errors that happened last time.
|
|
global.errors += tempinst.errors;
|
|
global.gaggedErrors += tempinst.errors;
|
|
|
|
// If the first instantiation was gagged, but this is not:
|
|
if (tempinst.inst.gagged)
|
|
{
|
|
// It had succeeded, mark it is a non-gagged instantiation,
|
|
// and reuse it.
|
|
tempinst.inst.gagged = tempinst.gagged;
|
|
}
|
|
|
|
tempinst.tnext = tempinst.inst.tnext;
|
|
tempinst.inst.tnext = tempinst;
|
|
|
|
/* A module can have explicit template instance and its alias
|
|
* in module scope (e,g, `alias Base64 = Base64Impl!('+', '/');`).
|
|
* If the first instantiation 'inst' had happened in non-root module,
|
|
* compiler can assume that its instantiated code would be included
|
|
* in the separately compiled obj/lib file (e.g. phobos.lib).
|
|
*
|
|
* However, if 'this' second instantiation happened in root module,
|
|
* compiler might need to invoke its codegen
|
|
* (https://issues.dlang.org/show_bug.cgi?id=2500 & https://issues.dlang.org/show_bug.cgi?id=2644).
|
|
* But whole import graph is not determined until all semantic pass finished,
|
|
* so 'inst' should conservatively finish the semantic3 pass for the codegen.
|
|
*/
|
|
if (tempinst.minst && tempinst.minst.isRoot() && !(tempinst.inst.minst && tempinst.inst.minst.isRoot()))
|
|
{
|
|
/* Swap the position of 'inst' and 'this' in the instantiation graph.
|
|
* Then, the primary instance `inst` will be changed to a root instance,
|
|
* along with all members of `inst` having their scopes updated.
|
|
*
|
|
* Before:
|
|
* non-root -> A!() -> B!()[inst] -> C!() { members[non-root] }
|
|
* |
|
|
* root -> D!() -> B!()[this]
|
|
*
|
|
* After:
|
|
* non-root -> A!() -> B!()[this]
|
|
* |
|
|
* root -> D!() -> B!()[inst] -> C!() { members[root] }
|
|
*/
|
|
Module mi = tempinst.minst;
|
|
TemplateInstance ti = tempinst.tinst;
|
|
tempinst.minst = tempinst.inst.minst;
|
|
tempinst.tinst = tempinst.inst.tinst;
|
|
tempinst.inst.minst = mi;
|
|
tempinst.inst.tinst = ti;
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=21299
|
|
`minst` has been updated on the primary instance `inst` so it is
|
|
now coming from a root module, however all Dsymbol `inst.members`
|
|
of the instance still have their `_scope.minst` pointing at the
|
|
original non-root module. We must now propagate `minst` to all
|
|
members so that forward referenced dependencies that get
|
|
instantiated will also be appended to the root module, otherwise
|
|
there will be undefined references at link-time. */
|
|
extern (C++) final class InstMemberWalker : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
TemplateInstance inst;
|
|
|
|
extern (D) this(TemplateInstance inst) scope @safe
|
|
{
|
|
this.inst = inst;
|
|
}
|
|
|
|
override void visit(Dsymbol d)
|
|
{
|
|
if (d._scope)
|
|
d._scope.minst = inst.minst;
|
|
}
|
|
|
|
override void visit(ScopeDsymbol sds)
|
|
{
|
|
sds.members.foreachDsymbol( s => s.accept(this) );
|
|
visit(cast(Dsymbol)sds);
|
|
}
|
|
|
|
override void visit(StructDeclaration sd)
|
|
{
|
|
// need to visit auto-generated methods as well
|
|
if (sd.xeq) visit(sd.xeq);
|
|
if (sd.xcmp) visit(sd.xcmp);
|
|
if (sd.xhash) visit(sd.xhash);
|
|
visit(cast(ScopeDsymbol)sd);
|
|
}
|
|
|
|
override void visit(AttribDeclaration ad)
|
|
{
|
|
ad.include(null).foreachDsymbol( s => s.accept(this) );
|
|
visit(cast(Dsymbol)ad);
|
|
}
|
|
|
|
override void visit(ConditionalDeclaration cd)
|
|
{
|
|
if (cd.condition.inc)
|
|
visit(cast(AttribDeclaration)cd);
|
|
else
|
|
visit(cast(Dsymbol)cd);
|
|
}
|
|
}
|
|
scope v = new InstMemberWalker(tempinst.inst);
|
|
tempinst.inst.accept(v);
|
|
|
|
if (!global.params.allInst &&
|
|
tempinst.minst) // if inst was not speculative...
|
|
{
|
|
assert(!tempinst.minst.isRoot()); // ... it was previously appended to a non-root module
|
|
// Append again to the root module members[], so that the instance will
|
|
// get codegen chances (depending on `tempinst.inst.needsCodegen()`).
|
|
tempinst.inst.appendToModuleMember();
|
|
}
|
|
|
|
assert(tempinst.inst.memberOf && tempinst.inst.memberOf.isRoot(), "no codegen chances");
|
|
}
|
|
|
|
// modules imported by an existing instance should be added to the module
|
|
// that instantiates the instance.
|
|
if (tempinst.minst)
|
|
foreach(imp; tempinst.inst.importedModules)
|
|
if (!tempinst.minst.aimports.contains(imp))
|
|
tempinst.minst.aimports.push(imp);
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("\tit's a match with instance %p, %d\n", tempinst.inst, tempinst.inst.semanticRun);
|
|
}
|
|
return;
|
|
}
|
|
static if (LOG)
|
|
{
|
|
printf("\timplement template instance %s '%s'\n", tempdecl.parent.toChars(), tempinst.toChars());
|
|
printf("\ttempdecl %s\n", tempdecl.toChars());
|
|
}
|
|
const errorsave = global.errors;
|
|
|
|
tempinst.inst = tempinst;
|
|
tempinst.parent = tempinst.enclosing ? tempinst.enclosing : tempdecl.parent;
|
|
//printf("parent = '%s'\n", parent.kind());
|
|
|
|
if (global.params.v.templates)
|
|
TemplateStats.incUnique(tempdecl, tempinst);
|
|
|
|
TemplateInstance tempdecl_instance_idx = tempdecl.addInstance(tempinst);
|
|
|
|
//getIdent();
|
|
|
|
// Store the place we added it to in target_symbol_list(_idx) so we can
|
|
// remove it later if we encounter an error.
|
|
Dsymbols* target_symbol_list = tempinst.appendToModuleMember();
|
|
size_t target_symbol_list_idx = target_symbol_list ? target_symbol_list.length - 1 : 0;
|
|
|
|
// Copy the syntax trees from the TemplateDeclaration
|
|
tempinst.members = Dsymbol.arraySyntaxCopy(tempdecl.members);
|
|
|
|
// resolve TemplateThisParameter
|
|
for (size_t i = 0; i < tempdecl.parameters.length; i++)
|
|
{
|
|
if ((*tempdecl.parameters)[i].isTemplateThisParameter() is null)
|
|
continue;
|
|
Type t = isType((*tempinst.tiargs)[i]);
|
|
assert(t);
|
|
if (STC stc = ModToStc(t.mod))
|
|
{
|
|
//printf("t = %s, stc = x%llx\n", t.toChars(), stc);
|
|
auto s = new Dsymbols();
|
|
s.push(new StorageClassDeclaration(stc, tempinst.members));
|
|
tempinst.members = s;
|
|
}
|
|
break;
|
|
}
|
|
|
|
// Create our own scope for the template parameters
|
|
Scope* _scope = tempdecl._scope;
|
|
if (tempdecl.semanticRun == PASS.initial)
|
|
{
|
|
.error(tempinst.loc, "%s `%s` template instantiation `%s` forward references template declaration `%s`",
|
|
tempinst.kind, tempinst.toPrettyChars, tempinst.toChars(), tempdecl.toChars());
|
|
return;
|
|
}
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("\tcreate scope for template parameters '%s'\n", tempinst.toChars());
|
|
}
|
|
tempinst.argsym = new ScopeDsymbol();
|
|
tempinst.argsym.parent = _scope.parent;
|
|
_scope = _scope.push(tempinst.argsym);
|
|
_scope.tinst = tempinst;
|
|
_scope.minst = tempinst.minst;
|
|
//scope.stc = STC.none;
|
|
|
|
// Declare each template parameter as an alias for the argument type
|
|
Scope* paramscope = _scope.push();
|
|
paramscope.stc = STC.none;
|
|
paramscope.visibility = Visibility(Visibility.Kind.public_); // https://issues.dlang.org/show_bug.cgi?id=14169
|
|
// template parameters should be public
|
|
tempinst.declareParameters(paramscope);
|
|
paramscope.pop();
|
|
|
|
// Add members of template instance to template instance symbol table
|
|
//parent = scope.scopesym;
|
|
tempinst.symtab = new DsymbolTable();
|
|
|
|
tempinst.members.foreachDsymbol( (s)
|
|
{
|
|
static if (LOG)
|
|
{
|
|
printf("\t adding member '%s' %p kind %s to '%s'\n", s.toChars(), s, s.kind(), tempinst.toChars());
|
|
}
|
|
s.addMember(_scope, tempinst);
|
|
});
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("adding members done\n");
|
|
}
|
|
|
|
/* See if there is only one member of template instance, and that
|
|
* member has the same name as the template instance.
|
|
* If so, this template instance becomes an alias for that member.
|
|
*/
|
|
//printf("members.length = %d\n", tempinst.members.length);
|
|
if (tempinst.members.length)
|
|
{
|
|
Dsymbol s;
|
|
if (oneMembers(tempinst.members, s, tempdecl.ident) && s)
|
|
{
|
|
//printf("tempdecl.ident = %s, s = `%s %s`\n", tempdecl.ident.toChars(), s.kind(), s.toPrettyChars());
|
|
//printf("setting aliasdecl\n");
|
|
tempinst.aliasdecl = s;
|
|
}
|
|
}
|
|
|
|
/* If function template declaration
|
|
*/
|
|
if (argumentList.length > 0 && tempinst.aliasdecl)
|
|
{
|
|
if (auto fd = tempinst.aliasdecl.isFuncDeclaration())
|
|
{
|
|
/* Transmit fargs to type so that TypeFunction.dsymbolSemantic() can
|
|
* resolve any "auto ref" storage classes.
|
|
*/
|
|
if (fd.type)
|
|
if (auto tf = fd.type.isTypeFunction())
|
|
tf.inferenceArguments = argumentList;
|
|
}
|
|
}
|
|
|
|
// Do semantic() analysis on template instance members
|
|
static if (LOG)
|
|
{
|
|
printf("\tdo semantic() on template instance members '%s'\n", tempinst.toChars());
|
|
}
|
|
Scope* sc2;
|
|
sc2 = _scope.push(tempinst);
|
|
//printf("enclosing = %d, sc.parent = %s\n", tempinst.enclosing, sc.parent.toChars());
|
|
sc2.parent = tempinst;
|
|
sc2.tinst = tempinst;
|
|
sc2.minst = tempinst.minst;
|
|
sc2.stc &= ~STC.deprecated_;
|
|
tempinst.tryExpandMembers(sc2);
|
|
|
|
tempinst.semanticRun = PASS.semanticdone;
|
|
|
|
/* ConditionalDeclaration may introduce eponymous declaration,
|
|
* so we should find it once again after semantic.
|
|
*/
|
|
if (tempinst.members.length)
|
|
{
|
|
Dsymbol s;
|
|
if (oneMembers(tempinst.members, s, tempdecl.ident) && s)
|
|
{
|
|
if (!tempinst.aliasdecl || tempinst.aliasdecl != s)
|
|
{
|
|
//printf("tempdecl.ident = %s, s = `%s %s`\n", tempdecl.ident.toChars(), s.kind(), s.toPrettyChars());
|
|
//printf("setting aliasdecl 2\n");
|
|
tempinst.aliasdecl = s;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (global.errors != errorsave)
|
|
goto Laftersemantic;
|
|
|
|
/* If any of the instantiation members didn't get semantic() run
|
|
* on them due to forward references, we cannot run semantic2()
|
|
* or semantic3() yet.
|
|
*/
|
|
{
|
|
bool found_deferred_ad = false;
|
|
for (size_t i = 0; i < Module.deferred.length; i++)
|
|
{
|
|
Dsymbol sd = Module.deferred[i];
|
|
AggregateDeclaration ad = sd.isAggregateDeclaration();
|
|
if (ad && ad.parent && ad.parent.isTemplateInstance())
|
|
{
|
|
//printf("deferred template aggregate: %s %s\n",
|
|
// sd.parent.toChars(), sd.toChars());
|
|
found_deferred_ad = true;
|
|
if (ad.parent == tempinst)
|
|
{
|
|
ad.deferred = tempinst;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (found_deferred_ad || Module.deferred.length)
|
|
goto Laftersemantic;
|
|
}
|
|
|
|
/* The problem is when to parse the initializer for a variable.
|
|
* Perhaps VarDeclaration.dsymbolSemantic() should do it like it does
|
|
* for initializers inside a function.
|
|
*/
|
|
//if (sc.parent.isFuncDeclaration())
|
|
{
|
|
/* https://issues.dlang.org/show_bug.cgi?id=782
|
|
* this has problems if the classes this depends on
|
|
* are forward referenced. Find a way to defer semantic()
|
|
* on this template.
|
|
*/
|
|
tempinst.semantic2(sc2);
|
|
}
|
|
if (global.errors != errorsave)
|
|
goto Laftersemantic;
|
|
|
|
if ((sc.func || sc.fullinst) && !tempinst.tinst)
|
|
{
|
|
/* If a template is instantiated inside function, the whole instantiation
|
|
* should be done at that position. But, immediate running semantic3 of
|
|
* dependent templates may cause unresolved forward reference.
|
|
* https://issues.dlang.org/show_bug.cgi?id=9050
|
|
* To avoid the issue, don't run semantic3 until semantic and semantic2 done.
|
|
*/
|
|
TemplateInstances deferred;
|
|
tempinst.deferred = &deferred;
|
|
|
|
//printf("Run semantic3 on %s\n", toChars());
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=23965
|
|
* DRuntime hooks are not deprecated, but may be used for deprecated
|
|
* types. Deprecations are disabled while analysing hooks to avoid
|
|
* spurious error messages.
|
|
*/
|
|
auto saveUseDeprecated = global.params.useDeprecated;
|
|
if (sc.isDeprecated() && isDRuntimeHook(tempinst.name))
|
|
global.params.useDeprecated = DiagnosticReporting.off;
|
|
|
|
tempinst.trySemantic3(sc2);
|
|
|
|
global.params.useDeprecated = saveUseDeprecated;
|
|
|
|
for (size_t i = 0; i < deferred.length; i++)
|
|
{
|
|
//printf("+ run deferred semantic3 on %s\n", deferred[i].toChars());
|
|
deferred[i].semantic3(null);
|
|
}
|
|
|
|
tempinst.deferred = null;
|
|
}
|
|
else if (tempinst.tinst)
|
|
{
|
|
bool doSemantic3 = false;
|
|
FuncDeclaration fd;
|
|
if (tempinst.aliasdecl)
|
|
fd = tempinst.aliasdecl.toAlias2().isFuncDeclaration();
|
|
|
|
if (fd)
|
|
{
|
|
/* Template function instantiation should run semantic3 immediately
|
|
* for attribute inference.
|
|
*/
|
|
scope fld = fd.isFuncLiteralDeclaration();
|
|
if (fld && fld.tok == TOK.reserved)
|
|
doSemantic3 = true;
|
|
else if (sc.func)
|
|
doSemantic3 = true;
|
|
}
|
|
else if (sc.func)
|
|
{
|
|
/* A lambda function in template arguments might capture the
|
|
* instantiated scope context. For the correct context inference,
|
|
* all instantiated functions should run the semantic3 immediately.
|
|
* See also compilable/test14973.d
|
|
*/
|
|
foreach (oarg; tempinst.tdtypes)
|
|
{
|
|
auto s = getDsymbol(oarg);
|
|
if (!s)
|
|
continue;
|
|
|
|
if (auto td = s.isTemplateDeclaration())
|
|
{
|
|
if (!td.literal)
|
|
continue;
|
|
assert(td.members && td.members.length == 1);
|
|
s = (*td.members)[0];
|
|
}
|
|
if (auto fld = s.isFuncLiteralDeclaration())
|
|
{
|
|
if (fld.tok == TOK.reserved)
|
|
{
|
|
doSemantic3 = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//printf("[%s] %s doSemantic3 = %d\n", tempinst.tinst.loc.toChars(), tempinst.tinst.toChars(), doSemantic3);
|
|
}
|
|
if (doSemantic3)
|
|
tempinst.trySemantic3(sc2);
|
|
|
|
TemplateInstance ti = tempinst.tinst;
|
|
int nest = 0;
|
|
while (ti && !ti.deferred && ti.tinst)
|
|
{
|
|
ti = ti.tinst;
|
|
if (++nest > global.recursionLimit)
|
|
{
|
|
global.gag = 0; // ensure error message gets printed
|
|
.error(tempinst.loc, "%s `%s` recursive expansion", tempinst.kind, tempinst.toPrettyChars);
|
|
fatal();
|
|
}
|
|
}
|
|
if (ti && ti.deferred)
|
|
{
|
|
//printf("deferred semantic3 of %p %s, ti = %s, ti.deferred = %p\n", this, toChars(), ti.toChars());
|
|
for (size_t i = 0;; i++)
|
|
{
|
|
if (i == ti.deferred.length)
|
|
{
|
|
ti.deferred.push(tempinst);
|
|
break;
|
|
}
|
|
if ((*ti.deferred)[i] == tempinst)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (tempinst.aliasdecl)
|
|
{
|
|
/* https://issues.dlang.org/show_bug.cgi?id=13816
|
|
* AliasDeclaration tries to resolve forward reference
|
|
* twice (See inuse check in AliasDeclaration.toAlias()). It's
|
|
* necessary to resolve mutual references of instantiated symbols, but
|
|
* it will left a true recursive alias in tuple declaration - an
|
|
* AliasDeclaration A refers TupleDeclaration B, and B contains A
|
|
* in its elements. To correctly make it an error, we strictly need to
|
|
* resolve the alias of eponymous member.
|
|
*/
|
|
tempinst.aliasdecl = tempinst.aliasdecl.toAlias2();
|
|
|
|
// stop AliasAssign tuple building
|
|
if (auto td = tempinst.aliasdecl.isTupleDeclaration())
|
|
td.building = false;
|
|
}
|
|
|
|
Laftersemantic:
|
|
sc2.pop();
|
|
_scope.pop();
|
|
|
|
// Give additional context info if error occurred during instantiation
|
|
if (global.errors != errorsave)
|
|
{
|
|
if (!tempinst.errors)
|
|
{
|
|
if (!tempdecl.literal)
|
|
.error(tempinst.loc, "%s `%s` error instantiating", tempinst.kind, tempinst.toPrettyChars);
|
|
if (tempinst.tinst)
|
|
tempinst.tinst.printInstantiationTrace();
|
|
}
|
|
tempinst.errors = true;
|
|
if (tempinst.gagged)
|
|
{
|
|
// Errors are gagged, so remove the template instance from the
|
|
// instance/symbol lists we added it to and reset our state to
|
|
// finish clean and so we can try to instantiate it again later
|
|
// (see https://issues.dlang.org/show_bug.cgi?id=4302 and https://issues.dlang.org/show_bug.cgi?id=6602).
|
|
tempdecl.removeInstance(tempdecl_instance_idx);
|
|
if (target_symbol_list)
|
|
{
|
|
// Because we added 'this' in the last position above, we
|
|
// should be able to remove it without messing other indices up.
|
|
assert((*target_symbol_list)[target_symbol_list_idx] == tempinst);
|
|
target_symbol_list.remove(target_symbol_list_idx);
|
|
tempinst.memberOf = null; // no longer a member
|
|
}
|
|
tempinst.semanticRun = PASS.initial;
|
|
tempinst.inst = null;
|
|
tempinst.symtab = null;
|
|
}
|
|
}
|
|
else if (errinst)
|
|
{
|
|
/* https://issues.dlang.org/show_bug.cgi?id=14541
|
|
* If the previous gagged instance had failed by
|
|
* circular references, currrent "error reproduction instantiation"
|
|
* might succeed, because of the difference of instantiated context.
|
|
* On such case, the cached error instance needs to be overridden by the
|
|
* succeeded instance.
|
|
*/
|
|
//printf("replaceInstance()\n");
|
|
assert(errinst.errors);
|
|
auto ti1 = TemplateInstanceBox(errinst);
|
|
tempdecl.instances.remove(ti1);
|
|
|
|
auto ti2 = TemplateInstanceBox(tempinst);
|
|
tempdecl.instances[ti2] = tempinst;
|
|
}
|
|
|
|
static if (LOG)
|
|
{
|
|
printf("-TemplateInstance.dsymbolSemantic('%s', this=%p)\n", tempinst.toChars(), tempinst);
|
|
}
|
|
}
|
|
|
|
/******************************************************
|
|
* Do template instance semantic for isAliasSeq templates.
|
|
* This is a greatly simplified version of templateInstanceSemantic().
|
|
*/
|
|
private
|
|
void aliasSeqInstanceSemantic(TemplateInstance tempinst, Scope* sc, TemplateDeclaration tempdecl)
|
|
{
|
|
//printf("[%s] aliasSeqInstance.dsymbolSemantic('%s')\n", tempinst.loc.toChars(), tempinst.toChars());
|
|
Scope* paramscope = sc.push();
|
|
paramscope.stc = STC.none;
|
|
paramscope.visibility = Visibility(Visibility.Kind.public_);
|
|
|
|
TemplateTupleParameter ttp = (*tempdecl.parameters)[0].isTemplateTupleParameter();
|
|
Tuple va = tempinst.tdtypes[0].isTuple();
|
|
Declaration d = new TupleDeclaration(tempinst.loc, ttp.ident, &va.objects);
|
|
d.storage_class |= STC.templateparameter;
|
|
d.dsymbolSemantic(sc);
|
|
|
|
paramscope.pop();
|
|
|
|
tempinst.aliasdecl = d;
|
|
|
|
tempinst.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
/******************************************************
|
|
* Do template instance semantic for isAlias templates.
|
|
* This is a greatly simplified version of templateInstanceSemantic().
|
|
*/
|
|
private
|
|
void aliasInstanceSemantic(TemplateInstance tempinst, Scope* sc, TemplateDeclaration tempdecl)
|
|
{
|
|
//printf("[%s] aliasInstance.dsymbolSemantic('%s')\n", tempinst.loc.toChars(), tempinst.toChars());
|
|
Scope* paramscope = sc.push();
|
|
paramscope.stc = STC.none;
|
|
paramscope.visibility = Visibility(Visibility.Kind.public_);
|
|
|
|
TemplateTypeParameter ttp = (*tempdecl.parameters)[0].isTemplateTypeParameter();
|
|
Type ta = tempinst.tdtypes[0].isType();
|
|
auto ad = tempdecl.onemember.isAliasDeclaration();
|
|
|
|
// Note: qualifiers can be in both 'ad.type.mod' and 'ad.storage_class'
|
|
Declaration d = new AliasDeclaration(tempinst.loc, ttp.ident, ta.addMod(ad.type.mod));
|
|
d.storage_class |= STC.templateparameter | ad.storage_class;
|
|
d.dsymbolSemantic(sc);
|
|
|
|
paramscope.pop();
|
|
|
|
tempinst.aliasdecl = d;
|
|
|
|
tempinst.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
// function used to perform semantic on AliasDeclaration
|
|
void aliasSemantic(AliasDeclaration ds, Scope* sc)
|
|
{
|
|
//printf("AliasDeclaration::semantic() %s %p\n", ds.toChars(), ds.aliassym);
|
|
|
|
// as DsymbolSemanticVisitor::visit(AliasDeclaration), in case we're called first.
|
|
// see https://issues.dlang.org/show_bug.cgi?id=21001
|
|
ds.storage_class |= sc.stc & STC.deprecated_;
|
|
ds.visibility = sc.visibility;
|
|
ds.userAttribDecl = sc.userAttribDecl;
|
|
|
|
void normalRet()
|
|
{
|
|
ds.inuse = 0;
|
|
ds.semanticRun = PASS.semanticdone;
|
|
|
|
if (auto sx = ds.overnext)
|
|
{
|
|
ds.overnext = null;
|
|
if (!ds.overloadInsert(sx))
|
|
ScopeDsymbol.multiplyDefined(Loc.initial, sx, ds);
|
|
}
|
|
}
|
|
|
|
void errorRet()
|
|
{
|
|
ds.aliassym = null;
|
|
ds.type = Type.terror;
|
|
ds.inuse = 0;
|
|
normalRet();
|
|
}
|
|
|
|
// preserve the original type
|
|
if (!ds.originalType && ds.type)
|
|
ds.originalType = ds.type.syntaxCopy();
|
|
|
|
if (ds.aliassym)
|
|
{
|
|
auto fd = ds.aliassym.isFuncLiteralDeclaration();
|
|
auto td = ds.aliassym.isTemplateDeclaration();
|
|
if (fd || td && td.literal)
|
|
{
|
|
if (fd && fd.semanticRun >= PASS.semanticdone)
|
|
return normalRet();
|
|
|
|
Expression e = new FuncExp(ds.loc, ds.aliassym);
|
|
e = e.expressionSemantic(sc);
|
|
if (auto fe = e.isFuncExp())
|
|
{
|
|
ds.aliassym = fe.td ? cast(Dsymbol)fe.td : fe.fd;
|
|
return normalRet();
|
|
}
|
|
else
|
|
return errorRet();
|
|
}
|
|
|
|
if (ds.aliassym.isTemplateInstance())
|
|
ds.aliassym.dsymbolSemantic(sc);
|
|
return normalRet();
|
|
}
|
|
ds.inuse = 1;
|
|
|
|
// Given:
|
|
// alias foo.bar.abc def;
|
|
// it is not knowable from the syntax whether `def` is an alias
|
|
// for type `foo.bar.abc` or an alias for symbol `foo.bar.abc`. It is up to the semantic()
|
|
// pass to distinguish.
|
|
// If it is a type, then `.type` is set and getType() will return that
|
|
// type. If it is a symbol, then `.aliassym` is set and type is `null` -
|
|
// toAlias() will return `.aliassym`
|
|
|
|
const errors = global.errors;
|
|
Type oldtype = ds.type;
|
|
|
|
// Ungag errors when not instantiated DeclDefs scope alias
|
|
auto ungag = Ungag(global.gag);
|
|
//printf("%s parent = %s, gag = %d, instantiated = %d\n", ds.toChars(), ds.parent.toChars(), global.gag, ds.isInstantiated() !is null);
|
|
if (ds.parent && global.gag && !ds.isInstantiated() && !ds.toParent2().isFuncDeclaration() && (sc.minst || sc.tinst))
|
|
{
|
|
//printf("%s type = %s\n", ds.toPrettyChars(), ds.type.toChars());
|
|
global.gag = 0;
|
|
}
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=18480
|
|
// Detect `alias sym = sym;` to prevent creating loops in overload overnext lists.
|
|
if (auto tident = ds.type.isTypeIdentifier())
|
|
{
|
|
if (sc.hasEdition(Edition.v2024) && tident.idents.length)
|
|
{
|
|
alias mt = tident;
|
|
Dsymbol pscopesym;
|
|
Dsymbol s = sc.search(ds.loc, mt.ident, pscopesym);
|
|
// detect `alias a = var1.member_var;` which confusingly resolves to
|
|
// `typeof(var1).member_var`, which can be valid inside the aggregate type
|
|
if (s && s.isVarDeclaration() &&
|
|
mt.ident != Id.This && mt.ident != Id._super)
|
|
{
|
|
s = tident.toDsymbol(sc);
|
|
// don't error for `var1.static_symbol`
|
|
if (s && s.needThis())
|
|
{
|
|
error(ds.loc, "cannot alias %s member `%s` of variable `%s`",
|
|
s.kind(), s.toChars(), mt.ident.toChars());
|
|
errorSupplemental(ds.loc, "Use `typeof(%s)` instead to preserve behaviour",
|
|
mt.ident.toChars());
|
|
}
|
|
}
|
|
}
|
|
// Selective imports are allowed to alias to the same name `import mod : sym=sym`.
|
|
if (!ds._import)
|
|
{
|
|
if (tident.ident is ds.ident && !tident.idents.length)
|
|
{
|
|
error(ds.loc, "`alias %s = %s;` cannot alias itself, use a qualified name to create an overload set",
|
|
ds.ident.toChars(), tident.ident.toChars());
|
|
ds.type = Type.terror;
|
|
}
|
|
}
|
|
}
|
|
/* This section is needed because Type.resolve() will:
|
|
* const x = 3;
|
|
* alias y = x;
|
|
* try to convert identifier x to 3.
|
|
*/
|
|
auto s = ds.type.toDsymbol(sc);
|
|
if (errors != global.errors)
|
|
return errorRet();
|
|
if (s == ds)
|
|
{
|
|
.error(ds.loc, "%s `%s` cannot resolve", ds.kind, ds.toPrettyChars);
|
|
return errorRet();
|
|
}
|
|
if (!s || !s.isEnumMember())
|
|
{
|
|
Type t;
|
|
Expression e;
|
|
Scope* sc2 = sc;
|
|
if (ds.storage_class & (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.disable))
|
|
{
|
|
// For 'ref' to be attached to function types, and picked
|
|
// up by Type.resolve(), it has to go into sc.
|
|
sc2 = sc.push();
|
|
sc2.stc |= ds.storage_class & (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.shared_ | STC.disable);
|
|
}
|
|
ds.type = ds.type.addSTC(ds.storage_class);
|
|
ds.type.resolve(ds.loc, sc2, e, t, s);
|
|
if (sc2 != sc)
|
|
sc2.pop();
|
|
|
|
if (e) // Try to convert Expression to Dsymbol
|
|
{
|
|
// TupleExp is naturally converted to a TupleDeclaration
|
|
if (auto te = e.isTupleExp())
|
|
s = new TupleDeclaration(te.loc, ds.ident, cast(Objects*)te.exps);
|
|
else
|
|
{
|
|
s = getDsymbol(e);
|
|
if (!s)
|
|
{
|
|
if (e.op != EXP.error)
|
|
.error(ds.loc, "%s `%s` cannot alias an expression `%s`", ds.kind, ds.toPrettyChars, e.toChars());
|
|
return errorRet();
|
|
}
|
|
}
|
|
}
|
|
ds.type = t;
|
|
}
|
|
if (s == ds)
|
|
{
|
|
assert(global.errors);
|
|
return errorRet();
|
|
}
|
|
if (s) // it's a symbolic alias
|
|
{
|
|
//printf("alias %s resolved to %s %s\n", ds.toChars(), s.kind(), s.toChars());
|
|
ds.type = null;
|
|
ds.aliassym = s;
|
|
}
|
|
else // it's a type alias
|
|
{
|
|
//printf("alias %s resolved to type %s\n", ds.toChars(), ds.type.toChars());
|
|
ds.type = ds.type.typeSemantic(ds.loc, sc);
|
|
ds.aliassym = null;
|
|
}
|
|
|
|
if (global.gag && errors != global.errors)
|
|
return errorRet();
|
|
|
|
normalRet();
|
|
}
|
|
|
|
/********************
|
|
* Perform semantic on AliasAssignment.
|
|
* Has a lot of similarities to aliasSemantic(). Perhaps they should share code.
|
|
*/
|
|
private void aliasAssignSemantic(AliasAssign ds, Scope* sc)
|
|
{
|
|
//printf("AliasAssign::semantic() %p, %s\n", ds, ds.ident.toChars());
|
|
|
|
void errorRet()
|
|
{
|
|
ds.errors = true;
|
|
ds.type = Type.terror;
|
|
ds.semanticRun = PASS.semanticdone;
|
|
return;
|
|
}
|
|
|
|
/* Find the AliasDeclaration corresponding to ds.
|
|
* Returns: AliasDeclaration if found, null if error
|
|
*/
|
|
AliasDeclaration findAliasDeclaration(AliasAssign ds, Scope* sc)
|
|
{
|
|
Dsymbol scopesym;
|
|
Dsymbol as = sc.search(ds.loc, ds.ident, scopesym);
|
|
if (!as)
|
|
{
|
|
.error(ds.loc, "%s `%s` undefined identifier `%s`", ds.kind, ds.toPrettyChars, ds.ident.toChars());
|
|
return null;
|
|
}
|
|
if (as.errors)
|
|
return null;
|
|
|
|
auto ad = as.isAliasDeclaration();
|
|
if (!ad)
|
|
{
|
|
.error(ds.loc, "%s `%s` identifier `%s` must be an alias declaration", ds.kind, ds.toPrettyChars, as.toChars());
|
|
return null;
|
|
}
|
|
|
|
if (ad.overnext)
|
|
{
|
|
error(ds.loc, "%s `%s` cannot reassign overloaded alias", ds.kind, ds.toPrettyChars);
|
|
return null;
|
|
}
|
|
|
|
// Check constraints on the parent
|
|
auto adParent = ad.toParent();
|
|
if (adParent != ds.toParent())
|
|
{
|
|
if (!adParent)
|
|
adParent = ds.toParent();
|
|
.error(ds.loc, "`%s` must have same parent `%s` as alias `%s`", ds.ident.toChars(), adParent.toChars(), ad.toChars());
|
|
return null;
|
|
}
|
|
if (!adParent.isTemplateInstance())
|
|
{
|
|
.error(ds.loc, "%s `%s` must be a member of a template", ds.kind, ds.toPrettyChars);
|
|
return null;
|
|
}
|
|
|
|
return ad;
|
|
}
|
|
|
|
auto aliassym = findAliasDeclaration(ds, sc);
|
|
if (!aliassym)
|
|
return errorRet();
|
|
|
|
if (aliassym.wasRead)
|
|
{
|
|
if (!aliassym.errors)
|
|
error(ds.loc, "%s was read, so cannot reassign", aliassym.toChars());
|
|
aliassym.errors = true;
|
|
return errorRet();
|
|
}
|
|
|
|
aliassym.ignoreRead = true; // temporarilly allow reads of aliassym
|
|
|
|
const storage_class = sc.stc & (STC.deprecated_ | STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.shared_ | STC.disable);
|
|
|
|
if (ds.aliassym)
|
|
{
|
|
auto fd = ds.aliassym.isFuncLiteralDeclaration();
|
|
auto td = ds.aliassym.isTemplateDeclaration();
|
|
if (fd && fd.semanticRun >= PASS.semanticdone)
|
|
{
|
|
}
|
|
else if (fd || td && td.literal)
|
|
{
|
|
|
|
Expression e = new FuncExp(ds.loc, ds.aliassym);
|
|
e = e.expressionSemantic(sc);
|
|
auto fe = e.isFuncExp();
|
|
if (!fe)
|
|
return errorRet();
|
|
ds.aliassym = fe.td ? cast(Dsymbol)fe.td : fe.fd;
|
|
}
|
|
else if (ds.aliassym.isTemplateInstance())
|
|
ds.aliassym.dsymbolSemantic(sc);
|
|
|
|
aliassym.type = null;
|
|
aliassym.aliassym = ds.aliassym;
|
|
return;
|
|
}
|
|
|
|
/* Given:
|
|
* abc = def;
|
|
* it is not knownable from the syntax whether `def` is a type or a symbol.
|
|
* It appears here as `ds.type`. Do semantic analysis on `def` to disambiguate.
|
|
*/
|
|
|
|
const errors = global.errors;
|
|
Dsymbol s;
|
|
|
|
// Try AliasSeq optimization
|
|
if (auto ti = ds.type.isTypeInstance())
|
|
{
|
|
if (!ti.tempinst.findTempDecl(sc, null))
|
|
return errorRet();
|
|
if (auto tempinst = isAliasSeq(sc, ti))
|
|
{
|
|
s = aliasAssignInPlace(sc, tempinst, aliassym);
|
|
if (!s)
|
|
return errorRet();
|
|
goto Lsymdone;
|
|
}
|
|
}
|
|
|
|
/* This section is needed because Type.resolve() will:
|
|
* const x = 3;
|
|
* alias y = x;
|
|
* try to convert identifier x to 3.
|
|
*/
|
|
s = ds.type.toDsymbol(sc);
|
|
if (errors != global.errors)
|
|
return errorRet();
|
|
if (s == aliassym)
|
|
{
|
|
.error(ds.loc, "%s `%s` cannot resolve", ds.kind, ds.toPrettyChars);
|
|
return errorRet();
|
|
}
|
|
|
|
if (!s || !s.isEnumMember())
|
|
{
|
|
Type t;
|
|
Expression e;
|
|
Scope* sc2 = sc;
|
|
if (storage_class & (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.shared_ | STC.disable))
|
|
{
|
|
// For 'ref' to be attached to function types, and picked
|
|
// up by Type.resolve(), it has to go into sc.
|
|
sc2 = sc.push();
|
|
sc2.stc |= storage_class & (STC.ref_ | STC.nothrow_ | STC.nogc | STC.pure_ | STC.shared_ | STC.disable);
|
|
}
|
|
ds.type = ds.type.addSTC(storage_class);
|
|
ds.type.resolve(ds.loc, sc2, e, t, s);
|
|
if (sc2 != sc)
|
|
sc2.pop();
|
|
|
|
if (e) // Try to convert Expression to Dsymbol
|
|
{
|
|
// TupleExp is naturally converted to a TupleDeclaration
|
|
if (auto te = e.isTupleExp())
|
|
s = new TupleDeclaration(te.loc, ds.ident, cast(Objects*)te.exps);
|
|
else
|
|
{
|
|
s = getDsymbol(e);
|
|
if (!s)
|
|
{
|
|
if (e.op != EXP.error)
|
|
.error(ds.loc, "%s `%s` cannot alias an expression `%s`", ds.kind, ds.toPrettyChars, e.toChars());
|
|
return errorRet();
|
|
}
|
|
}
|
|
}
|
|
ds.type = t;
|
|
}
|
|
if (s == aliassym)
|
|
{
|
|
assert(global.errors);
|
|
return errorRet();
|
|
}
|
|
|
|
if (s) // it's a symbolic alias
|
|
{
|
|
Lsymdone:
|
|
//printf("alias %s resolved to %s %s\n", toChars(), s.kind(), s.toChars());
|
|
aliassym.type = null;
|
|
aliassym.aliassym = s;
|
|
aliassym.storage_class |= sc.stc & STC.deprecated_;
|
|
aliassym.visibility = sc.visibility;
|
|
aliassym.userAttribDecl = sc.userAttribDecl;
|
|
}
|
|
else // it's a type alias
|
|
{
|
|
//printf("alias %s resolved to type %s\n", toChars(), type.toChars());
|
|
aliassym.type = ds.type.typeSemantic(ds.loc, sc);
|
|
aliassym.aliassym = null;
|
|
}
|
|
|
|
aliassym.ignoreRead = false;
|
|
|
|
if (aliassym.type && aliassym.type.ty == Terror ||
|
|
global.gag && errors != global.errors)
|
|
{
|
|
aliassym.type = Type.terror;
|
|
aliassym.aliassym = null;
|
|
return errorRet();
|
|
}
|
|
|
|
ds.semanticRun = PASS.semanticdone;
|
|
}
|
|
|
|
/***************************************
|
|
* Expands template instance arguments inside 'alias assign' target declaration (aliassym),
|
|
* instead of inside 'tempinst.tiargs' every time.
|
|
* Params:
|
|
* tempinst = AliasSeq instance
|
|
* aliassym = the AliasDeclaration corresponding to AliasAssign
|
|
* Returns:
|
|
* null.
|
|
*/
|
|
private TupleDeclaration aliasAssignInPlace(Scope* sc, TemplateInstance tempinst,
|
|
AliasDeclaration aliassym)
|
|
{
|
|
// Mark instance with semantic done, not needed but just in case.
|
|
tempinst.inst = tempinst;
|
|
tempinst.semanticRun = PASS.semanticdone;
|
|
TupleDeclaration td;
|
|
if (aliassym.type)
|
|
{
|
|
// Convert TypeTuple to TupleDeclaration to avoid back and forth allocations
|
|
// in the assignment process
|
|
if (auto tt = aliassym.type.isTypeTuple())
|
|
{
|
|
auto objs = new Objects(tt.arguments.length);
|
|
foreach (i, p; *tt.arguments)
|
|
(*objs)[i] = p.type;
|
|
td = new TupleDeclaration(tempinst.loc, aliassym.ident, objs);
|
|
td.storage_class |= STC.templateparameter;
|
|
td.building = true;
|
|
aliassym.type = null;
|
|
}
|
|
else if (aliassym.type.isTypeError())
|
|
return null;
|
|
|
|
}
|
|
else if (auto otd = aliassym.aliassym.isTupleDeclaration())
|
|
{
|
|
if (otd.building)
|
|
td = otd;
|
|
else
|
|
{
|
|
td = new TupleDeclaration(tempinst.loc, aliassym.ident, otd.objects.copy());
|
|
td.storage_class |= STC.templateparameter;
|
|
td.building = true;
|
|
}
|
|
}
|
|
// If starting from single element in aliassym (td == null) we need to build the tuple
|
|
// after semanticTiargs to keep same semantics (for example a FuncLiteraldeclaration
|
|
// template argument is converted to FuncExp)
|
|
if (td)
|
|
aliassym.aliassym = td;
|
|
aliassym.semanticRun = PASS.semanticdone;
|
|
if (!TemplateInstance.semanticTiargs(tempinst.loc, sc, tempinst.tiargs, 0, td))
|
|
{
|
|
tempinst.errors = true;
|
|
return null;
|
|
}
|
|
// The alias will stop tuple 'building' mode when used (in AliasDeclaration.toAlias(),
|
|
// then TupleDeclaration.getType() will work again)
|
|
aliassym.semanticRun = PASS.initial;
|
|
if (!td)
|
|
{
|
|
td = new TupleDeclaration(tempinst.loc, aliassym.ident, tempinst.tiargs);
|
|
td.storage_class |= STC.templateparameter;
|
|
td.building = true;
|
|
return td;
|
|
}
|
|
|
|
auto tiargs = tempinst.tiargs;
|
|
size_t oldlen = td.objects.length;
|
|
size_t origstart;
|
|
size_t insertidx;
|
|
size_t insertlen;
|
|
foreach (i, o; *tiargs)
|
|
{
|
|
if (o !is td)
|
|
{
|
|
++insertlen;
|
|
continue;
|
|
}
|
|
// tuple contains itself (tuple = AliasSeq!(..., tuple, ...))
|
|
if (insertlen) // insert any left element before
|
|
{
|
|
td.objects.insert(insertidx, (*tiargs)[i - insertlen .. i]);
|
|
if (insertidx == 0) // reset original tuple start point
|
|
origstart = insertlen;
|
|
insertlen = 0;
|
|
}
|
|
if (insertidx) // insert tuple if found more than one time
|
|
{
|
|
td.objects.reserve(oldlen); // reserve first to assert a valid slice
|
|
td.objects.pushSlice((*td.objects)[origstart .. origstart + oldlen]);
|
|
}
|
|
insertidx = td.objects.length;
|
|
}
|
|
if (insertlen)
|
|
{
|
|
if (insertlen != tiargs.length) // insert any left element
|
|
td.objects.pushSlice((*tiargs)[$ - insertlen .. $]);
|
|
else
|
|
// just assign tiargs if tuple = AliasSeq!(nottuple, nottuple...)
|
|
td.objects = tempinst.tiargs;
|
|
}
|
|
return td;
|
|
}
|
|
|
|
/***************************************
|
|
* Check if a template instance is a trivial AliasSeq but without other overloads.
|
|
* We can only be 100% sure of being AliasSeq after running semanticTiargs()
|
|
* and findBestMatch() but this optimization must happen before that.
|
|
*/
|
|
private TemplateInstance isAliasSeq(Scope* sc, TypeInstance ti)
|
|
{
|
|
auto tovers = ti.tempinst.tempdecl.isOverloadSet();
|
|
foreach (size_t oi; 0 .. tovers ? tovers.a.length : 1)
|
|
{
|
|
Dsymbol dstart = tovers ? tovers.a[oi] : ti.tempinst.tempdecl;
|
|
int r = overloadApply(dstart, (Dsymbol s)
|
|
{
|
|
auto td = s.isTemplateDeclaration();
|
|
if (!td || !td.isTrivialAliasSeq)
|
|
return 1;
|
|
return 0;
|
|
});
|
|
if (r)
|
|
return null;
|
|
}
|
|
return ti.tempinst;
|
|
}
|
|
|
|
/***************************************
|
|
* Find all instance fields in `ad`, then push them into `fields`.
|
|
*
|
|
* Runs semantic() for all instance field variables, but also
|
|
* the field types can remain yet not resolved forward references,
|
|
* except direct recursive definitions.
|
|
* After the process sizeok is set to Sizeok.fwd.
|
|
*
|
|
* Params:
|
|
* ad = the AggregateDeclaration to examine
|
|
* Returns:
|
|
* false if any errors occur.
|
|
*/
|
|
bool determineFields(AggregateDeclaration ad)
|
|
{
|
|
if (ad._scope)
|
|
dsymbolSemantic(ad, null);
|
|
if (ad.sizeok != Sizeok.none)
|
|
return true;
|
|
|
|
//printf("determineFields() %s, fields.length = %d\n", toChars(), fields.length);
|
|
// determineFields can be called recursively from one of the fields's v.semantic
|
|
ad.fields.setDim(0);
|
|
|
|
static int func(Dsymbol s, void* ctx)
|
|
{
|
|
auto ad = cast(AggregateDeclaration)ctx;
|
|
auto v = s.isVarDeclaration();
|
|
if (!v)
|
|
return 0;
|
|
if (v.storage_class & STC.manifest)
|
|
return 0;
|
|
|
|
if (v.semanticRun < PASS.semanticdone)
|
|
v.dsymbolSemantic(null);
|
|
// Return in case a recursive determineFields triggered by v.semantic already finished
|
|
if (ad.sizeok != Sizeok.none)
|
|
return 1;
|
|
|
|
if (v.aliasTuple)
|
|
{
|
|
// If this variable was really a tuple, process each element.
|
|
return v.aliasTuple.foreachVar(tv => tv.apply(&func, cast(void*) ad));
|
|
}
|
|
|
|
if (v.storage_class & (STC.static_ | STC.extern_ | STC.tls | STC.gshared | STC.manifest | STC.ctfe | STC.templateparameter))
|
|
return 0;
|
|
if (!v.isField() || v.semanticRun < PASS.semanticdone)
|
|
return 1; // unresolvable forward reference
|
|
|
|
ad.fields.push(v);
|
|
|
|
if (v.storage_class & STC.ref_)
|
|
return 0;
|
|
auto tv = v.type.baseElemOf();
|
|
if (auto tvs = tv.isTypeStruct())
|
|
{
|
|
if (ad == tvs.sym)
|
|
{
|
|
const(char)* psz = (v.type.toBasetype().ty == Tsarray) ? "static array of " : "";
|
|
.error(ad.loc, "%s `%s` cannot have field `%s` with %ssame struct type", ad.kind, ad.toPrettyChars, v.toChars(), psz);
|
|
ad.type = Type.terror;
|
|
ad.errors = true;
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
if (ad.members)
|
|
{
|
|
for (size_t i = 0; i < ad.members.length; i++)
|
|
{
|
|
auto s = (*ad.members)[i];
|
|
if (s.apply(&func, cast(void *)ad))
|
|
{
|
|
if (ad.sizeok != Sizeok.none)
|
|
{
|
|
// recursive determineFields already finished
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ad.sizeok != Sizeok.done)
|
|
ad.sizeok = Sizeok.fwd;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// Do an atomic operation (currently tailored to [shared] static ctors|dtors) needs
|
|
private CallExp doAtomicOp (string op, Identifier var, Expression arg)
|
|
{
|
|
assert(op == "-=" || op == "+=");
|
|
|
|
Module mod = Module.loadCoreAtomic();
|
|
if (!mod)
|
|
return null; // core.atomic couldn't be loaded
|
|
|
|
const loc = Loc.initial;
|
|
|
|
Objects* tiargs = new Objects(1);
|
|
(*tiargs)[0] = new StringExp(loc, op);
|
|
|
|
Expressions* args = new Expressions(2);
|
|
(*args)[0] = new IdentifierExp(loc, var);
|
|
(*args)[1] = arg;
|
|
|
|
auto sc = new ScopeExp(loc, mod);
|
|
auto dti = new DotTemplateInstanceExp(
|
|
loc, sc, Id.atomicOp, tiargs);
|
|
|
|
return CallExp.create(loc, dti, args);
|
|
}
|
|
|
|
/***************************************************
|
|
* Set up loc for a parse of a mixin. Append the input text to the mixin.
|
|
* Params:
|
|
* input = mixin text
|
|
* loc = location of expansion
|
|
* baseLoc = location to adjust
|
|
* mixinOut = sink for mixin text data
|
|
* Returns:
|
|
* adjusted loc suitable for Parser
|
|
*/
|
|
|
|
void adjustLocForMixin(const(char)[] input, Loc loc, ref BaseLoc baseLoc, ref Output mixinOut)
|
|
{
|
|
if (mixinOut.doOutput)
|
|
{
|
|
const lines = mixinOut.bufferLines;
|
|
writeMixin(input, loc, mixinOut.bufferLines, *mixinOut.buffer);
|
|
baseLoc.startLine = lines + 2;
|
|
baseLoc.filename = mixinOut.name;
|
|
return;
|
|
}
|
|
|
|
SourceLoc sl = SourceLoc(loc);
|
|
if (sl.filename.length == 0)
|
|
{
|
|
// Rare case of compiler-generated mixin exp, e.g. __xtoHash
|
|
baseLoc.filename = "";
|
|
return;
|
|
}
|
|
|
|
/* Create a pseudo-filename for the mixin string, as it may not even exist
|
|
* in the source file.
|
|
*/
|
|
auto len = sl.filename.length + 7 + (sl.linnum).sizeof * 3 + 1;
|
|
char* filename = cast(char*) mem.xmalloc(len);
|
|
snprintf(filename, len, "%.*s-mixin-%d", cast(int) sl.filename.length, sl.filename.ptr, cast(int) sl.linnum);
|
|
baseLoc.startLine = sl.line;
|
|
baseLoc.filename = filename.toDString;
|
|
}
|
|
|
|
/**************************************
|
|
* Append source code text to output for better debugging.
|
|
* Canonicalize line endings.
|
|
* Params:
|
|
* s = source code text
|
|
* loc = location of source code text
|
|
* lines = line count to update
|
|
* output = sink for output
|
|
*/
|
|
private void writeMixin(const(char)[] s, Loc loc, ref int lines, ref OutBuffer buf)
|
|
{
|
|
buf.writestring("// expansion at ");
|
|
buf.writestring(loc.toChars());
|
|
buf.writenl();
|
|
|
|
++lines;
|
|
|
|
// write by line to create consistent line endings
|
|
size_t lastpos = 0;
|
|
for (size_t i = 0; i < s.length; ++i)
|
|
{
|
|
// detect LF and CRLF
|
|
const c = s[i];
|
|
if (c == '\n' || (c == '\r' && i+1 < s.length && s[i+1] == '\n'))
|
|
{
|
|
buf.writestring(s[lastpos .. i]);
|
|
buf.writenl();
|
|
++lines;
|
|
if (c == '\r')
|
|
++i;
|
|
lastpos = i + 1;
|
|
}
|
|
}
|
|
|
|
if(lastpos < s.length)
|
|
buf.writestring(s[lastpos .. $]);
|
|
|
|
if (s.length == 0 || s[$-1] != '\n')
|
|
{
|
|
buf.writenl(); // ensure empty line after expansion
|
|
++lines;
|
|
}
|
|
buf.writenl();
|
|
++lines;
|
|
}
|
|
|
|
/*********************************************
|
|
* Search for ident as member of d.
|
|
* Params:
|
|
* d = dsymbol where ident is searched for
|
|
* loc = location to print for error messages
|
|
* ident = identifier to search for
|
|
* flags = search options
|
|
* Returns:
|
|
* null if not found
|
|
*/
|
|
Dsymbol search(Dsymbol d, Loc loc, Identifier ident, SearchOptFlags flags = SearchOpt.all)
|
|
{
|
|
scope v = new SearchVisitor(loc, ident, flags);
|
|
d.accept(v);
|
|
return v.result;
|
|
}
|
|
|
|
Dsymbol search_correct(Dsymbol d, Identifier ident)
|
|
{
|
|
/***************************************************
|
|
* Search for symbol with correct spelling.
|
|
*/
|
|
Dsymbol symbol_search_fp(const(char)[] seed, out int cost)
|
|
{
|
|
/* If not in the lexer's string table, it certainly isn't in the symbol table.
|
|
* Doing this first is a lot faster.
|
|
*/
|
|
if (!seed.length)
|
|
return null;
|
|
Identifier id = Identifier.lookup(seed);
|
|
if (!id)
|
|
return null;
|
|
cost = 0; // all the same cost
|
|
Dsymbol s = d;
|
|
Module.clearCache();
|
|
return s.search(Loc.initial, id, SearchOpt.ignoreErrors);
|
|
}
|
|
|
|
if (global.gag)
|
|
return null; // don't do it for speculative compiles; too time consuming
|
|
// search for exact name first
|
|
if (auto s = d.search(Loc.initial, ident, SearchOpt.ignoreErrors))
|
|
return s;
|
|
|
|
import dmd.root.speller : speller;
|
|
return speller!symbol_search_fp(ident.toString());
|
|
}
|
|
|
|
private extern(C++) class SearchVisitor : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
const Loc loc;
|
|
Identifier ident;
|
|
SearchOptFlags flags;
|
|
Dsymbol result;
|
|
|
|
this(Loc loc, Identifier ident, SearchOptFlags flags) @safe
|
|
{
|
|
this.loc = loc;
|
|
this.ident = ident;
|
|
this.flags = flags;
|
|
}
|
|
|
|
void setResult(Dsymbol d)
|
|
{
|
|
result = d;
|
|
}
|
|
|
|
override void visit(Dsymbol d)
|
|
{
|
|
//printf("Dsymbol::search(this=%p,%s, ident='%s')\n", d, d.toChars(), ident.toChars());
|
|
return setResult(null);
|
|
}
|
|
|
|
override void visit(ScopeDsymbol sds)
|
|
{
|
|
//printf("%s.ScopeDsymbol::search(ident='%s', flags=x%x)\n", sds.toChars(), ident.toChars(), flags);
|
|
//if (strcmp(ident.toChars(),"c") == 0) *(char*)0=0;
|
|
|
|
// Look in symbols declared in this module
|
|
if (sds.symtab && !(flags & SearchOpt.importsOnly))
|
|
{
|
|
//printf(" look in locals\n");
|
|
auto s1 = sds.symtab.lookup(ident);
|
|
if (s1)
|
|
{
|
|
//printf("\tfound in locals = '%s.%s'\n",toChars(),s1.toChars());
|
|
return setResult(s1);
|
|
}
|
|
}
|
|
//printf(" not found in locals\n");
|
|
|
|
// Look in imported scopes
|
|
if (!sds.importedScopes)
|
|
return setResult(null);
|
|
|
|
//printf(" look in imports\n");
|
|
Dsymbol s = null;
|
|
OverloadSet a = null;
|
|
// Look in imported modules
|
|
for (size_t i = 0; i < sds.importedScopes.length; i++)
|
|
{
|
|
// If private import, don't search it
|
|
if ((flags & SearchOpt.ignorePrivateImports) && sds.visibilities[i] == Visibility.Kind.private_)
|
|
continue;
|
|
SearchOptFlags sflags = flags & (SearchOpt.ignoreErrors | SearchOpt.ignoreAmbiguous); // remember these in recursive searches
|
|
Dsymbol ss = (*sds.importedScopes)[i];
|
|
//printf("\tscanning import '%s', visibilities = %d, isModule = %p, isImport = %p\n", ss.toChars(), visibilities[i], ss.isModule(), ss.isImport());
|
|
|
|
if (ss.isModule())
|
|
{
|
|
if (flags & SearchOpt.localsOnly)
|
|
continue;
|
|
}
|
|
else // mixin template
|
|
{
|
|
if (flags & SearchOpt.importsOnly)
|
|
continue;
|
|
|
|
sflags |= SearchOpt.localsOnly;
|
|
}
|
|
|
|
/* Don't find private members if ss is a module
|
|
*/
|
|
Dsymbol s2 = ss.search(loc, ident, sflags | (ss.isModule() ? SearchOpt.ignorePrivateImports : SearchOpt.all));
|
|
import dmd.access : symbolIsVisible;
|
|
if (!s2 || !(flags & SearchOpt.ignoreVisibility) && !symbolIsVisible(sds, s2))
|
|
continue;
|
|
if (!s)
|
|
{
|
|
s = s2;
|
|
if (s && s.isOverloadSet())
|
|
a = sds.mergeOverloadSet(ident, a, s);
|
|
}
|
|
else if (s2 && s != s2)
|
|
{
|
|
if (s.toAlias() == s2.toAlias() || s.getType() == s2.getType() && s.getType())
|
|
{
|
|
/* After following aliases, we found the same
|
|
* symbol, so it's not an ambiguity. But if one
|
|
* alias is deprecated or less accessible, prefer
|
|
* the other.
|
|
*/
|
|
if (s.isDeprecated() || s.visible() < s2.visible() && s2.visible().kind != Visibility.Kind.none)
|
|
s = s2;
|
|
}
|
|
else
|
|
{
|
|
/* Two imports of the same module should be regarded as
|
|
* the same.
|
|
*/
|
|
Import i1 = s.isImport();
|
|
Import i2 = s2.isImport();
|
|
if (!(i1 && i2 && (i1.mod == i2.mod || (!i1.parent.isImport() && !i2.parent.isImport() && i1.ident.equals(i2.ident)))))
|
|
{
|
|
/* https://issues.dlang.org/show_bug.cgi?id=8668
|
|
* Public selective import adds AliasDeclaration in module.
|
|
* To make an overload set, resolve aliases in here and
|
|
* get actual overload roots which accessible via s and s2.
|
|
*/
|
|
s = s.toAlias();
|
|
s2 = s2.toAlias();
|
|
/* If both s2 and s are overloadable (though we only
|
|
* need to check s once)
|
|
*/
|
|
|
|
auto so2 = s2.isOverloadSet();
|
|
if ((so2 || s2.isOverloadable()) && (a || s.isOverloadable()))
|
|
{
|
|
if (symbolIsVisible(sds, s2))
|
|
{
|
|
a = sds.mergeOverloadSet(ident, a, s2);
|
|
}
|
|
if (!symbolIsVisible(sds, s))
|
|
s = s2;
|
|
continue;
|
|
}
|
|
|
|
/* Two different overflow sets can have the same members
|
|
* https://issues.dlang.org/show_bug.cgi?id=16709
|
|
*/
|
|
auto so = s.isOverloadSet();
|
|
if (so && so2)
|
|
{
|
|
if (so.a.length == so2.a.length)
|
|
{
|
|
foreach (j; 0 .. so.a.length)
|
|
{
|
|
if (so.a[j] !is so2.a[j])
|
|
goto L1;
|
|
}
|
|
continue; // the same
|
|
L1:
|
|
{ } // different
|
|
}
|
|
}
|
|
|
|
if (flags & SearchOpt.ignoreAmbiguous) // if return NULL on ambiguity
|
|
return setResult(null);
|
|
|
|
/* If two imports from C import files, pick first one, as C has global name space
|
|
*/
|
|
if (s.isCsymbol() && s2.isCsymbol())
|
|
continue;
|
|
|
|
if (!(flags & SearchOpt.ignoreErrors))
|
|
ScopeDsymbol.multiplyDefined(loc, s, s2);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (s)
|
|
{
|
|
/* Build special symbol if we had multiple finds
|
|
*/
|
|
if (a)
|
|
{
|
|
if (!s.isOverloadSet())
|
|
a = sds.mergeOverloadSet(ident, a, s);
|
|
s = a;
|
|
}
|
|
//printf("\tfound in imports %s.%s\n", toChars(), s.toChars());
|
|
return setResult(s);
|
|
}
|
|
//printf(" not found in imports\n");
|
|
return setResult(null);
|
|
}
|
|
|
|
override void visit(WithScopeSymbol ws)
|
|
{
|
|
//printf("WithScopeSymbol.search(%s)\n", ident.toChars());
|
|
if (flags & SearchOpt.importsOnly)
|
|
return setResult(null);
|
|
// Acts as proxy to the with class declaration
|
|
Dsymbol s = null;
|
|
Expression eold = null;
|
|
for (Expression e = ws.withstate.exp; e && e != eold; e = resolveAliasThis(ws._scope, e, true))
|
|
{
|
|
if (auto se = e.isScopeExp())
|
|
{
|
|
s = se.sds;
|
|
}
|
|
else if (e.isTypeExp())
|
|
{
|
|
s = e.type.toDsymbol(null);
|
|
}
|
|
else
|
|
{
|
|
Type t = e.type.toBasetype();
|
|
s = t.toDsymbol(null);
|
|
}
|
|
if (s)
|
|
{
|
|
s = s.search(loc, ident, flags);
|
|
if (s)
|
|
return setResult(s);
|
|
}
|
|
eold = e;
|
|
}
|
|
return setResult(null);
|
|
}
|
|
|
|
override void visit(ArrayScopeSymbol ass)
|
|
{
|
|
//printf("ArrayScopeSymbol::search('%s', flags = %d)\n", ident.toChars(), flags);
|
|
if (ident != Id.dollar)
|
|
return setResult(null);
|
|
|
|
VarDeclaration* pvar;
|
|
Expression ce;
|
|
|
|
static Dsymbol dollarFromTypeTuple(Loc loc, TypeTuple tt, Scope* sc)
|
|
{
|
|
|
|
/* $ gives the number of type entries in the type tuple
|
|
*/
|
|
auto v = new VarDeclaration(loc, Type.tsize_t, Id.dollar, null);
|
|
Expression e = new IntegerExp(Loc.initial, tt.arguments.length, Type.tsize_t);
|
|
v._init = new ExpInitializer(Loc.initial, e);
|
|
v.storage_class |= STC.temp | STC.static_ | STC.const_;
|
|
v.dsymbolSemantic(sc);
|
|
return v;
|
|
}
|
|
|
|
const DYNCAST kind = ass.arrayContent.dyncast();
|
|
switch (kind) with (DYNCAST)
|
|
{
|
|
case dsymbol:
|
|
TupleDeclaration td = cast(TupleDeclaration) ass.arrayContent;
|
|
/* $ gives the number of elements in the tuple
|
|
*/
|
|
auto v = new VarDeclaration(loc, Type.tsize_t, Id.dollar, null);
|
|
Expression e = new IntegerExp(Loc.initial, td.objects.length, Type.tsize_t);
|
|
v._init = new ExpInitializer(Loc.initial, e);
|
|
v.storage_class |= STC.temp | STC.static_ | STC.const_;
|
|
v.dsymbolSemantic(ass._scope);
|
|
return setResult(v);
|
|
case type:
|
|
return setResult(dollarFromTypeTuple(loc, cast(TypeTuple) ass.arrayContent, ass._scope));
|
|
default:
|
|
break;
|
|
}
|
|
Expression exp = cast(Expression) ass.arrayContent;
|
|
if (auto ie = exp.isIndexExp())
|
|
{
|
|
/* array[index] where index is some function of $
|
|
*/
|
|
pvar = &ie.lengthVar;
|
|
ce = ie.e1;
|
|
}
|
|
else if (auto se = exp.isSliceExp())
|
|
{
|
|
/* array[lwr .. upr] where lwr or upr is some function of $
|
|
*/
|
|
pvar = &se.lengthVar;
|
|
ce = se.e1;
|
|
}
|
|
else if (auto ae = exp.isArrayExp())
|
|
{
|
|
/* array[e0, e1, e2, e3] where e0, e1, e2 are some function of $
|
|
* $ is a opDollar!(dim)() where dim is the dimension(0,1,2,...)
|
|
*/
|
|
pvar = &ae.lengthVar;
|
|
ce = ae.e1;
|
|
}
|
|
else
|
|
{
|
|
/* Didn't find $, look in enclosing scope(s).
|
|
*/
|
|
return setResult(null);
|
|
}
|
|
ce = ce.lastComma();
|
|
/* If we are indexing into an array that is really a type
|
|
* tuple, rewrite this as an index into a type tuple and
|
|
* try again.
|
|
*/
|
|
if (auto te = ce.isTypeExp())
|
|
{
|
|
if (auto ttp = te.type.isTypeTuple())
|
|
return setResult(dollarFromTypeTuple(loc, ttp, ass._scope));
|
|
}
|
|
/* *pvar is lazily initialized, so if we refer to $
|
|
* multiple times, it gets set only once.
|
|
*/
|
|
if (!*pvar) // if not already initialized
|
|
{
|
|
/* Create variable v and set it to the value of $
|
|
*/
|
|
VarDeclaration v;
|
|
Type t;
|
|
if (auto tupexp = ce.isTupleExp())
|
|
{
|
|
/* It is for an expression tuple, so the
|
|
* length will be a const.
|
|
*/
|
|
Expression e = new IntegerExp(Loc.initial, tupexp.exps.length, Type.tsize_t);
|
|
v = new VarDeclaration(loc, Type.tsize_t, Id.dollar, new ExpInitializer(Loc.initial, e));
|
|
v.storage_class |= STC.temp | STC.static_ | STC.const_;
|
|
}
|
|
else if (ce.type && (t = ce.type.toBasetype()) !is null && (t.ty == Tstruct || t.ty == Tclass))
|
|
{
|
|
// Look for opDollar
|
|
assert(exp.op == EXP.array || exp.op == EXP.slice);
|
|
AggregateDeclaration ad = isAggregate(t);
|
|
assert(ad);
|
|
Dsymbol s = ad.search(loc, Id.opDollar);
|
|
if (!s) // no dollar exists -- search in higher scope
|
|
return setResult(null);
|
|
s = s.toAlias();
|
|
Expression e = null;
|
|
// Check for multi-dimensional opDollar(dim) template.
|
|
if (TemplateDeclaration td = s.isTemplateDeclaration())
|
|
{
|
|
dinteger_t dim = 0;
|
|
if (auto ae = exp.isArrayExp())
|
|
{
|
|
dim = ae.currentDimension;
|
|
}
|
|
else if (exp.isSliceExp())
|
|
{
|
|
dim = 0; // slices are currently always one-dimensional
|
|
}
|
|
else
|
|
{
|
|
assert(0);
|
|
}
|
|
auto tiargs = new Objects();
|
|
Expression edim = new IntegerExp(Loc.initial, dim, Type.tsize_t);
|
|
edim = edim.expressionSemantic(ass._scope);
|
|
tiargs.push(edim);
|
|
e = new DotTemplateInstanceExp(loc, ce, td.ident, tiargs);
|
|
}
|
|
else
|
|
{
|
|
/* opDollar exists, but it's not a template.
|
|
* This is acceptable ONLY for single-dimension indexing.
|
|
* Note that it's impossible to have both template & function opDollar,
|
|
* because both take no arguments.
|
|
*/
|
|
auto ae = exp.isArrayExp();
|
|
if (ae && ae.arguments.length != 1)
|
|
{
|
|
error(exp.loc, "`%s` only defines opDollar for one dimension", ad.toChars());
|
|
return setResult(null);
|
|
}
|
|
Declaration d = s.isDeclaration();
|
|
assert(d);
|
|
e = new DotVarExp(loc, ce, d);
|
|
}
|
|
e = e.expressionSemantic(ass._scope);
|
|
if (!e.type)
|
|
error(exp.loc, "`%s` has no value", e.toChars());
|
|
t = e.type.toBasetype();
|
|
if (t && t.ty == Tfunction)
|
|
e = new CallExp(e.loc, e);
|
|
v = new VarDeclaration(loc, null, Id.dollar, new ExpInitializer(Loc.initial, e));
|
|
v.storage_class |= STC.temp | STC.ctfe | STC.rvalue;
|
|
}
|
|
else
|
|
{
|
|
/* For arrays, $ will either be a compile-time constant
|
|
* (in which case its value in set during constant-folding),
|
|
* or a variable (in which case an expression is created in
|
|
* toir.c).
|
|
*/
|
|
|
|
// https://issues.dlang.org/show_bug.cgi?id=16213
|
|
// For static arrays $ is known at compile time,
|
|
// so declare it as a manifest constant.
|
|
auto tsa = ce.type ? ce.type.isTypeSArray() : null;
|
|
if (tsa)
|
|
{
|
|
auto e = new ExpInitializer(loc, tsa.dim);
|
|
v = new VarDeclaration(loc, tsa.dim.type, Id.dollar, e, STC.manifest);
|
|
}
|
|
else
|
|
{
|
|
auto e = new VoidInitializer(Loc.initial);
|
|
e.type = Type.tsize_t;
|
|
v = new VarDeclaration(loc, Type.tsize_t, Id.dollar, e);
|
|
v.storage_class |= STC.temp | STC.ctfe; // it's never a true static variable
|
|
}
|
|
}
|
|
*pvar = v;
|
|
}
|
|
(*pvar).dsymbolSemantic(ass._scope);
|
|
return setResult((*pvar));
|
|
|
|
}
|
|
|
|
override void visit(Import imp)
|
|
{
|
|
//printf("%s.Import.search(ident = '%s', flags = x%x)\n", imp.toChars(), ident.toChars(), flags);
|
|
if (!imp.pkg)
|
|
{
|
|
imp.load(null);
|
|
imp.mod.importAll(null);
|
|
imp.mod.dsymbolSemantic(null);
|
|
}
|
|
// Forward it to the package/module
|
|
return setResult(imp.pkg.search(loc, ident, flags));
|
|
|
|
}
|
|
|
|
override void visit(Nspace ns)
|
|
{
|
|
//printf("%s.Nspace.search('%s')\n", toChars(), ident.toChars());
|
|
if (ns._scope && !ns.symtab)
|
|
dsymbolSemantic(ns, ns._scope);
|
|
|
|
if (!ns.members || !ns.symtab) // opaque or semantic() is not yet called
|
|
{
|
|
if (!(flags & SearchOpt.ignoreErrors))
|
|
.error(loc, "%s `%s` is forward referenced when looking for `%s`", ns.kind, ns.toPrettyChars, ident.toChars());
|
|
return setResult(null);
|
|
}
|
|
|
|
visit(cast(ScopeDsymbol)ns);
|
|
}
|
|
|
|
override void visit(EnumDeclaration em)
|
|
{
|
|
//printf("%s.EnumDeclaration::search('%s')\n", em.toChars(), ident.toChars());
|
|
if (em._scope)
|
|
{
|
|
// Try one last time to resolve this enum
|
|
dsymbolSemantic(em, em._scope);
|
|
}
|
|
|
|
visit(cast(ScopeDsymbol)em);
|
|
}
|
|
|
|
override void visit(Package pkg)
|
|
{
|
|
//printf("%s Package.search('%s', flags = x%x)\n", pkg.toChars(), ident.toChars(), flags);
|
|
flags &= ~cast(int)SearchOpt.localsOnly; // searching an import is always transitive
|
|
if (!pkg.isModule() && pkg.mod)
|
|
{
|
|
// Prefer full package name.
|
|
Dsymbol s = pkg.symtab ? pkg.symtab.lookup(ident) : null;
|
|
if (s)
|
|
return setResult(s);
|
|
//printf("[%s] through pkdmod: %s\n", loc.toChars(), toChars());
|
|
return setResult(pkg.mod.search(loc, ident, flags));
|
|
}
|
|
|
|
visit(cast(ScopeDsymbol)pkg);
|
|
}
|
|
|
|
override void visit(Module m)
|
|
{
|
|
/* Since modules can be circularly referenced,
|
|
* need to stop infinite recursive searches.
|
|
* This is done with the cache.
|
|
*/
|
|
//printf("%s Module.search('%s', flags = x%x) insearch = %d\n", m.toChars(), ident.toChars(), flags, m.insearch);
|
|
if (m.insearch)
|
|
return setResult(null);
|
|
|
|
/* Qualified module searches always search their imports,
|
|
* even if SearchLocalsOnly
|
|
*/
|
|
if (!(flags & SearchOpt.unqualifiedModule))
|
|
flags &= ~(SearchOpt.unqualifiedModule | SearchOpt.localsOnly);
|
|
|
|
if (m.searchCacheIdent == ident && m.searchCacheFlags == flags)
|
|
{
|
|
//printf("%s Module::search('%s', flags = %d) insearch = %d searchCacheSymbol = %s\n",
|
|
// toChars(), ident.toChars(), flags, insearch, searchCacheSymbol ? searchCacheSymbol.toChars() : "null");
|
|
return setResult(m.searchCacheSymbol);
|
|
}
|
|
|
|
const errors = global.errors;
|
|
|
|
m.insearch = true;
|
|
visit(cast(ScopeDsymbol)m);
|
|
Dsymbol s = result;
|
|
m.insearch = false;
|
|
|
|
if (errors == global.errors)
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=10752
|
|
// Can cache the result only when it does not cause
|
|
// access error so the side-effect should be reproduced in later search.
|
|
m.searchCacheIdent = ident;
|
|
m.searchCacheSymbol = s;
|
|
m.searchCacheFlags = flags;
|
|
}
|
|
return setResult(s);
|
|
}
|
|
|
|
override void visit(Declaration decl)
|
|
{
|
|
Dsymbol s = null;
|
|
if (decl.type)
|
|
{
|
|
s = decl.type.toDsymbol(decl._scope);
|
|
if (s)
|
|
s = s.search(loc, ident, flags);
|
|
}
|
|
return setResult(s);
|
|
}
|
|
|
|
override void visit(StructDeclaration sd)
|
|
{
|
|
//printf("%s.StructDeclaration::search('%s', flags = x%x)\n", sd.toChars(), ident.toChars(), flags);
|
|
if (sd._scope && !sd.symtab)
|
|
dsymbolSemantic(sd, sd._scope);
|
|
|
|
if (!sd.members || !sd.symtab) // opaque or semantic() is not yet called
|
|
{
|
|
// .stringof is always defined (but may be hidden by some other symbol)
|
|
if(ident != Id.stringof && !(flags & SearchOpt.ignoreErrors) && sd.semanticRun < PASS.semanticdone)
|
|
.error(loc, "%s `%s` is forward referenced when looking for `%s`", sd.kind, sd.toPrettyChars, ident.toChars());
|
|
return setResult(null);
|
|
}
|
|
|
|
visit(cast(ScopeDsymbol)sd);
|
|
}
|
|
|
|
override void visit(ClassDeclaration cd)
|
|
{
|
|
//printf("%s.ClassDeclaration.search('%s', flags=x%x)\n", cd.toChars(), ident.toChars(), flags);
|
|
//if (_scope) printf("%s baseok = %d\n", toChars(), baseok);
|
|
if (cd._scope && cd.baseok < Baseok.semanticdone)
|
|
{
|
|
if (!cd.inuse)
|
|
{
|
|
// must semantic on base class/interfaces
|
|
cd.inuse = true;
|
|
dsymbolSemantic(cd, null);
|
|
cd.inuse = false;
|
|
}
|
|
}
|
|
|
|
if (!cd.members || !cd.symtab) // opaque or addMember is not yet done
|
|
{
|
|
// .stringof is always defined (but may be hidden by some other symbol)
|
|
if (ident != Id.stringof && !(flags & SearchOpt.ignoreErrors) && cd.semanticRun < PASS.semanticdone)
|
|
cd.classError("%s `%s` is forward referenced when looking for `%s`", ident.toChars());
|
|
//*(char*)0=0;
|
|
return setResult(null);
|
|
}
|
|
|
|
visit(cast(ScopeDsymbol)cd);
|
|
auto s = result;
|
|
|
|
// don't search imports of base classes
|
|
if (flags & SearchOpt.importsOnly)
|
|
return setResult(s);
|
|
|
|
if (s)
|
|
return setResult(s);
|
|
|
|
// Search bases classes in depth-first, left to right order
|
|
foreach (b; (*cd.baseclasses)[])
|
|
{
|
|
if (!b.sym)
|
|
continue;
|
|
|
|
if (!b.sym.symtab)
|
|
{
|
|
cd.classError("%s `%s` base `%s` is forward referenced", b.sym.ident.toChars());
|
|
continue;
|
|
}
|
|
|
|
import dmd.access : symbolIsVisible;
|
|
|
|
s = b.sym.search(loc, ident, flags);
|
|
if (!s)
|
|
continue;
|
|
if (s == cd) // happens if s is nested in this and derives from this
|
|
s = null;
|
|
else if (!(flags & SearchOpt.ignoreVisibility) && !(s.visible().kind == Visibility.Kind.protected_) && !symbolIsVisible(cd, s))
|
|
s = null;
|
|
else
|
|
break;
|
|
}
|
|
|
|
return setResult(s);
|
|
}
|
|
}
|
|
/*************************************
|
|
* Set scope for future semantic analysis so we can
|
|
* deal better with forward references.
|
|
*
|
|
* Params:
|
|
* d = dsymbol for which the scope is set
|
|
* sc = scope that is used to set the value
|
|
*/
|
|
void setScope(Dsymbol d, Scope* sc)
|
|
{
|
|
scope setScopeVisitor = new SetScopeVisitor(sc);
|
|
d.accept(setScopeVisitor);
|
|
}
|
|
|
|
private extern(C++) class SetScopeVisitor : Visitor
|
|
{
|
|
alias visit = typeof(super).visit;
|
|
Scope* sc;
|
|
|
|
this(Scope* sc) @safe
|
|
{
|
|
this.sc = sc;
|
|
}
|
|
|
|
override void visit(Dsymbol d)
|
|
{
|
|
//printf("Dsymbol::setScope() %p %s, %p stc = %llx\n", d, d.toChars(), sc, sc.stc);
|
|
if (!sc.nofree)
|
|
sc.setNoFree(); // may need it even after semantic() finishes
|
|
d._scope = sc;
|
|
if (sc.depdecl)
|
|
d.depdecl = sc.depdecl;
|
|
if (!d.userAttribDecl)
|
|
d.userAttribDecl = sc.userAttribDecl;
|
|
}
|
|
|
|
override void visit(Import i)
|
|
{
|
|
visit(cast(Dsymbol)i);
|
|
if (i.aliasdecls.length)
|
|
{
|
|
if (!i.mod)
|
|
i.importAll(sc);
|
|
|
|
sc = sc.push(i.mod);
|
|
sc.visibility = i.visibility;
|
|
foreach (ad; i.aliasdecls)
|
|
ad.setScope(sc);
|
|
sc = sc.pop();
|
|
}
|
|
}
|
|
|
|
override void visit(Nspace ns)
|
|
{
|
|
visit(cast(Dsymbol)ns);
|
|
if (ns.members)
|
|
{
|
|
assert(sc);
|
|
sc = sc.push(ns);
|
|
sc.linkage = LINK.cpp; // namespaces default to C++ linkage
|
|
sc.parent = ns;
|
|
ns.members.foreachDsymbol(s => s.setScope(sc));
|
|
sc.pop();
|
|
}
|
|
}
|
|
|
|
override void visit(EnumDeclaration ed)
|
|
{
|
|
if (ed.semanticRun > PASS.initial)
|
|
return;
|
|
visit(cast(Dsymbol)ed);
|
|
}
|
|
|
|
override void visit(AggregateDeclaration ad)
|
|
{
|
|
// Might need a scope to resolve forward references. The check for
|
|
// semanticRun prevents unnecessary setting of _scope during deferred
|
|
// setScope phases for aggregates which already finished semantic().
|
|
// See https://issues.dlang.org/show_bug.cgi?id=16607
|
|
if (ad.semanticRun < PASS.semanticdone)
|
|
visit(cast(Dsymbol)ad);
|
|
}
|
|
|
|
override void visit(AttribDeclaration atr)
|
|
{
|
|
Dsymbols* d = atr.include(sc);
|
|
//printf("\tAttribDeclaration::setScope '%s', d = %p\n",toChars(), d);
|
|
if (d)
|
|
{
|
|
Scope* sc2 = atr.newScope(sc);
|
|
d.foreachDsymbol( s => s.setScope(sc2) );
|
|
if (sc2 != sc)
|
|
sc2.pop();
|
|
}
|
|
}
|
|
|
|
override void visit(DeprecatedDeclaration dd)
|
|
{
|
|
//printf("DeprecatedDeclaration::setScope() %p\n", this);
|
|
if (dd.decl)
|
|
visit(cast(Dsymbol)dd); // for forward reference
|
|
visit(cast(AttribDeclaration)dd);
|
|
}
|
|
|
|
override void visit(CPPMangleDeclaration cppmd)
|
|
{
|
|
if (cppmd.decl)
|
|
visit(cast(Dsymbol)cppmd); // for forward reference
|
|
visit(cast(AttribDeclaration)cppmd);
|
|
}
|
|
|
|
override void visit(AnonDeclaration anond)
|
|
{
|
|
if (anond.decl)
|
|
visit(cast(Dsymbol)anond); // for forward reference
|
|
visit(cast(AttribDeclaration)anond);
|
|
}
|
|
|
|
override void visit(ConditionalDeclaration condd)
|
|
{
|
|
condd.include(sc).foreachDsymbol( s => s.setScope(sc) );
|
|
}
|
|
|
|
override void visit(StaticIfDeclaration sid)
|
|
{
|
|
// do not evaluate condition before semantic pass
|
|
// But do set the scope, in case we need it for forward referencing
|
|
visit(cast(Dsymbol)sid); // for forward reference
|
|
}
|
|
|
|
override void visit(StaticForeachDeclaration sfd)
|
|
{
|
|
// do not evaluate condition before semantic pass
|
|
// But do set the scope, in case we need it for forward referencing
|
|
visit(cast(Dsymbol)sfd); // for forward reference
|
|
}
|
|
|
|
override void visit(MixinDeclaration md)
|
|
{
|
|
visit(cast(Dsymbol)md);
|
|
}
|
|
|
|
override void visit(UserAttributeDeclaration uad)
|
|
{
|
|
//printf("UserAttributeDeclaration::setScope() %p\n", this);
|
|
if (uad.decl)
|
|
visit(cast(Dsymbol)uad);
|
|
visit(cast(AttribDeclaration)uad);
|
|
}
|
|
}
|
|
|
|
void importAll(Dsymbol d, Scope* sc)
|
|
{
|
|
scope iav = new ImportAllVisitor(sc);
|
|
d.accept(iav);
|
|
}
|
|
|
|
extern(C++) class ImportAllVisitor : Visitor
|
|
{
|
|
alias visit = typeof(super).visit;
|
|
Scope* sc;
|
|
|
|
this(Scope* sc) @safe
|
|
{
|
|
this.sc = sc;
|
|
}
|
|
|
|
override void visit(Dsymbol d) {}
|
|
|
|
override void visit(Import imp)
|
|
{
|
|
if (imp.mod) return; // Already done
|
|
|
|
/*
|
|
* https://issues.dlang.org/show_bug.cgi?id=15525
|
|
*
|
|
* Loading the import has failed,
|
|
* most likely because of parsing errors.
|
|
* Therefore we cannot trust the resulting AST.
|
|
*/
|
|
if (imp.load(sc))
|
|
{
|
|
// https://issues.dlang.org/show_bug.cgi?id=23873
|
|
// For imports that are not at module or function level,
|
|
// e.g. aggregate level, the import symbol is added to the
|
|
// symbol table and later semantic is performed on it.
|
|
// This leads to semantic analysis on an malformed AST
|
|
// which causes all kinds of segfaults.
|
|
// The fix is to note that the module has errors and avoid
|
|
// semantic analysis on it.
|
|
if(imp.mod)
|
|
imp.mod.errors = true;
|
|
return;
|
|
}
|
|
|
|
if (!imp.mod) return; // Failed
|
|
|
|
if (sc.stc & STC.static_)
|
|
imp.isstatic = true;
|
|
imp.mod.importAll(null);
|
|
imp.mod.checkImportDeprecation(imp.loc, sc);
|
|
if (sc.explicitVisibility)
|
|
imp.visibility = sc.visibility;
|
|
if (!imp.isstatic && !imp.aliasId && !imp.names.length)
|
|
sc.scopesym.importScope(imp.mod, imp.visibility);
|
|
// Enable access to pkgs/mod as soon as posible, because compiler
|
|
// can traverse them before the import gets semantic (Issue: 21501)
|
|
if (!imp.aliasId && !imp.names.length)
|
|
imp.addPackageAccess(sc.scopesym);
|
|
}
|
|
|
|
override void visit(Module m)
|
|
{
|
|
//printf("+Module::importAll(this = %p, '%s'): parent = %p\n", m, m.toChars(), m.parent);
|
|
if (m._scope)
|
|
return; // already done
|
|
if (m.filetype == FileType.ddoc)
|
|
{
|
|
error(m.loc, "%s `%s` is a Ddoc file, cannot import it", m.kind, m.toPrettyChars);
|
|
return;
|
|
}
|
|
|
|
/* Note that modules get their own scope, from scratch.
|
|
* This is so regardless of where in the syntax a module
|
|
* gets imported, it is unaffected by context.
|
|
* Ignore prevsc.
|
|
*/
|
|
Scope* sc = Scope.createGlobal(m, global.errorSink); // create root scope
|
|
|
|
if (m.md && m.md.msg)
|
|
m.md.msg = semanticString(sc, m.md.msg, "deprecation message");
|
|
|
|
// Add import of "object", even for the "object" module.
|
|
// If it isn't there, some compiler rewrites, like
|
|
// classinst == classinst -> .object.opEquals(classinst, classinst)
|
|
// would fail inside object.d.
|
|
if (m.filetype != FileType.c &&
|
|
(m.members.length == 0 ||
|
|
(*m.members)[0].ident != Id.object ||
|
|
(*m.members)[0].isImport() is null))
|
|
{
|
|
auto im = new Import(m.loc, null, Id.object, null, 0);
|
|
m.members.shift(im);
|
|
}
|
|
if (!m.symtab)
|
|
{
|
|
// Add all symbols into module's symbol table
|
|
m.symtab = new DsymbolTable();
|
|
for (size_t i = 0; i < m.members.length; i++)
|
|
{
|
|
Dsymbol s = (*m.members)[i];
|
|
s.addMember(sc, sc.scopesym);
|
|
}
|
|
}
|
|
// anything else should be run after addMember, so version/debug symbols are defined
|
|
/* Set scope for the symbols so that if we forward reference
|
|
* a symbol, it can possibly be resolved on the spot.
|
|
* If this works out well, it can be extended to all modules
|
|
* before any semantic() on any of them.
|
|
*/
|
|
m.setScope(sc); // remember module scope for semantic
|
|
for (size_t i = 0; i < m.members.length; i++)
|
|
{
|
|
Dsymbol s = (*m.members)[i];
|
|
s.setScope(sc);
|
|
}
|
|
for (size_t i = 0; i < m.members.length; i++)
|
|
{
|
|
Dsymbol s = (*m.members)[i];
|
|
s.importAll(sc);
|
|
}
|
|
sc = sc.pop();
|
|
sc.pop(); // 2 pops because Scope.createGlobal() created 2
|
|
}
|
|
|
|
override void visit(AttribDeclaration atb)
|
|
{
|
|
Dsymbols* d = atb.include(sc);
|
|
//printf("\tAttribDeclaration::importAll '%s', d = %p\n", toChars(), d);
|
|
if (d)
|
|
{
|
|
Scope* sc2 = atb.newScope(sc);
|
|
d.foreachDsymbol( s => s.importAll(sc2) );
|
|
if (sc2 != sc)
|
|
sc2.pop();
|
|
}
|
|
}
|
|
|
|
// do not evaluate condition before semantic pass
|
|
override void visit(StaticIfDeclaration _) {}
|
|
// do not evaluate aggregate before semantic pass
|
|
override void visit(StaticForeachDeclaration _) {}
|
|
}
|
|
|
|
/*******************************
|
|
* Load module.
|
|
* Returns:
|
|
* true for errors, false for success
|
|
*/
|
|
extern (D) bool load(Import imp, Scope* sc)
|
|
{
|
|
// See if existing module
|
|
const errors = global.errors;
|
|
DsymbolTable dst = Package.resolve(imp.packages, null, &imp.pkg);
|
|
version (none)
|
|
{
|
|
if (pkg && pkg.isModule())
|
|
{
|
|
.error(loc, "can only import from a module, not from a member of module `%s`. Did you mean `import %s : %s`?", pkg.toChars(), pkg.toPrettyChars(), id.toChars());
|
|
mod = pkg.isModule(); // Error recovery - treat as import of that module
|
|
return true;
|
|
}
|
|
}
|
|
Dsymbol s = dst.lookup(imp.id);
|
|
if (s)
|
|
{
|
|
if (s.isModule())
|
|
imp.mod = cast(Module)s;
|
|
else
|
|
{
|
|
if (s.isAliasDeclaration())
|
|
{
|
|
.error(imp.loc, "%s `%s` conflicts with `%s`", s.kind(), s.toPrettyChars(), imp.id.toChars());
|
|
}
|
|
else if (Package p = s.isPackage())
|
|
{
|
|
if (p.isPkgMod == PKG.unknown)
|
|
{
|
|
const preverrors = global.errors;
|
|
imp.mod = Module.load(imp.loc, imp.packages, imp.id);
|
|
if (!imp.mod)
|
|
p.isPkgMod = PKG.package_;
|
|
else
|
|
{
|
|
// imp.mod is a package.d, or a normal module which conflicts with the package name.
|
|
if (imp.mod.isPackageFile)
|
|
imp.mod.tag = p.tag; // reuse the same package tag
|
|
else
|
|
{
|
|
// show error if Module.load does not
|
|
if (preverrors == global.errors)
|
|
.error(imp.loc, "%s `%s` from file %s conflicts with %s `%s`", imp.mod.kind(), imp.mod.toPrettyChars(), imp.mod.srcfile.toChars, p.kind(), p.toPrettyChars());
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
imp.mod = p.isPackageMod();
|
|
}
|
|
if (!imp.mod)
|
|
{
|
|
.error(imp.loc, "can only import from a module, not from package `%s.%s`", p.toPrettyChars(), imp.id.toChars());
|
|
}
|
|
}
|
|
else if (imp.pkg)
|
|
{
|
|
.error(imp.loc, "can only import from a module, not from package `%s.%s`", imp.pkg.toPrettyChars(), imp.id.toChars());
|
|
}
|
|
else
|
|
{
|
|
.error(imp.loc, "can only import from a module, not from package `%s`", imp.id.toChars());
|
|
}
|
|
}
|
|
}
|
|
if (!imp.mod)
|
|
{
|
|
// Load module
|
|
imp.mod = Module.load(imp.loc, imp.packages, imp.id);
|
|
if (imp.mod)
|
|
{
|
|
// imp.id may be different from mod.ident, if so then insert alias
|
|
dst.insert(imp.id, imp.mod);
|
|
}
|
|
}
|
|
if (imp.mod && !imp.mod.importedFrom)
|
|
imp.mod.importedFrom = sc ? sc._module.importedFrom : Module.rootModule;
|
|
if (!imp.pkg)
|
|
{
|
|
if (imp.mod && imp.mod.isPackageFile)
|
|
{
|
|
// one level depth package.d file (import pkg; ./pkg/package.d)
|
|
// it's necessary to use the wrapping Package already created
|
|
imp.pkg = imp.mod.pkg;
|
|
}
|
|
else
|
|
imp.pkg = imp.mod;
|
|
}
|
|
return global.errors != errors;
|
|
}
|
|
|
|
void setFieldOffset(Dsymbol d, AggregateDeclaration ad, FieldState* fieldState, bool isunion)
|
|
{
|
|
scope v = new SetFieldOffsetVisitor(ad, fieldState, isunion);
|
|
d.accept(v);
|
|
}
|
|
|
|
private extern(C++) class SetFieldOffsetVisitor : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
AggregateDeclaration ad;
|
|
FieldState* fieldState;
|
|
bool isunion;
|
|
|
|
this(AggregateDeclaration ad, FieldState* fieldState, bool isunion) @safe
|
|
{
|
|
this.ad = ad;
|
|
this.fieldState = fieldState;
|
|
this.isunion = isunion;
|
|
}
|
|
|
|
override void visit(Dsymbol d) {}
|
|
|
|
override void visit(Nspace ns)
|
|
{
|
|
//printf("Nspace::setFieldOffset() %s\n", toChars());
|
|
if (ns._scope) // if fwd reference
|
|
dsymbolSemantic(ns, null); // try to resolve it
|
|
ns.members.foreachDsymbol( s => s.setFieldOffset(ad, fieldState, isunion) );
|
|
}
|
|
|
|
override void visit(VarDeclaration vd)
|
|
{
|
|
//printf("VarDeclaration::setFieldOffset(ad = %s) %s\n", ad.toChars(), vd.toChars());
|
|
|
|
if (vd.aliasTuple)
|
|
{
|
|
// If this variable was really a tuple, set the offsets for the tuple fields
|
|
vd.aliasTuple.foreachVar((s) { s.setFieldOffset(ad, fieldState, isunion); });
|
|
return;
|
|
}
|
|
|
|
if (!vd.isField())
|
|
return;
|
|
assert(!(vd.storage_class & (STC.static_ | STC.extern_ | STC.parameter)));
|
|
|
|
//printf("+VarDeclaration::setFieldOffset(ad = %s) %s\n", ad.toChars(), toChars());
|
|
|
|
/* Fields that are tuples appear both as part of TupleDeclarations and
|
|
* as members. That means ignore them if they are already a field.
|
|
*/
|
|
if (vd.offset)
|
|
{
|
|
// already a field
|
|
fieldState.offset = ad.structsize; // https://issues.dlang.org/show_bug.cgi?id=13613
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < ad.fields.length; i++)
|
|
{
|
|
if (ad.fields[i] == vd)
|
|
{
|
|
// already a field
|
|
fieldState.offset = ad.structsize; // https://issues.dlang.org/show_bug.cgi?id=13613
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for forward referenced types which will fail the size() call
|
|
Type t = vd.type.toBasetype();
|
|
if (vd.storage_class & STC.ref_)
|
|
{
|
|
// References are the size of a pointer
|
|
t = Type.tvoidptr;
|
|
}
|
|
Type tv = t.baseElemOf();
|
|
if (tv.ty == Tstruct)
|
|
{
|
|
auto ts = cast(TypeStruct)tv;
|
|
assert(ts.sym != ad); // already checked in ad.determineFields()
|
|
if (!ts.sym.determineSize(vd.loc))
|
|
{
|
|
vd.type = Type.terror;
|
|
vd.errors = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// List in ad.fields. Even if the type is error, it's necessary to avoid
|
|
// pointless error diagnostic "more initializers than fields" on struct literal.
|
|
ad.fields.push(vd);
|
|
|
|
if (t.ty == Terror)
|
|
return;
|
|
|
|
/* If coming after a bit field in progress,
|
|
* advance past the field
|
|
*/
|
|
fieldState.inFlight = false;
|
|
|
|
const sz = t.size(vd.loc);
|
|
assert(sz != SIZE_INVALID && sz < uint.max);
|
|
uint memsize = cast(uint)sz; // size of member
|
|
uint memalignsize = target.fieldalign(t); // size of member for alignment purposes
|
|
vd.offset = placeField(vd.loc,
|
|
fieldState.offset,
|
|
memsize, memalignsize, vd.alignment,
|
|
ad.structsize, ad.alignsize,
|
|
isunion);
|
|
|
|
//printf("\t%s: memalignsize = %d\n", toChars(), memalignsize);
|
|
//printf(" addField '%s' to '%s' at offset %d, size = %d\n", toChars(), ad.toChars(), offset, memsize);
|
|
}
|
|
|
|
override void visit(BitFieldDeclaration bfd)
|
|
{
|
|
enum log = false;
|
|
static if (log)
|
|
{
|
|
printf("BitFieldDeclaration::setFieldOffset(ad: %s, field: %s)\n", ad.toChars(), bfd.toChars());
|
|
void print(const FieldState* fieldState)
|
|
{
|
|
fieldState.print();
|
|
printf(" fieldWidth = %d bits\n", bfd.fieldWidth);
|
|
}
|
|
print(fieldState);
|
|
}
|
|
|
|
Type t = bfd.type.toBasetype();
|
|
const bool anon = bfd.isAnonymous();
|
|
|
|
// List in ad.fields. Even if the type is error, it's necessary to avoid
|
|
// pointless error diagnostic "more initializers than fields" on struct literal.
|
|
if (!anon)
|
|
ad.fields.push(bfd);
|
|
|
|
if (t.ty == Terror)
|
|
return;
|
|
|
|
const sz = t.size(bfd.loc);
|
|
assert(sz != SIZE_INVALID && sz < uint.max);
|
|
uint memsize = cast(uint)sz; // size of member
|
|
uint memalignsize = target.fieldalign(t); // size of member for alignment purposes
|
|
if (log) printf(" memsize: %u memalignsize: %u\n", memsize, memalignsize);
|
|
|
|
if (bfd.fieldWidth == 0 && !anon)
|
|
error(bfd.loc, "named bit fields cannot have 0 width");
|
|
if (bfd.fieldWidth > memsize * 8)
|
|
error(bfd.loc, "bit field width %d is larger than type", bfd.fieldWidth);
|
|
|
|
const style = target.c.bitFieldStyle;
|
|
if (style != TargetC.BitFieldStyle.MS && style != TargetC.BitFieldStyle.Gcc_Clang)
|
|
assert(0, "unsupported bit-field style");
|
|
|
|
const isMicrosoftStyle = style == TargetC.BitFieldStyle.MS;
|
|
const contributesToAggregateAlignment = target.c.contributesToAggregateAlignment(bfd);
|
|
|
|
void startNewField()
|
|
{
|
|
if (log) printf("startNewField()\n");
|
|
uint alignsize;
|
|
if (isMicrosoftStyle)
|
|
alignsize = memsize; // not memalignsize
|
|
else
|
|
{
|
|
if (bfd.fieldWidth > 32)
|
|
alignsize = memalignsize;
|
|
else if (bfd.fieldWidth > 16)
|
|
alignsize = 4;
|
|
else if (bfd.fieldWidth > 8)
|
|
alignsize = 2;
|
|
else
|
|
alignsize = 1;
|
|
}
|
|
|
|
uint dummy;
|
|
bfd.offset = placeField(bfd.loc,
|
|
fieldState.offset,
|
|
memsize, alignsize, bfd.alignment,
|
|
ad.structsize,
|
|
contributesToAggregateAlignment ? ad.alignsize : dummy,
|
|
isunion);
|
|
|
|
fieldState.inFlight = true;
|
|
fieldState.fieldOffset = bfd.offset;
|
|
fieldState.bitOffset = 0;
|
|
fieldState.fieldSize = memsize;
|
|
}
|
|
|
|
if (ad.alignsize == 0)
|
|
ad.alignsize = 1;
|
|
if (!isMicrosoftStyle && contributesToAggregateAlignment && ad.alignsize < memalignsize)
|
|
ad.alignsize = memalignsize;
|
|
|
|
if (bfd.fieldWidth == 0)
|
|
{
|
|
if (!isMicrosoftStyle && !isunion)
|
|
{
|
|
// Use type of zero width field to align to next field
|
|
fieldState.offset = (fieldState.offset + memalignsize - 1) & ~(memalignsize - 1);
|
|
ad.structsize = fieldState.offset;
|
|
}
|
|
else if (isMicrosoftStyle && fieldState.inFlight && !isunion)
|
|
{
|
|
// documentation says align to next int
|
|
//const alsz = cast(uint)Type.tint32.size();
|
|
const alsz = memsize; // but it really does this
|
|
fieldState.offset = (fieldState.offset + alsz - 1) & ~(alsz - 1);
|
|
ad.structsize = fieldState.offset;
|
|
}
|
|
|
|
fieldState.inFlight = false;
|
|
return;
|
|
}
|
|
|
|
if (!fieldState.inFlight)
|
|
{
|
|
//printf("not in flight\n");
|
|
startNewField();
|
|
}
|
|
else if (!isMicrosoftStyle)
|
|
{
|
|
// If the bit-field spans more units of alignment than its type
|
|
// and is at the alignment boundary, start a new field at the
|
|
// next alignment boundary. This affects when offsetof reports
|
|
// a higher number and bitoffsetof starts at zero again.
|
|
if (fieldState.bitOffset % (memalignsize * 8) == 0 &&
|
|
fieldState.bitOffset + bfd.fieldWidth > memsize * 8)
|
|
{
|
|
if (log) printf("more units of alignment than its type\n");
|
|
startNewField(); // the bit field is full
|
|
}
|
|
else
|
|
{
|
|
// if alignment boundary is crossed
|
|
uint start = (fieldState.fieldOffset * 8 + fieldState.bitOffset) % (memalignsize * 8);
|
|
uint end = start + bfd.fieldWidth;
|
|
//printf("%s start: %d end: %d memalignsize: %d\n", ad.toChars(), start, end, memalignsize);
|
|
if (start / (memsize * 8) != (end - 1) / (memsize * 8))
|
|
{
|
|
if (log) printf("alignment is crossed\n");
|
|
startNewField();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (memsize != fieldState.fieldSize ||
|
|
fieldState.bitOffset + bfd.fieldWidth > fieldState.fieldSize * 8)
|
|
{
|
|
//printf("new field\n");
|
|
startNewField();
|
|
}
|
|
}
|
|
|
|
bfd.offset = fieldState.fieldOffset;
|
|
bfd.bitOffset = fieldState.bitOffset;
|
|
|
|
const pastField = bfd.bitOffset + bfd.fieldWidth;
|
|
if (isMicrosoftStyle)
|
|
fieldState.fieldSize = memsize;
|
|
else
|
|
{
|
|
const size = (pastField + 7) / 8;
|
|
fieldState.fieldSize = size;
|
|
//printf(" offset: %d, size: %d\n", offset, size);
|
|
if (isunion)
|
|
{
|
|
const newstructsize = bfd.offset + size;
|
|
if (newstructsize > ad.structsize)
|
|
ad.structsize = newstructsize;
|
|
}
|
|
else
|
|
ad.structsize = bfd.offset + size;
|
|
}
|
|
//printf("at end: ad.structsize = %d\n", cast(int)ad.structsize);
|
|
//print(fieldState);
|
|
|
|
if (!isunion)
|
|
{
|
|
fieldState.offset = bfd.offset + fieldState.fieldSize;
|
|
fieldState.bitOffset = pastField;
|
|
}
|
|
|
|
//printf("\t%s: offset = %d bitOffset = %d fieldWidth = %d memsize = %d\n", toChars(), offset, bitOffset, fieldWidth, memsize);
|
|
//printf("\t%s: memalignsize = %d\n", toChars(), memalignsize);
|
|
//printf(" addField '%s' to '%s' at offset %d, size = %d\n", toChars(), ad.toChars(), offset, memsize);
|
|
}
|
|
|
|
override void visit(TemplateMixin tm)
|
|
{
|
|
//printf("TemplateMixin.setFieldOffset() %s\n", tm.toChars());
|
|
if (tm._scope) // if fwd reference
|
|
dsymbolSemantic(tm, null); // try to resolve it
|
|
|
|
tm.members.foreachDsymbol( (s) { s.setFieldOffset(ad, fieldState, isunion); } );
|
|
}
|
|
|
|
override void visit(AttribDeclaration atd)
|
|
{
|
|
atd.include(null).foreachDsymbol( s => s.setFieldOffset(ad, fieldState, isunion) );
|
|
}
|
|
|
|
override void visit(AnonDeclaration anond)
|
|
{
|
|
//printf("\tAnonDeclaration::setFieldOffset %s %p\n", isunion ? "union" : "struct", anond);
|
|
if (anond.decl)
|
|
{
|
|
/* This works by treating an AnonDeclaration as an aggregate 'member',
|
|
* so in order to place that member we need to compute the member's
|
|
* size and alignment.
|
|
*/
|
|
size_t fieldstart = ad.fields.length;
|
|
|
|
/* Hackishly hijack ad's structsize and alignsize fields
|
|
* for use in our fake anon aggregate member.
|
|
*/
|
|
uint savestructsize = ad.structsize;
|
|
uint savealignsize = ad.alignsize;
|
|
ad.structsize = 0;
|
|
ad.alignsize = 0;
|
|
|
|
FieldState fs;
|
|
anond.decl.foreachDsymbol( (s)
|
|
{
|
|
s.setFieldOffset(ad, &fs, anond.isunion);
|
|
if (anond.isunion)
|
|
fs.offset = 0;
|
|
});
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=13613
|
|
* If the fields in this.members had been already
|
|
* added in ad.fields, just update *poffset for the subsequent
|
|
* field offset calculation.
|
|
*/
|
|
if (fieldstart == ad.fields.length)
|
|
{
|
|
ad.structsize = savestructsize;
|
|
ad.alignsize = savealignsize;
|
|
fieldState.offset = ad.structsize;
|
|
return;
|
|
}
|
|
|
|
anond.anonstructsize = ad.structsize;
|
|
anond.anonalignsize = ad.alignsize;
|
|
ad.structsize = savestructsize;
|
|
ad.alignsize = savealignsize;
|
|
|
|
// 0 sized structs are set to 1 byte
|
|
if (anond.anonstructsize == 0)
|
|
{
|
|
anond.anonstructsize = 1;
|
|
anond.anonalignsize = 1;
|
|
}
|
|
|
|
assert(anond._scope);
|
|
auto alignment = anond._scope.alignment();
|
|
|
|
/* Given the anon 'member's size and alignment,
|
|
* go ahead and place it.
|
|
*/
|
|
anond.anonoffset = placeField(anond.loc,
|
|
fieldState.offset,
|
|
anond.anonstructsize, anond.anonalignsize, alignment,
|
|
ad.structsize, ad.alignsize,
|
|
isunion);
|
|
|
|
// Add to the anon fields the base offset of this anonymous aggregate
|
|
//printf("anon fields, anonoffset = %d\n", anonoffset);
|
|
foreach (const i; fieldstart .. ad.fields.length)
|
|
{
|
|
VarDeclaration v = ad.fields[i];
|
|
//printf("\t[%d] %s %d\n", i, v.toChars(), v.offset);
|
|
v.offset += anond.anonoffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extern(D) Scope* newScope(Dsymbol d, Scope* sc)
|
|
{
|
|
scope nsv = new NewScopeVisitor(sc);
|
|
d.accept(nsv);
|
|
return nsv.sc;
|
|
}
|
|
|
|
private extern(C++) class NewScopeVisitor : Visitor
|
|
{
|
|
alias visit = typeof(super).visit;
|
|
Scope* sc;
|
|
this(Scope* sc)
|
|
{
|
|
this.sc = sc;
|
|
}
|
|
|
|
/****************************************
|
|
* A hook point to supply scope for members.
|
|
* addMember, setScope, importAll, semantic, semantic2 and semantic3 will use this.
|
|
*/
|
|
override void visit(AttribDeclaration dc){}
|
|
|
|
override void visit(StorageClassDeclaration swt)
|
|
{
|
|
STC scstc = sc.stc;
|
|
/* These sets of storage classes are mutually exclusive,
|
|
* so choose the innermost or most recent one.
|
|
*/
|
|
if (swt.stc & (STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.manifest))
|
|
scstc &= ~(STC.auto_ | STC.scope_ | STC.static_ | STC.extern_ | STC.manifest);
|
|
if (swt.stc & (STC.auto_ | STC.scope_ | STC.static_ | STC.manifest | STC.gshared))
|
|
scstc &= ~(STC.auto_ | STC.scope_ | STC.static_ | STC.manifest | STC.gshared);
|
|
if (swt.stc & (STC.const_ | STC.immutable_ | STC.manifest))
|
|
scstc &= ~(STC.const_ | STC.immutable_ | STC.manifest);
|
|
if (swt.stc & (STC.gshared | STC.shared_))
|
|
scstc &= ~(STC.gshared | STC.shared_);
|
|
if (swt.stc & (STC.safe | STC.trusted | STC.system))
|
|
scstc &= ~(STC.safe | STC.trusted | STC.system);
|
|
scstc |= swt.stc;
|
|
//printf("scstc = x%llx\n", scstc);
|
|
sc = swt.createNewScope(sc, scstc, sc.linkage, sc.cppmangle,
|
|
sc.visibility, sc.explicitVisibility, sc.aligndecl, sc.inlining);
|
|
}
|
|
|
|
/**
|
|
* Provides a new scope with `STC.deprecated_` and `Scope.depdecl` set
|
|
*
|
|
* Calls `StorageClassDeclaration.newScope` (as it must be called or copied
|
|
* in any function overriding `newScope`), then set the `Scope`'s depdecl.
|
|
*
|
|
* Returns:
|
|
* Always a new scope, to use for this `DeprecatedDeclaration`'s members.
|
|
*/
|
|
override void visit(DeprecatedDeclaration dpd)
|
|
{
|
|
auto oldsc = sc;
|
|
visit((cast(StorageClassDeclaration)dpd));
|
|
auto scx = sc;
|
|
sc = oldsc;
|
|
// The enclosing scope is deprecated as well
|
|
if (scx == sc)
|
|
scx = sc.push();
|
|
scx.depdecl = dpd;
|
|
sc = scx;
|
|
}
|
|
|
|
override void visit(LinkDeclaration lid)
|
|
{
|
|
sc= lid.createNewScope(sc, sc.stc, lid.linkage, sc.cppmangle, sc.visibility, sc.explicitVisibility,
|
|
sc.aligndecl, sc.inlining);
|
|
}
|
|
|
|
override void visit(CPPMangleDeclaration cpmd)
|
|
{
|
|
sc = cpmd.createNewScope(sc, sc.stc, LINK.cpp, cpmd.cppmangle, sc.visibility, sc.explicitVisibility,
|
|
sc.aligndecl, sc.inlining);
|
|
}
|
|
|
|
/**
|
|
* Returns:
|
|
* A copy of the parent scope, with `this` as `namespace` and C++ linkage
|
|
*///override Scope* visit(Scope* sc)
|
|
override void visit(CPPNamespaceDeclaration scd)
|
|
{
|
|
auto scx = sc.copy();
|
|
scx.linkage = LINK.cpp;
|
|
scx.namespace = scd;
|
|
sc = scx;
|
|
}
|
|
|
|
override void visit(VisibilityDeclaration atbd)
|
|
{
|
|
if (atbd.pkg_identifiers)
|
|
dsymbolSemantic(atbd, sc);
|
|
|
|
sc = atbd.createNewScope(sc, sc.stc, sc.linkage, sc.cppmangle, atbd.visibility, 1, sc.aligndecl, sc.inlining);
|
|
}
|
|
|
|
override void visit(AlignDeclaration visd)
|
|
{
|
|
sc = visd.createNewScope(sc, sc.stc, sc.linkage, sc.cppmangle, sc.visibility,
|
|
sc.explicitVisibility, visd, sc.inlining);
|
|
}
|
|
|
|
override void visit(PragmaDeclaration prd)
|
|
{
|
|
if (prd.ident == Id.Pinline)
|
|
{
|
|
// We keep track of this pragma inside scopes,
|
|
// then it's evaluated on demand in function semantic
|
|
sc = prd.createNewScope(sc, sc.stc, sc.linkage, sc.cppmangle, sc.visibility, sc.explicitVisibility, sc.aligndecl, prd); // @suppress(dscanner.style.long_line)
|
|
}
|
|
}
|
|
|
|
/**************************************
|
|
* Use the ForwardingScopeDsymbol as the parent symbol for members.
|
|
*/
|
|
override void visit(ForwardingAttribDeclaration fad)
|
|
{
|
|
sc = sc.push(fad.sym);
|
|
}
|
|
|
|
override void visit(UserAttributeDeclaration uac)
|
|
{
|
|
Scope* sc2 = sc;
|
|
if (uac.atts && uac.atts.length)
|
|
{
|
|
// create new one for changes
|
|
sc2 = sc.copy();
|
|
sc2.userAttribDecl = uac;
|
|
}
|
|
sc = sc2;
|
|
}
|
|
}
|
|
|
|
|
|
Dsymbols* include(Dsymbol d, Scope* sc)
|
|
{
|
|
scope icv = new ConditionIncludeVisitor(sc);
|
|
d.accept(icv);
|
|
return icv.symbols;
|
|
}
|
|
|
|
extern(C++) class ConditionIncludeVisitor : Visitor
|
|
{
|
|
alias visit = typeof(super).visit;
|
|
Scope* sc;
|
|
Dsymbols* symbols;
|
|
this(Scope* sc)
|
|
{
|
|
this.sc = sc;
|
|
}
|
|
|
|
override void visit(AttribDeclaration ad)
|
|
{
|
|
if (ad.errors)
|
|
{
|
|
symbols = null;
|
|
return;
|
|
}
|
|
symbols = ad.decl;
|
|
return;
|
|
}
|
|
|
|
// Decide if 'then' or 'else' code should be included
|
|
override void visit(ConditionalDeclaration cdc)
|
|
{
|
|
//printf("ConditionalDeclaration::include(sc = %p) scope = %p\n", sc, _scope);
|
|
|
|
if (cdc.errors)
|
|
{
|
|
symbols = null;
|
|
return;
|
|
}
|
|
assert(cdc.condition);
|
|
symbols = dmd.expressionsem.include(cdc.condition, cdc._scope ? cdc._scope : sc) ? cdc.decl : cdc.elsedecl;
|
|
}
|
|
|
|
override void visit(StaticIfDeclaration sif)
|
|
{
|
|
/****************************************
|
|
* Different from other AttribDeclaration subclasses, include() call requires
|
|
* the completion of addMember and setScope phases.
|
|
*/
|
|
//printf("StaticIfDeclaration::include(sc = %p) scope = %p\n", sc, _scope);
|
|
if (sif.errors || sif.onStack)
|
|
{
|
|
symbols = null;
|
|
return;
|
|
}
|
|
sif.onStack = true;
|
|
scope(exit) sif.onStack = false;
|
|
|
|
if (sc && sif.condition.inc == Include.notComputed)
|
|
{
|
|
assert(sif.scopesym); // addMember is already done
|
|
assert(sif._scope); // setScope is already done
|
|
|
|
Scope* saved_scope = sc;
|
|
sc = sif._scope;
|
|
visit(cast(ConditionalDeclaration) sif);
|
|
Dsymbols* d = symbols;
|
|
sc = saved_scope;
|
|
|
|
if (d && !sif.addisdone)
|
|
{
|
|
// Add members lazily.
|
|
d.foreachDsymbol( s => s.addMember(sif._scope, sif.scopesym) );
|
|
|
|
// Set the member scopes lazily.
|
|
d.foreachDsymbol( s => s.setScope(sif._scope) );
|
|
|
|
sif.addisdone = true;
|
|
}
|
|
symbols = d;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
visit(cast(ConditionalDeclaration)sif);
|
|
}
|
|
}
|
|
|
|
override void visit(StaticForeachDeclaration sfd)
|
|
{
|
|
if (sfd.errors || sfd.onStack)
|
|
{
|
|
symbols = null;
|
|
return;
|
|
}
|
|
if (sfd.cached)
|
|
{
|
|
assert(!sfd.onStack);
|
|
symbols = sfd.cache;
|
|
return;
|
|
}
|
|
sfd.onStack = true;
|
|
scope(exit) sfd.onStack = false;
|
|
|
|
if (sfd._scope)
|
|
{
|
|
sfd.sfe.prepare(sfd._scope); // lower static foreach aggregate
|
|
}
|
|
if (!sfd.sfe.ready())
|
|
{
|
|
symbols = null;// TODO: ok?
|
|
return;
|
|
}
|
|
|
|
// expand static foreach
|
|
import dmd.statementsem: makeTupleForeach;
|
|
Dsymbols* d = makeTupleForeach(sfd._scope, true, true, sfd.sfe.aggrfe, sfd.decl, sfd.sfe.needExpansion).decl;
|
|
if (d) // process generated declarations
|
|
{
|
|
// Add members lazily.
|
|
d.foreachDsymbol( s => s.addMember(sfd._scope, sfd.scopesym) );
|
|
|
|
// Set the member scopes lazily.
|
|
d.foreachDsymbol( s => s.setScope(sfd._scope) );
|
|
}
|
|
sfd.cached = true;
|
|
sfd.cache = d;
|
|
symbols = d;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called from a symbol's semantic to check if `gnuAbiTag` UDA
|
|
* can be applied to them
|
|
*
|
|
* Directly emits an error if the UDA doesn't work with this symbol
|
|
*
|
|
* Params:
|
|
* sym = symbol to check for `gnuAbiTag`
|
|
* linkage = Linkage of the symbol (Declaration.link or sc.link)
|
|
*/
|
|
void checkGNUABITag(Dsymbol sym, LINK linkage)
|
|
{
|
|
if (global.params.cplusplus < CppStdRevision.cpp11)
|
|
return;
|
|
|
|
foreachUdaNoSemantic(sym, (exp) {
|
|
if (!isGNUABITag(exp))
|
|
return 0; // continue
|
|
if (sym.isCPPNamespaceDeclaration() || sym.isNspace())
|
|
{
|
|
.error(exp.loc, "`@%s` cannot be applied to namespaces", Id.udaGNUAbiTag.toChars());
|
|
sym.errors = true;
|
|
}
|
|
else if (linkage != LINK.cpp)
|
|
{
|
|
.error(exp.loc, "`@%s` can only apply to C++ symbols", Id.udaGNUAbiTag.toChars());
|
|
sym.errors = true;
|
|
}
|
|
// Only one `@gnuAbiTag` is allowed by semantic2
|
|
return 1; // break
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Check if the provided expression references `core.attribute.gnuAbiTag`
|
|
*
|
|
* This should be called after semantic has been run on the expression.
|
|
* Semantic on UDA happens in semantic2 (see `dmd.semantic2`).
|
|
*
|
|
* Params:
|
|
* e = Expression to check (usually from `UserAttributeDeclaration.atts`)
|
|
*
|
|
* Returns:
|
|
* `true` if the expression references the compiler-recognized `gnuAbiTag`
|
|
*/
|
|
bool isGNUABITag(Expression e)
|
|
{
|
|
if (global.params.cplusplus < CppStdRevision.cpp11)
|
|
return false;
|
|
|
|
auto ts = e.type ? e.type.isTypeStruct() : null;
|
|
if (!ts)
|
|
return false;
|
|
if (ts.sym.ident != Id.udaGNUAbiTag || !ts.sym.parent)
|
|
return false;
|
|
// Can only be defined in druntime
|
|
Module m = ts.sym.parent.isModule();
|
|
if (!m || !m.isCoreModule(Id.attribute))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
/******************************************
|
|
* If a variable has a scope destructor call, return call for it.
|
|
* Otherwise, return NULL.
|
|
*/
|
|
private Expression callScopeDtor(VarDeclaration vd, Scope* sc)
|
|
{
|
|
//printf("VarDeclaration::callScopeDtor() %s\n", toChars());
|
|
|
|
// Destruction of STC.field's is handled by buildDtor()
|
|
if (vd.storage_class & (STC.nodtor | STC.ref_ | STC.out_ | STC.field))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (vd.iscatchvar)
|
|
return null; // destructor is built by `void semantic(Catch c, Scope* sc)`, not here
|
|
|
|
Expression e = null;
|
|
// Destructors for structs and arrays of structs
|
|
Type tv = vd.type.baseElemOf();
|
|
if (tv.ty == Tstruct)
|
|
{
|
|
StructDeclaration sd = (cast(TypeStruct)tv).sym;
|
|
if (!sd.dtor || sd.errors)
|
|
return null;
|
|
|
|
const sz = vd.type.size();
|
|
assert(sz != SIZE_INVALID);
|
|
if (!sz)
|
|
return null;
|
|
|
|
if (vd.type.toBasetype().ty == Tstruct)
|
|
{
|
|
// v.__xdtor()
|
|
e = new VarExp(vd.loc, vd);
|
|
|
|
/* This is a hack so we can call destructors on const/immutable objects.
|
|
* Need to add things like "const ~this()" and "immutable ~this()" to
|
|
* fix properly.
|
|
*/
|
|
e.type = e.type.mutableOf();
|
|
|
|
// Enable calling destructors on shared objects.
|
|
// The destructor is always a single, non-overloaded function,
|
|
// and must serve both shared and non-shared objects.
|
|
e.type = e.type.unSharedOf;
|
|
|
|
e = new DotVarExp(vd.loc, e, sd.dtor, false);
|
|
e = new CallExp(vd.loc, e);
|
|
}
|
|
else
|
|
{
|
|
// __ArrayDtor(v[0 .. n])
|
|
e = new VarExp(vd.loc, vd);
|
|
|
|
const sdsz = sd.type.size();
|
|
assert(sdsz != SIZE_INVALID && sdsz != 0);
|
|
const n = sz / sdsz;
|
|
SliceExp se = new SliceExp(vd.loc, e, new IntegerExp(vd.loc, 0, Type.tsize_t),
|
|
new IntegerExp(vd.loc, n, Type.tsize_t));
|
|
|
|
// Prevent redundant bounds check
|
|
se.upperIsInBounds = true;
|
|
se.lowerIsLessThanUpper = true;
|
|
|
|
// This is a hack so we can call destructors on const/immutable objects.
|
|
se.type = sd.type.arrayOf();
|
|
|
|
e = new CallExp(vd.loc, new IdentifierExp(vd.loc, Id.__ArrayDtor), se);
|
|
}
|
|
return e;
|
|
}
|
|
// Destructors for classes
|
|
if (!(vd.storage_class & (STC.auto_ | STC.scope_) && !(vd.storage_class & STC.parameter)))
|
|
return null;
|
|
|
|
for (ClassDeclaration cd = vd.type.isClassHandle(); cd; cd = cd.baseClass)
|
|
{
|
|
/* We can do better if there's a way with onstack
|
|
* classes to determine if there's no way the monitor
|
|
* could be set.
|
|
*/
|
|
//if (cd.isInterfaceDeclaration())
|
|
// error("interface `%s` cannot be scope", cd.toChars());
|
|
|
|
if (!vd.onstack) // if any destructors
|
|
continue;
|
|
// delete'ing C++ classes crashes (and delete is deprecated anyway)
|
|
if (cd.classKind == ClassKind.cpp)
|
|
{
|
|
// Don't call non-existant dtor
|
|
if (!cd.dtor)
|
|
break;
|
|
|
|
e = new VarExp(vd.loc, vd);
|
|
e.type = e.type.mutableOf().unSharedOf(); // Hack for mutable ctor on immutable instances
|
|
e = new DotVarExp(vd.loc, e, cd.dtor, false);
|
|
e = new CallExp(vd.loc, e);
|
|
break;
|
|
}
|
|
|
|
// delete this;
|
|
Expression ec;
|
|
ec = new VarExp(vd.loc, vd);
|
|
e = new DeleteExp(vd.loc, ec, true);
|
|
break;
|
|
}
|
|
return e;
|
|
}
|
|
|
|
/***************************************
|
|
* Collect all instance fields, then determine instance size.
|
|
* Returns:
|
|
* false if failed to determine the size.
|
|
*/
|
|
bool determineSize(AggregateDeclaration ad, Loc loc)
|
|
{
|
|
//printf("AggregateDeclaration::determineSize() %s, sizeok = %d\n", toChars(), sizeok);
|
|
|
|
// The previous instance size finalizing had:
|
|
if (ad.type.ty == Terror || ad.errors)
|
|
return false; // failed already
|
|
if (ad.sizeok == Sizeok.done)
|
|
return true; // succeeded
|
|
|
|
if (!ad.members)
|
|
{
|
|
.error(loc, "%s `%s` unknown size", ad.kind, ad.toPrettyChars);
|
|
return false;
|
|
}
|
|
|
|
if (ad._scope)
|
|
dsymbolSemantic(ad, null);
|
|
|
|
// Determine the instance size of base class first.
|
|
if (auto cd = ad.isClassDeclaration())
|
|
{
|
|
cd = cd.baseClass;
|
|
if (cd && !cd.determineSize(loc))
|
|
goto Lfail;
|
|
}
|
|
|
|
// Determine instance fields when sizeok == Sizeok.none
|
|
if (!ad.determineFields())
|
|
goto Lfail;
|
|
if (ad.sizeok != Sizeok.done)
|
|
ad.finalizeSize();
|
|
|
|
// this aggregate type has:
|
|
if (ad.type.ty == Terror)
|
|
return false; // marked as invalid during the finalizing.
|
|
if (ad.sizeok == Sizeok.done)
|
|
return true; // succeeded to calculate instance size.
|
|
|
|
Lfail:
|
|
// There's unresolvable forward reference.
|
|
if (ad.type != Type.terror)
|
|
error(loc, "%s `%s` no size because of forward reference", ad.kind, ad.toPrettyChars);
|
|
// Don't cache errors from speculative semantic, might be resolvable later.
|
|
// https://issues.dlang.org/show_bug.cgi?id=16574
|
|
if (!global.gag)
|
|
{
|
|
ad.type = Type.terror;
|
|
ad.errors = true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void addComment(Dsymbol d, const(char)* comment)
|
|
{
|
|
scope v = new AddCommentVisitor(comment);
|
|
d.accept(v);
|
|
}
|
|
|
|
extern (C++) class AddCommentVisitor: Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
const(char)* comment;
|
|
|
|
this(const(char)* comment)
|
|
{
|
|
this.comment = comment;
|
|
}
|
|
|
|
override void visit(Dsymbol d)
|
|
{
|
|
if (!comment || !*comment)
|
|
return;
|
|
|
|
//printf("addComment '%s' to Dsymbol %p '%s'\n", comment, this, toChars());
|
|
void* h = cast(void*)d; // just the pointer is the key
|
|
auto p = h in d.commentHashTable;
|
|
if (!p)
|
|
{
|
|
d.commentHashTable[h] = comment;
|
|
return;
|
|
}
|
|
if (strcmp(*p, comment) != 0)
|
|
{
|
|
// Concatenate the two
|
|
*p = Lexer.combineComments((*p).toDString(), comment.toDString(), true);
|
|
}
|
|
}
|
|
override void visit(AttribDeclaration atd)
|
|
{
|
|
if (comment)
|
|
{
|
|
atd.include(null).foreachDsymbol( s => s.addComment(comment) );
|
|
}
|
|
}
|
|
override void visit(ConditionalDeclaration cd)
|
|
{
|
|
if (comment)
|
|
{
|
|
cd.decl .foreachDsymbol( s => s.addComment(comment) );
|
|
cd.elsedecl.foreachDsymbol( s => s.addComment(comment) );
|
|
}
|
|
}
|
|
override void visit(StaticForeachDeclaration sfd) {}
|
|
}
|
|
|
|
void checkCtorConstInit(Dsymbol d)
|
|
{
|
|
scope v = new CheckCtorConstInitVisitor();
|
|
d.accept(v);
|
|
}
|
|
|
|
private extern(C++) class CheckCtorConstInitVisitor : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
override void visit(AttribDeclaration ad)
|
|
{
|
|
ad.include(null).foreachDsymbol( s => s.checkCtorConstInit() );
|
|
}
|
|
|
|
override void visit(VarDeclaration vd)
|
|
{
|
|
version (none)
|
|
{
|
|
/* doesn't work if more than one static ctor */
|
|
if (vd.ctorinit == 0 && vd.isCtorinit() && !vd.isField())
|
|
error("missing initializer in static constructor for const variable");
|
|
}
|
|
}
|
|
|
|
override void visit(Dsymbol d){}
|
|
}
|
|
|
|
/**************************************
|
|
* Determine if this symbol is only one.
|
|
* Returns:
|
|
* false, ps = null: There are 2 or more symbols
|
|
* true, ps = null: There are zero symbols
|
|
* true, ps = symbol: The one and only one symbol
|
|
*/
|
|
bool oneMember(Dsymbol d, out Dsymbol ps, Identifier ident)
|
|
{
|
|
scope v = new OneMemberVisitor(ps, ident);
|
|
d.accept(v);
|
|
return v.result;
|
|
}
|
|
|
|
private extern(C++) class OneMemberVisitor : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
Dsymbol* ps;
|
|
Identifier ident;
|
|
bool result;
|
|
|
|
this(out Dsymbol ps, Identifier ident)
|
|
{
|
|
this.ps = &ps;
|
|
this.ident = ident;
|
|
}
|
|
|
|
override void visit(AttribDeclaration atb)
|
|
{
|
|
Dsymbols* d = atb.include(null);
|
|
result = oneMembers(d, *ps, ident);
|
|
}
|
|
|
|
override void visit(StaticForeachDeclaration sfd)
|
|
{
|
|
// Required to support IFTI on a template that contains a
|
|
// `static foreach` declaration. `super.oneMember` calls
|
|
// include with a `null` scope. As `static foreach` requires
|
|
// the scope for expansion, `oneMember` can only return a
|
|
// precise result once `static foreach` has been expanded.
|
|
if (sfd.cached)
|
|
{
|
|
this.visit(cast(AttribDeclaration) sfd);
|
|
}
|
|
else
|
|
{
|
|
*ps = null; // a `static foreach` declaration may in general expand to multiple symbols
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
override void visit(StorageClassDeclaration scd)
|
|
{
|
|
bool t = oneMembers(scd.decl, *ps, ident);
|
|
if (t && *ps)
|
|
{
|
|
/* This is to deal with the following case:
|
|
* struct Tick {
|
|
* template to(T) { const T to() { ... } }
|
|
* }
|
|
* For eponymous function templates, the 'const' needs to get attached to 'to'
|
|
* before the semantic analysis of 'to', so that template overloading based on the
|
|
* 'this' pointer can be successful.
|
|
*/
|
|
if (FuncDeclaration fd = (*ps).isFuncDeclaration())
|
|
{
|
|
/* Use storage_class2 instead of storage_class otherwise when we do .di generation
|
|
* we'll wind up with 'const const' rather than 'const'.
|
|
*/
|
|
/* Don't think we need to worry about mutually exclusive storage classes here
|
|
*/
|
|
fd.storage_class2 |= scd.stc;
|
|
}
|
|
}
|
|
result = t;
|
|
}
|
|
|
|
override void visit(ConditionalDeclaration cd)
|
|
{
|
|
//printf("ConditionalDeclaration::oneMember(), inc = %d\n", condition.inc);
|
|
if (cd.condition.inc != Include.notComputed)
|
|
{
|
|
Dsymbols* d = dmd.expressionsem.include(cd.condition, null) ? cd.decl : cd.elsedecl;
|
|
result = oneMembers(d, *ps, ident);
|
|
}
|
|
else
|
|
{
|
|
bool res = (oneMembers(cd.decl, *ps, ident) && *ps is null && oneMembers(cd.elsedecl, *ps, ident) && *ps is null);
|
|
*ps = null;
|
|
result = res;
|
|
}
|
|
}
|
|
|
|
override void visit(ScopeDsymbol sd)
|
|
{
|
|
if (sd.isAnonymous())
|
|
result = oneMembers(sd.members, *ps, ident);
|
|
else {
|
|
// visit(Dsymbol dsym)
|
|
*ps = sd;
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
override void visit(StaticAssert sa)
|
|
{
|
|
//printf("StaticAssert::oneMember())\n");
|
|
*ps = null;
|
|
result = true;
|
|
}
|
|
|
|
override void visit(TemplateInstance ti)
|
|
{
|
|
*ps = null;
|
|
result = true;
|
|
}
|
|
|
|
override void visit(TemplateMixin tm)
|
|
{
|
|
*ps = tm;
|
|
result = true;
|
|
}
|
|
|
|
override void visit(Dsymbol dsym)
|
|
{
|
|
*ps = dsym;
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
/****************************************
|
|
* Return true if any of the members are static ctors or static dtors, or if
|
|
* any members have members that are.
|
|
*/
|
|
bool hasStaticCtorOrDtor(Dsymbol d)
|
|
{
|
|
scope v = new HasStaticCtorOrDtor();
|
|
d.accept(v);
|
|
return v.result;
|
|
}
|
|
|
|
private extern(C++) class HasStaticCtorOrDtor : Visitor
|
|
{
|
|
import dmd.mtype : Type;
|
|
|
|
alias visit = Visitor.visit;
|
|
bool result;
|
|
|
|
// attrib.d
|
|
override void visit(AttribDeclaration ad){
|
|
result = ad.include(null).foreachDsymbol( (s) { return s.hasStaticCtorOrDtor(); } ) != 0;
|
|
}
|
|
|
|
// dsymbol.d
|
|
override void visit(Dsymbol d){
|
|
//printf("Dsymbol::hasStaticCtorOrDtor() %s\n", toChars());
|
|
result = false;
|
|
}
|
|
|
|
override void visit(ScopeDsymbol sd) {
|
|
if (sd.members)
|
|
{
|
|
for (size_t i = 0; i < sd.members.length; i++)
|
|
{
|
|
Dsymbol member = (*(sd.members))[i];
|
|
if (member.hasStaticCtorOrDtor())
|
|
result = true;
|
|
return;
|
|
}
|
|
}
|
|
result = false;
|
|
}
|
|
|
|
// dtemplate.d
|
|
override void visit(TemplateDeclaration td) {
|
|
result = false; // don't scan uninstantiated templates
|
|
}
|
|
|
|
// func.d
|
|
override void visit(StaticCtorDeclaration scd) {
|
|
result = true;
|
|
}
|
|
|
|
override void visit(StaticDtorDeclaration sdd) @nogc nothrow pure @safe {
|
|
result = true;
|
|
}
|
|
}
|
|
|
|
bool isFuncHidden(ClassDeclaration cd, FuncDeclaration fd)
|
|
{
|
|
import dmd.funcsem : overloadApply;
|
|
//printf("ClassDeclaration.isFuncHidden(class = %s, fd = %s)\n", toChars(), fd.toPrettyChars());
|
|
Dsymbol s = cd.search(Loc.initial, fd.ident, SearchOpt.ignoreAmbiguous | SearchOpt.ignoreErrors);
|
|
if (!s)
|
|
{
|
|
//printf("not found\n");
|
|
/* Because, due to a hack, if there are multiple definitions
|
|
* of fd.ident, NULL is returned.
|
|
*/
|
|
return false;
|
|
}
|
|
s = s.toAlias();
|
|
if (auto os = s.isOverloadSet())
|
|
{
|
|
foreach (sm; os.a)
|
|
{
|
|
auto fm = sm.isFuncDeclaration();
|
|
if (overloadApply(fm, s => fd == s.isFuncDeclaration()))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
auto f = s.isFuncDeclaration();
|
|
//printf("%s fdstart = %p\n", s.kind(), fdstart);
|
|
if (overloadApply(f, s => fd == s.isFuncDeclaration()))
|
|
return false;
|
|
return !fd.parent.isTemplateMixin();
|
|
}
|
|
}
|
|
|
|
Dsymbol vtblSymbol(ClassDeclaration cd)
|
|
{
|
|
if (!cd.vtblsym)
|
|
{
|
|
auto vtype = Type.tvoidptr.immutableOf().sarrayOf(cd.vtbl.length);
|
|
auto var = new VarDeclaration(cd.loc, vtype, Identifier.idPool("__vtbl"), null, STC.immutable_ | STC.static_);
|
|
var.addMember(null, cd);
|
|
var.isdataseg = 1;
|
|
var._linkage = LINK.d;
|
|
var.semanticRun = PASS.semanticdone; // no more semantic wanted
|
|
cd.vtblsym = var;
|
|
}
|
|
return cd.vtblsym;
|
|
}
|
|
|
|
bool isAbstract(ClassDeclaration cd)
|
|
{
|
|
enum log = false;
|
|
if (cd.isabstract != ThreeState.none)
|
|
return cd.isabstract == ThreeState.yes;
|
|
|
|
if (log) printf("isAbstract(%s)\n", cd.toChars());
|
|
|
|
bool no() { if (log) printf("no\n"); cd.isabstract = ThreeState.no; return false; }
|
|
bool yes() { if (log) printf("yes\n"); cd.isabstract = ThreeState.yes; return true; }
|
|
|
|
if (cd.storage_class & STC.abstract_ || cd._scope && cd._scope.stc & STC.abstract_)
|
|
return yes();
|
|
|
|
if (cd.errors)
|
|
return no();
|
|
|
|
/* https://issues.dlang.org/show_bug.cgi?id=11169
|
|
* Resolve forward references to all class member functions,
|
|
* and determine whether this class is abstract.
|
|
*/
|
|
static int func(Dsymbol s, void*)
|
|
{
|
|
auto fd = s.isFuncDeclaration();
|
|
if (!fd)
|
|
return 0;
|
|
if (fd.storage_class & STC.static_)
|
|
return 0;
|
|
|
|
if (fd.isAbstract())
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
// opaque class is not abstract if it is not declared abstract
|
|
if (!(cd.members))
|
|
return no();
|
|
|
|
for (size_t i = 0; i < cd.members.length; i++)
|
|
{
|
|
auto s = (*(cd.members))[i];
|
|
if (s.apply(&func, null))
|
|
{
|
|
return yes();
|
|
}
|
|
}
|
|
|
|
/* If the base class is not abstract, then this class cannot
|
|
* be abstract.
|
|
*/
|
|
if (!cd.isInterfaceDeclaration() && (!cd.baseClass || !cd.baseClass.isAbstract()))
|
|
return no();
|
|
|
|
/* If any abstract functions are inherited, but not overridden,
|
|
* then the class is abstract. Do this by checking the vtbl[].
|
|
* Need to do semantic() on class to fill the vtbl[].
|
|
*/
|
|
cd.dsymbolSemantic(null);
|
|
|
|
/* The next line should work, but does not because when ClassDeclaration.dsymbolSemantic()
|
|
* is called recursively it can set PASS.semanticdone without finishing it.
|
|
*/
|
|
//if (semanticRun < PASS.semanticdone)
|
|
{
|
|
/* Could not complete semantic(). Try running semantic() on
|
|
* each of the virtual functions,
|
|
* which will fill in the vtbl[] overrides.
|
|
*/
|
|
static int virtualSemantic(Dsymbol s, void*)
|
|
{
|
|
auto fd = s.isFuncDeclaration();
|
|
if (fd && !(fd.storage_class & STC.static_) && !fd.isUnitTestDeclaration())
|
|
fd.dsymbolSemantic(null);
|
|
return 0;
|
|
}
|
|
|
|
for (size_t i = 0; i < cd.members.length; i++)
|
|
{
|
|
auto s = (*(cd.members))[i];
|
|
s.apply(&virtualSemantic,null);
|
|
}
|
|
}
|
|
|
|
/* Finally, check the vtbl[]
|
|
*/
|
|
foreach (i; 1 .. cd.vtbl.length)
|
|
{
|
|
auto fd = cd.vtbl[i].isFuncDeclaration();
|
|
//if (fd) printf("\tvtbl[%d] = [%s] %s\n", i, fd.loc.toChars(), fd.toPrettyChars());
|
|
if (!fd || fd.isAbstract())
|
|
{
|
|
return yes();
|
|
}
|
|
}
|
|
|
|
return no();
|
|
}
|
|
|
|
void finalizeSize(AggregateDeclaration ad)
|
|
{
|
|
scope v = new FinalizeSizeVisitor();
|
|
ad.accept(v);
|
|
}
|
|
|
|
/**********************************
|
|
* Determine if exp is all binary zeros.
|
|
* Params:
|
|
* exp = expression to check
|
|
* Returns:
|
|
* true if it's all binary 0
|
|
*/
|
|
bool _isZeroInit(Expression exp)
|
|
{
|
|
switch (exp.op)
|
|
{
|
|
case EXP.int64:
|
|
return exp.toInteger() == 0;
|
|
|
|
case EXP.null_:
|
|
return true;
|
|
|
|
case EXP.structLiteral:
|
|
{
|
|
auto sle = exp.isStructLiteralExp();
|
|
if (sle.sd.isNested())
|
|
return false;
|
|
const isCstruct = sle.sd.isCsymbol(); // C structs are default initialized to all zeros
|
|
foreach (i; 0 .. sle.sd.fields.length)
|
|
{
|
|
auto field = sle.sd.fields[i];
|
|
if (field.type.size(field.loc))
|
|
{
|
|
auto e = sle.elements && i < sle.elements.length ? (*sle.elements)[i] : null;
|
|
if (e ? !_isZeroInit(e)
|
|
: !isCstruct && !field.type.isZeroInit(field.loc))
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case EXP.arrayLiteral:
|
|
{
|
|
auto ale = cast(ArrayLiteralExp)exp;
|
|
|
|
const dim = ale.elements ? ale.elements.length : 0;
|
|
|
|
if (ale.type.toBasetype().ty == Tarray) // if initializing a dynamic array
|
|
return dim == 0;
|
|
|
|
foreach (i; 0 .. dim)
|
|
{
|
|
if (!_isZeroInit(ale[i]))
|
|
return false;
|
|
}
|
|
|
|
/* Note that true is returned for all T[0]
|
|
*/
|
|
return true;
|
|
}
|
|
|
|
case EXP.string_:
|
|
{
|
|
auto se = cast(StringExp)exp;
|
|
|
|
if (se.type.toBasetype().ty == Tarray) // if initializing a dynamic array
|
|
return se.len == 0;
|
|
|
|
foreach (i; 0 .. se.len)
|
|
{
|
|
if (se.getIndex(i) != 0)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
case EXP.vector:
|
|
{
|
|
auto ve = cast(VectorExp) exp;
|
|
return _isZeroInit(ve.e1);
|
|
}
|
|
|
|
case EXP.float64:
|
|
case EXP.complex80:
|
|
{
|
|
import dmd.root.ctfloat : CTFloat;
|
|
return (exp.toReal() is CTFloat.zero) &&
|
|
(exp.toImaginary() is CTFloat.zero);
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private extern(C++) class FinalizeSizeVisitor : Visitor
|
|
{
|
|
alias visit = Visitor.visit;
|
|
|
|
override void visit(ClassDeclaration outerCd)
|
|
{
|
|
assert(outerCd.sizeok != Sizeok.done);
|
|
|
|
// Set the offsets of the fields and determine the size of the class
|
|
if (outerCd.baseClass)
|
|
{
|
|
assert(outerCd.baseClass.sizeok == Sizeok.done);
|
|
|
|
outerCd.alignsize = outerCd.baseClass.alignsize;
|
|
if (outerCd.classKind == ClassKind.cpp)
|
|
outerCd.structsize = target.cpp.derivedClassOffset(outerCd.baseClass);
|
|
else
|
|
outerCd.structsize = outerCd.baseClass.structsize;
|
|
}
|
|
else if (outerCd.classKind == ClassKind.objc)
|
|
outerCd.structsize = 0; // no hidden member for an Objective-C class
|
|
else if (outerCd.isInterfaceDeclaration())
|
|
{
|
|
if (outerCd.interfaces.length == 0)
|
|
{
|
|
outerCd.alignsize = target.ptrsize;
|
|
outerCd.structsize = target.ptrsize; // allow room for __vptr
|
|
}
|
|
}
|
|
else
|
|
{
|
|
outerCd.alignsize = target.ptrsize;
|
|
outerCd.structsize = target.ptrsize; // allow room for __vptr
|
|
if (outerCd.hasMonitor())
|
|
outerCd.structsize += target.ptrsize; // allow room for __monitor
|
|
}
|
|
|
|
//printf("finalizeSize() %s, sizeok = %d\n", toChars(), sizeok);
|
|
size_t bi = 0; // index into vtblInterfaces[]
|
|
|
|
/****
|
|
* Runs through the inheritance graph to set the BaseClass.offset fields.
|
|
* Recursive in order to account for the size of the interface classes, if they are
|
|
* more than just interfaces.
|
|
* Params:
|
|
* cd = interface to look at
|
|
* baseOffset = offset of where cd will be placed
|
|
* Returns:
|
|
* subset of instantiated size used by cd for interfaces
|
|
*/
|
|
uint membersPlace(ClassDeclaration cd, uint baseOffset)
|
|
{
|
|
//printf(" membersPlace(%s, %d)\n", cd.toChars(), baseOffset);
|
|
uint offset = baseOffset;
|
|
|
|
foreach (BaseClass* b; cd.interfaces)
|
|
{
|
|
if (b.sym.sizeok != Sizeok.done)
|
|
b.sym.finalizeSize();
|
|
assert(b.sym.sizeok == Sizeok.done);
|
|
|
|
if (!b.sym.alignsize)
|
|
b.sym.alignsize = target.ptrsize;
|
|
offset = alignmember(structalign_t(cast(ushort)b.sym.alignsize), b.sym.alignsize, offset);
|
|
assert(bi < outerCd.vtblInterfaces.length);
|
|
|
|
BaseClass* bv = (*(outerCd.vtblInterfaces))[bi];
|
|
if (b.sym.interfaces.length == 0)
|
|
{
|
|
//printf("\tvtblInterfaces[%d] b=%p b.sym = %s, offset = %d\n", bi, bv, bv.sym.toChars(), offset);
|
|
bv.offset = offset;
|
|
++bi;
|
|
// All the base interfaces down the left side share the same offset
|
|
for (BaseClass* b2 = bv; b2.baseInterfaces.length; )
|
|
{
|
|
b2 = &b2.baseInterfaces[0];
|
|
b2.offset = offset;
|
|
//printf("\tvtblInterfaces[%d] b=%p sym = %s, offset = %d\n", bi, b2, b2.sym.toChars(), b2.offset);
|
|
}
|
|
}
|
|
membersPlace(b.sym, offset);
|
|
//printf(" %s size = %d\n", b.sym.toChars(), b.sym.structsize);
|
|
offset += b.sym.structsize;
|
|
if (outerCd.alignsize < b.sym.alignsize)
|
|
outerCd.alignsize = b.sym.alignsize;
|
|
}
|
|
return offset - baseOffset;
|
|
}
|
|
|
|
outerCd.structsize += membersPlace(outerCd, outerCd.structsize);
|
|
|
|
if (outerCd.isInterfaceDeclaration())
|
|
{
|
|
outerCd.sizeok = Sizeok.done;
|
|
return;
|
|
}
|
|
|
|
// FIXME: Currently setFieldOffset functions need to increase fields
|
|
// to calculate each variable offsets. It can be improved later.
|
|
outerCd.fields.setDim(0);
|
|
|
|
FieldState fieldState;
|
|
fieldState.offset = outerCd.structsize;
|
|
foreach (s; *(outerCd.members))
|
|
{
|
|
s.setFieldOffset(outerCd, &fieldState, false);
|
|
}
|
|
|
|
outerCd.sizeok = Sizeok.done;
|
|
|
|
// Calculate fields[i].overlapped
|
|
outerCd.checkOverlappedFields();
|
|
}
|
|
|
|
override void visit(StructDeclaration sd)
|
|
{
|
|
//printf("StructDeclaration::finalizeSize() %s, sizeok = %d\n", toChars(), sizeok);
|
|
assert(sd.sizeok != Sizeok.done);
|
|
|
|
if (sd.sizeok == Sizeok.inProcess)
|
|
{
|
|
return;
|
|
}
|
|
sd.sizeok = Sizeok.inProcess;
|
|
|
|
//printf("+StructDeclaration::finalizeSize() %s, fields.length = %d, sizeok = %d\n", toChars(), fields.length, sizeok);
|
|
|
|
sd.fields.setDim(0); // workaround
|
|
|
|
// Set the offsets of the fields and determine the size of the struct
|
|
FieldState fieldState;
|
|
bool isunion = sd.isUnionDeclaration() !is null;
|
|
for (size_t i = 0; i < sd.members.length; i++)
|
|
{
|
|
Dsymbol s = (*sd.members)[i];
|
|
s.setFieldOffset(sd, &fieldState, isunion);
|
|
if (sd.type.ty == Terror)
|
|
{
|
|
errorSupplemental(s.loc, "error on member `%s`", s.toPrettyChars);
|
|
sd.errors = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (sd.structsize == 0)
|
|
{
|
|
sd.hasNoFields = true;
|
|
sd.alignsize = 1;
|
|
|
|
// A fine mess of what size a zero sized struct should be
|
|
final switch (sd.classKind)
|
|
{
|
|
case ClassKind.d:
|
|
case ClassKind.cpp:
|
|
sd.structsize = 1;
|
|
break;
|
|
|
|
case ClassKind.c:
|
|
case ClassKind.objc:
|
|
if (target.c.bitFieldStyle == TargetC.BitFieldStyle.MS)
|
|
{
|
|
/* Undocumented MS behavior for:
|
|
* struct S { int :0; };
|
|
*/
|
|
sd.structsize = 4;
|
|
}
|
|
else
|
|
sd.structsize = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Round struct size up to next alignsize boundary.
|
|
// This will ensure that arrays of structs will get their internals
|
|
// aligned properly.
|
|
if (sd.alignment.isDefault() || sd.alignment.isPack())
|
|
sd.structsize = (sd.structsize + sd.alignsize - 1) & ~(sd.alignsize - 1);
|
|
else
|
|
sd.structsize = (sd.structsize + sd.alignment.get() - 1) & ~(sd.alignment.get() - 1);
|
|
|
|
sd.sizeok = Sizeok.done;
|
|
|
|
//printf("-StructDeclaration::finalizeSize() %s, fields.length = %d, structsize = %d\n", toChars(), cast(int)fields.length, cast(int)structsize);
|
|
|
|
if (sd.errors)
|
|
return;
|
|
|
|
// Calculate fields[i].overlapped
|
|
if (sd.checkOverlappedFields())
|
|
{
|
|
sd.errors = true;
|
|
return;
|
|
}
|
|
|
|
// Determine if struct is all zeros or not
|
|
sd.zeroInit = true;
|
|
auto lastOffset = -1;
|
|
foreach (vd; sd.fields)
|
|
{
|
|
// First skip zero sized fields
|
|
if (vd.type.size(vd.loc) == 0)
|
|
continue;
|
|
|
|
// only consider first sized member of an (anonymous) union
|
|
if (vd.overlapped && vd.offset == lastOffset)
|
|
continue;
|
|
lastOffset = vd.offset;
|
|
|
|
if (vd._init)
|
|
{
|
|
if (vd._init.isVoidInitializer())
|
|
/* Treat as 0 for the purposes of putting the initializer
|
|
* in the BSS segment, or doing a mass set to 0
|
|
*/
|
|
continue;
|
|
|
|
// Examine init to see if it is all 0s.
|
|
auto exp = vd.getConstInitializer();
|
|
if (!exp || !_isZeroInit(exp))
|
|
{
|
|
sd.zeroInit = false;
|
|
break;
|
|
}
|
|
}
|
|
else if (!vd.type.isZeroInit(sd.loc))
|
|
{
|
|
sd.zeroInit = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
sd.argTypes = target.toArgTypes(sd.type);
|
|
}
|
|
}
|
|
|
|
/*****************************************
|
|
* Is Dsymbol a variable that contains pointers?
|
|
*/
|
|
bool hasPointers(Dsymbol d)
|
|
{
|
|
scope v = new HasPointersVisitor();
|
|
d.accept(v);
|
|
return v.result;
|
|
}
|
|
|
|
private extern(C++) class HasPointersVisitor : Visitor
|
|
{
|
|
import dmd.mtype : Type;
|
|
|
|
alias visit = Visitor.visit;
|
|
bool result;
|
|
|
|
override void visit(AttribDeclaration ad)
|
|
{
|
|
result = ad.include(null).foreachDsymbol( (s) { return s.hasPointers(); } ) != 0;
|
|
}
|
|
|
|
override void visit(VarDeclaration vd)
|
|
{
|
|
import dmd.typesem : hasPointers;
|
|
result = (!vd.isDataseg() && vd.type.hasPointers());
|
|
}
|
|
|
|
override void visit(Dsymbol d)
|
|
{
|
|
//printf("Dsymbol::hasPointers() %s\n", toChars());
|
|
result = false;
|
|
}
|
|
|
|
override void visit(TemplateMixin tm)
|
|
{
|
|
//printf("TemplateMixin.hasPointers() %s\n", toChars());
|
|
result = tm.members.foreachDsymbol( (s) { return s.hasPointers(); } ) != 0;
|
|
}
|
|
|
|
override void visit(Nspace ns)
|
|
{
|
|
//printf("Nspace::hasPointers() %s\n", toChars());
|
|
result = ns.members.foreachDsymbol( (s) { return s.hasPointers(); } ) != 0;
|
|
}
|
|
}
|
|
|
|
/***************************************
|
|
* Expands attribute declarations in members in depth first
|
|
* order. Calls dg(size_t symidx, Dsymbol *sym) for each
|
|
* member.
|
|
* If dg returns !=0, stops and returns that value else returns 0.
|
|
* Use this function to avoid the O(N + N^2/2) complexity of
|
|
* calculating dim and calling N times getNth.
|
|
* Returns:
|
|
* last value returned by dg()
|
|
*/
|
|
int _foreach(Scope* sc, Dsymbols* members, scope ForeachDg dg, size_t* pn = null)
|
|
{
|
|
assert(dg);
|
|
if (!members)
|
|
return 0;
|
|
size_t n = pn ? *pn : 0; // take over index
|
|
int result = 0;
|
|
foreach (size_t i; 0 .. members.length)
|
|
{
|
|
import dmd.attrib : AttribDeclaration;
|
|
import dmd.dtemplate : TemplateMixin;
|
|
|
|
Dsymbol s = (*members)[i];
|
|
if (AttribDeclaration a = s.isAttribDeclaration())
|
|
result = _foreach(sc, a.include(sc), dg, &n);
|
|
else if (TemplateMixin tm = s.isTemplateMixin())
|
|
result = _foreach(sc, tm.members, dg, &n);
|
|
else if (s.isTemplateInstance())
|
|
{
|
|
}
|
|
else if (s.isUnitTestDeclaration())
|
|
{
|
|
}
|
|
else
|
|
result = dg(n++, s);
|
|
if (result)
|
|
break;
|
|
}
|
|
if (pn)
|
|
*pn = n; // update index
|
|
return result;
|
|
}
|
|
|
|
/****************************************
|
|
* Create array of the local classes in the Module, suitable
|
|
* for inclusion in ModuleInfo
|
|
* Params:
|
|
* mod = the Module
|
|
* aclasses = array to fill in
|
|
* Returns: array of local classes
|
|
*/
|
|
void getLocalClasses(Module mod, ref ClassDeclarations aclasses)
|
|
{
|
|
//printf("members.length = %d\n", mod.members.length);
|
|
int pushAddClassDg(size_t n, Dsymbol sm)
|
|
{
|
|
if (!sm)
|
|
return 0;
|
|
|
|
if (auto cd = sm.isClassDeclaration())
|
|
{
|
|
// compatibility with previous algorithm
|
|
if (cd.parent && cd.parent.isTemplateMixin())
|
|
return 0;
|
|
|
|
if (cd.classKind != ClassKind.objc)
|
|
aclasses.push(cd);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
_foreach(null, mod.members, &pushAddClassDg);
|
|
}
|
|
|
|
/*****************************************
|
|
* Same as Dsymbol::oneMember(), but look at an array of Dsymbols.
|
|
*/
|
|
extern (D) bool oneMembers(Dsymbols* members, out Dsymbol ps, Identifier ident)
|
|
{
|
|
//printf("Dsymbol::oneMembers() %d\n", members ? members.length : 0);
|
|
Dsymbol s = null;
|
|
if (!members)
|
|
{
|
|
ps = null;
|
|
return true;
|
|
}
|
|
|
|
for (size_t i = 0; i < members.length; i++)
|
|
{
|
|
Dsymbol sx = (*members)[i];
|
|
bool x = sx.oneMember(ps, ident); //MYTODO: this temporarily creates a new dependency to dsymbolsem, will need to extract oneMembers() later
|
|
//printf("\t[%d] kind %s = %d, s = %p\n", i, sx.kind(), x, *ps);
|
|
if (!x)
|
|
{
|
|
//printf("\tfalse 1\n");
|
|
assert(ps is null);
|
|
return false;
|
|
}
|
|
if (ps)
|
|
{
|
|
assert(ident);
|
|
if (!ps.ident || !ps.ident.equals(ident))
|
|
continue;
|
|
if (!s)
|
|
s = ps;
|
|
else if (s.isOverloadable() && ps.isOverloadable())
|
|
{
|
|
// keep head of overload set
|
|
FuncDeclaration f1 = s.isFuncDeclaration();
|
|
FuncDeclaration f2 = ps.isFuncDeclaration();
|
|
if (f1 && f2)
|
|
{
|
|
assert(!f1.isFuncAliasDeclaration());
|
|
assert(!f2.isFuncAliasDeclaration());
|
|
for (; f1 != f2; f1 = f1.overnext0)
|
|
{
|
|
if (f1.overnext0 is null)
|
|
{
|
|
f1.overnext0 = f2;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else // more than one symbol
|
|
{
|
|
ps = null;
|
|
//printf("\tfalse 2\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
ps = s; // s is the one symbol, null if none
|
|
//printf("\ttrue\n");
|
|
return true;
|
|
}
|