Skip to content

Possible regression in hasElaborateDestructor detection since DMD 2.111.0 #10950

@tchaloupka

Description

@tchaloupka

I've found what seems to be a regression related to the hasElaborateDestructor trait.
When compiling and running the following code with DMD 2.110.0, I get this output:

~C(0)
a.b.len=1
b.c.len=0
exit
~B(): c.len=0
~A(): b.len=1
~B(): c.len=1
~C(1)

However, when compiled with DMD 2.111.0, I get:

No destructor found for B
No destructor found for C

~C(1)
a.b.len=1
b.c.len=0
exit
~B(): c.len=0
~A(): b.len=1

So it seems that in 2.111.0 the compiler incorrectly determines that B and C have no destructors, even though they clearly do.

Here’s the minimal reproducible example:

import core.lifetime : moveEmplace;
import core.stdc.stdio : printf;
import core.stdc.stdlib : malloc, realloc, free;

import std.algorithm : max;
import std.traits : hasElaborateDestructor, isImplicitlyConvertible;

void main()
{
    A a;
    B b;
    b.c ~= C(1);
    a.b ~= b;

    printf("a.b.len=%ld\n", a.b.len);
    printf("b.c.len=%ld\n", b.c.len);
    printf("exit\n");
}

struct Array(T, size_t step = 64)
{
    @disable this(this);
    this(size_t cap) @trusted nothrow @nogc {
        buf = (cast(T*)malloc(T.sizeof * cap))[0..cap];
    }

    ~this() nothrow @nogc {
        if (buf) {
            static if (hasElaborateDestructor!T) {
                foreach (ref item; buf[0..len]) destroy(item);
            }
            else pragma(msg, "No destructor found for ", T);
            free(buf.ptr);
        }
    }

    void reserve(size_t elements) @trusted nothrow @nogc {
        if (buf.length - len >= elements) return;
        const newLen = buf.length + max(elements, step);
        if (!buf)
            buf = (cast(T*)malloc(T.sizeof * newLen))[0..newLen];
        else
            buf = (cast(T*)realloc(buf.ptr, T.sizeof * newLen))[0..newLen];
    }

    void put(U)(auto ref scope U item) @trusted
        if (isImplicitlyConvertible!(U, T))
    {
        reserve(1);
        () @trusted { item.moveEmplace(buf[len++]); }();
    }

    alias opOpAssign(string op : "~") = put;

private:
    T[] buf;
    size_t len;
}

struct A {
    Array!B b;
    ~this() nothrow @nogc {
        printf("~A(): b.len=%ld\n", b.len);
    }
}

struct B {
    Array!C c;
    ~this() nothrow @nogc {
        printf("~B(): c.len=%ld\n", c.len);
    }
}

struct C {
    int foo;
    ~this() nothrow @nogc {
        printf("~C(%d)\n", foo);
    }
}

Expected behavior:
hasElaborateDestructor!B and hasElaborateDestructor!C should return true (as in 2.110.0), since both types define destructors.

Actual behavior:
Starting with 2.111.0, both are incorrectly reported as having no destructors.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Severity:RegressionIssues that are regressions/PRs that fix regressions

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions