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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions