[Issue 15246] Destructor inheritance doesn't inherit attributes properly

via Digitalmars-d-bugs digitalmars-d-bugs at puremagic.com
Sun May 21 06:52:45 PDT 2017


https://issues.dlang.org/show_bug.cgi?id=15246

Stanislav Blinov <stanislav.blinov at gmail.com> changed:

           What    |Removed                     |Added
----------------------------------------------------------------------------
                 CC|                            |stanislav.blinov at gmail.com

--- Comment #12 from Stanislav Blinov <stanislav.blinov at gmail.com> ---
I agree with Andrei and Rainer. It should not matter in what order the runtime
calls the destructors. If it does call them, it calls all of them. So the net
observable effect should be as if all of them are called.
User code is not expected to call dtors explicitly. While we *can* (and should
be able to) do that, it should not allow attributes to conflict.
That means the strictest attribute set "wins", and derived class should not be
allowed to loosen the restrictions.

Effectively, the first class in the hierarchy to define a destructor has final
authority over the dtor attributes, and no derived classes after that can
define dtor with different attributes, or implicitly violate the attributes
(via members). This is not covariance, we're not looking at a virtual call.

If the class doesn't explicitly define a dtor, it should be inferred from
non-reference members (structs, fixed-size arrays, etc). As of right now,
members don't have any effect on the class dtor:

struct S { ~this() @nogc {} }

class A { S s; } // compiles
class B { S s;  ~this() {} } // compiles, but shouldn't

For a, ~this() @nogc should be inferred.
For B, it should be a compile-time error.

There should be a strict agreement on dtors throughout the hierarchy and within
each definition. Otherwise, we're free to violate attributes however we please.
For example, this also compiles (note, these are structs, not classes):

struct A { ~this() {} }
struct B { A a; ~this() @nogc {} }

But this function will not:

void snafu(B b) @nogc {}

Note the error message:
Error: @nogc function snafu cannot call non- at nogc destructor B.~this.

What??? So *there* it catches that B's dtor is not really @nogc!

While B's dtor in itself might not make any GC calls, destruction of B implies
destruction of all members. A's dtor is not @nogc, and so B's shouldn't be
@nogc.

Members that are reference types (classes, interfaces, dynamic arrays...)
should be excluded from that check, as user code is expected to explicitly
destruct them by calling the destroy() function, and so if users want to
destruct them with the object, they'd have to define a destructor, which in
turn will have to be checked against the destroy() calls made within:

class A { ~this() {} }

class B { A a; ~this() @nogc {} } // fine, a leaks (i.e. reliance on GC)
class C { A a; ~this() @nogc { destroy(a); } } // error, destroy() is not @nogc

class D : B { ~this() {} } // error, base dtor is @nogc

struct S { ~this() @nogc {} }

class E : A { S s; }  // error, E's dtor has to be @nogc, but A's dtor isn't

class F { ~this() @nogc {} }
class G { S s; F f; ~this() @nogc { destroy(f); } } // fine, S dtor is @nogc,
destroy(f) is inferred @nogc

Same goes for safety, purity, nothrow, etc:

class A { ~this() {} }

class B { A a; ~this() nothrow {} } // fine
class C { A a; ~this() { destroy(a); } } //error, destroy() may throw

class D { ~this() @safe pure {} }
class E : D { ~this() pure {} } // error, D.~this is @safe, E.~this should also
be @safe

...and so on.

We *could* allow covariance between @safe and @trusted, but once any of those
are in the hierarchy, @system dtors should be out the window.

rt_finalize does not need to change. destroy(obj) should statically typecheck
the hierarchy from obj up, cast a pointer to rt_finalize to a function with
appropriate set of attibutes, and call it. But we need to make sure that
rt_finalize doesn't perform any GC calls (it doesn't look like it does, the
monitor is not a GC-allocated object).

The only special cases here are if destroy(obj) is called with an Object (or an
interface, which just casts to Object anyway). For these, it looks like
destroy() should treat Object as if it was a class with this dtor:

~this() @system {}

If destroy() is called with an actual user-defined class, Object should be
ignored.

All of the above should ensure that destroy(obj) can safely infer attributes
from the hierarchy [typeof(obj), Object), as anything derived from typeof(obj)
is not allowed to violate those attributes.

--


More information about the Digitalmars-d-bugs mailing list