63 changes: 63 additions & 0 deletions src/module.c
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,69 @@ Module *Module::parse()
error("has non-identifier characters in filename, use module declaration instead");
}

// Add internal used functions in 'object' module members.
if (!parent && ident == Id::object)
{
static const utf8_t code_ArrayEq[] =
"bool _ArrayEq(T1, T2)(T1[] a, T2[] b) {\n"
" if (a.length != b.length) return false;\n"
" foreach (size_t i; 0 .. a.length) { if (a[i] != b[i]) return false; }\n"
" return true; }\n";

static const utf8_t code_ArrayPostblit[] =
"void _ArrayPostblit(T)(T[] a) { foreach (ref T e; a) e.__xpostblit(); }\n";

static const utf8_t code_ArrayDtor[] =
"void _ArrayDtor(T)(T[] a) { foreach_reverse (ref T e; a) e.__xdtor(); }\n";

static const utf8_t code_xopEquals[] =
"bool _xopEquals(in void*, in void*) { throw new Error(\"TypeInfo.equals is not implemented\"); }\n";

static const utf8_t code_xopCmp[] =
"bool _xopCmp(in void*, in void*) { throw new Error(\"TypeInfo.compare is not implemented\"); }\n";

Identifier *arreq = Id::_ArrayEq;
Identifier *xopeq = Identifier::idPool("_xopEquals");
Identifier *xopcmp = Identifier::idPool("_xopCmp");
for (size_t i = 0; i < members->dim; i++)
{
Dsymbol *sx = (*members)[i];
if (!sx) continue;
if (arreq && sx->ident == arreq) arreq = NULL;
if (xopeq && sx->ident == xopeq) xopeq = NULL;
if (xopcmp && sx->ident == xopcmp) xopcmp = NULL;
}

if (arreq)
{
Parser p(loc, this, code_ArrayEq, strlen((const char *)code_ArrayEq), 0);
p.nextToken();
members->append(p.parseDeclDefs(0));
}
{
Parser p(loc, this, code_ArrayPostblit, strlen((const char *)code_ArrayPostblit), 0);
p.nextToken();
members->append(p.parseDeclDefs(0));
}
{
Parser p(loc, this, code_ArrayDtor, strlen((const char *)code_ArrayDtor), 0);
p.nextToken();
members->append(p.parseDeclDefs(0));
}
if (xopeq)
{
Parser p(loc, this, code_xopEquals, strlen((const char *)code_xopEquals), 0);
p.nextToken();
members->append(p.parseDeclDefs(0));
}
if (xopcmp)
{
Parser p(loc, this, code_xopCmp, strlen((const char *)code_xopCmp), 0);
p.nextToken();
members->append(p.parseDeclDefs(0));
}
}

// Insert module into the symbol table
Dsymbol *s = this;
if (isPackageFile)
Expand Down
43 changes: 35 additions & 8 deletions src/struct.c
Original file line number Diff line number Diff line change
Expand Up @@ -729,13 +729,10 @@ void StructDeclaration::semantic(Scope *sc)
error("structs, unions cannot be abstract");
userAttribDecl = sc->userAttribDecl;
}
else if (symtab)
else if (symtab && !scx)
{
if (sizeok == SIZEOKdone || !scx)
{
semanticRun = PASSsemanticdone;
return;
}
semanticRun = PASSsemanticdone;
return;
}
semanticRun = PASSsemantic;

Expand All @@ -757,7 +754,6 @@ void StructDeclaration::semantic(Scope *sc)
}
}

sizeok = SIZEOKnone;
Scope *sc2 = sc->push(this);
sc2->stc &= STCsafe | STCtrusted | STCsystem;
sc2->parent = this;
Expand All @@ -768,6 +764,11 @@ void StructDeclaration::semantic(Scope *sc)
sc2->structalign = STRUCTALIGN_DEFAULT;
sc2->userAttribDecl = NULL;

if (sizeok == SIZEOKdone)
goto LafterSizeok;

sizeok = SIZEOKnone;

/* Set scope so if there are forward references, we still might be able to
* resolve individual members like enums.
*/
Expand Down Expand Up @@ -816,10 +817,33 @@ void StructDeclaration::semantic(Scope *sc)
}

Module::dprogress++;
semanticRun = PASSsemanticdone;

//printf("-StructDeclaration::semantic(this=%p, '%s')\n", this, toChars());

LafterSizeok:
// The additions of special member functions should have its own
// sub-semantic analysis pass, and have to be deferred sometimes.
// See the case in compilable/test14838.d
for (size_t i = 0; i < fields.dim; i++)
{
VarDeclaration *v = fields[i];
Type *tb = v->type->baseElemOf();
if (tb->ty != Tstruct)
continue;
StructDeclaration *sd = ((TypeStruct *)tb)->sym;
if (sd->semanticRun >= PASSsemanticdone)
continue;

sc2->pop();

scope = scx ? scx : sc->copy();
scope->setNoFree();
scope->module->addDeferredSemantic(this);

//printf("\tdeferring %s\n", toChars());
return;
}

/* Look for special member functions.
*/
aggNew = (NewDeclaration *)search(Loc(), Id::classNew);
Expand Down Expand Up @@ -871,6 +895,9 @@ void StructDeclaration::semantic(Scope *sc)
}
}

