https://issues.dlang.org/show_bug.cgi?id=15246
Stanislav Blinov <[email protected]> changed: What |Removed |Added ---------------------------------------------------------------------------- CC| |[email protected] --- Comment #12 from Stanislav Blinov <[email protected]> --- 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-@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. --
