diff --git a/mak/DOCS b/mak/DOCS index c75fd66000..5f3b014c45 100644 --- a/mak/DOCS +++ b/mak/DOCS @@ -76,10 +76,6 @@ DOCS=\ $(DOCDIR)\core_sys_darwin_mach_thread_act.html \ $(DOCDIR)\core_sys_darwin_netinet_in_.html \ \ - $(DOCDIR)\core_internal_destruction.html \ - \ - $(DOCDIR)\core_internal_array_capacity.html \ - \ $(DOCDIR)\rt_aaA.html \ $(DOCDIR)\rt_aApply.html \ $(DOCDIR)\rt_aApplyR.html \ diff --git a/posix.mak b/posix.mak index c99aa02208..84cd21928c 100644 --- a/posix.mak +++ b/posix.mak @@ -174,12 +174,6 @@ $(DOCDIR)/core_sys_darwin_mach_%.html : src/core/sys/darwin/mach/%.d $(DMD) $(DOCDIR)/core_sys_darwin_netinet_%.html : src/core/sys/darwin/netinet/%.d $(DMD) $(DMD) $(DDOCFLAGS) -Df$@ project.ddoc $(DOCFMT) $< -$(DOCDIR)/core_internal_destruction.html : src/core/internal/destruction.d $(DMD) - $(DMD) $(DDOCFLAGS) -Df$@ project.ddoc $(DOCFMT) $< - -$(DOCDIR)/core_internal_array_capacity.html : src/core/internal/array/capacity.d $(DMD) - $(DMD) $(DDOCFLAGS) -Df$@ project.ddoc $(DOCFMT) $< - $(DOCDIR)/rt_%.html : src/rt/%.d $(DMD) $(DMD) $(DDOCFLAGS) -Df$@ project.ddoc $(DOCFMT) $< diff --git a/src/core/internal/array/capacity.d b/src/core/internal/array/capacity.d index fc06314aae..9440428ebd 100644 --- a/src/core/internal/array/capacity.d +++ b/src/core/internal/array/capacity.d @@ -9,193 +9,6 @@ */ module core.internal.array.capacity; -// HACK: This is a lie. `_d_arraysetcapacity` is neither `nothrow` nor `pure`, but this lie is -// necessary for now to prevent breaking code. -private extern (C) size_t _d_arraysetcapacity(const TypeInfo ti, size_t newcapacity, void[]* arrptr) pure nothrow; - -/** -(Property) Gets the current _capacity of a slice. The _capacity is the size -that the slice can grow to before the underlying array must be -reallocated or extended. - -If an append must reallocate a slice with no possibility of extension, then -`0` is returned. This happens when the slice references a static array, or -if another slice references elements past the end of the current slice. - -Note: The _capacity of a slice may be impacted by operations on other slices. -*/ -@property size_t capacity(T)(T[] arr) pure nothrow @trusted -{ - return _d_arraysetcapacity(typeid(T[]), 0, cast(void[]*)&arr); -} - -/// -@safe unittest -{ - //Static array slice: no capacity - int[4] sarray = [1, 2, 3, 4]; - int[] slice = sarray[]; - assert(sarray.capacity == 0); - //Appending to slice will reallocate to a new array - slice ~= 5; - assert(slice.capacity >= 5); - - //Dynamic array slices - int[] a = [1, 2, 3, 4]; - int[] b = a[1 .. $]; - int[] c = a[1 .. $ - 1]; - debug(SENTINEL) {} else // non-zero capacity very much depends on the array and GC implementation - { - assert(a.capacity != 0); - assert(a.capacity == b.capacity + 1); //both a and b share the same tail - } - assert(c.capacity == 0); //an append to c must relocate c. -} - -/** -Reserves capacity for a slice. The capacity is the size -that the slice can grow to before the underlying array must be -reallocated or extended. - -Returns: The new capacity of the array (which may be larger than -the requested capacity). -*/ -size_t reserve(T)(ref T[] arr, size_t newcapacity) pure nothrow @trusted -{ - if (__ctfe) - return newcapacity; - else - return _d_arraysetcapacity(typeid(T[]), newcapacity, cast(void[]*)&arr); -} - -/// -@safe unittest -{ - //Static array slice: no capacity. Reserve relocates. - int[4] sarray = [1, 2, 3, 4]; - int[] slice = sarray[]; - auto u = slice.reserve(8); - assert(u >= 8); - assert(&sarray[0] !is &slice[0]); - assert(slice.capacity == u); - - //Dynamic array slices - int[] a = [1, 2, 3, 4]; - a.reserve(8); //prepare a for appending 4 more items - auto p = &a[0]; - u = a.capacity; - a ~= [5, 6, 7, 8]; - assert(p == &a[0]); //a should not have been reallocated - assert(u == a.capacity); //a should not have been extended -} - -// https://issues.dlang.org/show_bug.cgi?id=12330, reserve() at CTFE time -@safe unittest -{ - int[] foo() { - int[] result; - auto a = result.reserve = 5; - assert(a == 5); - return result; - } - enum r = foo(); -} - -// Issue 6646: should be possible to use array.reserve from SafeD. -@safe unittest -{ - int[] a; - a.reserve(10); -} - -// HACK: This is a lie. `_d_arrayshrinkfit` is not `nothrow`, but this lie is necessary -// for now to prevent breaking code. -private extern (C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) nothrow; - -/** -Assume that it is safe to append to this array. Appends made to this array -after calling this function may append in place, even if the array was a -slice of a larger array to begin with. - -Use this only when it is certain there are no elements in use beyond the -array in the memory block. If there are, those elements will be -overwritten by appending to this array. - -Warning: Calling this function, and then using references to data located after the -given array results in undefined behavior. - -Returns: - The input is returned. -*/ -auto ref inout(T[]) assumeSafeAppend(T)(auto ref inout(T[]) arr) nothrow @system -{ - _d_arrayshrinkfit(typeid(T[]), *(cast(void[]*)&arr)); - return arr; -} - -/// -@system unittest -{ - int[] a = [1, 2, 3, 4]; - - // Without assumeSafeAppend. Appending relocates. - int[] b = a [0 .. 3]; - b ~= 5; - assert(a.ptr != b.ptr); - - debug(SENTINEL) {} else - { - // With assumeSafeAppend. Appending overwrites. - int[] c = a [0 .. 3]; - c.assumeSafeAppend() ~= 5; - assert(a.ptr == c.ptr); - } -} - -@system unittest -{ - int[] arr; - auto newcap = arr.reserve(2000); - assert(newcap >= 2000); - assert(newcap == arr.capacity); - auto ptr = arr.ptr; - foreach (i; 0..2000) - arr ~= i; - assert(ptr == arr.ptr); - arr = arr[0..1]; - arr.assumeSafeAppend(); - arr ~= 5; - assert(ptr == arr.ptr); -} - -@system unittest -{ - int[] arr = [1, 2, 3]; - void foo(ref int[] i) - { - i ~= 5; - } - arr = arr[0 .. 2]; - foo(assumeSafeAppend(arr)); //pass by ref - assert(arr[]==[1, 2, 5]); - arr = arr[0 .. 1].assumeSafeAppend(); //pass by value -} - -// https://issues.dlang.org/show_bug.cgi?id=10574 -@system unittest -{ - int[] a; - immutable(int[]) b; - auto a2 = &assumeSafeAppend(a); - auto b2 = &assumeSafeAppend(b); - auto a3 = assumeSafeAppend(a[]); - auto b3 = assumeSafeAppend(b[]); - assert(is(typeof(*a2) == int[])); - assert(is(typeof(*b2) == immutable(int[]))); - assert(is(typeof(a3) == int[])); - assert(is(typeof(b3) == immutable(int[]))); -} - // HACK: `nothrow` and `pure` is faked. private extern (C) void[] _d_arraysetlengthT(const TypeInfo ti, size_t newlength, void[]* p) nothrow pure; private extern (C) void[] _d_arraysetlengthiT(const TypeInfo ti, size_t newlength, void[]* p) nothrow pure; diff --git a/src/core/internal/destruction.d b/src/core/internal/destruction.d index 010fc1e207..c47dbb9b56 100644 --- a/src/core/internal/destruction.d +++ b/src/core/internal/destruction.d @@ -9,477 +9,6 @@ */ module core.internal.destruction; -private extern (C) void rt_finalize(void *data, bool det=true) nothrow; - -/** -Destroys the given object and optionally resets to initial state. It's used to -_destroy an object, calling its destructor or finalizer so it no longer -references any other objects. It does $(I not) initiate a GC cycle or free -any GC memory. -If `initialize` is supplied `false`, the object is considered invalid after -destruction, and should not be referenced. -*/ -void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct)) -{ - destructRecurse(obj); - - static if (initialize) - { - // We need to re-initialize `obj`. Previously, an immutable static - // and memcpy were used to hold an initializer. With improved unions, this is no longer - // needed. - union UntypedInit - { - T dummy; - } - static struct UntypedStorage - { - align(T.alignof) void[T.sizeof] dummy; - } - - () @trusted { - *cast(UntypedStorage*) &obj = cast(UntypedStorage) UntypedInit.init; - } (); - } -} - -@safe unittest -{ - struct A { string s = "A"; } - A a = {s: "B"}; - assert(a.s == "B"); - a.destroy; - assert(a.s == "A"); -} - -nothrow @safe @nogc unittest -{ - { - struct A { string s = "A"; } - A a; - a.s = "asd"; - destroy!false(a); - assert(a.s == "asd"); - destroy(a); - assert(a.s == "A"); - } - { - static int destroyed = 0; - struct C - { - string s = "C"; - ~this() nothrow @safe @nogc - { - destroyed ++; - } - } - - struct B - { - C c; - string s = "B"; - ~this() nothrow @safe @nogc - { - destroyed ++; - } - } - B a; - a.s = "asd"; - a.c.s = "jkl"; - destroy!false(a); - assert(destroyed == 2); - assert(a.s == "asd"); - assert(a.c.s == "jkl" ); - destroy(a); - assert(destroyed == 4); - assert(a.s == "B"); - assert(a.c.s == "C" ); - } -} - -/// ditto -void destroy(bool initialize = true, T)(T obj) if (is(T == class)) -{ - static if (__traits(getLinkage, T) == "C++") - { - static if (__traits(hasMember, T, "__xdtor")) - obj.__xdtor(); - - static if (initialize) - { - enum classSize = __traits(classInstanceSize, T); - (cast(void*)obj)[0 .. classSize] = typeid(T).initializer[]; - } - } - else - rt_finalize(cast(void*)obj); -} - -/// ditto -void destroy(bool initialize = true, T)(T obj) if (is(T == interface)) -{ - static assert(__traits(getLinkage, T) == "D", "Invalid call to destroy() on extern(" ~ __traits(getLinkage, T) ~ ") interface"); - - destroy!initialize(cast(Object)obj); -} - -/// Reference type demonstration -@system unittest -{ - class C - { - struct Agg - { - static int dtorCount; - - int x = 10; - ~this() { dtorCount++; } - } - - static int dtorCount; - - string s = "S"; - Agg a; - ~this() { dtorCount++; } - } - - C c = new C(); - assert(c.dtorCount == 0); // destructor not yet called - assert(c.s == "S"); // initial state `c.s` is `"S"` - assert(c.a.dtorCount == 0); // destructor not yet called - assert(c.a.x == 10); // initial state `c.a.x` is `10` - c.s = "T"; - c.a.x = 30; - assert(c.s == "T"); // `c.s` is `"T"` - destroy(c); - assert(c.dtorCount == 1); // `c`'s destructor was called - assert(c.s == "S"); // `c.s` is back to its inital state, `"S"` - assert(c.a.dtorCount == 1); // `c.a`'s destructor was called - assert(c.a.x == 10); // `c.a.x` is back to its inital state, `10` - - // check C++ classes work too! - extern (C++) class CPP - { - struct Agg - { - __gshared int dtorCount; - - int x = 10; - ~this() { dtorCount++; } - } - - __gshared int dtorCount; - - string s = "S"; - Agg a; - ~this() { dtorCount++; } - } - - CPP cpp = new CPP(); - assert(cpp.dtorCount == 0); // destructor not yet called - assert(cpp.s == "S"); // initial state `cpp.s` is `"S"` - assert(cpp.a.dtorCount == 0); // destructor not yet called - assert(cpp.a.x == 10); // initial state `cpp.a.x` is `10` - cpp.s = "T"; - cpp.a.x = 30; - assert(cpp.s == "T"); // `cpp.s` is `"T"` - destroy!false(cpp); // destroy without initialization - assert(cpp.dtorCount == 1); // `cpp`'s destructor was called - assert(cpp.s == "T"); // `cpp.s` is not initialized - assert(cpp.a.dtorCount == 1); // `cpp.a`'s destructor was called - assert(cpp.a.x == 30); // `cpp.a.x` is not initialized - destroy(cpp); - assert(cpp.dtorCount == 2); // `cpp`'s destructor was called again - assert(cpp.s == "S"); // `cpp.s` is back to its inital state, `"S"` - assert(cpp.a.dtorCount == 2); // `cpp.a`'s destructor was called again - assert(cpp.a.x == 10); // `cpp.a.x` is back to its inital state, `10` -} - -/// Value type demonstration -@safe unittest -{ - int i; - assert(i == 0); // `i`'s initial state is `0` - i = 1; - assert(i == 1); // `i` changed to `1` - destroy!false(i); - assert(i == 1); // `i` was not initialized - destroy(i); - assert(i == 0); // `i` is back to its initial state `0` -} - -@system unittest -{ - extern(C++) - static class C - { - void* ptr; - this() {} - } - - destroy!false(new C()); - destroy!true(new C()); -} - -@system unittest -{ - // class with an `alias this` - class A - { - static int dtorCount; - ~this() - { - dtorCount++; - } - } - - class B - { - A a; - alias a this; - this() - { - a = new A; - } - static int dtorCount; - ~this() - { - dtorCount++; - } - } - auto b = new B; - assert(A.dtorCount == 0); - assert(B.dtorCount == 0); - destroy(b); - assert(A.dtorCount == 0); - assert(B.dtorCount == 1); -} - -@system unittest -{ - interface I { } - { - class A: I { string s = "A"; this() {} } - auto a = new A, b = new A; - a.s = b.s = "asd"; - destroy(a); - assert(a.s == "A"); - - I i = b; - destroy(i); - assert(b.s == "A"); - } - { - static bool destroyed = false; - class B: I - { - string s = "B"; - this() {} - ~this() - { - destroyed = true; - } - } - auto a = new B, b = new B; - a.s = b.s = "asd"; - destroy(a); - assert(destroyed); - assert(a.s == "B"); - - destroyed = false; - I i = b; - destroy(i); - assert(destroyed); - assert(b.s == "B"); - } - // this test is invalid now that the default ctor is not run after clearing - version (none) - { - class C - { - string s; - this() - { - s = "C"; - } - } - auto a = new C; - a.s = "asd"; - destroy(a); - assert(a.s == "C"); - } -} - -nothrow @safe @nogc unittest -{ - { - struct A { string s = "A"; } - A a; - a.s = "asd"; - destroy!false(a); - assert(a.s == "asd"); - destroy(a); - assert(a.s == "A"); - } - { - static int destroyed = 0; - struct C - { - string s = "C"; - ~this() nothrow @safe @nogc - { - destroyed ++; - } - } - - struct B - { - C c; - string s = "B"; - ~this() nothrow @safe @nogc - { - destroyed ++; - } - } - B a; - a.s = "asd"; - a.c.s = "jkl"; - destroy!false(a); - assert(destroyed == 2); - assert(a.s == "asd"); - assert(a.c.s == "jkl" ); - destroy(a); - assert(destroyed == 4); - assert(a.s == "B"); - assert(a.c.s == "C" ); - } -} - -nothrow unittest -{ - // Bugzilla 20049: Test to ensure proper behavior of `nothrow` destructors - class C - { - static int dtorCount = 0; - this() nothrow {} - ~this() nothrow { dtorCount++; } - } - - auto c = new C; - destroy(c); - assert(C.dtorCount == 1); -} - -/// ditto -void destroy(bool initialize = true, T : U[n], U, size_t n)(ref T obj) if (!is(T == struct)) -{ - foreach_reverse (ref e; obj[]) - destroy!initialize(e); -} - -@safe unittest -{ - int[2] a; - a[0] = 1; - a[1] = 2; - destroy!false(a); - assert(a == [ 1, 2 ]); - destroy(a); - assert(a == [ 0, 0 ]); -} - -@safe unittest -{ - static struct vec2f { - float[2] values; - alias values this; - } - - vec2f v; - destroy!(true, vec2f)(v); -} - -@system unittest -{ - // Bugzilla 15009 - static string op; - static struct S - { - int x; - this(int x) { op ~= "C" ~ cast(char)('0'+x); this.x = x; } - this(this) { op ~= "P" ~ cast(char)('0'+x); } - ~this() { op ~= "D" ~ cast(char)('0'+x); } - } - - { - S[2] a1 = [S(1), S(2)]; - op = ""; - } - assert(op == "D2D1"); // built-in scope destruction - { - S[2] a1 = [S(1), S(2)]; - op = ""; - destroy(a1); - assert(op == "D2D1"); // consistent with built-in behavior - } - - { - S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; - op = ""; - } - assert(op == "D4D3D2D1"); - { - S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; - op = ""; - destroy(a2); - assert(op == "D4D3D2D1", op); - } -} - -/// ditto -void destroy(bool initialize = true, T)(ref T obj) - if (!is(T == struct) && !is(T == interface) && !is(T == class) && !__traits(isStaticArray, T)) -{ - static if (initialize) - obj = T.init; -} - -@safe unittest -{ - { - int a = 42; - destroy!false(a); - assert(a == 42); - destroy(a); - assert(a == 0); - } - { - float a = 42; - destroy!false(a); - assert(a == 42); - destroy(a); - assert(a != a); // isnan - } -} - -@safe unittest -{ - // Bugzilla 14746 - static struct HasDtor - { - ~this() { assert(0); } - } - static struct Owner - { - HasDtor* ptr; - alias ptr this; - } - - Owner o; - assert(o.ptr is null); - destroy(o); // must not reach in HasDtor.__dtor() -} - // compiler frontend lowers dynamic array deconstruction to this void __ArrayDtor(T)(T[] a) { @@ -487,7 +16,7 @@ void __ArrayDtor(T)(T[] a) e.__xdtor(); } -package void destructRecurse(E, size_t n)(ref E[n] arr) +public void destructRecurse(E, size_t n)(ref E[n] arr) { import core.internal.traits : hasElaborateDestructor; @@ -498,7 +27,7 @@ package void destructRecurse(E, size_t n)(ref E[n] arr) } } -package void destructRecurse(S)(ref S s) +public void destructRecurse(S)(ref S s) if (is(S == struct)) { static if (__traits(hasMember, S, "__xdtor") && diff --git a/src/object.d b/src/object.d index 464984be28..2bdabca390 100644 --- a/src/object.d +++ b/src/object.d @@ -2110,16 +2110,6 @@ class Error : Throwable } } -/// See $(REF capacity, core,internal,array,capacity) -public import core.internal.array.capacity: capacity; -/// See $(REF reserve, core,internal,array,capacity) -public import core.internal.array.capacity: reserve; -/// See $(REF assumeSafeAppend, core,internal,array,capacity) -public import core.internal.array.capacity: assumeSafeAppend; - -/// See $(REF destroy, core,internal,destruction) -public import core.internal.destruction: destroy; - extern (C) { // from druntime/src/rt/aaA.d @@ -2988,6 +2978,193 @@ private U[] _dup(T, U)(T[] a) // pure nothrow depends on postblit return res; } +// HACK: This is a lie. `_d_arraysetcapacity` is neither `nothrow` nor `pure`, but this lie is +// necessary for now to prevent breaking code. +private extern (C) size_t _d_arraysetcapacity(const TypeInfo ti, size_t newcapacity, void[]* arrptr) pure nothrow; + +/** +(Property) Gets the current _capacity of a slice. The _capacity is the size +that the slice can grow to before the underlying array must be +reallocated or extended. + +If an append must reallocate a slice with no possibility of extension, then +`0` is returned. This happens when the slice references a static array, or +if another slice references elements past the end of the current slice. + +Note: The _capacity of a slice may be impacted by operations on other slices. +*/ +@property size_t capacity(T)(T[] arr) pure nothrow @trusted +{ + return _d_arraysetcapacity(typeid(T[]), 0, cast(void[]*)&arr); +} + +/// +@safe unittest +{ + //Static array slice: no capacity + int[4] sarray = [1, 2, 3, 4]; + int[] slice = sarray[]; + assert(sarray.capacity == 0); + //Appending to slice will reallocate to a new array + slice ~= 5; + assert(slice.capacity >= 5); + + //Dynamic array slices + int[] a = [1, 2, 3, 4]; + int[] b = a[1 .. $]; + int[] c = a[1 .. $ - 1]; + debug(SENTINEL) {} else // non-zero capacity very much depends on the array and GC implementation + { + assert(a.capacity != 0); + assert(a.capacity == b.capacity + 1); //both a and b share the same tail + } + assert(c.capacity == 0); //an append to c must relocate c. +} + +/** +Reserves capacity for a slice. The capacity is the size +that the slice can grow to before the underlying array must be +reallocated or extended. + +Returns: The new capacity of the array (which may be larger than +the requested capacity). +*/ +size_t reserve(T)(ref T[] arr, size_t newcapacity) pure nothrow @trusted +{ + if (__ctfe) + return newcapacity; + else + return _d_arraysetcapacity(typeid(T[]), newcapacity, cast(void[]*)&arr); +} + +/// +@safe unittest +{ + //Static array slice: no capacity. Reserve relocates. + int[4] sarray = [1, 2, 3, 4]; + int[] slice = sarray[]; + auto u = slice.reserve(8); + assert(u >= 8); + assert(&sarray[0] !is &slice[0]); + assert(slice.capacity == u); + + //Dynamic array slices + int[] a = [1, 2, 3, 4]; + a.reserve(8); //prepare a for appending 4 more items + auto p = &a[0]; + u = a.capacity; + a ~= [5, 6, 7, 8]; + assert(p == &a[0]); //a should not have been reallocated + assert(u == a.capacity); //a should not have been extended +} + +// https://issues.dlang.org/show_bug.cgi?id=12330, reserve() at CTFE time +@safe unittest +{ + int[] foo() { + int[] result; + auto a = result.reserve = 5; + assert(a == 5); + return result; + } + enum r = foo(); +} + +// Issue 6646: should be possible to use array.reserve from SafeD. +@safe unittest +{ + int[] a; + a.reserve(10); +} + +// HACK: This is a lie. `_d_arrayshrinkfit` is not `nothrow`, but this lie is necessary +// for now to prevent breaking code. +private extern (C) void _d_arrayshrinkfit(const TypeInfo ti, void[] arr) nothrow; + +/** +Assume that it is safe to append to this array. Appends made to this array +after calling this function may append in place, even if the array was a +slice of a larger array to begin with. + +Use this only when it is certain there are no elements in use beyond the +array in the memory block. If there are, those elements will be +overwritten by appending to this array. + +Warning: Calling this function, and then using references to data located after the +given array results in undefined behavior. + +Returns: + The input is returned. +*/ +auto ref inout(T[]) assumeSafeAppend(T)(auto ref inout(T[]) arr) nothrow @system +{ + _d_arrayshrinkfit(typeid(T[]), *(cast(void[]*)&arr)); + return arr; +} + +/// +@system unittest +{ + int[] a = [1, 2, 3, 4]; + + // Without assumeSafeAppend. Appending relocates. + int[] b = a [0 .. 3]; + b ~= 5; + assert(a.ptr != b.ptr); + + debug(SENTINEL) {} else + { + // With assumeSafeAppend. Appending overwrites. + int[] c = a [0 .. 3]; + c.assumeSafeAppend() ~= 5; + assert(a.ptr == c.ptr); + } +} + +@system unittest +{ + int[] arr; + auto newcap = arr.reserve(2000); + assert(newcap >= 2000); + assert(newcap == arr.capacity); + auto ptr = arr.ptr; + foreach (i; 0..2000) + arr ~= i; + assert(ptr == arr.ptr); + arr = arr[0..1]; + arr.assumeSafeAppend(); + arr ~= 5; + assert(ptr == arr.ptr); +} + +@system unittest +{ + int[] arr = [1, 2, 3]; + void foo(ref int[] i) + { + i ~= 5; + } + arr = arr[0 .. 2]; + foo(assumeSafeAppend(arr)); //pass by ref + assert(arr[]==[1, 2, 5]); + arr = arr[0 .. 1].assumeSafeAppend(); //pass by value +} + +// https://issues.dlang.org/show_bug.cgi?id=10574 +@system unittest +{ + int[] a; + immutable(int[]) b; + auto a2 = &assumeSafeAppend(a); + auto b2 = &assumeSafeAppend(b); + auto a3 = assumeSafeAppend(a[]); + auto b3 = assumeSafeAppend(b[]); + assert(is(typeof(*a2) == int[])); + assert(is(typeof(*b2) == immutable(int[]))); + assert(is(typeof(a3) == int[])); + assert(is(typeof(b3) == immutable(int[]))); +} + private extern (C) void[] _d_newarrayU(const TypeInfo ti, size_t length) pure nothrow; @@ -3173,6 +3350,479 @@ private void _doPostblit(T)(T[] arr) auto a = arr.dup; // dup does escape } +/** +Destroys the given object and optionally resets to initial state. It's used to +_destroy an object, calling its destructor or finalizer so it no longer +references any other objects. It does $(I not) initiate a GC cycle or free +any GC memory. +If `initialize` is supplied `false`, the object is considered invalid after +destruction, and should not be referenced. +*/ +void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct)) +{ + import core.internal.destruction : destructRecurse; + + destructRecurse(obj); + + static if (initialize) + { + // We need to re-initialize `obj`. Previously, an immutable static + // and memcpy were used to hold an initializer. With improved unions, this is no longer + // needed. + union UntypedInit + { + T dummy; + } + static struct UntypedStorage + { + align(T.alignof) void[T.sizeof] dummy; + } + + () @trusted { + *cast(UntypedStorage*) &obj = cast(UntypedStorage) UntypedInit.init; + } (); + } +} + +@safe unittest +{ + struct A { string s = "A"; } + A a = {s: "B"}; + assert(a.s == "B"); + a.destroy; + assert(a.s == "A"); +} + +nothrow @safe @nogc unittest +{ + { + struct A { string s = "A"; } + A a; + a.s = "asd"; + destroy!false(a); + assert(a.s == "asd"); + destroy(a); + assert(a.s == "A"); + } + { + static int destroyed = 0; + struct C + { + string s = "C"; + ~this() nothrow @safe @nogc + { + destroyed ++; + } + } + + struct B + { + C c; + string s = "B"; + ~this() nothrow @safe @nogc + { + destroyed ++; + } + } + B a; + a.s = "asd"; + a.c.s = "jkl"; + destroy!false(a); + assert(destroyed == 2); + assert(a.s == "asd"); + assert(a.c.s == "jkl" ); + destroy(a); + assert(destroyed == 4); + assert(a.s == "B"); + assert(a.c.s == "C" ); + } +} + +private extern (C) void rt_finalize(void *data, bool det=true) nothrow; + +/// ditto +void destroy(bool initialize = true, T)(T obj) if (is(T == class)) +{ + static if (__traits(getLinkage, T) == "C++") + { + static if (__traits(hasMember, T, "__xdtor")) + obj.__xdtor(); + + static if (initialize) + { + enum classSize = __traits(classInstanceSize, T); + (cast(void*)obj)[0 .. classSize] = typeid(T).initializer[]; + } + } + else + rt_finalize(cast(void*)obj); +} + +/// ditto +void destroy(bool initialize = true, T)(T obj) if (is(T == interface)) +{ + static assert(__traits(getLinkage, T) == "D", "Invalid call to destroy() on extern(" ~ __traits(getLinkage, T) ~ ") interface"); + + destroy!initialize(cast(Object)obj); +} + +/// Reference type demonstration +@system unittest +{ + class C + { + struct Agg + { + static int dtorCount; + + int x = 10; + ~this() { dtorCount++; } + } + + static int dtorCount; + + string s = "S"; + Agg a; + ~this() { dtorCount++; } + } + + C c = new C(); + assert(c.dtorCount == 0); // destructor not yet called + assert(c.s == "S"); // initial state `c.s` is `"S"` + assert(c.a.dtorCount == 0); // destructor not yet called + assert(c.a.x == 10); // initial state `c.a.x` is `10` + c.s = "T"; + c.a.x = 30; + assert(c.s == "T"); // `c.s` is `"T"` + destroy(c); + assert(c.dtorCount == 1); // `c`'s destructor was called + assert(c.s == "S"); // `c.s` is back to its inital state, `"S"` + assert(c.a.dtorCount == 1); // `c.a`'s destructor was called + assert(c.a.x == 10); // `c.a.x` is back to its inital state, `10` + + // check C++ classes work too! + extern (C++) class CPP + { + struct Agg + { + __gshared int dtorCount; + + int x = 10; + ~this() { dtorCount++; } + } + + __gshared int dtorCount; + + string s = "S"; + Agg a; + ~this() { dtorCount++; } + } + + CPP cpp = new CPP(); + assert(cpp.dtorCount == 0); // destructor not yet called + assert(cpp.s == "S"); // initial state `cpp.s` is `"S"` + assert(cpp.a.dtorCount == 0); // destructor not yet called + assert(cpp.a.x == 10); // initial state `cpp.a.x` is `10` + cpp.s = "T"; + cpp.a.x = 30; + assert(cpp.s == "T"); // `cpp.s` is `"T"` + destroy!false(cpp); // destroy without initialization + assert(cpp.dtorCount == 1); // `cpp`'s destructor was called + assert(cpp.s == "T"); // `cpp.s` is not initialized + assert(cpp.a.dtorCount == 1); // `cpp.a`'s destructor was called + assert(cpp.a.x == 30); // `cpp.a.x` is not initialized + destroy(cpp); + assert(cpp.dtorCount == 2); // `cpp`'s destructor was called again + assert(cpp.s == "S"); // `cpp.s` is back to its inital state, `"S"` + assert(cpp.a.dtorCount == 2); // `cpp.a`'s destructor was called again + assert(cpp.a.x == 10); // `cpp.a.x` is back to its inital state, `10` +} + +/// Value type demonstration +@safe unittest +{ + int i; + assert(i == 0); // `i`'s initial state is `0` + i = 1; + assert(i == 1); // `i` changed to `1` + destroy!false(i); + assert(i == 1); // `i` was not initialized + destroy(i); + assert(i == 0); // `i` is back to its initial state `0` +} + +@system unittest +{ + extern(C++) + static class C + { + void* ptr; + this() {} + } + + destroy!false(new C()); + destroy!true(new C()); +} + +@system unittest +{ + // class with an `alias this` + class A + { + static int dtorCount; + ~this() + { + dtorCount++; + } + } + + class B + { + A a; + alias a this; + this() + { + a = new A; + } + static int dtorCount; + ~this() + { + dtorCount++; + } + } + auto b = new B; + assert(A.dtorCount == 0); + assert(B.dtorCount == 0); + destroy(b); + assert(A.dtorCount == 0); + assert(B.dtorCount == 1); +} + +@system unittest +{ + interface I { } + { + class A: I { string s = "A"; this() {} } + auto a = new A, b = new A; + a.s = b.s = "asd"; + destroy(a); + assert(a.s == "A"); + + I i = b; + destroy(i); + assert(b.s == "A"); + } + { + static bool destroyed = false; + class B: I + { + string s = "B"; + this() {} + ~this() + { + destroyed = true; + } + } + auto a = new B, b = new B; + a.s = b.s = "asd"; + destroy(a); + assert(destroyed); + assert(a.s == "B"); + + destroyed = false; + I i = b; + destroy(i); + assert(destroyed); + assert(b.s == "B"); + } + // this test is invalid now that the default ctor is not run after clearing + version (none) + { + class C + { + string s; + this() + { + s = "C"; + } + } + auto a = new C; + a.s = "asd"; + destroy(a); + assert(a.s == "C"); + } +} + +nothrow @safe @nogc unittest +{ + { + struct A { string s = "A"; } + A a; + a.s = "asd"; + destroy!false(a); + assert(a.s == "asd"); + destroy(a); + assert(a.s == "A"); + } + { + static int destroyed = 0; + struct C + { + string s = "C"; + ~this() nothrow @safe @nogc + { + destroyed ++; + } + } + + struct B + { + C c; + string s = "B"; + ~this() nothrow @safe @nogc + { + destroyed ++; + } + } + B a; + a.s = "asd"; + a.c.s = "jkl"; + destroy!false(a); + assert(destroyed == 2); + assert(a.s == "asd"); + assert(a.c.s == "jkl" ); + destroy(a); + assert(destroyed == 4); + assert(a.s == "B"); + assert(a.c.s == "C" ); + } +} + +nothrow unittest +{ + // Bugzilla 20049: Test to ensure proper behavior of `nothrow` destructors + class C + { + static int dtorCount = 0; + this() nothrow {} + ~this() nothrow { dtorCount++; } + } + + auto c = new C; + destroy(c); + assert(C.dtorCount == 1); +} + +/// ditto +void destroy(bool initialize = true, T : U[n], U, size_t n)(ref T obj) if (!is(T == struct)) +{ + foreach_reverse (ref e; obj[]) + destroy!initialize(e); +} + +@safe unittest +{ + int[2] a; + a[0] = 1; + a[1] = 2; + destroy!false(a); + assert(a == [ 1, 2 ]); + destroy(a); + assert(a == [ 0, 0 ]); +} + +@safe unittest +{ + static struct vec2f { + float[2] values; + alias values this; + } + + vec2f v; + destroy!(true, vec2f)(v); +} + +@system unittest +{ + // Bugzilla 15009 + static string op; + static struct S + { + int x; + this(int x) { op ~= "C" ~ cast(char)('0'+x); this.x = x; } + this(this) { op ~= "P" ~ cast(char)('0'+x); } + ~this() { op ~= "D" ~ cast(char)('0'+x); } + } + + { + S[2] a1 = [S(1), S(2)]; + op = ""; + } + assert(op == "D2D1"); // built-in scope destruction + { + S[2] a1 = [S(1), S(2)]; + op = ""; + destroy(a1); + assert(op == "D2D1"); // consistent with built-in behavior + } + + { + S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; + op = ""; + } + assert(op == "D4D3D2D1"); + { + S[2][2] a2 = [[S(1), S(2)], [S(3), S(4)]]; + op = ""; + destroy(a2); + assert(op == "D4D3D2D1", op); + } +} + +/// ditto +void destroy(bool initialize = true, T)(ref T obj) + if (!is(T == struct) && !is(T == interface) && !is(T == class) && !__traits(isStaticArray, T)) +{ + static if (initialize) + obj = T.init; +} + +@safe unittest +{ + { + int a = 42; + destroy!false(a); + assert(a == 42); + destroy(a); + assert(a == 0); + } + { + float a = 42; + destroy!false(a); + assert(a == 42); + destroy(a); + assert(a != a); // isnan + } +} + +@safe unittest +{ + // Bugzilla 14746 + static struct HasDtor + { + ~this() { assert(0); } + } + static struct Owner + { + HasDtor* ptr; + alias ptr this; + } + + Owner o; + assert(o.ptr is null); + destroy(o); // must not reach in HasDtor.__dtor() +} + /* ************************************************************************ COMPILER SUPPORT The compiler lowers certain expressions to instantiations of the following