Module::dprogress++;
semanticRun = PASSsemanticdone;

TypeTuple *tup = toArgTypes(type);
size_t dim = tup->arguments->dim;
if (dim >= 1)
Expand Down
91 changes: 91 additions & 0 deletions test/compilable/test14838.d
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// PERMUTE_ARGS:

struct A(T) { ~this() {} }
class C { A!int[1] array; }

void test14838() pure nothrow @nogc @safe
{
C c;
c.__xdtor(); // C.~this() will also be inferred to
// pure nothrow @nogc @safe

A!int[1] array;
// scope destructor call does not cause attribute violation.
}

// ----

/*
* This is a reduced test case comes from std.container.Array template,
* to fix the semantic analysis order issue for correct destructor attribute inference.
*
* Before the bugfix:
* 1. StructDeclaration('Array!int')->semantic() instantiates
* RangeT!(Array!int) at the `alias Range = ...;`, but
* StructDeclaration('RangeT!(Array!int)')->semantic() exits
* with sizeok == SIZEOKfwd, because the size of _outer_ field is not yet determined.
* 2. StructDeclaration('Array!int')->semantic() succeeds to determine the size
* (sizeok = SIZEOKdone).
* 3. StructDeclaration('Array!int')->buildOpAssign() will generate opAssign because
* Array!int._data field has identity opAssign member function.
* 4. The semantic3 will get called for the generated opAssign, then
* 6-1. Array!int.~this() semantic3, and
* 6-2. RefCounted!(Array!int.Payload).~this() semantic3
* will also get called to infer their attributes.
* 5. In RefCounted!(Array!int.Payload).~this(), destroy(t) will be instantiated.
* At that, TemplateInstance::expandMembers() will invoke runDeferredSemantic()
* and it will re-run StructDeclaration('RangeT!(Array!int)')->semantic().
* 6. StructDeclaration('RangeT!(Array!int)')->semantic() determines the size
* (sizeok = SIZEOKdone). Then, it will generate identity opAssign and run its semantic3.
* It will need to infer RangeT!(Array!int).~this() attribute, then it requires the
* correct attribute of Array!int.~this().
*
* However, the Array!int.~this() attribute is not yet determined! [bug]
* -> it's wongly handled as impure/system/throwable/gc-able.
*
* -> then, the attribute inference results for
* RangeT!(Array!int).~this() and Array!int.~this() will be incorrect.
*
* After the bugfix:
* In 6, StructDeclaration('RangeT!(Array!int)')->semantic() will check that:
* all base struct types of the instance fields have completed addition of
* special functions (dtor, opAssign, etc).
* If not, it will defer the completion of its semantic pass.
*/

void destroy14838(S)(ref S s) if (is(S == struct))
{
s.__xdtor();
}

struct RefCounted14838(T)
{
~this()
{
T t;
.destroy14838(t);
}

void opAssign(typeof(this) rhs) {}
}

struct RangeT14838(A)
{
A[1] _outer_;
}

struct Array14838(T)
{
struct Payload
{
~this() {}
}
RefCounted14838!Payload _data;

alias Range = RangeT14838!Array14838;
}

class Test14838
{
Array14838!int[1] field;
}
54 changes: 54 additions & 0 deletions test/runnable/sdtor.d
Original file line number Diff line number Diff line change
Expand Up @@ -3942,6 +3942,59 @@ void test14264()
assert(dtor == 4);
}

/**********************************/
// 14838

int test14838() pure nothrow @safe
{
int dtor;

struct S14838(T)
{
~this() { ++dtor; }
}
struct X14838
{
S14838!int ms;
const S14838!int cs;

S14838!int[2] ma;
const S14838!int[2] ca;

S14838!int[2][2] ma2x2;
const S14838!int[2][2] ca2x2;

// number of S14838 = 1*2 + 2*2 + 4*2 = 14
}

void test(Dg)(scope Dg code)
{
dtor = 0;
code();
}

test(delegate{ S14838!int a; }); assert(dtor == 1);
test(delegate{ const S14838!int a; }); assert(dtor == 1);

test(delegate{ S14838!int[2] a; }); assert(dtor == 2);
test(delegate{ const S14838!int[2] a; }); assert(dtor == 2);

test(delegate{ S14838!int[2][2] a; }); assert(dtor == 4);
test(delegate{ const S14838!int[2][2] a; }); assert(dtor == 4);

test(delegate{ X14838 a; }); assert(dtor == 1 * 14);
test(delegate{ const X14838 a; }); assert(dtor == 1 * 14);

test(delegate{ X14838[2] a; }); assert(dtor == 2 * 14);
test(delegate{ const X14838[2] a; }); assert(dtor == 2 * 14);

test(delegate{ X14838[2][2] a; }); assert(dtor == 4 * 14);
test(delegate{ const X14838[2][2] a; }); assert(dtor == 4 * 14);

return 1;
}
static assert(test14838());

/**********************************/

int main()
Expand Down Expand Up @@ -4058,6 +4111,7 @@ int main()
test13669();
test13095();
test14264();
test14838();

printf("Success\n");
return 0;
Expand Down