Skip to content

object.destroy fails for local self-referential types with "no size because of forward references" #23355

Description

@shoo

object.destroy cannot be instantiated for a self-referential type defined inside a function, even though the equivalent top-level type compiles successfully.

The failure occurs during semantic analysis with:

Error: no size because of forward references

The error occurs while instantiating core.internal.destruction.destructRecurse.

Example:

The following top-level definition compiles successfully:

struct Recursive(T)
{
    T* _inst;
    void _release() {
        object.destroy(*_inst);
    }
}
struct Node
{
    int id;
    Recursive!Node next;
    ~this() {}
}
void main() {}

However, moving the same type into a function causes a compilation error:

struct Recursive(T)
{
    T* _inst;
    void _release() {
        object.destroy(*_inst);
    }
}
void main() {
    struct Node
    {
        int id;
        Recursive!Node next;
        ~this() {}
    }
}

Actual result:

onlineapp.d(9): Error: no size because of forward references
    struct Node
    ^
/dlang/dmd/linux/bin64/../../src/druntime/import/object.d(4119): Error: template instance `core.internal.destruction.destructRecurse!(Node)` error instantiating
    destructRecurse(obj);
                   ^
onlineapp.d(5):        instantiated from here: `destroy!(true, Node)`
        object.destroy(*_inst);
                      ^
onlineapp.d(12):        instantiated from here: `Recursive!(Node)`
        Recursive!Node next;
        ^
onlineapp.d(9): Error: no size because of forward references
    struct Node
    ^
onlineapp.d(9): Error: no size because of forward references
    struct Node
    ^
onlineapp.d(9): Error: no size because of forward references
    struct Node
    ^
onlineapp.d(9): Error: struct `onlineapp.main.Node` no size because of forward reference
    struct Node
    ^
/dlang/dmd/linux/bin64/../../src/druntime/import/object.d(4124): Error: template instance `core.internal.lifetime.emplaceInitializer!(Node)` error instantiating
        emplaceInitializer(obj); // emplace T.init
                          ^
onlineapp.d(5):        instantiated from here: `destroy!(true, Node)`
        object.destroy(*_inst);
                      ^
onlineapp.d(12):        instantiated from here: `Recursive!(Node)`
        Recursive!Node next;
        ^

Expected result
Both programs should compile successfully, since the equivalent module-scope definition compiles successfully.
The behavior of object.destroy should not depend on whether a self-referential type is declared at module scope or local scope.

Additional notes:
object.destroy instantiates core.internal.destruction.destructRecurse, which internally checks for the compiler-generated __xdtor. The issue may therefore be related to handling incomplete types during semantic analysis.

This may be related to the inconsistent behavior of hasElaborateDestructor for incomplete self-referential types. A separate issue has been filed for that behavior.
#23352
Although both issues involve incomplete self-referential types, they concern different problems. hasElaborateDestructor is a compiler trait whose semantics may legitimately depend on whether a type is complete. In contrast, object.destroy is a public API and should remain usable regardless of how destructor availability is internally determined. Therefore these are reported as separate issues.

Additional note 2:
The issue is not limited to compilation failures. In the following example, the program compiles successfully, but object.destroy fails to invoke the destructor of the referenced object.

static int x = 0;
struct Recursive(T)
{
	T* _inst;
    ~this() {
        if (_inst)
	        object.destroy(_inst);
    }
}
struct Node
{
    int id;
    Recursive!Node next;
    ~this() {
        x++;
    }
}
void main() {
    {
        Node n1;
        n1.next._inst = new Node;
    }
    assert(x == 2); // Fails: object.destroy does not invoke `new Node`'s destructor.
}

The assertion fails because only the stack-allocated Node is destroyed. The destructor of the heap-allocated Node, which should be invoked through object.destroy, is never called.
This appears to occur because destructRecurse!Node is instantiated while Node is still incomplete, causing the static if (__traits(hasMember, S, "__xdtor")) branch to be permanently evaluated as false.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions